agno 0.1.2__py3-none-any.whl → 2.3.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 +44 -5
- agno/agent/agent.py +10531 -2975
- agno/api/agent.py +14 -53
- agno/api/api.py +7 -46
- agno/api/evals.py +22 -0
- agno/api/os.py +17 -0
- agno/api/routes.py +6 -25
- agno/api/schemas/__init__.py +9 -0
- agno/api/schemas/agent.py +6 -9
- agno/api/schemas/evals.py +16 -0
- agno/api/schemas/os.py +14 -0
- agno/api/schemas/team.py +10 -10
- agno/api/schemas/utils.py +21 -0
- agno/api/schemas/workflows.py +16 -0
- agno/api/settings.py +53 -0
- agno/api/team.py +22 -26
- 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/compression/__init__.py +3 -0
- agno/compression/manager.py +247 -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 +946 -0
- agno/db/dynamo/__init__.py +3 -0
- agno/db/dynamo/dynamo.py +2781 -0
- agno/db/dynamo/schemas.py +442 -0
- agno/db/dynamo/utils.py +743 -0
- agno/db/firestore/__init__.py +3 -0
- agno/db/firestore/firestore.py +2379 -0
- agno/db/firestore/schemas.py +181 -0
- agno/db/firestore/utils.py +376 -0
- agno/db/gcs_json/__init__.py +3 -0
- agno/db/gcs_json/gcs_json_db.py +1791 -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 +1312 -0
- agno/db/in_memory/utils.py +230 -0
- agno/db/json/__init__.py +3 -0
- agno/db/json/json_db.py +1777 -0
- agno/db/json/utils.py +230 -0
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +635 -0
- agno/db/migrations/versions/v2_3_0.py +938 -0
- agno/db/mongo/__init__.py +17 -0
- agno/db/mongo/async_mongo.py +2760 -0
- agno/db/mongo/mongo.py +2597 -0
- agno/db/mongo/schemas.py +119 -0
- agno/db/mongo/utils.py +276 -0
- agno/db/mysql/__init__.py +4 -0
- agno/db/mysql/async_mysql.py +2912 -0
- agno/db/mysql/mysql.py +2923 -0
- agno/db/mysql/schemas.py +186 -0
- agno/db/mysql/utils.py +488 -0
- agno/db/postgres/__init__.py +4 -0
- agno/db/postgres/async_postgres.py +2579 -0
- agno/db/postgres/postgres.py +2870 -0
- agno/db/postgres/schemas.py +187 -0
- agno/db/postgres/utils.py +442 -0
- agno/db/redis/__init__.py +3 -0
- agno/db/redis/redis.py +2141 -0
- agno/db/redis/schemas.py +159 -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 +34 -0
- agno/db/schemas/knowledge.py +40 -0
- agno/db/schemas/memory.py +61 -0
- agno/db/singlestore/__init__.py +3 -0
- agno/db/singlestore/schemas.py +179 -0
- agno/db/singlestore/singlestore.py +2877 -0
- agno/db/singlestore/utils.py +384 -0
- agno/db/sqlite/__init__.py +4 -0
- agno/db/sqlite/async_sqlite.py +2911 -0
- agno/db/sqlite/schemas.py +181 -0
- agno/db/sqlite/sqlite.py +2908 -0
- agno/db/sqlite/utils.py +429 -0
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +334 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1908 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +118 -0
- agno/eval/__init__.py +24 -0
- agno/eval/accuracy.py +666 -276
- agno/eval/agent_as_judge.py +861 -0
- agno/eval/base.py +29 -0
- agno/eval/performance.py +779 -0
- agno/eval/reliability.py +241 -62
- agno/eval/utils.py +120 -0
- agno/exceptions.py +143 -1
- 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/hooks/__init__.py +3 -0
- agno/hooks/decorator.py +164 -0
- agno/integrations/discord/__init__.py +3 -0
- agno/integrations/discord/client.py +203 -0
- agno/knowledge/__init__.py +5 -1
- agno/{document → knowledge}/chunking/agentic.py +22 -14
- agno/{document → knowledge}/chunking/document.py +2 -2
- agno/{document → knowledge}/chunking/fixed.py +7 -6
- agno/knowledge/chunking/markdown.py +151 -0
- agno/{document → knowledge}/chunking/recursive.py +15 -3
- agno/knowledge/chunking/row.py +39 -0
- agno/knowledge/chunking/semantic.py +91 -0
- agno/knowledge/chunking/strategy.py +165 -0
- agno/knowledge/content.py +74 -0
- agno/knowledge/document/__init__.py +5 -0
- agno/{document → knowledge/document}/base.py +12 -2
- agno/knowledge/embedder/__init__.py +5 -0
- agno/knowledge/embedder/aws_bedrock.py +343 -0
- agno/knowledge/embedder/azure_openai.py +210 -0
- agno/{embedder → knowledge/embedder}/base.py +8 -0
- agno/knowledge/embedder/cohere.py +323 -0
- agno/knowledge/embedder/fastembed.py +62 -0
- agno/{embedder → knowledge/embedder}/fireworks.py +1 -1
- 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/{embedder → knowledge/embedder}/together.py +1 -1
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/embedder/voyageai.py +165 -0
- agno/knowledge/knowledge.py +3006 -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 +164 -0
- agno/knowledge/reader/docx_reader.py +82 -0
- agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
- agno/knowledge/reader/firecrawl_reader.py +201 -0
- agno/knowledge/reader/json_reader.py +88 -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 +193 -0
- agno/knowledge/reader/text_reader.py +127 -0
- agno/knowledge/reader/web_search_reader.py +325 -0
- agno/knowledge/reader/website_reader.py +455 -0
- agno/knowledge/reader/wikipedia_reader.py +91 -0
- agno/knowledge/reader/youtube_reader.py +78 -0
- agno/knowledge/remote_content/remote_content.py +88 -0
- agno/knowledge/reranker/__init__.py +3 -0
- agno/{reranker → knowledge/reranker}/base.py +1 -1
- agno/{reranker → knowledge/reranker}/cohere.py +2 -2
- 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 +234 -0
- agno/media.py +439 -95
- agno/memory/__init__.py +16 -3
- agno/memory/manager.py +1474 -123
- agno/memory/strategies/__init__.py +15 -0
- agno/memory/strategies/base.py +66 -0
- agno/memory/strategies/summarize.py +196 -0
- agno/memory/strategies/types.py +37 -0
- agno/models/aimlapi/__init__.py +5 -0
- agno/models/aimlapi/aimlapi.py +62 -0
- agno/models/anthropic/__init__.py +4 -0
- agno/models/anthropic/claude.py +960 -496
- agno/models/aws/__init__.py +15 -0
- agno/models/aws/bedrock.py +686 -451
- agno/models/aws/claude.py +190 -183
- agno/models/azure/__init__.py +18 -1
- agno/models/azure/ai_foundry.py +489 -0
- agno/models/azure/openai_chat.py +89 -40
- agno/models/base.py +2477 -550
- agno/models/cerebras/__init__.py +12 -0
- agno/models/cerebras/cerebras.py +565 -0
- agno/models/cerebras/cerebras_openai.py +131 -0
- agno/models/cohere/__init__.py +4 -0
- agno/models/cohere/chat.py +306 -492
- agno/models/cometapi/__init__.py +5 -0
- agno/models/cometapi/cometapi.py +74 -0
- agno/models/dashscope/__init__.py +5 -0
- agno/models/dashscope/dashscope.py +90 -0
- agno/models/deepinfra/__init__.py +5 -0
- agno/models/deepinfra/deepinfra.py +45 -0
- agno/models/deepseek/__init__.py +4 -0
- agno/models/deepseek/deepseek.py +110 -9
- agno/models/fireworks/__init__.py +4 -0
- agno/models/fireworks/fireworks.py +19 -22
- agno/models/google/__init__.py +3 -7
- agno/models/google/gemini.py +1717 -662
- agno/models/google/utils.py +22 -0
- agno/models/groq/__init__.py +4 -0
- agno/models/groq/groq.py +391 -666
- agno/models/huggingface/__init__.py +4 -0
- agno/models/huggingface/huggingface.py +266 -538
- agno/models/ibm/__init__.py +5 -0
- agno/models/ibm/watsonx.py +432 -0
- agno/models/internlm/__init__.py +3 -0
- agno/models/internlm/internlm.py +20 -3
- agno/models/langdb/__init__.py +1 -0
- agno/models/langdb/langdb.py +60 -0
- agno/models/litellm/__init__.py +14 -0
- agno/models/litellm/chat.py +503 -0
- agno/models/litellm/litellm_openai.py +42 -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 +361 -39
- agno/models/meta/__init__.py +12 -0
- agno/models/meta/llama.py +502 -0
- agno/models/meta/llama_openai.py +79 -0
- agno/models/metrics.py +120 -0
- agno/models/mistral/__init__.py +4 -0
- agno/models/mistral/mistral.py +293 -393
- agno/models/nebius/__init__.py +3 -0
- agno/models/nebius/nebius.py +53 -0
- agno/models/nexus/__init__.py +3 -0
- agno/models/nexus/nexus.py +22 -0
- agno/models/nvidia/__init__.py +4 -0
- agno/models/nvidia/nvidia.py +22 -3
- agno/models/ollama/__init__.py +4 -2
- agno/models/ollama/chat.py +257 -492
- agno/models/openai/__init__.py +7 -0
- agno/models/openai/chat.py +725 -770
- agno/models/openai/like.py +16 -2
- agno/models/openai/responses.py +1121 -0
- agno/models/openrouter/__init__.py +4 -0
- agno/models/openrouter/openrouter.py +62 -5
- agno/models/perplexity/__init__.py +5 -0
- agno/models/perplexity/perplexity.py +203 -0
- agno/models/portkey/__init__.py +3 -0
- agno/models/portkey/portkey.py +82 -0
- agno/models/requesty/__init__.py +5 -0
- agno/models/requesty/requesty.py +69 -0
- agno/models/response.py +177 -7
- agno/models/sambanova/__init__.py +4 -0
- agno/models/sambanova/sambanova.py +23 -4
- agno/models/siliconflow/__init__.py +5 -0
- agno/models/siliconflow/siliconflow.py +42 -0
- agno/models/together/__init__.py +4 -0
- agno/models/together/together.py +21 -164
- agno/models/utils.py +266 -0
- agno/models/vercel/__init__.py +3 -0
- agno/models/vercel/v0.py +43 -0
- agno/models/vertexai/__init__.py +0 -1
- agno/models/vertexai/claude.py +190 -0
- agno/models/vllm/__init__.py +3 -0
- agno/models/vllm/vllm.py +83 -0
- agno/models/xai/__init__.py +2 -0
- agno/models/xai/xai.py +111 -7
- agno/os/__init__.py +3 -0
- agno/os/app.py +1027 -0
- agno/os/auth.py +244 -0
- agno/os/config.py +126 -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 +249 -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 +147 -0
- agno/os/interfaces/agui/utils.py +574 -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 +210 -0
- agno/os/interfaces/whatsapp/security.py +55 -0
- agno/os/interfaces/whatsapp/whatsapp.py +36 -0
- agno/os/mcp.py +293 -0
- agno/os/middleware/__init__.py +9 -0
- agno/os/middleware/jwt.py +797 -0
- agno/os/router.py +258 -0
- agno/os/routers/__init__.py +3 -0
- agno/os/routers/agents/__init__.py +3 -0
- agno/os/routers/agents/router.py +599 -0
- agno/os/routers/agents/schema.py +261 -0
- agno/os/routers/evals/__init__.py +3 -0
- agno/os/routers/evals/evals.py +450 -0
- agno/os/routers/evals/schemas.py +174 -0
- agno/os/routers/evals/utils.py +231 -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 +1008 -0
- agno/os/routers/knowledge/schemas.py +178 -0
- agno/os/routers/memory/__init__.py +3 -0
- agno/os/routers/memory/memory.py +661 -0
- agno/os/routers/memory/schemas.py +88 -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/routers/teams/__init__.py +3 -0
- agno/os/routers/teams/router.py +512 -0
- agno/os/routers/teams/schema.py +257 -0
- agno/os/routers/traces/__init__.py +3 -0
- agno/os/routers/traces/schemas.py +414 -0
- agno/os/routers/traces/traces.py +499 -0
- agno/os/routers/workflows/__init__.py +3 -0
- agno/os/routers/workflows/router.py +624 -0
- agno/os/routers/workflows/schema.py +75 -0
- agno/os/schema.py +534 -0
- agno/os/scopes.py +469 -0
- agno/{playground → os}/settings.py +7 -15
- agno/os/utils.py +973 -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 +24 -1
- agno/reasoning/ollama.py +67 -0
- agno/reasoning/openai.py +86 -0
- agno/reasoning/step.py +2 -1
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +822 -0
- agno/run/base.py +247 -0
- agno/run/cancel.py +81 -0
- agno/run/requirement.py +181 -0
- agno/run/team.py +767 -0
- agno/run/workflow.py +708 -0
- agno/session/__init__.py +10 -0
- agno/session/agent.py +260 -0
- agno/session/summary.py +265 -0
- agno/session/team.py +342 -0
- agno/session/workflow.py +501 -0
- agno/table.py +10 -0
- agno/team/__init__.py +37 -0
- agno/team/team.py +9536 -0
- agno/tools/__init__.py +7 -0
- agno/tools/agentql.py +120 -0
- agno/tools/airflow.py +22 -12
- agno/tools/api.py +122 -0
- agno/tools/apify.py +276 -83
- agno/tools/{arxiv_toolkit.py → arxiv.py} +20 -12
- agno/tools/aws_lambda.py +28 -7
- agno/tools/aws_ses.py +66 -0
- agno/tools/baidusearch.py +11 -4
- 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 +32 -23
- agno/tools/calculator.py +24 -37
- agno/tools/cartesia.py +187 -0
- agno/tools/{clickup_tool.py → clickup.py} +17 -28
- agno/tools/confluence.py +91 -26
- agno/tools/crawl4ai.py +139 -43
- agno/tools/csv_toolkit.py +28 -22
- agno/tools/dalle.py +36 -22
- agno/tools/daytona.py +475 -0
- agno/tools/decorator.py +169 -14
- agno/tools/desi_vocal.py +23 -11
- agno/tools/discord.py +32 -29
- agno/tools/docker.py +716 -0
- agno/tools/duckdb.py +76 -81
- agno/tools/duckduckgo.py +43 -40
- agno/tools/e2b.py +703 -0
- agno/tools/eleven_labs.py +65 -54
- agno/tools/email.py +13 -5
- agno/tools/evm.py +129 -0
- agno/tools/exa.py +324 -42
- agno/tools/fal.py +39 -35
- agno/tools/file.py +196 -30
- agno/tools/file_generation.py +356 -0
- agno/tools/financial_datasets.py +288 -0
- agno/tools/firecrawl.py +108 -33
- agno/tools/function.py +960 -122
- agno/tools/giphy.py +34 -12
- agno/tools/github.py +1294 -97
- agno/tools/gmail.py +922 -0
- agno/tools/google_bigquery.py +117 -0
- agno/tools/google_drive.py +271 -0
- agno/tools/google_maps.py +253 -0
- agno/tools/googlecalendar.py +607 -107
- agno/tools/googlesheets.py +377 -0
- agno/tools/hackernews.py +20 -12
- agno/tools/jina.py +24 -14
- agno/tools/jira.py +48 -19
- agno/tools/knowledge.py +218 -0
- agno/tools/linear.py +82 -43
- agno/tools/linkup.py +58 -0
- agno/tools/local_file_system.py +15 -7
- agno/tools/lumalab.py +41 -26
- 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/memory.py +419 -0
- agno/tools/mlx_transcribe.py +11 -9
- 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 +163 -82
- agno/tools/moviepy_video.py +18 -13
- agno/tools/nano_banana.py +151 -0
- agno/tools/neo4j.py +134 -0
- agno/tools/newspaper.py +15 -4
- agno/tools/newspaper4k.py +19 -6
- agno/tools/notion.py +204 -0
- agno/tools/openai.py +181 -17
- agno/tools/openbb.py +27 -20
- agno/tools/opencv.py +321 -0
- agno/tools/openweather.py +233 -0
- agno/tools/oxylabs.py +385 -0
- agno/tools/pandas.py +25 -15
- agno/tools/parallel.py +314 -0
- agno/tools/postgres.py +238 -185
- agno/tools/pubmed.py +125 -13
- agno/tools/python.py +48 -35
- agno/tools/reasoning.py +283 -0
- agno/tools/reddit.py +207 -29
- agno/tools/redshift.py +406 -0
- agno/tools/replicate.py +69 -26
- agno/tools/resend.py +11 -6
- agno/tools/scrapegraph.py +179 -19
- agno/tools/searxng.py +23 -31
- agno/tools/serpapi.py +15 -10
- agno/tools/serper.py +255 -0
- agno/tools/shell.py +23 -12
- agno/tools/shopify.py +1519 -0
- agno/tools/slack.py +56 -14
- agno/tools/sleep.py +8 -6
- agno/tools/spider.py +35 -11
- agno/tools/spotify.py +919 -0
- agno/tools/sql.py +34 -19
- agno/tools/tavily.py +158 -8
- agno/tools/telegram.py +18 -8
- agno/tools/todoist.py +218 -0
- agno/tools/toolkit.py +134 -9
- agno/tools/trafilatura.py +388 -0
- agno/tools/trello.py +25 -28
- agno/tools/twilio.py +18 -9
- 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 +23 -19
- agno/tools/webtools.py +45 -0
- agno/tools/whatsapp.py +286 -0
- agno/tools/wikipedia.py +28 -19
- agno/tools/workflow.py +285 -0
- agno/tools/{twitter.py → x.py} +142 -46
- agno/tools/yfinance.py +41 -39
- agno/tools/youtube.py +34 -17
- agno/tools/zendesk.py +15 -5
- agno/tools/zep.py +454 -0
- agno/tools/zoom.py +86 -37
- agno/tracing/__init__.py +12 -0
- agno/tracing/exporter.py +157 -0
- agno/tracing/schemas.py +276 -0
- agno/tracing/setup.py +111 -0
- agno/utils/agent.py +938 -0
- agno/utils/audio.py +37 -1
- agno/utils/certs.py +27 -0
- agno/utils/code_execution.py +11 -0
- agno/utils/common.py +103 -20
- agno/utils/cryptography.py +22 -0
- agno/utils/dttm.py +33 -0
- agno/utils/events.py +700 -0
- agno/utils/functions.py +107 -37
- agno/utils/gemini.py +426 -0
- agno/utils/hooks.py +171 -0
- agno/utils/http.py +185 -0
- agno/utils/json_schema.py +159 -37
- agno/utils/knowledge.py +36 -0
- agno/utils/location.py +19 -0
- agno/utils/log.py +221 -8
- agno/utils/mcp.py +214 -0
- agno/utils/media.py +335 -14
- agno/utils/merge_dict.py +22 -1
- agno/utils/message.py +77 -2
- agno/utils/models/ai_foundry.py +50 -0
- agno/utils/models/claude.py +373 -0
- agno/utils/models/cohere.py +94 -0
- agno/utils/models/llama.py +85 -0
- agno/utils/models/mistral.py +100 -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 +1 -1
- agno/utils/pprint.py +124 -8
- agno/utils/print_response/agent.py +930 -0
- agno/utils/print_response/team.py +1914 -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/serialize.py +32 -0
- agno/utils/shell.py +4 -4
- agno/utils/streamlit.py +487 -0
- agno/utils/string.py +204 -51
- agno/utils/team.py +139 -0
- agno/utils/timer.py +9 -2
- agno/utils/tokens.py +657 -0
- agno/utils/tools.py +19 -1
- agno/utils/whatsapp.py +305 -0
- agno/utils/yaml_io.py +3 -3
- agno/vectordb/__init__.py +2 -0
- agno/vectordb/base.py +87 -9
- agno/vectordb/cassandra/__init__.py +5 -1
- agno/vectordb/cassandra/cassandra.py +383 -27
- agno/vectordb/chroma/__init__.py +4 -0
- agno/vectordb/chroma/chromadb.py +748 -83
- agno/vectordb/clickhouse/__init__.py +7 -1
- agno/vectordb/clickhouse/clickhousedb.py +554 -53
- agno/vectordb/couchbase/__init__.py +3 -0
- agno/vectordb/couchbase/couchbase.py +1446 -0
- agno/vectordb/lancedb/__init__.py +5 -0
- agno/vectordb/lancedb/lance_db.py +730 -98
- 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 +3 -0
- agno/vectordb/milvus/milvus.py +966 -78
- agno/vectordb/mongodb/__init__.py +9 -1
- agno/vectordb/mongodb/mongodb.py +1175 -172
- agno/vectordb/pgvector/__init__.py +8 -0
- agno/vectordb/pgvector/pgvector.py +599 -115
- agno/vectordb/pineconedb/__init__.py +5 -1
- agno/vectordb/pineconedb/pineconedb.py +406 -43
- agno/vectordb/qdrant/__init__.py +4 -0
- agno/vectordb/qdrant/qdrant.py +914 -61
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +682 -0
- agno/vectordb/singlestore/__init__.py +8 -1
- agno/vectordb/singlestore/singlestore.py +771 -0
- agno/vectordb/surrealdb/__init__.py +3 -0
- agno/vectordb/surrealdb/surrealdb.py +663 -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 +1009 -0
- agno/workflow/__init__.py +23 -1
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +759 -0
- agno/workflow/loop.py +756 -0
- agno/workflow/parallel.py +853 -0
- agno/workflow/router.py +723 -0
- agno/workflow/step.py +1564 -0
- agno/workflow/steps.py +613 -0
- agno/workflow/types.py +556 -0
- agno/workflow/workflow.py +4327 -514
- agno-2.3.13.dist-info/METADATA +639 -0
- agno-2.3.13.dist-info/RECORD +613 -0
- {agno-0.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +1 -1
- agno-2.3.13.dist-info/licenses/LICENSE +201 -0
- agno/api/playground.py +0 -91
- agno/api/schemas/playground.py +0 -22
- agno/api/schemas/user.py +0 -22
- agno/api/schemas/workspace.py +0 -46
- agno/api/user.py +0 -160
- agno/api/workspace.py +0 -151
- agno/cli/auth_server.py +0 -118
- agno/cli/config.py +0 -275
- agno/cli/console.py +0 -88
- agno/cli/credentials.py +0 -23
- agno/cli/entrypoint.py +0 -571
- agno/cli/operator.py +0 -355
- agno/cli/settings.py +0 -85
- agno/cli/ws/ws_cli.py +0 -817
- agno/constants.py +0 -13
- agno/document/__init__.py +0 -1
- agno/document/chunking/semantic.py +0 -47
- agno/document/chunking/strategy.py +0 -31
- agno/document/reader/__init__.py +0 -1
- agno/document/reader/arxiv_reader.py +0 -41
- agno/document/reader/base.py +0 -22
- agno/document/reader/csv_reader.py +0 -84
- agno/document/reader/docx_reader.py +0 -46
- agno/document/reader/firecrawl_reader.py +0 -99
- agno/document/reader/json_reader.py +0 -43
- agno/document/reader/pdf_reader.py +0 -219
- agno/document/reader/s3/pdf_reader.py +0 -46
- agno/document/reader/s3/text_reader.py +0 -51
- agno/document/reader/text_reader.py +0 -41
- agno/document/reader/website_reader.py +0 -175
- agno/document/reader/youtube_reader.py +0 -50
- agno/embedder/__init__.py +0 -1
- agno/embedder/azure_openai.py +0 -86
- agno/embedder/cohere.py +0 -72
- agno/embedder/fastembed.py +0 -37
- agno/embedder/google.py +0 -73
- agno/embedder/huggingface.py +0 -54
- agno/embedder/mistral.py +0 -80
- agno/embedder/ollama.py +0 -57
- agno/embedder/openai.py +0 -74
- agno/embedder/sentence_transformer.py +0 -38
- agno/embedder/voyageai.py +0 -64
- agno/eval/perf.py +0 -201
- agno/file/__init__.py +0 -1
- agno/file/file.py +0 -16
- agno/file/local/csv.py +0 -32
- agno/file/local/txt.py +0 -19
- agno/infra/app.py +0 -240
- agno/infra/base.py +0 -144
- agno/infra/context.py +0 -20
- agno/infra/db_app.py +0 -52
- agno/infra/resource.py +0 -205
- agno/infra/resources.py +0 -55
- agno/knowledge/agent.py +0 -230
- agno/knowledge/arxiv.py +0 -22
- agno/knowledge/combined.py +0 -22
- agno/knowledge/csv.py +0 -28
- agno/knowledge/csv_url.py +0 -19
- agno/knowledge/document.py +0 -20
- agno/knowledge/docx.py +0 -30
- agno/knowledge/json.py +0 -28
- agno/knowledge/langchain.py +0 -71
- agno/knowledge/llamaindex.py +0 -66
- agno/knowledge/pdf.py +0 -28
- agno/knowledge/pdf_url.py +0 -26
- agno/knowledge/s3/base.py +0 -60
- agno/knowledge/s3/pdf.py +0 -21
- agno/knowledge/s3/text.py +0 -23
- agno/knowledge/text.py +0 -30
- agno/knowledge/website.py +0 -88
- agno/knowledge/wikipedia.py +0 -31
- agno/knowledge/youtube.py +0 -22
- agno/memory/agent.py +0 -392
- agno/memory/classifier.py +0 -104
- agno/memory/db/__init__.py +0 -1
- agno/memory/db/base.py +0 -42
- agno/memory/db/mongodb.py +0 -189
- agno/memory/db/postgres.py +0 -203
- agno/memory/db/sqlite.py +0 -193
- agno/memory/memory.py +0 -15
- agno/memory/row.py +0 -36
- agno/memory/summarizer.py +0 -192
- agno/memory/summary.py +0 -19
- agno/memory/workflow.py +0 -38
- agno/models/google/gemini_openai.py +0 -26
- agno/models/ollama/hermes.py +0 -221
- agno/models/ollama/tools.py +0 -362
- agno/models/vertexai/gemini.py +0 -595
- agno/playground/__init__.py +0 -3
- agno/playground/async_router.py +0 -421
- agno/playground/deploy.py +0 -249
- agno/playground/operator.py +0 -92
- agno/playground/playground.py +0 -91
- agno/playground/schemas.py +0 -76
- agno/playground/serve.py +0 -55
- agno/playground/sync_router.py +0 -405
- agno/reasoning/agent.py +0 -68
- agno/run/response.py +0 -112
- agno/storage/agent/__init__.py +0 -0
- agno/storage/agent/base.py +0 -38
- agno/storage/agent/dynamodb.py +0 -350
- agno/storage/agent/json.py +0 -92
- agno/storage/agent/mongodb.py +0 -228
- agno/storage/agent/postgres.py +0 -367
- agno/storage/agent/session.py +0 -79
- agno/storage/agent/singlestore.py +0 -303
- agno/storage/agent/sqlite.py +0 -357
- agno/storage/agent/yaml.py +0 -93
- agno/storage/workflow/__init__.py +0 -0
- agno/storage/workflow/base.py +0 -40
- agno/storage/workflow/mongodb.py +0 -233
- agno/storage/workflow/postgres.py +0 -366
- agno/storage/workflow/session.py +0 -60
- agno/storage/workflow/sqlite.py +0 -359
- agno/tools/googlesearch.py +0 -88
- agno/utils/defaults.py +0 -57
- agno/utils/filesystem.py +0 -39
- agno/utils/git.py +0 -52
- agno/utils/json_io.py +0 -30
- agno/utils/load_env.py +0 -19
- agno/utils/py_io.py +0 -19
- agno/utils/pyproject.py +0 -18
- agno/utils/resource_filter.py +0 -31
- agno/vectordb/singlestore/s2vectordb.py +0 -390
- agno/vectordb/singlestore/s2vectordb2.py +0 -355
- agno/workspace/__init__.py +0 -0
- agno/workspace/config.py +0 -325
- agno/workspace/enums.py +0 -6
- agno/workspace/helpers.py +0 -48
- agno/workspace/operator.py +0 -758
- agno/workspace/settings.py +0 -63
- agno-0.1.2.dist-info/LICENSE +0 -375
- agno-0.1.2.dist-info/METADATA +0 -502
- agno-0.1.2.dist-info/RECORD +0 -352
- agno-0.1.2.dist-info/entry_points.txt +0 -3
- /agno/{cli → db/migrations}/__init__.py +0 -0
- /agno/{cli/ws → db/migrations/versions}/__init__.py +0 -0
- /agno/{document/chunking/__init__.py → db/schemas/metrics.py} +0 -0
- /agno/{document/reader/s3 → integrations}/__init__.py +0 -0
- /agno/{file/local → knowledge/chunking}/__init__.py +0 -0
- /agno/{infra → knowledge/remote_content}/__init__.py +0 -0
- /agno/{knowledge/s3 → tools/models}/__init__.py +0 -0
- /agno/{reranker → utils/models}/__init__.py +0 -0
- /agno/{storage → utils/print_response}/__init__.py +0 -0
- {agno-0.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any, Dict, List, Optional, Sequence, Union
|
|
3
|
+
|
|
4
|
+
from agno.media import Image
|
|
5
|
+
from agno.utils.log import logger
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _process_bytes_image(image: bytes) -> Dict[str, Any]:
|
|
9
|
+
"""Process bytes image data."""
|
|
10
|
+
import base64
|
|
11
|
+
|
|
12
|
+
base64_image = base64.b64encode(image).decode("utf-8")
|
|
13
|
+
image_url = f"data:image/jpeg;base64,{base64_image}"
|
|
14
|
+
return {"type": "input_image", "image_url": image_url}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _process_image_path(image_path: Union[Path, str]) -> Dict[str, Any]:
|
|
18
|
+
"""Process image ( file path)."""
|
|
19
|
+
# Process local file image
|
|
20
|
+
import base64
|
|
21
|
+
import mimetypes
|
|
22
|
+
|
|
23
|
+
path = image_path if isinstance(image_path, Path) else Path(image_path)
|
|
24
|
+
if not path.exists():
|
|
25
|
+
raise FileNotFoundError(f"Image file not found: {image_path}")
|
|
26
|
+
|
|
27
|
+
mime_type = mimetypes.guess_type(image_path)[0] or "image/jpeg"
|
|
28
|
+
with open(path, "rb") as image_file:
|
|
29
|
+
base64_image = base64.b64encode(image_file.read()).decode("utf-8")
|
|
30
|
+
image_url = f"data:{mime_type};base64,{base64_image}"
|
|
31
|
+
return {"type": "input_image", "image_url": image_url}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _process_image_url(image_url: str) -> Dict[str, Any]:
|
|
35
|
+
"""Process image (base64 or URL)."""
|
|
36
|
+
|
|
37
|
+
if image_url.startswith("data:image") or image_url.startswith(("http://", "https://")):
|
|
38
|
+
return {"type": "input_image", "image_url": image_url}
|
|
39
|
+
else:
|
|
40
|
+
raise ValueError("Image URL must start with 'data:image' or 'http(s)://'.")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _process_image(image: Image) -> Optional[Dict[str, Any]]:
|
|
44
|
+
"""Process an image based on the format."""
|
|
45
|
+
|
|
46
|
+
if image.url is not None:
|
|
47
|
+
image_payload = _process_image_url(image.url)
|
|
48
|
+
|
|
49
|
+
elif image.filepath is not None:
|
|
50
|
+
image_payload = _process_image_path(image.filepath)
|
|
51
|
+
|
|
52
|
+
elif image.content is not None:
|
|
53
|
+
image_payload = _process_bytes_image(image.content)
|
|
54
|
+
|
|
55
|
+
else:
|
|
56
|
+
logger.warning(f"Unsupported image format: {image}")
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
if image.detail:
|
|
60
|
+
image_payload["image_url"]["detail"] = image.detail
|
|
61
|
+
|
|
62
|
+
return image_payload
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def images_to_message(images: Sequence[Image]) -> List[Dict[str, Any]]:
|
|
66
|
+
"""
|
|
67
|
+
Add images to a message for the model. By default, we use the OpenAI image format but other Models
|
|
68
|
+
can override this method to use a different image format.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
images: Sequence of images in various formats:
|
|
72
|
+
- str: base64 encoded image, URL, or file path
|
|
73
|
+
- Dict: pre-formatted image data
|
|
74
|
+
- bytes: raw image data
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Message content with images added in the format expected by the model
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
# Create a default message content with text
|
|
81
|
+
image_messages: List[Dict[str, Any]] = []
|
|
82
|
+
|
|
83
|
+
# Add images to the message content
|
|
84
|
+
for image in images:
|
|
85
|
+
try:
|
|
86
|
+
image_data = _process_image(image)
|
|
87
|
+
if image_data:
|
|
88
|
+
image_messages.append(image_data)
|
|
89
|
+
except Exception as e:
|
|
90
|
+
logger.error(f"Failed to process image: {str(e)}")
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
return image_messages
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def sanitize_response_schema(schema: dict):
|
|
97
|
+
"""
|
|
98
|
+
Recursively sanitize a Pydantic-generated JSON schema to comply with OpenAI's response_format rules:
|
|
99
|
+
|
|
100
|
+
- Sets "additionalProperties": false for all object types to disallow extra fields,
|
|
101
|
+
EXCEPT when additionalProperties is already defined with a schema (Dict types).
|
|
102
|
+
- Removes "default": null from optional fields.
|
|
103
|
+
- Ensures that all fields defined in "properties" are listed in "required",
|
|
104
|
+
making every property explicitly required as per OpenAI's expectations,
|
|
105
|
+
EXCEPT for Dict fields which should not be in the required array.
|
|
106
|
+
"""
|
|
107
|
+
if isinstance(schema, dict):
|
|
108
|
+
# Enforce additionalProperties: false for object types, but preserve Dict schemas
|
|
109
|
+
if schema.get("type") == "object":
|
|
110
|
+
# Only set additionalProperties to False if it's not already defined with a schema
|
|
111
|
+
# This preserves Dict[str, T] fields which need additionalProperties to define value types
|
|
112
|
+
if "additionalProperties" not in schema:
|
|
113
|
+
schema["additionalProperties"] = False
|
|
114
|
+
elif schema.get("additionalProperties") is True:
|
|
115
|
+
# Convert True to False for strict mode, but preserve schema objects
|
|
116
|
+
schema["additionalProperties"] = False
|
|
117
|
+
|
|
118
|
+
# Ensure all properties are required, EXCEPT Dict fields
|
|
119
|
+
if "properties" in schema:
|
|
120
|
+
from agno.utils.models.schema_utils import is_dict_field
|
|
121
|
+
|
|
122
|
+
required_fields = []
|
|
123
|
+
for prop_name, prop_schema in schema["properties"].items():
|
|
124
|
+
# Use the utility function to check if this is a Dict field
|
|
125
|
+
if not is_dict_field(prop_schema):
|
|
126
|
+
required_fields.append(prop_name)
|
|
127
|
+
|
|
128
|
+
schema["required"] = required_fields
|
|
129
|
+
|
|
130
|
+
# Remove only default: null
|
|
131
|
+
if "default" in schema and schema["default"] is None:
|
|
132
|
+
schema.pop("default")
|
|
133
|
+
|
|
134
|
+
# Recurse into all values
|
|
135
|
+
for value in schema.values():
|
|
136
|
+
sanitize_response_schema(value)
|
|
137
|
+
|
|
138
|
+
elif isinstance(schema, list):
|
|
139
|
+
for item in schema:
|
|
140
|
+
sanitize_response_schema(item)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility functions for handling JSON schemas across different model providers.
|
|
3
|
+
This module provides model-agnostic schema transformations and validations.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any, Dict, Type
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def is_dict_field(schema: Dict[str, Any]) -> bool:
|
|
12
|
+
"""
|
|
13
|
+
Check if a schema represents a Dict[str, T] field.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
schema: JSON schema dictionary
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
bool: True if the schema represents a Dict field
|
|
20
|
+
"""
|
|
21
|
+
return (
|
|
22
|
+
schema.get("type") == "object"
|
|
23
|
+
and "additionalProperties" in schema
|
|
24
|
+
and isinstance(schema["additionalProperties"], dict)
|
|
25
|
+
and "type" in schema["additionalProperties"]
|
|
26
|
+
and "properties" not in schema
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_dict_value_type(schema: Dict[str, Any]) -> str:
|
|
31
|
+
"""
|
|
32
|
+
Extract the value type from a Dict field schema.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
schema: JSON schema dictionary for a Dict field
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
str: The type of values in the dictionary (e.g., "integer", "string")
|
|
39
|
+
"""
|
|
40
|
+
if is_dict_field(schema):
|
|
41
|
+
return schema["additionalProperties"]["type"]
|
|
42
|
+
return "string"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def normalize_schema_for_provider(schema: Dict[str, Any], provider: str) -> Dict[str, Any]:
|
|
46
|
+
"""
|
|
47
|
+
Normalize a Pydantic-generated schema for a specific model provider.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
schema: Original Pydantic JSON schema
|
|
51
|
+
provider: Model provider name ("openai", "gemini", "anthropic", etc.)
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Dict[str, Any]: Normalized schema for the provider
|
|
55
|
+
"""
|
|
56
|
+
# Make a deep copy to avoid modifying the original
|
|
57
|
+
import copy
|
|
58
|
+
|
|
59
|
+
normalized = copy.deepcopy(schema)
|
|
60
|
+
|
|
61
|
+
if provider.lower() == "openai":
|
|
62
|
+
return _normalize_for_openai(normalized)
|
|
63
|
+
elif provider.lower() == "gemini":
|
|
64
|
+
return _normalize_for_gemini(normalized)
|
|
65
|
+
else:
|
|
66
|
+
# Default normalization for other providers
|
|
67
|
+
return _normalize_generic(normalized)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _normalize_for_openai(schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
71
|
+
"""Normalize schema for OpenAI structured outputs."""
|
|
72
|
+
from agno.utils.models.openai_responses import sanitize_response_schema
|
|
73
|
+
|
|
74
|
+
sanitize_response_schema(schema)
|
|
75
|
+
return schema
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _normalize_for_gemini(schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
79
|
+
"""
|
|
80
|
+
Normalize schema for Gemini.
|
|
81
|
+
Gemini has specific requirements for object types and doesn't support
|
|
82
|
+
additionalProperties in the same way as JSON Schema.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
def _process_schema(s: Dict[str, Any]) -> None:
|
|
86
|
+
if isinstance(s, dict):
|
|
87
|
+
# Handle Dict fields - preserve additionalProperties info for convert_schema
|
|
88
|
+
if is_dict_field(s):
|
|
89
|
+
# For Gemini, we need to preserve the additionalProperties info
|
|
90
|
+
# so that convert_schema can create appropriate placeholder properties
|
|
91
|
+
value_type = get_dict_value_type(s)
|
|
92
|
+
|
|
93
|
+
# Update description to indicate this is a dictionary
|
|
94
|
+
current_desc = s.get("description", "")
|
|
95
|
+
if current_desc:
|
|
96
|
+
s["description"] = f"{current_desc} (Dictionary with {value_type} values)"
|
|
97
|
+
else:
|
|
98
|
+
s["description"] = f"Dictionary with {value_type} values"
|
|
99
|
+
|
|
100
|
+
# Keep additionalProperties for convert_schema to process
|
|
101
|
+
# Don't remove it here - let convert_schema handle the conversion
|
|
102
|
+
|
|
103
|
+
# Recursively process nested schemas
|
|
104
|
+
for value in s.values():
|
|
105
|
+
if isinstance(value, dict):
|
|
106
|
+
_process_schema(value)
|
|
107
|
+
elif isinstance(value, list):
|
|
108
|
+
for item in value:
|
|
109
|
+
if isinstance(item, dict):
|
|
110
|
+
_process_schema(item)
|
|
111
|
+
|
|
112
|
+
_process_schema(schema)
|
|
113
|
+
return schema
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _normalize_generic(schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
117
|
+
"""Generic normalization for other providers."""
|
|
118
|
+
|
|
119
|
+
def _process_schema(s: Dict[str, Any]) -> None:
|
|
120
|
+
if isinstance(s, dict):
|
|
121
|
+
# Remove null defaults
|
|
122
|
+
if "default" in s and s["default"] is None:
|
|
123
|
+
s.pop("default")
|
|
124
|
+
|
|
125
|
+
# Recursively process nested schemas
|
|
126
|
+
for value in s.values():
|
|
127
|
+
if isinstance(value, dict):
|
|
128
|
+
_process_schema(value)
|
|
129
|
+
elif isinstance(value, list):
|
|
130
|
+
for item in value:
|
|
131
|
+
if isinstance(item, dict):
|
|
132
|
+
_process_schema(item)
|
|
133
|
+
|
|
134
|
+
_process_schema(schema)
|
|
135
|
+
return schema
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def get_response_schema_for_provider(output_schema: Type[BaseModel], provider: str) -> Dict[str, Any]:
|
|
139
|
+
"""
|
|
140
|
+
Get a properly formatted response schema for a specific model provider.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
output_schema: Pydantic BaseModel class
|
|
144
|
+
provider: Model provider name
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Dict[str, Any]: Provider-specific schema
|
|
148
|
+
"""
|
|
149
|
+
# Generate the base schema
|
|
150
|
+
base_schema = output_schema.model_json_schema()
|
|
151
|
+
|
|
152
|
+
# Normalize for the specific provider
|
|
153
|
+
return normalize_schema_for_provider(base_schema, provider)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Sequence
|
|
2
|
+
|
|
3
|
+
from agno.media import Image
|
|
4
|
+
from agno.models.message import Message
|
|
5
|
+
from agno.utils.log import log_error, log_warning
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def format_images_for_message(message: Message, images: Sequence[Image]) -> Message:
|
|
9
|
+
"""
|
|
10
|
+
Format an image into the format expected by WatsonX.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
# Create a default message content with text
|
|
14
|
+
message_content_with_image: List[Dict[str, Any]] = [{"type": "text", "text": message.content}]
|
|
15
|
+
|
|
16
|
+
# Add images to the message content
|
|
17
|
+
for image in images:
|
|
18
|
+
try:
|
|
19
|
+
if image.content is not None:
|
|
20
|
+
image_content = image.content
|
|
21
|
+
elif image.url is not None:
|
|
22
|
+
image_content = image.get_content_bytes() # type: ignore
|
|
23
|
+
else:
|
|
24
|
+
log_warning(f"Unsupported image format: {image}")
|
|
25
|
+
continue
|
|
26
|
+
|
|
27
|
+
if image_content is not None:
|
|
28
|
+
import base64
|
|
29
|
+
|
|
30
|
+
base64_image = base64.b64encode(image_content).decode("utf-8")
|
|
31
|
+
image_url = f"data:image/jpeg;base64,{base64_image}"
|
|
32
|
+
image_payload = {"type": "image_url", "image_url": {"url": image_url}}
|
|
33
|
+
message_content_with_image.append(image_payload)
|
|
34
|
+
|
|
35
|
+
except Exception as e:
|
|
36
|
+
log_error(f"Failed to process image: {str(e)}")
|
|
37
|
+
|
|
38
|
+
# Update the message content with the images
|
|
39
|
+
if len(message_content_with_image) > 1:
|
|
40
|
+
message.content = message_content_with_image
|
|
41
|
+
return message
|
agno/utils/openai.py
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import mimetypes
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Dict, List, Optional, Sequence, Union
|
|
5
|
+
|
|
6
|
+
from agno.media import Audio, File, Image
|
|
7
|
+
from agno.utils.log import log_error, log_warning
|
|
8
|
+
|
|
9
|
+
# Ensure .webp is recognized
|
|
10
|
+
mimetypes.add_type("image/webp", ".webp")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def audio_to_message(audio: Sequence[Audio]) -> List[Dict[str, Any]]:
|
|
14
|
+
"""
|
|
15
|
+
Add audio to a message for the model. By default, we use the OpenAI audio format but other Models
|
|
16
|
+
can override this method to use a different audio format.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
audio: Pre-formatted audio data like {
|
|
20
|
+
"content": encoded_string,
|
|
21
|
+
"format": "wav"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Message content with audio added in the format expected by the model
|
|
26
|
+
"""
|
|
27
|
+
from urllib.parse import urlparse
|
|
28
|
+
|
|
29
|
+
audio_messages = []
|
|
30
|
+
for audio_snippet in audio:
|
|
31
|
+
encoded_string: Optional[str] = None
|
|
32
|
+
audio_format: Optional[str] = audio_snippet.format
|
|
33
|
+
|
|
34
|
+
# The audio is raw data
|
|
35
|
+
if audio_snippet.content:
|
|
36
|
+
encoded_string = base64.b64encode(audio_snippet.content).decode("utf-8")
|
|
37
|
+
if not audio_format:
|
|
38
|
+
audio_format = "wav" # Default format if not provided
|
|
39
|
+
|
|
40
|
+
# The audio is a URL
|
|
41
|
+
elif audio_snippet.url:
|
|
42
|
+
audio_bytes = audio_snippet.get_content_bytes()
|
|
43
|
+
if audio_bytes is not None:
|
|
44
|
+
encoded_string = base64.b64encode(audio_bytes).decode("utf-8")
|
|
45
|
+
if not audio_format:
|
|
46
|
+
# Try to guess format from URL extension
|
|
47
|
+
try:
|
|
48
|
+
# Parse the URL first to isolate the path
|
|
49
|
+
parsed_url = urlparse(audio_snippet.url)
|
|
50
|
+
# Get suffix from the path component only
|
|
51
|
+
audio_format = Path(parsed_url.path).suffix.lstrip(".")
|
|
52
|
+
if not audio_format: # Handle cases like URLs ending in /
|
|
53
|
+
log_warning(
|
|
54
|
+
f"Could not determine audio format from URL path: {parsed_url.path}. Defaulting."
|
|
55
|
+
)
|
|
56
|
+
audio_format = "wav"
|
|
57
|
+
except Exception as e:
|
|
58
|
+
log_warning(
|
|
59
|
+
f"Could not determine audio format from URL: {audio_snippet.url}. Error: {e}. Defaulting."
|
|
60
|
+
)
|
|
61
|
+
audio_format = "wav" # Default if guessing fails
|
|
62
|
+
|
|
63
|
+
# The audio is a file path
|
|
64
|
+
elif audio_snippet.filepath:
|
|
65
|
+
path = Path(audio_snippet.filepath)
|
|
66
|
+
if path.exists() and path.is_file():
|
|
67
|
+
try:
|
|
68
|
+
with open(path, "rb") as audio_file:
|
|
69
|
+
encoded_string = base64.b64encode(audio_file.read()).decode("utf-8")
|
|
70
|
+
if not audio_format:
|
|
71
|
+
audio_format = path.suffix.lstrip(".")
|
|
72
|
+
except Exception as e:
|
|
73
|
+
log_error(f"Failed to read audio file {path}: {e}")
|
|
74
|
+
continue # Skip this audio snippet if file reading fails
|
|
75
|
+
else:
|
|
76
|
+
log_error(f"Audio file not found or is not a file: {path}")
|
|
77
|
+
continue # Skip if file doesn't exist
|
|
78
|
+
|
|
79
|
+
# Append the message if we successfully processed the audio
|
|
80
|
+
if encoded_string and audio_format:
|
|
81
|
+
audio_messages.append(
|
|
82
|
+
{
|
|
83
|
+
"type": "input_audio",
|
|
84
|
+
"input_audio": {
|
|
85
|
+
"data": encoded_string,
|
|
86
|
+
"format": audio_format,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
)
|
|
90
|
+
else:
|
|
91
|
+
log_error(f"Could not process audio snippet: {audio_snippet}")
|
|
92
|
+
|
|
93
|
+
return audio_messages
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _process_bytes_image(image: bytes, image_format: Optional[str] = None) -> Dict[str, Any]:
|
|
97
|
+
"""Process bytes image data."""
|
|
98
|
+
base64_image = base64.b64encode(image).decode("utf-8")
|
|
99
|
+
|
|
100
|
+
# Use provided format or attempt detection, defaulting to JPEG
|
|
101
|
+
if image_format:
|
|
102
|
+
mime_type = f"image/{image_format.lower()}"
|
|
103
|
+
else:
|
|
104
|
+
# Try to detect the image format from the bytes
|
|
105
|
+
try:
|
|
106
|
+
import imghdr
|
|
107
|
+
|
|
108
|
+
detected_format = imghdr.what(None, h=image)
|
|
109
|
+
mime_type = f"image/{detected_format}" if detected_format else "image/jpeg"
|
|
110
|
+
except Exception:
|
|
111
|
+
mime_type = "image/jpeg"
|
|
112
|
+
|
|
113
|
+
image_url = f"data:{mime_type};base64,{base64_image}"
|
|
114
|
+
return {"type": "image_url", "image_url": {"url": image_url}}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _process_image_path(image_path: Union[Path, str]) -> Dict[str, Any]:
|
|
118
|
+
"""Process image ( file path)."""
|
|
119
|
+
# Process local file image
|
|
120
|
+
path = Path(image_path) # Ensure it's a Path object
|
|
121
|
+
if not path.exists():
|
|
122
|
+
raise FileNotFoundError(f"Image file not found: {image_path}")
|
|
123
|
+
if not path.is_file():
|
|
124
|
+
raise IsADirectoryError(f"Image path is not a file: {image_path}")
|
|
125
|
+
|
|
126
|
+
mime_type = mimetypes.guess_type(path)[0] or "image/jpeg" # Default to jpeg if guess fails
|
|
127
|
+
try:
|
|
128
|
+
with open(path, "rb") as image_file:
|
|
129
|
+
base64_image = base64.b64encode(image_file.read()).decode("utf-8")
|
|
130
|
+
image_url = f"data:{mime_type};base64,{base64_image}"
|
|
131
|
+
return {"type": "image_url", "image_url": {"url": image_url}}
|
|
132
|
+
except Exception as e:
|
|
133
|
+
log_error(f"Failed to read image file {path}: {e}")
|
|
134
|
+
raise # Re-raise the exception after logging
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _process_image_url(image_url: str) -> Dict[str, Any]:
|
|
138
|
+
"""Process image (base64 or URL)."""
|
|
139
|
+
|
|
140
|
+
if image_url.startswith("data:image") or image_url.startswith(("http://", "https://")):
|
|
141
|
+
return {"type": "image_url", "image_url": {"url": image_url}}
|
|
142
|
+
else:
|
|
143
|
+
raise ValueError("Image URL must start with 'data:image' or 'http(s)://'.")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def process_image(image: Image) -> Optional[Dict[str, Any]]:
|
|
147
|
+
"""Process an image based on the format."""
|
|
148
|
+
image_payload: Optional[Dict[str, Any]] = None # Initialize
|
|
149
|
+
try:
|
|
150
|
+
if image.url is not None:
|
|
151
|
+
image_payload = _process_image_url(image.url)
|
|
152
|
+
|
|
153
|
+
elif image.filepath is not None:
|
|
154
|
+
image_payload = _process_image_path(image.filepath)
|
|
155
|
+
|
|
156
|
+
elif image.content is not None:
|
|
157
|
+
# Pass the format from the Image object
|
|
158
|
+
image_payload = _process_bytes_image(image.content, image.format)
|
|
159
|
+
|
|
160
|
+
else:
|
|
161
|
+
log_warning(f"Unsupported image format or no data provided: {image}")
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
if image_payload and image.detail: # Check if payload was created before adding detail
|
|
165
|
+
# Ensure image_url key exists before trying to access its sub-dictionary
|
|
166
|
+
if "image_url" not in image_payload:
|
|
167
|
+
# Initialize if missing (though unlikely based on helper funcs)
|
|
168
|
+
image_payload["image_url"] = {}
|
|
169
|
+
image_payload["image_url"]["detail"] = image.detail
|
|
170
|
+
|
|
171
|
+
return image_payload
|
|
172
|
+
|
|
173
|
+
except (FileNotFoundError, IsADirectoryError, ValueError) as e:
|
|
174
|
+
log_error(f"Failed to process image due to invalid input: {str(e)}")
|
|
175
|
+
return None # Return None for handled validation errors
|
|
176
|
+
except Exception as e:
|
|
177
|
+
log_error(f"An unexpected error occurred while processing image: {str(e)}")
|
|
178
|
+
# Depending on policy, you might want to return None or re-raise
|
|
179
|
+
return None # Return None for unexpected errors as well, preventing crashes
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def images_to_message(images: Sequence[Image]) -> List[Dict[str, Any]]:
|
|
183
|
+
"""
|
|
184
|
+
Add images to a message for the model. By default, we use the OpenAI image format but other Models
|
|
185
|
+
can override this method to use a different image format.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
images: Sequence of images in various formats:
|
|
189
|
+
- str: base64 encoded image, URL, or file path
|
|
190
|
+
- Dict: pre-formatted image data
|
|
191
|
+
- bytes: raw image data
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Message content with images added in the format expected by the model
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
# Create a default message content with text
|
|
198
|
+
image_messages: List[Dict[str, Any]] = []
|
|
199
|
+
|
|
200
|
+
# Add images to the message content
|
|
201
|
+
for image in images:
|
|
202
|
+
try:
|
|
203
|
+
image_data = process_image(image)
|
|
204
|
+
if image_data:
|
|
205
|
+
image_messages.append(image_data)
|
|
206
|
+
except Exception as e:
|
|
207
|
+
log_error(f"Failed to process image: {str(e)}")
|
|
208
|
+
continue
|
|
209
|
+
|
|
210
|
+
return image_messages
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _format_file_for_message(file: File) -> Optional[Dict[str, Any]]:
|
|
214
|
+
"""
|
|
215
|
+
Add a document url, base64 encoded content or OpenAI file to a message.
|
|
216
|
+
"""
|
|
217
|
+
import base64
|
|
218
|
+
import mimetypes
|
|
219
|
+
from pathlib import Path
|
|
220
|
+
|
|
221
|
+
# Case 1: Document is a URL
|
|
222
|
+
if file.url is not None:
|
|
223
|
+
from urllib.parse import urlparse
|
|
224
|
+
|
|
225
|
+
result = file.file_url_content
|
|
226
|
+
if not result:
|
|
227
|
+
log_error(f"Failed to fetch file from URL: {file.url}")
|
|
228
|
+
return None
|
|
229
|
+
content_bytes, mime_type = result
|
|
230
|
+
name = Path(urlparse(file.url).path).name or "file"
|
|
231
|
+
_mime = mime_type or file.mime_type or mimetypes.guess_type(name)[0] or "application/pdf"
|
|
232
|
+
_encoded = base64.b64encode(content_bytes).decode("utf-8")
|
|
233
|
+
_data_url = f"data:{_mime};base64,{_encoded}"
|
|
234
|
+
return {"type": "file", "file": {"filename": name, "file_data": _data_url}}
|
|
235
|
+
|
|
236
|
+
# Case 2: Document is a local file path
|
|
237
|
+
if file.filepath is not None:
|
|
238
|
+
path = Path(file.filepath)
|
|
239
|
+
if not path.is_file():
|
|
240
|
+
log_error(f"File not found: {path}")
|
|
241
|
+
return None
|
|
242
|
+
data = path.read_bytes()
|
|
243
|
+
|
|
244
|
+
_mime = file.mime_type or mimetypes.guess_type(path.name)[0] or "application/pdf"
|
|
245
|
+
_encoded = base64.b64encode(data).decode("utf-8")
|
|
246
|
+
_data_url = f"data:{_mime};base64,{_encoded}"
|
|
247
|
+
return {"type": "file", "file": {"filename": path.name, "file_data": _data_url}}
|
|
248
|
+
|
|
249
|
+
# Case 3: Document is bytes content
|
|
250
|
+
if file.content is not None:
|
|
251
|
+
name = getattr(file, "filename", "file")
|
|
252
|
+
_mime = file.mime_type or mimetypes.guess_type(name)[0] or "application/pdf"
|
|
253
|
+
_encoded = base64.b64encode(file.content).decode("utf-8")
|
|
254
|
+
_data_url = f"data:{_mime};base64,{_encoded}"
|
|
255
|
+
return {"type": "file", "file": {"filename": name, "file_data": _data_url}}
|
|
256
|
+
|
|
257
|
+
return None
|
agno/utils/pickle.py
CHANGED
|
@@ -21,7 +21,7 @@ def unpickle_object_from_file(file_path: Path, verify_class: Optional[Any] = Non
|
|
|
21
21
|
import pickle
|
|
22
22
|
|
|
23
23
|
_obj = None
|
|
24
|
-
#
|
|
24
|
+
# log_debug(f"Reading {file_path}")
|
|
25
25
|
if file_path.exists() and file_path.is_file():
|
|
26
26
|
_obj = pickle.load(file_path.open("rb"))
|
|
27
27
|
|