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
|
@@ -0,0 +1,883 @@
|
|
|
1
|
+
from collections.abc import AsyncIterator
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from os import getenv
|
|
4
|
+
from typing import Any, Dict, Iterator, List, Literal, Optional, Type, Union
|
|
5
|
+
from uuid import uuid4
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
from agno.exceptions import ModelProviderError
|
|
11
|
+
from agno.media import Audio
|
|
12
|
+
from agno.models.base import Model
|
|
13
|
+
from agno.models.message import Message
|
|
14
|
+
from agno.models.metrics import Metrics
|
|
15
|
+
from agno.models.response import ModelResponse
|
|
16
|
+
from agno.run.agent import RunOutput
|
|
17
|
+
from agno.run.team import TeamRunOutput
|
|
18
|
+
from agno.utils.log import log_debug, log_error, log_warning
|
|
19
|
+
from agno.utils.openai import _format_file_for_message, audio_to_message, images_to_message
|
|
20
|
+
from agno.utils.reasoning import extract_thinking_content
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
from openai import APIConnectionError, APIStatusError, RateLimitError
|
|
24
|
+
from openai import AsyncOpenAI as AsyncOpenAIClient
|
|
25
|
+
from openai import OpenAI as OpenAIClient
|
|
26
|
+
from openai.types import CompletionUsage
|
|
27
|
+
from openai.types.chat import ChatCompletion, ChatCompletionAudio, ChatCompletionChunk
|
|
28
|
+
from openai.types.chat.chat_completion_chunk import ChoiceDelta, ChoiceDeltaToolCall
|
|
29
|
+
except (ImportError, ModuleNotFoundError):
|
|
30
|
+
raise ImportError("`openai` not installed. Please install using `pip install openai`")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class OpenAIChat(Model):
|
|
35
|
+
"""
|
|
36
|
+
A class for interacting with OpenAI models using the Chat completions API.
|
|
37
|
+
|
|
38
|
+
For more information, see: https://platform.openai.com/docs/api-reference/chat/create
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
id: str = "gpt-4o"
|
|
42
|
+
name: str = "OpenAIChat"
|
|
43
|
+
provider: str = "OpenAI"
|
|
44
|
+
supports_native_structured_outputs: bool = True
|
|
45
|
+
|
|
46
|
+
# Request parameters
|
|
47
|
+
store: Optional[bool] = None
|
|
48
|
+
reasoning_effort: Optional[str] = None
|
|
49
|
+
verbosity: Optional[Literal["low", "medium", "high"]] = None
|
|
50
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
51
|
+
frequency_penalty: Optional[float] = None
|
|
52
|
+
logit_bias: Optional[Any] = None
|
|
53
|
+
logprobs: Optional[bool] = None
|
|
54
|
+
top_logprobs: Optional[int] = None
|
|
55
|
+
max_tokens: Optional[int] = None
|
|
56
|
+
max_completion_tokens: Optional[int] = None
|
|
57
|
+
modalities: Optional[List[str]] = None # "text" and/or "audio"
|
|
58
|
+
audio: Optional[Dict[str, Any]] = (
|
|
59
|
+
None # E.g. {"voice": "alloy", "format": "wav"}. `format` must be one of `wav`, `mp3`, `flac`, `opus`, or `pcm16`. `voice` must be one of `ash`, `ballad`, `coral`, `sage`, `verse`, `alloy`, `echo`, and `shimmer`.
|
|
60
|
+
)
|
|
61
|
+
presence_penalty: Optional[float] = None
|
|
62
|
+
seed: Optional[int] = None
|
|
63
|
+
stop: Optional[Union[str, List[str]]] = None
|
|
64
|
+
temperature: Optional[float] = None
|
|
65
|
+
user: Optional[str] = None
|
|
66
|
+
top_p: Optional[float] = None
|
|
67
|
+
service_tier: Optional[str] = None # "auto" | "default" | "flex" | "priority", defaults to "auto" when not set
|
|
68
|
+
strict_output: bool = True # When True, guarantees schema adherence for structured outputs. When False, attempts to follow schema as a guide but may occasionally deviate
|
|
69
|
+
extra_headers: Optional[Any] = None
|
|
70
|
+
extra_query: Optional[Any] = None
|
|
71
|
+
extra_body: Optional[Any] = None
|
|
72
|
+
request_params: Optional[Dict[str, Any]] = None
|
|
73
|
+
role_map: Optional[Dict[str, str]] = None
|
|
74
|
+
|
|
75
|
+
# Client parameters
|
|
76
|
+
api_key: Optional[str] = None
|
|
77
|
+
organization: Optional[str] = None
|
|
78
|
+
base_url: Optional[Union[str, httpx.URL]] = None
|
|
79
|
+
timeout: Optional[float] = None
|
|
80
|
+
max_retries: Optional[int] = None
|
|
81
|
+
default_headers: Optional[Any] = None
|
|
82
|
+
default_query: Optional[Any] = None
|
|
83
|
+
http_client: Optional[Union[httpx.Client, httpx.AsyncClient]] = None
|
|
84
|
+
client_params: Optional[Dict[str, Any]] = None
|
|
85
|
+
|
|
86
|
+
# OpenAI clients
|
|
87
|
+
client: Optional[OpenAIClient] = None
|
|
88
|
+
async_client: Optional[AsyncOpenAIClient] = None
|
|
89
|
+
|
|
90
|
+
# The role to map the message role to.
|
|
91
|
+
default_role_map = {
|
|
92
|
+
"system": "developer",
|
|
93
|
+
"user": "user",
|
|
94
|
+
"assistant": "assistant",
|
|
95
|
+
"tool": "tool",
|
|
96
|
+
"model": "assistant",
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
def _get_client_params(self) -> Dict[str, Any]:
|
|
100
|
+
# Fetch API key from env if not already set
|
|
101
|
+
if not self.api_key:
|
|
102
|
+
self.api_key = getenv("OPENAI_API_KEY")
|
|
103
|
+
if not self.api_key:
|
|
104
|
+
log_error("OPENAI_API_KEY not set. Please set the OPENAI_API_KEY environment variable.")
|
|
105
|
+
|
|
106
|
+
# Define base client params
|
|
107
|
+
base_params = {
|
|
108
|
+
"api_key": self.api_key,
|
|
109
|
+
"organization": self.organization,
|
|
110
|
+
"base_url": self.base_url,
|
|
111
|
+
"timeout": self.timeout,
|
|
112
|
+
"max_retries": self.max_retries,
|
|
113
|
+
"default_headers": self.default_headers,
|
|
114
|
+
"default_query": self.default_query,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
# Create client_params dict with non-None values
|
|
118
|
+
client_params = {k: v for k, v in base_params.items() if v is not None}
|
|
119
|
+
|
|
120
|
+
# Add additional client params if provided
|
|
121
|
+
if self.client_params:
|
|
122
|
+
client_params.update(self.client_params)
|
|
123
|
+
return client_params
|
|
124
|
+
|
|
125
|
+
def get_client(self) -> OpenAIClient:
|
|
126
|
+
"""
|
|
127
|
+
Returns an OpenAI client.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
OpenAIClient: An instance of the OpenAI client.
|
|
131
|
+
"""
|
|
132
|
+
if self.client and not self.client.is_closed():
|
|
133
|
+
return self.client
|
|
134
|
+
|
|
135
|
+
client_params: Dict[str, Any] = self._get_client_params()
|
|
136
|
+
if self.http_client:
|
|
137
|
+
if isinstance(self.http_client, httpx.Client):
|
|
138
|
+
client_params["http_client"] = self.http_client
|
|
139
|
+
else:
|
|
140
|
+
log_debug("http_client is not an instance of httpx.Client.")
|
|
141
|
+
|
|
142
|
+
self.client = OpenAIClient(**client_params)
|
|
143
|
+
return self.client
|
|
144
|
+
|
|
145
|
+
def get_async_client(self) -> AsyncOpenAIClient:
|
|
146
|
+
"""
|
|
147
|
+
Returns an asynchronous OpenAI client.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
AsyncOpenAIClient: An instance of the asynchronous OpenAI client.
|
|
151
|
+
"""
|
|
152
|
+
if self.async_client and not self.async_client.is_closed():
|
|
153
|
+
return self.async_client
|
|
154
|
+
|
|
155
|
+
client_params: Dict[str, Any] = self._get_client_params()
|
|
156
|
+
if self.http_client and isinstance(self.http_client, httpx.AsyncClient):
|
|
157
|
+
client_params["http_client"] = self.http_client
|
|
158
|
+
else:
|
|
159
|
+
if self.http_client:
|
|
160
|
+
log_debug("The current http_client is not async. A default httpx.AsyncClient will be used instead.")
|
|
161
|
+
# Create a new async HTTP client with custom limits
|
|
162
|
+
client_params["http_client"] = httpx.AsyncClient(
|
|
163
|
+
limits=httpx.Limits(max_connections=1000, max_keepalive_connections=100)
|
|
164
|
+
)
|
|
165
|
+
self.async_client = AsyncOpenAIClient(**client_params)
|
|
166
|
+
return self.async_client
|
|
167
|
+
|
|
168
|
+
def get_request_params(
|
|
169
|
+
self,
|
|
170
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
171
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
172
|
+
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
173
|
+
run_response: Optional[Union[RunOutput, TeamRunOutput]] = None,
|
|
174
|
+
) -> Dict[str, Any]:
|
|
175
|
+
"""
|
|
176
|
+
Returns keyword arguments for API requests.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Dict[str, Any]: A dictionary of keyword arguments for API requests.
|
|
180
|
+
"""
|
|
181
|
+
# Define base request parameters
|
|
182
|
+
base_params = {
|
|
183
|
+
"store": self.store,
|
|
184
|
+
"reasoning_effort": self.reasoning_effort,
|
|
185
|
+
"verbosity": self.verbosity,
|
|
186
|
+
"frequency_penalty": self.frequency_penalty,
|
|
187
|
+
"logit_bias": self.logit_bias,
|
|
188
|
+
"logprobs": self.logprobs,
|
|
189
|
+
"top_logprobs": self.top_logprobs,
|
|
190
|
+
"max_tokens": self.max_tokens,
|
|
191
|
+
"max_completion_tokens": self.max_completion_tokens,
|
|
192
|
+
"modalities": self.modalities,
|
|
193
|
+
"audio": self.audio,
|
|
194
|
+
"presence_penalty": self.presence_penalty,
|
|
195
|
+
"seed": self.seed,
|
|
196
|
+
"stop": self.stop,
|
|
197
|
+
"temperature": self.temperature,
|
|
198
|
+
"user": self.user,
|
|
199
|
+
"top_p": self.top_p,
|
|
200
|
+
"extra_headers": self.extra_headers,
|
|
201
|
+
"extra_query": self.extra_query,
|
|
202
|
+
"extra_body": self.extra_body,
|
|
203
|
+
"metadata": self.metadata,
|
|
204
|
+
"service_tier": self.service_tier,
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
# Handle response format - always use JSON schema approach
|
|
208
|
+
if response_format is not None:
|
|
209
|
+
if isinstance(response_format, type) and issubclass(response_format, BaseModel):
|
|
210
|
+
# Convert Pydantic to JSON schema for regular endpoint
|
|
211
|
+
from agno.utils.models.schema_utils import get_response_schema_for_provider
|
|
212
|
+
|
|
213
|
+
schema = get_response_schema_for_provider(response_format, "openai")
|
|
214
|
+
base_params["response_format"] = {
|
|
215
|
+
"type": "json_schema",
|
|
216
|
+
"json_schema": {
|
|
217
|
+
"name": response_format.__name__,
|
|
218
|
+
"schema": schema,
|
|
219
|
+
"strict": self.strict_output,
|
|
220
|
+
},
|
|
221
|
+
}
|
|
222
|
+
else:
|
|
223
|
+
# Handle other response format types (like {"type": "json_object"})
|
|
224
|
+
base_params["response_format"] = response_format
|
|
225
|
+
|
|
226
|
+
# Filter out None values
|
|
227
|
+
request_params = {k: v for k, v in base_params.items() if v is not None}
|
|
228
|
+
|
|
229
|
+
# Add tools
|
|
230
|
+
if tools is not None and len(tools) > 0:
|
|
231
|
+
# Remove unsupported fields for OpenAILike models
|
|
232
|
+
if self.provider in ["AIMLAPI", "Fireworks", "Nvidia"]:
|
|
233
|
+
for tool in tools:
|
|
234
|
+
if tool.get("type") == "function":
|
|
235
|
+
if tool["function"].get("requires_confirmation") is not None:
|
|
236
|
+
del tool["function"]["requires_confirmation"]
|
|
237
|
+
if tool["function"].get("external_execution") is not None:
|
|
238
|
+
del tool["function"]["external_execution"]
|
|
239
|
+
|
|
240
|
+
request_params["tools"] = tools
|
|
241
|
+
|
|
242
|
+
if tool_choice is not None:
|
|
243
|
+
request_params["tool_choice"] = tool_choice
|
|
244
|
+
|
|
245
|
+
# Add additional request params if provided
|
|
246
|
+
if self.request_params:
|
|
247
|
+
request_params.update(self.request_params)
|
|
248
|
+
|
|
249
|
+
if request_params:
|
|
250
|
+
log_debug(f"Calling {self.provider} with request parameters: {request_params}", log_level=2)
|
|
251
|
+
return request_params
|
|
252
|
+
|
|
253
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
254
|
+
"""
|
|
255
|
+
Convert the model to a dictionary.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
Dict[str, Any]: The dictionary representation of the model.
|
|
259
|
+
"""
|
|
260
|
+
model_dict = super().to_dict()
|
|
261
|
+
model_dict.update(
|
|
262
|
+
{
|
|
263
|
+
"store": self.store,
|
|
264
|
+
"reasoning_effort": self.reasoning_effort,
|
|
265
|
+
"verbosity": self.verbosity,
|
|
266
|
+
"frequency_penalty": self.frequency_penalty,
|
|
267
|
+
"logit_bias": self.logit_bias,
|
|
268
|
+
"logprobs": self.logprobs,
|
|
269
|
+
"top_logprobs": self.top_logprobs,
|
|
270
|
+
"max_tokens": self.max_tokens,
|
|
271
|
+
"max_completion_tokens": self.max_completion_tokens,
|
|
272
|
+
"modalities": self.modalities,
|
|
273
|
+
"audio": self.audio,
|
|
274
|
+
"presence_penalty": self.presence_penalty,
|
|
275
|
+
"seed": self.seed,
|
|
276
|
+
"stop": self.stop,
|
|
277
|
+
"temperature": self.temperature,
|
|
278
|
+
"top_p": self.top_p,
|
|
279
|
+
"user": self.user,
|
|
280
|
+
"extra_headers": self.extra_headers,
|
|
281
|
+
"extra_query": self.extra_query,
|
|
282
|
+
"extra_body": self.extra_body,
|
|
283
|
+
"service_tier": self.service_tier,
|
|
284
|
+
}
|
|
285
|
+
)
|
|
286
|
+
cleaned_dict = {k: v for k, v in model_dict.items() if v is not None}
|
|
287
|
+
return cleaned_dict
|
|
288
|
+
|
|
289
|
+
def _format_message(self, message: Message) -> Dict[str, Any]:
|
|
290
|
+
"""
|
|
291
|
+
Format a message into the format expected by OpenAI.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
message (Message): The message to format.
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
Dict[str, Any]: The formatted message.
|
|
298
|
+
"""
|
|
299
|
+
message_dict: Dict[str, Any] = {
|
|
300
|
+
"role": self.role_map[message.role] if self.role_map else self.default_role_map[message.role],
|
|
301
|
+
"content": message.content,
|
|
302
|
+
"name": message.name,
|
|
303
|
+
"tool_call_id": message.tool_call_id,
|
|
304
|
+
"tool_calls": message.tool_calls,
|
|
305
|
+
}
|
|
306
|
+
message_dict = {k: v for k, v in message_dict.items() if v is not None}
|
|
307
|
+
|
|
308
|
+
# Ignore non-string message content
|
|
309
|
+
# because we assume that the images/audio are already added to the message
|
|
310
|
+
if (message.images is not None and len(message.images) > 0) or (
|
|
311
|
+
message.audio is not None and len(message.audio) > 0
|
|
312
|
+
):
|
|
313
|
+
# Ignore non-string message content
|
|
314
|
+
# because we assume that the images/audio are already added to the message
|
|
315
|
+
if isinstance(message.content, str):
|
|
316
|
+
message_dict["content"] = [{"type": "text", "text": message.content}]
|
|
317
|
+
if message.images is not None:
|
|
318
|
+
message_dict["content"].extend(images_to_message(images=message.images))
|
|
319
|
+
|
|
320
|
+
if message.audio is not None:
|
|
321
|
+
message_dict["content"].extend(audio_to_message(audio=message.audio))
|
|
322
|
+
|
|
323
|
+
if message.audio_output is not None:
|
|
324
|
+
message_dict["content"] = ""
|
|
325
|
+
message_dict["audio"] = {"id": message.audio_output.id}
|
|
326
|
+
|
|
327
|
+
if message.videos is not None and len(message.videos) > 0:
|
|
328
|
+
log_warning("Video input is currently unsupported.")
|
|
329
|
+
|
|
330
|
+
# OpenAI expects the tool_calls to be None if empty, not an empty list
|
|
331
|
+
if message.tool_calls is not None and len(message.tool_calls) == 0:
|
|
332
|
+
message_dict["tool_calls"] = None
|
|
333
|
+
|
|
334
|
+
if message.files is not None:
|
|
335
|
+
# Ensure content is a list of parts
|
|
336
|
+
content = message_dict.get("content")
|
|
337
|
+
if isinstance(content, str): # wrap existing text
|
|
338
|
+
text = content
|
|
339
|
+
message_dict["content"] = [{"type": "text", "text": text}]
|
|
340
|
+
elif content is None:
|
|
341
|
+
message_dict["content"] = []
|
|
342
|
+
# Insert each file part before text parts
|
|
343
|
+
for file in message.files:
|
|
344
|
+
file_part = _format_file_for_message(file)
|
|
345
|
+
if file_part:
|
|
346
|
+
message_dict["content"].insert(0, file_part)
|
|
347
|
+
|
|
348
|
+
# Manually add the content field even if it is None
|
|
349
|
+
if message.content is None:
|
|
350
|
+
message_dict["content"] = ""
|
|
351
|
+
return message_dict
|
|
352
|
+
|
|
353
|
+
def invoke(
|
|
354
|
+
self,
|
|
355
|
+
messages: List[Message],
|
|
356
|
+
assistant_message: Message,
|
|
357
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
358
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
359
|
+
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
360
|
+
run_response: Optional[Union[RunOutput, TeamRunOutput]] = None,
|
|
361
|
+
) -> ModelResponse:
|
|
362
|
+
"""
|
|
363
|
+
Send a chat completion request to the OpenAI API and parse the response.
|
|
364
|
+
|
|
365
|
+
Args:
|
|
366
|
+
messages (List[Message]): A list of messages to send to the model.
|
|
367
|
+
assistant_message (Message): The assistant message to populate.
|
|
368
|
+
response_format (Optional[Union[Dict, Type[BaseModel]]]): The response format to use.
|
|
369
|
+
tools (Optional[List[Dict[str, Any]]]): The tools to use.
|
|
370
|
+
tool_choice (Optional[Union[str, Dict[str, Any]]]): The tool choice to use.
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
ModelResponse: The chat completion response from the API.
|
|
374
|
+
"""
|
|
375
|
+
try:
|
|
376
|
+
if run_response and run_response.metrics:
|
|
377
|
+
run_response.metrics.set_time_to_first_token()
|
|
378
|
+
|
|
379
|
+
assistant_message.metrics.start_timer()
|
|
380
|
+
|
|
381
|
+
provider_response = self.get_client().chat.completions.create(
|
|
382
|
+
model=self.id,
|
|
383
|
+
messages=[self._format_message(m) for m in messages], # type: ignore
|
|
384
|
+
**self.get_request_params(
|
|
385
|
+
response_format=response_format, tools=tools, tool_choice=tool_choice, run_response=run_response
|
|
386
|
+
),
|
|
387
|
+
)
|
|
388
|
+
assistant_message.metrics.stop_timer()
|
|
389
|
+
|
|
390
|
+
# Parse the response into an Agno ModelResponse object
|
|
391
|
+
model_response = self._parse_provider_response(provider_response, response_format=response_format)
|
|
392
|
+
|
|
393
|
+
return model_response
|
|
394
|
+
|
|
395
|
+
except RateLimitError as e:
|
|
396
|
+
log_error(f"Rate limit error from OpenAI API: {e}")
|
|
397
|
+
error_message = e.response.json().get("error", {})
|
|
398
|
+
error_message = (
|
|
399
|
+
error_message.get("message", "Unknown model error")
|
|
400
|
+
if isinstance(error_message, dict)
|
|
401
|
+
else error_message
|
|
402
|
+
)
|
|
403
|
+
raise ModelProviderError(
|
|
404
|
+
message=error_message,
|
|
405
|
+
status_code=e.response.status_code,
|
|
406
|
+
model_name=self.name,
|
|
407
|
+
model_id=self.id,
|
|
408
|
+
) from e
|
|
409
|
+
except APIConnectionError as e:
|
|
410
|
+
log_error(f"API connection error from OpenAI API: {e}")
|
|
411
|
+
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
|
|
412
|
+
except APIStatusError as e:
|
|
413
|
+
log_error(f"API status error from OpenAI API: {e}")
|
|
414
|
+
try:
|
|
415
|
+
error_message = e.response.json().get("error", {})
|
|
416
|
+
except Exception:
|
|
417
|
+
error_message = e.response.text
|
|
418
|
+
error_message = (
|
|
419
|
+
error_message.get("message", "Unknown model error")
|
|
420
|
+
if isinstance(error_message, dict)
|
|
421
|
+
else error_message
|
|
422
|
+
)
|
|
423
|
+
raise ModelProviderError(
|
|
424
|
+
message=error_message,
|
|
425
|
+
status_code=e.response.status_code,
|
|
426
|
+
model_name=self.name,
|
|
427
|
+
model_id=self.id,
|
|
428
|
+
) from e
|
|
429
|
+
except Exception as e:
|
|
430
|
+
log_error(f"Error from OpenAI API: {e}")
|
|
431
|
+
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
|
|
432
|
+
|
|
433
|
+
async def ainvoke(
|
|
434
|
+
self,
|
|
435
|
+
messages: List[Message],
|
|
436
|
+
assistant_message: Message,
|
|
437
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
438
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
439
|
+
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
440
|
+
run_response: Optional[Union[RunOutput, TeamRunOutput]] = None,
|
|
441
|
+
) -> ModelResponse:
|
|
442
|
+
"""
|
|
443
|
+
Sends an asynchronous chat completion request to the OpenAI API.
|
|
444
|
+
|
|
445
|
+
Args:
|
|
446
|
+
messages (List[Message]): A list of messages to send to the model.
|
|
447
|
+
assistant_message (Message): The assistant message to populate.
|
|
448
|
+
response_format (Optional[Union[Dict, Type[BaseModel]]]): The response format to use.
|
|
449
|
+
tools (Optional[List[Dict[str, Any]]]): The tools to use.
|
|
450
|
+
tool_choice (Optional[Union[str, Dict[str, Any]]]): The tool choice to use.
|
|
451
|
+
|
|
452
|
+
Returns:
|
|
453
|
+
ModelResponse: The chat completion response from the API.
|
|
454
|
+
"""
|
|
455
|
+
try:
|
|
456
|
+
if run_response and run_response.metrics:
|
|
457
|
+
run_response.metrics.set_time_to_first_token()
|
|
458
|
+
|
|
459
|
+
assistant_message.metrics.start_timer()
|
|
460
|
+
response = await self.get_async_client().chat.completions.create(
|
|
461
|
+
model=self.id,
|
|
462
|
+
messages=[self._format_message(m) for m in messages], # type: ignore
|
|
463
|
+
**self.get_request_params(
|
|
464
|
+
response_format=response_format, tools=tools, tool_choice=tool_choice, run_response=run_response
|
|
465
|
+
),
|
|
466
|
+
)
|
|
467
|
+
assistant_message.metrics.stop_timer()
|
|
468
|
+
|
|
469
|
+
# Parse the response into an Agno ModelResponse object
|
|
470
|
+
provider_response: ModelResponse = self._parse_provider_response(response, response_format=response_format)
|
|
471
|
+
|
|
472
|
+
return provider_response
|
|
473
|
+
|
|
474
|
+
except RateLimitError as e:
|
|
475
|
+
log_error(f"Rate limit error from OpenAI API: {e}")
|
|
476
|
+
error_message = e.response.json().get("error", {})
|
|
477
|
+
error_message = (
|
|
478
|
+
error_message.get("message", "Unknown model error")
|
|
479
|
+
if isinstance(error_message, dict)
|
|
480
|
+
else error_message
|
|
481
|
+
)
|
|
482
|
+
raise ModelProviderError(
|
|
483
|
+
message=error_message,
|
|
484
|
+
status_code=e.response.status_code,
|
|
485
|
+
model_name=self.name,
|
|
486
|
+
model_id=self.id,
|
|
487
|
+
) from e
|
|
488
|
+
except APIConnectionError as e:
|
|
489
|
+
log_error(f"API connection error from OpenAI API: {e}")
|
|
490
|
+
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
|
|
491
|
+
except APIStatusError as e:
|
|
492
|
+
log_error(f"API status error from OpenAI API: {e}")
|
|
493
|
+
try:
|
|
494
|
+
error_message = e.response.json().get("error", {})
|
|
495
|
+
except Exception:
|
|
496
|
+
error_message = e.response.text
|
|
497
|
+
error_message = (
|
|
498
|
+
error_message.get("message", "Unknown model error")
|
|
499
|
+
if isinstance(error_message, dict)
|
|
500
|
+
else error_message
|
|
501
|
+
)
|
|
502
|
+
raise ModelProviderError(
|
|
503
|
+
message=error_message,
|
|
504
|
+
status_code=e.response.status_code,
|
|
505
|
+
model_name=self.name,
|
|
506
|
+
model_id=self.id,
|
|
507
|
+
) from e
|
|
508
|
+
except Exception as e:
|
|
509
|
+
log_error(f"Error from OpenAI API: {e}")
|
|
510
|
+
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
|
|
511
|
+
|
|
512
|
+
def invoke_stream(
|
|
513
|
+
self,
|
|
514
|
+
messages: List[Message],
|
|
515
|
+
assistant_message: Message,
|
|
516
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
517
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
518
|
+
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
519
|
+
run_response: Optional[Union[RunOutput, TeamRunOutput]] = None,
|
|
520
|
+
) -> Iterator[ModelResponse]:
|
|
521
|
+
"""
|
|
522
|
+
Send a streaming chat completion request to the OpenAI API.
|
|
523
|
+
|
|
524
|
+
Args:
|
|
525
|
+
messages (List[Message]): A list of messages to send to the model.
|
|
526
|
+
|
|
527
|
+
Returns:
|
|
528
|
+
Iterator[ModelResponse]: An iterator of model responses.
|
|
529
|
+
"""
|
|
530
|
+
|
|
531
|
+
try:
|
|
532
|
+
if run_response and run_response.metrics:
|
|
533
|
+
run_response.metrics.set_time_to_first_token()
|
|
534
|
+
|
|
535
|
+
assistant_message.metrics.start_timer()
|
|
536
|
+
|
|
537
|
+
for chunk in self.get_client().chat.completions.create(
|
|
538
|
+
model=self.id,
|
|
539
|
+
messages=[self._format_message(m) for m in messages], # type: ignore
|
|
540
|
+
stream=True,
|
|
541
|
+
stream_options={"include_usage": True},
|
|
542
|
+
**self.get_request_params(
|
|
543
|
+
response_format=response_format, tools=tools, tool_choice=tool_choice, run_response=run_response
|
|
544
|
+
),
|
|
545
|
+
):
|
|
546
|
+
yield self._parse_provider_response_delta(chunk)
|
|
547
|
+
|
|
548
|
+
assistant_message.metrics.stop_timer()
|
|
549
|
+
|
|
550
|
+
except RateLimitError as e:
|
|
551
|
+
log_error(f"Rate limit error from OpenAI API: {e}")
|
|
552
|
+
error_message = e.response.json().get("error", {})
|
|
553
|
+
error_message = (
|
|
554
|
+
error_message.get("message", "Unknown model error")
|
|
555
|
+
if isinstance(error_message, dict)
|
|
556
|
+
else error_message
|
|
557
|
+
)
|
|
558
|
+
raise ModelProviderError(
|
|
559
|
+
message=error_message,
|
|
560
|
+
status_code=e.response.status_code,
|
|
561
|
+
model_name=self.name,
|
|
562
|
+
model_id=self.id,
|
|
563
|
+
) from e
|
|
564
|
+
except APIConnectionError as e:
|
|
565
|
+
log_error(f"API connection error from OpenAI API: {e}")
|
|
566
|
+
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
|
|
567
|
+
except APIStatusError as e:
|
|
568
|
+
log_error(f"API status error from OpenAI API: {e}")
|
|
569
|
+
try:
|
|
570
|
+
error_message = e.response.json().get("error", {})
|
|
571
|
+
except Exception:
|
|
572
|
+
error_message = e.response.text
|
|
573
|
+
error_message = (
|
|
574
|
+
error_message.get("message", "Unknown model error")
|
|
575
|
+
if isinstance(error_message, dict)
|
|
576
|
+
else error_message
|
|
577
|
+
)
|
|
578
|
+
raise ModelProviderError(
|
|
579
|
+
message=error_message,
|
|
580
|
+
status_code=e.response.status_code,
|
|
581
|
+
model_name=self.name,
|
|
582
|
+
model_id=self.id,
|
|
583
|
+
) from e
|
|
584
|
+
except Exception as e:
|
|
585
|
+
log_error(f"Error from OpenAI API: {e}")
|
|
586
|
+
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
|
|
587
|
+
|
|
588
|
+
async def ainvoke_stream(
|
|
589
|
+
self,
|
|
590
|
+
messages: List[Message],
|
|
591
|
+
assistant_message: Message,
|
|
592
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
593
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
594
|
+
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
595
|
+
run_response: Optional[Union[RunOutput, TeamRunOutput]] = None,
|
|
596
|
+
) -> AsyncIterator[ModelResponse]:
|
|
597
|
+
"""
|
|
598
|
+
Sends an asynchronous streaming chat completion request to the OpenAI API.
|
|
599
|
+
|
|
600
|
+
Args:
|
|
601
|
+
messages (List[Message]): A list of messages to send to the model.
|
|
602
|
+
|
|
603
|
+
Returns:
|
|
604
|
+
Any: An asynchronous iterator of model responses.
|
|
605
|
+
"""
|
|
606
|
+
|
|
607
|
+
try:
|
|
608
|
+
if run_response and run_response.metrics:
|
|
609
|
+
run_response.metrics.set_time_to_first_token()
|
|
610
|
+
|
|
611
|
+
assistant_message.metrics.start_timer()
|
|
612
|
+
|
|
613
|
+
async_stream = await self.get_async_client().chat.completions.create(
|
|
614
|
+
model=self.id,
|
|
615
|
+
messages=[self._format_message(m) for m in messages], # type: ignore
|
|
616
|
+
stream=True,
|
|
617
|
+
stream_options={"include_usage": True},
|
|
618
|
+
**self.get_request_params(
|
|
619
|
+
response_format=response_format, tools=tools, tool_choice=tool_choice, run_response=run_response
|
|
620
|
+
),
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
async for chunk in async_stream:
|
|
624
|
+
yield self._parse_provider_response_delta(chunk)
|
|
625
|
+
|
|
626
|
+
assistant_message.metrics.stop_timer()
|
|
627
|
+
|
|
628
|
+
except RateLimitError as e:
|
|
629
|
+
log_error(f"Rate limit error from OpenAI API: {e}")
|
|
630
|
+
error_message = e.response.json().get("error", {})
|
|
631
|
+
error_message = (
|
|
632
|
+
error_message.get("message", "Unknown model error")
|
|
633
|
+
if isinstance(error_message, dict)
|
|
634
|
+
else error_message
|
|
635
|
+
)
|
|
636
|
+
raise ModelProviderError(
|
|
637
|
+
message=error_message,
|
|
638
|
+
status_code=e.response.status_code,
|
|
639
|
+
model_name=self.name,
|
|
640
|
+
model_id=self.id,
|
|
641
|
+
) from e
|
|
642
|
+
except APIConnectionError as e:
|
|
643
|
+
log_error(f"API connection error from OpenAI API: {e}")
|
|
644
|
+
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
|
|
645
|
+
except APIStatusError as e:
|
|
646
|
+
log_error(f"API status error from OpenAI API: {e}")
|
|
647
|
+
try:
|
|
648
|
+
error_message = e.response.json().get("error", {})
|
|
649
|
+
except Exception:
|
|
650
|
+
error_message = e.response.text
|
|
651
|
+
error_message = (
|
|
652
|
+
error_message.get("message", "Unknown model error")
|
|
653
|
+
if isinstance(error_message, dict)
|
|
654
|
+
else error_message
|
|
655
|
+
)
|
|
656
|
+
raise ModelProviderError(
|
|
657
|
+
message=error_message,
|
|
658
|
+
status_code=e.response.status_code,
|
|
659
|
+
model_name=self.name,
|
|
660
|
+
model_id=self.id,
|
|
661
|
+
) from e
|
|
662
|
+
except Exception as e:
|
|
663
|
+
log_error(f"Error from OpenAI API: {e}")
|
|
664
|
+
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
|
|
665
|
+
|
|
666
|
+
@staticmethod
|
|
667
|
+
def parse_tool_calls(tool_calls_data: List[ChoiceDeltaToolCall]) -> List[Dict[str, Any]]:
|
|
668
|
+
"""
|
|
669
|
+
Build tool calls from streamed tool call data.
|
|
670
|
+
|
|
671
|
+
Args:
|
|
672
|
+
tool_calls_data (List[ChoiceDeltaToolCall]): The tool call data to build from.
|
|
673
|
+
|
|
674
|
+
Returns:
|
|
675
|
+
List[Dict[str, Any]]: The built tool calls.
|
|
676
|
+
"""
|
|
677
|
+
tool_calls: List[Dict[str, Any]] = []
|
|
678
|
+
for _tool_call in tool_calls_data:
|
|
679
|
+
_index = _tool_call.index or 0
|
|
680
|
+
_tool_call_id = _tool_call.id
|
|
681
|
+
_tool_call_type = _tool_call.type
|
|
682
|
+
_function_name = _tool_call.function.name if _tool_call.function else None
|
|
683
|
+
_function_arguments = _tool_call.function.arguments if _tool_call.function else None
|
|
684
|
+
|
|
685
|
+
if len(tool_calls) <= _index:
|
|
686
|
+
tool_calls.extend([{}] * (_index - len(tool_calls) + 1))
|
|
687
|
+
tool_call_entry = tool_calls[_index]
|
|
688
|
+
if not tool_call_entry:
|
|
689
|
+
tool_call_entry["id"] = _tool_call_id
|
|
690
|
+
tool_call_entry["type"] = _tool_call_type
|
|
691
|
+
tool_call_entry["function"] = {
|
|
692
|
+
"name": _function_name or "",
|
|
693
|
+
"arguments": _function_arguments or "",
|
|
694
|
+
}
|
|
695
|
+
else:
|
|
696
|
+
if _function_name:
|
|
697
|
+
tool_call_entry["function"]["name"] += _function_name
|
|
698
|
+
if _function_arguments:
|
|
699
|
+
tool_call_entry["function"]["arguments"] += _function_arguments
|
|
700
|
+
if _tool_call_id:
|
|
701
|
+
tool_call_entry["id"] = _tool_call_id
|
|
702
|
+
if _tool_call_type:
|
|
703
|
+
tool_call_entry["type"] = _tool_call_type
|
|
704
|
+
return tool_calls
|
|
705
|
+
|
|
706
|
+
def _parse_provider_response(
|
|
707
|
+
self,
|
|
708
|
+
response: ChatCompletion,
|
|
709
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
710
|
+
) -> ModelResponse:
|
|
711
|
+
"""
|
|
712
|
+
Parse the OpenAI response into a ModelResponse.
|
|
713
|
+
"""
|
|
714
|
+
model_response = ModelResponse()
|
|
715
|
+
|
|
716
|
+
if hasattr(response, "error") and response.error: # type: ignore
|
|
717
|
+
raise ModelProviderError(
|
|
718
|
+
message=response.error.get("message", "Unknown model error"), # type: ignore
|
|
719
|
+
model_name=self.name,
|
|
720
|
+
model_id=self.id,
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
# Get response message
|
|
724
|
+
response_message = response.choices[0].message
|
|
725
|
+
|
|
726
|
+
# Add role
|
|
727
|
+
if response_message.role is not None:
|
|
728
|
+
model_response.role = response_message.role
|
|
729
|
+
|
|
730
|
+
# Add content
|
|
731
|
+
if response_message.content is not None:
|
|
732
|
+
model_response.content = response_message.content
|
|
733
|
+
|
|
734
|
+
# Extract thinking content before any structured parsing
|
|
735
|
+
if model_response.content:
|
|
736
|
+
reasoning_content, output_content = extract_thinking_content(model_response.content)
|
|
737
|
+
if reasoning_content:
|
|
738
|
+
model_response.reasoning_content = reasoning_content
|
|
739
|
+
model_response.content = output_content
|
|
740
|
+
# Add tool calls
|
|
741
|
+
if response_message.tool_calls is not None and len(response_message.tool_calls) > 0:
|
|
742
|
+
try:
|
|
743
|
+
model_response.tool_calls = [t.model_dump() for t in response_message.tool_calls]
|
|
744
|
+
except Exception as e:
|
|
745
|
+
log_warning(f"Error processing tool calls: {e}")
|
|
746
|
+
|
|
747
|
+
# Add audio transcript to content if available
|
|
748
|
+
response_audio: Optional[ChatCompletionAudio] = response_message.audio
|
|
749
|
+
if response_audio and response_audio.transcript and not model_response.content:
|
|
750
|
+
model_response.content = response_audio.transcript
|
|
751
|
+
|
|
752
|
+
# Add audio if present
|
|
753
|
+
if hasattr(response_message, "audio") and response_message.audio is not None:
|
|
754
|
+
# If the audio output modality is requested, we can extract an audio response
|
|
755
|
+
try:
|
|
756
|
+
if isinstance(response_message.audio, dict):
|
|
757
|
+
model_response.audio = Audio(
|
|
758
|
+
id=response_message.audio.get("id"),
|
|
759
|
+
content=response_message.audio.get("data"),
|
|
760
|
+
expires_at=response_message.audio.get("expires_at"),
|
|
761
|
+
transcript=response_message.audio.get("transcript"),
|
|
762
|
+
)
|
|
763
|
+
else:
|
|
764
|
+
model_response.audio = Audio(
|
|
765
|
+
id=response_message.audio.id,
|
|
766
|
+
content=response_message.audio.data,
|
|
767
|
+
expires_at=response_message.audio.expires_at,
|
|
768
|
+
transcript=response_message.audio.transcript,
|
|
769
|
+
)
|
|
770
|
+
except Exception as e:
|
|
771
|
+
log_warning(f"Error processing audio: {e}")
|
|
772
|
+
|
|
773
|
+
if hasattr(response_message, "reasoning_content") and response_message.reasoning_content is not None: # type: ignore
|
|
774
|
+
model_response.reasoning_content = response_message.reasoning_content # type: ignore
|
|
775
|
+
|
|
776
|
+
if response.usage is not None:
|
|
777
|
+
model_response.response_usage = self._get_metrics(response.usage)
|
|
778
|
+
|
|
779
|
+
return model_response
|
|
780
|
+
|
|
781
|
+
def _parse_provider_response_delta(self, response_delta: ChatCompletionChunk) -> ModelResponse:
|
|
782
|
+
"""
|
|
783
|
+
Parse the OpenAI streaming response into a ModelResponse.
|
|
784
|
+
|
|
785
|
+
Args:
|
|
786
|
+
response_delta: Raw response chunk from OpenAI
|
|
787
|
+
|
|
788
|
+
Returns:
|
|
789
|
+
ModelResponse: Parsed response data
|
|
790
|
+
"""
|
|
791
|
+
model_response = ModelResponse()
|
|
792
|
+
|
|
793
|
+
if response_delta.choices and len(response_delta.choices) > 0:
|
|
794
|
+
choice_delta: ChoiceDelta = response_delta.choices[0].delta
|
|
795
|
+
|
|
796
|
+
if choice_delta:
|
|
797
|
+
# Add content
|
|
798
|
+
if choice_delta.content is not None:
|
|
799
|
+
model_response.content = choice_delta.content
|
|
800
|
+
|
|
801
|
+
# Add tool calls
|
|
802
|
+
if choice_delta.tool_calls is not None:
|
|
803
|
+
model_response.tool_calls = choice_delta.tool_calls # type: ignore
|
|
804
|
+
|
|
805
|
+
if hasattr(choice_delta, "reasoning_content") and choice_delta.reasoning_content is not None:
|
|
806
|
+
model_response.reasoning_content = choice_delta.reasoning_content
|
|
807
|
+
|
|
808
|
+
# Add audio if present
|
|
809
|
+
if hasattr(choice_delta, "audio") and choice_delta.audio is not None:
|
|
810
|
+
try:
|
|
811
|
+
audio_data = None
|
|
812
|
+
audio_id = None
|
|
813
|
+
audio_expires_at = None
|
|
814
|
+
audio_transcript = None
|
|
815
|
+
|
|
816
|
+
if isinstance(choice_delta.audio, dict):
|
|
817
|
+
audio_data = choice_delta.audio.get("data")
|
|
818
|
+
audio_id = choice_delta.audio.get("id")
|
|
819
|
+
audio_expires_at = choice_delta.audio.get("expires_at")
|
|
820
|
+
audio_transcript = choice_delta.audio.get("transcript")
|
|
821
|
+
else:
|
|
822
|
+
audio_data = choice_delta.audio.data
|
|
823
|
+
audio_id = choice_delta.audio.id
|
|
824
|
+
audio_expires_at = choice_delta.audio.expires_at
|
|
825
|
+
audio_transcript = choice_delta.audio.transcript
|
|
826
|
+
|
|
827
|
+
# Only create Audio object if there's actual content
|
|
828
|
+
if audio_data is not None:
|
|
829
|
+
model_response.audio = Audio(
|
|
830
|
+
id=audio_id,
|
|
831
|
+
content=audio_data,
|
|
832
|
+
expires_at=audio_expires_at,
|
|
833
|
+
transcript=audio_transcript,
|
|
834
|
+
sample_rate=24000,
|
|
835
|
+
mime_type="pcm16",
|
|
836
|
+
)
|
|
837
|
+
# If no content but there's transcript/metadata, create minimal Audio object
|
|
838
|
+
elif audio_transcript is not None or audio_id is not None:
|
|
839
|
+
model_response.audio = Audio(
|
|
840
|
+
id=audio_id or str(uuid4()),
|
|
841
|
+
content=b"",
|
|
842
|
+
expires_at=audio_expires_at,
|
|
843
|
+
transcript=audio_transcript,
|
|
844
|
+
sample_rate=24000,
|
|
845
|
+
mime_type="pcm16",
|
|
846
|
+
)
|
|
847
|
+
except Exception as e:
|
|
848
|
+
log_warning(f"Error processing audio: {e}")
|
|
849
|
+
|
|
850
|
+
# Add usage metrics if present
|
|
851
|
+
if response_delta.usage is not None:
|
|
852
|
+
model_response.response_usage = self._get_metrics(response_delta.usage)
|
|
853
|
+
|
|
854
|
+
return model_response
|
|
855
|
+
|
|
856
|
+
def _get_metrics(self, response_usage: CompletionUsage) -> Metrics:
|
|
857
|
+
"""
|
|
858
|
+
Parse the given OpenAI-specific usage into an Agno Metrics object.
|
|
859
|
+
|
|
860
|
+
Args:
|
|
861
|
+
response_usage: Usage data from OpenAI
|
|
862
|
+
|
|
863
|
+
Returns:
|
|
864
|
+
Metrics: Parsed metrics data
|
|
865
|
+
"""
|
|
866
|
+
|
|
867
|
+
metrics = Metrics()
|
|
868
|
+
|
|
869
|
+
metrics.input_tokens = response_usage.prompt_tokens or 0
|
|
870
|
+
metrics.output_tokens = response_usage.completion_tokens or 0
|
|
871
|
+
metrics.total_tokens = response_usage.total_tokens or 0
|
|
872
|
+
|
|
873
|
+
# Add the prompt_tokens_details field
|
|
874
|
+
if prompt_token_details := response_usage.prompt_tokens_details:
|
|
875
|
+
metrics.audio_input_tokens = prompt_token_details.audio_tokens or 0
|
|
876
|
+
metrics.cache_read_tokens = prompt_token_details.cached_tokens or 0
|
|
877
|
+
|
|
878
|
+
# Add the completion_tokens_details field
|
|
879
|
+
if completion_tokens_details := response_usage.completion_tokens_details:
|
|
880
|
+
metrics.audio_output_tokens = completion_tokens_details.audio_tokens or 0
|
|
881
|
+
metrics.reasoning_tokens = completion_tokens_details.reasoning_tokens or 0
|
|
882
|
+
|
|
883
|
+
return metrics
|