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.
Files changed (314) hide show
  1. agno/agent/agent.py +6015 -2823
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/culture/__init__.py +3 -0
  5. agno/culture/manager.py +956 -0
  6. agno/db/async_postgres/__init__.py +3 -0
  7. agno/db/base.py +385 -6
  8. agno/db/dynamo/dynamo.py +388 -81
  9. agno/db/dynamo/schemas.py +47 -10
  10. agno/db/dynamo/utils.py +63 -4
  11. agno/db/firestore/firestore.py +435 -64
  12. agno/db/firestore/schemas.py +11 -0
  13. agno/db/firestore/utils.py +102 -4
  14. agno/db/gcs_json/gcs_json_db.py +384 -42
  15. agno/db/gcs_json/utils.py +60 -26
  16. agno/db/in_memory/in_memory_db.py +351 -66
  17. agno/db/in_memory/utils.py +60 -2
  18. agno/db/json/json_db.py +339 -48
  19. agno/db/json/utils.py +60 -26
  20. agno/db/migrations/manager.py +199 -0
  21. agno/db/migrations/v1_to_v2.py +510 -37
  22. agno/db/migrations/versions/__init__.py +0 -0
  23. agno/db/migrations/versions/v2_3_0.py +938 -0
  24. agno/db/mongo/__init__.py +15 -1
  25. agno/db/mongo/async_mongo.py +2036 -0
  26. agno/db/mongo/mongo.py +653 -76
  27. agno/db/mongo/schemas.py +13 -0
  28. agno/db/mongo/utils.py +80 -8
  29. agno/db/mysql/mysql.py +687 -25
  30. agno/db/mysql/schemas.py +61 -37
  31. agno/db/mysql/utils.py +60 -2
  32. agno/db/postgres/__init__.py +2 -1
  33. agno/db/postgres/async_postgres.py +2001 -0
  34. agno/db/postgres/postgres.py +676 -57
  35. agno/db/postgres/schemas.py +43 -18
  36. agno/db/postgres/utils.py +164 -2
  37. agno/db/redis/redis.py +344 -38
  38. agno/db/redis/schemas.py +18 -0
  39. agno/db/redis/utils.py +60 -2
  40. agno/db/schemas/__init__.py +2 -1
  41. agno/db/schemas/culture.py +120 -0
  42. agno/db/schemas/memory.py +13 -0
  43. agno/db/singlestore/schemas.py +26 -1
  44. agno/db/singlestore/singlestore.py +687 -53
  45. agno/db/singlestore/utils.py +60 -2
  46. agno/db/sqlite/__init__.py +2 -1
  47. agno/db/sqlite/async_sqlite.py +2371 -0
  48. agno/db/sqlite/schemas.py +24 -0
  49. agno/db/sqlite/sqlite.py +774 -85
  50. agno/db/sqlite/utils.py +168 -5
  51. agno/db/surrealdb/__init__.py +3 -0
  52. agno/db/surrealdb/metrics.py +292 -0
  53. agno/db/surrealdb/models.py +309 -0
  54. agno/db/surrealdb/queries.py +71 -0
  55. agno/db/surrealdb/surrealdb.py +1361 -0
  56. agno/db/surrealdb/utils.py +147 -0
  57. agno/db/utils.py +50 -22
  58. agno/eval/accuracy.py +50 -43
  59. agno/eval/performance.py +6 -3
  60. agno/eval/reliability.py +6 -3
  61. agno/eval/utils.py +33 -16
  62. agno/exceptions.py +68 -1
  63. agno/filters.py +354 -0
  64. agno/guardrails/__init__.py +6 -0
  65. agno/guardrails/base.py +19 -0
  66. agno/guardrails/openai.py +144 -0
  67. agno/guardrails/pii.py +94 -0
  68. agno/guardrails/prompt_injection.py +52 -0
  69. agno/integrations/discord/client.py +1 -0
  70. agno/knowledge/chunking/agentic.py +13 -10
  71. agno/knowledge/chunking/fixed.py +1 -1
  72. agno/knowledge/chunking/semantic.py +40 -8
  73. agno/knowledge/chunking/strategy.py +59 -15
  74. agno/knowledge/embedder/aws_bedrock.py +9 -4
  75. agno/knowledge/embedder/azure_openai.py +54 -0
  76. agno/knowledge/embedder/base.py +2 -0
  77. agno/knowledge/embedder/cohere.py +184 -5
  78. agno/knowledge/embedder/fastembed.py +1 -1
  79. agno/knowledge/embedder/google.py +79 -1
  80. agno/knowledge/embedder/huggingface.py +9 -4
  81. agno/knowledge/embedder/jina.py +63 -0
  82. agno/knowledge/embedder/mistral.py +78 -11
  83. agno/knowledge/embedder/nebius.py +1 -1
  84. agno/knowledge/embedder/ollama.py +13 -0
  85. agno/knowledge/embedder/openai.py +37 -65
  86. agno/knowledge/embedder/sentence_transformer.py +8 -4
  87. agno/knowledge/embedder/vllm.py +262 -0
  88. agno/knowledge/embedder/voyageai.py +69 -16
  89. agno/knowledge/knowledge.py +594 -186
  90. agno/knowledge/reader/base.py +9 -2
  91. agno/knowledge/reader/csv_reader.py +8 -10
  92. agno/knowledge/reader/docx_reader.py +5 -6
  93. agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
  94. agno/knowledge/reader/json_reader.py +6 -5
  95. agno/knowledge/reader/markdown_reader.py +13 -13
  96. agno/knowledge/reader/pdf_reader.py +43 -68
  97. agno/knowledge/reader/pptx_reader.py +101 -0
  98. agno/knowledge/reader/reader_factory.py +51 -6
  99. agno/knowledge/reader/s3_reader.py +3 -15
  100. agno/knowledge/reader/tavily_reader.py +194 -0
  101. agno/knowledge/reader/text_reader.py +13 -13
  102. agno/knowledge/reader/web_search_reader.py +2 -43
  103. agno/knowledge/reader/website_reader.py +43 -25
  104. agno/knowledge/reranker/__init__.py +2 -8
  105. agno/knowledge/types.py +9 -0
  106. agno/knowledge/utils.py +20 -0
  107. agno/media.py +72 -0
  108. agno/memory/manager.py +336 -82
  109. agno/models/aimlapi/aimlapi.py +2 -2
  110. agno/models/anthropic/claude.py +183 -37
  111. agno/models/aws/bedrock.py +52 -112
  112. agno/models/aws/claude.py +33 -1
  113. agno/models/azure/ai_foundry.py +33 -15
  114. agno/models/azure/openai_chat.py +25 -8
  115. agno/models/base.py +999 -519
  116. agno/models/cerebras/cerebras.py +19 -13
  117. agno/models/cerebras/cerebras_openai.py +8 -5
  118. agno/models/cohere/chat.py +27 -1
  119. agno/models/cometapi/__init__.py +5 -0
  120. agno/models/cometapi/cometapi.py +57 -0
  121. agno/models/dashscope/dashscope.py +1 -0
  122. agno/models/deepinfra/deepinfra.py +2 -2
  123. agno/models/deepseek/deepseek.py +2 -2
  124. agno/models/fireworks/fireworks.py +2 -2
  125. agno/models/google/gemini.py +103 -31
  126. agno/models/groq/groq.py +28 -11
  127. agno/models/huggingface/huggingface.py +2 -1
  128. agno/models/internlm/internlm.py +2 -2
  129. agno/models/langdb/langdb.py +4 -4
  130. agno/models/litellm/chat.py +18 -1
  131. agno/models/litellm/litellm_openai.py +2 -2
  132. agno/models/llama_cpp/__init__.py +5 -0
  133. agno/models/llama_cpp/llama_cpp.py +22 -0
  134. agno/models/message.py +139 -0
  135. agno/models/meta/llama.py +27 -10
  136. agno/models/meta/llama_openai.py +5 -17
  137. agno/models/nebius/nebius.py +6 -6
  138. agno/models/nexus/__init__.py +3 -0
  139. agno/models/nexus/nexus.py +22 -0
  140. agno/models/nvidia/nvidia.py +2 -2
  141. agno/models/ollama/chat.py +59 -5
  142. agno/models/openai/chat.py +69 -29
  143. agno/models/openai/responses.py +103 -106
  144. agno/models/openrouter/openrouter.py +41 -3
  145. agno/models/perplexity/perplexity.py +4 -5
  146. agno/models/portkey/portkey.py +3 -3
  147. agno/models/requesty/__init__.py +5 -0
  148. agno/models/requesty/requesty.py +52 -0
  149. agno/models/response.py +77 -1
  150. agno/models/sambanova/sambanova.py +2 -2
  151. agno/models/siliconflow/__init__.py +5 -0
  152. agno/models/siliconflow/siliconflow.py +25 -0
  153. agno/models/together/together.py +2 -2
  154. agno/models/utils.py +254 -8
  155. agno/models/vercel/v0.py +2 -2
  156. agno/models/vertexai/__init__.py +0 -0
  157. agno/models/vertexai/claude.py +96 -0
  158. agno/models/vllm/vllm.py +1 -0
  159. agno/models/xai/xai.py +3 -2
  160. agno/os/app.py +543 -178
  161. agno/os/auth.py +24 -14
  162. agno/os/config.py +1 -0
  163. agno/os/interfaces/__init__.py +1 -0
  164. agno/os/interfaces/a2a/__init__.py +3 -0
  165. agno/os/interfaces/a2a/a2a.py +42 -0
  166. agno/os/interfaces/a2a/router.py +250 -0
  167. agno/os/interfaces/a2a/utils.py +924 -0
  168. agno/os/interfaces/agui/agui.py +23 -7
  169. agno/os/interfaces/agui/router.py +27 -3
  170. agno/os/interfaces/agui/utils.py +242 -142
  171. agno/os/interfaces/base.py +6 -2
  172. agno/os/interfaces/slack/router.py +81 -23
  173. agno/os/interfaces/slack/slack.py +29 -14
  174. agno/os/interfaces/whatsapp/router.py +11 -4
  175. agno/os/interfaces/whatsapp/whatsapp.py +14 -7
  176. agno/os/mcp.py +111 -54
  177. agno/os/middleware/__init__.py +7 -0
  178. agno/os/middleware/jwt.py +233 -0
  179. agno/os/router.py +556 -139
  180. agno/os/routers/evals/evals.py +71 -34
  181. agno/os/routers/evals/schemas.py +31 -31
  182. agno/os/routers/evals/utils.py +6 -5
  183. agno/os/routers/health.py +31 -0
  184. agno/os/routers/home.py +52 -0
  185. agno/os/routers/knowledge/knowledge.py +185 -38
  186. agno/os/routers/knowledge/schemas.py +82 -22
  187. agno/os/routers/memory/memory.py +158 -53
  188. agno/os/routers/memory/schemas.py +20 -16
  189. agno/os/routers/metrics/metrics.py +20 -8
  190. agno/os/routers/metrics/schemas.py +16 -16
  191. agno/os/routers/session/session.py +499 -38
  192. agno/os/schema.py +308 -198
  193. agno/os/utils.py +401 -41
  194. agno/reasoning/anthropic.py +80 -0
  195. agno/reasoning/azure_ai_foundry.py +2 -2
  196. agno/reasoning/deepseek.py +2 -2
  197. agno/reasoning/default.py +3 -1
  198. agno/reasoning/gemini.py +73 -0
  199. agno/reasoning/groq.py +2 -2
  200. agno/reasoning/ollama.py +2 -2
  201. agno/reasoning/openai.py +7 -2
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +248 -94
  205. agno/run/base.py +44 -5
  206. agno/run/team.py +238 -97
  207. agno/run/workflow.py +144 -33
  208. agno/session/agent.py +105 -89
  209. agno/session/summary.py +65 -25
  210. agno/session/team.py +176 -96
  211. agno/session/workflow.py +406 -40
  212. agno/team/team.py +3854 -1610
  213. agno/tools/dalle.py +2 -4
  214. agno/tools/decorator.py +4 -2
  215. agno/tools/duckduckgo.py +15 -11
  216. agno/tools/e2b.py +14 -7
  217. agno/tools/eleven_labs.py +23 -25
  218. agno/tools/exa.py +21 -16
  219. agno/tools/file.py +153 -23
  220. agno/tools/file_generation.py +350 -0
  221. agno/tools/firecrawl.py +4 -4
  222. agno/tools/function.py +250 -30
  223. agno/tools/gmail.py +238 -14
  224. agno/tools/google_drive.py +270 -0
  225. agno/tools/googlecalendar.py +36 -8
  226. agno/tools/googlesheets.py +20 -5
  227. agno/tools/jira.py +20 -0
  228. agno/tools/knowledge.py +3 -3
  229. agno/tools/mcp/__init__.py +10 -0
  230. agno/tools/mcp/mcp.py +331 -0
  231. agno/tools/mcp/multi_mcp.py +347 -0
  232. agno/tools/mcp/params.py +24 -0
  233. agno/tools/mcp_toolbox.py +284 -0
  234. agno/tools/mem0.py +11 -17
  235. agno/tools/memori.py +1 -53
  236. agno/tools/memory.py +419 -0
  237. agno/tools/models/nebius.py +5 -5
  238. agno/tools/models_labs.py +20 -10
  239. agno/tools/notion.py +204 -0
  240. agno/tools/parallel.py +314 -0
  241. agno/tools/scrapegraph.py +58 -31
  242. agno/tools/searxng.py +2 -2
  243. agno/tools/serper.py +2 -2
  244. agno/tools/slack.py +18 -3
  245. agno/tools/spider.py +2 -2
  246. agno/tools/tavily.py +146 -0
  247. agno/tools/whatsapp.py +1 -1
  248. agno/tools/workflow.py +278 -0
  249. agno/tools/yfinance.py +12 -11
  250. agno/utils/agent.py +820 -0
  251. agno/utils/audio.py +27 -0
  252. agno/utils/common.py +90 -1
  253. agno/utils/events.py +217 -2
  254. agno/utils/gemini.py +180 -22
  255. agno/utils/hooks.py +57 -0
  256. agno/utils/http.py +111 -0
  257. agno/utils/knowledge.py +12 -5
  258. agno/utils/log.py +1 -0
  259. agno/utils/mcp.py +92 -2
  260. agno/utils/media.py +188 -10
  261. agno/utils/merge_dict.py +22 -1
  262. agno/utils/message.py +60 -0
  263. agno/utils/models/claude.py +40 -11
  264. agno/utils/print_response/agent.py +105 -21
  265. agno/utils/print_response/team.py +103 -38
  266. agno/utils/print_response/workflow.py +251 -34
  267. agno/utils/reasoning.py +22 -1
  268. agno/utils/serialize.py +32 -0
  269. agno/utils/streamlit.py +16 -10
  270. agno/utils/string.py +41 -0
  271. agno/utils/team.py +98 -9
  272. agno/utils/tools.py +1 -1
  273. agno/vectordb/base.py +23 -4
  274. agno/vectordb/cassandra/cassandra.py +65 -9
  275. agno/vectordb/chroma/chromadb.py +182 -38
  276. agno/vectordb/clickhouse/clickhousedb.py +64 -11
  277. agno/vectordb/couchbase/couchbase.py +105 -10
  278. agno/vectordb/lancedb/lance_db.py +124 -133
  279. agno/vectordb/langchaindb/langchaindb.py +25 -7
  280. agno/vectordb/lightrag/lightrag.py +17 -3
  281. agno/vectordb/llamaindex/__init__.py +3 -0
  282. agno/vectordb/llamaindex/llamaindexdb.py +46 -7
  283. agno/vectordb/milvus/milvus.py +126 -9
  284. agno/vectordb/mongodb/__init__.py +7 -1
  285. agno/vectordb/mongodb/mongodb.py +112 -7
  286. agno/vectordb/pgvector/pgvector.py +142 -21
  287. agno/vectordb/pineconedb/pineconedb.py +80 -8
  288. agno/vectordb/qdrant/qdrant.py +125 -39
  289. agno/vectordb/redis/__init__.py +9 -0
  290. agno/vectordb/redis/redisdb.py +694 -0
  291. agno/vectordb/singlestore/singlestore.py +111 -25
  292. agno/vectordb/surrealdb/surrealdb.py +31 -5
  293. agno/vectordb/upstashdb/upstashdb.py +76 -8
  294. agno/vectordb/weaviate/weaviate.py +86 -15
  295. agno/workflow/__init__.py +2 -0
  296. agno/workflow/agent.py +299 -0
  297. agno/workflow/condition.py +112 -18
  298. agno/workflow/loop.py +69 -10
  299. agno/workflow/parallel.py +266 -118
  300. agno/workflow/router.py +110 -17
  301. agno/workflow/step.py +638 -129
  302. agno/workflow/steps.py +65 -6
  303. agno/workflow/types.py +61 -23
  304. agno/workflow/workflow.py +2085 -272
  305. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/METADATA +182 -58
  306. agno-2.3.0.dist-info/RECORD +577 -0
  307. agno/knowledge/reader/url_reader.py +0 -128
  308. agno/tools/googlesearch.py +0 -98
  309. agno/tools/mcp.py +0 -610
  310. agno/utils/models/aws_claude.py +0 -170
  311. agno-2.0.1.dist-info/RECORD +0 -515
  312. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
  313. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/licenses/LICENSE +0 -0
  314. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
