agno 2.2.13__py3-none-any.whl → 2.4.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agno/agent/__init__.py +6 -0
- agno/agent/agent.py +5252 -3145
- agno/agent/remote.py +525 -0
- agno/api/api.py +2 -0
- agno/client/__init__.py +3 -0
- agno/client/a2a/__init__.py +10 -0
- agno/client/a2a/client.py +554 -0
- agno/client/a2a/schemas.py +112 -0
- agno/client/a2a/utils.py +369 -0
- agno/client/os.py +2669 -0
- agno/compression/__init__.py +3 -0
- agno/compression/manager.py +247 -0
- agno/culture/manager.py +2 -2
- agno/db/base.py +927 -6
- agno/db/dynamo/dynamo.py +788 -2
- agno/db/dynamo/schemas.py +128 -0
- agno/db/dynamo/utils.py +26 -3
- agno/db/firestore/firestore.py +674 -50
- agno/db/firestore/schemas.py +41 -0
- agno/db/firestore/utils.py +25 -10
- agno/db/gcs_json/gcs_json_db.py +506 -3
- agno/db/gcs_json/utils.py +14 -2
- agno/db/in_memory/in_memory_db.py +203 -4
- agno/db/in_memory/utils.py +14 -2
- agno/db/json/json_db.py +498 -2
- agno/db/json/utils.py +14 -2
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/utils.py +19 -0
- agno/db/migrations/v1_to_v2.py +54 -16
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +977 -0
- agno/db/mongo/async_mongo.py +1013 -39
- agno/db/mongo/mongo.py +684 -4
- agno/db/mongo/schemas.py +48 -0
- agno/db/mongo/utils.py +17 -0
- agno/db/mysql/__init__.py +2 -1
- agno/db/mysql/async_mysql.py +2958 -0
- agno/db/mysql/mysql.py +722 -53
- agno/db/mysql/schemas.py +77 -11
- agno/db/mysql/utils.py +151 -8
- agno/db/postgres/async_postgres.py +1254 -137
- agno/db/postgres/postgres.py +2316 -93
- agno/db/postgres/schemas.py +153 -21
- agno/db/postgres/utils.py +22 -7
- agno/db/redis/redis.py +531 -3
- agno/db/redis/schemas.py +36 -0
- agno/db/redis/utils.py +31 -15
- agno/db/schemas/evals.py +1 -0
- agno/db/schemas/memory.py +20 -9
- agno/db/singlestore/schemas.py +70 -1
- agno/db/singlestore/singlestore.py +737 -74
- agno/db/singlestore/utils.py +13 -3
- agno/db/sqlite/async_sqlite.py +1069 -89
- agno/db/sqlite/schemas.py +133 -1
- agno/db/sqlite/sqlite.py +2203 -165
- agno/db/sqlite/utils.py +21 -11
- agno/db/surrealdb/models.py +25 -0
- agno/db/surrealdb/surrealdb.py +603 -1
- agno/db/utils.py +60 -0
- agno/eval/__init__.py +26 -3
- agno/eval/accuracy.py +25 -12
- agno/eval/agent_as_judge.py +871 -0
- agno/eval/base.py +29 -0
- agno/eval/performance.py +10 -4
- agno/eval/reliability.py +22 -13
- agno/eval/utils.py +2 -1
- agno/exceptions.py +42 -0
- agno/hooks/__init__.py +3 -0
- agno/hooks/decorator.py +164 -0
- agno/integrations/discord/client.py +13 -2
- agno/knowledge/__init__.py +4 -0
- agno/knowledge/chunking/code.py +90 -0
- agno/knowledge/chunking/document.py +65 -4
- agno/knowledge/chunking/fixed.py +4 -1
- agno/knowledge/chunking/markdown.py +102 -11
- agno/knowledge/chunking/recursive.py +2 -2
- agno/knowledge/chunking/semantic.py +130 -48
- agno/knowledge/chunking/strategy.py +18 -0
- agno/knowledge/embedder/azure_openai.py +0 -1
- agno/knowledge/embedder/google.py +1 -1
- agno/knowledge/embedder/mistral.py +1 -1
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/openai.py +16 -12
- agno/knowledge/filesystem.py +412 -0
- agno/knowledge/knowledge.py +4261 -1199
- agno/knowledge/protocol.py +134 -0
- agno/knowledge/reader/arxiv_reader.py +3 -2
- agno/knowledge/reader/base.py +9 -7
- agno/knowledge/reader/csv_reader.py +91 -42
- agno/knowledge/reader/docx_reader.py +9 -10
- agno/knowledge/reader/excel_reader.py +225 -0
- agno/knowledge/reader/field_labeled_csv_reader.py +38 -48
- agno/knowledge/reader/firecrawl_reader.py +3 -2
- agno/knowledge/reader/json_reader.py +16 -22
- agno/knowledge/reader/markdown_reader.py +15 -14
- agno/knowledge/reader/pdf_reader.py +33 -28
- agno/knowledge/reader/pptx_reader.py +9 -10
- agno/knowledge/reader/reader_factory.py +135 -1
- agno/knowledge/reader/s3_reader.py +8 -16
- agno/knowledge/reader/tavily_reader.py +3 -3
- agno/knowledge/reader/text_reader.py +15 -14
- agno/knowledge/reader/utils/__init__.py +17 -0
- agno/knowledge/reader/utils/spreadsheet.py +114 -0
- agno/knowledge/reader/web_search_reader.py +8 -65
- agno/knowledge/reader/website_reader.py +16 -13
- agno/knowledge/reader/wikipedia_reader.py +36 -3
- agno/knowledge/reader/youtube_reader.py +3 -2
- agno/knowledge/remote_content/__init__.py +33 -0
- agno/knowledge/remote_content/config.py +266 -0
- agno/knowledge/remote_content/remote_content.py +105 -17
- agno/knowledge/utils.py +76 -22
- agno/learn/__init__.py +71 -0
- agno/learn/config.py +463 -0
- agno/learn/curate.py +185 -0
- agno/learn/machine.py +725 -0
- agno/learn/schemas.py +1114 -0
- agno/learn/stores/__init__.py +38 -0
- agno/learn/stores/decision_log.py +1156 -0
- agno/learn/stores/entity_memory.py +3275 -0
- agno/learn/stores/learned_knowledge.py +1583 -0
- agno/learn/stores/protocol.py +117 -0
- agno/learn/stores/session_context.py +1217 -0
- agno/learn/stores/user_memory.py +1495 -0
- agno/learn/stores/user_profile.py +1220 -0
- agno/learn/utils.py +209 -0
- agno/media.py +22 -6
- agno/memory/__init__.py +14 -1
- agno/memory/manager.py +223 -8
- agno/memory/strategies/__init__.py +15 -0
- agno/memory/strategies/base.py +66 -0
- agno/memory/strategies/summarize.py +196 -0
- agno/memory/strategies/types.py +37 -0
- agno/models/aimlapi/aimlapi.py +17 -0
- agno/models/anthropic/claude.py +434 -59
- agno/models/aws/bedrock.py +121 -20
- agno/models/aws/claude.py +131 -274
- agno/models/azure/ai_foundry.py +10 -6
- agno/models/azure/openai_chat.py +33 -10
- agno/models/base.py +1162 -561
- agno/models/cerebras/cerebras.py +120 -24
- agno/models/cerebras/cerebras_openai.py +21 -2
- agno/models/cohere/chat.py +65 -6
- agno/models/cometapi/cometapi.py +18 -1
- agno/models/dashscope/dashscope.py +2 -3
- agno/models/deepinfra/deepinfra.py +18 -1
- agno/models/deepseek/deepseek.py +69 -3
- agno/models/fireworks/fireworks.py +18 -1
- agno/models/google/gemini.py +959 -89
- agno/models/google/utils.py +22 -0
- agno/models/groq/groq.py +48 -18
- agno/models/huggingface/huggingface.py +17 -6
- agno/models/ibm/watsonx.py +16 -6
- agno/models/internlm/internlm.py +18 -1
- agno/models/langdb/langdb.py +13 -1
- agno/models/litellm/chat.py +88 -9
- agno/models/litellm/litellm_openai.py +18 -1
- agno/models/message.py +24 -5
- agno/models/meta/llama.py +40 -13
- agno/models/meta/llama_openai.py +22 -21
- agno/models/metrics.py +12 -0
- agno/models/mistral/mistral.py +8 -4
- agno/models/n1n/__init__.py +3 -0
- agno/models/n1n/n1n.py +57 -0
- agno/models/nebius/nebius.py +6 -7
- agno/models/nvidia/nvidia.py +20 -3
- agno/models/ollama/__init__.py +2 -0
- agno/models/ollama/chat.py +17 -6
- agno/models/ollama/responses.py +100 -0
- agno/models/openai/__init__.py +2 -0
- agno/models/openai/chat.py +117 -26
- agno/models/openai/open_responses.py +46 -0
- agno/models/openai/responses.py +110 -32
- agno/models/openrouter/__init__.py +2 -0
- agno/models/openrouter/openrouter.py +67 -2
- agno/models/openrouter/responses.py +146 -0
- agno/models/perplexity/perplexity.py +19 -1
- agno/models/portkey/portkey.py +7 -6
- agno/models/requesty/requesty.py +19 -2
- agno/models/response.py +20 -2
- agno/models/sambanova/sambanova.py +20 -3
- agno/models/siliconflow/siliconflow.py +19 -2
- agno/models/together/together.py +20 -3
- agno/models/vercel/v0.py +20 -3
- agno/models/vertexai/claude.py +124 -4
- agno/models/vllm/vllm.py +19 -14
- agno/models/xai/xai.py +19 -2
- agno/os/app.py +467 -137
- agno/os/auth.py +253 -5
- agno/os/config.py +22 -0
- agno/os/interfaces/a2a/a2a.py +7 -6
- agno/os/interfaces/a2a/router.py +635 -26
- agno/os/interfaces/a2a/utils.py +32 -33
- agno/os/interfaces/agui/agui.py +5 -3
- agno/os/interfaces/agui/router.py +26 -16
- agno/os/interfaces/agui/utils.py +97 -57
- agno/os/interfaces/base.py +7 -7
- agno/os/interfaces/slack/router.py +16 -7
- agno/os/interfaces/slack/slack.py +7 -7
- agno/os/interfaces/whatsapp/router.py +35 -7
- agno/os/interfaces/whatsapp/security.py +3 -1
- agno/os/interfaces/whatsapp/whatsapp.py +11 -8
- agno/os/managers.py +326 -0
- agno/os/mcp.py +652 -79
- agno/os/middleware/__init__.py +4 -0
- agno/os/middleware/jwt.py +718 -115
- agno/os/middleware/trailing_slash.py +27 -0
- agno/os/router.py +105 -1558
- agno/os/routers/agents/__init__.py +3 -0
- agno/os/routers/agents/router.py +655 -0
- agno/os/routers/agents/schema.py +288 -0
- agno/os/routers/components/__init__.py +3 -0
- agno/os/routers/components/components.py +475 -0
- agno/os/routers/database.py +155 -0
- agno/os/routers/evals/evals.py +111 -18
- agno/os/routers/evals/schemas.py +38 -5
- agno/os/routers/evals/utils.py +80 -11
- agno/os/routers/health.py +3 -3
- agno/os/routers/knowledge/knowledge.py +284 -35
- agno/os/routers/knowledge/schemas.py +14 -2
- agno/os/routers/memory/memory.py +274 -11
- agno/os/routers/memory/schemas.py +44 -3
- agno/os/routers/metrics/metrics.py +30 -15
- agno/os/routers/metrics/schemas.py +10 -6
- agno/os/routers/registry/__init__.py +3 -0
- agno/os/routers/registry/registry.py +337 -0
- agno/os/routers/session/session.py +143 -14
- agno/os/routers/teams/__init__.py +3 -0
- agno/os/routers/teams/router.py +550 -0
- agno/os/routers/teams/schema.py +280 -0
- agno/os/routers/traces/__init__.py +3 -0
- agno/os/routers/traces/schemas.py +414 -0
- agno/os/routers/traces/traces.py +549 -0
- agno/os/routers/workflows/__init__.py +3 -0
- agno/os/routers/workflows/router.py +757 -0
- agno/os/routers/workflows/schema.py +139 -0
- agno/os/schema.py +157 -584
- agno/os/scopes.py +469 -0
- agno/os/settings.py +3 -0
- agno/os/utils.py +574 -185
- agno/reasoning/anthropic.py +85 -1
- agno/reasoning/azure_ai_foundry.py +93 -1
- agno/reasoning/deepseek.py +102 -2
- agno/reasoning/default.py +6 -7
- agno/reasoning/gemini.py +87 -3
- agno/reasoning/groq.py +109 -2
- agno/reasoning/helpers.py +6 -7
- agno/reasoning/manager.py +1238 -0
- agno/reasoning/ollama.py +93 -1
- agno/reasoning/openai.py +115 -1
- agno/reasoning/vertexai.py +85 -1
- agno/registry/__init__.py +3 -0
- agno/registry/registry.py +68 -0
- agno/remote/__init__.py +3 -0
- agno/remote/base.py +581 -0
- agno/run/__init__.py +2 -4
- agno/run/agent.py +134 -19
- agno/run/base.py +49 -1
- agno/run/cancel.py +65 -52
- agno/run/cancellation_management/__init__.py +9 -0
- agno/run/cancellation_management/base.py +78 -0
- agno/run/cancellation_management/in_memory_cancellation_manager.py +100 -0
- agno/run/cancellation_management/redis_cancellation_manager.py +236 -0
- agno/run/requirement.py +181 -0
- agno/run/team.py +111 -19
- agno/run/workflow.py +2 -1
- agno/session/agent.py +57 -92
- agno/session/summary.py +1 -1
- agno/session/team.py +62 -115
- agno/session/workflow.py +353 -57
- agno/skills/__init__.py +17 -0
- agno/skills/agent_skills.py +377 -0
- agno/skills/errors.py +32 -0
- agno/skills/loaders/__init__.py +4 -0
- agno/skills/loaders/base.py +27 -0
- agno/skills/loaders/local.py +216 -0
- agno/skills/skill.py +65 -0
- agno/skills/utils.py +107 -0
- agno/skills/validator.py +277 -0
- agno/table.py +10 -0
- agno/team/__init__.py +5 -1
- agno/team/remote.py +447 -0
- agno/team/team.py +3769 -2202
- agno/tools/brandfetch.py +27 -18
- agno/tools/browserbase.py +225 -16
- agno/tools/crawl4ai.py +3 -0
- agno/tools/duckduckgo.py +25 -71
- agno/tools/exa.py +0 -21
- agno/tools/file.py +14 -13
- agno/tools/file_generation.py +12 -6
- agno/tools/firecrawl.py +15 -7
- agno/tools/function.py +94 -113
- agno/tools/google_bigquery.py +11 -2
- agno/tools/google_drive.py +4 -3
- agno/tools/knowledge.py +9 -4
- agno/tools/mcp/mcp.py +301 -18
- agno/tools/mcp/multi_mcp.py +269 -14
- agno/tools/mem0.py +11 -10
- agno/tools/memory.py +47 -46
- agno/tools/mlx_transcribe.py +10 -7
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/nano_banana.py +151 -0
- agno/tools/parallel.py +0 -7
- agno/tools/postgres.py +76 -36
- agno/tools/python.py +14 -6
- agno/tools/reasoning.py +30 -23
- agno/tools/redshift.py +406 -0
- agno/tools/shopify.py +1519 -0
- agno/tools/spotify.py +919 -0
- agno/tools/tavily.py +4 -1
- agno/tools/toolkit.py +253 -18
- agno/tools/websearch.py +93 -0
- agno/tools/website.py +1 -1
- agno/tools/wikipedia.py +1 -1
- agno/tools/workflow.py +56 -48
- agno/tools/yfinance.py +12 -11
- agno/tracing/__init__.py +12 -0
- agno/tracing/exporter.py +161 -0
- agno/tracing/schemas.py +276 -0
- agno/tracing/setup.py +112 -0
- agno/utils/agent.py +251 -10
- agno/utils/cryptography.py +22 -0
- agno/utils/dttm.py +33 -0
- agno/utils/events.py +264 -7
- agno/utils/hooks.py +111 -3
- agno/utils/http.py +161 -2
- agno/utils/mcp.py +49 -8
- agno/utils/media.py +22 -1
- agno/utils/models/ai_foundry.py +9 -2
- agno/utils/models/claude.py +20 -5
- agno/utils/models/cohere.py +9 -2
- agno/utils/models/llama.py +9 -2
- agno/utils/models/mistral.py +4 -2
- agno/utils/os.py +0 -0
- agno/utils/print_response/agent.py +99 -16
- agno/utils/print_response/team.py +223 -24
- agno/utils/print_response/workflow.py +0 -2
- agno/utils/prompts.py +8 -6
- agno/utils/remote.py +23 -0
- agno/utils/response.py +1 -13
- agno/utils/string.py +91 -2
- agno/utils/team.py +62 -12
- agno/utils/tokens.py +657 -0
- agno/vectordb/base.py +15 -2
- agno/vectordb/cassandra/cassandra.py +1 -1
- agno/vectordb/chroma/__init__.py +2 -1
- agno/vectordb/chroma/chromadb.py +468 -23
- agno/vectordb/clickhouse/clickhousedb.py +1 -1
- agno/vectordb/couchbase/couchbase.py +6 -2
- agno/vectordb/lancedb/lance_db.py +7 -38
- agno/vectordb/lightrag/lightrag.py +7 -6
- agno/vectordb/milvus/milvus.py +118 -84
- agno/vectordb/mongodb/__init__.py +2 -1
- agno/vectordb/mongodb/mongodb.py +14 -31
- agno/vectordb/pgvector/pgvector.py +120 -66
- agno/vectordb/pineconedb/pineconedb.py +2 -19
- agno/vectordb/qdrant/__init__.py +2 -1
- agno/vectordb/qdrant/qdrant.py +33 -56
- agno/vectordb/redis/__init__.py +2 -1
- agno/vectordb/redis/redisdb.py +19 -31
- agno/vectordb/singlestore/singlestore.py +17 -9
- agno/vectordb/surrealdb/surrealdb.py +2 -38
- agno/vectordb/weaviate/__init__.py +2 -1
- agno/vectordb/weaviate/weaviate.py +7 -3
- agno/workflow/__init__.py +5 -1
- agno/workflow/agent.py +2 -2
- agno/workflow/condition.py +12 -10
- agno/workflow/loop.py +28 -9
- agno/workflow/parallel.py +21 -13
- agno/workflow/remote.py +362 -0
- agno/workflow/router.py +12 -9
- agno/workflow/step.py +261 -36
- agno/workflow/steps.py +12 -8
- agno/workflow/types.py +40 -77
- agno/workflow/workflow.py +939 -213
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/METADATA +134 -181
- agno-2.4.3.dist-info/RECORD +677 -0
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/WHEEL +1 -1
- agno/tools/googlesearch.py +0 -98
- agno/tools/memori.py +0 -339
- agno-2.2.13.dist-info/RECORD +0 -575
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/licenses/LICENSE +0 -0
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from os import getenv
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
from agno.models.openai.open_responses import OpenResponses
|
|
6
|
+
from agno.utils.log import log_debug
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class OllamaResponses(OpenResponses):
|
|
11
|
+
"""
|
|
12
|
+
A class for interacting with Ollama models using the OpenAI Responses API.
|
|
13
|
+
|
|
14
|
+
This uses Ollama's OpenAI-compatible `/v1/responses` endpoint, which was added
|
|
15
|
+
in Ollama v0.13.3. It allows using Ollama models with the Responses API format.
|
|
16
|
+
|
|
17
|
+
Note: Ollama's Responses API is stateless - it does not support `previous_response_id`
|
|
18
|
+
or conversation chaining. Each request is independent.
|
|
19
|
+
|
|
20
|
+
Requirements:
|
|
21
|
+
- Ollama v0.13.3 or later
|
|
22
|
+
- For local usage: Ollama server running at http://localhost:11434
|
|
23
|
+
- For Ollama Cloud: Set OLLAMA_API_KEY environment variable
|
|
24
|
+
|
|
25
|
+
For more information, see: https://docs.ollama.com/api/openai-compatibility
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
id (str): The model id. Defaults to "gpt-oss:20b".
|
|
29
|
+
name (str): The model name. Defaults to "OllamaResponses".
|
|
30
|
+
provider (str): The provider name. Defaults to "Ollama".
|
|
31
|
+
host (Optional[str]): The Ollama server host. Defaults to "http://localhost:11434".
|
|
32
|
+
api_key (Optional[str]): The API key for Ollama Cloud. Not required for local usage.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
id: str = "gpt-oss:20b"
|
|
36
|
+
name: str = "OllamaResponses"
|
|
37
|
+
provider: str = "Ollama"
|
|
38
|
+
|
|
39
|
+
# Ollama server host - defaults to local instance
|
|
40
|
+
host: Optional[str] = None
|
|
41
|
+
|
|
42
|
+
# API key for Ollama Cloud (not required for local)
|
|
43
|
+
api_key: Optional[str] = field(default_factory=lambda: getenv("OLLAMA_API_KEY"))
|
|
44
|
+
|
|
45
|
+
# Ollama's Responses API is stateless
|
|
46
|
+
store: Optional[bool] = False
|
|
47
|
+
|
|
48
|
+
def _get_client_params(self) -> Dict[str, Any]:
|
|
49
|
+
"""
|
|
50
|
+
Get client parameters for API requests.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Dict[str, Any]: Client parameters including base_url and optional api_key.
|
|
54
|
+
"""
|
|
55
|
+
# Determine the base URL
|
|
56
|
+
if self.host:
|
|
57
|
+
base_url = self.host.rstrip("/")
|
|
58
|
+
if not base_url.endswith("/v1"):
|
|
59
|
+
base_url = f"{base_url}/v1"
|
|
60
|
+
elif self.api_key:
|
|
61
|
+
# Ollama Cloud
|
|
62
|
+
base_url = "https://ollama.com/v1"
|
|
63
|
+
log_debug(f"Using Ollama Cloud endpoint: {base_url}")
|
|
64
|
+
else:
|
|
65
|
+
# Local Ollama instance
|
|
66
|
+
base_url = "http://localhost:11434/v1"
|
|
67
|
+
|
|
68
|
+
# Build client params
|
|
69
|
+
base_params: Dict[str, Any] = {
|
|
70
|
+
"base_url": base_url,
|
|
71
|
+
"timeout": self.timeout,
|
|
72
|
+
"max_retries": self.max_retries,
|
|
73
|
+
"default_headers": self.default_headers,
|
|
74
|
+
"default_query": self.default_query,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Add API key if provided (required for Ollama Cloud, ignored for local)
|
|
78
|
+
if self.api_key:
|
|
79
|
+
base_params["api_key"] = self.api_key
|
|
80
|
+
else:
|
|
81
|
+
# OpenAI client requires an api_key, but Ollama ignores it locally
|
|
82
|
+
base_params["api_key"] = "ollama"
|
|
83
|
+
|
|
84
|
+
# Filter out None values
|
|
85
|
+
client_params = {k: v for k, v in base_params.items() if v is not None}
|
|
86
|
+
|
|
87
|
+
# Add additional client params if provided
|
|
88
|
+
if self.client_params:
|
|
89
|
+
client_params.update(self.client_params)
|
|
90
|
+
|
|
91
|
+
return client_params
|
|
92
|
+
|
|
93
|
+
def _using_reasoning_model(self) -> bool:
|
|
94
|
+
"""
|
|
95
|
+
Ollama doesn't have native reasoning models like OpenAI's o-series.
|
|
96
|
+
|
|
97
|
+
Some models may support thinking/reasoning through their architecture
|
|
98
|
+
(like DeepSeek-R1), but they don't use OpenAI's reasoning API format.
|
|
99
|
+
"""
|
|
100
|
+
return False
|
agno/models/openai/__init__.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
from agno.models.openai.chat import OpenAIChat
|
|
2
2
|
from agno.models.openai.like import OpenAILike
|
|
3
|
+
from agno.models.openai.open_responses import OpenResponses
|
|
3
4
|
from agno.models.openai.responses import OpenAIResponses
|
|
4
5
|
|
|
5
6
|
__all__ = [
|
|
6
7
|
"OpenAIChat",
|
|
7
8
|
"OpenAILike",
|
|
8
9
|
"OpenAIResponses",
|
|
10
|
+
"OpenResponses",
|
|
9
11
|
]
|
agno/models/openai/chat.py
CHANGED
|
@@ -7,7 +7,7 @@ from uuid import uuid4
|
|
|
7
7
|
import httpx
|
|
8
8
|
from pydantic import BaseModel
|
|
9
9
|
|
|
10
|
-
from agno.exceptions import ModelProviderError
|
|
10
|
+
from agno.exceptions import ModelAuthenticationError, ModelProviderError
|
|
11
11
|
from agno.media import Audio
|
|
12
12
|
from agno.models.base import Model
|
|
13
13
|
from agno.models.message import Message
|
|
@@ -15,6 +15,7 @@ from agno.models.metrics import Metrics
|
|
|
15
15
|
from agno.models.response import ModelResponse
|
|
16
16
|
from agno.run.agent import RunOutput
|
|
17
17
|
from agno.run.team import TeamRunOutput
|
|
18
|
+
from agno.utils.http import get_default_async_client, get_default_sync_client
|
|
18
19
|
from agno.utils.log import log_debug, log_error, log_warning
|
|
19
20
|
from agno.utils.openai import _format_file_for_message, audio_to_message, images_to_message
|
|
20
21
|
from agno.utils.reasoning import extract_thinking_content
|
|
@@ -42,6 +43,8 @@ class OpenAIChat(Model):
|
|
|
42
43
|
name: str = "OpenAIChat"
|
|
43
44
|
provider: str = "OpenAI"
|
|
44
45
|
supports_native_structured_outputs: bool = True
|
|
46
|
+
# If True, only collect metrics on the final streaming chunk (for providers with cumulative token counts)
|
|
47
|
+
collect_metrics_on_completion: bool = False
|
|
45
48
|
|
|
46
49
|
# Request parameters
|
|
47
50
|
store: Optional[bool] = None
|
|
@@ -83,7 +86,7 @@ class OpenAIChat(Model):
|
|
|
83
86
|
http_client: Optional[Union[httpx.Client, httpx.AsyncClient]] = None
|
|
84
87
|
client_params: Optional[Dict[str, Any]] = None
|
|
85
88
|
|
|
86
|
-
#
|
|
89
|
+
# Cached clients to avoid recreating them on every request
|
|
87
90
|
client: Optional[OpenAIClient] = None
|
|
88
91
|
async_client: Optional[AsyncOpenAIClient] = None
|
|
89
92
|
|
|
@@ -101,7 +104,10 @@ class OpenAIChat(Model):
|
|
|
101
104
|
if not self.api_key:
|
|
102
105
|
self.api_key = getenv("OPENAI_API_KEY")
|
|
103
106
|
if not self.api_key:
|
|
104
|
-
|
|
107
|
+
raise ModelAuthenticationError(
|
|
108
|
+
message="OPENAI_API_KEY not set. Please set the OPENAI_API_KEY environment variable.",
|
|
109
|
+
model_name=self.name,
|
|
110
|
+
)
|
|
105
111
|
|
|
106
112
|
# Define base client params
|
|
107
113
|
base_params = {
|
|
@@ -124,44 +130,59 @@ class OpenAIChat(Model):
|
|
|
124
130
|
|
|
125
131
|
def get_client(self) -> OpenAIClient:
|
|
126
132
|
"""
|
|
127
|
-
Returns an OpenAI client.
|
|
133
|
+
Returns an OpenAI client. Caches the client to avoid recreating it on every request.
|
|
128
134
|
|
|
129
135
|
Returns:
|
|
130
136
|
OpenAIClient: An instance of the OpenAI client.
|
|
131
137
|
"""
|
|
132
|
-
|
|
138
|
+
# Return cached client if it exists and is not closed
|
|
139
|
+
if self.client is not None and not self.client.is_closed():
|
|
133
140
|
return self.client
|
|
134
141
|
|
|
142
|
+
log_debug(f"Creating new sync OpenAI client for model {self.id}")
|
|
135
143
|
client_params: Dict[str, Any] = self._get_client_params()
|
|
136
144
|
if self.http_client:
|
|
137
145
|
if isinstance(self.http_client, httpx.Client):
|
|
138
146
|
client_params["http_client"] = self.http_client
|
|
139
147
|
else:
|
|
140
|
-
|
|
148
|
+
log_warning("http_client is not an instance of httpx.Client. Using default global httpx.Client.")
|
|
149
|
+
# Use global sync client when user http_client is invalid
|
|
150
|
+
client_params["http_client"] = get_default_sync_client()
|
|
151
|
+
else:
|
|
152
|
+
# Use global sync client when no custom http_client is provided
|
|
153
|
+
client_params["http_client"] = get_default_sync_client()
|
|
141
154
|
|
|
155
|
+
# Create and cache the client
|
|
142
156
|
self.client = OpenAIClient(**client_params)
|
|
143
157
|
return self.client
|
|
144
158
|
|
|
145
159
|
def get_async_client(self) -> AsyncOpenAIClient:
|
|
146
160
|
"""
|
|
147
|
-
Returns an asynchronous OpenAI client.
|
|
161
|
+
Returns an asynchronous OpenAI client. Caches the client to avoid recreating it on every request.
|
|
148
162
|
|
|
149
163
|
Returns:
|
|
150
164
|
AsyncOpenAIClient: An instance of the asynchronous OpenAI client.
|
|
151
165
|
"""
|
|
152
|
-
if
|
|
166
|
+
# Return cached client if it exists and is not closed
|
|
167
|
+
if self.async_client is not None and not self.async_client.is_closed():
|
|
153
168
|
return self.async_client
|
|
154
169
|
|
|
170
|
+
log_debug(f"Creating new async OpenAI client for model {self.id}")
|
|
155
171
|
client_params: Dict[str, Any] = self._get_client_params()
|
|
156
|
-
if self.http_client
|
|
157
|
-
|
|
172
|
+
if self.http_client:
|
|
173
|
+
if isinstance(self.http_client, httpx.AsyncClient):
|
|
174
|
+
client_params["http_client"] = self.http_client
|
|
175
|
+
else:
|
|
176
|
+
log_warning(
|
|
177
|
+
"http_client is not an instance of httpx.AsyncClient. Using default global httpx.AsyncClient."
|
|
178
|
+
)
|
|
179
|
+
# Use global async client when user http_client is invalid
|
|
180
|
+
client_params["http_client"] = get_default_async_client()
|
|
158
181
|
else:
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
limits=httpx.Limits(max_connections=1000, max_keepalive_connections=100)
|
|
164
|
-
)
|
|
182
|
+
# Use global async client when no custom http_client is provided
|
|
183
|
+
client_params["http_client"] = get_default_async_client()
|
|
184
|
+
|
|
185
|
+
# Create and cache the client
|
|
165
186
|
self.async_client = AsyncOpenAIClient(**client_params)
|
|
166
187
|
return self.async_client
|
|
167
188
|
|
|
@@ -229,7 +250,7 @@ class OpenAIChat(Model):
|
|
|
229
250
|
# Add tools
|
|
230
251
|
if tools is not None and len(tools) > 0:
|
|
231
252
|
# Remove unsupported fields for OpenAILike models
|
|
232
|
-
if self.provider in ["AIMLAPI", "Fireworks", "Nvidia"]:
|
|
253
|
+
if self.provider in ["AIMLAPI", "Fireworks", "Nvidia", "VLLM"]:
|
|
233
254
|
for tool in tools:
|
|
234
255
|
if tool.get("type") == "function":
|
|
235
256
|
if tool["function"].get("requires_confirmation") is not None:
|
|
@@ -286,19 +307,29 @@ class OpenAIChat(Model):
|
|
|
286
307
|
cleaned_dict = {k: v for k, v in model_dict.items() if v is not None}
|
|
287
308
|
return cleaned_dict
|
|
288
309
|
|
|
289
|
-
|
|
310
|
+
@classmethod
|
|
311
|
+
def from_dict(cls, data: Dict[str, Any]) -> "OpenAIChat":
|
|
312
|
+
"""
|
|
313
|
+
Create an OpenAIChat model from a dictionary.
|
|
314
|
+
"""
|
|
315
|
+
return cls(**data)
|
|
316
|
+
|
|
317
|
+
def _format_message(self, message: Message, compress_tool_results: bool = False) -> Dict[str, Any]:
|
|
290
318
|
"""
|
|
291
319
|
Format a message into the format expected by OpenAI.
|
|
292
320
|
|
|
293
321
|
Args:
|
|
294
322
|
message (Message): The message to format.
|
|
323
|
+
compress_tool_results: Whether to compress tool results.
|
|
295
324
|
|
|
296
325
|
Returns:
|
|
297
326
|
Dict[str, Any]: The formatted message.
|
|
298
327
|
"""
|
|
328
|
+
tool_result = message.get_content(use_compressed_content=compress_tool_results)
|
|
329
|
+
|
|
299
330
|
message_dict: Dict[str, Any] = {
|
|
300
331
|
"role": self.role_map[message.role] if self.role_map else self.default_role_map[message.role],
|
|
301
|
-
"content":
|
|
332
|
+
"content": tool_result,
|
|
302
333
|
"name": message.name,
|
|
303
334
|
"tool_call_id": message.tool_call_id,
|
|
304
335
|
"tool_calls": message.tool_calls,
|
|
@@ -358,6 +389,7 @@ class OpenAIChat(Model):
|
|
|
358
389
|
tools: Optional[List[Dict[str, Any]]] = None,
|
|
359
390
|
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
360
391
|
run_response: Optional[Union[RunOutput, TeamRunOutput]] = None,
|
|
392
|
+
compress_tool_results: bool = False,
|
|
361
393
|
) -> ModelResponse:
|
|
362
394
|
"""
|
|
363
395
|
Send a chat completion request to the OpenAI API and parse the response.
|
|
@@ -368,6 +400,7 @@ class OpenAIChat(Model):
|
|
|
368
400
|
response_format (Optional[Union[Dict, Type[BaseModel]]]): The response format to use.
|
|
369
401
|
tools (Optional[List[Dict[str, Any]]]): The tools to use.
|
|
370
402
|
tool_choice (Optional[Union[str, Dict[str, Any]]]): The tool choice to use.
|
|
403
|
+
compress_tool_results: Whether to compress tool results.
|
|
371
404
|
|
|
372
405
|
Returns:
|
|
373
406
|
ModelResponse: The chat completion response from the API.
|
|
@@ -380,7 +413,7 @@ class OpenAIChat(Model):
|
|
|
380
413
|
|
|
381
414
|
provider_response = self.get_client().chat.completions.create(
|
|
382
415
|
model=self.id,
|
|
383
|
-
messages=[self._format_message(m) for m in messages], # type: ignore
|
|
416
|
+
messages=[self._format_message(m, compress_tool_results) for m in messages], # type: ignore
|
|
384
417
|
**self.get_request_params(
|
|
385
418
|
response_format=response_format, tools=tools, tool_choice=tool_choice, run_response=run_response
|
|
386
419
|
),
|
|
@@ -426,6 +459,9 @@ class OpenAIChat(Model):
|
|
|
426
459
|
model_name=self.name,
|
|
427
460
|
model_id=self.id,
|
|
428
461
|
) from e
|
|
462
|
+
except ModelAuthenticationError as e:
|
|
463
|
+
log_error(f"Model authentication error from OpenAI API: {e}")
|
|
464
|
+
raise e
|
|
429
465
|
except Exception as e:
|
|
430
466
|
log_error(f"Error from OpenAI API: {e}")
|
|
431
467
|
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
|
|
@@ -438,6 +474,7 @@ class OpenAIChat(Model):
|
|
|
438
474
|
tools: Optional[List[Dict[str, Any]]] = None,
|
|
439
475
|
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
440
476
|
run_response: Optional[Union[RunOutput, TeamRunOutput]] = None,
|
|
477
|
+
compress_tool_results: bool = False,
|
|
441
478
|
) -> ModelResponse:
|
|
442
479
|
"""
|
|
443
480
|
Sends an asynchronous chat completion request to the OpenAI API.
|
|
@@ -448,6 +485,7 @@ class OpenAIChat(Model):
|
|
|
448
485
|
response_format (Optional[Union[Dict, Type[BaseModel]]]): The response format to use.
|
|
449
486
|
tools (Optional[List[Dict[str, Any]]]): The tools to use.
|
|
450
487
|
tool_choice (Optional[Union[str, Dict[str, Any]]]): The tool choice to use.
|
|
488
|
+
compress_tool_results: Whether to compress tool results.
|
|
451
489
|
|
|
452
490
|
Returns:
|
|
453
491
|
ModelResponse: The chat completion response from the API.
|
|
@@ -459,7 +497,7 @@ class OpenAIChat(Model):
|
|
|
459
497
|
assistant_message.metrics.start_timer()
|
|
460
498
|
response = await self.get_async_client().chat.completions.create(
|
|
461
499
|
model=self.id,
|
|
462
|
-
messages=[self._format_message(m) for m in messages], # type: ignore
|
|
500
|
+
messages=[self._format_message(m, compress_tool_results) for m in messages], # type: ignore
|
|
463
501
|
**self.get_request_params(
|
|
464
502
|
response_format=response_format, tools=tools, tool_choice=tool_choice, run_response=run_response
|
|
465
503
|
),
|
|
@@ -505,6 +543,9 @@ class OpenAIChat(Model):
|
|
|
505
543
|
model_name=self.name,
|
|
506
544
|
model_id=self.id,
|
|
507
545
|
) from e
|
|
546
|
+
except ModelAuthenticationError as e:
|
|
547
|
+
log_error(f"Model authentication error from OpenAI API: {e}")
|
|
548
|
+
raise e
|
|
508
549
|
except Exception as e:
|
|
509
550
|
log_error(f"Error from OpenAI API: {e}")
|
|
510
551
|
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
|
|
@@ -517,12 +558,14 @@ class OpenAIChat(Model):
|
|
|
517
558
|
tools: Optional[List[Dict[str, Any]]] = None,
|
|
518
559
|
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
519
560
|
run_response: Optional[Union[RunOutput, TeamRunOutput]] = None,
|
|
561
|
+
compress_tool_results: bool = False,
|
|
520
562
|
) -> Iterator[ModelResponse]:
|
|
521
563
|
"""
|
|
522
564
|
Send a streaming chat completion request to the OpenAI API.
|
|
523
565
|
|
|
524
566
|
Args:
|
|
525
567
|
messages (List[Message]): A list of messages to send to the model.
|
|
568
|
+
compress_tool_results: Whether to compress tool results.
|
|
526
569
|
|
|
527
570
|
Returns:
|
|
528
571
|
Iterator[ModelResponse]: An iterator of model responses.
|
|
@@ -536,7 +579,7 @@ class OpenAIChat(Model):
|
|
|
536
579
|
|
|
537
580
|
for chunk in self.get_client().chat.completions.create(
|
|
538
581
|
model=self.id,
|
|
539
|
-
messages=[self._format_message(m) for m in messages], # type: ignore
|
|
582
|
+
messages=[self._format_message(m, compress_tool_results) for m in messages], # type: ignore
|
|
540
583
|
stream=True,
|
|
541
584
|
stream_options={"include_usage": True},
|
|
542
585
|
**self.get_request_params(
|
|
@@ -581,6 +624,9 @@ class OpenAIChat(Model):
|
|
|
581
624
|
model_name=self.name,
|
|
582
625
|
model_id=self.id,
|
|
583
626
|
) from e
|
|
627
|
+
except ModelAuthenticationError as e:
|
|
628
|
+
log_error(f"Model authentication error from OpenAI API: {e}")
|
|
629
|
+
raise e
|
|
584
630
|
except Exception as e:
|
|
585
631
|
log_error(f"Error from OpenAI API: {e}")
|
|
586
632
|
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
|
|
@@ -593,12 +639,14 @@ class OpenAIChat(Model):
|
|
|
593
639
|
tools: Optional[List[Dict[str, Any]]] = None,
|
|
594
640
|
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
595
641
|
run_response: Optional[Union[RunOutput, TeamRunOutput]] = None,
|
|
642
|
+
compress_tool_results: bool = False,
|
|
596
643
|
) -> AsyncIterator[ModelResponse]:
|
|
597
644
|
"""
|
|
598
645
|
Sends an asynchronous streaming chat completion request to the OpenAI API.
|
|
599
646
|
|
|
600
647
|
Args:
|
|
601
648
|
messages (List[Message]): A list of messages to send to the model.
|
|
649
|
+
compress_tool_results: Whether to compress tool results.
|
|
602
650
|
|
|
603
651
|
Returns:
|
|
604
652
|
Any: An asynchronous iterator of model responses.
|
|
@@ -612,7 +660,7 @@ class OpenAIChat(Model):
|
|
|
612
660
|
|
|
613
661
|
async_stream = await self.get_async_client().chat.completions.create(
|
|
614
662
|
model=self.id,
|
|
615
|
-
messages=[self._format_message(m) for m in messages], # type: ignore
|
|
663
|
+
messages=[self._format_message(m, compress_tool_results) for m in messages], # type: ignore
|
|
616
664
|
stream=True,
|
|
617
665
|
stream_options={"include_usage": True},
|
|
618
666
|
**self.get_request_params(
|
|
@@ -659,6 +707,9 @@ class OpenAIChat(Model):
|
|
|
659
707
|
model_name=self.name,
|
|
660
708
|
model_id=self.id,
|
|
661
709
|
) from e
|
|
710
|
+
except ModelAuthenticationError as e:
|
|
711
|
+
log_error(f"Model authentication error from OpenAI API: {e}")
|
|
712
|
+
raise e
|
|
662
713
|
except Exception as e:
|
|
663
714
|
log_error(f"Error from OpenAI API: {e}")
|
|
664
715
|
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
|
|
@@ -703,6 +754,21 @@ class OpenAIChat(Model):
|
|
|
703
754
|
tool_call_entry["type"] = _tool_call_type
|
|
704
755
|
return tool_calls
|
|
705
756
|
|
|
757
|
+
def _should_collect_metrics(self, response: ChatCompletionChunk) -> bool:
|
|
758
|
+
"""
|
|
759
|
+
Determine if metrics should be collected from the response.
|
|
760
|
+
"""
|
|
761
|
+
if not response.usage:
|
|
762
|
+
return False
|
|
763
|
+
|
|
764
|
+
if not self.collect_metrics_on_completion:
|
|
765
|
+
return True
|
|
766
|
+
|
|
767
|
+
if not response.choices:
|
|
768
|
+
return False
|
|
769
|
+
|
|
770
|
+
return response.choices[0].finish_reason is not None
|
|
771
|
+
|
|
706
772
|
def _parse_provider_response(
|
|
707
773
|
self,
|
|
708
774
|
response: ChatCompletion,
|
|
@@ -726,7 +792,6 @@ class OpenAIChat(Model):
|
|
|
726
792
|
# Add role
|
|
727
793
|
if response_message.role is not None:
|
|
728
794
|
model_response.role = response_message.role
|
|
729
|
-
|
|
730
795
|
# Add content
|
|
731
796
|
if response_message.content is not None:
|
|
732
797
|
model_response.content = response_message.content
|
|
@@ -772,10 +837,22 @@ class OpenAIChat(Model):
|
|
|
772
837
|
|
|
773
838
|
if hasattr(response_message, "reasoning_content") and response_message.reasoning_content is not None: # type: ignore
|
|
774
839
|
model_response.reasoning_content = response_message.reasoning_content # type: ignore
|
|
840
|
+
elif hasattr(response_message, "reasoning") and response_message.reasoning is not None: # type: ignore
|
|
841
|
+
model_response.reasoning_content = response_message.reasoning # type: ignore
|
|
775
842
|
|
|
776
843
|
if response.usage is not None:
|
|
777
844
|
model_response.response_usage = self._get_metrics(response.usage)
|
|
778
845
|
|
|
846
|
+
if model_response.provider_data is None:
|
|
847
|
+
model_response.provider_data = {}
|
|
848
|
+
|
|
849
|
+
if response.id:
|
|
850
|
+
model_response.provider_data["id"] = response.id
|
|
851
|
+
if response.system_fingerprint:
|
|
852
|
+
model_response.provider_data["system_fingerprint"] = response.system_fingerprint
|
|
853
|
+
if response.model_extra:
|
|
854
|
+
model_response.provider_data["model_extra"] = response.model_extra
|
|
855
|
+
|
|
779
856
|
return model_response
|
|
780
857
|
|
|
781
858
|
def _parse_provider_response_delta(self, response_delta: ChatCompletionChunk) -> ModelResponse:
|
|
@@ -792,18 +869,30 @@ class OpenAIChat(Model):
|
|
|
792
869
|
|
|
793
870
|
if response_delta.choices and len(response_delta.choices) > 0:
|
|
794
871
|
choice_delta: ChoiceDelta = response_delta.choices[0].delta
|
|
795
|
-
|
|
796
872
|
if choice_delta:
|
|
797
873
|
# Add content
|
|
798
874
|
if choice_delta.content is not None:
|
|
799
875
|
model_response.content = choice_delta.content
|
|
800
876
|
|
|
877
|
+
# We only want to handle these if content is present
|
|
878
|
+
if model_response.provider_data is None:
|
|
879
|
+
model_response.provider_data = {}
|
|
880
|
+
|
|
881
|
+
if response_delta.id:
|
|
882
|
+
model_response.provider_data["id"] = response_delta.id
|
|
883
|
+
if response_delta.system_fingerprint:
|
|
884
|
+
model_response.provider_data["system_fingerprint"] = response_delta.system_fingerprint
|
|
885
|
+
if response_delta.model_extra:
|
|
886
|
+
model_response.provider_data["model_extra"] = response_delta.model_extra
|
|
887
|
+
|
|
801
888
|
# Add tool calls
|
|
802
889
|
if choice_delta.tool_calls is not None:
|
|
803
890
|
model_response.tool_calls = choice_delta.tool_calls # type: ignore
|
|
804
891
|
|
|
805
892
|
if hasattr(choice_delta, "reasoning_content") and choice_delta.reasoning_content is not None:
|
|
806
893
|
model_response.reasoning_content = choice_delta.reasoning_content
|
|
894
|
+
elif hasattr(choice_delta, "reasoning") and choice_delta.reasoning is not None:
|
|
895
|
+
model_response.reasoning_content = choice_delta.reasoning
|
|
807
896
|
|
|
808
897
|
# Add audio if present
|
|
809
898
|
if hasattr(choice_delta, "audio") and choice_delta.audio is not None:
|
|
@@ -848,7 +937,7 @@ class OpenAIChat(Model):
|
|
|
848
937
|
log_warning(f"Error processing audio: {e}")
|
|
849
938
|
|
|
850
939
|
# Add usage metrics if present
|
|
851
|
-
if response_delta.usage is not None:
|
|
940
|
+
if self._should_collect_metrics(response_delta) and response_delta.usage is not None:
|
|
852
941
|
model_response.response_usage = self._get_metrics(response_delta.usage)
|
|
853
942
|
|
|
854
943
|
return model_response
|
|
@@ -880,4 +969,6 @@ class OpenAIChat(Model):
|
|
|
880
969
|
metrics.audio_output_tokens = completion_tokens_details.audio_tokens or 0
|
|
881
970
|
metrics.reasoning_tokens = completion_tokens_details.reasoning_tokens or 0
|
|
882
971
|
|
|
972
|
+
metrics.cost = getattr(response_usage, "cost", None)
|
|
973
|
+
|
|
883
974
|
return metrics
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from agno.models.openai.responses import OpenAIResponses
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class OpenResponses(OpenAIResponses):
|
|
9
|
+
"""
|
|
10
|
+
A base class for interacting with any provider using the Open Responses API specification.
|
|
11
|
+
|
|
12
|
+
Open Responses is an open-source specification for building multi-provider, interoperable
|
|
13
|
+
LLM interfaces based on the OpenAI Responses API. This class provides a foundation for
|
|
14
|
+
providers that implement the spec (e.g., Ollama, OpenRouter).
|
|
15
|
+
|
|
16
|
+
For more information, see: https://openresponses.org
|
|
17
|
+
|
|
18
|
+
Key differences from OpenAIResponses:
|
|
19
|
+
- Configurable base_url for pointing to different API endpoints
|
|
20
|
+
- Stateless by default (no previous_response_id chaining)
|
|
21
|
+
- Flexible api_key handling for providers that don't require authentication
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
id (str): The model id. Defaults to "not-provided".
|
|
25
|
+
name (str): The model name. Defaults to "OpenResponses".
|
|
26
|
+
api_key (Optional[str]): The API key. Defaults to "not-provided".
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
id: str = "not-provided"
|
|
30
|
+
name: str = "OpenResponses"
|
|
31
|
+
provider: str = "OpenResponses"
|
|
32
|
+
api_key: Optional[str] = "not-provided"
|
|
33
|
+
|
|
34
|
+
# Disable stateful features by default for compatible providers
|
|
35
|
+
# Most OpenAI-compatible providers don't support previous_response_id chaining
|
|
36
|
+
store: Optional[bool] = False
|
|
37
|
+
|
|
38
|
+
def _using_reasoning_model(self) -> bool:
|
|
39
|
+
"""
|
|
40
|
+
Override to disable reasoning model detection for compatible providers.
|
|
41
|
+
|
|
42
|
+
Most compatible providers don't support OpenAI's reasoning models,
|
|
43
|
+
so we disable the special handling by default. Subclasses can override
|
|
44
|
+
this if they support specific reasoning models.
|
|
45
|
+
"""
|
|
46
|
+
return False
|