agno 2.2.13__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/__init__.py +8 -0
- agno/agent/__init__.py +51 -0
- agno/agent/agent.py +10405 -0
- agno/api/__init__.py +0 -0
- agno/api/agent.py +28 -0
- agno/api/api.py +40 -0
- agno/api/evals.py +22 -0
- agno/api/os.py +17 -0
- agno/api/routes.py +13 -0
- agno/api/schemas/__init__.py +9 -0
- agno/api/schemas/agent.py +16 -0
- agno/api/schemas/evals.py +16 -0
- agno/api/schemas/os.py +14 -0
- agno/api/schemas/response.py +6 -0
- agno/api/schemas/team.py +16 -0
- agno/api/schemas/utils.py +21 -0
- agno/api/schemas/workflows.py +16 -0
- agno/api/settings.py +53 -0
- agno/api/team.py +30 -0
- agno/api/workflow.py +28 -0
- agno/cloud/aws/base.py +214 -0
- agno/cloud/aws/s3/__init__.py +2 -0
- agno/cloud/aws/s3/api_client.py +43 -0
- agno/cloud/aws/s3/bucket.py +195 -0
- agno/cloud/aws/s3/object.py +57 -0
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/__init__.py +24 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +598 -0
- agno/db/dynamo/__init__.py +3 -0
- agno/db/dynamo/dynamo.py +2042 -0
- agno/db/dynamo/schemas.py +314 -0
- agno/db/dynamo/utils.py +743 -0
- agno/db/firestore/__init__.py +3 -0
- agno/db/firestore/firestore.py +1795 -0
- agno/db/firestore/schemas.py +140 -0
- agno/db/firestore/utils.py +376 -0
- agno/db/gcs_json/__init__.py +3 -0
- agno/db/gcs_json/gcs_json_db.py +1335 -0
- agno/db/gcs_json/utils.py +228 -0
- agno/db/in_memory/__init__.py +3 -0
- agno/db/in_memory/in_memory_db.py +1160 -0
- agno/db/in_memory/utils.py +230 -0
- agno/db/json/__init__.py +3 -0
- agno/db/json/json_db.py +1328 -0
- agno/db/json/utils.py +230 -0
- agno/db/migrations/__init__.py +0 -0
- agno/db/migrations/v1_to_v2.py +635 -0
- agno/db/mongo/__init__.py +17 -0
- agno/db/mongo/async_mongo.py +2026 -0
- agno/db/mongo/mongo.py +1982 -0
- agno/db/mongo/schemas.py +87 -0
- agno/db/mongo/utils.py +259 -0
- agno/db/mysql/__init__.py +3 -0
- agno/db/mysql/mysql.py +2308 -0
- agno/db/mysql/schemas.py +138 -0
- agno/db/mysql/utils.py +355 -0
- agno/db/postgres/__init__.py +4 -0
- agno/db/postgres/async_postgres.py +1927 -0
- agno/db/postgres/postgres.py +2260 -0
- agno/db/postgres/schemas.py +139 -0
- agno/db/postgres/utils.py +442 -0
- agno/db/redis/__init__.py +3 -0
- agno/db/redis/redis.py +1660 -0
- agno/db/redis/schemas.py +123 -0
- agno/db/redis/utils.py +346 -0
- agno/db/schemas/__init__.py +4 -0
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/evals.py +33 -0
- agno/db/schemas/knowledge.py +40 -0
- agno/db/schemas/memory.py +46 -0
- agno/db/schemas/metrics.py +0 -0
- agno/db/singlestore/__init__.py +3 -0
- agno/db/singlestore/schemas.py +130 -0
- agno/db/singlestore/singlestore.py +2272 -0
- agno/db/singlestore/utils.py +384 -0
- agno/db/sqlite/__init__.py +4 -0
- agno/db/sqlite/async_sqlite.py +2293 -0
- agno/db/sqlite/schemas.py +133 -0
- agno/db/sqlite/sqlite.py +2288 -0
- agno/db/sqlite/utils.py +431 -0
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +309 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1353 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +116 -0
- agno/debug.py +18 -0
- agno/eval/__init__.py +14 -0
- agno/eval/accuracy.py +834 -0
- agno/eval/performance.py +773 -0
- agno/eval/reliability.py +306 -0
- agno/eval/utils.py +119 -0
- agno/exceptions.py +161 -0
- agno/filters.py +354 -0
- agno/guardrails/__init__.py +6 -0
- agno/guardrails/base.py +19 -0
- agno/guardrails/openai.py +144 -0
- agno/guardrails/pii.py +94 -0
- agno/guardrails/prompt_injection.py +52 -0
- agno/integrations/__init__.py +0 -0
- agno/integrations/discord/__init__.py +3 -0
- agno/integrations/discord/client.py +203 -0
- agno/knowledge/__init__.py +5 -0
- agno/knowledge/chunking/__init__.py +0 -0
- agno/knowledge/chunking/agentic.py +79 -0
- agno/knowledge/chunking/document.py +91 -0
- agno/knowledge/chunking/fixed.py +57 -0
- agno/knowledge/chunking/markdown.py +151 -0
- agno/knowledge/chunking/recursive.py +63 -0
- agno/knowledge/chunking/row.py +39 -0
- agno/knowledge/chunking/semantic.py +86 -0
- agno/knowledge/chunking/strategy.py +165 -0
- agno/knowledge/content.py +74 -0
- agno/knowledge/document/__init__.py +5 -0
- agno/knowledge/document/base.py +58 -0
- agno/knowledge/embedder/__init__.py +5 -0
- agno/knowledge/embedder/aws_bedrock.py +343 -0
- agno/knowledge/embedder/azure_openai.py +210 -0
- agno/knowledge/embedder/base.py +23 -0
- agno/knowledge/embedder/cohere.py +323 -0
- agno/knowledge/embedder/fastembed.py +62 -0
- agno/knowledge/embedder/fireworks.py +13 -0
- agno/knowledge/embedder/google.py +258 -0
- agno/knowledge/embedder/huggingface.py +94 -0
- agno/knowledge/embedder/jina.py +182 -0
- agno/knowledge/embedder/langdb.py +22 -0
- agno/knowledge/embedder/mistral.py +206 -0
- agno/knowledge/embedder/nebius.py +13 -0
- agno/knowledge/embedder/ollama.py +154 -0
- agno/knowledge/embedder/openai.py +195 -0
- agno/knowledge/embedder/sentence_transformer.py +63 -0
- agno/knowledge/embedder/together.py +13 -0
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/embedder/voyageai.py +165 -0
- agno/knowledge/knowledge.py +1988 -0
- agno/knowledge/reader/__init__.py +7 -0
- agno/knowledge/reader/arxiv_reader.py +81 -0
- agno/knowledge/reader/base.py +95 -0
- agno/knowledge/reader/csv_reader.py +166 -0
- agno/knowledge/reader/docx_reader.py +82 -0
- agno/knowledge/reader/field_labeled_csv_reader.py +292 -0
- agno/knowledge/reader/firecrawl_reader.py +201 -0
- agno/knowledge/reader/json_reader.py +87 -0
- agno/knowledge/reader/markdown_reader.py +137 -0
- agno/knowledge/reader/pdf_reader.py +431 -0
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +313 -0
- agno/knowledge/reader/s3_reader.py +89 -0
- agno/knowledge/reader/tavily_reader.py +194 -0
- agno/knowledge/reader/text_reader.py +115 -0
- agno/knowledge/reader/web_search_reader.py +372 -0
- agno/knowledge/reader/website_reader.py +455 -0
- agno/knowledge/reader/wikipedia_reader.py +59 -0
- agno/knowledge/reader/youtube_reader.py +78 -0
- agno/knowledge/remote_content/__init__.py +0 -0
- agno/knowledge/remote_content/remote_content.py +88 -0
- agno/knowledge/reranker/__init__.py +3 -0
- agno/knowledge/reranker/base.py +14 -0
- agno/knowledge/reranker/cohere.py +64 -0
- agno/knowledge/reranker/infinity.py +195 -0
- agno/knowledge/reranker/sentence_transformer.py +54 -0
- agno/knowledge/types.py +39 -0
- agno/knowledge/utils.py +189 -0
- agno/media.py +462 -0
- agno/memory/__init__.py +3 -0
- agno/memory/manager.py +1327 -0
- agno/models/__init__.py +0 -0
- agno/models/aimlapi/__init__.py +5 -0
- agno/models/aimlapi/aimlapi.py +45 -0
- agno/models/anthropic/__init__.py +5 -0
- agno/models/anthropic/claude.py +757 -0
- agno/models/aws/__init__.py +15 -0
- agno/models/aws/bedrock.py +701 -0
- agno/models/aws/claude.py +378 -0
- agno/models/azure/__init__.py +18 -0
- agno/models/azure/ai_foundry.py +485 -0
- agno/models/azure/openai_chat.py +131 -0
- agno/models/base.py +2175 -0
- agno/models/cerebras/__init__.py +12 -0
- agno/models/cerebras/cerebras.py +501 -0
- agno/models/cerebras/cerebras_openai.py +112 -0
- agno/models/cohere/__init__.py +5 -0
- agno/models/cohere/chat.py +389 -0
- agno/models/cometapi/__init__.py +5 -0
- agno/models/cometapi/cometapi.py +57 -0
- agno/models/dashscope/__init__.py +5 -0
- agno/models/dashscope/dashscope.py +91 -0
- agno/models/deepinfra/__init__.py +5 -0
- agno/models/deepinfra/deepinfra.py +28 -0
- agno/models/deepseek/__init__.py +5 -0
- agno/models/deepseek/deepseek.py +61 -0
- agno/models/defaults.py +1 -0
- agno/models/fireworks/__init__.py +5 -0
- agno/models/fireworks/fireworks.py +26 -0
- agno/models/google/__init__.py +5 -0
- agno/models/google/gemini.py +1085 -0
- agno/models/groq/__init__.py +5 -0
- agno/models/groq/groq.py +556 -0
- agno/models/huggingface/__init__.py +5 -0
- agno/models/huggingface/huggingface.py +491 -0
- agno/models/ibm/__init__.py +5 -0
- agno/models/ibm/watsonx.py +422 -0
- agno/models/internlm/__init__.py +3 -0
- agno/models/internlm/internlm.py +26 -0
- agno/models/langdb/__init__.py +1 -0
- agno/models/langdb/langdb.py +48 -0
- agno/models/litellm/__init__.py +14 -0
- agno/models/litellm/chat.py +468 -0
- agno/models/litellm/litellm_openai.py +25 -0
- agno/models/llama_cpp/__init__.py +5 -0
- agno/models/llama_cpp/llama_cpp.py +22 -0
- agno/models/lmstudio/__init__.py +5 -0
- agno/models/lmstudio/lmstudio.py +25 -0
- agno/models/message.py +434 -0
- agno/models/meta/__init__.py +12 -0
- agno/models/meta/llama.py +475 -0
- agno/models/meta/llama_openai.py +78 -0
- agno/models/metrics.py +120 -0
- agno/models/mistral/__init__.py +5 -0
- agno/models/mistral/mistral.py +432 -0
- agno/models/nebius/__init__.py +3 -0
- agno/models/nebius/nebius.py +54 -0
- agno/models/nexus/__init__.py +3 -0
- agno/models/nexus/nexus.py +22 -0
- agno/models/nvidia/__init__.py +5 -0
- agno/models/nvidia/nvidia.py +28 -0
- agno/models/ollama/__init__.py +5 -0
- agno/models/ollama/chat.py +441 -0
- agno/models/openai/__init__.py +9 -0
- agno/models/openai/chat.py +883 -0
- agno/models/openai/like.py +27 -0
- agno/models/openai/responses.py +1050 -0
- agno/models/openrouter/__init__.py +5 -0
- agno/models/openrouter/openrouter.py +66 -0
- agno/models/perplexity/__init__.py +5 -0
- agno/models/perplexity/perplexity.py +187 -0
- agno/models/portkey/__init__.py +3 -0
- agno/models/portkey/portkey.py +81 -0
- agno/models/requesty/__init__.py +5 -0
- agno/models/requesty/requesty.py +52 -0
- agno/models/response.py +199 -0
- agno/models/sambanova/__init__.py +5 -0
- agno/models/sambanova/sambanova.py +28 -0
- agno/models/siliconflow/__init__.py +5 -0
- agno/models/siliconflow/siliconflow.py +25 -0
- agno/models/together/__init__.py +5 -0
- agno/models/together/together.py +25 -0
- agno/models/utils.py +266 -0
- agno/models/vercel/__init__.py +3 -0
- agno/models/vercel/v0.py +26 -0
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +70 -0
- agno/models/vllm/__init__.py +3 -0
- agno/models/vllm/vllm.py +78 -0
- agno/models/xai/__init__.py +3 -0
- agno/models/xai/xai.py +113 -0
- agno/os/__init__.py +3 -0
- agno/os/app.py +876 -0
- agno/os/auth.py +57 -0
- agno/os/config.py +104 -0
- agno/os/interfaces/__init__.py +1 -0
- agno/os/interfaces/a2a/__init__.py +3 -0
- agno/os/interfaces/a2a/a2a.py +42 -0
- agno/os/interfaces/a2a/router.py +250 -0
- agno/os/interfaces/a2a/utils.py +924 -0
- agno/os/interfaces/agui/__init__.py +3 -0
- agno/os/interfaces/agui/agui.py +47 -0
- agno/os/interfaces/agui/router.py +144 -0
- agno/os/interfaces/agui/utils.py +534 -0
- agno/os/interfaces/base.py +25 -0
- agno/os/interfaces/slack/__init__.py +3 -0
- agno/os/interfaces/slack/router.py +148 -0
- agno/os/interfaces/slack/security.py +30 -0
- agno/os/interfaces/slack/slack.py +47 -0
- agno/os/interfaces/whatsapp/__init__.py +3 -0
- agno/os/interfaces/whatsapp/router.py +211 -0
- agno/os/interfaces/whatsapp/security.py +53 -0
- agno/os/interfaces/whatsapp/whatsapp.py +36 -0
- agno/os/mcp.py +292 -0
- agno/os/middleware/__init__.py +7 -0
- agno/os/middleware/jwt.py +233 -0
- agno/os/router.py +1763 -0
- agno/os/routers/__init__.py +3 -0
- agno/os/routers/evals/__init__.py +3 -0
- agno/os/routers/evals/evals.py +430 -0
- agno/os/routers/evals/schemas.py +142 -0
- agno/os/routers/evals/utils.py +162 -0
- agno/os/routers/health.py +31 -0
- agno/os/routers/home.py +52 -0
- agno/os/routers/knowledge/__init__.py +3 -0
- agno/os/routers/knowledge/knowledge.py +997 -0
- agno/os/routers/knowledge/schemas.py +178 -0
- agno/os/routers/memory/__init__.py +3 -0
- agno/os/routers/memory/memory.py +515 -0
- agno/os/routers/memory/schemas.py +62 -0
- agno/os/routers/metrics/__init__.py +3 -0
- agno/os/routers/metrics/metrics.py +190 -0
- agno/os/routers/metrics/schemas.py +47 -0
- agno/os/routers/session/__init__.py +3 -0
- agno/os/routers/session/session.py +997 -0
- agno/os/schema.py +1055 -0
- agno/os/settings.py +43 -0
- agno/os/utils.py +630 -0
- agno/py.typed +0 -0
- agno/reasoning/__init__.py +0 -0
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/azure_ai_foundry.py +67 -0
- agno/reasoning/deepseek.py +63 -0
- agno/reasoning/default.py +97 -0
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/groq.py +71 -0
- agno/reasoning/helpers.py +63 -0
- agno/reasoning/ollama.py +67 -0
- agno/reasoning/openai.py +86 -0
- agno/reasoning/step.py +31 -0
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +787 -0
- agno/run/base.py +229 -0
- agno/run/cancel.py +81 -0
- agno/run/messages.py +32 -0
- agno/run/team.py +753 -0
- agno/run/workflow.py +708 -0
- agno/session/__init__.py +10 -0
- agno/session/agent.py +295 -0
- agno/session/summary.py +265 -0
- agno/session/team.py +392 -0
- agno/session/workflow.py +205 -0
- agno/team/__init__.py +37 -0
- agno/team/team.py +8793 -0
- agno/tools/__init__.py +10 -0
- agno/tools/agentql.py +120 -0
- agno/tools/airflow.py +69 -0
- agno/tools/api.py +122 -0
- agno/tools/apify.py +314 -0
- agno/tools/arxiv.py +127 -0
- agno/tools/aws_lambda.py +53 -0
- agno/tools/aws_ses.py +66 -0
- agno/tools/baidusearch.py +89 -0
- agno/tools/bitbucket.py +292 -0
- agno/tools/brandfetch.py +213 -0
- agno/tools/bravesearch.py +106 -0
- agno/tools/brightdata.py +367 -0
- agno/tools/browserbase.py +209 -0
- agno/tools/calcom.py +255 -0
- agno/tools/calculator.py +151 -0
- agno/tools/cartesia.py +187 -0
- agno/tools/clickup.py +244 -0
- agno/tools/confluence.py +240 -0
- agno/tools/crawl4ai.py +158 -0
- agno/tools/csv_toolkit.py +185 -0
- agno/tools/dalle.py +110 -0
- agno/tools/daytona.py +475 -0
- agno/tools/decorator.py +262 -0
- agno/tools/desi_vocal.py +108 -0
- agno/tools/discord.py +161 -0
- agno/tools/docker.py +716 -0
- agno/tools/duckdb.py +379 -0
- agno/tools/duckduckgo.py +91 -0
- agno/tools/e2b.py +703 -0
- agno/tools/eleven_labs.py +196 -0
- agno/tools/email.py +67 -0
- agno/tools/evm.py +129 -0
- agno/tools/exa.py +396 -0
- agno/tools/fal.py +127 -0
- agno/tools/file.py +240 -0
- agno/tools/file_generation.py +350 -0
- agno/tools/financial_datasets.py +288 -0
- agno/tools/firecrawl.py +143 -0
- agno/tools/function.py +1187 -0
- agno/tools/giphy.py +93 -0
- agno/tools/github.py +1760 -0
- agno/tools/gmail.py +922 -0
- agno/tools/google_bigquery.py +117 -0
- agno/tools/google_drive.py +270 -0
- agno/tools/google_maps.py +253 -0
- agno/tools/googlecalendar.py +674 -0
- agno/tools/googlesearch.py +98 -0
- agno/tools/googlesheets.py +377 -0
- agno/tools/hackernews.py +77 -0
- agno/tools/jina.py +101 -0
- agno/tools/jira.py +170 -0
- agno/tools/knowledge.py +218 -0
- agno/tools/linear.py +426 -0
- agno/tools/linkup.py +58 -0
- agno/tools/local_file_system.py +90 -0
- agno/tools/lumalab.py +183 -0
- agno/tools/mcp/__init__.py +10 -0
- agno/tools/mcp/mcp.py +331 -0
- agno/tools/mcp/multi_mcp.py +347 -0
- agno/tools/mcp/params.py +24 -0
- agno/tools/mcp_toolbox.py +284 -0
- agno/tools/mem0.py +193 -0
- agno/tools/memori.py +339 -0
- agno/tools/memory.py +419 -0
- agno/tools/mlx_transcribe.py +139 -0
- agno/tools/models/__init__.py +0 -0
- agno/tools/models/azure_openai.py +190 -0
- agno/tools/models/gemini.py +203 -0
- agno/tools/models/groq.py +158 -0
- agno/tools/models/morph.py +186 -0
- agno/tools/models/nebius.py +124 -0
- agno/tools/models_labs.py +195 -0
- agno/tools/moviepy_video.py +349 -0
- agno/tools/neo4j.py +134 -0
- agno/tools/newspaper.py +46 -0
- agno/tools/newspaper4k.py +93 -0
- agno/tools/notion.py +204 -0
- agno/tools/openai.py +202 -0
- agno/tools/openbb.py +160 -0
- agno/tools/opencv.py +321 -0
- agno/tools/openweather.py +233 -0
- agno/tools/oxylabs.py +385 -0
- agno/tools/pandas.py +102 -0
- agno/tools/parallel.py +314 -0
- agno/tools/postgres.py +257 -0
- agno/tools/pubmed.py +188 -0
- agno/tools/python.py +205 -0
- agno/tools/reasoning.py +283 -0
- agno/tools/reddit.py +467 -0
- agno/tools/replicate.py +117 -0
- agno/tools/resend.py +62 -0
- agno/tools/scrapegraph.py +222 -0
- agno/tools/searxng.py +152 -0
- agno/tools/serpapi.py +116 -0
- agno/tools/serper.py +255 -0
- agno/tools/shell.py +53 -0
- agno/tools/slack.py +136 -0
- agno/tools/sleep.py +20 -0
- agno/tools/spider.py +116 -0
- agno/tools/sql.py +154 -0
- agno/tools/streamlit/__init__.py +0 -0
- agno/tools/streamlit/components.py +113 -0
- agno/tools/tavily.py +254 -0
- agno/tools/telegram.py +48 -0
- agno/tools/todoist.py +218 -0
- agno/tools/tool_registry.py +1 -0
- agno/tools/toolkit.py +146 -0
- agno/tools/trafilatura.py +388 -0
- agno/tools/trello.py +274 -0
- agno/tools/twilio.py +186 -0
- agno/tools/user_control_flow.py +78 -0
- agno/tools/valyu.py +228 -0
- agno/tools/visualization.py +467 -0
- agno/tools/webbrowser.py +28 -0
- agno/tools/webex.py +76 -0
- agno/tools/website.py +54 -0
- agno/tools/webtools.py +45 -0
- agno/tools/whatsapp.py +286 -0
- agno/tools/wikipedia.py +63 -0
- agno/tools/workflow.py +278 -0
- agno/tools/x.py +335 -0
- agno/tools/yfinance.py +257 -0
- agno/tools/youtube.py +184 -0
- agno/tools/zendesk.py +82 -0
- agno/tools/zep.py +454 -0
- agno/tools/zoom.py +382 -0
- agno/utils/__init__.py +0 -0
- agno/utils/agent.py +820 -0
- agno/utils/audio.py +49 -0
- agno/utils/certs.py +27 -0
- agno/utils/code_execution.py +11 -0
- agno/utils/common.py +132 -0
- agno/utils/dttm.py +13 -0
- agno/utils/enum.py +22 -0
- agno/utils/env.py +11 -0
- agno/utils/events.py +696 -0
- agno/utils/format_str.py +16 -0
- agno/utils/functions.py +166 -0
- agno/utils/gemini.py +426 -0
- agno/utils/hooks.py +57 -0
- agno/utils/http.py +74 -0
- agno/utils/json_schema.py +234 -0
- agno/utils/knowledge.py +36 -0
- agno/utils/location.py +19 -0
- agno/utils/log.py +255 -0
- agno/utils/mcp.py +214 -0
- agno/utils/media.py +352 -0
- agno/utils/merge_dict.py +41 -0
- agno/utils/message.py +118 -0
- agno/utils/models/__init__.py +0 -0
- agno/utils/models/ai_foundry.py +43 -0
- agno/utils/models/claude.py +358 -0
- agno/utils/models/cohere.py +87 -0
- agno/utils/models/llama.py +78 -0
- agno/utils/models/mistral.py +98 -0
- agno/utils/models/openai_responses.py +140 -0
- agno/utils/models/schema_utils.py +153 -0
- agno/utils/models/watsonx.py +41 -0
- agno/utils/openai.py +257 -0
- agno/utils/pickle.py +32 -0
- agno/utils/pprint.py +178 -0
- agno/utils/print_response/__init__.py +0 -0
- agno/utils/print_response/agent.py +842 -0
- agno/utils/print_response/team.py +1724 -0
- agno/utils/print_response/workflow.py +1668 -0
- agno/utils/prompts.py +111 -0
- agno/utils/reasoning.py +108 -0
- agno/utils/response.py +163 -0
- agno/utils/response_iterator.py +17 -0
- agno/utils/safe_formatter.py +24 -0
- agno/utils/serialize.py +32 -0
- agno/utils/shell.py +22 -0
- agno/utils/streamlit.py +487 -0
- agno/utils/string.py +231 -0
- agno/utils/team.py +139 -0
- agno/utils/timer.py +41 -0
- agno/utils/tools.py +102 -0
- agno/utils/web.py +23 -0
- agno/utils/whatsapp.py +305 -0
- agno/utils/yaml_io.py +25 -0
- agno/vectordb/__init__.py +3 -0
- agno/vectordb/base.py +127 -0
- agno/vectordb/cassandra/__init__.py +5 -0
- agno/vectordb/cassandra/cassandra.py +501 -0
- agno/vectordb/cassandra/extra_param_mixin.py +11 -0
- agno/vectordb/cassandra/index.py +13 -0
- agno/vectordb/chroma/__init__.py +5 -0
- agno/vectordb/chroma/chromadb.py +929 -0
- agno/vectordb/clickhouse/__init__.py +9 -0
- agno/vectordb/clickhouse/clickhousedb.py +835 -0
- agno/vectordb/clickhouse/index.py +9 -0
- agno/vectordb/couchbase/__init__.py +3 -0
- agno/vectordb/couchbase/couchbase.py +1442 -0
- agno/vectordb/distance.py +7 -0
- agno/vectordb/lancedb/__init__.py +6 -0
- agno/vectordb/lancedb/lance_db.py +995 -0
- agno/vectordb/langchaindb/__init__.py +5 -0
- agno/vectordb/langchaindb/langchaindb.py +163 -0
- agno/vectordb/lightrag/__init__.py +5 -0
- agno/vectordb/lightrag/lightrag.py +388 -0
- agno/vectordb/llamaindex/__init__.py +3 -0
- agno/vectordb/llamaindex/llamaindexdb.py +166 -0
- agno/vectordb/milvus/__init__.py +4 -0
- agno/vectordb/milvus/milvus.py +1182 -0
- agno/vectordb/mongodb/__init__.py +9 -0
- agno/vectordb/mongodb/mongodb.py +1417 -0
- agno/vectordb/pgvector/__init__.py +12 -0
- agno/vectordb/pgvector/index.py +23 -0
- agno/vectordb/pgvector/pgvector.py +1462 -0
- agno/vectordb/pineconedb/__init__.py +5 -0
- agno/vectordb/pineconedb/pineconedb.py +747 -0
- agno/vectordb/qdrant/__init__.py +5 -0
- agno/vectordb/qdrant/qdrant.py +1134 -0
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +694 -0
- agno/vectordb/search.py +7 -0
- agno/vectordb/singlestore/__init__.py +10 -0
- agno/vectordb/singlestore/index.py +41 -0
- agno/vectordb/singlestore/singlestore.py +763 -0
- agno/vectordb/surrealdb/__init__.py +3 -0
- agno/vectordb/surrealdb/surrealdb.py +699 -0
- agno/vectordb/upstashdb/__init__.py +5 -0
- agno/vectordb/upstashdb/upstashdb.py +718 -0
- agno/vectordb/weaviate/__init__.py +8 -0
- agno/vectordb/weaviate/index.py +15 -0
- agno/vectordb/weaviate/weaviate.py +1005 -0
- agno/workflow/__init__.py +23 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +738 -0
- agno/workflow/loop.py +735 -0
- agno/workflow/parallel.py +824 -0
- agno/workflow/router.py +702 -0
- agno/workflow/step.py +1432 -0
- agno/workflow/steps.py +592 -0
- agno/workflow/types.py +520 -0
- agno/workflow/workflow.py +4321 -0
- agno-2.2.13.dist-info/METADATA +614 -0
- agno-2.2.13.dist-info/RECORD +575 -0
- agno-2.2.13.dist-info/WHEEL +5 -0
- agno-2.2.13.dist-info/licenses/LICENSE +201 -0
- agno-2.2.13.dist-info/top_level.txt +1 -0
agno/utils/mcp.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from functools import partial
|
|
3
|
+
from uuid import uuid4
|
|
4
|
+
|
|
5
|
+
from agno.utils.log import log_debug, log_exception
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from mcp import ClientSession
|
|
9
|
+
from mcp.types import CallToolResult, EmbeddedResource, ImageContent, TextContent
|
|
10
|
+
from mcp.types import Tool as MCPTool
|
|
11
|
+
except (ImportError, ModuleNotFoundError):
|
|
12
|
+
raise ImportError("`mcp` not installed. Please install using `pip install mcp`")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from agno.media import Image
|
|
16
|
+
from agno.tools.function import ToolResult
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_entrypoint_for_tool(tool: MCPTool, session: ClientSession):
|
|
20
|
+
"""
|
|
21
|
+
Return an entrypoint for an MCP tool.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
tool: The MCP tool to create an entrypoint for
|
|
25
|
+
session: The session to use
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Callable: The entrypoint function for the tool
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
async def call_tool(tool_name: str, **kwargs) -> ToolResult:
|
|
32
|
+
try:
|
|
33
|
+
await session.send_ping()
|
|
34
|
+
except Exception as e:
|
|
35
|
+
print(e)
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
log_debug(f"Calling MCP Tool '{tool_name}' with args: {kwargs}")
|
|
39
|
+
result: CallToolResult = await session.call_tool(tool_name, kwargs) # type: ignore
|
|
40
|
+
|
|
41
|
+
# Return an error if the tool call failed
|
|
42
|
+
if result.isError:
|
|
43
|
+
return ToolResult(content=f"Error from MCP tool '{tool_name}': {result.content}")
|
|
44
|
+
|
|
45
|
+
# Process the result content
|
|
46
|
+
response_str = ""
|
|
47
|
+
images = []
|
|
48
|
+
|
|
49
|
+
for content_item in result.content:
|
|
50
|
+
if isinstance(content_item, TextContent):
|
|
51
|
+
text_content = content_item.text
|
|
52
|
+
|
|
53
|
+
# Parse as JSON to check for custom image format
|
|
54
|
+
try:
|
|
55
|
+
parsed_json = json.loads(text_content)
|
|
56
|
+
if (
|
|
57
|
+
isinstance(parsed_json, dict)
|
|
58
|
+
and parsed_json.get("type") == "image"
|
|
59
|
+
and "data" in parsed_json
|
|
60
|
+
):
|
|
61
|
+
log_debug("Found custom JSON image format in TextContent")
|
|
62
|
+
|
|
63
|
+
# Extract image data
|
|
64
|
+
image_data = parsed_json.get("data")
|
|
65
|
+
mime_type = parsed_json.get("mimeType", "image/png")
|
|
66
|
+
|
|
67
|
+
if image_data and isinstance(image_data, str):
|
|
68
|
+
import base64
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
image_bytes = base64.b64decode(image_data)
|
|
72
|
+
except Exception as e:
|
|
73
|
+
log_debug(f"Failed to decode base64 image data: {e}")
|
|
74
|
+
image_bytes = None
|
|
75
|
+
|
|
76
|
+
if image_bytes:
|
|
77
|
+
img_artifact = Image(
|
|
78
|
+
id=str(uuid4()),
|
|
79
|
+
url=None,
|
|
80
|
+
content=image_bytes,
|
|
81
|
+
mime_type=mime_type,
|
|
82
|
+
)
|
|
83
|
+
images.append(img_artifact)
|
|
84
|
+
response_str += "Image has been generated and added to the response.\n"
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
except (json.JSONDecodeError, TypeError):
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
response_str += text_content + "\n"
|
|
91
|
+
|
|
92
|
+
elif isinstance(content_item, ImageContent):
|
|
93
|
+
# Handle standard MCP ImageContent
|
|
94
|
+
image_data = getattr(content_item, "data", None)
|
|
95
|
+
|
|
96
|
+
if image_data and isinstance(image_data, str):
|
|
97
|
+
import base64
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
image_data = base64.b64decode(image_data)
|
|
101
|
+
except Exception as e:
|
|
102
|
+
log_debug(f"Failed to decode base64 image data: {e}")
|
|
103
|
+
image_data = None
|
|
104
|
+
|
|
105
|
+
img_artifact = Image(
|
|
106
|
+
id=str(uuid4()),
|
|
107
|
+
url=getattr(content_item, "url", None),
|
|
108
|
+
content=image_data,
|
|
109
|
+
mime_type=getattr(content_item, "mimeType", "image/png"),
|
|
110
|
+
)
|
|
111
|
+
images.append(img_artifact)
|
|
112
|
+
response_str += "Image has been generated and added to the response.\n"
|
|
113
|
+
elif isinstance(content_item, EmbeddedResource):
|
|
114
|
+
# Handle embedded resources
|
|
115
|
+
response_str += f"[Embedded resource: {content_item.resource.model_dump_json()}]\n"
|
|
116
|
+
else:
|
|
117
|
+
# Handle other content types
|
|
118
|
+
response_str += f"[Unsupported content type: {content_item.type}]\n"
|
|
119
|
+
|
|
120
|
+
return ToolResult(
|
|
121
|
+
content=response_str.strip(),
|
|
122
|
+
images=images if images else None,
|
|
123
|
+
)
|
|
124
|
+
except Exception as e:
|
|
125
|
+
log_exception(f"Failed to call MCP tool '{tool_name}': {e}")
|
|
126
|
+
return ToolResult(content=f"Error: {e}")
|
|
127
|
+
|
|
128
|
+
return partial(call_tool, tool_name=tool.name)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def prepare_command(command: str) -> list[str]:
|
|
132
|
+
"""Sanitize a command and split it into parts before using it to run a MCP server."""
|
|
133
|
+
import os
|
|
134
|
+
import shutil
|
|
135
|
+
from shlex import split
|
|
136
|
+
|
|
137
|
+
# Block dangerous characters
|
|
138
|
+
if any(char in command for char in ["&", "|", ";", "`", "$", "(", ")"]):
|
|
139
|
+
raise ValueError("MCP command can't contain shell metacharacters")
|
|
140
|
+
|
|
141
|
+
parts = split(command)
|
|
142
|
+
if not parts:
|
|
143
|
+
raise ValueError("MCP command can't be empty")
|
|
144
|
+
|
|
145
|
+
# Only allow specific executables
|
|
146
|
+
ALLOWED_COMMANDS = {
|
|
147
|
+
# Python
|
|
148
|
+
"python",
|
|
149
|
+
"python3",
|
|
150
|
+
"uv",
|
|
151
|
+
"uvx",
|
|
152
|
+
"pipx",
|
|
153
|
+
# Node
|
|
154
|
+
"node",
|
|
155
|
+
"npm",
|
|
156
|
+
"npx",
|
|
157
|
+
"yarn",
|
|
158
|
+
"pnpm",
|
|
159
|
+
"bun",
|
|
160
|
+
# Other runtimes
|
|
161
|
+
"deno",
|
|
162
|
+
"java",
|
|
163
|
+
"ruby",
|
|
164
|
+
"docker",
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
executable = parts[0].split("/")[-1]
|
|
168
|
+
|
|
169
|
+
# Check if it's a relative path starting with ./ or ../
|
|
170
|
+
if executable.startswith("./") or executable.startswith("../"):
|
|
171
|
+
# Allow relative paths to binaries
|
|
172
|
+
return parts
|
|
173
|
+
|
|
174
|
+
# Check if it's an absolute path to a binary
|
|
175
|
+
if executable.startswith("/") and os.path.isfile(executable):
|
|
176
|
+
# Allow absolute paths to existing files
|
|
177
|
+
return parts
|
|
178
|
+
|
|
179
|
+
# Check if it's a binary in current directory without ./
|
|
180
|
+
if "/" not in executable and os.path.isfile(executable):
|
|
181
|
+
# Allow binaries in current directory
|
|
182
|
+
return parts
|
|
183
|
+
|
|
184
|
+
# Check if it's a binary in PATH
|
|
185
|
+
if shutil.which(executable):
|
|
186
|
+
return parts
|
|
187
|
+
|
|
188
|
+
if executable not in ALLOWED_COMMANDS:
|
|
189
|
+
raise ValueError(f"MCP command needs to use one of the following executables: {ALLOWED_COMMANDS}")
|
|
190
|
+
|
|
191
|
+
first_part = parts[0]
|
|
192
|
+
executable = first_part.split("/")[-1]
|
|
193
|
+
|
|
194
|
+
# Allow known commands
|
|
195
|
+
if executable in ALLOWED_COMMANDS:
|
|
196
|
+
return parts
|
|
197
|
+
|
|
198
|
+
# Allow relative paths to custom binaries
|
|
199
|
+
if first_part.startswith(("./", "../")):
|
|
200
|
+
return parts
|
|
201
|
+
|
|
202
|
+
# Allow absolute paths to existing files
|
|
203
|
+
if first_part.startswith("/") and os.path.isfile(first_part):
|
|
204
|
+
return parts
|
|
205
|
+
|
|
206
|
+
# Allow binaries in current directory without ./
|
|
207
|
+
if "/" not in first_part and os.path.isfile(first_part):
|
|
208
|
+
return parts
|
|
209
|
+
|
|
210
|
+
# Allow binaries in PATH
|
|
211
|
+
if shutil.which(first_part):
|
|
212
|
+
return parts
|
|
213
|
+
|
|
214
|
+
raise ValueError(f"MCP command needs to use one of the following executables: {ALLOWED_COMMANDS}")
|
agno/utils/media.py
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import time
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
from agno.media import Audio, File, Image, Video
|
|
10
|
+
from agno.utils.log import log_info, log_warning
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SampleDataFileExtension(str, Enum):
|
|
14
|
+
DOCX = "docx"
|
|
15
|
+
PDF = "pdf"
|
|
16
|
+
TXT = "txt"
|
|
17
|
+
JSON = "json"
|
|
18
|
+
CSV = "csv"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def download_image(url: str, output_path: str) -> bool:
|
|
22
|
+
"""
|
|
23
|
+
Downloads an image from the specified URL and saves it to the given local path.
|
|
24
|
+
Parameters:
|
|
25
|
+
- url (str): URL of the image to download.
|
|
26
|
+
- output_path (str): Local filesystem path to save the image
|
|
27
|
+
"""
|
|
28
|
+
try:
|
|
29
|
+
# Send HTTP GET request to the image URL
|
|
30
|
+
response = httpx.get(url)
|
|
31
|
+
response.raise_for_status() # Raise an exception for HTTP errors
|
|
32
|
+
|
|
33
|
+
# Check if the response contains image content
|
|
34
|
+
content_type = response.headers.get("Content-Type")
|
|
35
|
+
if not content_type or not content_type.startswith("image"):
|
|
36
|
+
log_warning(f"URL does not point to an image. Content-Type: {content_type}")
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
path = Path(output_path)
|
|
40
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
41
|
+
|
|
42
|
+
# Write the image to the local file in binary mode
|
|
43
|
+
with open(output_path, "wb") as file:
|
|
44
|
+
for chunk in response.iter_bytes(chunk_size=8192):
|
|
45
|
+
if chunk:
|
|
46
|
+
file.write(chunk)
|
|
47
|
+
|
|
48
|
+
log_info(f"Image successfully downloaded and saved to '{output_path}'.")
|
|
49
|
+
return True
|
|
50
|
+
|
|
51
|
+
except httpx.HTTPError as e:
|
|
52
|
+
log_warning(f"Error downloading the image: {e}")
|
|
53
|
+
return False
|
|
54
|
+
except IOError as e:
|
|
55
|
+
log_warning(f"Error saving the image to '{output_path}': {e}")
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def download_video(url: str, output_path: str) -> str:
|
|
60
|
+
"""Download video from URL"""
|
|
61
|
+
response = httpx.get(url)
|
|
62
|
+
response.raise_for_status()
|
|
63
|
+
|
|
64
|
+
with open(output_path, "wb") as f:
|
|
65
|
+
for chunk in response.iter_bytes(chunk_size=8192):
|
|
66
|
+
f.write(chunk)
|
|
67
|
+
return output_path
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def download_file(url: str, output_path: str) -> None:
|
|
71
|
+
"""
|
|
72
|
+
Download a file from a given URL and save it to the specified path.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
url (str): The URL of the file to download
|
|
76
|
+
output_path (str): The local path where the file should be saved
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
httpx.HTTPError: If the download fails
|
|
80
|
+
"""
|
|
81
|
+
try:
|
|
82
|
+
response = httpx.get(url)
|
|
83
|
+
response.raise_for_status()
|
|
84
|
+
|
|
85
|
+
output_file = Path(output_path)
|
|
86
|
+
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
87
|
+
|
|
88
|
+
with open(output_file, "wb") as f:
|
|
89
|
+
for chunk in response.iter_bytes(chunk_size=8192):
|
|
90
|
+
if chunk:
|
|
91
|
+
f.write(chunk)
|
|
92
|
+
|
|
93
|
+
except httpx.HTTPError as e:
|
|
94
|
+
raise Exception(f"Failed to download file from {url}: {str(e)}")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def save_base64_data(base64_data: str, output_path: str) -> bool:
|
|
98
|
+
"""
|
|
99
|
+
Saves base64 string to the specified path as bytes.
|
|
100
|
+
"""
|
|
101
|
+
try:
|
|
102
|
+
# Decode the base64 string into bytes
|
|
103
|
+
decoded_data = base64.b64decode(base64_data)
|
|
104
|
+
except Exception as e:
|
|
105
|
+
raise Exception(f"An unexpected error occurred during base64 decoding: {e}")
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
path = Path(output_path)
|
|
109
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
110
|
+
|
|
111
|
+
# Write the bytes to the local file in binary mode
|
|
112
|
+
with open(path, "wb") as file:
|
|
113
|
+
file.write(decoded_data)
|
|
114
|
+
|
|
115
|
+
log_info(f"Data successfully saved to '{path}'.")
|
|
116
|
+
return True
|
|
117
|
+
except Exception as e:
|
|
118
|
+
raise Exception(f"An unexpected error occurred while saving data to '{output_path}': {e}")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def wait_for_media_ready(url: str, timeout: int = 120, interval: int = 5, verbose: bool = True) -> bool:
|
|
122
|
+
"""
|
|
123
|
+
Wait for media to be ready at URL by polling with HEAD requests.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
url (str): The URL to check for media availability
|
|
127
|
+
timeout (int): Maximum time to wait in seconds (default: 120)
|
|
128
|
+
interval (int): Seconds between each check (default: 5)
|
|
129
|
+
verbose (bool): Whether to print progress messages (default: True)
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
bool: True if media is ready, False if timeout reached
|
|
133
|
+
"""
|
|
134
|
+
max_attempts = timeout // interval
|
|
135
|
+
|
|
136
|
+
if verbose:
|
|
137
|
+
log_info("Media generated! Waiting for upload to complete...")
|
|
138
|
+
|
|
139
|
+
for attempt in range(max_attempts):
|
|
140
|
+
try:
|
|
141
|
+
response = httpx.head(url, timeout=10)
|
|
142
|
+
response.raise_for_status()
|
|
143
|
+
if verbose:
|
|
144
|
+
log_info(f"Media ready: {url}")
|
|
145
|
+
return True
|
|
146
|
+
except httpx.HTTPError:
|
|
147
|
+
pass
|
|
148
|
+
|
|
149
|
+
if verbose and (attempt + 1) % 3 == 0:
|
|
150
|
+
log_info(f"Still processing... ({(attempt + 1) * interval}s elapsed)")
|
|
151
|
+
|
|
152
|
+
time.sleep(interval)
|
|
153
|
+
|
|
154
|
+
if verbose:
|
|
155
|
+
log_warning(f"Timeout waiting for media. Try this URL later: {url}")
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def download_knowledge_filters_sample_data(
|
|
160
|
+
num_files: int = 5, file_extension: SampleDataFileExtension = SampleDataFileExtension.DOCX
|
|
161
|
+
) -> List[str]:
|
|
162
|
+
"""
|
|
163
|
+
Download sample data files with configurable file extension.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
num_files (int): Number of files to download
|
|
167
|
+
file_extension (SampleDataFileExtension): File extension type (DOCX, PDF, TXT, JSON)
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
List[str]: List of paths to downloaded files
|
|
171
|
+
"""
|
|
172
|
+
file_paths = []
|
|
173
|
+
root_path = Path.cwd()
|
|
174
|
+
|
|
175
|
+
for i in range(1, num_files + 1):
|
|
176
|
+
if file_extension == SampleDataFileExtension.CSV:
|
|
177
|
+
filename = f"filters_{i}.csv"
|
|
178
|
+
else:
|
|
179
|
+
filename = f"cv_{i}.{file_extension.value}"
|
|
180
|
+
|
|
181
|
+
download_path = root_path / "cookbook" / "data" / filename
|
|
182
|
+
download_path.parent.mkdir(parents=True, exist_ok=True)
|
|
183
|
+
|
|
184
|
+
download_file(
|
|
185
|
+
f"https://agno-public.s3.us-east-1.amazonaws.com/demo_data/filters/{filename}", str(download_path)
|
|
186
|
+
)
|
|
187
|
+
file_paths.append(str(download_path))
|
|
188
|
+
return file_paths
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def reconstruct_image_from_dict(img_data):
|
|
192
|
+
"""
|
|
193
|
+
Reconstruct an Image object from dictionary data.
|
|
194
|
+
|
|
195
|
+
Handles both base64-encoded content (from database) and regular image data (url/filepath).
|
|
196
|
+
"""
|
|
197
|
+
try:
|
|
198
|
+
if isinstance(img_data, dict):
|
|
199
|
+
# If content is base64 string, decode it back to bytes
|
|
200
|
+
if "content" in img_data and isinstance(img_data["content"], str):
|
|
201
|
+
return Image.from_base64(
|
|
202
|
+
img_data["content"],
|
|
203
|
+
id=img_data.get("id"),
|
|
204
|
+
mime_type=img_data.get("mime_type"),
|
|
205
|
+
format=img_data.get("format"),
|
|
206
|
+
detail=img_data.get("detail"),
|
|
207
|
+
original_prompt=img_data.get("original_prompt"),
|
|
208
|
+
revised_prompt=img_data.get("revised_prompt"),
|
|
209
|
+
alt_text=img_data.get("alt_text"),
|
|
210
|
+
)
|
|
211
|
+
else:
|
|
212
|
+
# Regular image (filepath/url)
|
|
213
|
+
return Image(**img_data)
|
|
214
|
+
return img_data
|
|
215
|
+
except Exception as e:
|
|
216
|
+
log_warning(f"Failed to reconstruct image from dict: {e}")
|
|
217
|
+
return None
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def reconstruct_video_from_dict(vid_data):
|
|
221
|
+
"""
|
|
222
|
+
Reconstruct a Video object from dictionary data.
|
|
223
|
+
|
|
224
|
+
Handles both base64-encoded content (from database) and regular video data (url/filepath).
|
|
225
|
+
"""
|
|
226
|
+
try:
|
|
227
|
+
if isinstance(vid_data, dict):
|
|
228
|
+
# If content is base64 string, decode it back to bytes
|
|
229
|
+
if "content" in vid_data and isinstance(vid_data["content"], str):
|
|
230
|
+
return Video.from_base64(
|
|
231
|
+
vid_data["content"],
|
|
232
|
+
id=vid_data.get("id"),
|
|
233
|
+
mime_type=vid_data.get("mime_type"),
|
|
234
|
+
format=vid_data.get("format"),
|
|
235
|
+
)
|
|
236
|
+
else:
|
|
237
|
+
# Regular video (filepath/url)
|
|
238
|
+
return Video(**vid_data)
|
|
239
|
+
return vid_data
|
|
240
|
+
except Exception as e:
|
|
241
|
+
log_warning(f"Failed to reconstruct video from dict: {e}")
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def reconstruct_audio_from_dict(aud_data):
|
|
246
|
+
"""
|
|
247
|
+
Reconstruct an Audio object from dictionary data.
|
|
248
|
+
|
|
249
|
+
Handles both base64-encoded content (from database) and regular audio data (url/filepath).
|
|
250
|
+
"""
|
|
251
|
+
try:
|
|
252
|
+
if isinstance(aud_data, dict):
|
|
253
|
+
# If content is base64 string, decode it back to bytes
|
|
254
|
+
if "content" in aud_data and isinstance(aud_data["content"], str):
|
|
255
|
+
return Audio.from_base64(
|
|
256
|
+
aud_data["content"],
|
|
257
|
+
id=aud_data.get("id"),
|
|
258
|
+
mime_type=aud_data.get("mime_type"),
|
|
259
|
+
transcript=aud_data.get("transcript"),
|
|
260
|
+
expires_at=aud_data.get("expires_at"),
|
|
261
|
+
sample_rate=aud_data.get("sample_rate", 24000),
|
|
262
|
+
channels=aud_data.get("channels", 1),
|
|
263
|
+
)
|
|
264
|
+
else:
|
|
265
|
+
# Regular audio (filepath/url)
|
|
266
|
+
return Audio(**aud_data)
|
|
267
|
+
return aud_data
|
|
268
|
+
except Exception as e:
|
|
269
|
+
log_warning(f"Failed to reconstruct audio from dict: {e}")
|
|
270
|
+
return None
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def reconstruct_file_from_dict(file_data):
|
|
274
|
+
"""
|
|
275
|
+
Reconstruct a File object from dictionary data.
|
|
276
|
+
|
|
277
|
+
Handles both base64-encoded content (from database) and regular file data (url/filepath).
|
|
278
|
+
"""
|
|
279
|
+
try:
|
|
280
|
+
if isinstance(file_data, dict):
|
|
281
|
+
# If content is base64 string, decode it back to bytes
|
|
282
|
+
if "content" in file_data and isinstance(file_data["content"], str):
|
|
283
|
+
return File.from_base64(
|
|
284
|
+
file_data["content"],
|
|
285
|
+
id=file_data.get("id"),
|
|
286
|
+
mime_type=file_data.get("mime_type"),
|
|
287
|
+
filename=file_data.get("filename"),
|
|
288
|
+
name=file_data.get("name"),
|
|
289
|
+
format=file_data.get("format"),
|
|
290
|
+
)
|
|
291
|
+
else:
|
|
292
|
+
# Regular file (filepath/url)
|
|
293
|
+
return File(**file_data)
|
|
294
|
+
return file_data
|
|
295
|
+
except Exception as e:
|
|
296
|
+
log_warning(f"Failed to reconstruct file from dict: {e}")
|
|
297
|
+
return None
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def reconstruct_images(images: Optional[List[dict]]) -> Optional[List[Image]]:
|
|
301
|
+
"""Reconstruct a list of Image objects from list of dictionaries.
|
|
302
|
+
|
|
303
|
+
Failed reconstructions are skipped with a warning logged.
|
|
304
|
+
"""
|
|
305
|
+
if not images:
|
|
306
|
+
return None
|
|
307
|
+
reconstructed = [reconstruct_image_from_dict(img_data) for img_data in images]
|
|
308
|
+
valid_images = [img for img in reconstructed if img is not None]
|
|
309
|
+
return valid_images if valid_images else None
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def reconstruct_videos(videos: Optional[List[dict]]) -> Optional[List[Video]]:
|
|
313
|
+
"""Reconstruct a list of Video objects from list of dictionaries.
|
|
314
|
+
|
|
315
|
+
Failed reconstructions are skipped with a warning logged.
|
|
316
|
+
"""
|
|
317
|
+
if not videos:
|
|
318
|
+
return None
|
|
319
|
+
reconstructed = [reconstruct_video_from_dict(vid_data) for vid_data in videos]
|
|
320
|
+
valid_videos = [vid for vid in reconstructed if vid is not None]
|
|
321
|
+
return valid_videos if valid_videos else None
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def reconstruct_audio_list(audio: Optional[List[dict]]) -> Optional[List[Audio]]:
|
|
325
|
+
"""Reconstruct a list of Audio objects from list of dictionaries.
|
|
326
|
+
|
|
327
|
+
Failed reconstructions are skipped with a warning logged.
|
|
328
|
+
"""
|
|
329
|
+
if not audio:
|
|
330
|
+
return None
|
|
331
|
+
reconstructed = [reconstruct_audio_from_dict(aud_data) for aud_data in audio]
|
|
332
|
+
valid_audio = [aud for aud in reconstructed if aud is not None]
|
|
333
|
+
return valid_audio if valid_audio else None
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def reconstruct_files(files: Optional[List[dict]]) -> Optional[List[File]]:
|
|
337
|
+
"""Reconstruct a list of File objects from list of dictionaries.
|
|
338
|
+
|
|
339
|
+
Failed reconstructions are skipped with a warning logged.
|
|
340
|
+
"""
|
|
341
|
+
if not files:
|
|
342
|
+
return None
|
|
343
|
+
reconstructed = [reconstruct_file_from_dict(file_data) for file_data in files]
|
|
344
|
+
valid_files = [f for f in reconstructed if f is not None]
|
|
345
|
+
return valid_files if valid_files else None
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def reconstruct_response_audio(audio: Optional[dict]) -> Optional[Audio]:
|
|
349
|
+
"""Reconstruct a single Audio object for response audio."""
|
|
350
|
+
if not audio:
|
|
351
|
+
return None
|
|
352
|
+
return reconstruct_audio_from_dict(audio)
|
agno/utils/merge_dict.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from typing import Any, Dict, List
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def merge_dictionaries(a: Dict[str, Any], b: Dict[str, Any]) -> None:
|
|
5
|
+
"""
|
|
6
|
+
Recursively merges two dictionaries.
|
|
7
|
+
If there are conflicting keys, values from 'b' will take precedence.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
a (Dict[str, Any]): The first dictionary to be merged.
|
|
11
|
+
b (Dict[str, Any]): The second dictionary, whose values will take precedence.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
None: The function modifies the first dictionary in place.
|
|
15
|
+
"""
|
|
16
|
+
for key in b:
|
|
17
|
+
if key in a and isinstance(a[key], dict) and isinstance(b[key], dict):
|
|
18
|
+
merge_dictionaries(a[key], b[key])
|
|
19
|
+
else:
|
|
20
|
+
a[key] = b[key]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def merge_parallel_session_states(original_state: Dict[str, Any], modified_states: List[Dict[str, Any]]) -> None:
|
|
24
|
+
"""
|
|
25
|
+
Smart merge for parallel session states that only applies actual changes.
|
|
26
|
+
This prevents parallel steps from overwriting each other's changes.
|
|
27
|
+
"""
|
|
28
|
+
if not original_state or not modified_states:
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
# Collect all actual changes (keys where value differs from original)
|
|
32
|
+
all_changes = {}
|
|
33
|
+
for modified_state in modified_states:
|
|
34
|
+
if modified_state:
|
|
35
|
+
for key, value in modified_state.items():
|
|
36
|
+
if key not in original_state or original_state[key] != value:
|
|
37
|
+
all_changes[key] = value
|
|
38
|
+
|
|
39
|
+
# Apply all collected changes to the original state
|
|
40
|
+
for key, value in all_changes.items():
|
|
41
|
+
original_state[key] = value
|