@@ -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 = False
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
- # Create a new async HTTP client with custom limits
129
- client_params["http_client"] = httpx.AsyncClient(
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
- request_params["parallel_tool_calls"] = self.parallel_tool_calls
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=True as required by Cerebras API
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"] = True
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 = False
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
- request_params["parallel_tool_calls"] = self.parallel_tool_calls
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)
@@ -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.log import log_debug, log_error
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,5 @@
1
+ from agno.models.cometapi.cometapi import CometAPI
2
+
3
+ __all__ = [
4
+ "CometAPI",
5
+ ]
@@ -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
@@ -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"
@@ -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 convert_schema, format_function_definitions, format_image_for_message
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 # "Text" and/or "Image"
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 to JSON schema, then normalize for Gemini, then convert to Gemini schema format
195
-
196
- # Get the normalized schema for Gemini
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(system_message, response_format=response_format, tools=tools)
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(system_message, response_format=response_format, tools=tools)
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(system_message, response_format=response_format, tools=tools)
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(system_message, response_format=response_format, tools=tools)
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
- message_parts.append(Part.from_text(text=content_str))
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
- message_parts.append(
468
- Part.from_function_call(
469
- name=tool_call["function"]["name"],
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
- message_parts = [Part.from_text(text=content)]
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
- if model_response.images is None:
821
- model_response.images = []
822
- model_response.images.append(
823
- Image(id=str(uuid4()), content=part.inline_data.data, mime_type=part.inline_data.mime_type)
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
- if model_response.images is None:
933
- model_response.images = []
934
- model_response.images.append(
935
- Image(id=str(uuid4()), content=part.inline_data.data, mime_type=part.inline_data.mime_type)
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