agno 2.2.13__py3-none-any.whl → 2.4.3__py3-none-any.whl

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