agno 2.2.13__py3-none-any.whl → 2.4.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agno/agent/__init__.py +6 -0
- agno/agent/agent.py +5252 -3145
- agno/agent/remote.py +525 -0
- agno/api/api.py +2 -0
- agno/client/__init__.py +3 -0
- agno/client/a2a/__init__.py +10 -0
- agno/client/a2a/client.py +554 -0
- agno/client/a2a/schemas.py +112 -0
- agno/client/a2a/utils.py +369 -0
- agno/client/os.py +2669 -0
- agno/compression/__init__.py +3 -0
- agno/compression/manager.py +247 -0
- agno/culture/manager.py +2 -2
- agno/db/base.py +927 -6
- agno/db/dynamo/dynamo.py +788 -2
- agno/db/dynamo/schemas.py +128 -0
- agno/db/dynamo/utils.py +26 -3
- agno/db/firestore/firestore.py +674 -50
- agno/db/firestore/schemas.py +41 -0
- agno/db/firestore/utils.py +25 -10
- agno/db/gcs_json/gcs_json_db.py +506 -3
- agno/db/gcs_json/utils.py +14 -2
- agno/db/in_memory/in_memory_db.py +203 -4
- agno/db/in_memory/utils.py +14 -2
- agno/db/json/json_db.py +498 -2
- agno/db/json/utils.py +14 -2
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/utils.py +19 -0
- agno/db/migrations/v1_to_v2.py +54 -16
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +977 -0
- agno/db/mongo/async_mongo.py +1013 -39
- agno/db/mongo/mongo.py +684 -4
- agno/db/mongo/schemas.py +48 -0
- agno/db/mongo/utils.py +17 -0
- agno/db/mysql/__init__.py +2 -1
- agno/db/mysql/async_mysql.py +2958 -0
- agno/db/mysql/mysql.py +722 -53
- agno/db/mysql/schemas.py +77 -11
- agno/db/mysql/utils.py +151 -8
- agno/db/postgres/async_postgres.py +1254 -137
- agno/db/postgres/postgres.py +2316 -93
- agno/db/postgres/schemas.py +153 -21
- agno/db/postgres/utils.py +22 -7
- agno/db/redis/redis.py +531 -3
- agno/db/redis/schemas.py +36 -0
- agno/db/redis/utils.py +31 -15
- agno/db/schemas/evals.py +1 -0
- agno/db/schemas/memory.py +20 -9
- agno/db/singlestore/schemas.py +70 -1
- agno/db/singlestore/singlestore.py +737 -74
- agno/db/singlestore/utils.py +13 -3
- agno/db/sqlite/async_sqlite.py +1069 -89
- agno/db/sqlite/schemas.py +133 -1
- agno/db/sqlite/sqlite.py +2203 -165
- agno/db/sqlite/utils.py +21 -11
- agno/db/surrealdb/models.py +25 -0
- agno/db/surrealdb/surrealdb.py +603 -1
- agno/db/utils.py +60 -0
- agno/eval/__init__.py +26 -3
- agno/eval/accuracy.py +25 -12
- agno/eval/agent_as_judge.py +871 -0
- agno/eval/base.py +29 -0
- agno/eval/performance.py +10 -4
- agno/eval/reliability.py +22 -13
- agno/eval/utils.py +2 -1
- agno/exceptions.py +42 -0
- agno/hooks/__init__.py +3 -0
- agno/hooks/decorator.py +164 -0
- agno/integrations/discord/client.py +13 -2
- agno/knowledge/__init__.py +4 -0
- agno/knowledge/chunking/code.py +90 -0
- agno/knowledge/chunking/document.py +65 -4
- agno/knowledge/chunking/fixed.py +4 -1
- agno/knowledge/chunking/markdown.py +102 -11
- agno/knowledge/chunking/recursive.py +2 -2
- agno/knowledge/chunking/semantic.py +130 -48
- agno/knowledge/chunking/strategy.py +18 -0
- agno/knowledge/embedder/azure_openai.py +0 -1
- agno/knowledge/embedder/google.py +1 -1
- agno/knowledge/embedder/mistral.py +1 -1
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/openai.py +16 -12
- agno/knowledge/filesystem.py +412 -0
- agno/knowledge/knowledge.py +4261 -1199
- agno/knowledge/protocol.py +134 -0
- agno/knowledge/reader/arxiv_reader.py +3 -2
- agno/knowledge/reader/base.py +9 -7
- agno/knowledge/reader/csv_reader.py +91 -42
- agno/knowledge/reader/docx_reader.py +9 -10
- agno/knowledge/reader/excel_reader.py +225 -0
- agno/knowledge/reader/field_labeled_csv_reader.py +38 -48
- agno/knowledge/reader/firecrawl_reader.py +3 -2
- agno/knowledge/reader/json_reader.py +16 -22
- agno/knowledge/reader/markdown_reader.py +15 -14
- agno/knowledge/reader/pdf_reader.py +33 -28
- agno/knowledge/reader/pptx_reader.py +9 -10
- agno/knowledge/reader/reader_factory.py +135 -1
- agno/knowledge/reader/s3_reader.py +8 -16
- agno/knowledge/reader/tavily_reader.py +3 -3
- agno/knowledge/reader/text_reader.py +15 -14
- agno/knowledge/reader/utils/__init__.py +17 -0
- agno/knowledge/reader/utils/spreadsheet.py +114 -0
- agno/knowledge/reader/web_search_reader.py +8 -65
- agno/knowledge/reader/website_reader.py +16 -13
- agno/knowledge/reader/wikipedia_reader.py +36 -3
- agno/knowledge/reader/youtube_reader.py +3 -2
- agno/knowledge/remote_content/__init__.py +33 -0
- agno/knowledge/remote_content/config.py +266 -0
- agno/knowledge/remote_content/remote_content.py +105 -17
- agno/knowledge/utils.py +76 -22
- agno/learn/__init__.py +71 -0
- agno/learn/config.py +463 -0
- agno/learn/curate.py +185 -0
- agno/learn/machine.py +725 -0
- agno/learn/schemas.py +1114 -0
- agno/learn/stores/__init__.py +38 -0
- agno/learn/stores/decision_log.py +1156 -0
- agno/learn/stores/entity_memory.py +3275 -0
- agno/learn/stores/learned_knowledge.py +1583 -0
- agno/learn/stores/protocol.py +117 -0
- agno/learn/stores/session_context.py +1217 -0
- agno/learn/stores/user_memory.py +1495 -0
- agno/learn/stores/user_profile.py +1220 -0
- agno/learn/utils.py +209 -0
- agno/media.py +22 -6
- agno/memory/__init__.py +14 -1
- agno/memory/manager.py +223 -8
- agno/memory/strategies/__init__.py +15 -0
- agno/memory/strategies/base.py +66 -0
- agno/memory/strategies/summarize.py +196 -0
- agno/memory/strategies/types.py +37 -0
- agno/models/aimlapi/aimlapi.py +17 -0
- agno/models/anthropic/claude.py +434 -59
- agno/models/aws/bedrock.py +121 -20
- agno/models/aws/claude.py +131 -274
- agno/models/azure/ai_foundry.py +10 -6
- agno/models/azure/openai_chat.py +33 -10
- agno/models/base.py +1162 -561
- agno/models/cerebras/cerebras.py +120 -24
- agno/models/cerebras/cerebras_openai.py +21 -2
- agno/models/cohere/chat.py +65 -6
- agno/models/cometapi/cometapi.py +18 -1
- agno/models/dashscope/dashscope.py +2 -3
- agno/models/deepinfra/deepinfra.py +18 -1
- agno/models/deepseek/deepseek.py +69 -3
- agno/models/fireworks/fireworks.py +18 -1
- agno/models/google/gemini.py +959 -89
- agno/models/google/utils.py +22 -0
- agno/models/groq/groq.py +48 -18
- agno/models/huggingface/huggingface.py +17 -6
- agno/models/ibm/watsonx.py +16 -6
- agno/models/internlm/internlm.py +18 -1
- agno/models/langdb/langdb.py +13 -1
- agno/models/litellm/chat.py +88 -9
- agno/models/litellm/litellm_openai.py +18 -1
- agno/models/message.py +24 -5
- agno/models/meta/llama.py +40 -13
- agno/models/meta/llama_openai.py +22 -21
- agno/models/metrics.py +12 -0
- agno/models/mistral/mistral.py +8 -4
- agno/models/n1n/__init__.py +3 -0
- agno/models/n1n/n1n.py +57 -0
- agno/models/nebius/nebius.py +6 -7
- agno/models/nvidia/nvidia.py +20 -3
- agno/models/ollama/__init__.py +2 -0
- agno/models/ollama/chat.py +17 -6
- agno/models/ollama/responses.py +100 -0
- agno/models/openai/__init__.py +2 -0
- agno/models/openai/chat.py +117 -26
- agno/models/openai/open_responses.py +46 -0
- agno/models/openai/responses.py +110 -32
- agno/models/openrouter/__init__.py +2 -0
- agno/models/openrouter/openrouter.py +67 -2
- agno/models/openrouter/responses.py +146 -0
- agno/models/perplexity/perplexity.py +19 -1
- agno/models/portkey/portkey.py +7 -6
- agno/models/requesty/requesty.py +19 -2
- agno/models/response.py +20 -2
- agno/models/sambanova/sambanova.py +20 -3
- agno/models/siliconflow/siliconflow.py +19 -2
- agno/models/together/together.py +20 -3
- agno/models/vercel/v0.py +20 -3
- agno/models/vertexai/claude.py +124 -4
- agno/models/vllm/vllm.py +19 -14
- agno/models/xai/xai.py +19 -2
- agno/os/app.py +467 -137
- agno/os/auth.py +253 -5
- agno/os/config.py +22 -0
- agno/os/interfaces/a2a/a2a.py +7 -6
- agno/os/interfaces/a2a/router.py +635 -26
- agno/os/interfaces/a2a/utils.py +32 -33
- agno/os/interfaces/agui/agui.py +5 -3
- agno/os/interfaces/agui/router.py +26 -16
- agno/os/interfaces/agui/utils.py +97 -57
- agno/os/interfaces/base.py +7 -7
- agno/os/interfaces/slack/router.py +16 -7
- agno/os/interfaces/slack/slack.py +7 -7
- agno/os/interfaces/whatsapp/router.py +35 -7
- agno/os/interfaces/whatsapp/security.py +3 -1
- agno/os/interfaces/whatsapp/whatsapp.py +11 -8
- agno/os/managers.py +326 -0
- agno/os/mcp.py +652 -79
- agno/os/middleware/__init__.py +4 -0
- agno/os/middleware/jwt.py +718 -115
- agno/os/middleware/trailing_slash.py +27 -0
- agno/os/router.py +105 -1558
- agno/os/routers/agents/__init__.py +3 -0
- agno/os/routers/agents/router.py +655 -0
- agno/os/routers/agents/schema.py +288 -0
- agno/os/routers/components/__init__.py +3 -0
- agno/os/routers/components/components.py +475 -0
- agno/os/routers/database.py +155 -0
- agno/os/routers/evals/evals.py +111 -18
- agno/os/routers/evals/schemas.py +38 -5
- agno/os/routers/evals/utils.py +80 -11
- agno/os/routers/health.py +3 -3
- agno/os/routers/knowledge/knowledge.py +284 -35
- agno/os/routers/knowledge/schemas.py +14 -2
- agno/os/routers/memory/memory.py +274 -11
- agno/os/routers/memory/schemas.py +44 -3
- agno/os/routers/metrics/metrics.py +30 -15
- agno/os/routers/metrics/schemas.py +10 -6
- agno/os/routers/registry/__init__.py +3 -0
- agno/os/routers/registry/registry.py +337 -0
- agno/os/routers/session/session.py +143 -14
- agno/os/routers/teams/__init__.py +3 -0
- agno/os/routers/teams/router.py +550 -0
- agno/os/routers/teams/schema.py +280 -0
- agno/os/routers/traces/__init__.py +3 -0
- agno/os/routers/traces/schemas.py +414 -0
- agno/os/routers/traces/traces.py +549 -0
- agno/os/routers/workflows/__init__.py +3 -0
- agno/os/routers/workflows/router.py +757 -0
- agno/os/routers/workflows/schema.py +139 -0
- agno/os/schema.py +157 -584
- agno/os/scopes.py +469 -0
- agno/os/settings.py +3 -0
- agno/os/utils.py +574 -185
- agno/reasoning/anthropic.py +85 -1
- agno/reasoning/azure_ai_foundry.py +93 -1
- agno/reasoning/deepseek.py +102 -2
- agno/reasoning/default.py +6 -7
- agno/reasoning/gemini.py +87 -3
- agno/reasoning/groq.py +109 -2
- agno/reasoning/helpers.py +6 -7
- agno/reasoning/manager.py +1238 -0
- agno/reasoning/ollama.py +93 -1
- agno/reasoning/openai.py +115 -1
- agno/reasoning/vertexai.py +85 -1
- agno/registry/__init__.py +3 -0
- agno/registry/registry.py +68 -0
- agno/remote/__init__.py +3 -0
- agno/remote/base.py +581 -0
- agno/run/__init__.py +2 -4
- agno/run/agent.py +134 -19
- agno/run/base.py +49 -1
- agno/run/cancel.py +65 -52
- agno/run/cancellation_management/__init__.py +9 -0
- agno/run/cancellation_management/base.py +78 -0
- agno/run/cancellation_management/in_memory_cancellation_manager.py +100 -0
- agno/run/cancellation_management/redis_cancellation_manager.py +236 -0
- agno/run/requirement.py +181 -0
- agno/run/team.py +111 -19
- agno/run/workflow.py +2 -1
- agno/session/agent.py +57 -92
- agno/session/summary.py +1 -1
- agno/session/team.py +62 -115
- agno/session/workflow.py +353 -57
- agno/skills/__init__.py +17 -0
- agno/skills/agent_skills.py +377 -0
- agno/skills/errors.py +32 -0
- agno/skills/loaders/__init__.py +4 -0
- agno/skills/loaders/base.py +27 -0
- agno/skills/loaders/local.py +216 -0
- agno/skills/skill.py +65 -0
- agno/skills/utils.py +107 -0
- agno/skills/validator.py +277 -0
- agno/table.py +10 -0
- agno/team/__init__.py +5 -1
- agno/team/remote.py +447 -0
- agno/team/team.py +3769 -2202
- agno/tools/brandfetch.py +27 -18
- agno/tools/browserbase.py +225 -16
- agno/tools/crawl4ai.py +3 -0
- agno/tools/duckduckgo.py +25 -71
- agno/tools/exa.py +0 -21
- agno/tools/file.py +14 -13
- agno/tools/file_generation.py +12 -6
- agno/tools/firecrawl.py +15 -7
- agno/tools/function.py +94 -113
- agno/tools/google_bigquery.py +11 -2
- agno/tools/google_drive.py +4 -3
- agno/tools/knowledge.py +9 -4
- agno/tools/mcp/mcp.py +301 -18
- agno/tools/mcp/multi_mcp.py +269 -14
- agno/tools/mem0.py +11 -10
- agno/tools/memory.py +47 -46
- agno/tools/mlx_transcribe.py +10 -7
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/nano_banana.py +151 -0
- agno/tools/parallel.py +0 -7
- agno/tools/postgres.py +76 -36
- agno/tools/python.py +14 -6
- agno/tools/reasoning.py +30 -23
- agno/tools/redshift.py +406 -0
- agno/tools/shopify.py +1519 -0
- agno/tools/spotify.py +919 -0
- agno/tools/tavily.py +4 -1
- agno/tools/toolkit.py +253 -18
- agno/tools/websearch.py +93 -0
- agno/tools/website.py +1 -1
- agno/tools/wikipedia.py +1 -1
- agno/tools/workflow.py +56 -48
- agno/tools/yfinance.py +12 -11
- agno/tracing/__init__.py +12 -0
- agno/tracing/exporter.py +161 -0
- agno/tracing/schemas.py +276 -0
- agno/tracing/setup.py +112 -0
- agno/utils/agent.py +251 -10
- agno/utils/cryptography.py +22 -0
- agno/utils/dttm.py +33 -0
- agno/utils/events.py +264 -7
- agno/utils/hooks.py +111 -3
- agno/utils/http.py +161 -2
- agno/utils/mcp.py +49 -8
- agno/utils/media.py +22 -1
- agno/utils/models/ai_foundry.py +9 -2
- agno/utils/models/claude.py +20 -5
- agno/utils/models/cohere.py +9 -2
- agno/utils/models/llama.py +9 -2
- agno/utils/models/mistral.py +4 -2
- agno/utils/os.py +0 -0
- agno/utils/print_response/agent.py +99 -16
- agno/utils/print_response/team.py +223 -24
- agno/utils/print_response/workflow.py +0 -2
- agno/utils/prompts.py +8 -6
- agno/utils/remote.py +23 -0
- agno/utils/response.py +1 -13
- agno/utils/string.py +91 -2
- agno/utils/team.py +62 -12
- agno/utils/tokens.py +657 -0
- agno/vectordb/base.py +15 -2
- agno/vectordb/cassandra/cassandra.py +1 -1
- agno/vectordb/chroma/__init__.py +2 -1
- agno/vectordb/chroma/chromadb.py +468 -23
- agno/vectordb/clickhouse/clickhousedb.py +1 -1
- agno/vectordb/couchbase/couchbase.py +6 -2
- agno/vectordb/lancedb/lance_db.py +7 -38
- agno/vectordb/lightrag/lightrag.py +7 -6
- agno/vectordb/milvus/milvus.py +118 -84
- agno/vectordb/mongodb/__init__.py +2 -1
- agno/vectordb/mongodb/mongodb.py +14 -31
- agno/vectordb/pgvector/pgvector.py +120 -66
- agno/vectordb/pineconedb/pineconedb.py +2 -19
- agno/vectordb/qdrant/__init__.py +2 -1
- agno/vectordb/qdrant/qdrant.py +33 -56
- agno/vectordb/redis/__init__.py +2 -1
- agno/vectordb/redis/redisdb.py +19 -31
- agno/vectordb/singlestore/singlestore.py +17 -9
- agno/vectordb/surrealdb/surrealdb.py +2 -38
- agno/vectordb/weaviate/__init__.py +2 -1
- agno/vectordb/weaviate/weaviate.py +7 -3
- agno/workflow/__init__.py +5 -1
- agno/workflow/agent.py +2 -2
- agno/workflow/condition.py +12 -10
- agno/workflow/loop.py +28 -9
- agno/workflow/parallel.py +21 -13
- agno/workflow/remote.py +362 -0
- agno/workflow/router.py +12 -9
- agno/workflow/step.py +261 -36
- agno/workflow/steps.py +12 -8
- agno/workflow/types.py +40 -77
- agno/workflow/workflow.py +939 -213
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/METADATA +134 -181
- agno-2.4.3.dist-info/RECORD +677 -0
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/WHEEL +1 -1
- agno/tools/googlesearch.py +0 -98
- agno/tools/memori.py +0 -339
- agno-2.2.13.dist-info/RECORD +0 -575
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/licenses/LICENSE +0 -0
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/top_level.txt +0 -0
agno/tools/memory.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from textwrap import dedent
|
|
3
|
-
from typing import Any,
|
|
3
|
+
from typing import Any, List, Optional
|
|
4
4
|
from uuid import uuid4
|
|
5
5
|
|
|
6
6
|
from agno.db.base import BaseDb
|
|
7
7
|
from agno.db.schemas import UserMemory
|
|
8
|
+
from agno.run import RunContext
|
|
8
9
|
from agno.tools import Toolkit
|
|
9
10
|
from agno.utils.log import log_debug, log_error
|
|
10
11
|
|
|
@@ -62,7 +63,7 @@ class MemoryTools(Toolkit):
|
|
|
62
63
|
**kwargs,
|
|
63
64
|
)
|
|
64
65
|
|
|
65
|
-
def think(self,
|
|
66
|
+
def think(self, run_context: RunContext, thought: str) -> str:
|
|
66
67
|
"""Use this tool as a scratchpad to reason about memory operations, refine your approach, brainstorm memory content, or revise your plan.
|
|
67
68
|
|
|
68
69
|
Call `Think` whenever you need to figure out what to do next, analyze the user's requirements, plan memory operations, or decide on execution strategy.
|
|
@@ -75,14 +76,14 @@ class MemoryTools(Toolkit):
|
|
|
75
76
|
log_debug(f"Memory Thought: {thought}")
|
|
76
77
|
|
|
77
78
|
# Add the thought to the session state
|
|
78
|
-
if session_state is None:
|
|
79
|
-
session_state = {}
|
|
80
|
-
if "memory_thoughts" not in session_state:
|
|
81
|
-
session_state["memory_thoughts"] = []
|
|
82
|
-
session_state["memory_thoughts"].append(thought)
|
|
79
|
+
if run_context.session_state is None:
|
|
80
|
+
run_context.session_state = {}
|
|
81
|
+
if "memory_thoughts" not in run_context.session_state:
|
|
82
|
+
run_context.session_state["memory_thoughts"] = []
|
|
83
|
+
run_context.session_state["memory_thoughts"].append(thought)
|
|
83
84
|
|
|
84
85
|
# Return the full log of thoughts and the new thought
|
|
85
|
-
thoughts = "\n".join([f"- {t}" for t in session_state["memory_thoughts"]])
|
|
86
|
+
thoughts = "\n".join([f"- {t}" for t in run_context.session_state["memory_thoughts"]])
|
|
86
87
|
formatted_thoughts = dedent(
|
|
87
88
|
f"""Memory Thoughts:
|
|
88
89
|
{thoughts}
|
|
@@ -93,21 +94,21 @@ class MemoryTools(Toolkit):
|
|
|
93
94
|
log_error(f"Error recording memory thought: {e}")
|
|
94
95
|
return f"Error recording memory thought: {e}"
|
|
95
96
|
|
|
96
|
-
def get_memories(self,
|
|
97
|
+
def get_memories(self, run_context: RunContext) -> str:
|
|
97
98
|
"""
|
|
98
99
|
Use this tool to get a list of memories for the current user from the database.
|
|
99
100
|
"""
|
|
100
101
|
try:
|
|
101
|
-
# Get user info from
|
|
102
|
-
user_id =
|
|
102
|
+
# Get user info from run context
|
|
103
|
+
user_id = run_context.user_id
|
|
103
104
|
|
|
104
105
|
memories = self.db.get_user_memories(user_id=user_id)
|
|
105
106
|
|
|
106
107
|
# Store the result in session state for analysis
|
|
107
|
-
if session_state is None:
|
|
108
|
-
session_state = {}
|
|
109
|
-
if "memory_operations" not in session_state:
|
|
110
|
-
session_state["memory_operations"] = []
|
|
108
|
+
if run_context.session_state is None:
|
|
109
|
+
run_context.session_state = {}
|
|
110
|
+
if "memory_operations" not in run_context.session_state:
|
|
111
|
+
run_context.session_state["memory_operations"] = []
|
|
111
112
|
|
|
112
113
|
operation_result = {
|
|
113
114
|
"operation": "get_memories",
|
|
@@ -115,7 +116,7 @@ class MemoryTools(Toolkit):
|
|
|
115
116
|
"memories": [memory.to_dict() for memory in memories], # type: ignore
|
|
116
117
|
"error": None,
|
|
117
118
|
}
|
|
118
|
-
session_state["memory_operations"].append(operation_result)
|
|
119
|
+
run_context.session_state["memory_operations"].append(operation_result)
|
|
119
120
|
|
|
120
121
|
return json.dumps([memory.to_dict() for memory in memories], indent=2) # type: ignore
|
|
121
122
|
except Exception as e:
|
|
@@ -124,7 +125,7 @@ class MemoryTools(Toolkit):
|
|
|
124
125
|
|
|
125
126
|
def add_memory(
|
|
126
127
|
self,
|
|
127
|
-
|
|
128
|
+
run_context: RunContext,
|
|
128
129
|
memory: str,
|
|
129
130
|
topics: Optional[List[str]] = None,
|
|
130
131
|
) -> str:
|
|
@@ -140,8 +141,8 @@ class MemoryTools(Toolkit):
|
|
|
140
141
|
try:
|
|
141
142
|
log_debug(f"Adding memory: {memory}")
|
|
142
143
|
|
|
143
|
-
# Get user
|
|
144
|
-
user_id =
|
|
144
|
+
# Get user info from run context
|
|
145
|
+
user_id = run_context.user_id
|
|
145
146
|
|
|
146
147
|
# Create UserMemory object
|
|
147
148
|
user_memory = UserMemory(
|
|
@@ -155,10 +156,10 @@ class MemoryTools(Toolkit):
|
|
|
155
156
|
created_memory = self.db.upsert_user_memory(user_memory)
|
|
156
157
|
|
|
157
158
|
# Store the result in session state for analysis
|
|
158
|
-
if session_state is None:
|
|
159
|
-
session_state = {}
|
|
160
|
-
if "memory_operations" not in session_state:
|
|
161
|
-
session_state["memory_operations"] = []
|
|
159
|
+
if run_context.session_state is None:
|
|
160
|
+
run_context.session_state = {}
|
|
161
|
+
if "memory_operations" not in run_context.session_state:
|
|
162
|
+
run_context.session_state["memory_operations"] = []
|
|
162
163
|
|
|
163
164
|
memory_dict = created_memory.to_dict() if created_memory else None # type: ignore
|
|
164
165
|
|
|
@@ -168,7 +169,7 @@ class MemoryTools(Toolkit):
|
|
|
168
169
|
"memory": memory_dict,
|
|
169
170
|
"error": None,
|
|
170
171
|
}
|
|
171
|
-
session_state["memory_operations"].append(operation_result)
|
|
172
|
+
run_context.session_state["memory_operations"].append(operation_result)
|
|
172
173
|
|
|
173
174
|
if created_memory:
|
|
174
175
|
return json.dumps({"success": True, "operation": "add_memory", "memory": memory_dict}, indent=2)
|
|
@@ -183,7 +184,7 @@ class MemoryTools(Toolkit):
|
|
|
183
184
|
|
|
184
185
|
def update_memory(
|
|
185
186
|
self,
|
|
186
|
-
|
|
187
|
+
run_context: RunContext,
|
|
187
188
|
memory_id: str,
|
|
188
189
|
memory: Optional[str] = None,
|
|
189
190
|
topics: Optional[List[str]] = None,
|
|
@@ -221,10 +222,10 @@ class MemoryTools(Toolkit):
|
|
|
221
222
|
updated_result = self.db.upsert_user_memory(updated_memory)
|
|
222
223
|
|
|
223
224
|
# Store the result in session state for analysis
|
|
224
|
-
if session_state is None:
|
|
225
|
-
session_state = {}
|
|
226
|
-
if "memory_operations" not in session_state:
|
|
227
|
-
session_state["memory_operations"] = []
|
|
225
|
+
if run_context.session_state is None:
|
|
226
|
+
run_context.session_state = {}
|
|
227
|
+
if "memory_operations" not in run_context.session_state:
|
|
228
|
+
run_context.session_state["memory_operations"] = []
|
|
228
229
|
|
|
229
230
|
memory_dict = updated_result.to_dict() if updated_result else None # type: ignore
|
|
230
231
|
|
|
@@ -234,7 +235,7 @@ class MemoryTools(Toolkit):
|
|
|
234
235
|
"memory": memory_dict,
|
|
235
236
|
"error": None,
|
|
236
237
|
}
|
|
237
|
-
session_state["memory_operations"].append(operation_result)
|
|
238
|
+
run_context.session_state["memory_operations"].append(operation_result)
|
|
238
239
|
|
|
239
240
|
if updated_result:
|
|
240
241
|
return json.dumps({"success": True, "operation": "update_memory", "memory": memory_dict}, indent=2)
|
|
@@ -249,7 +250,7 @@ class MemoryTools(Toolkit):
|
|
|
249
250
|
|
|
250
251
|
def delete_memory(
|
|
251
252
|
self,
|
|
252
|
-
|
|
253
|
+
run_context: RunContext,
|
|
253
254
|
memory_id: str,
|
|
254
255
|
) -> str:
|
|
255
256
|
"""Use this tool to delete a memory from the database.
|
|
@@ -275,10 +276,10 @@ class MemoryTools(Toolkit):
|
|
|
275
276
|
self.db.delete_user_memory(memory_id)
|
|
276
277
|
|
|
277
278
|
# Store the result in session state for analysis
|
|
278
|
-
if session_state is None:
|
|
279
|
-
session_state = {}
|
|
280
|
-
if "memory_operations" not in session_state:
|
|
281
|
-
session_state["memory_operations"] = []
|
|
279
|
+
if run_context.session_state is None:
|
|
280
|
+
run_context.session_state = {}
|
|
281
|
+
if "memory_operations" not in run_context.session_state:
|
|
282
|
+
run_context.session_state["memory_operations"] = []
|
|
282
283
|
|
|
283
284
|
memory_dict = existing_memory.to_dict() if existing_memory else None # type: ignore
|
|
284
285
|
|
|
@@ -289,7 +290,7 @@ class MemoryTools(Toolkit):
|
|
|
289
290
|
"deleted_memory": memory_dict,
|
|
290
291
|
"error": None,
|
|
291
292
|
}
|
|
292
|
-
session_state["memory_operations"].append(operation_result)
|
|
293
|
+
run_context.session_state["memory_operations"].append(operation_result)
|
|
293
294
|
|
|
294
295
|
return json.dumps(
|
|
295
296
|
{
|
|
@@ -305,7 +306,7 @@ class MemoryTools(Toolkit):
|
|
|
305
306
|
log_error(f"Error deleting memory: {e}")
|
|
306
307
|
return json.dumps({"success": False, "operation": "delete_memory", "error": str(e)}, indent=2)
|
|
307
308
|
|
|
308
|
-
def analyze(self,
|
|
309
|
+
def analyze(self, run_context: RunContext, analysis: str) -> str:
|
|
309
310
|
"""Use this tool to evaluate whether the memory operations results are correct and sufficient.
|
|
310
311
|
If not, go back to "Think" or use memory operations with refined parameters.
|
|
311
312
|
|
|
@@ -316,14 +317,14 @@ class MemoryTools(Toolkit):
|
|
|
316
317
|
log_debug(f"Memory Analysis: {analysis}")
|
|
317
318
|
|
|
318
319
|
# Add the analysis to the session state
|
|
319
|
-
if session_state is None:
|
|
320
|
-
session_state = {}
|
|
321
|
-
if "memory_analysis" not in session_state:
|
|
322
|
-
session_state["memory_analysis"] = []
|
|
323
|
-
session_state["memory_analysis"].append(analysis)
|
|
320
|
+
if run_context.session_state is None:
|
|
321
|
+
run_context.session_state = {}
|
|
322
|
+
if "memory_analysis" not in run_context.session_state:
|
|
323
|
+
run_context.session_state["memory_analysis"] = []
|
|
324
|
+
run_context.session_state["memory_analysis"].append(analysis)
|
|
324
325
|
|
|
325
326
|
# Return the full log of analysis and the new analysis
|
|
326
|
-
analysis_log = "\n".join([f"- {a}" for a in session_state["memory_analysis"]])
|
|
327
|
+
analysis_log = "\n".join([f"- {a}" for a in run_context.session_state["memory_analysis"]])
|
|
327
328
|
formatted_analysis = dedent(
|
|
328
329
|
f"""Memory Analysis:
|
|
329
330
|
{analysis_log}
|
|
@@ -338,7 +339,7 @@ class MemoryTools(Toolkit):
|
|
|
338
339
|
You have access to the Think, Add Memory, Update Memory, Delete Memory, and Analyze tools that will help you manage user memories and analyze their operations. Use these tools as frequently as needed to successfully complete memory management tasks.
|
|
339
340
|
|
|
340
341
|
## How to use the Think, Memory Operations, and Analyze tools:
|
|
341
|
-
|
|
342
|
+
|
|
342
343
|
1. **Think**
|
|
343
344
|
- Purpose: A scratchpad for planning memory operations, brainstorming memory content, and refining your approach. You never reveal your "Think" content to the user.
|
|
344
345
|
- Usage: Call `think` whenever you need to figure out what memory operations to perform, analyze requirements, or decide on strategy.
|
|
@@ -407,12 +408,12 @@ class MemoryTools(Toolkit):
|
|
|
407
408
|
Analyze: Successfully deleted the outdated work schedule memory. The old information won't interfere with future scheduling requests.
|
|
408
409
|
|
|
409
410
|
Final Answer: I've removed your old work schedule information. Feel free to share your new schedule when you're ready, and I'll store the updated information.
|
|
410
|
-
|
|
411
|
+
|
|
411
412
|
#### Example 4: Retrieving Memories
|
|
412
413
|
|
|
413
414
|
User: What have you remembered about me?
|
|
414
415
|
Think: The user wants to retrieve memories about themselves. I should use the get_memories tool to retrieve the memories.
|
|
415
|
-
Get Memories:
|
|
416
|
+
Get Memories:
|
|
416
417
|
Analyze: Successfully retrieved the memories about the user. The memories are relevant to the user's preferences and activities.
|
|
417
418
|
|
|
418
419
|
Final Answer: I've retrieved the memories about you. You like to hike in the mountains on weekends and travel to new places and experience different cultures. You are planning to travel to Africa in December.\
|
agno/tools/mlx_transcribe.py
CHANGED
|
@@ -17,7 +17,7 @@ provides high-quality transcription capabilities.
|
|
|
17
17
|
|
|
18
18
|
import json
|
|
19
19
|
from pathlib import Path
|
|
20
|
-
from typing import Any, Dict, List, Optional,
|
|
20
|
+
from typing import Any, Dict, List, Optional, Union
|
|
21
21
|
|
|
22
22
|
from agno.tools import Toolkit
|
|
23
23
|
from agno.utils.log import log_info, logger
|
|
@@ -33,9 +33,10 @@ class MLXTranscribeTools(Toolkit):
|
|
|
33
33
|
self,
|
|
34
34
|
base_dir: Optional[Path] = None,
|
|
35
35
|
enable_read_files_in_base_dir: bool = True,
|
|
36
|
+
restrict_to_base_dir: bool = True,
|
|
36
37
|
path_or_hf_repo: str = "mlx-community/whisper-large-v3-turbo",
|
|
37
38
|
verbose: Optional[bool] = None,
|
|
38
|
-
temperature: Optional[Union[float,
|
|
39
|
+
temperature: Optional[Union[float, tuple[float, ...]]] = None,
|
|
39
40
|
compression_ratio_threshold: Optional[float] = None,
|
|
40
41
|
logprob_threshold: Optional[float] = None,
|
|
41
42
|
no_speech_threshold: Optional[float] = None,
|
|
@@ -50,10 +51,11 @@ class MLXTranscribeTools(Toolkit):
|
|
|
50
51
|
all: bool = False,
|
|
51
52
|
**kwargs,
|
|
52
53
|
):
|
|
53
|
-
self.base_dir: Path = base_dir or Path.cwd()
|
|
54
|
+
self.base_dir: Path = (base_dir or Path.cwd()).resolve()
|
|
55
|
+
self.restrict_to_base_dir = restrict_to_base_dir
|
|
54
56
|
self.path_or_hf_repo: str = path_or_hf_repo
|
|
55
57
|
self.verbose: Optional[bool] = verbose
|
|
56
|
-
self.temperature: Optional[Union[float,
|
|
58
|
+
self.temperature: Optional[Union[float, tuple[float, ...]]] = temperature
|
|
57
59
|
self.compression_ratio_threshold: Optional[float] = compression_ratio_threshold
|
|
58
60
|
self.logprob_threshold: Optional[float] = logprob_threshold
|
|
59
61
|
self.no_speech_threshold: Optional[float] = no_speech_threshold
|
|
@@ -83,9 +85,10 @@ class MLXTranscribeTools(Toolkit):
|
|
|
83
85
|
str: The transcribed text or an error message if the transcription fails.
|
|
84
86
|
"""
|
|
85
87
|
try:
|
|
86
|
-
|
|
87
|
-
if
|
|
88
|
-
return "
|
|
88
|
+
safe, file_path = self._check_path(file_name, self.base_dir, self.restrict_to_base_dir)
|
|
89
|
+
if not safe:
|
|
90
|
+
return f"Error: Path '{file_name}' is outside the allowed base directory"
|
|
91
|
+
audio_file_path = str(file_path)
|
|
89
92
|
|
|
90
93
|
log_info(f"Transcribing audio file {audio_file_path}")
|
|
91
94
|
transcription_kwargs: Dict[str, Any] = {
|
agno/tools/models/nebius.py
CHANGED
|
@@ -12,12 +12,12 @@ from agno.utils.log import log_error, log_warning
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class NebiusTools(Toolkit):
|
|
15
|
-
"""Tools for interacting with Nebius
|
|
15
|
+
"""Tools for interacting with Nebius Token Factory's text-to-image API"""
|
|
16
16
|
|
|
17
17
|
def __init__(
|
|
18
18
|
self,
|
|
19
19
|
api_key: Optional[str] = None,
|
|
20
|
-
base_url: str = "https://api.
|
|
20
|
+
base_url: str = "https://api.tokenfactory.nebius.com/v1",
|
|
21
21
|
image_model: str = "black-forest-labs/flux-schnell",
|
|
22
22
|
image_quality: Optional[str] = "standard",
|
|
23
23
|
image_size: Optional[str] = "1024x1024",
|
|
@@ -26,11 +26,11 @@ class NebiusTools(Toolkit):
|
|
|
26
26
|
all: bool = False,
|
|
27
27
|
**kwargs,
|
|
28
28
|
):
|
|
29
|
-
"""Initialize Nebius
|
|
29
|
+
"""Initialize Nebius Token Factory text-to-image tools.
|
|
30
30
|
|
|
31
31
|
Args:
|
|
32
32
|
api_key: Nebius API key. If not provided, will look for NEBIUS_API_KEY environment variable.
|
|
33
|
-
base_url: The base URL for the Nebius
|
|
33
|
+
base_url: The base URL for the Nebius Token Factory API. This should be configured according to Nebius's documentation.
|
|
34
34
|
image_model: The model to use for generation. Options include:
|
|
35
35
|
- "black-forest-labs/flux-schnell" (fastest)
|
|
36
36
|
- "black-forest-labs/flux-dev" (balanced)
|
|
@@ -69,7 +69,7 @@ class NebiusTools(Toolkit):
|
|
|
69
69
|
agent: Agent,
|
|
70
70
|
prompt: str,
|
|
71
71
|
) -> ToolResult:
|
|
72
|
-
"""Generate images based on a text prompt using Nebius
|
|
72
|
+
"""Generate images based on a text prompt using Nebius Token Factory.
|
|
73
73
|
|
|
74
74
|
Args:
|
|
75
75
|
agent: The agent instance for adding images
|
agno/tools/models_labs.py
CHANGED
|
@@ -4,10 +4,8 @@ from os import getenv
|
|
|
4
4
|
from typing import Any, Dict, List, Optional, Union
|
|
5
5
|
from uuid import uuid4
|
|
6
6
|
|
|
7
|
-
from agno.agent import Agent
|
|
8
7
|
from agno.media import Audio, Image, Video
|
|
9
8
|
from agno.models.response import FileType
|
|
10
|
-
from agno.team import Team
|
|
11
9
|
from agno.tools import Toolkit
|
|
12
10
|
from agno.tools.function import ToolResult
|
|
13
11
|
from agno.utils.log import log_debug, log_info, logger
|
|
@@ -22,12 +20,14 @@ MODELS_LAB_URLS = {
|
|
|
22
20
|
"MP4": "https://modelslab.com/api/v6/video/text2video",
|
|
23
21
|
"MP3": "https://modelslab.com/api/v6/voice/music_gen",
|
|
24
22
|
"GIF": "https://modelslab.com/api/v6/video/text2video",
|
|
23
|
+
"WAV": "https://modelslab.com/api/v6/voice/sfx",
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
MODELS_LAB_FETCH_URLS = {
|
|
28
27
|
"MP4": "https://modelslab.com/api/v6/video/fetch",
|
|
29
28
|
"MP3": "https://modelslab.com/api/v6/voice/fetch",
|
|
30
29
|
"GIF": "https://modelslab.com/api/v6/video/fetch",
|
|
30
|
+
"WAV": "https://modelslab.com/api/v6/voice/fetch",
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
|
|
@@ -78,6 +78,13 @@ class ModelsLabTools(Toolkit):
|
|
|
78
78
|
"output_type": self.file_type.value,
|
|
79
79
|
}
|
|
80
80
|
base_payload |= video_template # Use |= instead of update()
|
|
81
|
+
elif self.file_type == FileType.WAV:
|
|
82
|
+
sfx_template = {
|
|
83
|
+
"duration": 10,
|
|
84
|
+
"output_format": "wav",
|
|
85
|
+
"temp": False,
|
|
86
|
+
}
|
|
87
|
+
base_payload |= sfx_template # Use |= instead of update()
|
|
81
88
|
else:
|
|
82
89
|
audio_template = {
|
|
83
90
|
"base64": False,
|
|
@@ -101,7 +108,7 @@ class ModelsLabTools(Toolkit):
|
|
|
101
108
|
elif self.file_type == FileType.GIF:
|
|
102
109
|
image_artifact = Image(id=str(media_id), url=media_url)
|
|
103
110
|
artifacts["images"].append(image_artifact)
|
|
104
|
-
elif self.file_type
|
|
111
|
+
elif self.file_type in [FileType.MP3, FileType.WAV]:
|
|
105
112
|
audio_artifact = Audio(id=str(media_id), url=media_url)
|
|
106
113
|
artifacts["audios"].append(audio_artifact)
|
|
107
114
|
|
|
@@ -131,7 +138,7 @@ class ModelsLabTools(Toolkit):
|
|
|
131
138
|
|
|
132
139
|
return False
|
|
133
140
|
|
|
134
|
-
def generate_media(self,
|
|
141
|
+
def generate_media(self, prompt: str) -> ToolResult:
|
|
135
142
|
"""Generate media (video, image, or audio) given a prompt."""
|
|
136
143
|
if not self.api_key:
|
|
137
144
|
return ToolResult(content="Please set the MODELS_LAB_API_KEY")
|
|
@@ -157,7 +164,6 @@ class ModelsLabTools(Toolkit):
|
|
|
157
164
|
return ToolResult(content=f"Error: {result['error']}")
|
|
158
165
|
|
|
159
166
|
eta = result.get("eta")
|
|
160
|
-
url_links = result.get("future_links")
|
|
161
167
|
media_id = str(uuid4())
|
|
162
168
|
|
|
163
169
|
# Collect all media artifacts
|
|
@@ -165,17 +171,21 @@ class ModelsLabTools(Toolkit):
|
|
|
165
171
|
all_videos = []
|
|
166
172
|
all_audios = []
|
|
167
173
|
|
|
174
|
+
if self.file_type == FileType.WAV:
|
|
175
|
+
url_links = result.get("output", [])
|
|
176
|
+
else:
|
|
177
|
+
url_links = result.get("future_links")
|
|
168
178
|
for media_url in url_links:
|
|
169
179
|
artifacts = self._create_media_artifacts(media_id, media_url, str(eta))
|
|
170
180
|
all_images.extend(artifacts["images"])
|
|
171
181
|
all_videos.extend(artifacts["videos"])
|
|
172
182
|
all_audios.extend(artifacts["audios"])
|
|
173
183
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
184
|
+
if self.wait_for_completion and isinstance(eta, int):
|
|
185
|
+
if self._wait_for_media(media_id, eta):
|
|
186
|
+
log_info("Media generation completed successfully")
|
|
187
|
+
else:
|
|
188
|
+
logger.warning("Media generation timed out")
|
|
179
189
|
|
|
180
190
|
# Return ToolResult with appropriate media artifacts
|
|
181
191
|
return ToolResult(
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from io import BytesIO
|
|
5
|
+
from typing import Any, List, Optional
|
|
6
|
+
from uuid import uuid4
|
|
7
|
+
|
|
8
|
+
from agno.media import Image
|
|
9
|
+
from agno.tools import Toolkit
|
|
10
|
+
from agno.tools.function import ToolResult
|
|
11
|
+
from agno.utils.log import log_debug, logger
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
from google import genai
|
|
15
|
+
from google.genai import types
|
|
16
|
+
from PIL import Image as PILImage
|
|
17
|
+
|
|
18
|
+
except ImportError as exc:
|
|
19
|
+
missing = []
|
|
20
|
+
try:
|
|
21
|
+
from google.genai import types
|
|
22
|
+
except ImportError:
|
|
23
|
+
missing.append("google-genai")
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
from PIL import Image as PILImage
|
|
27
|
+
except ImportError:
|
|
28
|
+
missing.append("Pillow")
|
|
29
|
+
|
|
30
|
+
raise ImportError(
|
|
31
|
+
f"Missing required package(s): {', '.join(missing)}. Install using: pip install {' '.join(missing)}"
|
|
32
|
+
) from exc
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Note: Expand this list as new models become supported by the Google Content Generation API.
|
|
36
|
+
ALLOWED_MODELS = ["gemini-2.5-flash-image"]
|
|
37
|
+
ALLOWED_RATIOS = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class NanoBananaTools(Toolkit):
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
model: str = "gemini-2.5-flash-image",
|
|
44
|
+
aspect_ratio: str = "1:1",
|
|
45
|
+
api_key: Optional[str] = None,
|
|
46
|
+
enable_create_image: bool = True,
|
|
47
|
+
**kwargs,
|
|
48
|
+
):
|
|
49
|
+
self.model = model
|
|
50
|
+
self.aspect_ratio = aspect_ratio
|
|
51
|
+
self.api_key = api_key or os.getenv("GOOGLE_API_KEY")
|
|
52
|
+
|
|
53
|
+
# Validate model
|
|
54
|
+
if model not in ALLOWED_MODELS:
|
|
55
|
+
raise ValueError(f"Invalid model '{model}'. Supported: {', '.join(ALLOWED_MODELS)}")
|
|
56
|
+
|
|
57
|
+
if self.aspect_ratio not in ALLOWED_RATIOS:
|
|
58
|
+
raise ValueError(f"Invalid aspect_ratio '{self.aspect_ratio}'. Supported: {', '.join(ALLOWED_RATIOS)}")
|
|
59
|
+
|
|
60
|
+
if not self.api_key:
|
|
61
|
+
raise ValueError("GOOGLE_API_KEY not set. Export it: `export GOOGLE_API_KEY=<your-key>`")
|
|
62
|
+
|
|
63
|
+
tools: List[Any] = []
|
|
64
|
+
if enable_create_image:
|
|
65
|
+
tools.append(self.create_image)
|
|
66
|
+
|
|
67
|
+
super().__init__(name="nano_banana", tools=tools, **kwargs)
|
|
68
|
+
|
|
69
|
+
def create_image(self, prompt: str) -> ToolResult:
|
|
70
|
+
"""Generate an image from a text prompt."""
|
|
71
|
+
try:
|
|
72
|
+
client = genai.Client(api_key=self.api_key)
|
|
73
|
+
log_debug(f"NanoBanana generating image with prompt: {prompt}")
|
|
74
|
+
|
|
75
|
+
cfg = types.GenerateContentConfig(
|
|
76
|
+
response_modalities=["IMAGE"],
|
|
77
|
+
image_config=types.ImageConfig(aspect_ratio=self.aspect_ratio),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
response = client.models.generate_content(
|
|
81
|
+
model=self.model,
|
|
82
|
+
contents=[prompt], # type: ignore
|
|
83
|
+
config=cfg,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
generated_images: List[Image] = []
|
|
87
|
+
response_str = ""
|
|
88
|
+
|
|
89
|
+
if not hasattr(response, "candidates") or not response.candidates:
|
|
90
|
+
logger.warning("No candidates in response")
|
|
91
|
+
return ToolResult(content="No images were generated in the response")
|
|
92
|
+
|
|
93
|
+
# Process each candidate
|
|
94
|
+
for candidate in response.candidates:
|
|
95
|
+
if not hasattr(candidate, "content") or not candidate.content or not candidate.content.parts:
|
|
96
|
+
continue
|
|
97
|
+
|
|
98
|
+
for part in candidate.content.parts:
|
|
99
|
+
if hasattr(part, "text") and part.text:
|
|
100
|
+
response_str += part.text + "\n"
|
|
101
|
+
|
|
102
|
+
if hasattr(part, "inline_data") and part.inline_data:
|
|
103
|
+
try:
|
|
104
|
+
# Extract image data from the blob
|
|
105
|
+
image_data = part.inline_data.data
|
|
106
|
+
mime_type = getattr(part.inline_data, "mime_type", "image/png")
|
|
107
|
+
|
|
108
|
+
if image_data:
|
|
109
|
+
pil_img = PILImage.open(BytesIO(image_data))
|
|
110
|
+
|
|
111
|
+
# Save to buffer with proper format
|
|
112
|
+
buffer = BytesIO()
|
|
113
|
+
image_format = "PNG" if "png" in mime_type.lower() else "JPEG"
|
|
114
|
+
pil_img.save(buffer, format=image_format)
|
|
115
|
+
buffer.seek(0)
|
|
116
|
+
|
|
117
|
+
agno_img = Image(
|
|
118
|
+
id=str(uuid4()),
|
|
119
|
+
content=buffer.getvalue(),
|
|
120
|
+
original_prompt=prompt,
|
|
121
|
+
)
|
|
122
|
+
generated_images.append(agno_img)
|
|
123
|
+
|
|
124
|
+
log_debug(f"Successfully processed image with ID: {agno_img.id}")
|
|
125
|
+
response_str += f"Image generated successfully (ID: {agno_img.id}).\n"
|
|
126
|
+
|
|
127
|
+
except Exception as img_exc:
|
|
128
|
+
logger.error(f"Failed to process image data: {img_exc}")
|
|
129
|
+
response_str += f"Failed to process image: {img_exc}\n"
|
|
130
|
+
|
|
131
|
+
if hasattr(response, "usage_metadata") and response.usage_metadata:
|
|
132
|
+
log_debug(
|
|
133
|
+
f"Token usage - Prompt: {response.usage_metadata.prompt_token_count}, "
|
|
134
|
+
f"Response: {response.usage_metadata.candidates_token_count}, "
|
|
135
|
+
f"Total: {response.usage_metadata.total_token_count}"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
if generated_images:
|
|
139
|
+
return ToolResult(
|
|
140
|
+
content=response_str.strip() or "Image(s) generated successfully",
|
|
141
|
+
images=generated_images,
|
|
142
|
+
)
|
|
143
|
+
else:
|
|
144
|
+
return ToolResult(
|
|
145
|
+
content=response_str.strip() or "No images were generated",
|
|
146
|
+
images=None,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
except Exception as exc:
|
|
150
|
+
logger.error(f"NanoBanana image generation failed: {exc}")
|
|
151
|
+
return ToolResult(content=f"Error generating image: {str(exc)}")
|
agno/tools/parallel.py
CHANGED
|
@@ -41,7 +41,6 @@ class ParallelTools(Toolkit):
|
|
|
41
41
|
include_domains (Optional[List[str]]): Default domains to restrict results to. Default is None.
|
|
42
42
|
exclude_domains (Optional[List[str]]): Default domains to exclude from results. Default is None.
|
|
43
43
|
max_age_seconds (Optional[int]): Default cache age threshold (minimum 600). Default is None.
|
|
44
|
-
timeout_seconds (Optional[float]): Default timeout for content retrieval. Default is None.
|
|
45
44
|
disable_cache_fallback (Optional[bool]): Default cache fallback behavior. Default is None.
|
|
46
45
|
"""
|
|
47
46
|
|
|
@@ -58,7 +57,6 @@ class ParallelTools(Toolkit):
|
|
|
58
57
|
include_domains: Optional[List[str]] = None,
|
|
59
58
|
exclude_domains: Optional[List[str]] = None,
|
|
60
59
|
max_age_seconds: Optional[int] = None,
|
|
61
|
-
timeout_seconds: Optional[float] = None,
|
|
62
60
|
disable_cache_fallback: Optional[bool] = None,
|
|
63
61
|
**kwargs,
|
|
64
62
|
):
|
|
@@ -73,7 +71,6 @@ class ParallelTools(Toolkit):
|
|
|
73
71
|
self.include_domains = include_domains
|
|
74
72
|
self.exclude_domains = exclude_domains
|
|
75
73
|
self.max_age_seconds = max_age_seconds
|
|
76
|
-
self.timeout_seconds = timeout_seconds
|
|
77
74
|
self.disable_cache_fallback = disable_cache_fallback
|
|
78
75
|
|
|
79
76
|
self.parallel_client = ParallelClient(
|
|
@@ -153,8 +150,6 @@ class ParallelTools(Toolkit):
|
|
|
153
150
|
fetch_policy: Dict[str, Any] = {}
|
|
154
151
|
if self.max_age_seconds is not None:
|
|
155
152
|
fetch_policy["max_age_seconds"] = self.max_age_seconds
|
|
156
|
-
if self.timeout_seconds is not None:
|
|
157
|
-
fetch_policy["timeout_seconds"] = self.timeout_seconds
|
|
158
153
|
if self.disable_cache_fallback is not None:
|
|
159
154
|
fetch_policy["disable_cache_fallback"] = self.disable_cache_fallback
|
|
160
155
|
|
|
@@ -256,8 +251,6 @@ class ParallelTools(Toolkit):
|
|
|
256
251
|
fetch_policy: Dict[str, Any] = {}
|
|
257
252
|
if self.max_age_seconds is not None:
|
|
258
253
|
fetch_policy["max_age_seconds"] = self.max_age_seconds
|
|
259
|
-
if self.timeout_seconds is not None:
|
|
260
|
-
fetch_policy["timeout_seconds"] = self.timeout_seconds
|
|
261
254
|
if self.disable_cache_fallback is not None:
|
|
262
255
|
fetch_policy["disable_cache_fallback"] = self.disable_cache_fallback
|
|
263
256
|
|