agno 2.0.0rc2__py3-none-any.whl → 2.3.0__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/agent.py +6009 -2874
- agno/api/api.py +2 -0
- agno/api/os.py +1 -1
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +385 -6
- agno/db/dynamo/dynamo.py +388 -81
- agno/db/dynamo/schemas.py +47 -10
- agno/db/dynamo/utils.py +63 -4
- agno/db/firestore/firestore.py +435 -64
- agno/db/firestore/schemas.py +11 -0
- agno/db/firestore/utils.py +102 -4
- agno/db/gcs_json/gcs_json_db.py +384 -42
- agno/db/gcs_json/utils.py +60 -26
- agno/db/in_memory/in_memory_db.py +351 -66
- agno/db/in_memory/utils.py +60 -2
- agno/db/json/json_db.py +339 -48
- agno/db/json/utils.py +60 -26
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +510 -37
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +938 -0
- agno/db/mongo/__init__.py +15 -1
- agno/db/mongo/async_mongo.py +2036 -0
- agno/db/mongo/mongo.py +653 -76
- agno/db/mongo/schemas.py +13 -0
- agno/db/mongo/utils.py +80 -8
- agno/db/mysql/mysql.py +687 -25
- agno/db/mysql/schemas.py +61 -37
- agno/db/mysql/utils.py +60 -2
- agno/db/postgres/__init__.py +2 -1
- agno/db/postgres/async_postgres.py +2001 -0
- agno/db/postgres/postgres.py +676 -57
- agno/db/postgres/schemas.py +43 -18
- agno/db/postgres/utils.py +164 -2
- agno/db/redis/redis.py +344 -38
- agno/db/redis/schemas.py +18 -0
- agno/db/redis/utils.py +60 -2
- agno/db/schemas/__init__.py +2 -1
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/memory.py +13 -0
- agno/db/singlestore/schemas.py +26 -1
- agno/db/singlestore/singlestore.py +687 -53
- agno/db/singlestore/utils.py +60 -2
- agno/db/sqlite/__init__.py +2 -1
- agno/db/sqlite/async_sqlite.py +2371 -0
- agno/db/sqlite/schemas.py +24 -0
- agno/db/sqlite/sqlite.py +774 -85
- agno/db/sqlite/utils.py +168 -5
- 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 +1361 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +50 -22
- agno/eval/accuracy.py +50 -43
- agno/eval/performance.py +6 -3
- agno/eval/reliability.py +6 -3
- agno/eval/utils.py +33 -16
- agno/exceptions.py +68 -1
- agno/filters.py +354 -0
- agno/guardrails/__init__.py +6 -0
- agno/guardrails/base.py +19 -0
- agno/guardrails/openai.py +144 -0
- agno/guardrails/pii.py +94 -0
- agno/guardrails/prompt_injection.py +52 -0
- agno/integrations/discord/client.py +1 -0
- agno/knowledge/chunking/agentic.py +13 -10
- agno/knowledge/chunking/fixed.py +1 -1
- agno/knowledge/chunking/semantic.py +40 -8
- agno/knowledge/chunking/strategy.py +59 -15
- agno/knowledge/embedder/aws_bedrock.py +9 -4
- agno/knowledge/embedder/azure_openai.py +54 -0
- agno/knowledge/embedder/base.py +2 -0
- agno/knowledge/embedder/cohere.py +184 -5
- agno/knowledge/embedder/fastembed.py +1 -1
- agno/knowledge/embedder/google.py +79 -1
- agno/knowledge/embedder/huggingface.py +9 -4
- agno/knowledge/embedder/jina.py +63 -0
- agno/knowledge/embedder/mistral.py +78 -11
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/ollama.py +13 -0
- agno/knowledge/embedder/openai.py +37 -65
- agno/knowledge/embedder/sentence_transformer.py +8 -4
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/embedder/voyageai.py +69 -16
- agno/knowledge/knowledge.py +595 -187
- agno/knowledge/reader/base.py +9 -2
- agno/knowledge/reader/csv_reader.py +8 -10
- agno/knowledge/reader/docx_reader.py +5 -6
- agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
- agno/knowledge/reader/json_reader.py +6 -5
- agno/knowledge/reader/markdown_reader.py +13 -13
- agno/knowledge/reader/pdf_reader.py +43 -68
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +51 -6
- agno/knowledge/reader/s3_reader.py +3 -15
- agno/knowledge/reader/tavily_reader.py +194 -0
- agno/knowledge/reader/text_reader.py +13 -13
- agno/knowledge/reader/web_search_reader.py +2 -43
- agno/knowledge/reader/website_reader.py +43 -25
- agno/knowledge/reranker/__init__.py +3 -0
- agno/knowledge/types.py +9 -0
- agno/knowledge/utils.py +20 -0
- agno/media.py +339 -266
- agno/memory/manager.py +336 -82
- agno/models/aimlapi/aimlapi.py +2 -2
- agno/models/anthropic/claude.py +183 -37
- agno/models/aws/bedrock.py +52 -112
- agno/models/aws/claude.py +33 -1
- agno/models/azure/ai_foundry.py +33 -15
- agno/models/azure/openai_chat.py +25 -8
- agno/models/base.py +1011 -566
- agno/models/cerebras/cerebras.py +19 -13
- agno/models/cerebras/cerebras_openai.py +8 -5
- agno/models/cohere/chat.py +27 -1
- agno/models/cometapi/__init__.py +5 -0
- agno/models/cometapi/cometapi.py +57 -0
- agno/models/dashscope/dashscope.py +1 -0
- agno/models/deepinfra/deepinfra.py +2 -2
- agno/models/deepseek/deepseek.py +2 -2
- agno/models/fireworks/fireworks.py +2 -2
- agno/models/google/gemini.py +110 -37
- agno/models/groq/groq.py +28 -11
- agno/models/huggingface/huggingface.py +2 -1
- agno/models/internlm/internlm.py +2 -2
- agno/models/langdb/langdb.py +4 -4
- agno/models/litellm/chat.py +18 -1
- agno/models/litellm/litellm_openai.py +2 -2
- agno/models/llama_cpp/__init__.py +5 -0
- agno/models/llama_cpp/llama_cpp.py +22 -0
- agno/models/message.py +143 -4
- agno/models/meta/llama.py +27 -10
- agno/models/meta/llama_openai.py +5 -17
- agno/models/nebius/nebius.py +6 -6
- agno/models/nexus/__init__.py +3 -0
- agno/models/nexus/nexus.py +22 -0
- agno/models/nvidia/nvidia.py +2 -2
- agno/models/ollama/chat.py +60 -6
- agno/models/openai/chat.py +102 -43
- agno/models/openai/responses.py +103 -106
- agno/models/openrouter/openrouter.py +41 -3
- agno/models/perplexity/perplexity.py +4 -5
- agno/models/portkey/portkey.py +3 -3
- agno/models/requesty/__init__.py +5 -0
- agno/models/requesty/requesty.py +52 -0
- agno/models/response.py +81 -5
- agno/models/sambanova/sambanova.py +2 -2
- agno/models/siliconflow/__init__.py +5 -0
- agno/models/siliconflow/siliconflow.py +25 -0
- agno/models/together/together.py +2 -2
- agno/models/utils.py +254 -8
- agno/models/vercel/v0.py +2 -2
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +96 -0
- agno/models/vllm/vllm.py +1 -0
- agno/models/xai/xai.py +3 -2
- agno/os/app.py +543 -175
- agno/os/auth.py +24 -14
- agno/os/config.py +1 -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/agui.py +23 -7
- agno/os/interfaces/agui/router.py +27 -3
- agno/os/interfaces/agui/utils.py +242 -142
- agno/os/interfaces/base.py +6 -2
- agno/os/interfaces/slack/router.py +81 -23
- agno/os/interfaces/slack/slack.py +29 -14
- agno/os/interfaces/whatsapp/router.py +11 -4
- agno/os/interfaces/whatsapp/whatsapp.py +14 -7
- agno/os/mcp.py +111 -54
- agno/os/middleware/__init__.py +7 -0
- agno/os/middleware/jwt.py +233 -0
- agno/os/router.py +556 -139
- agno/os/routers/evals/evals.py +71 -34
- agno/os/routers/evals/schemas.py +31 -31
- agno/os/routers/evals/utils.py +6 -5
- agno/os/routers/health.py +31 -0
- agno/os/routers/home.py +52 -0
- agno/os/routers/knowledge/knowledge.py +185 -38
- agno/os/routers/knowledge/schemas.py +82 -22
- agno/os/routers/memory/memory.py +158 -53
- agno/os/routers/memory/schemas.py +20 -16
- agno/os/routers/metrics/metrics.py +20 -8
- agno/os/routers/metrics/schemas.py +16 -16
- agno/os/routers/session/session.py +499 -38
- agno/os/schema.py +308 -198
- agno/os/utils.py +401 -41
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/azure_ai_foundry.py +2 -2
- agno/reasoning/deepseek.py +2 -2
- agno/reasoning/default.py +3 -1
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/groq.py +2 -2
- agno/reasoning/ollama.py +2 -2
- agno/reasoning/openai.py +7 -2
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +266 -112
- agno/run/base.py +53 -24
- agno/run/team.py +252 -111
- agno/run/workflow.py +156 -45
- agno/session/agent.py +105 -89
- agno/session/summary.py +65 -25
- agno/session/team.py +176 -96
- agno/session/workflow.py +406 -40
- agno/team/team.py +3854 -1692
- agno/tools/brightdata.py +3 -3
- agno/tools/cartesia.py +3 -5
- agno/tools/dalle.py +9 -8
- agno/tools/decorator.py +4 -2
- agno/tools/desi_vocal.py +2 -2
- agno/tools/duckduckgo.py +15 -11
- agno/tools/e2b.py +20 -13
- agno/tools/eleven_labs.py +26 -28
- agno/tools/exa.py +21 -16
- agno/tools/fal.py +4 -4
- agno/tools/file.py +153 -23
- agno/tools/file_generation.py +350 -0
- agno/tools/firecrawl.py +4 -4
- agno/tools/function.py +257 -37
- agno/tools/giphy.py +2 -2
- agno/tools/gmail.py +238 -14
- agno/tools/google_drive.py +270 -0
- agno/tools/googlecalendar.py +36 -8
- agno/tools/googlesheets.py +20 -5
- agno/tools/jira.py +20 -0
- agno/tools/knowledge.py +3 -3
- agno/tools/lumalab.py +3 -3
- 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 +11 -17
- agno/tools/memori.py +1 -53
- agno/tools/memory.py +419 -0
- agno/tools/models/azure_openai.py +2 -2
- agno/tools/models/gemini.py +3 -3
- agno/tools/models/groq.py +3 -5
- agno/tools/models/nebius.py +7 -7
- agno/tools/models_labs.py +25 -15
- agno/tools/notion.py +204 -0
- agno/tools/openai.py +4 -9
- agno/tools/opencv.py +3 -3
- agno/tools/parallel.py +314 -0
- agno/tools/replicate.py +7 -7
- agno/tools/scrapegraph.py +58 -31
- agno/tools/searxng.py +2 -2
- agno/tools/serper.py +2 -2
- agno/tools/slack.py +18 -3
- agno/tools/spider.py +2 -2
- agno/tools/tavily.py +146 -0
- agno/tools/whatsapp.py +1 -1
- agno/tools/workflow.py +278 -0
- agno/tools/yfinance.py +12 -11
- agno/utils/agent.py +820 -0
- agno/utils/audio.py +27 -0
- agno/utils/common.py +90 -1
- agno/utils/events.py +222 -7
- agno/utils/gemini.py +181 -23
- agno/utils/hooks.py +57 -0
- agno/utils/http.py +111 -0
- agno/utils/knowledge.py +12 -5
- agno/utils/log.py +1 -0
- agno/utils/mcp.py +95 -5
- agno/utils/media.py +188 -10
- agno/utils/merge_dict.py +22 -1
- agno/utils/message.py +60 -0
- agno/utils/models/claude.py +40 -11
- agno/utils/models/cohere.py +1 -1
- agno/utils/models/watsonx.py +1 -1
- agno/utils/openai.py +1 -1
- agno/utils/print_response/agent.py +105 -21
- agno/utils/print_response/team.py +103 -38
- agno/utils/print_response/workflow.py +251 -34
- agno/utils/reasoning.py +22 -1
- agno/utils/serialize.py +32 -0
- agno/utils/streamlit.py +16 -10
- agno/utils/string.py +41 -0
- agno/utils/team.py +98 -9
- agno/utils/tools.py +1 -1
- agno/vectordb/base.py +23 -4
- agno/vectordb/cassandra/cassandra.py +65 -9
- agno/vectordb/chroma/chromadb.py +182 -38
- agno/vectordb/clickhouse/clickhousedb.py +64 -11
- agno/vectordb/couchbase/couchbase.py +105 -10
- agno/vectordb/lancedb/lance_db.py +183 -135
- agno/vectordb/langchaindb/langchaindb.py +25 -7
- agno/vectordb/lightrag/lightrag.py +17 -3
- agno/vectordb/llamaindex/__init__.py +3 -0
- agno/vectordb/llamaindex/llamaindexdb.py +46 -7
- agno/vectordb/milvus/milvus.py +126 -9
- agno/vectordb/mongodb/__init__.py +7 -1
- agno/vectordb/mongodb/mongodb.py +112 -7
- agno/vectordb/pgvector/pgvector.py +142 -21
- agno/vectordb/pineconedb/pineconedb.py +80 -8
- agno/vectordb/qdrant/qdrant.py +125 -39
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +694 -0
- agno/vectordb/singlestore/singlestore.py +111 -25
- agno/vectordb/surrealdb/surrealdb.py +31 -5
- agno/vectordb/upstashdb/upstashdb.py +76 -8
- agno/vectordb/weaviate/weaviate.py +86 -15
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +112 -18
- agno/workflow/loop.py +69 -10
- agno/workflow/parallel.py +266 -118
- agno/workflow/router.py +110 -17
- agno/workflow/step.py +645 -136
- agno/workflow/steps.py +65 -6
- agno/workflow/types.py +71 -33
- agno/workflow/workflow.py +2113 -300
- agno-2.3.0.dist-info/METADATA +618 -0
- agno-2.3.0.dist-info/RECORD +577 -0
- agno-2.3.0.dist-info/licenses/LICENSE +201 -0
- agno/knowledge/reader/url_reader.py +0 -128
- agno/tools/googlesearch.py +0 -98
- agno/tools/mcp.py +0 -610
- agno/utils/models/aws_claude.py +0 -170
- agno-2.0.0rc2.dist-info/METADATA +0 -355
- agno-2.0.0rc2.dist-info/RECORD +0 -515
- agno-2.0.0rc2.dist-info/licenses/LICENSE +0 -375
- {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
- {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
agno/models/openai/responses.py
CHANGED
|
@@ -8,21 +8,20 @@ from typing_extensions import Literal
|
|
|
8
8
|
|
|
9
9
|
from agno.exceptions import ModelProviderError
|
|
10
10
|
from agno.media import File
|
|
11
|
-
from agno.models.base import
|
|
11
|
+
from agno.models.base import Model
|
|
12
12
|
from agno.models.message import Citations, Message, UrlCitation
|
|
13
13
|
from agno.models.metrics import Metrics
|
|
14
14
|
from agno.models.response import ModelResponse
|
|
15
15
|
from agno.run.agent import RunOutput
|
|
16
|
+
from agno.utils.http import get_default_async_client, get_default_sync_client
|
|
16
17
|
from agno.utils.log import log_debug, log_error, log_warning
|
|
17
18
|
from agno.utils.models.openai_responses import images_to_message
|
|
18
19
|
from agno.utils.models.schema_utils import get_response_schema_for_provider
|
|
19
20
|
|
|
20
21
|
try:
|
|
21
22
|
from openai import APIConnectionError, APIStatusError, AsyncOpenAI, OpenAI, RateLimitError
|
|
22
|
-
from openai.types.responses
|
|
23
|
-
|
|
24
|
-
from openai.types.responses.response_usage import ResponseUsage
|
|
25
|
-
except (ImportError, ModuleNotFoundError) as e:
|
|
23
|
+
from openai.types.responses import Response, ResponseReasoningItem, ResponseStreamEvent, ResponseUsage
|
|
24
|
+
except ImportError as e:
|
|
26
25
|
raise ImportError("`openai` not installed. Please install using `pip install openai -U`") from e
|
|
27
26
|
|
|
28
27
|
|
|
@@ -47,7 +46,7 @@ class OpenAIResponses(Model):
|
|
|
47
46
|
parallel_tool_calls: Optional[bool] = None
|
|
48
47
|
reasoning: Optional[Dict[str, Any]] = None
|
|
49
48
|
verbosity: Optional[Literal["low", "medium", "high"]] = None
|
|
50
|
-
reasoning_effort: Optional[Literal["minimal", "medium", "high"]] = None
|
|
49
|
+
reasoning_effort: Optional[Literal["minimal", "low", "medium", "high"]] = None
|
|
51
50
|
reasoning_summary: Optional[Literal["auto", "concise", "detailed"]] = None
|
|
52
51
|
store: Optional[bool] = None
|
|
53
52
|
temperature: Optional[float] = None
|
|
@@ -55,6 +54,10 @@ class OpenAIResponses(Model):
|
|
|
55
54
|
truncation: Optional[Literal["auto", "disabled"]] = None
|
|
56
55
|
user: Optional[str] = None
|
|
57
56
|
service_tier: Optional[Literal["auto", "default", "flex", "priority"]] = None
|
|
57
|
+
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
|
|
58
|
+
extra_headers: Optional[Any] = None
|
|
59
|
+
extra_query: Optional[Any] = None
|
|
60
|
+
extra_body: Optional[Any] = None
|
|
58
61
|
request_params: Optional[Dict[str, Any]] = None
|
|
59
62
|
|
|
60
63
|
# Client parameters
|
|
@@ -65,7 +68,7 @@ class OpenAIResponses(Model):
|
|
|
65
68
|
max_retries: Optional[int] = None
|
|
66
69
|
default_headers: Optional[Dict[str, str]] = None
|
|
67
70
|
default_query: Optional[Dict[str, str]] = None
|
|
68
|
-
http_client: Optional[httpx.Client] = None
|
|
71
|
+
http_client: Optional[Union[httpx.Client, httpx.AsyncClient]] = None
|
|
69
72
|
client_params: Optional[Dict[str, Any]] = None
|
|
70
73
|
|
|
71
74
|
# Parameters affecting built-in tools
|
|
@@ -138,7 +141,7 @@ class OpenAIResponses(Model):
|
|
|
138
141
|
|
|
139
142
|
def get_client(self) -> OpenAI:
|
|
140
143
|
"""
|
|
141
|
-
Returns an OpenAI client.
|
|
144
|
+
Returns an OpenAI client. Caches the client to avoid recreating it on every request.
|
|
142
145
|
|
|
143
146
|
Returns:
|
|
144
147
|
OpenAI: An instance of the OpenAI client.
|
|
@@ -149,28 +152,29 @@ class OpenAIResponses(Model):
|
|
|
149
152
|
client_params: Dict[str, Any] = self._get_client_params()
|
|
150
153
|
if self.http_client is not None:
|
|
151
154
|
client_params["http_client"] = self.http_client
|
|
155
|
+
else:
|
|
156
|
+
# Use global sync client when no custom http_client is provided
|
|
157
|
+
client_params["http_client"] = get_default_sync_client()
|
|
152
158
|
|
|
153
159
|
self.client = OpenAI(**client_params)
|
|
154
160
|
return self.client
|
|
155
161
|
|
|
156
162
|
def get_async_client(self) -> AsyncOpenAI:
|
|
157
163
|
"""
|
|
158
|
-
Returns an asynchronous OpenAI client.
|
|
164
|
+
Returns an asynchronous OpenAI client. Caches the client to avoid recreating it on every request.
|
|
159
165
|
|
|
160
166
|
Returns:
|
|
161
167
|
AsyncOpenAI: An instance of the asynchronous OpenAI client.
|
|
162
168
|
"""
|
|
163
|
-
if self.async_client:
|
|
169
|
+
if self.async_client and not self.async_client.is_closed():
|
|
164
170
|
return self.async_client
|
|
165
171
|
|
|
166
172
|
client_params: Dict[str, Any] = self._get_client_params()
|
|
167
|
-
if self.http_client:
|
|
173
|
+
if self.http_client and isinstance(self.http_client, httpx.AsyncClient):
|
|
168
174
|
client_params["http_client"] = self.http_client
|
|
169
175
|
else:
|
|
170
|
-
#
|
|
171
|
-
client_params["http_client"] =
|
|
172
|
-
limits=httpx.Limits(max_connections=1000, max_keepalive_connections=100)
|
|
173
|
-
)
|
|
176
|
+
# Use global async client when no custom http_client is provided
|
|
177
|
+
client_params["http_client"] = get_default_async_client()
|
|
174
178
|
|
|
175
179
|
self.async_client = AsyncOpenAI(**client_params)
|
|
176
180
|
return self.async_client
|
|
@@ -201,6 +205,9 @@ class OpenAIResponses(Model):
|
|
|
201
205
|
"truncation": self.truncation,
|
|
202
206
|
"user": self.user,
|
|
203
207
|
"service_tier": self.service_tier,
|
|
208
|
+
"extra_headers": self.extra_headers,
|
|
209
|
+
"extra_query": self.extra_query,
|
|
210
|
+
"extra_body": self.extra_body,
|
|
204
211
|
}
|
|
205
212
|
# Populate the reasoning parameter
|
|
206
213
|
base_params = self._set_reasoning_request_param(base_params)
|
|
@@ -220,7 +227,7 @@ class OpenAIResponses(Model):
|
|
|
220
227
|
"type": "json_schema",
|
|
221
228
|
"name": response_format.__name__,
|
|
222
229
|
"schema": schema,
|
|
223
|
-
"strict":
|
|
230
|
+
"strict": self.strict_output,
|
|
224
231
|
}
|
|
225
232
|
else:
|
|
226
233
|
# JSON mode
|
|
@@ -256,23 +263,36 @@ class OpenAIResponses(Model):
|
|
|
256
263
|
|
|
257
264
|
# Handle reasoning tools for o3 and o4-mini models
|
|
258
265
|
if self._using_reasoning_model() and messages is not None:
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
if
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
previous_response_id = msg.provider_data["response_id"]
|
|
271
|
-
log_debug(f"Using previous_response_id: {previous_response_id}")
|
|
272
|
-
break
|
|
266
|
+
if self.store is False:
|
|
267
|
+
request_params["store"] = False
|
|
268
|
+
|
|
269
|
+
# Add encrypted reasoning content to include if not already present
|
|
270
|
+
include_list = request_params.get("include", []) or []
|
|
271
|
+
if "reasoning.encrypted_content" not in include_list:
|
|
272
|
+
include_list.append("reasoning.encrypted_content")
|
|
273
|
+
if request_params.get("include") is None:
|
|
274
|
+
request_params["include"] = include_list
|
|
275
|
+
elif isinstance(request_params["include"], list):
|
|
276
|
+
request_params["include"].extend(include_list)
|
|
273
277
|
|
|
274
|
-
|
|
275
|
-
request_params["
|
|
278
|
+
else:
|
|
279
|
+
request_params["store"] = True
|
|
280
|
+
|
|
281
|
+
# Check if the last assistant message has a previous_response_id to continue from
|
|
282
|
+
previous_response_id = None
|
|
283
|
+
for msg in reversed(messages):
|
|
284
|
+
if (
|
|
285
|
+
msg.role == "assistant"
|
|
286
|
+
and hasattr(msg, "provider_data")
|
|
287
|
+
and msg.provider_data
|
|
288
|
+
and "response_id" in msg.provider_data
|
|
289
|
+
):
|
|
290
|
+
previous_response_id = msg.provider_data["response_id"]
|
|
291
|
+
log_debug(f"Using previous_response_id: {previous_response_id}")
|
|
292
|
+
break
|
|
293
|
+
|
|
294
|
+
if previous_response_id:
|
|
295
|
+
request_params["previous_response_id"] = previous_response_id
|
|
276
296
|
|
|
277
297
|
# Add additional request params if provided
|
|
278
298
|
if self.request_params:
|
|
@@ -375,7 +395,7 @@ class OpenAIResponses(Model):
|
|
|
375
395
|
|
|
376
396
|
return formatted_tools
|
|
377
397
|
|
|
378
|
-
def _format_messages(self, messages: List[Message]) -> List[Dict[str, Any]]:
|
|
398
|
+
def _format_messages(self, messages: List[Message]) -> List[Union[Dict[str, Any], ResponseReasoningItem]]:
|
|
379
399
|
"""
|
|
380
400
|
Format a message into the format expected by OpenAI.
|
|
381
401
|
|
|
@@ -385,13 +405,16 @@ class OpenAIResponses(Model):
|
|
|
385
405
|
Returns:
|
|
386
406
|
Dict[str, Any]: The formatted message.
|
|
387
407
|
"""
|
|
388
|
-
formatted_messages: List[Dict[str, Any]] = []
|
|
408
|
+
formatted_messages: List[Union[Dict[str, Any], ResponseReasoningItem]] = []
|
|
409
|
+
|
|
410
|
+
messages_to_format = messages
|
|
411
|
+
previous_response_id: Optional[str] = None
|
|
389
412
|
|
|
390
|
-
if self._using_reasoning_model():
|
|
413
|
+
if self._using_reasoning_model() and self.store is not False:
|
|
391
414
|
# Detect whether we're chaining via previous_response_id. If so, we should NOT
|
|
392
415
|
# re-send prior function_call items; the Responses API already has the state and
|
|
393
416
|
# expects only the corresponding function_call_output items.
|
|
394
|
-
|
|
417
|
+
|
|
395
418
|
for msg in reversed(messages):
|
|
396
419
|
if (
|
|
397
420
|
msg.role == "assistant"
|
|
@@ -400,6 +423,11 @@ class OpenAIResponses(Model):
|
|
|
400
423
|
and "response_id" in msg.provider_data
|
|
401
424
|
):
|
|
402
425
|
previous_response_id = msg.provider_data["response_id"]
|
|
426
|
+
msg_index = messages.index(msg)
|
|
427
|
+
|
|
428
|
+
# Include messages after this assistant message
|
|
429
|
+
messages_to_format = messages[msg_index + 1 :]
|
|
430
|
+
|
|
403
431
|
break
|
|
404
432
|
|
|
405
433
|
# Build a mapping from function_call id (fc_*) → call_id (call_*) from prior assistant tool_calls
|
|
@@ -413,7 +441,7 @@ class OpenAIResponses(Model):
|
|
|
413
441
|
if isinstance(fc_id, str) and isinstance(call_id, str):
|
|
414
442
|
fc_id_to_call_id[fc_id] = call_id
|
|
415
443
|
|
|
416
|
-
for message in
|
|
444
|
+
for message in messages_to_format:
|
|
417
445
|
if message.role in ["user", "system"]:
|
|
418
446
|
message_dict: Dict[str, Any] = {
|
|
419
447
|
"role": self.role_map[message.role],
|
|
@@ -475,6 +503,12 @@ class OpenAIResponses(Model):
|
|
|
475
503
|
content = message.content if message.content is not None else ""
|
|
476
504
|
formatted_messages.append({"role": self.role_map[message.role], "content": content})
|
|
477
505
|
|
|
506
|
+
if self.store is False and hasattr(message, "provider_data") and message.provider_data is not None:
|
|
507
|
+
if message.provider_data.get("reasoning_output") is not None:
|
|
508
|
+
reasoning_output = ResponseReasoningItem.model_validate(
|
|
509
|
+
message.provider_data["reasoning_output"]
|
|
510
|
+
)
|
|
511
|
+
formatted_messages.append(reasoning_output)
|
|
478
512
|
return formatted_messages
|
|
479
513
|
|
|
480
514
|
def invoke(
|
|
@@ -774,63 +808,6 @@ class OpenAIResponses(Model):
|
|
|
774
808
|
_fc_message.tool_call_id = tool_call_ids[_fc_message_index]
|
|
775
809
|
messages.append(_fc_message)
|
|
776
810
|
|
|
777
|
-
def process_response_stream(
|
|
778
|
-
self,
|
|
779
|
-
messages: List[Message],
|
|
780
|
-
assistant_message: Message,
|
|
781
|
-
stream_data: MessageData,
|
|
782
|
-
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
783
|
-
tools: Optional[List[Dict[str, Any]]] = None,
|
|
784
|
-
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
785
|
-
run_response: Optional[RunOutput] = None,
|
|
786
|
-
) -> Iterator[ModelResponse]:
|
|
787
|
-
"""Process the synchronous response stream."""
|
|
788
|
-
for model_response_delta in self.invoke_stream(
|
|
789
|
-
messages=messages,
|
|
790
|
-
assistant_message=assistant_message,
|
|
791
|
-
tools=tools,
|
|
792
|
-
response_format=response_format,
|
|
793
|
-
tool_choice=tool_choice,
|
|
794
|
-
run_response=run_response,
|
|
795
|
-
):
|
|
796
|
-
yield from self._populate_stream_data_and_assistant_message(
|
|
797
|
-
stream_data=stream_data,
|
|
798
|
-
assistant_message=assistant_message,
|
|
799
|
-
model_response_delta=model_response_delta,
|
|
800
|
-
)
|
|
801
|
-
|
|
802
|
-
# Add final metrics to assistant message
|
|
803
|
-
self._populate_assistant_message(assistant_message=assistant_message, provider_response=model_response_delta)
|
|
804
|
-
|
|
805
|
-
async def aprocess_response_stream(
|
|
806
|
-
self,
|
|
807
|
-
messages: List[Message],
|
|
808
|
-
assistant_message: Message,
|
|
809
|
-
stream_data: MessageData,
|
|
810
|
-
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
811
|
-
tools: Optional[List[Dict[str, Any]]] = None,
|
|
812
|
-
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
813
|
-
run_response: Optional[RunOutput] = None,
|
|
814
|
-
) -> AsyncIterator[ModelResponse]:
|
|
815
|
-
"""Process the asynchronous response stream."""
|
|
816
|
-
async for model_response_delta in self.ainvoke_stream(
|
|
817
|
-
messages=messages,
|
|
818
|
-
assistant_message=assistant_message,
|
|
819
|
-
tools=tools,
|
|
820
|
-
response_format=response_format,
|
|
821
|
-
tool_choice=tool_choice,
|
|
822
|
-
run_response=run_response,
|
|
823
|
-
):
|
|
824
|
-
for model_response in self._populate_stream_data_and_assistant_message(
|
|
825
|
-
stream_data=stream_data,
|
|
826
|
-
assistant_message=assistant_message,
|
|
827
|
-
model_response_delta=model_response_delta,
|
|
828
|
-
):
|
|
829
|
-
yield model_response
|
|
830
|
-
|
|
831
|
-
# Add final metrics to assistant message
|
|
832
|
-
self._populate_assistant_message(assistant_message=assistant_message, provider_response=model_response_delta)
|
|
833
|
-
|
|
834
811
|
def _parse_provider_response(self, response: Response, **kwargs) -> ModelResponse:
|
|
835
812
|
"""
|
|
836
813
|
Parse the OpenAI response into a ModelResponse.
|
|
@@ -858,7 +835,7 @@ class OpenAIResponses(Model):
|
|
|
858
835
|
|
|
859
836
|
# Add role
|
|
860
837
|
model_response.role = "assistant"
|
|
861
|
-
reasoning_summary: str =
|
|
838
|
+
reasoning_summary: Optional[str] = None
|
|
862
839
|
|
|
863
840
|
for output in response.output:
|
|
864
841
|
# Add content
|
|
@@ -898,8 +875,14 @@ class OpenAIResponses(Model):
|
|
|
898
875
|
model_response.extra = model_response.extra or {}
|
|
899
876
|
model_response.extra.setdefault("tool_call_ids", []).append(output.call_id)
|
|
900
877
|
|
|
901
|
-
#
|
|
878
|
+
# Handle reasoning output items
|
|
902
879
|
elif output.type == "reasoning":
|
|
880
|
+
# Save encrypted reasoning content for ZDR mode
|
|
881
|
+
if self.store is False:
|
|
882
|
+
if model_response.provider_data is None:
|
|
883
|
+
model_response.provider_data = {}
|
|
884
|
+
model_response.provider_data["reasoning_output"] = output.model_dump(exclude_none=True)
|
|
885
|
+
|
|
903
886
|
if reasoning_summaries := getattr(output, "summary", None):
|
|
904
887
|
for summary in reasoning_summaries:
|
|
905
888
|
if isinstance(summary, dict):
|
|
@@ -1009,19 +992,27 @@ class OpenAIResponses(Model):
|
|
|
1009
992
|
elif stream_event.type == "response.completed":
|
|
1010
993
|
model_response = ModelResponse()
|
|
1011
994
|
|
|
1012
|
-
#
|
|
1013
|
-
if self.reasoning_summary is not None:
|
|
995
|
+
# Handle reasoning output items
|
|
996
|
+
if self.reasoning_summary is not None or self.store is False:
|
|
1014
997
|
summary_text: str = ""
|
|
1015
998
|
for out in getattr(stream_event.response, "output", []) or []:
|
|
1016
999
|
if getattr(out, "type", None) == "reasoning":
|
|
1017
|
-
|
|
1018
|
-
if
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1000
|
+
# In ZDR mode (store=False), store reasoning data for next request
|
|
1001
|
+
if self.store is False and hasattr(out, "encrypted_content"):
|
|
1002
|
+
if model_response.provider_data is None:
|
|
1003
|
+
model_response.provider_data = {}
|
|
1004
|
+
# Store the complete output item
|
|
1005
|
+
model_response.provider_data["reasoning_output"] = out.model_dump(exclude_none=True)
|
|
1006
|
+
if self.reasoning_summary is not None:
|
|
1007
|
+
summaries = getattr(out, "summary", None)
|
|
1008
|
+
if summaries:
|
|
1009
|
+
for s in summaries:
|
|
1010
|
+
text_val = s.get("text") if isinstance(s, dict) else getattr(s, "text", None)
|
|
1011
|
+
if text_val:
|
|
1012
|
+
if summary_text:
|
|
1013
|
+
summary_text += "\n\n"
|
|
1014
|
+
summary_text += text_val
|
|
1015
|
+
|
|
1025
1016
|
if summary_text:
|
|
1026
1017
|
model_response.reasoning_content = summary_text
|
|
1027
1018
|
|
|
@@ -1047,4 +1038,10 @@ class OpenAIResponses(Model):
|
|
|
1047
1038
|
metrics.output_tokens = response_usage.output_tokens or 0
|
|
1048
1039
|
metrics.total_tokens = response_usage.total_tokens or 0
|
|
1049
1040
|
|
|
1041
|
+
if input_tokens_details := response_usage.input_tokens_details:
|
|
1042
|
+
metrics.cache_read_tokens = input_tokens_details.cached_tokens
|
|
1043
|
+
|
|
1044
|
+
if output_tokens_details := response_usage.output_tokens_details:
|
|
1045
|
+
metrics.reasoning_tokens = output_tokens_details.reasoning_tokens
|
|
1046
|
+
|
|
1050
1047
|
return metrics
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
2
|
from os import getenv
|
|
3
|
-
from typing import Optional
|
|
3
|
+
from typing import Any, Dict, List, Optional, Type, Union
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
4
6
|
|
|
5
7
|
from agno.models.openai.like import OpenAILike
|
|
8
|
+
from agno.run.agent import RunOutput
|
|
6
9
|
|
|
7
10
|
|
|
8
11
|
@dataclass
|
|
@@ -17,12 +20,47 @@ class OpenRouter(OpenAILike):
|
|
|
17
20
|
api_key (Optional[str]): The API key.
|
|
18
21
|
base_url (str): The base URL. Defaults to "https://openrouter.ai/api/v1".
|
|
19
22
|
max_tokens (int): The maximum number of tokens. Defaults to 1024.
|
|
23
|
+
fallback_models (Optional[List[str]]): List of fallback model IDs to use if the primary model
|
|
24
|
+
fails due to rate limits, timeouts, or unavailability. OpenRouter will automatically try
|
|
25
|
+
these models in order. Example: ["anthropic/claude-sonnet-4", "deepseek/deepseek-r1"]
|
|
20
26
|
"""
|
|
21
27
|
|
|
22
28
|
id: str = "gpt-4o"
|
|
23
29
|
name: str = "OpenRouter"
|
|
24
30
|
provider: str = "OpenRouter"
|
|
25
31
|
|
|
26
|
-
api_key: Optional[str] = getenv("OPENROUTER_API_KEY")
|
|
32
|
+
api_key: Optional[str] = field(default_factory=lambda: getenv("OPENROUTER_API_KEY"))
|
|
27
33
|
base_url: str = "https://openrouter.ai/api/v1"
|
|
28
34
|
max_tokens: int = 1024
|
|
35
|
+
models: Optional[List[str]] = None # Dynamic model routing https://openrouter.ai/docs/features/model-routing
|
|
36
|
+
|
|
37
|
+
def get_request_params(
|
|
38
|
+
self,
|
|
39
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
40
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
41
|
+
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
42
|
+
run_response: Optional[RunOutput] = None,
|
|
43
|
+
) -> Dict[str, Any]:
|
|
44
|
+
"""
|
|
45
|
+
Returns keyword arguments for API requests, including fallback models configuration.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Dict[str, Any]: A dictionary of keyword arguments for API requests.
|
|
49
|
+
"""
|
|
50
|
+
# Get base request params from parent class
|
|
51
|
+
request_params = super().get_request_params(
|
|
52
|
+
response_format=response_format, tools=tools, tool_choice=tool_choice, run_response=run_response
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Add fallback models to extra_body if specified
|
|
56
|
+
if self.models:
|
|
57
|
+
# Get existing extra_body or create new dict
|
|
58
|
+
extra_body = request_params.get("extra_body") or {}
|
|
59
|
+
|
|
60
|
+
# Merge fallback models into extra_body
|
|
61
|
+
extra_body["models"] = self.models
|
|
62
|
+
|
|
63
|
+
# Update request params
|
|
64
|
+
request_params["extra_body"] = extra_body
|
|
65
|
+
|
|
66
|
+
return request_params
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
2
|
from os import getenv
|
|
3
|
-
from typing import Any, Dict,
|
|
3
|
+
from typing import Any, Dict, Optional, Type, Union
|
|
4
4
|
|
|
5
5
|
from pydantic import BaseModel
|
|
6
6
|
|
|
@@ -42,7 +42,7 @@ class Perplexity(OpenAILike):
|
|
|
42
42
|
name: str = "Perplexity"
|
|
43
43
|
provider: str = "Perplexity"
|
|
44
44
|
|
|
45
|
-
api_key: Optional[str] = getenv("PERPLEXITY_API_KEY")
|
|
45
|
+
api_key: Optional[str] = field(default_factory=lambda: getenv("PERPLEXITY_API_KEY"))
|
|
46
46
|
base_url: str = "https://api.perplexity.ai/"
|
|
47
47
|
max_tokens: int = 1024
|
|
48
48
|
top_k: Optional[float] = None
|
|
@@ -53,8 +53,7 @@ class Perplexity(OpenAILike):
|
|
|
53
53
|
def get_request_params(
|
|
54
54
|
self,
|
|
55
55
|
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
56
|
-
|
|
57
|
-
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
56
|
+
**kwargs: Any,
|
|
58
57
|
) -> Dict[str, Any]:
|
|
59
58
|
"""
|
|
60
59
|
Returns keyword arguments for API requests.
|
agno/models/portkey/portkey.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
2
|
from os import getenv
|
|
3
3
|
from typing import Any, Dict, Optional, cast
|
|
4
4
|
|
|
@@ -30,8 +30,8 @@ class Portkey(OpenAILike):
|
|
|
30
30
|
name: str = "Portkey"
|
|
31
31
|
provider: str = "Portkey"
|
|
32
32
|
|
|
33
|
-
portkey_api_key: Optional[str] = getenv("PORTKEY_API_KEY")
|
|
34
|
-
virtual_key: Optional[str] = getenv("PORTKEY_VIRTUAL_KEY")
|
|
33
|
+
portkey_api_key: Optional[str] = field(default_factory=lambda: getenv("PORTKEY_API_KEY"))
|
|
34
|
+
virtual_key: Optional[str] = field(default_factory=lambda: getenv("PORTKEY_VIRTUAL_KEY"))
|
|
35
35
|
config: Optional[Dict[str, Any]] = None
|
|
36
36
|
base_url: str = PORTKEY_GATEWAY_URL
|
|
37
37
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from os import getenv
|
|
3
|
+
from typing import Any, Dict, List, Optional, Type, Union
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
from agno.models.openai.like import OpenAILike
|
|
8
|
+
from agno.run.agent import RunOutput
|
|
9
|
+
from agno.run.team import TeamRunOutput
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class Requesty(OpenAILike):
|
|
14
|
+
"""
|
|
15
|
+
A class for using models hosted on Requesty.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
id (str): The model id. Defaults to "openai/gpt-4.1".
|
|
19
|
+
provider (str): The provider name. Defaults to "Requesty".
|
|
20
|
+
api_key (Optional[str]): The API key.
|
|
21
|
+
base_url (str): The base URL. Defaults to "https://router.requesty.ai/v1".
|
|
22
|
+
max_tokens (int): The maximum number of tokens. Defaults to 1024.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
id: str = "openai/gpt-4.1"
|
|
26
|
+
name: str = "Requesty"
|
|
27
|
+
provider: str = "Requesty"
|
|
28
|
+
|
|
29
|
+
api_key: Optional[str] = field(default_factory=lambda: getenv("REQUESTY_API_KEY"))
|
|
30
|
+
base_url: str = "https://router.requesty.ai/v1"
|
|
31
|
+
max_tokens: int = 1024
|
|
32
|
+
|
|
33
|
+
def get_request_params(
|
|
34
|
+
self,
|
|
35
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
36
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
37
|
+
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
38
|
+
run_response: Optional[Union[RunOutput, TeamRunOutput]] = None,
|
|
39
|
+
) -> Dict[str, Any]:
|
|
40
|
+
params = super().get_request_params(
|
|
41
|
+
response_format=response_format, tools=tools, tool_choice=tool_choice, run_response=run_response
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
if "extra_body" not in params:
|
|
45
|
+
params["extra_body"] = {}
|
|
46
|
+
params["extra_body"]["requesty"] = {}
|
|
47
|
+
if run_response and run_response.user_id:
|
|
48
|
+
params["extra_body"]["requesty"]["user_id"] = run_response.user_id
|
|
49
|
+
if run_response and run_response.session_id:
|
|
50
|
+
params["extra_body"]["requesty"]["trace_id"] = run_response.session_id
|
|
51
|
+
|
|
52
|
+
return params
|
agno/models/response.py
CHANGED
|
@@ -3,7 +3,7 @@ from enum import Enum
|
|
|
3
3
|
from time import time
|
|
4
4
|
from typing import Any, Dict, List, Optional
|
|
5
5
|
|
|
6
|
-
from agno.media import
|
|
6
|
+
from agno.media import Audio, File, Image, Video
|
|
7
7
|
from agno.models.message import Citations
|
|
8
8
|
from agno.models.metrics import Metrics
|
|
9
9
|
from agno.tools.function import UserInputField
|
|
@@ -29,11 +29,15 @@ class ToolExecution:
|
|
|
29
29
|
result: Optional[str] = None
|
|
30
30
|
metrics: Optional[Metrics] = None
|
|
31
31
|
|
|
32
|
+
# In the case where a tool call creates a run of an agent/team/workflow
|
|
33
|
+
child_run_id: Optional[str] = None
|
|
34
|
+
|
|
32
35
|
# If True, the agent will stop executing after this tool call.
|
|
33
36
|
stop_after_tool_call: bool = False
|
|
34
37
|
|
|
35
38
|
created_at: int = int(time())
|
|
36
39
|
|
|
40
|
+
# User control flow requirements
|
|
37
41
|
requires_confirmation: Optional[bool] = None
|
|
38
42
|
confirmed: Optional[bool] = None
|
|
39
43
|
confirmation_note: Optional[str] = None
|
|
@@ -66,6 +70,7 @@ class ToolExecution:
|
|
|
66
70
|
tool_args=data.get("tool_args"),
|
|
67
71
|
tool_call_error=data.get("tool_call_error"),
|
|
68
72
|
result=data.get("result"),
|
|
73
|
+
child_run_id=data.get("child_run_id"),
|
|
69
74
|
stop_after_tool_call=data.get("stop_after_tool_call", False),
|
|
70
75
|
requires_confirmation=data.get("requires_confirmation"),
|
|
71
76
|
confirmed=data.get("confirmed"),
|
|
@@ -87,12 +92,13 @@ class ModelResponse:
|
|
|
87
92
|
|
|
88
93
|
content: Optional[Any] = None
|
|
89
94
|
parsed: Optional[Any] = None
|
|
90
|
-
audio: Optional[
|
|
95
|
+
audio: Optional[Audio] = None
|
|
91
96
|
|
|
92
97
|
# Unified media fields for LLM-generated and tool-generated media artifacts
|
|
93
|
-
images: Optional[List[
|
|
94
|
-
videos: Optional[List[
|
|
95
|
-
audios: Optional[List[
|
|
98
|
+
images: Optional[List[Image]] = None
|
|
99
|
+
videos: Optional[List[Video]] = None
|
|
100
|
+
audios: Optional[List[Audio]] = None
|
|
101
|
+
files: Optional[List[File]] = None
|
|
96
102
|
|
|
97
103
|
# Model tool calls
|
|
98
104
|
tool_calls: List[Dict[str, Any]] = field(default_factory=list)
|
|
@@ -117,8 +123,78 @@ class ModelResponse:
|
|
|
117
123
|
|
|
118
124
|
updated_session_state: Optional[Dict[str, Any]] = None
|
|
119
125
|
|
|
126
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
127
|
+
"""Serialize ModelResponse to dictionary for caching."""
|
|
128
|
+
_dict = asdict(self)
|
|
129
|
+
|
|
130
|
+
# Handle special serialization for audio
|
|
131
|
+
if self.audio is not None:
|
|
132
|
+
_dict["audio"] = self.audio.to_dict()
|
|
133
|
+
|
|
134
|
+
# Handle lists of media objects
|
|
135
|
+
if self.images is not None:
|
|
136
|
+
_dict["images"] = [img.to_dict() for img in self.images]
|
|
137
|
+
if self.videos is not None:
|
|
138
|
+
_dict["videos"] = [vid.to_dict() for vid in self.videos]
|
|
139
|
+
if self.audios is not None:
|
|
140
|
+
_dict["audios"] = [aud.to_dict() for aud in self.audios]
|
|
141
|
+
if self.files is not None:
|
|
142
|
+
_dict["files"] = [f.to_dict() for f in self.files]
|
|
143
|
+
|
|
144
|
+
# Handle tool executions
|
|
145
|
+
if self.tool_executions is not None:
|
|
146
|
+
_dict["tool_executions"] = [tool_execution.to_dict() for tool_execution in self.tool_executions]
|
|
147
|
+
|
|
148
|
+
# Handle response usage which might be a Pydantic BaseModel
|
|
149
|
+
response_usage = _dict.pop("response_usage", None)
|
|
150
|
+
if response_usage is not None:
|
|
151
|
+
try:
|
|
152
|
+
from pydantic import BaseModel
|
|
153
|
+
|
|
154
|
+
if isinstance(response_usage, BaseModel):
|
|
155
|
+
_dict["response_usage"] = response_usage.model_dump()
|
|
156
|
+
else:
|
|
157
|
+
_dict["response_usage"] = response_usage
|
|
158
|
+
except ImportError:
|
|
159
|
+
_dict["response_usage"] = response_usage
|
|
160
|
+
|
|
161
|
+
return _dict
|
|
162
|
+
|
|
163
|
+
@classmethod
|
|
164
|
+
def from_dict(cls, data: Dict[str, Any]) -> "ModelResponse":
|
|
165
|
+
"""Reconstruct ModelResponse from cached dictionary."""
|
|
166
|
+
# Reconstruct media objects
|
|
167
|
+
if data.get("audio"):
|
|
168
|
+
data["audio"] = Audio(**data["audio"])
|
|
169
|
+
|
|
170
|
+
if data.get("images"):
|
|
171
|
+
data["images"] = [Image(**img) for img in data["images"]]
|
|
172
|
+
if data.get("videos"):
|
|
173
|
+
data["videos"] = [Video(**vid) for vid in data["videos"]]
|
|
174
|
+
if data.get("audios"):
|
|
175
|
+
data["audios"] = [Audio(**aud) for aud in data["audios"]]
|
|
176
|
+
if data.get("files"):
|
|
177
|
+
data["files"] = [File(**f) for f in data["files"]]
|
|
178
|
+
|
|
179
|
+
# Reconstruct tool executions
|
|
180
|
+
if data.get("tool_executions"):
|
|
181
|
+
data["tool_executions"] = [ToolExecution.from_dict(te) for te in data["tool_executions"]]
|
|
182
|
+
|
|
183
|
+
# Reconstruct citations
|
|
184
|
+
if data.get("citations") and isinstance(data["citations"], dict):
|
|
185
|
+
data["citations"] = Citations(**data["citations"])
|
|
186
|
+
|
|
187
|
+
# Reconstruct response usage (Metrics)
|
|
188
|
+
if data.get("response_usage") and isinstance(data["response_usage"], dict):
|
|
189
|
+
from agno.models.metrics import Metrics
|
|
190
|
+
|
|
191
|
+
data["response_usage"] = Metrics(**data["response_usage"])
|
|
192
|
+
|
|
193
|
+
return cls(**data)
|
|
194
|
+
|
|
120
195
|
|
|
121
196
|
class FileType(str, Enum):
|
|
122
197
|
MP4 = "mp4"
|
|
123
198
|
GIF = "gif"
|
|
124
199
|
MP3 = "mp3"
|
|
200
|
+
WAV = "wav"
|