agno 2.0.1__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 +6015 -2823
- 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 +594 -186
- 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 +2 -8
- agno/knowledge/types.py +9 -0
- agno/knowledge/utils.py +20 -0
- agno/media.py +72 -0
- 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 +999 -519
- 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 +103 -31
- 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 +139 -0
- 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 +59 -5
- agno/models/openai/chat.py +69 -29
- 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 +77 -1
- 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 -178
- 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 +248 -94
- agno/run/base.py +44 -5
- agno/run/team.py +238 -97
- agno/run/workflow.py +144 -33
- 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 -1610
- agno/tools/dalle.py +2 -4
- agno/tools/decorator.py +4 -2
- agno/tools/duckduckgo.py +15 -11
- agno/tools/e2b.py +14 -7
- agno/tools/eleven_labs.py +23 -25
- agno/tools/exa.py +21 -16
- agno/tools/file.py +153 -23
- agno/tools/file_generation.py +350 -0
- agno/tools/firecrawl.py +4 -4
- agno/tools/function.py +250 -30
- 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/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/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/notion.py +204 -0
- agno/tools/parallel.py +314 -0
- 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 +217 -2
- agno/utils/gemini.py +180 -22
- 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 +92 -2
- 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/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 +124 -133
- 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 +638 -129
- agno/workflow/steps.py +65 -6
- agno/workflow/types.py +61 -23
- agno/workflow/workflow.py +2085 -272
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/METADATA +182 -58
- agno-2.3.0.dist-info/RECORD +577 -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.1.dist-info/RECORD +0 -515
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/licenses/LICENSE +0 -0
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
agno/models/cerebras/cerebras.py
CHANGED
|
@@ -12,6 +12,7 @@ from agno.models.message import Message
|
|
|
12
12
|
from agno.models.metrics import Metrics
|
|
13
13
|
from agno.models.response import ModelResponse
|
|
14
14
|
from agno.run.agent import RunOutput
|
|
15
|
+
from agno.utils.http import get_default_async_client, get_default_sync_client
|
|
15
16
|
from agno.utils.log import log_debug, log_error, log_warning
|
|
16
17
|
|
|
17
18
|
try:
|
|
@@ -45,12 +46,13 @@ class Cerebras(Model):
|
|
|
45
46
|
supports_json_schema_outputs: bool = True
|
|
46
47
|
|
|
47
48
|
# Request parameters
|
|
48
|
-
parallel_tool_calls: bool =
|
|
49
|
+
parallel_tool_calls: Optional[bool] = None
|
|
49
50
|
max_completion_tokens: Optional[int] = None
|
|
50
51
|
repetition_penalty: Optional[float] = None
|
|
51
52
|
temperature: Optional[float] = None
|
|
52
53
|
top_p: Optional[float] = None
|
|
53
54
|
top_k: Optional[int] = None
|
|
55
|
+
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
|
|
54
56
|
extra_headers: Optional[Any] = None
|
|
55
57
|
extra_query: Optional[Any] = None
|
|
56
58
|
extra_body: Optional[Any] = None
|
|
@@ -63,7 +65,7 @@ class Cerebras(Model):
|
|
|
63
65
|
max_retries: Optional[int] = None
|
|
64
66
|
default_headers: Optional[Any] = None
|
|
65
67
|
default_query: Optional[Any] = None
|
|
66
|
-
http_client: Optional[httpx.Client] = None
|
|
68
|
+
http_client: Optional[Union[httpx.Client, httpx.AsyncClient]] = None
|
|
67
69
|
client_params: Optional[Dict[str, Any]] = None
|
|
68
70
|
|
|
69
71
|
# Cerebras clients
|
|
@@ -102,12 +104,15 @@ class Cerebras(Model):
|
|
|
102
104
|
Returns:
|
|
103
105
|
CerebrasClient: An instance of the Cerebras client.
|
|
104
106
|
"""
|
|
105
|
-
if self.client:
|
|
107
|
+
if self.client and not self.client.is_closed():
|
|
106
108
|
return self.client
|
|
107
109
|
|
|
108
110
|
client_params: Dict[str, Any] = self._get_client_params()
|
|
109
111
|
if self.http_client is not None:
|
|
110
112
|
client_params["http_client"] = self.http_client
|
|
113
|
+
else:
|
|
114
|
+
# Use global sync client when no custom http_client is provided
|
|
115
|
+
client_params["http_client"] = get_default_sync_client()
|
|
111
116
|
self.client = CerebrasClient(**client_params)
|
|
112
117
|
return self.client
|
|
113
118
|
|
|
@@ -118,17 +123,15 @@ class Cerebras(Model):
|
|
|
118
123
|
Returns:
|
|
119
124
|
AsyncCerebras: An instance of the asynchronous Cerebras client.
|
|
120
125
|
"""
|
|
121
|
-
if self.async_client:
|
|
126
|
+
if self.async_client and not self.async_client.is_closed():
|
|
122
127
|
return self.async_client
|
|
123
128
|
|
|
124
129
|
client_params: Dict[str, Any] = self._get_client_params()
|
|
125
|
-
if self.http_client:
|
|
130
|
+
if self.http_client and isinstance(self.http_client, httpx.AsyncClient):
|
|
126
131
|
client_params["http_client"] = self.http_client
|
|
127
132
|
else:
|
|
128
|
-
#
|
|
129
|
-
client_params["http_client"] =
|
|
130
|
-
limits=httpx.Limits(max_connections=1000, max_keepalive_connections=100)
|
|
131
|
-
)
|
|
133
|
+
# Use global async client when no custom http_client is provided
|
|
134
|
+
client_params["http_client"] = get_default_async_client()
|
|
132
135
|
self.async_client = AsyncCerebrasClient(**client_params)
|
|
133
136
|
return self.async_client
|
|
134
137
|
|
|
@@ -136,6 +139,7 @@ class Cerebras(Model):
|
|
|
136
139
|
self,
|
|
137
140
|
tools: Optional[List[Dict[str, Any]]] = None,
|
|
138
141
|
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
142
|
+
**kwargs: Any,
|
|
139
143
|
) -> Dict[str, Any]:
|
|
140
144
|
"""
|
|
141
145
|
Returns keyword arguments for API requests.
|
|
@@ -166,7 +170,6 @@ class Cerebras(Model):
|
|
|
166
170
|
"type": "function",
|
|
167
171
|
"function": {
|
|
168
172
|
"name": tool["function"]["name"],
|
|
169
|
-
"strict": True, # Ensure strict adherence to expected outputs
|
|
170
173
|
"description": tool["function"]["description"],
|
|
171
174
|
"parameters": tool["function"]["parameters"],
|
|
172
175
|
},
|
|
@@ -174,7 +177,10 @@ class Cerebras(Model):
|
|
|
174
177
|
for tool in tools
|
|
175
178
|
]
|
|
176
179
|
# Cerebras requires parallel_tool_calls=False for llama-4-scout-17b-16e-instruct
|
|
177
|
-
|
|
180
|
+
if self.id == "llama-4-scout-17b-16e-instruct":
|
|
181
|
+
request_params["parallel_tool_calls"] = False
|
|
182
|
+
elif self.parallel_tool_calls is not None:
|
|
183
|
+
request_params["parallel_tool_calls"] = self.parallel_tool_calls
|
|
178
184
|
|
|
179
185
|
# Handle response format for structured outputs
|
|
180
186
|
if response_format is not None:
|
|
@@ -183,10 +189,10 @@ class Cerebras(Model):
|
|
|
183
189
|
and response_format.get("type") == "json_schema"
|
|
184
190
|
and isinstance(response_format.get("json_schema"), dict)
|
|
185
191
|
):
|
|
186
|
-
# Ensure json_schema has strict
|
|
192
|
+
# Ensure json_schema has strict parameter set
|
|
187
193
|
schema = response_format["json_schema"]
|
|
188
194
|
if isinstance(schema.get("schema"), dict) and "strict" not in schema:
|
|
189
|
-
schema["strict"] =
|
|
195
|
+
schema["strict"] = self.strict_output
|
|
190
196
|
|
|
191
197
|
request_params["response_format"] = response_format
|
|
192
198
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from dataclasses import dataclass
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
3
|
from os import getenv
|
|
4
4
|
from typing import Any, Dict, List, Optional, Type, Union
|
|
5
5
|
|
|
@@ -16,15 +16,16 @@ class CerebrasOpenAI(OpenAILike):
|
|
|
16
16
|
name: str = "CerebrasOpenAI"
|
|
17
17
|
provider: str = "CerebrasOpenAI"
|
|
18
18
|
|
|
19
|
-
parallel_tool_calls: bool =
|
|
19
|
+
parallel_tool_calls: Optional[bool] = None
|
|
20
20
|
base_url: str = "https://api.cerebras.ai/v1"
|
|
21
|
-
api_key: Optional[str] = getenv("CEREBRAS_API_KEY", None)
|
|
21
|
+
api_key: Optional[str] = field(default_factory=lambda: getenv("CEREBRAS_API_KEY", None))
|
|
22
22
|
|
|
23
23
|
def get_request_params(
|
|
24
24
|
self,
|
|
25
25
|
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
26
26
|
tools: Optional[List[Dict[str, Any]]] = None,
|
|
27
27
|
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
28
|
+
**kwargs: Any,
|
|
28
29
|
) -> Dict[str, Any]:
|
|
29
30
|
"""
|
|
30
31
|
Returns keyword arguments for API requests.
|
|
@@ -44,7 +45,6 @@ class CerebrasOpenAI(OpenAILike):
|
|
|
44
45
|
"type": "function",
|
|
45
46
|
"function": {
|
|
46
47
|
"name": tool["function"]["name"],
|
|
47
|
-
"strict": True, # Ensure strict adherence to expected outputs
|
|
48
48
|
"description": tool["function"]["description"],
|
|
49
49
|
"parameters": tool["function"]["parameters"],
|
|
50
50
|
},
|
|
@@ -52,7 +52,10 @@ class CerebrasOpenAI(OpenAILike):
|
|
|
52
52
|
for tool in tools
|
|
53
53
|
]
|
|
54
54
|
# Cerebras requires parallel_tool_calls=False for llama-4-scout-17b-16e-instruct
|
|
55
|
-
|
|
55
|
+
if self.id == "llama-4-scout-17b-16e-instruct":
|
|
56
|
+
request_params["parallel_tool_calls"] = False
|
|
57
|
+
elif self.parallel_tool_calls is not None:
|
|
58
|
+
request_params["parallel_tool_calls"] = self.parallel_tool_calls
|
|
56
59
|
|
|
57
60
|
if request_params:
|
|
58
61
|
log_debug(f"Calling {self.provider} with request parameters: {request_params}", log_level=2)
|
agno/models/cohere/chat.py
CHANGED
|
@@ -2,6 +2,7 @@ from dataclasses import dataclass
|
|
|
2
2
|
from os import getenv
|
|
3
3
|
from typing import Any, AsyncIterator, Dict, Iterator, List, Optional, Tuple, Type, Union
|
|
4
4
|
|
|
5
|
+
import httpx
|
|
5
6
|
from pydantic import BaseModel
|
|
6
7
|
|
|
7
8
|
from agno.exceptions import ModelProviderError
|
|
@@ -10,7 +11,8 @@ from agno.models.message import Message
|
|
|
10
11
|
from agno.models.metrics import Metrics
|
|
11
12
|
from agno.models.response import ModelResponse
|
|
12
13
|
from agno.run.agent import RunOutput
|
|
13
|
-
from agno.utils.
|
|
14
|
+
from agno.utils.http import get_default_async_client, get_default_sync_client
|
|
15
|
+
from agno.utils.log import log_debug, log_error, log_warning
|
|
14
16
|
from agno.utils.models.cohere import format_messages
|
|
15
17
|
|
|
16
18
|
try:
|
|
@@ -50,6 +52,7 @@ class Cohere(Model):
|
|
|
50
52
|
# -*- Client parameters
|
|
51
53
|
api_key: Optional[str] = None
|
|
52
54
|
client_params: Optional[Dict[str, Any]] = None
|
|
55
|
+
http_client: Optional[Union[httpx.Client, httpx.AsyncClient]] = None
|
|
53
56
|
# -*- Provide the Cohere client manually
|
|
54
57
|
client: Optional[CohereClient] = None
|
|
55
58
|
async_client: Optional[CohereAsyncClient] = None
|
|
@@ -66,6 +69,17 @@ class Cohere(Model):
|
|
|
66
69
|
|
|
67
70
|
_client_params["api_key"] = self.api_key
|
|
68
71
|
|
|
72
|
+
if self.http_client:
|
|
73
|
+
if isinstance(self.http_client, httpx.Client):
|
|
74
|
+
_client_params["httpx_client"] = self.http_client
|
|
75
|
+
else:
|
|
76
|
+
log_warning("http_client is not an instance of httpx.Client. Using default global httpx.Client.")
|
|
77
|
+
# Use global sync client when user http_client is invalid
|
|
78
|
+
_client_params["httpx_client"] = get_default_sync_client()
|
|
79
|
+
else:
|
|
80
|
+
# Use global sync client when no custom http_client is provided
|
|
81
|
+
_client_params["httpx_client"] = get_default_sync_client()
|
|
82
|
+
|
|
69
83
|
self.client = CohereClient(**_client_params)
|
|
70
84
|
return self.client # type: ignore
|
|
71
85
|
|
|
@@ -82,6 +96,18 @@ class Cohere(Model):
|
|
|
82
96
|
|
|
83
97
|
_client_params["api_key"] = self.api_key
|
|
84
98
|
|
|
99
|
+
if self.http_client:
|
|
100
|
+
if isinstance(self.http_client, httpx.AsyncClient):
|
|
101
|
+
_client_params["httpx_client"] = self.http_client
|
|
102
|
+
else:
|
|
103
|
+
log_warning(
|
|
104
|
+
"http_client is not an instance of httpx.AsyncClient. Using default global httpx.AsyncClient."
|
|
105
|
+
)
|
|
106
|
+
# Use global async client when user http_client is invalid
|
|
107
|
+
_client_params["httpx_client"] = get_default_async_client()
|
|
108
|
+
else:
|
|
109
|
+
# Use global async client when no custom http_client is provided
|
|
110
|
+
_client_params["httpx_client"] = get_default_async_client()
|
|
85
111
|
self.async_client = CohereAsyncClient(**_client_params)
|
|
86
112
|
return self.async_client # type: ignore
|
|
87
113
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from os import getenv
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from agno.models.openai.like import OpenAILike
|
|
8
|
+
from agno.utils.log import log_debug
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class CometAPI(OpenAILike):
|
|
13
|
+
"""
|
|
14
|
+
The CometAPI class provides access to multiple AI model providers
|
|
15
|
+
(GPT, Claude, Gemini, DeepSeek, etc.) through OpenAI-compatible endpoints.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
id (str): The id of the CometAPI model to use. Default is "gpt-5-mini".
|
|
19
|
+
name (str): The name for this model. Defaults to "CometAPI".
|
|
20
|
+
api_key (str): The API key for CometAPI. Defaults to COMETAPI_KEY environment variable.
|
|
21
|
+
base_url (str): The base URL for CometAPI. Defaults to "https://api.cometapi.com/v1".
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
name: str = "CometAPI"
|
|
25
|
+
id: str = "gpt-5-mini"
|
|
26
|
+
api_key: Optional[str] = field(default_factory=lambda: getenv("COMETAPI_KEY"))
|
|
27
|
+
base_url: str = "https://api.cometapi.com/v1"
|
|
28
|
+
|
|
29
|
+
def get_available_models(self) -> List[str]:
|
|
30
|
+
"""
|
|
31
|
+
Fetch available chat models from CometAPI, filtering out non-chat models.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
List of available chat model IDs
|
|
35
|
+
"""
|
|
36
|
+
if not self.api_key:
|
|
37
|
+
log_debug("No API key provided, returning empty model list")
|
|
38
|
+
return []
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
with httpx.Client() as client:
|
|
42
|
+
response = client.get(
|
|
43
|
+
f"{self.base_url}/models",
|
|
44
|
+
headers={"Authorization": f"Bearer {self.api_key}", "Accept": "application/json"},
|
|
45
|
+
timeout=30.0,
|
|
46
|
+
)
|
|
47
|
+
response.raise_for_status()
|
|
48
|
+
|
|
49
|
+
data = response.json()
|
|
50
|
+
all_models = data.get("data", [])
|
|
51
|
+
|
|
52
|
+
log_debug(f"Found {len(all_models)} total models")
|
|
53
|
+
return sorted(all_models)
|
|
54
|
+
|
|
55
|
+
except Exception as e:
|
|
56
|
+
log_debug(f"Error fetching models from CometAPI: {e}")
|
|
57
|
+
return []
|
|
@@ -73,6 +73,7 @@ class DashScope(OpenAILike):
|
|
|
73
73
|
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
74
74
|
tools: Optional[List[Dict[str, Any]]] = None,
|
|
75
75
|
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
76
|
+
**kwargs: Any,
|
|
76
77
|
) -> Dict[str, Any]:
|
|
77
78
|
params = super().get_request_params(response_format=response_format, tools=tools, tool_choice=tool_choice)
|
|
78
79
|
|
|
@@ -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 Optional
|
|
4
4
|
|
|
@@ -22,7 +22,7 @@ class DeepInfra(OpenAILike):
|
|
|
22
22
|
name: str = "DeepInfra"
|
|
23
23
|
provider: str = "DeepInfra"
|
|
24
24
|
|
|
25
|
-
api_key: Optional[str] = getenv("DEEPINFRA_API_KEY")
|
|
25
|
+
api_key: Optional[str] = field(default_factory=lambda: getenv("DEEPINFRA_API_KEY"))
|
|
26
26
|
base_url: str = "https://api.deepinfra.com/v1/openai"
|
|
27
27
|
|
|
28
28
|
supports_native_structured_outputs: bool = False
|
agno/models/deepseek/deepseek.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
|
|
4
4
|
|
|
@@ -23,7 +23,7 @@ class DeepSeek(OpenAILike):
|
|
|
23
23
|
name: str = "DeepSeek"
|
|
24
24
|
provider: str = "DeepSeek"
|
|
25
25
|
|
|
26
|
-
api_key: Optional[str] = getenv("DEEPSEEK_API_KEY")
|
|
26
|
+
api_key: Optional[str] = field(default_factory=lambda: getenv("DEEPSEEK_API_KEY"))
|
|
27
27
|
base_url: str = "https://api.deepseek.com"
|
|
28
28
|
|
|
29
29
|
# Their support for structured outputs is currently broken
|
|
@@ -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 Optional
|
|
4
4
|
|
|
@@ -22,5 +22,5 @@ class Fireworks(OpenAILike):
|
|
|
22
22
|
name: str = "Fireworks"
|
|
23
23
|
provider: str = "Fireworks"
|
|
24
24
|
|
|
25
|
-
api_key: Optional[str] = getenv("FIREWORKS_API_KEY")
|
|
25
|
+
api_key: Optional[str] = field(default_factory=lambda: getenv("FIREWORKS_API_KEY"))
|
|
26
26
|
base_url: str = "https://api.fireworks.ai/inference/v1"
|
agno/models/google/gemini.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import base64
|
|
1
2
|
import json
|
|
2
3
|
import time
|
|
3
4
|
from collections.abc import AsyncIterator
|
|
@@ -16,9 +17,8 @@ from agno.models.message import Citations, Message, UrlCitation
|
|
|
16
17
|
from agno.models.metrics import Metrics
|
|
17
18
|
from agno.models.response import ModelResponse
|
|
18
19
|
from agno.run.agent import RunOutput
|
|
19
|
-
from agno.utils.gemini import
|
|
20
|
+
from agno.utils.gemini import format_function_definitions, format_image_for_message, prepare_response_schema
|
|
20
21
|
from agno.utils.log import log_debug, log_error, log_info, log_warning
|
|
21
|
-
from agno.utils.models.schema_utils import get_response_schema_for_provider
|
|
22
22
|
|
|
23
23
|
try:
|
|
24
24
|
from google import genai
|
|
@@ -27,6 +27,7 @@ try:
|
|
|
27
27
|
from google.genai.types import (
|
|
28
28
|
Content,
|
|
29
29
|
DynamicRetrievalConfig,
|
|
30
|
+
FunctionCallingConfigMode,
|
|
30
31
|
GenerateContentConfig,
|
|
31
32
|
GenerateContentResponse,
|
|
32
33
|
GenerateContentResponseUsageMetadata,
|
|
@@ -87,7 +88,7 @@ class Gemini(Model):
|
|
|
87
88
|
presence_penalty: Optional[float] = None
|
|
88
89
|
frequency_penalty: Optional[float] = None
|
|
89
90
|
seed: Optional[int] = None
|
|
90
|
-
response_modalities: Optional[list[str]] = None # "
|
|
91
|
+
response_modalities: Optional[list[str]] = None # "TEXT", "IMAGE", and/or "AUDIO"
|
|
91
92
|
speech_config: Optional[dict[str, Any]] = None
|
|
92
93
|
cached_content: Optional[Any] = None
|
|
93
94
|
thinking_budget: Optional[int] = None # Thinking budget for Gemini 2.5 models
|
|
@@ -151,6 +152,7 @@ class Gemini(Model):
|
|
|
151
152
|
system_message: Optional[str] = None,
|
|
152
153
|
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
153
154
|
tools: Optional[List[Dict[str, Any]]] = None,
|
|
155
|
+
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
154
156
|
) -> Dict[str, Any]:
|
|
155
157
|
"""
|
|
156
158
|
Returns the request keyword arguments for the GenerativeModel client.
|
|
@@ -191,12 +193,9 @@ class Gemini(Model):
|
|
|
191
193
|
|
|
192
194
|
if response_format is not None and isinstance(response_format, type) and issubclass(response_format, BaseModel):
|
|
193
195
|
config["response_mime_type"] = "application/json" # type: ignore
|
|
194
|
-
# Convert Pydantic model
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
normalized_schema = get_response_schema_for_provider(response_format, "gemini")
|
|
198
|
-
gemini_schema = convert_schema(normalized_schema)
|
|
199
|
-
config["response_schema"] = gemini_schema
|
|
196
|
+
# Convert Pydantic model using our hybrid approach
|
|
197
|
+
# This will handle complex schemas with nested models, dicts, and circular refs
|
|
198
|
+
config["response_schema"] = prepare_response_schema(response_format)
|
|
200
199
|
|
|
201
200
|
# Add thinking configuration
|
|
202
201
|
thinking_config_params = {}
|
|
@@ -249,6 +248,18 @@ class Gemini(Model):
|
|
|
249
248
|
elif tools:
|
|
250
249
|
config["tools"] = [format_function_definitions(tools)]
|
|
251
250
|
|
|
251
|
+
if tool_choice is not None:
|
|
252
|
+
if isinstance(tool_choice, str) and tool_choice.lower() == "auto":
|
|
253
|
+
config["tool_config"] = {"function_calling_config": {"mode": FunctionCallingConfigMode.AUTO}}
|
|
254
|
+
elif isinstance(tool_choice, str) and tool_choice.lower() == "none":
|
|
255
|
+
config["tool_config"] = {"function_calling_config": {"mode": FunctionCallingConfigMode.NONE}}
|
|
256
|
+
elif isinstance(tool_choice, str) and tool_choice.lower() == "validated":
|
|
257
|
+
config["tool_config"] = {"function_calling_config": {"mode": FunctionCallingConfigMode.VALIDATED}}
|
|
258
|
+
elif isinstance(tool_choice, str) and tool_choice.lower() == "any":
|
|
259
|
+
config["tool_config"] = {"function_calling_config": {"mode": FunctionCallingConfigMode.ANY}}
|
|
260
|
+
else:
|
|
261
|
+
config["tool_config"] = {"function_calling_config": {"mode": tool_choice}}
|
|
262
|
+
|
|
252
263
|
config = {k: v for k, v in config.items() if v is not None}
|
|
253
264
|
|
|
254
265
|
if config:
|
|
@@ -275,7 +286,9 @@ class Gemini(Model):
|
|
|
275
286
|
Invokes the model with a list of messages and returns the response.
|
|
276
287
|
"""
|
|
277
288
|
formatted_messages, system_message = self._format_messages(messages)
|
|
278
|
-
request_kwargs = self.get_request_params(
|
|
289
|
+
request_kwargs = self.get_request_params(
|
|
290
|
+
system_message, response_format=response_format, tools=tools, tool_choice=tool_choice
|
|
291
|
+
)
|
|
279
292
|
try:
|
|
280
293
|
if run_response and run_response.metrics:
|
|
281
294
|
run_response.metrics.set_time_to_first_token()
|
|
@@ -319,7 +332,9 @@ class Gemini(Model):
|
|
|
319
332
|
"""
|
|
320
333
|
formatted_messages, system_message = self._format_messages(messages)
|
|
321
334
|
|
|
322
|
-
request_kwargs = self.get_request_params(
|
|
335
|
+
request_kwargs = self.get_request_params(
|
|
336
|
+
system_message, response_format=response_format, tools=tools, tool_choice=tool_choice
|
|
337
|
+
)
|
|
323
338
|
try:
|
|
324
339
|
if run_response and run_response.metrics:
|
|
325
340
|
run_response.metrics.set_time_to_first_token()
|
|
@@ -360,7 +375,9 @@ class Gemini(Model):
|
|
|
360
375
|
"""
|
|
361
376
|
formatted_messages, system_message = self._format_messages(messages)
|
|
362
377
|
|
|
363
|
-
request_kwargs = self.get_request_params(
|
|
378
|
+
request_kwargs = self.get_request_params(
|
|
379
|
+
system_message, response_format=response_format, tools=tools, tool_choice=tool_choice
|
|
380
|
+
)
|
|
364
381
|
|
|
365
382
|
try:
|
|
366
383
|
if run_response and run_response.metrics:
|
|
@@ -404,7 +421,9 @@ class Gemini(Model):
|
|
|
404
421
|
"""
|
|
405
422
|
formatted_messages, system_message = self._format_messages(messages)
|
|
406
423
|
|
|
407
|
-
request_kwargs = self.get_request_params(
|
|
424
|
+
request_kwargs = self.get_request_params(
|
|
425
|
+
system_message, response_format=response_format, tools=tools, tool_choice=tool_choice
|
|
426
|
+
)
|
|
408
427
|
|
|
409
428
|
try:
|
|
410
429
|
if run_response and run_response.metrics:
|
|
@@ -462,14 +481,18 @@ class Gemini(Model):
|
|
|
462
481
|
if role == "model" and message.tool_calls is not None and len(message.tool_calls) > 0:
|
|
463
482
|
if content is not None:
|
|
464
483
|
content_str = content if isinstance(content, str) else str(content)
|
|
465
|
-
|
|
484
|
+
part = Part.from_text(text=content_str)
|
|
485
|
+
if message.provider_data and "thought_signature" in message.provider_data:
|
|
486
|
+
part.thought_signature = base64.b64decode(message.provider_data["thought_signature"])
|
|
487
|
+
message_parts.append(part)
|
|
466
488
|
for tool_call in message.tool_calls:
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
args=json.loads(tool_call["function"]["arguments"]),
|
|
471
|
-
)
|
|
489
|
+
part = Part.from_function_call(
|
|
490
|
+
name=tool_call["function"]["name"],
|
|
491
|
+
args=json.loads(tool_call["function"]["arguments"]),
|
|
472
492
|
)
|
|
493
|
+
if "thought_signature" in tool_call:
|
|
494
|
+
part.thought_signature = base64.b64decode(tool_call["thought_signature"])
|
|
495
|
+
message_parts.append(part)
|
|
473
496
|
# Function call results
|
|
474
497
|
elif message.tool_calls is not None and len(message.tool_calls) > 0:
|
|
475
498
|
for tool_call in message.tool_calls:
|
|
@@ -481,7 +504,10 @@ class Gemini(Model):
|
|
|
481
504
|
# Regular text content
|
|
482
505
|
else:
|
|
483
506
|
if isinstance(content, str):
|
|
484
|
-
|
|
507
|
+
part = Part.from_text(text=content)
|
|
508
|
+
if message.provider_data and "thought_signature" in message.provider_data:
|
|
509
|
+
part.thought_signature = base64.b64decode(message.provider_data["thought_signature"])
|
|
510
|
+
message_parts = [part]
|
|
485
511
|
|
|
486
512
|
if role == "user" and message.tool_calls is None:
|
|
487
513
|
# Add images to the message for the model
|
|
@@ -816,12 +842,30 @@ class Gemini(Model):
|
|
|
816
842
|
else:
|
|
817
843
|
model_response.content += content_str
|
|
818
844
|
|
|
845
|
+
# Capture thought signature for text parts
|
|
846
|
+
if hasattr(part, "thought_signature") and part.thought_signature:
|
|
847
|
+
if model_response.provider_data is None:
|
|
848
|
+
model_response.provider_data = {}
|
|
849
|
+
model_response.provider_data["thought_signature"] = base64.b64encode(
|
|
850
|
+
part.thought_signature
|
|
851
|
+
).decode("ascii")
|
|
852
|
+
|
|
819
853
|
if hasattr(part, "inline_data") and part.inline_data is not None:
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
854
|
+
# Handle audio responses (for TTS models)
|
|
855
|
+
if part.inline_data.mime_type and part.inline_data.mime_type.startswith("audio/"):
|
|
856
|
+
# Store raw bytes data
|
|
857
|
+
model_response.audio = Audio(
|
|
858
|
+
id=str(uuid4()),
|
|
859
|
+
content=part.inline_data.data,
|
|
860
|
+
mime_type=part.inline_data.mime_type,
|
|
861
|
+
)
|
|
862
|
+
# Image responses
|
|
863
|
+
else:
|
|
864
|
+
if model_response.images is None:
|
|
865
|
+
model_response.images = []
|
|
866
|
+
model_response.images.append(
|
|
867
|
+
Image(id=str(uuid4()), content=part.inline_data.data, mime_type=part.inline_data.mime_type)
|
|
868
|
+
)
|
|
825
869
|
|
|
826
870
|
# Extract function call if present
|
|
827
871
|
if hasattr(part, "function_call") and part.function_call is not None:
|
|
@@ -837,6 +881,10 @@ class Gemini(Model):
|
|
|
837
881
|
},
|
|
838
882
|
}
|
|
839
883
|
|
|
884
|
+
# Capture thought signature for function calls
|
|
885
|
+
if hasattr(part, "thought_signature") and part.thought_signature:
|
|
886
|
+
tool_call["thought_signature"] = base64.b64encode(part.thought_signature).decode("ascii")
|
|
887
|
+
|
|
840
888
|
model_response.tool_calls.append(tool_call)
|
|
841
889
|
|
|
842
890
|
citations = Citations()
|
|
@@ -928,12 +976,32 @@ class Gemini(Model):
|
|
|
928
976
|
else:
|
|
929
977
|
model_response.content += text_content
|
|
930
978
|
|
|
979
|
+
# Capture thought signature for text parts
|
|
980
|
+
if hasattr(part, "thought_signature") and part.thought_signature:
|
|
981
|
+
if model_response.provider_data is None:
|
|
982
|
+
model_response.provider_data = {}
|
|
983
|
+
model_response.provider_data["thought_signature"] = base64.b64encode(
|
|
984
|
+
part.thought_signature
|
|
985
|
+
).decode("ascii")
|
|
986
|
+
|
|
931
987
|
if hasattr(part, "inline_data") and part.inline_data is not None:
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
988
|
+
# Audio responses
|
|
989
|
+
if part.inline_data.mime_type and part.inline_data.mime_type.startswith("audio/"):
|
|
990
|
+
# Store raw bytes audio data
|
|
991
|
+
model_response.audio = Audio(
|
|
992
|
+
id=str(uuid4()),
|
|
993
|
+
content=part.inline_data.data,
|
|
994
|
+
mime_type=part.inline_data.mime_type,
|
|
995
|
+
)
|
|
996
|
+
# Image responses
|
|
997
|
+
else:
|
|
998
|
+
if model_response.images is None:
|
|
999
|
+
model_response.images = []
|
|
1000
|
+
model_response.images.append(
|
|
1001
|
+
Image(
|
|
1002
|
+
id=str(uuid4()), content=part.inline_data.data, mime_type=part.inline_data.mime_type
|
|
1003
|
+
)
|
|
1004
|
+
)
|
|
937
1005
|
|
|
938
1006
|
# Extract function call if present
|
|
939
1007
|
if hasattr(part, "function_call") and part.function_call is not None:
|
|
@@ -949,6 +1017,10 @@ class Gemini(Model):
|
|
|
949
1017
|
},
|
|
950
1018
|
}
|
|
951
1019
|
|
|
1020
|
+
# Capture thought signature for function calls
|
|
1021
|
+
if hasattr(part, "thought_signature") and part.thought_signature:
|
|
1022
|
+
tool_call["thought_signature"] = base64.b64encode(part.thought_signature).decode("ascii")
|
|
1023
|
+
|
|
952
1024
|
model_response.tool_calls.append(tool_call)
|
|
953
1025
|
|
|
954
1026
|
if response_delta.candidates[0].grounding_metadata is not None:
|
|
@@ -1033,9 +1105,9 @@ class Gemini(Model):
|
|
|
1033
1105
|
|
|
1034
1106
|
metrics.input_tokens = response_usage.prompt_token_count or 0
|
|
1035
1107
|
metrics.output_tokens = response_usage.candidates_token_count or 0
|
|
1036
|
-
metrics.total_tokens = metrics.input_tokens + metrics.output_tokens
|
|
1037
1108
|
if response_usage.thoughts_token_count is not None:
|
|
1038
1109
|
metrics.output_tokens += response_usage.thoughts_token_count or 0
|
|
1110
|
+
metrics.total_tokens = metrics.input_tokens + metrics.output_tokens
|
|
1039
1111
|
|
|
1040
1112
|
metrics.cache_read_tokens = response_usage.cached_content_token_count or 0
|
|
1041
1113
|
|