agno 0.1.2__py3-none-any.whl → 2.3.13__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 (723) hide show
  1. agno/__init__.py +8 -0
  2. agno/agent/__init__.py +44 -5
  3. agno/agent/agent.py +10531 -2975
  4. agno/api/agent.py +14 -53
  5. agno/api/api.py +7 -46
  6. agno/api/evals.py +22 -0
  7. agno/api/os.py +17 -0
  8. agno/api/routes.py +6 -25
  9. agno/api/schemas/__init__.py +9 -0
  10. agno/api/schemas/agent.py +6 -9
  11. agno/api/schemas/evals.py +16 -0
  12. agno/api/schemas/os.py +14 -0
  13. agno/api/schemas/team.py +10 -10
  14. agno/api/schemas/utils.py +21 -0
  15. agno/api/schemas/workflows.py +16 -0
  16. agno/api/settings.py +53 -0
  17. agno/api/team.py +22 -26
  18. agno/api/workflow.py +28 -0
  19. agno/cloud/aws/base.py +214 -0
  20. agno/cloud/aws/s3/__init__.py +2 -0
  21. agno/cloud/aws/s3/api_client.py +43 -0
  22. agno/cloud/aws/s3/bucket.py +195 -0
  23. agno/cloud/aws/s3/object.py +57 -0
  24. agno/compression/__init__.py +3 -0
  25. agno/compression/manager.py +247 -0
  26. agno/culture/__init__.py +3 -0
  27. agno/culture/manager.py +956 -0
  28. agno/db/__init__.py +24 -0
  29. agno/db/async_postgres/__init__.py +3 -0
  30. agno/db/base.py +946 -0
  31. agno/db/dynamo/__init__.py +3 -0
  32. agno/db/dynamo/dynamo.py +2781 -0
  33. agno/db/dynamo/schemas.py +442 -0
  34. agno/db/dynamo/utils.py +743 -0
  35. agno/db/firestore/__init__.py +3 -0
  36. agno/db/firestore/firestore.py +2379 -0
  37. agno/db/firestore/schemas.py +181 -0
  38. agno/db/firestore/utils.py +376 -0
  39. agno/db/gcs_json/__init__.py +3 -0
  40. agno/db/gcs_json/gcs_json_db.py +1791 -0
  41. agno/db/gcs_json/utils.py +228 -0
  42. agno/db/in_memory/__init__.py +3 -0
  43. agno/db/in_memory/in_memory_db.py +1312 -0
  44. agno/db/in_memory/utils.py +230 -0
  45. agno/db/json/__init__.py +3 -0
  46. agno/db/json/json_db.py +1777 -0
  47. agno/db/json/utils.py +230 -0
  48. agno/db/migrations/manager.py +199 -0
  49. agno/db/migrations/v1_to_v2.py +635 -0
  50. agno/db/migrations/versions/v2_3_0.py +938 -0
  51. agno/db/mongo/__init__.py +17 -0
  52. agno/db/mongo/async_mongo.py +2760 -0
  53. agno/db/mongo/mongo.py +2597 -0
  54. agno/db/mongo/schemas.py +119 -0
  55. agno/db/mongo/utils.py +276 -0
  56. agno/db/mysql/__init__.py +4 -0
  57. agno/db/mysql/async_mysql.py +2912 -0
  58. agno/db/mysql/mysql.py +2923 -0
  59. agno/db/mysql/schemas.py +186 -0
  60. agno/db/mysql/utils.py +488 -0
  61. agno/db/postgres/__init__.py +4 -0
  62. agno/db/postgres/async_postgres.py +2579 -0
  63. agno/db/postgres/postgres.py +2870 -0
  64. agno/db/postgres/schemas.py +187 -0
  65. agno/db/postgres/utils.py +442 -0
  66. agno/db/redis/__init__.py +3 -0
  67. agno/db/redis/redis.py +2141 -0
  68. agno/db/redis/schemas.py +159 -0
  69. agno/db/redis/utils.py +346 -0
  70. agno/db/schemas/__init__.py +4 -0
  71. agno/db/schemas/culture.py +120 -0
  72. agno/db/schemas/evals.py +34 -0
  73. agno/db/schemas/knowledge.py +40 -0
  74. agno/db/schemas/memory.py +61 -0
  75. agno/db/singlestore/__init__.py +3 -0
  76. agno/db/singlestore/schemas.py +179 -0
  77. agno/db/singlestore/singlestore.py +2877 -0
  78. agno/db/singlestore/utils.py +384 -0
  79. agno/db/sqlite/__init__.py +4 -0
  80. agno/db/sqlite/async_sqlite.py +2911 -0
  81. agno/db/sqlite/schemas.py +181 -0
  82. agno/db/sqlite/sqlite.py +2908 -0
  83. agno/db/sqlite/utils.py +429 -0
  84. agno/db/surrealdb/__init__.py +3 -0
  85. agno/db/surrealdb/metrics.py +292 -0
  86. agno/db/surrealdb/models.py +334 -0
  87. agno/db/surrealdb/queries.py +71 -0
  88. agno/db/surrealdb/surrealdb.py +1908 -0
  89. agno/db/surrealdb/utils.py +147 -0
  90. agno/db/utils.py +118 -0
  91. agno/eval/__init__.py +24 -0
  92. agno/eval/accuracy.py +666 -276
  93. agno/eval/agent_as_judge.py +861 -0
  94. agno/eval/base.py +29 -0
  95. agno/eval/performance.py +779 -0
  96. agno/eval/reliability.py +241 -62
  97. agno/eval/utils.py +120 -0
  98. agno/exceptions.py +143 -1
  99. agno/filters.py +354 -0
  100. agno/guardrails/__init__.py +6 -0
  101. agno/guardrails/base.py +19 -0
  102. agno/guardrails/openai.py +144 -0
  103. agno/guardrails/pii.py +94 -0
  104. agno/guardrails/prompt_injection.py +52 -0
  105. agno/hooks/__init__.py +3 -0
  106. agno/hooks/decorator.py +164 -0
  107. agno/integrations/discord/__init__.py +3 -0
  108. agno/integrations/discord/client.py +203 -0
  109. agno/knowledge/__init__.py +5 -1
  110. agno/{document → knowledge}/chunking/agentic.py +22 -14
  111. agno/{document → knowledge}/chunking/document.py +2 -2
  112. agno/{document → knowledge}/chunking/fixed.py +7 -6
  113. agno/knowledge/chunking/markdown.py +151 -0
  114. agno/{document → knowledge}/chunking/recursive.py +15 -3
  115. agno/knowledge/chunking/row.py +39 -0
  116. agno/knowledge/chunking/semantic.py +91 -0
  117. agno/knowledge/chunking/strategy.py +165 -0
  118. agno/knowledge/content.py +74 -0
  119. agno/knowledge/document/__init__.py +5 -0
  120. agno/{document → knowledge/document}/base.py +12 -2
  121. agno/knowledge/embedder/__init__.py +5 -0
  122. agno/knowledge/embedder/aws_bedrock.py +343 -0
  123. agno/knowledge/embedder/azure_openai.py +210 -0
  124. agno/{embedder → knowledge/embedder}/base.py +8 -0
  125. agno/knowledge/embedder/cohere.py +323 -0
  126. agno/knowledge/embedder/fastembed.py +62 -0
  127. agno/{embedder → knowledge/embedder}/fireworks.py +1 -1
  128. agno/knowledge/embedder/google.py +258 -0
  129. agno/knowledge/embedder/huggingface.py +94 -0
  130. agno/knowledge/embedder/jina.py +182 -0
  131. agno/knowledge/embedder/langdb.py +22 -0
  132. agno/knowledge/embedder/mistral.py +206 -0
  133. agno/knowledge/embedder/nebius.py +13 -0
  134. agno/knowledge/embedder/ollama.py +154 -0
  135. agno/knowledge/embedder/openai.py +195 -0
  136. agno/knowledge/embedder/sentence_transformer.py +63 -0
  137. agno/{embedder → knowledge/embedder}/together.py +1 -1
  138. agno/knowledge/embedder/vllm.py +262 -0
  139. agno/knowledge/embedder/voyageai.py +165 -0
  140. agno/knowledge/knowledge.py +3006 -0
  141. agno/knowledge/reader/__init__.py +7 -0
  142. agno/knowledge/reader/arxiv_reader.py +81 -0
  143. agno/knowledge/reader/base.py +95 -0
  144. agno/knowledge/reader/csv_reader.py +164 -0
  145. agno/knowledge/reader/docx_reader.py +82 -0
  146. agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
  147. agno/knowledge/reader/firecrawl_reader.py +201 -0
  148. agno/knowledge/reader/json_reader.py +88 -0
  149. agno/knowledge/reader/markdown_reader.py +137 -0
  150. agno/knowledge/reader/pdf_reader.py +431 -0
  151. agno/knowledge/reader/pptx_reader.py +101 -0
  152. agno/knowledge/reader/reader_factory.py +313 -0
  153. agno/knowledge/reader/s3_reader.py +89 -0
  154. agno/knowledge/reader/tavily_reader.py +193 -0
  155. agno/knowledge/reader/text_reader.py +127 -0
  156. agno/knowledge/reader/web_search_reader.py +325 -0
  157. agno/knowledge/reader/website_reader.py +455 -0
  158. agno/knowledge/reader/wikipedia_reader.py +91 -0
  159. agno/knowledge/reader/youtube_reader.py +78 -0
  160. agno/knowledge/remote_content/remote_content.py +88 -0
  161. agno/knowledge/reranker/__init__.py +3 -0
  162. agno/{reranker → knowledge/reranker}/base.py +1 -1
  163. agno/{reranker → knowledge/reranker}/cohere.py +2 -2
  164. agno/knowledge/reranker/infinity.py +195 -0
  165. agno/knowledge/reranker/sentence_transformer.py +54 -0
  166. agno/knowledge/types.py +39 -0
  167. agno/knowledge/utils.py +234 -0
  168. agno/media.py +439 -95
  169. agno/memory/__init__.py +16 -3
  170. agno/memory/manager.py +1474 -123
  171. agno/memory/strategies/__init__.py +15 -0
  172. agno/memory/strategies/base.py +66 -0
  173. agno/memory/strategies/summarize.py +196 -0
  174. agno/memory/strategies/types.py +37 -0
  175. agno/models/aimlapi/__init__.py +5 -0
  176. agno/models/aimlapi/aimlapi.py +62 -0
  177. agno/models/anthropic/__init__.py +4 -0
  178. agno/models/anthropic/claude.py +960 -496
  179. agno/models/aws/__init__.py +15 -0
  180. agno/models/aws/bedrock.py +686 -451
  181. agno/models/aws/claude.py +190 -183
  182. agno/models/azure/__init__.py +18 -1
  183. agno/models/azure/ai_foundry.py +489 -0
  184. agno/models/azure/openai_chat.py +89 -40
  185. agno/models/base.py +2477 -550
  186. agno/models/cerebras/__init__.py +12 -0
  187. agno/models/cerebras/cerebras.py +565 -0
  188. agno/models/cerebras/cerebras_openai.py +131 -0
  189. agno/models/cohere/__init__.py +4 -0
  190. agno/models/cohere/chat.py +306 -492
  191. agno/models/cometapi/__init__.py +5 -0
  192. agno/models/cometapi/cometapi.py +74 -0
  193. agno/models/dashscope/__init__.py +5 -0
  194. agno/models/dashscope/dashscope.py +90 -0
  195. agno/models/deepinfra/__init__.py +5 -0
  196. agno/models/deepinfra/deepinfra.py +45 -0
  197. agno/models/deepseek/__init__.py +4 -0
  198. agno/models/deepseek/deepseek.py +110 -9
  199. agno/models/fireworks/__init__.py +4 -0
  200. agno/models/fireworks/fireworks.py +19 -22
  201. agno/models/google/__init__.py +3 -7
  202. agno/models/google/gemini.py +1717 -662
  203. agno/models/google/utils.py +22 -0
  204. agno/models/groq/__init__.py +4 -0
  205. agno/models/groq/groq.py +391 -666
  206. agno/models/huggingface/__init__.py +4 -0
  207. agno/models/huggingface/huggingface.py +266 -538
  208. agno/models/ibm/__init__.py +5 -0
  209. agno/models/ibm/watsonx.py +432 -0
  210. agno/models/internlm/__init__.py +3 -0
  211. agno/models/internlm/internlm.py +20 -3
  212. agno/models/langdb/__init__.py +1 -0
  213. agno/models/langdb/langdb.py +60 -0
  214. agno/models/litellm/__init__.py +14 -0
  215. agno/models/litellm/chat.py +503 -0
  216. agno/models/litellm/litellm_openai.py +42 -0
  217. agno/models/llama_cpp/__init__.py +5 -0
  218. agno/models/llama_cpp/llama_cpp.py +22 -0
  219. agno/models/lmstudio/__init__.py +5 -0
  220. agno/models/lmstudio/lmstudio.py +25 -0
  221. agno/models/message.py +361 -39
  222. agno/models/meta/__init__.py +12 -0
  223. agno/models/meta/llama.py +502 -0
  224. agno/models/meta/llama_openai.py +79 -0
  225. agno/models/metrics.py +120 -0
  226. agno/models/mistral/__init__.py +4 -0
  227. agno/models/mistral/mistral.py +293 -393
  228. agno/models/nebius/__init__.py +3 -0
  229. agno/models/nebius/nebius.py +53 -0
  230. agno/models/nexus/__init__.py +3 -0
  231. agno/models/nexus/nexus.py +22 -0
  232. agno/models/nvidia/__init__.py +4 -0
  233. agno/models/nvidia/nvidia.py +22 -3
  234. agno/models/ollama/__init__.py +4 -2
  235. agno/models/ollama/chat.py +257 -492
  236. agno/models/openai/__init__.py +7 -0
  237. agno/models/openai/chat.py +725 -770
  238. agno/models/openai/like.py +16 -2
  239. agno/models/openai/responses.py +1121 -0
  240. agno/models/openrouter/__init__.py +4 -0
  241. agno/models/openrouter/openrouter.py +62 -5
  242. agno/models/perplexity/__init__.py +5 -0
  243. agno/models/perplexity/perplexity.py +203 -0
  244. agno/models/portkey/__init__.py +3 -0
  245. agno/models/portkey/portkey.py +82 -0
  246. agno/models/requesty/__init__.py +5 -0
  247. agno/models/requesty/requesty.py +69 -0
  248. agno/models/response.py +177 -7
  249. agno/models/sambanova/__init__.py +4 -0
  250. agno/models/sambanova/sambanova.py +23 -4
  251. agno/models/siliconflow/__init__.py +5 -0
  252. agno/models/siliconflow/siliconflow.py +42 -0
  253. agno/models/together/__init__.py +4 -0
  254. agno/models/together/together.py +21 -164
  255. agno/models/utils.py +266 -0
  256. agno/models/vercel/__init__.py +3 -0
  257. agno/models/vercel/v0.py +43 -0
  258. agno/models/vertexai/__init__.py +0 -1
  259. agno/models/vertexai/claude.py +190 -0
  260. agno/models/vllm/__init__.py +3 -0
  261. agno/models/vllm/vllm.py +83 -0
  262. agno/models/xai/__init__.py +2 -0
  263. agno/models/xai/xai.py +111 -7
  264. agno/os/__init__.py +3 -0
  265. agno/os/app.py +1027 -0
  266. agno/os/auth.py +244 -0
  267. agno/os/config.py +126 -0
  268. agno/os/interfaces/__init__.py +1 -0
  269. agno/os/interfaces/a2a/__init__.py +3 -0
  270. agno/os/interfaces/a2a/a2a.py +42 -0
  271. agno/os/interfaces/a2a/router.py +249 -0
  272. agno/os/interfaces/a2a/utils.py +924 -0
  273. agno/os/interfaces/agui/__init__.py +3 -0
  274. agno/os/interfaces/agui/agui.py +47 -0
  275. agno/os/interfaces/agui/router.py +147 -0
  276. agno/os/interfaces/agui/utils.py +574 -0
  277. agno/os/interfaces/base.py +25 -0
  278. agno/os/interfaces/slack/__init__.py +3 -0
  279. agno/os/interfaces/slack/router.py +148 -0
  280. agno/os/interfaces/slack/security.py +30 -0
  281. agno/os/interfaces/slack/slack.py +47 -0
  282. agno/os/interfaces/whatsapp/__init__.py +3 -0
  283. agno/os/interfaces/whatsapp/router.py +210 -0
  284. agno/os/interfaces/whatsapp/security.py +55 -0
  285. agno/os/interfaces/whatsapp/whatsapp.py +36 -0
  286. agno/os/mcp.py +293 -0
  287. agno/os/middleware/__init__.py +9 -0
  288. agno/os/middleware/jwt.py +797 -0
  289. agno/os/router.py +258 -0
  290. agno/os/routers/__init__.py +3 -0
  291. agno/os/routers/agents/__init__.py +3 -0
  292. agno/os/routers/agents/router.py +599 -0
  293. agno/os/routers/agents/schema.py +261 -0
  294. agno/os/routers/evals/__init__.py +3 -0
  295. agno/os/routers/evals/evals.py +450 -0
  296. agno/os/routers/evals/schemas.py +174 -0
  297. agno/os/routers/evals/utils.py +231 -0
  298. agno/os/routers/health.py +31 -0
  299. agno/os/routers/home.py +52 -0
  300. agno/os/routers/knowledge/__init__.py +3 -0
  301. agno/os/routers/knowledge/knowledge.py +1008 -0
  302. agno/os/routers/knowledge/schemas.py +178 -0
  303. agno/os/routers/memory/__init__.py +3 -0
  304. agno/os/routers/memory/memory.py +661 -0
  305. agno/os/routers/memory/schemas.py +88 -0
  306. agno/os/routers/metrics/__init__.py +3 -0
  307. agno/os/routers/metrics/metrics.py +190 -0
  308. agno/os/routers/metrics/schemas.py +47 -0
  309. agno/os/routers/session/__init__.py +3 -0
  310. agno/os/routers/session/session.py +997 -0
  311. agno/os/routers/teams/__init__.py +3 -0
  312. agno/os/routers/teams/router.py +512 -0
  313. agno/os/routers/teams/schema.py +257 -0
  314. agno/os/routers/traces/__init__.py +3 -0
  315. agno/os/routers/traces/schemas.py +414 -0
  316. agno/os/routers/traces/traces.py +499 -0
  317. agno/os/routers/workflows/__init__.py +3 -0
  318. agno/os/routers/workflows/router.py +624 -0
  319. agno/os/routers/workflows/schema.py +75 -0
  320. agno/os/schema.py +534 -0
  321. agno/os/scopes.py +469 -0
  322. agno/{playground → os}/settings.py +7 -15
  323. agno/os/utils.py +973 -0
  324. agno/reasoning/anthropic.py +80 -0
  325. agno/reasoning/azure_ai_foundry.py +67 -0
  326. agno/reasoning/deepseek.py +63 -0
  327. agno/reasoning/default.py +97 -0
  328. agno/reasoning/gemini.py +73 -0
  329. agno/reasoning/groq.py +71 -0
  330. agno/reasoning/helpers.py +24 -1
  331. agno/reasoning/ollama.py +67 -0
  332. agno/reasoning/openai.py +86 -0
  333. agno/reasoning/step.py +2 -1
  334. agno/reasoning/vertexai.py +76 -0
  335. agno/run/__init__.py +6 -0
  336. agno/run/agent.py +822 -0
  337. agno/run/base.py +247 -0
  338. agno/run/cancel.py +81 -0
  339. agno/run/requirement.py +181 -0
  340. agno/run/team.py +767 -0
  341. agno/run/workflow.py +708 -0
  342. agno/session/__init__.py +10 -0
  343. agno/session/agent.py +260 -0
  344. agno/session/summary.py +265 -0
  345. agno/session/team.py +342 -0
  346. agno/session/workflow.py +501 -0
  347. agno/table.py +10 -0
  348. agno/team/__init__.py +37 -0
  349. agno/team/team.py +9536 -0
  350. agno/tools/__init__.py +7 -0
  351. agno/tools/agentql.py +120 -0
  352. agno/tools/airflow.py +22 -12
  353. agno/tools/api.py +122 -0
  354. agno/tools/apify.py +276 -83
  355. agno/tools/{arxiv_toolkit.py → arxiv.py} +20 -12
  356. agno/tools/aws_lambda.py +28 -7
  357. agno/tools/aws_ses.py +66 -0
  358. agno/tools/baidusearch.py +11 -4
  359. agno/tools/bitbucket.py +292 -0
  360. agno/tools/brandfetch.py +213 -0
  361. agno/tools/bravesearch.py +106 -0
  362. agno/tools/brightdata.py +367 -0
  363. agno/tools/browserbase.py +209 -0
  364. agno/tools/calcom.py +32 -23
  365. agno/tools/calculator.py +24 -37
  366. agno/tools/cartesia.py +187 -0
  367. agno/tools/{clickup_tool.py → clickup.py} +17 -28
  368. agno/tools/confluence.py +91 -26
  369. agno/tools/crawl4ai.py +139 -43
  370. agno/tools/csv_toolkit.py +28 -22
  371. agno/tools/dalle.py +36 -22
  372. agno/tools/daytona.py +475 -0
  373. agno/tools/decorator.py +169 -14
  374. agno/tools/desi_vocal.py +23 -11
  375. agno/tools/discord.py +32 -29
  376. agno/tools/docker.py +716 -0
  377. agno/tools/duckdb.py +76 -81
  378. agno/tools/duckduckgo.py +43 -40
  379. agno/tools/e2b.py +703 -0
  380. agno/tools/eleven_labs.py +65 -54
  381. agno/tools/email.py +13 -5
  382. agno/tools/evm.py +129 -0
  383. agno/tools/exa.py +324 -42
  384. agno/tools/fal.py +39 -35
  385. agno/tools/file.py +196 -30
  386. agno/tools/file_generation.py +356 -0
  387. agno/tools/financial_datasets.py +288 -0
  388. agno/tools/firecrawl.py +108 -33
  389. agno/tools/function.py +960 -122
  390. agno/tools/giphy.py +34 -12
  391. agno/tools/github.py +1294 -97
  392. agno/tools/gmail.py +922 -0
  393. agno/tools/google_bigquery.py +117 -0
  394. agno/tools/google_drive.py +271 -0
  395. agno/tools/google_maps.py +253 -0
  396. agno/tools/googlecalendar.py +607 -107
  397. agno/tools/googlesheets.py +377 -0
  398. agno/tools/hackernews.py +20 -12
  399. agno/tools/jina.py +24 -14
  400. agno/tools/jira.py +48 -19
  401. agno/tools/knowledge.py +218 -0
  402. agno/tools/linear.py +82 -43
  403. agno/tools/linkup.py +58 -0
  404. agno/tools/local_file_system.py +15 -7
  405. agno/tools/lumalab.py +41 -26
  406. agno/tools/mcp/__init__.py +10 -0
  407. agno/tools/mcp/mcp.py +331 -0
  408. agno/tools/mcp/multi_mcp.py +347 -0
  409. agno/tools/mcp/params.py +24 -0
  410. agno/tools/mcp_toolbox.py +284 -0
  411. agno/tools/mem0.py +193 -0
  412. agno/tools/memory.py +419 -0
  413. agno/tools/mlx_transcribe.py +11 -9
  414. agno/tools/models/azure_openai.py +190 -0
  415. agno/tools/models/gemini.py +203 -0
  416. agno/tools/models/groq.py +158 -0
  417. agno/tools/models/morph.py +186 -0
  418. agno/tools/models/nebius.py +124 -0
  419. agno/tools/models_labs.py +163 -82
  420. agno/tools/moviepy_video.py +18 -13
  421. agno/tools/nano_banana.py +151 -0
  422. agno/tools/neo4j.py +134 -0
  423. agno/tools/newspaper.py +15 -4
  424. agno/tools/newspaper4k.py +19 -6
  425. agno/tools/notion.py +204 -0
  426. agno/tools/openai.py +181 -17
  427. agno/tools/openbb.py +27 -20
  428. agno/tools/opencv.py +321 -0
  429. agno/tools/openweather.py +233 -0
  430. agno/tools/oxylabs.py +385 -0
  431. agno/tools/pandas.py +25 -15
  432. agno/tools/parallel.py +314 -0
  433. agno/tools/postgres.py +238 -185
  434. agno/tools/pubmed.py +125 -13
  435. agno/tools/python.py +48 -35
  436. agno/tools/reasoning.py +283 -0
  437. agno/tools/reddit.py +207 -29
  438. agno/tools/redshift.py +406 -0
  439. agno/tools/replicate.py +69 -26
  440. agno/tools/resend.py +11 -6
  441. agno/tools/scrapegraph.py +179 -19
  442. agno/tools/searxng.py +23 -31
  443. agno/tools/serpapi.py +15 -10
  444. agno/tools/serper.py +255 -0
  445. agno/tools/shell.py +23 -12
  446. agno/tools/shopify.py +1519 -0
  447. agno/tools/slack.py +56 -14
  448. agno/tools/sleep.py +8 -6
  449. agno/tools/spider.py +35 -11
  450. agno/tools/spotify.py +919 -0
  451. agno/tools/sql.py +34 -19
  452. agno/tools/tavily.py +158 -8
  453. agno/tools/telegram.py +18 -8
  454. agno/tools/todoist.py +218 -0
  455. agno/tools/toolkit.py +134 -9
  456. agno/tools/trafilatura.py +388 -0
  457. agno/tools/trello.py +25 -28
  458. agno/tools/twilio.py +18 -9
  459. agno/tools/user_control_flow.py +78 -0
  460. agno/tools/valyu.py +228 -0
  461. agno/tools/visualization.py +467 -0
  462. agno/tools/webbrowser.py +28 -0
  463. agno/tools/webex.py +76 -0
  464. agno/tools/website.py +23 -19
  465. agno/tools/webtools.py +45 -0
  466. agno/tools/whatsapp.py +286 -0
  467. agno/tools/wikipedia.py +28 -19
  468. agno/tools/workflow.py +285 -0
  469. agno/tools/{twitter.py → x.py} +142 -46
  470. agno/tools/yfinance.py +41 -39
  471. agno/tools/youtube.py +34 -17
  472. agno/tools/zendesk.py +15 -5
  473. agno/tools/zep.py +454 -0
  474. agno/tools/zoom.py +86 -37
  475. agno/tracing/__init__.py +12 -0
  476. agno/tracing/exporter.py +157 -0
  477. agno/tracing/schemas.py +276 -0
  478. agno/tracing/setup.py +111 -0
  479. agno/utils/agent.py +938 -0
  480. agno/utils/audio.py +37 -1
  481. agno/utils/certs.py +27 -0
  482. agno/utils/code_execution.py +11 -0
  483. agno/utils/common.py +103 -20
  484. agno/utils/cryptography.py +22 -0
  485. agno/utils/dttm.py +33 -0
  486. agno/utils/events.py +700 -0
  487. agno/utils/functions.py +107 -37
  488. agno/utils/gemini.py +426 -0
  489. agno/utils/hooks.py +171 -0
  490. agno/utils/http.py +185 -0
  491. agno/utils/json_schema.py +159 -37
  492. agno/utils/knowledge.py +36 -0
  493. agno/utils/location.py +19 -0
  494. agno/utils/log.py +221 -8
  495. agno/utils/mcp.py +214 -0
  496. agno/utils/media.py +335 -14
  497. agno/utils/merge_dict.py +22 -1
  498. agno/utils/message.py +77 -2
  499. agno/utils/models/ai_foundry.py +50 -0
  500. agno/utils/models/claude.py +373 -0
  501. agno/utils/models/cohere.py +94 -0
  502. agno/utils/models/llama.py +85 -0
  503. agno/utils/models/mistral.py +100 -0
  504. agno/utils/models/openai_responses.py +140 -0
  505. agno/utils/models/schema_utils.py +153 -0
  506. agno/utils/models/watsonx.py +41 -0
  507. agno/utils/openai.py +257 -0
  508. agno/utils/pickle.py +1 -1
  509. agno/utils/pprint.py +124 -8
  510. agno/utils/print_response/agent.py +930 -0
  511. agno/utils/print_response/team.py +1914 -0
  512. agno/utils/print_response/workflow.py +1668 -0
  513. agno/utils/prompts.py +111 -0
  514. agno/utils/reasoning.py +108 -0
  515. agno/utils/response.py +163 -0
  516. agno/utils/serialize.py +32 -0
  517. agno/utils/shell.py +4 -4
  518. agno/utils/streamlit.py +487 -0
  519. agno/utils/string.py +204 -51
  520. agno/utils/team.py +139 -0
  521. agno/utils/timer.py +9 -2
  522. agno/utils/tokens.py +657 -0
  523. agno/utils/tools.py +19 -1
  524. agno/utils/whatsapp.py +305 -0
  525. agno/utils/yaml_io.py +3 -3
  526. agno/vectordb/__init__.py +2 -0
  527. agno/vectordb/base.py +87 -9
  528. agno/vectordb/cassandra/__init__.py +5 -1
  529. agno/vectordb/cassandra/cassandra.py +383 -27
  530. agno/vectordb/chroma/__init__.py +4 -0
  531. agno/vectordb/chroma/chromadb.py +748 -83
  532. agno/vectordb/clickhouse/__init__.py +7 -1
  533. agno/vectordb/clickhouse/clickhousedb.py +554 -53
  534. agno/vectordb/couchbase/__init__.py +3 -0
  535. agno/vectordb/couchbase/couchbase.py +1446 -0
  536. agno/vectordb/lancedb/__init__.py +5 -0
  537. agno/vectordb/lancedb/lance_db.py +730 -98
  538. agno/vectordb/langchaindb/__init__.py +5 -0
  539. agno/vectordb/langchaindb/langchaindb.py +163 -0
  540. agno/vectordb/lightrag/__init__.py +5 -0
  541. agno/vectordb/lightrag/lightrag.py +388 -0
  542. agno/vectordb/llamaindex/__init__.py +3 -0
  543. agno/vectordb/llamaindex/llamaindexdb.py +166 -0
  544. agno/vectordb/milvus/__init__.py +3 -0
  545. agno/vectordb/milvus/milvus.py +966 -78
  546. agno/vectordb/mongodb/__init__.py +9 -1
  547. agno/vectordb/mongodb/mongodb.py +1175 -172
  548. agno/vectordb/pgvector/__init__.py +8 -0
  549. agno/vectordb/pgvector/pgvector.py +599 -115
  550. agno/vectordb/pineconedb/__init__.py +5 -1
  551. agno/vectordb/pineconedb/pineconedb.py +406 -43
  552. agno/vectordb/qdrant/__init__.py +4 -0
  553. agno/vectordb/qdrant/qdrant.py +914 -61
  554. agno/vectordb/redis/__init__.py +9 -0
  555. agno/vectordb/redis/redisdb.py +682 -0
  556. agno/vectordb/singlestore/__init__.py +8 -1
  557. agno/vectordb/singlestore/singlestore.py +771 -0
  558. agno/vectordb/surrealdb/__init__.py +3 -0
  559. agno/vectordb/surrealdb/surrealdb.py +663 -0
  560. agno/vectordb/upstashdb/__init__.py +5 -0
  561. agno/vectordb/upstashdb/upstashdb.py +718 -0
  562. agno/vectordb/weaviate/__init__.py +8 -0
  563. agno/vectordb/weaviate/index.py +15 -0
  564. agno/vectordb/weaviate/weaviate.py +1009 -0
  565. agno/workflow/__init__.py +23 -1
  566. agno/workflow/agent.py +299 -0
  567. agno/workflow/condition.py +759 -0
  568. agno/workflow/loop.py +756 -0
  569. agno/workflow/parallel.py +853 -0
  570. agno/workflow/router.py +723 -0
  571. agno/workflow/step.py +1564 -0
  572. agno/workflow/steps.py +613 -0
  573. agno/workflow/types.py +556 -0
  574. agno/workflow/workflow.py +4327 -514
  575. agno-2.3.13.dist-info/METADATA +639 -0
  576. agno-2.3.13.dist-info/RECORD +613 -0
  577. {agno-0.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +1 -1
  578. agno-2.3.13.dist-info/licenses/LICENSE +201 -0
  579. agno/api/playground.py +0 -91
  580. agno/api/schemas/playground.py +0 -22
  581. agno/api/schemas/user.py +0 -22
  582. agno/api/schemas/workspace.py +0 -46
  583. agno/api/user.py +0 -160
  584. agno/api/workspace.py +0 -151
  585. agno/cli/auth_server.py +0 -118
  586. agno/cli/config.py +0 -275
  587. agno/cli/console.py +0 -88
  588. agno/cli/credentials.py +0 -23
  589. agno/cli/entrypoint.py +0 -571
  590. agno/cli/operator.py +0 -355
  591. agno/cli/settings.py +0 -85
  592. agno/cli/ws/ws_cli.py +0 -817
  593. agno/constants.py +0 -13
  594. agno/document/__init__.py +0 -1
  595. agno/document/chunking/semantic.py +0 -47
  596. agno/document/chunking/strategy.py +0 -31
  597. agno/document/reader/__init__.py +0 -1
  598. agno/document/reader/arxiv_reader.py +0 -41
  599. agno/document/reader/base.py +0 -22
  600. agno/document/reader/csv_reader.py +0 -84
  601. agno/document/reader/docx_reader.py +0 -46
  602. agno/document/reader/firecrawl_reader.py +0 -99
  603. agno/document/reader/json_reader.py +0 -43
  604. agno/document/reader/pdf_reader.py +0 -219
  605. agno/document/reader/s3/pdf_reader.py +0 -46
  606. agno/document/reader/s3/text_reader.py +0 -51
  607. agno/document/reader/text_reader.py +0 -41
  608. agno/document/reader/website_reader.py +0 -175
  609. agno/document/reader/youtube_reader.py +0 -50
  610. agno/embedder/__init__.py +0 -1
  611. agno/embedder/azure_openai.py +0 -86
  612. agno/embedder/cohere.py +0 -72
  613. agno/embedder/fastembed.py +0 -37
  614. agno/embedder/google.py +0 -73
  615. agno/embedder/huggingface.py +0 -54
  616. agno/embedder/mistral.py +0 -80
  617. agno/embedder/ollama.py +0 -57
  618. agno/embedder/openai.py +0 -74
  619. agno/embedder/sentence_transformer.py +0 -38
  620. agno/embedder/voyageai.py +0 -64
  621. agno/eval/perf.py +0 -201
  622. agno/file/__init__.py +0 -1
  623. agno/file/file.py +0 -16
  624. agno/file/local/csv.py +0 -32
  625. agno/file/local/txt.py +0 -19
  626. agno/infra/app.py +0 -240
  627. agno/infra/base.py +0 -144
  628. agno/infra/context.py +0 -20
  629. agno/infra/db_app.py +0 -52
  630. agno/infra/resource.py +0 -205
  631. agno/infra/resources.py +0 -55
  632. agno/knowledge/agent.py +0 -230
  633. agno/knowledge/arxiv.py +0 -22
  634. agno/knowledge/combined.py +0 -22
  635. agno/knowledge/csv.py +0 -28
  636. agno/knowledge/csv_url.py +0 -19
  637. agno/knowledge/document.py +0 -20
  638. agno/knowledge/docx.py +0 -30
  639. agno/knowledge/json.py +0 -28
  640. agno/knowledge/langchain.py +0 -71
  641. agno/knowledge/llamaindex.py +0 -66
  642. agno/knowledge/pdf.py +0 -28
  643. agno/knowledge/pdf_url.py +0 -26
  644. agno/knowledge/s3/base.py +0 -60
  645. agno/knowledge/s3/pdf.py +0 -21
  646. agno/knowledge/s3/text.py +0 -23
  647. agno/knowledge/text.py +0 -30
  648. agno/knowledge/website.py +0 -88
  649. agno/knowledge/wikipedia.py +0 -31
  650. agno/knowledge/youtube.py +0 -22
  651. agno/memory/agent.py +0 -392
  652. agno/memory/classifier.py +0 -104
  653. agno/memory/db/__init__.py +0 -1
  654. agno/memory/db/base.py +0 -42
  655. agno/memory/db/mongodb.py +0 -189
  656. agno/memory/db/postgres.py +0 -203
  657. agno/memory/db/sqlite.py +0 -193
  658. agno/memory/memory.py +0 -15
  659. agno/memory/row.py +0 -36
  660. agno/memory/summarizer.py +0 -192
  661. agno/memory/summary.py +0 -19
  662. agno/memory/workflow.py +0 -38
  663. agno/models/google/gemini_openai.py +0 -26
  664. agno/models/ollama/hermes.py +0 -221
  665. agno/models/ollama/tools.py +0 -362
  666. agno/models/vertexai/gemini.py +0 -595
  667. agno/playground/__init__.py +0 -3
  668. agno/playground/async_router.py +0 -421
  669. agno/playground/deploy.py +0 -249
  670. agno/playground/operator.py +0 -92
  671. agno/playground/playground.py +0 -91
  672. agno/playground/schemas.py +0 -76
  673. agno/playground/serve.py +0 -55
  674. agno/playground/sync_router.py +0 -405
  675. agno/reasoning/agent.py +0 -68
  676. agno/run/response.py +0 -112
  677. agno/storage/agent/__init__.py +0 -0
  678. agno/storage/agent/base.py +0 -38
  679. agno/storage/agent/dynamodb.py +0 -350
  680. agno/storage/agent/json.py +0 -92
  681. agno/storage/agent/mongodb.py +0 -228
  682. agno/storage/agent/postgres.py +0 -367
  683. agno/storage/agent/session.py +0 -79
  684. agno/storage/agent/singlestore.py +0 -303
  685. agno/storage/agent/sqlite.py +0 -357
  686. agno/storage/agent/yaml.py +0 -93
  687. agno/storage/workflow/__init__.py +0 -0
  688. agno/storage/workflow/base.py +0 -40
  689. agno/storage/workflow/mongodb.py +0 -233
  690. agno/storage/workflow/postgres.py +0 -366
  691. agno/storage/workflow/session.py +0 -60
  692. agno/storage/workflow/sqlite.py +0 -359
  693. agno/tools/googlesearch.py +0 -88
  694. agno/utils/defaults.py +0 -57
  695. agno/utils/filesystem.py +0 -39
  696. agno/utils/git.py +0 -52
  697. agno/utils/json_io.py +0 -30
  698. agno/utils/load_env.py +0 -19
  699. agno/utils/py_io.py +0 -19
  700. agno/utils/pyproject.py +0 -18
  701. agno/utils/resource_filter.py +0 -31
  702. agno/vectordb/singlestore/s2vectordb.py +0 -390
  703. agno/vectordb/singlestore/s2vectordb2.py +0 -355
  704. agno/workspace/__init__.py +0 -0
  705. agno/workspace/config.py +0 -325
  706. agno/workspace/enums.py +0 -6
  707. agno/workspace/helpers.py +0 -48
  708. agno/workspace/operator.py +0 -758
  709. agno/workspace/settings.py +0 -63
  710. agno-0.1.2.dist-info/LICENSE +0 -375
  711. agno-0.1.2.dist-info/METADATA +0 -502
  712. agno-0.1.2.dist-info/RECORD +0 -352
  713. agno-0.1.2.dist-info/entry_points.txt +0 -3
  714. /agno/{cli → db/migrations}/__init__.py +0 -0
  715. /agno/{cli/ws → db/migrations/versions}/__init__.py +0 -0
  716. /agno/{document/chunking/__init__.py → db/schemas/metrics.py} +0 -0
  717. /agno/{document/reader/s3 → integrations}/__init__.py +0 -0
  718. /agno/{file/local → knowledge/chunking}/__init__.py +0 -0
  719. /agno/{infra → knowledge/remote_content}/__init__.py +0 -0
  720. /agno/{knowledge/s3 → tools/models}/__init__.py +0 -0
  721. /agno/{reranker → utils/models}/__init__.py +0 -0
  722. /agno/{storage → utils/print_response}/__init__.py +0 -0
  723. {agno-0.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1121 @@
1
+ import time
2
+ from dataclasses import dataclass, field
3
+ from typing import Any, AsyncIterator, Dict, Iterator, List, Optional, Tuple, Type, Union
4
+
5
+ import httpx
6
+ from pydantic import BaseModel
7
+ from typing_extensions import Literal
8
+
9
+ from agno.exceptions import ModelAuthenticationError, ModelProviderError
10
+ from agno.media import File
11
+ from agno.models.base import Model
12
+ from agno.models.message import Citations, Message, UrlCitation
13
+ from agno.models.metrics import Metrics
14
+ from agno.models.response import ModelResponse
15
+ from agno.run.agent import RunOutput
16
+ from agno.utils.http import get_default_async_client, get_default_sync_client
17
+ from agno.utils.log import log_debug, log_error, log_warning
18
+ from agno.utils.models.openai_responses import images_to_message
19
+ from agno.utils.models.schema_utils import get_response_schema_for_provider
20
+ from agno.utils.tokens import count_schema_tokens
21
+
22
+ try:
23
+ from openai import APIConnectionError, APIStatusError, AsyncOpenAI, OpenAI, RateLimitError
24
+ from openai.types.responses import Response, ResponseReasoningItem, ResponseStreamEvent, ResponseUsage
25
+ except ImportError as e:
26
+ raise ImportError("`openai` not installed. Please install using `pip install openai -U`") from e
27
+
28
+
29
+ @dataclass
30
+ class OpenAIResponses(Model):
31
+ """
32
+ A class for interacting with OpenAI models using the Responses API.
33
+
34
+ For more information, see: https://platform.openai.com/docs/api-reference/responses
35
+ """
36
+
37
+ id: str = "gpt-4o"
38
+ name: str = "OpenAIResponses"
39
+ provider: str = "OpenAI"
40
+ supports_native_structured_outputs: bool = True
41
+
42
+ # Request parameters
43
+ include: Optional[List[str]] = None
44
+ max_output_tokens: Optional[int] = None
45
+ max_tool_calls: Optional[int] = None
46
+ metadata: Optional[Dict[str, Any]] = None
47
+ parallel_tool_calls: Optional[bool] = None
48
+ reasoning: Optional[Dict[str, Any]] = None
49
+ verbosity: Optional[Literal["low", "medium", "high"]] = None
50
+ reasoning_effort: Optional[Literal["minimal", "low", "medium", "high"]] = None
51
+ reasoning_summary: Optional[Literal["auto", "concise", "detailed"]] = None
52
+ store: Optional[bool] = None
53
+ temperature: Optional[float] = None
54
+ top_p: Optional[float] = None
55
+ truncation: Optional[Literal["auto", "disabled"]] = None
56
+ user: Optional[str] = None
57
+ service_tier: Optional[Literal["auto", "default", "flex", "priority"]] = None
58
+ 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
59
+ extra_headers: Optional[Any] = None
60
+ extra_query: Optional[Any] = None
61
+ extra_body: Optional[Any] = None
62
+ request_params: Optional[Dict[str, Any]] = None
63
+
64
+ # Client parameters
65
+ api_key: Optional[str] = None
66
+ organization: Optional[str] = None
67
+ base_url: Optional[Union[str, httpx.URL]] = None
68
+ timeout: Optional[float] = None
69
+ max_retries: Optional[int] = None
70
+ default_headers: Optional[Dict[str, str]] = None
71
+ default_query: Optional[Dict[str, str]] = None
72
+ http_client: Optional[Union[httpx.Client, httpx.AsyncClient]] = None
73
+ client_params: Optional[Dict[str, Any]] = None
74
+
75
+ # Parameters affecting built-in tools
76
+ vector_store_name: str = "knowledge_base"
77
+
78
+ # OpenAI clients
79
+ client: Optional[OpenAI] = None
80
+ async_client: Optional[AsyncOpenAI] = None
81
+
82
+ # The role to map the message role to.
83
+ role_map: Dict[str, str] = field(
84
+ default_factory=lambda: {
85
+ "system": "developer",
86
+ "user": "user",
87
+ "assistant": "assistant",
88
+ "tool": "tool",
89
+ }
90
+ )
91
+
92
+ def _using_reasoning_model(self) -> bool:
93
+ """Return True if the contextual used model is a known reasoning model."""
94
+ return self.id.startswith("o3") or self.id.startswith("o4-mini") or self.id.startswith("gpt-5")
95
+
96
+ def _set_reasoning_request_param(self, base_params: Dict[str, Any]) -> Dict[str, Any]:
97
+ """Set the reasoning request parameter."""
98
+ base_params["reasoning"] = self.reasoning or {}
99
+
100
+ if self.reasoning_effort is not None:
101
+ base_params["reasoning"]["effort"] = self.reasoning_effort
102
+
103
+ if self.reasoning_summary is not None:
104
+ base_params["reasoning"]["summary"] = self.reasoning_summary
105
+
106
+ return base_params
107
+
108
+ def _get_client_params(self) -> Dict[str, Any]:
109
+ """
110
+ Get client parameters for API requests.
111
+
112
+ Returns:
113
+ Dict[str, Any]: Client parameters
114
+ """
115
+ from os import getenv
116
+
117
+ # Fetch API key from env if not already set
118
+ if not self.api_key:
119
+ self.api_key = getenv("OPENAI_API_KEY")
120
+ if not self.api_key:
121
+ raise ModelAuthenticationError(
122
+ message="OPENAI_API_KEY not set. Please set the OPENAI_API_KEY environment variable.",
123
+ model_name=self.name,
124
+ )
125
+
126
+ # Define base client params
127
+ base_params = {
128
+ "api_key": self.api_key,
129
+ "organization": self.organization,
130
+ "base_url": self.base_url,
131
+ "timeout": self.timeout,
132
+ "max_retries": self.max_retries,
133
+ "default_headers": self.default_headers,
134
+ "default_query": self.default_query,
135
+ }
136
+
137
+ # Create client_params dict with non-None values
138
+ client_params = {k: v for k, v in base_params.items() if v is not None}
139
+
140
+ # Add additional client params if provided
141
+ if self.client_params:
142
+ client_params.update(self.client_params)
143
+
144
+ return client_params
145
+
146
+ def get_client(self) -> OpenAI:
147
+ """
148
+ Returns an OpenAI client. Caches the client to avoid recreating it on every request.
149
+
150
+ Returns:
151
+ OpenAI: An instance of the OpenAI client.
152
+ """
153
+ if self.client and not self.client.is_closed():
154
+ return self.client
155
+
156
+ client_params: Dict[str, Any] = self._get_client_params()
157
+ if self.http_client is not None:
158
+ client_params["http_client"] = self.http_client
159
+ else:
160
+ # Use global sync client when no custom http_client is provided
161
+ client_params["http_client"] = get_default_sync_client()
162
+
163
+ self.client = OpenAI(**client_params)
164
+ return self.client
165
+
166
+ def get_async_client(self) -> AsyncOpenAI:
167
+ """
168
+ Returns an asynchronous OpenAI client. Caches the client to avoid recreating it on every request.
169
+
170
+ Returns:
171
+ AsyncOpenAI: An instance of the asynchronous OpenAI client.
172
+ """
173
+ if self.async_client and not self.async_client.is_closed():
174
+ return self.async_client
175
+
176
+ client_params: Dict[str, Any] = self._get_client_params()
177
+ if self.http_client and isinstance(self.http_client, httpx.AsyncClient):
178
+ client_params["http_client"] = self.http_client
179
+ else:
180
+ # Use global async client when no custom http_client is provided
181
+ client_params["http_client"] = get_default_async_client()
182
+
183
+ self.async_client = AsyncOpenAI(**client_params)
184
+ return self.async_client
185
+
186
+ def get_request_params(
187
+ self,
188
+ messages: Optional[List[Message]] = None,
189
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
190
+ tools: Optional[List[Dict[str, Any]]] = None,
191
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
192
+ ) -> Dict[str, Any]:
193
+ """
194
+ Returns keyword arguments for API requests.
195
+
196
+ Returns:
197
+ Dict[str, Any]: A dictionary of keyword arguments for API requests.
198
+ """
199
+ # Define base request parameters
200
+ base_params: Dict[str, Any] = {
201
+ "include": self.include,
202
+ "max_output_tokens": self.max_output_tokens,
203
+ "max_tool_calls": self.max_tool_calls,
204
+ "metadata": self.metadata,
205
+ "parallel_tool_calls": self.parallel_tool_calls,
206
+ "store": self.store,
207
+ "temperature": self.temperature,
208
+ "top_p": self.top_p,
209
+ "truncation": self.truncation,
210
+ "user": self.user,
211
+ "service_tier": self.service_tier,
212
+ "extra_headers": self.extra_headers,
213
+ "extra_query": self.extra_query,
214
+ "extra_body": self.extra_body,
215
+ }
216
+ # Populate the reasoning parameter
217
+ base_params = self._set_reasoning_request_param(base_params)
218
+
219
+ # Build text parameter
220
+ text_params: Dict[str, Any] = {}
221
+
222
+ # Add verbosity if specified
223
+ if self.verbosity is not None:
224
+ text_params["verbosity"] = self.verbosity
225
+
226
+ # Set the response format
227
+ if response_format is not None:
228
+ if isinstance(response_format, type) and issubclass(response_format, BaseModel):
229
+ schema = get_response_schema_for_provider(response_format, "openai")
230
+ text_params["format"] = {
231
+ "type": "json_schema",
232
+ "name": response_format.__name__,
233
+ "schema": schema,
234
+ "strict": self.strict_output,
235
+ }
236
+ else:
237
+ # JSON mode
238
+ text_params["format"] = {"type": "json_object"}
239
+
240
+ # Add text parameter if there are any text-level params
241
+ if text_params:
242
+ base_params["text"] = text_params
243
+
244
+ # Filter out None values
245
+ request_params: Dict[str, Any] = {k: v for k, v in base_params.items() if v is not None}
246
+
247
+ # Deep research models require web_search_preview tool or MCP tool
248
+ if "deep-research" in self.id:
249
+ if tools is None:
250
+ tools = []
251
+
252
+ # Check if web_search_preview tool is already present
253
+ has_web_search = any(tool.get("type") == "web_search_preview" for tool in tools)
254
+
255
+ # Add web_search_preview if not present - this enables the model to search
256
+ # the web for current information and provide citations
257
+ if not has_web_search:
258
+ web_search_tool = {"type": "web_search_preview"}
259
+ tools.insert(0, web_search_tool)
260
+ log_debug(f"Added web_search_preview tool for deep research model: {self.id}")
261
+
262
+ if tools:
263
+ request_params["tools"] = self._format_tool_params(messages=messages, tools=tools) # type: ignore
264
+
265
+ if tool_choice is not None:
266
+ request_params["tool_choice"] = tool_choice
267
+
268
+ # Handle reasoning tools for o3 and o4-mini models
269
+ if self._using_reasoning_model() and messages is not None:
270
+ if self.store is False:
271
+ request_params["store"] = False
272
+
273
+ # Add encrypted reasoning content to include if not already present
274
+ include_list = request_params.get("include", []) or []
275
+ if "reasoning.encrypted_content" not in include_list:
276
+ include_list.append("reasoning.encrypted_content")
277
+ if request_params.get("include") is None:
278
+ request_params["include"] = include_list
279
+ elif isinstance(request_params["include"], list):
280
+ request_params["include"].extend(include_list)
281
+
282
+ else:
283
+ request_params["store"] = True
284
+
285
+ # Check if the last assistant message has a previous_response_id to continue from
286
+ previous_response_id = None
287
+ for msg in reversed(messages):
288
+ if (
289
+ msg.role == "assistant"
290
+ and hasattr(msg, "provider_data")
291
+ and msg.provider_data
292
+ and "response_id" in msg.provider_data
293
+ ):
294
+ previous_response_id = msg.provider_data["response_id"]
295
+ log_debug(f"Using previous_response_id: {previous_response_id}")
296
+ break
297
+
298
+ if previous_response_id:
299
+ request_params["previous_response_id"] = previous_response_id
300
+
301
+ # Add additional request params if provided
302
+ if self.request_params:
303
+ request_params.update(self.request_params)
304
+
305
+ if request_params:
306
+ log_debug(f"Calling {self.provider} with request parameters: {request_params}", log_level=2)
307
+ return request_params
308
+
309
+ def _upload_file(self, file: File) -> Optional[str]:
310
+ """Upload a file to the OpenAI vector database."""
311
+ from pathlib import Path
312
+ from urllib.parse import urlparse
313
+
314
+ if file.url is not None:
315
+ file_content_tuple = file.file_url_content
316
+ if file_content_tuple is not None:
317
+ file_content = file_content_tuple[0]
318
+ else:
319
+ return None
320
+ file_name = Path(urlparse(file.url).path).name or "file"
321
+ file_tuple = (file_name, file_content)
322
+ result = self.get_client().files.create(file=file_tuple, purpose="assistants")
323
+ return result.id
324
+ elif file.filepath is not None:
325
+ import mimetypes
326
+
327
+ file_path = file.filepath if isinstance(file.filepath, Path) else Path(file.filepath)
328
+ if file_path.exists() and file_path.is_file():
329
+ file_name = file_path.name
330
+ file_content = file_path.read_bytes() # type: ignore
331
+ content_type = mimetypes.guess_type(file_path)[0]
332
+ result = self.get_client().files.create(
333
+ file=(file_name, file_content, content_type),
334
+ purpose="assistants", # type: ignore
335
+ )
336
+ return result.id
337
+ else:
338
+ raise ValueError(f"File not found: {file_path}")
339
+ elif file.content is not None:
340
+ result = self.get_client().files.create(file=file.content, purpose="assistants")
341
+ return result.id
342
+
343
+ return None
344
+
345
+ def _create_vector_store(self, file_ids: List[str]) -> str:
346
+ """Create a vector store for the files."""
347
+ vector_store = self.get_client().vector_stores.create(name=self.vector_store_name)
348
+ for file_id in file_ids:
349
+ self.get_client().vector_stores.files.create(vector_store_id=vector_store.id, file_id=file_id)
350
+ while True:
351
+ uploaded_files = self.get_client().vector_stores.files.list(vector_store_id=vector_store.id)
352
+ all_completed = True
353
+ failed = False
354
+ for file in uploaded_files:
355
+ if file.status == "failed":
356
+ log_error(f"File {file.id} failed to upload.")
357
+ failed = True
358
+ break
359
+ if file.status != "completed":
360
+ all_completed = False
361
+ if all_completed or failed:
362
+ break
363
+ time.sleep(1)
364
+ return vector_store.id
365
+
366
+ def _format_tool_params(
367
+ self, messages: List[Message], tools: Optional[List[Dict[str, Any]]] = None
368
+ ) -> List[Dict[str, Any]]:
369
+ """Format the tool parameters for the OpenAI Responses API."""
370
+ formatted_tools = []
371
+ if tools:
372
+ for _tool in tools:
373
+ if _tool.get("type") == "function":
374
+ _tool_dict = _tool.get("function", {})
375
+ _tool_dict["type"] = "function"
376
+ for prop in _tool_dict.get("parameters", {}).get("properties", {}).values():
377
+ if isinstance(prop.get("type", ""), list):
378
+ prop["type"] = prop["type"][0]
379
+
380
+ formatted_tools.append(_tool_dict)
381
+ else:
382
+ formatted_tools.append(_tool)
383
+
384
+ # Find files to upload to the OpenAI vector database
385
+ file_ids = []
386
+ for message in messages:
387
+ # Upload any attached files to the OpenAI vector database
388
+ if message.files is not None and len(message.files) > 0:
389
+ for file in message.files:
390
+ file_id = self._upload_file(file)
391
+ if file_id is not None:
392
+ file_ids.append(file_id)
393
+
394
+ vector_store_id = self._create_vector_store(file_ids) if file_ids else None
395
+
396
+ # Add the file IDs to the tool parameters
397
+ for _tool in formatted_tools:
398
+ if _tool["type"] == "file_search" and vector_store_id is not None:
399
+ _tool["vector_store_ids"] = [vector_store_id]
400
+
401
+ return formatted_tools
402
+
403
+ def _format_messages(
404
+ self, messages: List[Message], compress_tool_results: bool = False
405
+ ) -> List[Union[Dict[str, Any], ResponseReasoningItem]]:
406
+ """
407
+ Format a message into the format expected by OpenAI.
408
+
409
+ Args:
410
+ messages (List[Message]): The message to format.
411
+ compress_tool_results: Whether to compress tool results.
412
+
413
+ Returns:
414
+ Dict[str, Any]: The formatted message.
415
+ """
416
+ formatted_messages: List[Union[Dict[str, Any], ResponseReasoningItem]] = []
417
+
418
+ messages_to_format = messages
419
+ previous_response_id: Optional[str] = None
420
+
421
+ if self._using_reasoning_model() and self.store is not False:
422
+ # Detect whether we're chaining via previous_response_id. If so, we should NOT
423
+ # re-send prior function_call items; the Responses API already has the state and
424
+ # expects only the corresponding function_call_output items.
425
+
426
+ for msg in reversed(messages):
427
+ if (
428
+ msg.role == "assistant"
429
+ and hasattr(msg, "provider_data")
430
+ and msg.provider_data
431
+ and "response_id" in msg.provider_data
432
+ ):
433
+ previous_response_id = msg.provider_data["response_id"]
434
+ msg_index = messages.index(msg)
435
+
436
+ # Include messages after this assistant message
437
+ messages_to_format = messages[msg_index + 1 :]
438
+
439
+ break
440
+
441
+ # Build a mapping from function_call id (fc_*) → call_id (call_*) from prior assistant tool_calls
442
+ fc_id_to_call_id: Dict[str, str] = {}
443
+ for msg in messages:
444
+ tool_calls = getattr(msg, "tool_calls", None)
445
+ if tool_calls:
446
+ for tc in tool_calls:
447
+ fc_id = tc.get("id")
448
+ call_id = tc.get("call_id") or fc_id
449
+ if isinstance(fc_id, str) and isinstance(call_id, str):
450
+ fc_id_to_call_id[fc_id] = call_id
451
+
452
+ for message in messages_to_format:
453
+ if message.role in ["user", "system"]:
454
+ message_dict: Dict[str, Any] = {
455
+ "role": self.role_map[message.role],
456
+ "content": message.get_content(use_compressed_content=compress_tool_results),
457
+ }
458
+ message_dict = {k: v for k, v in message_dict.items() if v is not None}
459
+
460
+ # Ignore non-string message content
461
+ # because we assume that the images/audio are already added to the message
462
+ if message.images is not None and len(message.images) > 0:
463
+ # Ignore non-string message content
464
+ # because we assume that the images/audio are already added to the message
465
+ if isinstance(message.content, str):
466
+ message_dict["content"] = [{"type": "input_text", "text": message.content}]
467
+ if message.images is not None:
468
+ message_dict["content"].extend(images_to_message(images=message.images))
469
+
470
+ if message.audio is not None and len(message.audio) > 0:
471
+ log_warning("Audio input is currently unsupported.")
472
+
473
+ if message.videos is not None and len(message.videos) > 0:
474
+ log_warning("Video input is currently unsupported.")
475
+
476
+ formatted_messages.append(message_dict)
477
+
478
+ # Tool call result
479
+ elif message.role == "tool":
480
+ tool_result = message.get_content(use_compressed_content=compress_tool_results)
481
+
482
+ if message.tool_call_id and tool_result is not None:
483
+ function_call_id = message.tool_call_id
484
+ # Normalize: if a fc_* id was provided, translate to its corresponding call_* id
485
+ if isinstance(function_call_id, str) and function_call_id in fc_id_to_call_id:
486
+ call_id_value = fc_id_to_call_id[function_call_id]
487
+ else:
488
+ call_id_value = function_call_id
489
+ formatted_messages.append(
490
+ {"type": "function_call_output", "call_id": call_id_value, "output": tool_result}
491
+ )
492
+ # Tool Calls
493
+ elif message.tool_calls is not None and len(message.tool_calls) > 0:
494
+ # Only skip re-sending prior function_call items when we have a previous_response_id
495
+ # (reasoning models). For non-reasoning models, we must include the prior function_call
496
+ # so the API can associate the subsequent function_call_output by call_id.
497
+ if self._using_reasoning_model() and previous_response_id is not None:
498
+ continue
499
+
500
+ for tool_call in message.tool_calls:
501
+ formatted_messages.append(
502
+ {
503
+ "type": "function_call",
504
+ "id": tool_call.get("id"),
505
+ "call_id": tool_call.get("call_id", tool_call.get("id")),
506
+ "name": tool_call["function"]["name"],
507
+ "arguments": tool_call["function"]["arguments"],
508
+ "status": "completed",
509
+ }
510
+ )
511
+ elif message.role == "assistant":
512
+ # Handle null content by converting to empty string
513
+ content = message.content if message.content is not None else ""
514
+ formatted_messages.append({"role": self.role_map[message.role], "content": content})
515
+
516
+ if self.store is False and hasattr(message, "provider_data") and message.provider_data is not None:
517
+ if message.provider_data.get("reasoning_output") is not None:
518
+ reasoning_output = ResponseReasoningItem.model_validate(
519
+ message.provider_data["reasoning_output"]
520
+ )
521
+ formatted_messages.append(reasoning_output)
522
+ return formatted_messages
523
+
524
+ def count_tokens(
525
+ self,
526
+ messages: List[Message],
527
+ tools: Optional[List[Dict[str, Any]]] = None,
528
+ output_schema: Optional[Union[Dict, Type[BaseModel]]] = None,
529
+ ) -> int:
530
+ try:
531
+ formatted_input = self._format_messages(messages, compress_tool_results=True)
532
+ formatted_tools = self._format_tool_params(messages, tools) if tools else None
533
+
534
+ response = self.get_client().responses.input_tokens.count(
535
+ model=self.id,
536
+ input=formatted_input, # type: ignore
537
+ instructions=self.instructions, # type: ignore
538
+ tools=formatted_tools, # type: ignore
539
+ )
540
+ return response.input_tokens + count_schema_tokens(output_schema, self.id)
541
+ except Exception as e:
542
+ log_warning(f"Failed to count tokens via API: {e}")
543
+ return super().count_tokens(messages, tools, output_schema)
544
+
545
+ async def acount_tokens(
546
+ self,
547
+ messages: List[Message],
548
+ tools: Optional[List[Dict[str, Any]]] = None,
549
+ output_schema: Optional[Union[Dict, Type[BaseModel]]] = None,
550
+ ) -> int:
551
+ """Async version of count_tokens using the async client."""
552
+ try:
553
+ formatted_input = self._format_messages(messages, compress_tool_results=True)
554
+ formatted_tools = self._format_tool_params(messages, tools) if tools else None
555
+
556
+ response = await self.get_async_client().responses.input_tokens.count(
557
+ model=self.id,
558
+ input=formatted_input, # type: ignore
559
+ instructions=self.instructions, # type: ignore
560
+ tools=formatted_tools, # type: ignore
561
+ )
562
+ return response.input_tokens + count_schema_tokens(output_schema, self.id)
563
+ except Exception as e:
564
+ log_warning(f"Failed to count tokens via API: {e}")
565
+ return await super().acount_tokens(messages, tools, output_schema)
566
+
567
+ def invoke(
568
+ self,
569
+ messages: List[Message],
570
+ assistant_message: Message,
571
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
572
+ tools: Optional[List[Dict[str, Any]]] = None,
573
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
574
+ run_response: Optional[RunOutput] = None,
575
+ compress_tool_results: bool = False,
576
+ ) -> ModelResponse:
577
+ """
578
+ Send a request to the OpenAI Responses API.
579
+ """
580
+ try:
581
+ request_params = self.get_request_params(
582
+ messages=messages, response_format=response_format, tools=tools, tool_choice=tool_choice
583
+ )
584
+
585
+ if run_response and run_response.metrics:
586
+ run_response.metrics.set_time_to_first_token()
587
+
588
+ assistant_message.metrics.start_timer()
589
+
590
+ provider_response = self.get_client().responses.create(
591
+ model=self.id,
592
+ input=self._format_messages(messages, compress_tool_results), # type: ignore
593
+ **request_params,
594
+ )
595
+
596
+ assistant_message.metrics.stop_timer()
597
+
598
+ model_response = self._parse_provider_response(provider_response, response_format=response_format)
599
+
600
+ return model_response
601
+
602
+ except RateLimitError as exc:
603
+ log_error(f"Rate limit error from OpenAI API: {exc}")
604
+ error_message = exc.response.json().get("error", {})
605
+ error_message = (
606
+ error_message.get("message", "Unknown model error")
607
+ if isinstance(error_message, dict)
608
+ else error_message
609
+ )
610
+ raise ModelProviderError(
611
+ message=error_message,
612
+ status_code=exc.response.status_code,
613
+ model_name=self.name,
614
+ model_id=self.id,
615
+ ) from exc
616
+ except APIConnectionError as exc:
617
+ log_error(f"API connection error from OpenAI API: {exc}")
618
+ raise ModelProviderError(message=str(exc), model_name=self.name, model_id=self.id) from exc
619
+ except APIStatusError as exc:
620
+ log_error(f"API status error from OpenAI API: {exc}")
621
+ error_message = exc.response.json().get("error", {})
622
+ error_message = (
623
+ error_message.get("message", "Unknown model error")
624
+ if isinstance(error_message, dict)
625
+ else error_message
626
+ )
627
+ raise ModelProviderError(
628
+ message=error_message,
629
+ status_code=exc.response.status_code,
630
+ model_name=self.name,
631
+ model_id=self.id,
632
+ ) from exc
633
+ except ModelAuthenticationError as exc:
634
+ log_error(f"Model authentication error from OpenAI API: {exc}")
635
+ raise exc
636
+ except Exception as exc:
637
+ log_error(f"Error from OpenAI API: {exc}")
638
+ raise ModelProviderError(message=str(exc), model_name=self.name, model_id=self.id) from exc
639
+
640
+ async def ainvoke(
641
+ self,
642
+ messages: List[Message],
643
+ assistant_message: Message,
644
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
645
+ tools: Optional[List[Dict[str, Any]]] = None,
646
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
647
+ run_response: Optional[RunOutput] = None,
648
+ compress_tool_results: bool = False,
649
+ ) -> ModelResponse:
650
+ """
651
+ Sends an asynchronous request to the OpenAI Responses API.
652
+ """
653
+ try:
654
+ request_params = self.get_request_params(
655
+ messages=messages, response_format=response_format, tools=tools, tool_choice=tool_choice
656
+ )
657
+
658
+ if run_response and run_response.metrics:
659
+ run_response.metrics.set_time_to_first_token()
660
+
661
+ assistant_message.metrics.start_timer()
662
+
663
+ provider_response = await self.get_async_client().responses.create(
664
+ model=self.id,
665
+ input=self._format_messages(messages, compress_tool_results), # type: ignore
666
+ **request_params,
667
+ )
668
+
669
+ assistant_message.metrics.stop_timer()
670
+
671
+ model_response = self._parse_provider_response(provider_response, response_format=response_format)
672
+
673
+ return model_response
674
+
675
+ except RateLimitError as exc:
676
+ log_error(f"Rate limit error from OpenAI API: {exc}")
677
+ error_message = exc.response.json().get("error", {})
678
+ error_message = (
679
+ error_message.get("message", "Unknown model error")
680
+ if isinstance(error_message, dict)
681
+ else error_message
682
+ )
683
+ raise ModelProviderError(
684
+ message=error_message,
685
+ status_code=exc.response.status_code,
686
+ model_name=self.name,
687
+ model_id=self.id,
688
+ ) from exc
689
+ except APIConnectionError as exc:
690
+ log_error(f"API connection error from OpenAI API: {exc}")
691
+ raise ModelProviderError(message=str(exc), model_name=self.name, model_id=self.id) from exc
692
+ except APIStatusError as exc:
693
+ log_error(f"API status error from OpenAI API: {exc}")
694
+ error_message = exc.response.json().get("error", {})
695
+ error_message = (
696
+ error_message.get("message", "Unknown model error")
697
+ if isinstance(error_message, dict)
698
+ else error_message
699
+ )
700
+ raise ModelProviderError(
701
+ message=error_message,
702
+ status_code=exc.response.status_code,
703
+ model_name=self.name,
704
+ model_id=self.id,
705
+ ) from exc
706
+ except ModelAuthenticationError as exc:
707
+ log_error(f"Model authentication error from OpenAI API: {exc}")
708
+ raise exc
709
+ except Exception as exc:
710
+ log_error(f"Error from OpenAI API: {exc}")
711
+ raise ModelProviderError(message=str(exc), model_name=self.name, model_id=self.id) from exc
712
+
713
+ def invoke_stream(
714
+ self,
715
+ messages: List[Message],
716
+ assistant_message: Message,
717
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
718
+ tools: Optional[List[Dict[str, Any]]] = None,
719
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
720
+ run_response: Optional[RunOutput] = None,
721
+ compress_tool_results: bool = False,
722
+ ) -> Iterator[ModelResponse]:
723
+ """
724
+ Send a streaming request to the OpenAI Responses API.
725
+ """
726
+ try:
727
+ request_params = self.get_request_params(
728
+ messages=messages, response_format=response_format, tools=tools, tool_choice=tool_choice
729
+ )
730
+ tool_use: Dict[str, Any] = {}
731
+
732
+ if run_response and run_response.metrics:
733
+ run_response.metrics.set_time_to_first_token()
734
+
735
+ assistant_message.metrics.start_timer()
736
+
737
+ for chunk in self.get_client().responses.create(
738
+ model=self.id,
739
+ input=self._format_messages(messages, compress_tool_results), # type: ignore
740
+ stream=True,
741
+ **request_params,
742
+ ):
743
+ model_response, tool_use = self._parse_provider_response_delta(
744
+ stream_event=chunk, # type: ignore
745
+ assistant_message=assistant_message,
746
+ tool_use=tool_use, # type: ignore
747
+ )
748
+ yield model_response
749
+
750
+ assistant_message.metrics.stop_timer()
751
+
752
+ except RateLimitError as exc:
753
+ log_error(f"Rate limit error from OpenAI API: {exc}")
754
+ error_message = exc.response.json().get("error", {})
755
+ error_message = (
756
+ error_message.get("message", "Unknown model error")
757
+ if isinstance(error_message, dict)
758
+ else error_message
759
+ )
760
+ raise ModelProviderError(
761
+ message=error_message,
762
+ status_code=exc.response.status_code,
763
+ model_name=self.name,
764
+ model_id=self.id,
765
+ ) from exc
766
+ except APIConnectionError as exc:
767
+ log_error(f"API connection error from OpenAI API: {exc}")
768
+ raise ModelProviderError(message=str(exc), model_name=self.name, model_id=self.id) from exc
769
+ except APIStatusError as exc:
770
+ log_error(f"API status error from OpenAI API: {exc}")
771
+ error_message = exc.response.json().get("error", {})
772
+ error_message = (
773
+ error_message.get("message", "Unknown model error")
774
+ if isinstance(error_message, dict)
775
+ else error_message
776
+ )
777
+ raise ModelProviderError(
778
+ message=error_message,
779
+ status_code=exc.response.status_code,
780
+ model_name=self.name,
781
+ model_id=self.id,
782
+ ) from exc
783
+ except ModelAuthenticationError as exc:
784
+ log_error(f"Model authentication error from OpenAI API: {exc}")
785
+ raise exc
786
+ except Exception as exc:
787
+ log_error(f"Error from OpenAI API: {exc}")
788
+ raise ModelProviderError(message=str(exc), model_name=self.name, model_id=self.id) from exc
789
+
790
+ async def ainvoke_stream(
791
+ self,
792
+ messages: List[Message],
793
+ assistant_message: Message,
794
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
795
+ tools: Optional[List[Dict[str, Any]]] = None,
796
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
797
+ run_response: Optional[RunOutput] = None,
798
+ compress_tool_results: bool = False,
799
+ ) -> AsyncIterator[ModelResponse]:
800
+ """
801
+ Sends an asynchronous streaming request to the OpenAI Responses API.
802
+ """
803
+ try:
804
+ request_params = self.get_request_params(
805
+ messages=messages, response_format=response_format, tools=tools, tool_choice=tool_choice
806
+ )
807
+ tool_use: Dict[str, Any] = {}
808
+
809
+ if run_response and run_response.metrics:
810
+ run_response.metrics.set_time_to_first_token()
811
+
812
+ assistant_message.metrics.start_timer()
813
+
814
+ async_stream = await self.get_async_client().responses.create(
815
+ model=self.id,
816
+ input=self._format_messages(messages, compress_tool_results), # type: ignore
817
+ stream=True,
818
+ **request_params,
819
+ )
820
+ async for chunk in async_stream: # type: ignore
821
+ model_response, tool_use = self._parse_provider_response_delta(chunk, assistant_message, tool_use) # type: ignore
822
+ yield model_response
823
+
824
+ assistant_message.metrics.stop_timer()
825
+
826
+ except RateLimitError as exc:
827
+ log_error(f"Rate limit error from OpenAI API: {exc}")
828
+ error_message = exc.response.json().get("error", {})
829
+ error_message = (
830
+ error_message.get("message", "Unknown model error")
831
+ if isinstance(error_message, dict)
832
+ else error_message
833
+ )
834
+ raise ModelProviderError(
835
+ message=error_message,
836
+ status_code=exc.response.status_code,
837
+ model_name=self.name,
838
+ model_id=self.id,
839
+ ) from exc
840
+ except APIConnectionError as exc:
841
+ log_error(f"API connection error from OpenAI API: {exc}")
842
+ raise ModelProviderError(message=str(exc), model_name=self.name, model_id=self.id) from exc
843
+ except APIStatusError as exc:
844
+ log_error(f"API status error from OpenAI API: {exc}")
845
+ error_message = exc.response.json().get("error", {})
846
+ error_message = (
847
+ error_message.get("message", "Unknown model error")
848
+ if isinstance(error_message, dict)
849
+ else error_message
850
+ )
851
+ raise ModelProviderError(
852
+ message=error_message,
853
+ status_code=exc.response.status_code,
854
+ model_name=self.name,
855
+ model_id=self.id,
856
+ ) from exc
857
+ except ModelAuthenticationError as exc:
858
+ log_error(f"Model authentication error from OpenAI API: {exc}")
859
+ raise exc
860
+ except Exception as exc:
861
+ log_error(f"Error from OpenAI API: {exc}")
862
+ raise ModelProviderError(message=str(exc), model_name=self.name, model_id=self.id) from exc
863
+
864
+ def format_function_call_results(
865
+ self,
866
+ messages: List[Message],
867
+ function_call_results: List[Message],
868
+ tool_call_ids: List[str],
869
+ compress_tool_results: bool = False,
870
+ ) -> None:
871
+ """
872
+ Handle the results of function calls.
873
+
874
+ Args:
875
+ messages (List[Message]): The list of conversation messages.
876
+ function_call_results (List[Message]): The results of the function calls.
877
+ tool_ids (List[str]): The tool ids.
878
+ compress_tool_results (bool): Whether to compress tool results.
879
+ """
880
+ if len(function_call_results) > 0:
881
+ for _fc_message_index, _fc_message in enumerate(function_call_results):
882
+ _fc_message.tool_call_id = tool_call_ids[_fc_message_index]
883
+ messages.append(_fc_message)
884
+
885
+ def _parse_provider_response(self, response: Response, **kwargs) -> ModelResponse:
886
+ """
887
+ Parse the OpenAI response into a ModelResponse.
888
+
889
+ Args:
890
+ response: Response from invoke() method
891
+
892
+ Returns:
893
+ ModelResponse: Parsed response data
894
+ """
895
+ model_response = ModelResponse()
896
+
897
+ if response.error:
898
+ raise ModelProviderError(
899
+ message=response.error.message,
900
+ model_name=self.name,
901
+ model_id=self.id,
902
+ )
903
+
904
+ # Store the response ID for continuity
905
+ if response.id:
906
+ if model_response.provider_data is None:
907
+ model_response.provider_data = {}
908
+ model_response.provider_data["response_id"] = response.id
909
+
910
+ # Add role
911
+ model_response.role = "assistant"
912
+ reasoning_summary: Optional[str] = None
913
+
914
+ for output in response.output:
915
+ # Add content
916
+ if output.type == "message":
917
+ model_response.content = response.output_text
918
+
919
+ # Add citations
920
+ citations = Citations()
921
+ for content in output.content:
922
+ if content.type == "output_text" and content.annotations:
923
+ citations.raw = [annotation.model_dump() for annotation in content.annotations]
924
+ for annotation in content.annotations:
925
+ if annotation.type == "url_citation":
926
+ if citations.urls is None:
927
+ citations.urls = []
928
+ citations.urls.append(UrlCitation(url=annotation.url, title=annotation.title))
929
+ if citations.urls or citations.documents:
930
+ model_response.citations = citations
931
+
932
+ # Add tool calls
933
+ elif output.type == "function_call":
934
+ if model_response.tool_calls is None:
935
+ model_response.tool_calls = []
936
+ model_response.tool_calls.append(
937
+ {
938
+ "id": output.id,
939
+ # Store additional call_id from OpenAI responses
940
+ "call_id": output.call_id or output.id,
941
+ "type": "function",
942
+ "function": {
943
+ "name": output.name,
944
+ "arguments": output.arguments,
945
+ },
946
+ }
947
+ )
948
+
949
+ model_response.extra = model_response.extra or {}
950
+ model_response.extra.setdefault("tool_call_ids", []).append(output.call_id)
951
+
952
+ # Handle reasoning output items
953
+ elif output.type == "reasoning":
954
+ # Save encrypted reasoning content for ZDR mode
955
+ if self.store is False:
956
+ if model_response.provider_data is None:
957
+ model_response.provider_data = {}
958
+ model_response.provider_data["reasoning_output"] = output.model_dump(exclude_none=True)
959
+
960
+ if reasoning_summaries := getattr(output, "summary", None):
961
+ for summary in reasoning_summaries:
962
+ if isinstance(summary, dict):
963
+ summary_text = summary.get("text")
964
+ else:
965
+ summary_text = getattr(summary, "text", None)
966
+ if summary_text:
967
+ reasoning_summary = (reasoning_summary or "") + summary_text
968
+
969
+ # Add reasoning content
970
+ if reasoning_summary is not None:
971
+ model_response.reasoning_content = reasoning_summary
972
+ elif self.reasoning is not None:
973
+ model_response.reasoning_content = response.output_text
974
+
975
+ # Add metrics
976
+ if response.usage is not None:
977
+ model_response.response_usage = self._get_metrics(response.usage)
978
+
979
+ return model_response
980
+
981
+ def _parse_provider_response_delta(
982
+ self, stream_event: ResponseStreamEvent, assistant_message: Message, tool_use: Dict[str, Any]
983
+ ) -> Tuple[ModelResponse, Dict[str, Any]]:
984
+ """
985
+ Parse the streaming response from the model provider into a ModelResponse object.
986
+
987
+ Args:
988
+ response: Raw response chunk from the model provider
989
+
990
+ Returns:
991
+ ModelResponse: Parsed response delta
992
+ """
993
+ model_response = ModelResponse()
994
+
995
+ # 1. Add response ID
996
+ if stream_event.type == "response.created":
997
+ if stream_event.response.id:
998
+ if model_response.provider_data is None:
999
+ model_response.provider_data = {}
1000
+ model_response.provider_data["response_id"] = stream_event.response.id
1001
+ if not assistant_message.metrics.time_to_first_token:
1002
+ assistant_message.metrics.set_time_to_first_token()
1003
+
1004
+ # 2. Add citations
1005
+ elif stream_event.type == "response.output_text.annotation.added":
1006
+ if model_response.citations is None:
1007
+ model_response.citations = Citations(raw=[stream_event.annotation])
1008
+ else:
1009
+ model_response.citations.raw.append(stream_event.annotation) # type: ignore
1010
+
1011
+ if isinstance(stream_event.annotation, dict):
1012
+ if stream_event.annotation.get("type") == "url_citation":
1013
+ if model_response.citations.urls is None:
1014
+ model_response.citations.urls = []
1015
+ model_response.citations.urls.append(
1016
+ UrlCitation(url=stream_event.annotation.get("url"), title=stream_event.annotation.get("title"))
1017
+ )
1018
+ else:
1019
+ if stream_event.annotation.type == "url_citation": # type: ignore
1020
+ if model_response.citations.urls is None:
1021
+ model_response.citations.urls = []
1022
+ model_response.citations.urls.append(
1023
+ UrlCitation(url=stream_event.annotation.url, title=stream_event.annotation.title) # type: ignore
1024
+ )
1025
+
1026
+ # 3. Add content
1027
+ elif stream_event.type == "response.output_text.delta":
1028
+ model_response.content = stream_event.delta
1029
+
1030
+ # Treat the output_text deltas as reasoning content if the reasoning summary is not requested.
1031
+ if self.reasoning is not None and self.reasoning_summary is None:
1032
+ model_response.reasoning_content = stream_event.delta
1033
+
1034
+ # 4. Add tool calls information
1035
+
1036
+ # 4.1 Add starting tool call
1037
+ elif stream_event.type == "response.output_item.added":
1038
+ item = stream_event.item
1039
+ if item.type == "function_call":
1040
+ tool_use = {
1041
+ "id": getattr(item, "id", None),
1042
+ "call_id": getattr(item, "call_id", None) or getattr(item, "id", None),
1043
+ "type": "function",
1044
+ "function": {
1045
+ "name": item.name,
1046
+ "arguments": item.arguments,
1047
+ },
1048
+ }
1049
+
1050
+ # 4.2 Add tool call arguments
1051
+ elif stream_event.type == "response.function_call_arguments.delta":
1052
+ tool_use["function"]["arguments"] += stream_event.delta
1053
+
1054
+ # 4.3 Add tool call completion data
1055
+ elif stream_event.type == "response.output_item.done" and tool_use:
1056
+ model_response.tool_calls = [tool_use]
1057
+ if assistant_message.tool_calls is None:
1058
+ assistant_message.tool_calls = []
1059
+ assistant_message.tool_calls.append(tool_use)
1060
+
1061
+ model_response.extra = model_response.extra or {}
1062
+ model_response.extra.setdefault("tool_call_ids", []).append(tool_use["call_id"])
1063
+ tool_use = {}
1064
+
1065
+ # 5. Add metrics
1066
+ elif stream_event.type == "response.completed":
1067
+ model_response = ModelResponse()
1068
+
1069
+ # Handle reasoning output items
1070
+ if self.reasoning_summary is not None or self.store is False:
1071
+ summary_text: str = ""
1072
+ for out in getattr(stream_event.response, "output", []) or []:
1073
+ if getattr(out, "type", None) == "reasoning":
1074
+ # In ZDR mode (store=False), store reasoning data for next request
1075
+ if self.store is False and hasattr(out, "encrypted_content"):
1076
+ if model_response.provider_data is None:
1077
+ model_response.provider_data = {}
1078
+ # Store the complete output item
1079
+ model_response.provider_data["reasoning_output"] = out.model_dump(exclude_none=True)
1080
+ if self.reasoning_summary is not None:
1081
+ summaries = getattr(out, "summary", None)
1082
+ if summaries:
1083
+ for s in summaries:
1084
+ text_val = s.get("text") if isinstance(s, dict) else getattr(s, "text", None)
1085
+ if text_val:
1086
+ if summary_text:
1087
+ summary_text += "\n\n"
1088
+ summary_text += text_val
1089
+
1090
+ if summary_text:
1091
+ model_response.reasoning_content = summary_text
1092
+
1093
+ # Add metrics
1094
+ if stream_event.response.usage is not None:
1095
+ model_response.response_usage = self._get_metrics(stream_event.response.usage)
1096
+
1097
+ return model_response, tool_use
1098
+
1099
+ def _get_metrics(self, response_usage: ResponseUsage) -> Metrics:
1100
+ """
1101
+ Parse the given OpenAI-specific usage into an Agno Metrics object.
1102
+
1103
+ Args:
1104
+ response: The response from the provider.
1105
+
1106
+ Returns:
1107
+ Metrics: Parsed metrics data
1108
+ """
1109
+ metrics = Metrics()
1110
+
1111
+ metrics.input_tokens = response_usage.input_tokens or 0
1112
+ metrics.output_tokens = response_usage.output_tokens or 0
1113
+ metrics.total_tokens = response_usage.total_tokens or 0
1114
+
1115
+ if input_tokens_details := response_usage.input_tokens_details:
1116
+ metrics.cache_read_tokens = input_tokens_details.cached_tokens
1117
+
1118
+ if output_tokens_details := response_usage.output_tokens_details:
1119
+ metrics.reasoning_tokens = output_tokens_details.reasoning_tokens
1120
+
1121
+ return metrics