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/message.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
from typing import Dict, List, Union
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
from agno.models.message import Message
|
|
7
|
+
from agno.utils.log import log_debug
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def filter_tool_calls(messages: List[Message], max_tool_calls: int) -> None:
|
|
11
|
+
"""
|
|
12
|
+
Filter messages (in-place) to keep only the most recent N tool calls.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
messages: List of messages to filter (modified in-place)
|
|
16
|
+
max_tool_calls: Number of recent tool calls to keep
|
|
17
|
+
"""
|
|
18
|
+
# Count total tool calls
|
|
19
|
+
tool_call_count = sum(1 for m in messages if m.role == "tool")
|
|
20
|
+
|
|
21
|
+
# No filtering needed
|
|
22
|
+
if tool_call_count <= max_tool_calls:
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
# Collect tool_call_ids to keep (most recent N)
|
|
26
|
+
tool_call_ids_list: List[str] = []
|
|
27
|
+
for msg in reversed(messages):
|
|
28
|
+
if msg.role == "tool" and len(tool_call_ids_list) < max_tool_calls:
|
|
29
|
+
if msg.tool_call_id:
|
|
30
|
+
tool_call_ids_list.append(msg.tool_call_id)
|
|
31
|
+
|
|
32
|
+
tool_call_ids_to_keep: set[str] = set(tool_call_ids_list)
|
|
33
|
+
|
|
34
|
+
# Filter messages in-place
|
|
35
|
+
filtered_messages = []
|
|
36
|
+
for msg in messages:
|
|
37
|
+
if msg.role == "tool":
|
|
38
|
+
# Keep only tool results in our window
|
|
39
|
+
if msg.tool_call_id in tool_call_ids_to_keep:
|
|
40
|
+
filtered_messages.append(msg)
|
|
41
|
+
elif msg.role == "assistant" and msg.tool_calls:
|
|
42
|
+
# Filter tool_calls within the assistant message
|
|
43
|
+
# Use deepcopy to ensure complete isolation of the filtered message
|
|
44
|
+
filtered_msg = deepcopy(msg)
|
|
45
|
+
# Filter tool_calls
|
|
46
|
+
if filtered_msg.tool_calls is not None:
|
|
47
|
+
filtered_msg.tool_calls = [
|
|
48
|
+
tc for tc in filtered_msg.tool_calls if tc.get("id") in tool_call_ids_to_keep
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
if filtered_msg.tool_calls:
|
|
52
|
+
# Has tool_calls remaining, keep it
|
|
53
|
+
filtered_messages.append(filtered_msg)
|
|
54
|
+
# skip empty messages
|
|
55
|
+
elif filtered_msg.content:
|
|
56
|
+
filtered_msg.tool_calls = None
|
|
57
|
+
filtered_messages.append(filtered_msg)
|
|
58
|
+
else:
|
|
59
|
+
filtered_messages.append(msg)
|
|
60
|
+
|
|
61
|
+
messages[:] = filtered_messages
|
|
62
|
+
|
|
63
|
+
# Log filtering information
|
|
64
|
+
num_filtered = tool_call_count - len(tool_call_ids_to_keep)
|
|
65
|
+
log_debug(f"Filtered {num_filtered} tool calls, kept {len(tool_call_ids_to_keep)}")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_text_from_message(message: Union[List, Dict, str, Message, BaseModel]) -> str:
|
|
69
|
+
"""Return the user texts from the message"""
|
|
70
|
+
import json
|
|
71
|
+
|
|
72
|
+
if isinstance(message, str):
|
|
73
|
+
return message
|
|
74
|
+
if isinstance(message, BaseModel):
|
|
75
|
+
return message.model_dump_json(indent=2, exclude_none=True)
|
|
76
|
+
if isinstance(message, list):
|
|
77
|
+
text_messages = []
|
|
78
|
+
if len(message) == 0:
|
|
79
|
+
return ""
|
|
80
|
+
|
|
81
|
+
# Check if it's a list of Message objects
|
|
82
|
+
if isinstance(message[0], Message):
|
|
83
|
+
for m in message:
|
|
84
|
+
if isinstance(m, Message) and m.role == "user" and m.content is not None:
|
|
85
|
+
# Recursively extract text from the message content
|
|
86
|
+
content_text = get_text_from_message(m.content)
|
|
87
|
+
if content_text:
|
|
88
|
+
text_messages.append(content_text)
|
|
89
|
+
elif "type" in message[0]:
|
|
90
|
+
for m in message:
|
|
91
|
+
m_type = m.get("type")
|
|
92
|
+
if m_type is not None and isinstance(m_type, str):
|
|
93
|
+
m_value = m.get(m_type)
|
|
94
|
+
if m_value is not None and isinstance(m_value, str):
|
|
95
|
+
if m_type == "text":
|
|
96
|
+
text_messages.append(m_value)
|
|
97
|
+
# if m_type == "image_url":
|
|
98
|
+
# text_messages.append(f"Image: {m_value}")
|
|
99
|
+
# else:
|
|
100
|
+
# text_messages.append(f"{m_type}: {m_value}")
|
|
101
|
+
elif "role" in message[0]:
|
|
102
|
+
for m in message:
|
|
103
|
+
m_role = m.get("role")
|
|
104
|
+
if m_role is not None and isinstance(m_role, str):
|
|
105
|
+
m_content = m.get("content")
|
|
106
|
+
if m_content is not None and isinstance(m_content, str):
|
|
107
|
+
if m_role == "user":
|
|
108
|
+
text_messages.append(m_content)
|
|
109
|
+
if len(text_messages) > 0:
|
|
110
|
+
return "\n".join(text_messages)
|
|
111
|
+
if isinstance(message, dict):
|
|
112
|
+
if "content" in message:
|
|
113
|
+
return get_text_from_message(message["content"])
|
|
114
|
+
else:
|
|
115
|
+
return json.dumps(message, indent=2)
|
|
116
|
+
if isinstance(message, Message) and message.content is not None:
|
|
117
|
+
return get_text_from_message(message.content)
|
|
118
|
+
return ""
|
|
File without changes
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from typing import Any, Dict
|
|
2
|
+
|
|
3
|
+
from agno.models.message import Message
|
|
4
|
+
from agno.utils.log import log_warning
|
|
5
|
+
from agno.utils.openai import images_to_message
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def format_message(message: Message) -> Dict[str, Any]:
|
|
9
|
+
"""
|
|
10
|
+
Format a message into the format expected by OpenAI.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
message (Message): The message to format.
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
Dict[str, Any]: The formatted message.
|
|
17
|
+
"""
|
|
18
|
+
message_dict: Dict[str, Any] = {
|
|
19
|
+
"role": message.role,
|
|
20
|
+
"content": message.content,
|
|
21
|
+
"name": message.name,
|
|
22
|
+
"tool_call_id": message.tool_call_id,
|
|
23
|
+
"tool_calls": message.tool_calls,
|
|
24
|
+
}
|
|
25
|
+
message_dict = {k: v for k, v in message_dict.items() if v is not None}
|
|
26
|
+
|
|
27
|
+
if message.images is not None and len(message.images) > 0:
|
|
28
|
+
# Ignore non-string message content
|
|
29
|
+
# because we assume that the images/audio are already added to the message
|
|
30
|
+
if isinstance(message.content, str):
|
|
31
|
+
message_dict["content"] = [{"type": "text", "text": message.content}]
|
|
32
|
+
message_dict["content"].extend(images_to_message(images=message.images))
|
|
33
|
+
|
|
34
|
+
if message.audio is not None and len(message.audio) > 0:
|
|
35
|
+
log_warning("Audio input is currently unsupported.")
|
|
36
|
+
|
|
37
|
+
if message.files is not None and len(message.files) > 0:
|
|
38
|
+
log_warning("File input is currently unsupported.")
|
|
39
|
+
|
|
40
|
+
if message.videos is not None and len(message.videos) > 0:
|
|
41
|
+
log_warning("Video input is currently unsupported.")
|
|
42
|
+
|
|
43
|
+
return message_dict
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
4
|
+
|
|
5
|
+
from agno.media import File, Image
|
|
6
|
+
from agno.models.message import Message
|
|
7
|
+
from agno.utils.log import log_error, log_warning
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
from anthropic.types import (
|
|
11
|
+
TextBlock,
|
|
12
|
+
ToolUseBlock,
|
|
13
|
+
)
|
|
14
|
+
except ImportError:
|
|
15
|
+
raise ImportError("`anthropic` not installed. Please install using `pip install anthropic`")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class MCPToolConfiguration:
|
|
20
|
+
enabled: bool = True
|
|
21
|
+
allowed_tools: List[str] = field(default_factory=list)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class MCPServerConfiguration:
|
|
26
|
+
type: str
|
|
27
|
+
url: str
|
|
28
|
+
name: str
|
|
29
|
+
tool_configuration: Optional[MCPToolConfiguration] = None
|
|
30
|
+
authorization_token: Optional[str] = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
ROLE_MAP = {
|
|
34
|
+
"system": "system",
|
|
35
|
+
"developer": "system",
|
|
36
|
+
"user": "user",
|
|
37
|
+
"assistant": "assistant",
|
|
38
|
+
"tool": "user",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _format_image_for_message(image: Image) -> Optional[Dict[str, Any]]:
|
|
43
|
+
"""
|
|
44
|
+
Add an image to a message by converting it to base64 encoded format.
|
|
45
|
+
"""
|
|
46
|
+
using_filetype = False
|
|
47
|
+
|
|
48
|
+
import base64
|
|
49
|
+
|
|
50
|
+
# 'imghdr' was deprecated in Python 3.11: https://docs.python.org/3/library/imghdr.html
|
|
51
|
+
# 'filetype' used as a fallback
|
|
52
|
+
try:
|
|
53
|
+
import imghdr
|
|
54
|
+
except ImportError:
|
|
55
|
+
try:
|
|
56
|
+
import filetype
|
|
57
|
+
|
|
58
|
+
using_filetype = True
|
|
59
|
+
except ImportError:
|
|
60
|
+
raise ImportError("`filetype` not installed. Please install using `pip install filetype`")
|
|
61
|
+
|
|
62
|
+
type_mapping = {
|
|
63
|
+
"jpeg": "image/jpeg",
|
|
64
|
+
"jpg": "image/jpeg",
|
|
65
|
+
"png": "image/png",
|
|
66
|
+
"gif": "image/gif",
|
|
67
|
+
"webp": "image/webp",
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
img_type = None
|
|
72
|
+
|
|
73
|
+
# Case 0: Image is an Anthropic uploaded file
|
|
74
|
+
if image.content is not None and hasattr(image.content, "id"):
|
|
75
|
+
content_bytes = image.content
|
|
76
|
+
|
|
77
|
+
# Case 1: Image is a URL
|
|
78
|
+
if image.url is not None:
|
|
79
|
+
content_bytes = image.get_content_bytes() # type: ignore
|
|
80
|
+
|
|
81
|
+
# If image URL has a suffix, use it as the type (without dot)
|
|
82
|
+
import os
|
|
83
|
+
from urllib.parse import urlparse
|
|
84
|
+
|
|
85
|
+
if image.url:
|
|
86
|
+
parsed_url = urlparse(image.url)
|
|
87
|
+
_, ext = os.path.splitext(parsed_url.path)
|
|
88
|
+
if ext:
|
|
89
|
+
img_type = ext.lstrip(".").lower()
|
|
90
|
+
|
|
91
|
+
# Case 2: Image is a local file path
|
|
92
|
+
elif image.filepath is not None:
|
|
93
|
+
from pathlib import Path
|
|
94
|
+
|
|
95
|
+
path = Path(image.filepath) if isinstance(image.filepath, str) else image.filepath
|
|
96
|
+
if path.exists() and path.is_file():
|
|
97
|
+
with open(image.filepath, "rb") as f:
|
|
98
|
+
content_bytes = f.read()
|
|
99
|
+
|
|
100
|
+
# If image file path has a suffix, use it as the type (without dot)
|
|
101
|
+
path_ext = path.suffix.lstrip(".")
|
|
102
|
+
if path_ext:
|
|
103
|
+
img_type = path_ext.lower()
|
|
104
|
+
else:
|
|
105
|
+
log_error(f"Image file not found: {image}")
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
# Case 3: Image is a bytes object
|
|
109
|
+
elif image.content is not None:
|
|
110
|
+
content_bytes = image.content
|
|
111
|
+
|
|
112
|
+
else:
|
|
113
|
+
log_error(f"Unsupported image type: {type(image)}")
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
if not img_type:
|
|
117
|
+
if using_filetype:
|
|
118
|
+
kind = filetype.guess(content_bytes)
|
|
119
|
+
if not kind:
|
|
120
|
+
log_error("Unable to determine image type")
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
img_type = kind.extension
|
|
124
|
+
else:
|
|
125
|
+
img_type = imghdr.what(None, h=content_bytes) # type: ignore
|
|
126
|
+
|
|
127
|
+
if not img_type:
|
|
128
|
+
log_error("Unable to determine image type")
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
media_type = type_mapping.get(img_type)
|
|
132
|
+
if not media_type:
|
|
133
|
+
log_error(f"Unsupported image type: {img_type}")
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
"type": "image",
|
|
138
|
+
"source": {
|
|
139
|
+
"type": "base64",
|
|
140
|
+
"media_type": media_type,
|
|
141
|
+
"data": base64.b64encode(content_bytes).decode("utf-8"), # type: ignore
|
|
142
|
+
},
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
except Exception as e:
|
|
146
|
+
log_error(f"Error processing image: {e}")
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _format_file_for_message(file: File) -> Optional[Dict[str, Any]]:
|
|
151
|
+
"""
|
|
152
|
+
Add a document url or base64 encoded content to a message.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
mime_mapping = {
|
|
156
|
+
"application/pdf": "base64",
|
|
157
|
+
"text/plain": "text",
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# Case 0: File is an Anthropic uploaded file
|
|
161
|
+
if file.external is not None and hasattr(file.external, "id"):
|
|
162
|
+
return {
|
|
163
|
+
"type": "document",
|
|
164
|
+
"source": {
|
|
165
|
+
"type": "file",
|
|
166
|
+
"file_id": file.external.id,
|
|
167
|
+
},
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
# Case 1: Document is a URL
|
|
171
|
+
if file.url is not None:
|
|
172
|
+
return {
|
|
173
|
+
"type": "document",
|
|
174
|
+
"source": {
|
|
175
|
+
"type": "url",
|
|
176
|
+
"url": file.url,
|
|
177
|
+
},
|
|
178
|
+
"citations": {"enabled": True},
|
|
179
|
+
}
|
|
180
|
+
# Case 2: Document is a local file path
|
|
181
|
+
elif file.filepath is not None:
|
|
182
|
+
import base64
|
|
183
|
+
from pathlib import Path
|
|
184
|
+
|
|
185
|
+
path = Path(file.filepath) if isinstance(file.filepath, str) else file.filepath
|
|
186
|
+
if path.exists() and path.is_file():
|
|
187
|
+
file_data = base64.standard_b64encode(path.read_bytes()).decode("utf-8")
|
|
188
|
+
|
|
189
|
+
# Determine media type
|
|
190
|
+
media_type = file.mime_type
|
|
191
|
+
if media_type is None:
|
|
192
|
+
import mimetypes
|
|
193
|
+
|
|
194
|
+
media_type = mimetypes.guess_type(file.filepath)[0] or "application/pdf"
|
|
195
|
+
|
|
196
|
+
# Map media type to type, default to "base64" if no mapping exists
|
|
197
|
+
type = mime_mapping.get(media_type, "base64")
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
"type": "document",
|
|
201
|
+
"source": {
|
|
202
|
+
"type": type,
|
|
203
|
+
"media_type": media_type,
|
|
204
|
+
"data": file_data,
|
|
205
|
+
},
|
|
206
|
+
"citations": {"enabled": True},
|
|
207
|
+
}
|
|
208
|
+
else:
|
|
209
|
+
log_error(f"Document file not found: {file}")
|
|
210
|
+
return None
|
|
211
|
+
# Case 3: Document is bytes content
|
|
212
|
+
elif file.content is not None:
|
|
213
|
+
import base64
|
|
214
|
+
|
|
215
|
+
file_data = base64.standard_b64encode(file.content).decode("utf-8")
|
|
216
|
+
return {
|
|
217
|
+
"type": "document",
|
|
218
|
+
"source": {"type": "base64", "media_type": file.mime_type or "application/pdf", "data": file_data},
|
|
219
|
+
"citations": {"enabled": True},
|
|
220
|
+
}
|
|
221
|
+
return None
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def format_messages(messages: List[Message]) -> Tuple[List[Dict[str, str]], str]:
|
|
225
|
+
"""
|
|
226
|
+
Process the list of messages and separate them into API messages and system messages.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
messages (List[Message]): The list of messages to process.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
Tuple[List[Dict[str, str]], str]: A tuple containing the list of API messages and the concatenated system messages.
|
|
233
|
+
"""
|
|
234
|
+
chat_messages: List[Dict[str, str]] = []
|
|
235
|
+
system_messages: List[str] = []
|
|
236
|
+
|
|
237
|
+
for message in messages:
|
|
238
|
+
content = message.content or ""
|
|
239
|
+
# Both "system" and "developer" roles should be extracted as system messages
|
|
240
|
+
if message.role in ("system", "developer"):
|
|
241
|
+
if content is not None:
|
|
242
|
+
system_messages.append(content) # type: ignore
|
|
243
|
+
continue
|
|
244
|
+
elif message.role == "user":
|
|
245
|
+
if isinstance(content, str):
|
|
246
|
+
content = [{"type": "text", "text": content}]
|
|
247
|
+
|
|
248
|
+
if message.images is not None:
|
|
249
|
+
for image in message.images:
|
|
250
|
+
image_content = _format_image_for_message(image)
|
|
251
|
+
if image_content:
|
|
252
|
+
content.append(image_content)
|
|
253
|
+
|
|
254
|
+
if message.files is not None:
|
|
255
|
+
for file in message.files:
|
|
256
|
+
file_content = _format_file_for_message(file)
|
|
257
|
+
if file_content:
|
|
258
|
+
content.append(file_content)
|
|
259
|
+
|
|
260
|
+
if message.audio is not None and len(message.audio) > 0:
|
|
261
|
+
log_warning("Audio input is currently unsupported.")
|
|
262
|
+
|
|
263
|
+
if message.videos is not None and len(message.videos) > 0:
|
|
264
|
+
log_warning("Video input is currently unsupported.")
|
|
265
|
+
|
|
266
|
+
elif message.role == "assistant":
|
|
267
|
+
content = []
|
|
268
|
+
|
|
269
|
+
if message.reasoning_content is not None and message.provider_data is not None:
|
|
270
|
+
from anthropic.types import RedactedThinkingBlock, ThinkingBlock
|
|
271
|
+
|
|
272
|
+
content.append(
|
|
273
|
+
ThinkingBlock(
|
|
274
|
+
thinking=message.reasoning_content,
|
|
275
|
+
signature=message.provider_data.get("signature"),
|
|
276
|
+
type="thinking",
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
if message.redacted_reasoning_content is not None:
|
|
281
|
+
from anthropic.types import RedactedThinkingBlock
|
|
282
|
+
|
|
283
|
+
content.append(
|
|
284
|
+
RedactedThinkingBlock(data=message.redacted_reasoning_content, type="redacted_reasoning_content")
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
if isinstance(message.content, str) and message.content and len(message.content.strip()) > 0:
|
|
288
|
+
content.append(TextBlock(text=message.content, type="text"))
|
|
289
|
+
|
|
290
|
+
if message.tool_calls:
|
|
291
|
+
for tool_call in message.tool_calls:
|
|
292
|
+
content.append(
|
|
293
|
+
ToolUseBlock(
|
|
294
|
+
id=tool_call["id"],
|
|
295
|
+
input=json.loads(tool_call["function"]["arguments"])
|
|
296
|
+
if "arguments" in tool_call["function"]
|
|
297
|
+
else {},
|
|
298
|
+
name=tool_call["function"]["name"],
|
|
299
|
+
type="tool_use",
|
|
300
|
+
)
|
|
301
|
+
)
|
|
302
|
+
elif message.role == "tool":
|
|
303
|
+
content = []
|
|
304
|
+
content.append(
|
|
305
|
+
{
|
|
306
|
+
"type": "tool_result",
|
|
307
|
+
"tool_use_id": message.tool_call_id,
|
|
308
|
+
"content": str(message.content),
|
|
309
|
+
}
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
# Skip empty assistant responses
|
|
313
|
+
if message.role == "assistant" and not content:
|
|
314
|
+
continue
|
|
315
|
+
|
|
316
|
+
chat_messages.append({"role": ROLE_MAP[message.role], "content": content}) # type: ignore
|
|
317
|
+
return chat_messages, " ".join(system_messages)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def format_tools_for_model(tools: Optional[List[Dict[str, Any]]] = None) -> Optional[List[Dict[str, Any]]]:
|
|
321
|
+
"""
|
|
322
|
+
Transforms function definitions into a format accepted by the Anthropic API.
|
|
323
|
+
"""
|
|
324
|
+
if not tools:
|
|
325
|
+
return None
|
|
326
|
+
|
|
327
|
+
parsed_tools: List[Dict[str, Any]] = []
|
|
328
|
+
for tool_def in tools:
|
|
329
|
+
if tool_def.get("type", "") != "function":
|
|
330
|
+
parsed_tools.append(tool_def)
|
|
331
|
+
continue
|
|
332
|
+
|
|
333
|
+
func_def = tool_def.get("function", {})
|
|
334
|
+
parameters: Dict[str, Any] = func_def.get("parameters", {})
|
|
335
|
+
properties: Dict[str, Any] = parameters.get("properties", {})
|
|
336
|
+
required: List[str] = parameters.get("required", [])
|
|
337
|
+
required_params: List[str] = required
|
|
338
|
+
|
|
339
|
+
input_properties: Dict[str, Any] = {}
|
|
340
|
+
for param_name, param_info in properties.items():
|
|
341
|
+
# Preserve the complete schema structure for complex types
|
|
342
|
+
input_properties[param_name] = param_info.copy()
|
|
343
|
+
|
|
344
|
+
# Ensure description is present (default to empty if missing)
|
|
345
|
+
if "description" not in input_properties[param_name]:
|
|
346
|
+
input_properties[param_name]["description"] = ""
|
|
347
|
+
|
|
348
|
+
tool = {
|
|
349
|
+
"name": func_def.get("name") or "",
|
|
350
|
+
"description": func_def.get("description") or "",
|
|
351
|
+
"input_schema": {
|
|
352
|
+
"type": parameters.get("type", "object"),
|
|
353
|
+
"properties": input_properties,
|
|
354
|
+
"required": required_params,
|
|
355
|
+
},
|
|
356
|
+
}
|
|
357
|
+
parsed_tools.append(tool)
|
|
358
|
+
return parsed_tools
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any, Dict, List, Sequence
|
|
3
|
+
|
|
4
|
+
from agno.media import Image
|
|
5
|
+
from agno.models.message import Message
|
|
6
|
+
from agno.utils.log import log_error, log_warning
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _format_images_for_message(message: Message, images: Sequence[Image]) -> List[Dict[str, Any]]:
|
|
10
|
+
"""
|
|
11
|
+
Format an image into the format expected by WatsonX.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
# Create a default message content with text
|
|
15
|
+
message_content_with_image: List[Dict[str, Any]] = [{"type": "text", "text": message.content}]
|
|
16
|
+
|
|
17
|
+
# Add images to the message content
|
|
18
|
+
for image in images:
|
|
19
|
+
try:
|
|
20
|
+
if image.content is not None:
|
|
21
|
+
image_content = image.content
|
|
22
|
+
elif image.url is not None:
|
|
23
|
+
image_content = image.get_content_bytes() # type: ignore
|
|
24
|
+
elif image.filepath is not None:
|
|
25
|
+
if isinstance(image.filepath, Path):
|
|
26
|
+
image_content = image.filepath.read_bytes()
|
|
27
|
+
else:
|
|
28
|
+
with open(image.filepath, "rb") as f:
|
|
29
|
+
image_content = f.read()
|
|
30
|
+
else:
|
|
31
|
+
log_warning(f"Unsupported image format: {image}")
|
|
32
|
+
continue
|
|
33
|
+
|
|
34
|
+
if image_content is not None:
|
|
35
|
+
import base64
|
|
36
|
+
|
|
37
|
+
base64_image = base64.b64encode(image_content).decode("utf-8")
|
|
38
|
+
image_url = f"data:image/jpeg;base64,{base64_image}"
|
|
39
|
+
image_payload = {"type": "image_url", "image_url": {"url": image_url}}
|
|
40
|
+
message_content_with_image.append(image_payload)
|
|
41
|
+
|
|
42
|
+
except Exception as e:
|
|
43
|
+
log_error(f"Failed to process image: {str(e)}")
|
|
44
|
+
|
|
45
|
+
# Update the message content with the images
|
|
46
|
+
return message_content_with_image
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def format_messages(messages: List[Message]) -> List[Dict[str, Any]]:
|
|
50
|
+
"""
|
|
51
|
+
Format messages for the Cohere API.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
messages (List[Message]): The list of messages.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
List[Dict[str, Any]]: The formatted messages.
|
|
58
|
+
"""
|
|
59
|
+
formatted_messages = []
|
|
60
|
+
for message in messages:
|
|
61
|
+
message_dict = {
|
|
62
|
+
"role": message.role,
|
|
63
|
+
"content": message.content,
|
|
64
|
+
"name": message.name,
|
|
65
|
+
"tool_call_id": message.tool_call_id,
|
|
66
|
+
"tool_calls": message.tool_calls,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if message.images is not None and len(message.images) > 0:
|
|
70
|
+
# Ignore non-string message content
|
|
71
|
+
if isinstance(message.content, str):
|
|
72
|
+
message_content_with_image = _format_images_for_message(message=message, images=message.images)
|
|
73
|
+
if len(message_content_with_image) > 1:
|
|
74
|
+
message_dict["content"] = message_content_with_image
|
|
75
|
+
|
|
76
|
+
if message.videos is not None and len(message.videos) > 0:
|
|
77
|
+
log_warning("Video input is currently unsupported.")
|
|
78
|
+
|
|
79
|
+
if message.audio is not None and len(message.audio) > 0:
|
|
80
|
+
log_warning("Audio input is currently unsupported.")
|
|
81
|
+
|
|
82
|
+
if message.files is not None and len(message.files) > 0:
|
|
83
|
+
log_warning("File input is currently unsupported.")
|
|
84
|
+
|
|
85
|
+
message_dict = {k: v for k, v in message_dict.items() if v is not None}
|
|
86
|
+
formatted_messages.append(message_dict)
|
|
87
|
+
return formatted_messages
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from typing import Any, Dict
|
|
2
|
+
|
|
3
|
+
from agno.agent import Message
|
|
4
|
+
from agno.utils.log import log_warning
|
|
5
|
+
from agno.utils.openai import process_image
|
|
6
|
+
|
|
7
|
+
ROLE_MAP = {
|
|
8
|
+
"user": "user",
|
|
9
|
+
"assistant": "assistant",
|
|
10
|
+
"system": "system",
|
|
11
|
+
"tool": "tool",
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
TOOL_CALL_ROLE_MAP = {
|
|
15
|
+
"user": "user",
|
|
16
|
+
"assistant": "assistant",
|
|
17
|
+
"system": "user",
|
|
18
|
+
"tool": "tool",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def format_message(message: Message, openai_like: bool = False, tool_calls: bool = False) -> Dict[str, Any]:
|
|
23
|
+
"""
|
|
24
|
+
Format a message into the format expected by Llama API.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
message (Message): The message to format.
|
|
28
|
+
openai_like (bool): Whether to format the message as an OpenAI-like message.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Dict[str, Any]: The formatted message.
|
|
32
|
+
"""
|
|
33
|
+
message_dict: Dict[str, Any] = {
|
|
34
|
+
"role": ROLE_MAP[message.role] if not tool_calls else TOOL_CALL_ROLE_MAP[message.role],
|
|
35
|
+
"content": [{"type": "text", "text": message.content or " "}],
|
|
36
|
+
"name": message.name,
|
|
37
|
+
"tool_call_id": message.tool_call_id,
|
|
38
|
+
"tool_calls": message.tool_calls,
|
|
39
|
+
}
|
|
40
|
+
message_dict = {k: v for k, v in message_dict.items() if v is not None}
|
|
41
|
+
|
|
42
|
+
if message.images is not None and len(message.images) > 0:
|
|
43
|
+
for image in message.images:
|
|
44
|
+
image_payload = process_image(image)
|
|
45
|
+
if image_payload:
|
|
46
|
+
message_dict["content"].append(image_payload)
|
|
47
|
+
|
|
48
|
+
if message.videos is not None and len(message.videos) > 0:
|
|
49
|
+
log_warning("Video input is currently unsupported.")
|
|
50
|
+
|
|
51
|
+
if message.audio is not None and len(message.audio) > 0:
|
|
52
|
+
log_warning("Audio input is currently unsupported.")
|
|
53
|
+
|
|
54
|
+
if message.role == "tool":
|
|
55
|
+
message_dict = {
|
|
56
|
+
"role": "tool",
|
|
57
|
+
"tool_call_id": message.tool_call_id,
|
|
58
|
+
"content": message.content,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if message.role == "assistant":
|
|
62
|
+
text_content = {"type": "text", "text": message.content or " "}
|
|
63
|
+
|
|
64
|
+
if message.tool_calls is not None and len(message.tool_calls) > 0:
|
|
65
|
+
message_dict = {
|
|
66
|
+
"content": [text_content] if openai_like else text_content,
|
|
67
|
+
"role": "assistant",
|
|
68
|
+
"tool_calls": message.tool_calls,
|
|
69
|
+
"stop_reason": "tool_calls",
|
|
70
|
+
}
|
|
71
|
+
else:
|
|
72
|
+
message_dict = {
|
|
73
|
+
"role": "assistant",
|
|
74
|
+
"content": [text_content] if openai_like else text_content,
|
|
75
|
+
"stop_reason": "stop",
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return message_dict
|