agno 2.2.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 (575) hide show
  1. agno/__init__.py +8 -0
  2. agno/agent/__init__.py +51 -0
  3. agno/agent/agent.py +10405 -0
  4. agno/api/__init__.py +0 -0
  5. agno/api/agent.py +28 -0
  6. agno/api/api.py +40 -0
  7. agno/api/evals.py +22 -0
  8. agno/api/os.py +17 -0
  9. agno/api/routes.py +13 -0
  10. agno/api/schemas/__init__.py +9 -0
  11. agno/api/schemas/agent.py +16 -0
  12. agno/api/schemas/evals.py +16 -0
  13. agno/api/schemas/os.py +14 -0
  14. agno/api/schemas/response.py +6 -0
  15. agno/api/schemas/team.py +16 -0
  16. agno/api/schemas/utils.py +21 -0
  17. agno/api/schemas/workflows.py +16 -0
  18. agno/api/settings.py +53 -0
  19. agno/api/team.py +30 -0
  20. agno/api/workflow.py +28 -0
  21. agno/cloud/aws/base.py +214 -0
  22. agno/cloud/aws/s3/__init__.py +2 -0
  23. agno/cloud/aws/s3/api_client.py +43 -0
  24. agno/cloud/aws/s3/bucket.py +195 -0
  25. agno/cloud/aws/s3/object.py +57 -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 +598 -0
  31. agno/db/dynamo/__init__.py +3 -0
  32. agno/db/dynamo/dynamo.py +2042 -0
  33. agno/db/dynamo/schemas.py +314 -0
  34. agno/db/dynamo/utils.py +743 -0
  35. agno/db/firestore/__init__.py +3 -0
  36. agno/db/firestore/firestore.py +1795 -0
  37. agno/db/firestore/schemas.py +140 -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 +1335 -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 +1160 -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 +1328 -0
  47. agno/db/json/utils.py +230 -0
  48. agno/db/migrations/__init__.py +0 -0
  49. agno/db/migrations/v1_to_v2.py +635 -0
  50. agno/db/mongo/__init__.py +17 -0
  51. agno/db/mongo/async_mongo.py +2026 -0
  52. agno/db/mongo/mongo.py +1982 -0
  53. agno/db/mongo/schemas.py +87 -0
  54. agno/db/mongo/utils.py +259 -0
  55. agno/db/mysql/__init__.py +3 -0
  56. agno/db/mysql/mysql.py +2308 -0
  57. agno/db/mysql/schemas.py +138 -0
  58. agno/db/mysql/utils.py +355 -0
  59. agno/db/postgres/__init__.py +4 -0
  60. agno/db/postgres/async_postgres.py +1927 -0
  61. agno/db/postgres/postgres.py +2260 -0
  62. agno/db/postgres/schemas.py +139 -0
  63. agno/db/postgres/utils.py +442 -0
  64. agno/db/redis/__init__.py +3 -0
  65. agno/db/redis/redis.py +1660 -0
  66. agno/db/redis/schemas.py +123 -0
  67. agno/db/redis/utils.py +346 -0
  68. agno/db/schemas/__init__.py +4 -0
  69. agno/db/schemas/culture.py +120 -0
  70. agno/db/schemas/evals.py +33 -0
  71. agno/db/schemas/knowledge.py +40 -0
  72. agno/db/schemas/memory.py +46 -0
  73. agno/db/schemas/metrics.py +0 -0
  74. agno/db/singlestore/__init__.py +3 -0
  75. agno/db/singlestore/schemas.py +130 -0
  76. agno/db/singlestore/singlestore.py +2272 -0
  77. agno/db/singlestore/utils.py +384 -0
  78. agno/db/sqlite/__init__.py +4 -0
  79. agno/db/sqlite/async_sqlite.py +2293 -0
  80. agno/db/sqlite/schemas.py +133 -0
  81. agno/db/sqlite/sqlite.py +2288 -0
  82. agno/db/sqlite/utils.py +431 -0
  83. agno/db/surrealdb/__init__.py +3 -0
  84. agno/db/surrealdb/metrics.py +292 -0
  85. agno/db/surrealdb/models.py +309 -0
  86. agno/db/surrealdb/queries.py +71 -0
  87. agno/db/surrealdb/surrealdb.py +1353 -0
  88. agno/db/surrealdb/utils.py +147 -0
  89. agno/db/utils.py +116 -0
  90. agno/debug.py +18 -0
  91. agno/eval/__init__.py +14 -0
  92. agno/eval/accuracy.py +834 -0
  93. agno/eval/performance.py +773 -0
  94. agno/eval/reliability.py +306 -0
  95. agno/eval/utils.py +119 -0
  96. agno/exceptions.py +161 -0
  97. agno/filters.py +354 -0
  98. agno/guardrails/__init__.py +6 -0
  99. agno/guardrails/base.py +19 -0
  100. agno/guardrails/openai.py +144 -0
  101. agno/guardrails/pii.py +94 -0
  102. agno/guardrails/prompt_injection.py +52 -0
  103. agno/integrations/__init__.py +0 -0
  104. agno/integrations/discord/__init__.py +3 -0
  105. agno/integrations/discord/client.py +203 -0
  106. agno/knowledge/__init__.py +5 -0
  107. agno/knowledge/chunking/__init__.py +0 -0
  108. agno/knowledge/chunking/agentic.py +79 -0
  109. agno/knowledge/chunking/document.py +91 -0
  110. agno/knowledge/chunking/fixed.py +57 -0
  111. agno/knowledge/chunking/markdown.py +151 -0
  112. agno/knowledge/chunking/recursive.py +63 -0
  113. agno/knowledge/chunking/row.py +39 -0
  114. agno/knowledge/chunking/semantic.py +86 -0
  115. agno/knowledge/chunking/strategy.py +165 -0
  116. agno/knowledge/content.py +74 -0
  117. agno/knowledge/document/__init__.py +5 -0
  118. agno/knowledge/document/base.py +58 -0
  119. agno/knowledge/embedder/__init__.py +5 -0
  120. agno/knowledge/embedder/aws_bedrock.py +343 -0
  121. agno/knowledge/embedder/azure_openai.py +210 -0
  122. agno/knowledge/embedder/base.py +23 -0
  123. agno/knowledge/embedder/cohere.py +323 -0
  124. agno/knowledge/embedder/fastembed.py +62 -0
  125. agno/knowledge/embedder/fireworks.py +13 -0
  126. agno/knowledge/embedder/google.py +258 -0
  127. agno/knowledge/embedder/huggingface.py +94 -0
  128. agno/knowledge/embedder/jina.py +182 -0
  129. agno/knowledge/embedder/langdb.py +22 -0
  130. agno/knowledge/embedder/mistral.py +206 -0
  131. agno/knowledge/embedder/nebius.py +13 -0
  132. agno/knowledge/embedder/ollama.py +154 -0
  133. agno/knowledge/embedder/openai.py +195 -0
  134. agno/knowledge/embedder/sentence_transformer.py +63 -0
  135. agno/knowledge/embedder/together.py +13 -0
  136. agno/knowledge/embedder/vllm.py +262 -0
  137. agno/knowledge/embedder/voyageai.py +165 -0
  138. agno/knowledge/knowledge.py +1988 -0
  139. agno/knowledge/reader/__init__.py +7 -0
  140. agno/knowledge/reader/arxiv_reader.py +81 -0
  141. agno/knowledge/reader/base.py +95 -0
  142. agno/knowledge/reader/csv_reader.py +166 -0
  143. agno/knowledge/reader/docx_reader.py +82 -0
  144. agno/knowledge/reader/field_labeled_csv_reader.py +292 -0
  145. agno/knowledge/reader/firecrawl_reader.py +201 -0
  146. agno/knowledge/reader/json_reader.py +87 -0
  147. agno/knowledge/reader/markdown_reader.py +137 -0
  148. agno/knowledge/reader/pdf_reader.py +431 -0
  149. agno/knowledge/reader/pptx_reader.py +101 -0
  150. agno/knowledge/reader/reader_factory.py +313 -0
  151. agno/knowledge/reader/s3_reader.py +89 -0
  152. agno/knowledge/reader/tavily_reader.py +194 -0
  153. agno/knowledge/reader/text_reader.py +115 -0
  154. agno/knowledge/reader/web_search_reader.py +372 -0
  155. agno/knowledge/reader/website_reader.py +455 -0
  156. agno/knowledge/reader/wikipedia_reader.py +59 -0
  157. agno/knowledge/reader/youtube_reader.py +78 -0
  158. agno/knowledge/remote_content/__init__.py +0 -0
  159. agno/knowledge/remote_content/remote_content.py +88 -0
  160. agno/knowledge/reranker/__init__.py +3 -0
  161. agno/knowledge/reranker/base.py +14 -0
  162. agno/knowledge/reranker/cohere.py +64 -0
  163. agno/knowledge/reranker/infinity.py +195 -0
  164. agno/knowledge/reranker/sentence_transformer.py +54 -0
  165. agno/knowledge/types.py +39 -0
  166. agno/knowledge/utils.py +189 -0
  167. agno/media.py +462 -0
  168. agno/memory/__init__.py +3 -0
  169. agno/memory/manager.py +1327 -0
  170. agno/models/__init__.py +0 -0
  171. agno/models/aimlapi/__init__.py +5 -0
  172. agno/models/aimlapi/aimlapi.py +45 -0
  173. agno/models/anthropic/__init__.py +5 -0
  174. agno/models/anthropic/claude.py +757 -0
  175. agno/models/aws/__init__.py +15 -0
  176. agno/models/aws/bedrock.py +701 -0
  177. agno/models/aws/claude.py +378 -0
  178. agno/models/azure/__init__.py +18 -0
  179. agno/models/azure/ai_foundry.py +485 -0
  180. agno/models/azure/openai_chat.py +131 -0
  181. agno/models/base.py +2175 -0
  182. agno/models/cerebras/__init__.py +12 -0
  183. agno/models/cerebras/cerebras.py +501 -0
  184. agno/models/cerebras/cerebras_openai.py +112 -0
  185. agno/models/cohere/__init__.py +5 -0
  186. agno/models/cohere/chat.py +389 -0
  187. agno/models/cometapi/__init__.py +5 -0
  188. agno/models/cometapi/cometapi.py +57 -0
  189. agno/models/dashscope/__init__.py +5 -0
  190. agno/models/dashscope/dashscope.py +91 -0
  191. agno/models/deepinfra/__init__.py +5 -0
  192. agno/models/deepinfra/deepinfra.py +28 -0
  193. agno/models/deepseek/__init__.py +5 -0
  194. agno/models/deepseek/deepseek.py +61 -0
  195. agno/models/defaults.py +1 -0
  196. agno/models/fireworks/__init__.py +5 -0
  197. agno/models/fireworks/fireworks.py +26 -0
  198. agno/models/google/__init__.py +5 -0
  199. agno/models/google/gemini.py +1085 -0
  200. agno/models/groq/__init__.py +5 -0
  201. agno/models/groq/groq.py +556 -0
  202. agno/models/huggingface/__init__.py +5 -0
  203. agno/models/huggingface/huggingface.py +491 -0
  204. agno/models/ibm/__init__.py +5 -0
  205. agno/models/ibm/watsonx.py +422 -0
  206. agno/models/internlm/__init__.py +3 -0
  207. agno/models/internlm/internlm.py +26 -0
  208. agno/models/langdb/__init__.py +1 -0
  209. agno/models/langdb/langdb.py +48 -0
  210. agno/models/litellm/__init__.py +14 -0
  211. agno/models/litellm/chat.py +468 -0
  212. agno/models/litellm/litellm_openai.py +25 -0
  213. agno/models/llama_cpp/__init__.py +5 -0
  214. agno/models/llama_cpp/llama_cpp.py +22 -0
  215. agno/models/lmstudio/__init__.py +5 -0
  216. agno/models/lmstudio/lmstudio.py +25 -0
  217. agno/models/message.py +434 -0
  218. agno/models/meta/__init__.py +12 -0
  219. agno/models/meta/llama.py +475 -0
  220. agno/models/meta/llama_openai.py +78 -0
  221. agno/models/metrics.py +120 -0
  222. agno/models/mistral/__init__.py +5 -0
  223. agno/models/mistral/mistral.py +432 -0
  224. agno/models/nebius/__init__.py +3 -0
  225. agno/models/nebius/nebius.py +54 -0
  226. agno/models/nexus/__init__.py +3 -0
  227. agno/models/nexus/nexus.py +22 -0
  228. agno/models/nvidia/__init__.py +5 -0
  229. agno/models/nvidia/nvidia.py +28 -0
  230. agno/models/ollama/__init__.py +5 -0
  231. agno/models/ollama/chat.py +441 -0
  232. agno/models/openai/__init__.py +9 -0
  233. agno/models/openai/chat.py +883 -0
  234. agno/models/openai/like.py +27 -0
  235. agno/models/openai/responses.py +1050 -0
  236. agno/models/openrouter/__init__.py +5 -0
  237. agno/models/openrouter/openrouter.py +66 -0
  238. agno/models/perplexity/__init__.py +5 -0
  239. agno/models/perplexity/perplexity.py +187 -0
  240. agno/models/portkey/__init__.py +3 -0
  241. agno/models/portkey/portkey.py +81 -0
  242. agno/models/requesty/__init__.py +5 -0
  243. agno/models/requesty/requesty.py +52 -0
  244. agno/models/response.py +199 -0
  245. agno/models/sambanova/__init__.py +5 -0
  246. agno/models/sambanova/sambanova.py +28 -0
  247. agno/models/siliconflow/__init__.py +5 -0
  248. agno/models/siliconflow/siliconflow.py +25 -0
  249. agno/models/together/__init__.py +5 -0
  250. agno/models/together/together.py +25 -0
  251. agno/models/utils.py +266 -0
  252. agno/models/vercel/__init__.py +3 -0
  253. agno/models/vercel/v0.py +26 -0
  254. agno/models/vertexai/__init__.py +0 -0
  255. agno/models/vertexai/claude.py +70 -0
  256. agno/models/vllm/__init__.py +3 -0
  257. agno/models/vllm/vllm.py +78 -0
  258. agno/models/xai/__init__.py +3 -0
  259. agno/models/xai/xai.py +113 -0
  260. agno/os/__init__.py +3 -0
  261. agno/os/app.py +876 -0
  262. agno/os/auth.py +57 -0
  263. agno/os/config.py +104 -0
  264. agno/os/interfaces/__init__.py +1 -0
  265. agno/os/interfaces/a2a/__init__.py +3 -0
  266. agno/os/interfaces/a2a/a2a.py +42 -0
  267. agno/os/interfaces/a2a/router.py +250 -0
  268. agno/os/interfaces/a2a/utils.py +924 -0
  269. agno/os/interfaces/agui/__init__.py +3 -0
  270. agno/os/interfaces/agui/agui.py +47 -0
  271. agno/os/interfaces/agui/router.py +144 -0
  272. agno/os/interfaces/agui/utils.py +534 -0
  273. agno/os/interfaces/base.py +25 -0
  274. agno/os/interfaces/slack/__init__.py +3 -0
  275. agno/os/interfaces/slack/router.py +148 -0
  276. agno/os/interfaces/slack/security.py +30 -0
  277. agno/os/interfaces/slack/slack.py +47 -0
  278. agno/os/interfaces/whatsapp/__init__.py +3 -0
  279. agno/os/interfaces/whatsapp/router.py +211 -0
  280. agno/os/interfaces/whatsapp/security.py +53 -0
  281. agno/os/interfaces/whatsapp/whatsapp.py +36 -0
  282. agno/os/mcp.py +292 -0
  283. agno/os/middleware/__init__.py +7 -0
  284. agno/os/middleware/jwt.py +233 -0
  285. agno/os/router.py +1763 -0
  286. agno/os/routers/__init__.py +3 -0
  287. agno/os/routers/evals/__init__.py +3 -0
  288. agno/os/routers/evals/evals.py +430 -0
  289. agno/os/routers/evals/schemas.py +142 -0
  290. agno/os/routers/evals/utils.py +162 -0
  291. agno/os/routers/health.py +31 -0
  292. agno/os/routers/home.py +52 -0
  293. agno/os/routers/knowledge/__init__.py +3 -0
  294. agno/os/routers/knowledge/knowledge.py +997 -0
  295. agno/os/routers/knowledge/schemas.py +178 -0
  296. agno/os/routers/memory/__init__.py +3 -0
  297. agno/os/routers/memory/memory.py +515 -0
  298. agno/os/routers/memory/schemas.py +62 -0
  299. agno/os/routers/metrics/__init__.py +3 -0
  300. agno/os/routers/metrics/metrics.py +190 -0
  301. agno/os/routers/metrics/schemas.py +47 -0
  302. agno/os/routers/session/__init__.py +3 -0
  303. agno/os/routers/session/session.py +997 -0
  304. agno/os/schema.py +1055 -0
  305. agno/os/settings.py +43 -0
  306. agno/os/utils.py +630 -0
  307. agno/py.typed +0 -0
  308. agno/reasoning/__init__.py +0 -0
  309. agno/reasoning/anthropic.py +80 -0
  310. agno/reasoning/azure_ai_foundry.py +67 -0
  311. agno/reasoning/deepseek.py +63 -0
  312. agno/reasoning/default.py +97 -0
  313. agno/reasoning/gemini.py +73 -0
  314. agno/reasoning/groq.py +71 -0
  315. agno/reasoning/helpers.py +63 -0
  316. agno/reasoning/ollama.py +67 -0
  317. agno/reasoning/openai.py +86 -0
  318. agno/reasoning/step.py +31 -0
  319. agno/reasoning/vertexai.py +76 -0
  320. agno/run/__init__.py +6 -0
  321. agno/run/agent.py +787 -0
  322. agno/run/base.py +229 -0
  323. agno/run/cancel.py +81 -0
  324. agno/run/messages.py +32 -0
  325. agno/run/team.py +753 -0
  326. agno/run/workflow.py +708 -0
  327. agno/session/__init__.py +10 -0
  328. agno/session/agent.py +295 -0
  329. agno/session/summary.py +265 -0
  330. agno/session/team.py +392 -0
  331. agno/session/workflow.py +205 -0
  332. agno/team/__init__.py +37 -0
  333. agno/team/team.py +8793 -0
  334. agno/tools/__init__.py +10 -0
  335. agno/tools/agentql.py +120 -0
  336. agno/tools/airflow.py +69 -0
  337. agno/tools/api.py +122 -0
  338. agno/tools/apify.py +314 -0
  339. agno/tools/arxiv.py +127 -0
  340. agno/tools/aws_lambda.py +53 -0
  341. agno/tools/aws_ses.py +66 -0
  342. agno/tools/baidusearch.py +89 -0
  343. agno/tools/bitbucket.py +292 -0
  344. agno/tools/brandfetch.py +213 -0
  345. agno/tools/bravesearch.py +106 -0
  346. agno/tools/brightdata.py +367 -0
  347. agno/tools/browserbase.py +209 -0
  348. agno/tools/calcom.py +255 -0
  349. agno/tools/calculator.py +151 -0
  350. agno/tools/cartesia.py +187 -0
  351. agno/tools/clickup.py +244 -0
  352. agno/tools/confluence.py +240 -0
  353. agno/tools/crawl4ai.py +158 -0
  354. agno/tools/csv_toolkit.py +185 -0
  355. agno/tools/dalle.py +110 -0
  356. agno/tools/daytona.py +475 -0
  357. agno/tools/decorator.py +262 -0
  358. agno/tools/desi_vocal.py +108 -0
  359. agno/tools/discord.py +161 -0
  360. agno/tools/docker.py +716 -0
  361. agno/tools/duckdb.py +379 -0
  362. agno/tools/duckduckgo.py +91 -0
  363. agno/tools/e2b.py +703 -0
  364. agno/tools/eleven_labs.py +196 -0
  365. agno/tools/email.py +67 -0
  366. agno/tools/evm.py +129 -0
  367. agno/tools/exa.py +396 -0
  368. agno/tools/fal.py +127 -0
  369. agno/tools/file.py +240 -0
  370. agno/tools/file_generation.py +350 -0
  371. agno/tools/financial_datasets.py +288 -0
  372. agno/tools/firecrawl.py +143 -0
  373. agno/tools/function.py +1187 -0
  374. agno/tools/giphy.py +93 -0
  375. agno/tools/github.py +1760 -0
  376. agno/tools/gmail.py +922 -0
  377. agno/tools/google_bigquery.py +117 -0
  378. agno/tools/google_drive.py +270 -0
  379. agno/tools/google_maps.py +253 -0
  380. agno/tools/googlecalendar.py +674 -0
  381. agno/tools/googlesearch.py +98 -0
  382. agno/tools/googlesheets.py +377 -0
  383. agno/tools/hackernews.py +77 -0
  384. agno/tools/jina.py +101 -0
  385. agno/tools/jira.py +170 -0
  386. agno/tools/knowledge.py +218 -0
  387. agno/tools/linear.py +426 -0
  388. agno/tools/linkup.py +58 -0
  389. agno/tools/local_file_system.py +90 -0
  390. agno/tools/lumalab.py +183 -0
  391. agno/tools/mcp/__init__.py +10 -0
  392. agno/tools/mcp/mcp.py +331 -0
  393. agno/tools/mcp/multi_mcp.py +347 -0
  394. agno/tools/mcp/params.py +24 -0
  395. agno/tools/mcp_toolbox.py +284 -0
  396. agno/tools/mem0.py +193 -0
  397. agno/tools/memori.py +339 -0
  398. agno/tools/memory.py +419 -0
  399. agno/tools/mlx_transcribe.py +139 -0
  400. agno/tools/models/__init__.py +0 -0
  401. agno/tools/models/azure_openai.py +190 -0
  402. agno/tools/models/gemini.py +203 -0
  403. agno/tools/models/groq.py +158 -0
  404. agno/tools/models/morph.py +186 -0
  405. agno/tools/models/nebius.py +124 -0
  406. agno/tools/models_labs.py +195 -0
  407. agno/tools/moviepy_video.py +349 -0
  408. agno/tools/neo4j.py +134 -0
  409. agno/tools/newspaper.py +46 -0
  410. agno/tools/newspaper4k.py +93 -0
  411. agno/tools/notion.py +204 -0
  412. agno/tools/openai.py +202 -0
  413. agno/tools/openbb.py +160 -0
  414. agno/tools/opencv.py +321 -0
  415. agno/tools/openweather.py +233 -0
  416. agno/tools/oxylabs.py +385 -0
  417. agno/tools/pandas.py +102 -0
  418. agno/tools/parallel.py +314 -0
  419. agno/tools/postgres.py +257 -0
  420. agno/tools/pubmed.py +188 -0
  421. agno/tools/python.py +205 -0
  422. agno/tools/reasoning.py +283 -0
  423. agno/tools/reddit.py +467 -0
  424. agno/tools/replicate.py +117 -0
  425. agno/tools/resend.py +62 -0
  426. agno/tools/scrapegraph.py +222 -0
  427. agno/tools/searxng.py +152 -0
  428. agno/tools/serpapi.py +116 -0
  429. agno/tools/serper.py +255 -0
  430. agno/tools/shell.py +53 -0
  431. agno/tools/slack.py +136 -0
  432. agno/tools/sleep.py +20 -0
  433. agno/tools/spider.py +116 -0
  434. agno/tools/sql.py +154 -0
  435. agno/tools/streamlit/__init__.py +0 -0
  436. agno/tools/streamlit/components.py +113 -0
  437. agno/tools/tavily.py +254 -0
  438. agno/tools/telegram.py +48 -0
  439. agno/tools/todoist.py +218 -0
  440. agno/tools/tool_registry.py +1 -0
  441. agno/tools/toolkit.py +146 -0
  442. agno/tools/trafilatura.py +388 -0
  443. agno/tools/trello.py +274 -0
  444. agno/tools/twilio.py +186 -0
  445. agno/tools/user_control_flow.py +78 -0
  446. agno/tools/valyu.py +228 -0
  447. agno/tools/visualization.py +467 -0
  448. agno/tools/webbrowser.py +28 -0
  449. agno/tools/webex.py +76 -0
  450. agno/tools/website.py +54 -0
  451. agno/tools/webtools.py +45 -0
  452. agno/tools/whatsapp.py +286 -0
  453. agno/tools/wikipedia.py +63 -0
  454. agno/tools/workflow.py +278 -0
  455. agno/tools/x.py +335 -0
  456. agno/tools/yfinance.py +257 -0
  457. agno/tools/youtube.py +184 -0
  458. agno/tools/zendesk.py +82 -0
  459. agno/tools/zep.py +454 -0
  460. agno/tools/zoom.py +382 -0
  461. agno/utils/__init__.py +0 -0
  462. agno/utils/agent.py +820 -0
  463. agno/utils/audio.py +49 -0
  464. agno/utils/certs.py +27 -0
  465. agno/utils/code_execution.py +11 -0
  466. agno/utils/common.py +132 -0
  467. agno/utils/dttm.py +13 -0
  468. agno/utils/enum.py +22 -0
  469. agno/utils/env.py +11 -0
  470. agno/utils/events.py +696 -0
  471. agno/utils/format_str.py +16 -0
  472. agno/utils/functions.py +166 -0
  473. agno/utils/gemini.py +426 -0
  474. agno/utils/hooks.py +57 -0
  475. agno/utils/http.py +74 -0
  476. agno/utils/json_schema.py +234 -0
  477. agno/utils/knowledge.py +36 -0
  478. agno/utils/location.py +19 -0
  479. agno/utils/log.py +255 -0
  480. agno/utils/mcp.py +214 -0
  481. agno/utils/media.py +352 -0
  482. agno/utils/merge_dict.py +41 -0
  483. agno/utils/message.py +118 -0
  484. agno/utils/models/__init__.py +0 -0
  485. agno/utils/models/ai_foundry.py +43 -0
  486. agno/utils/models/claude.py +358 -0
  487. agno/utils/models/cohere.py +87 -0
  488. agno/utils/models/llama.py +78 -0
  489. agno/utils/models/mistral.py +98 -0
  490. agno/utils/models/openai_responses.py +140 -0
  491. agno/utils/models/schema_utils.py +153 -0
  492. agno/utils/models/watsonx.py +41 -0
  493. agno/utils/openai.py +257 -0
  494. agno/utils/pickle.py +32 -0
  495. agno/utils/pprint.py +178 -0
  496. agno/utils/print_response/__init__.py +0 -0
  497. agno/utils/print_response/agent.py +842 -0
  498. agno/utils/print_response/team.py +1724 -0
  499. agno/utils/print_response/workflow.py +1668 -0
  500. agno/utils/prompts.py +111 -0
  501. agno/utils/reasoning.py +108 -0
  502. agno/utils/response.py +163 -0
  503. agno/utils/response_iterator.py +17 -0
  504. agno/utils/safe_formatter.py +24 -0
  505. agno/utils/serialize.py +32 -0
  506. agno/utils/shell.py +22 -0
  507. agno/utils/streamlit.py +487 -0
  508. agno/utils/string.py +231 -0
  509. agno/utils/team.py +139 -0
  510. agno/utils/timer.py +41 -0
  511. agno/utils/tools.py +102 -0
  512. agno/utils/web.py +23 -0
  513. agno/utils/whatsapp.py +305 -0
  514. agno/utils/yaml_io.py +25 -0
  515. agno/vectordb/__init__.py +3 -0
  516. agno/vectordb/base.py +127 -0
  517. agno/vectordb/cassandra/__init__.py +5 -0
  518. agno/vectordb/cassandra/cassandra.py +501 -0
  519. agno/vectordb/cassandra/extra_param_mixin.py +11 -0
  520. agno/vectordb/cassandra/index.py +13 -0
  521. agno/vectordb/chroma/__init__.py +5 -0
  522. agno/vectordb/chroma/chromadb.py +929 -0
  523. agno/vectordb/clickhouse/__init__.py +9 -0
  524. agno/vectordb/clickhouse/clickhousedb.py +835 -0
  525. agno/vectordb/clickhouse/index.py +9 -0
  526. agno/vectordb/couchbase/__init__.py +3 -0
  527. agno/vectordb/couchbase/couchbase.py +1442 -0
  528. agno/vectordb/distance.py +7 -0
  529. agno/vectordb/lancedb/__init__.py +6 -0
  530. agno/vectordb/lancedb/lance_db.py +995 -0
  531. agno/vectordb/langchaindb/__init__.py +5 -0
  532. agno/vectordb/langchaindb/langchaindb.py +163 -0
  533. agno/vectordb/lightrag/__init__.py +5 -0
  534. agno/vectordb/lightrag/lightrag.py +388 -0
  535. agno/vectordb/llamaindex/__init__.py +3 -0
  536. agno/vectordb/llamaindex/llamaindexdb.py +166 -0
  537. agno/vectordb/milvus/__init__.py +4 -0
  538. agno/vectordb/milvus/milvus.py +1182 -0
  539. agno/vectordb/mongodb/__init__.py +9 -0
  540. agno/vectordb/mongodb/mongodb.py +1417 -0
  541. agno/vectordb/pgvector/__init__.py +12 -0
  542. agno/vectordb/pgvector/index.py +23 -0
  543. agno/vectordb/pgvector/pgvector.py +1462 -0
  544. agno/vectordb/pineconedb/__init__.py +5 -0
  545. agno/vectordb/pineconedb/pineconedb.py +747 -0
  546. agno/vectordb/qdrant/__init__.py +5 -0
  547. agno/vectordb/qdrant/qdrant.py +1134 -0
  548. agno/vectordb/redis/__init__.py +9 -0
  549. agno/vectordb/redis/redisdb.py +694 -0
  550. agno/vectordb/search.py +7 -0
  551. agno/vectordb/singlestore/__init__.py +10 -0
  552. agno/vectordb/singlestore/index.py +41 -0
  553. agno/vectordb/singlestore/singlestore.py +763 -0
  554. agno/vectordb/surrealdb/__init__.py +3 -0
  555. agno/vectordb/surrealdb/surrealdb.py +699 -0
  556. agno/vectordb/upstashdb/__init__.py +5 -0
  557. agno/vectordb/upstashdb/upstashdb.py +718 -0
  558. agno/vectordb/weaviate/__init__.py +8 -0
  559. agno/vectordb/weaviate/index.py +15 -0
  560. agno/vectordb/weaviate/weaviate.py +1005 -0
  561. agno/workflow/__init__.py +23 -0
  562. agno/workflow/agent.py +299 -0
  563. agno/workflow/condition.py +738 -0
  564. agno/workflow/loop.py +735 -0
  565. agno/workflow/parallel.py +824 -0
  566. agno/workflow/router.py +702 -0
  567. agno/workflow/step.py +1432 -0
  568. agno/workflow/steps.py +592 -0
  569. agno/workflow/types.py +520 -0
  570. agno/workflow/workflow.py +4321 -0
  571. agno-2.2.13.dist-info/METADATA +614 -0
  572. agno-2.2.13.dist-info/RECORD +575 -0
  573. agno-2.2.13.dist-info/WHEEL +5 -0
  574. agno-2.2.13.dist-info/licenses/LICENSE +201 -0
  575. agno-2.2.13.dist-info/top_level.txt +1 -0
@@ -0,0 +1,883 @@
1
+ from collections.abc import AsyncIterator
2
+ from dataclasses import dataclass
3
+ from os import getenv
4
+ from typing import Any, Dict, Iterator, List, Literal, Optional, Type, Union
5
+ from uuid import uuid4
6
+
7
+ import httpx
8
+ from pydantic import BaseModel
9
+
10
+ from agno.exceptions import ModelProviderError
11
+ from agno.media import Audio
12
+ from agno.models.base import Model
13
+ from agno.models.message import Message
14
+ from agno.models.metrics import Metrics
15
+ from agno.models.response import ModelResponse
16
+ from agno.run.agent import RunOutput
17
+ from agno.run.team import TeamRunOutput
18
+ from agno.utils.log import log_debug, log_error, log_warning
19
+ from agno.utils.openai import _format_file_for_message, audio_to_message, images_to_message
20
+ from agno.utils.reasoning import extract_thinking_content
21
+
22
+ try:
23
+ from openai import APIConnectionError, APIStatusError, RateLimitError
24
+ from openai import AsyncOpenAI as AsyncOpenAIClient
25
+ from openai import OpenAI as OpenAIClient
26
+ from openai.types import CompletionUsage
27
+ from openai.types.chat import ChatCompletion, ChatCompletionAudio, ChatCompletionChunk
28
+ from openai.types.chat.chat_completion_chunk import ChoiceDelta, ChoiceDeltaToolCall
29
+ except (ImportError, ModuleNotFoundError):
30
+ raise ImportError("`openai` not installed. Please install using `pip install openai`")
31
+
32
+
33
+ @dataclass
34
+ class OpenAIChat(Model):
35
+ """
36
+ A class for interacting with OpenAI models using the Chat completions API.
37
+
38
+ For more information, see: https://platform.openai.com/docs/api-reference/chat/create
39
+ """
40
+
41
+ id: str = "gpt-4o"
42
+ name: str = "OpenAIChat"
43
+ provider: str = "OpenAI"
44
+ supports_native_structured_outputs: bool = True
45
+
46
+ # Request parameters
47
+ store: Optional[bool] = None
48
+ reasoning_effort: Optional[str] = None
49
+ verbosity: Optional[Literal["low", "medium", "high"]] = None
50
+ metadata: Optional[Dict[str, Any]] = None
51
+ frequency_penalty: Optional[float] = None
52
+ logit_bias: Optional[Any] = None
53
+ logprobs: Optional[bool] = None
54
+ top_logprobs: Optional[int] = None
55
+ max_tokens: Optional[int] = None
56
+ max_completion_tokens: Optional[int] = None
57
+ modalities: Optional[List[str]] = None # "text" and/or "audio"
58
+ audio: Optional[Dict[str, Any]] = (
59
+ None # E.g. {"voice": "alloy", "format": "wav"}. `format` must be one of `wav`, `mp3`, `flac`, `opus`, or `pcm16`. `voice` must be one of `ash`, `ballad`, `coral`, `sage`, `verse`, `alloy`, `echo`, and `shimmer`.
60
+ )
61
+ presence_penalty: Optional[float] = None
62
+ seed: Optional[int] = None
63
+ stop: Optional[Union[str, List[str]]] = None
64
+ temperature: Optional[float] = None
65
+ user: Optional[str] = None
66
+ top_p: Optional[float] = None
67
+ service_tier: Optional[str] = None # "auto" | "default" | "flex" | "priority", defaults to "auto" when not set
68
+ 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
69
+ extra_headers: Optional[Any] = None
70
+ extra_query: Optional[Any] = None
71
+ extra_body: Optional[Any] = None
72
+ request_params: Optional[Dict[str, Any]] = None
73
+ role_map: Optional[Dict[str, str]] = None
74
+
75
+ # Client parameters
76
+ api_key: Optional[str] = None
77
+ organization: Optional[str] = None
78
+ base_url: Optional[Union[str, httpx.URL]] = None
79
+ timeout: Optional[float] = None
80
+ max_retries: Optional[int] = None
81
+ default_headers: Optional[Any] = None
82
+ default_query: Optional[Any] = None
83
+ http_client: Optional[Union[httpx.Client, httpx.AsyncClient]] = None
84
+ client_params: Optional[Dict[str, Any]] = None
85
+
86
+ # OpenAI clients
87
+ client: Optional[OpenAIClient] = None
88
+ async_client: Optional[AsyncOpenAIClient] = None
89
+
90
+ # The role to map the message role to.
91
+ default_role_map = {
92
+ "system": "developer",
93
+ "user": "user",
94
+ "assistant": "assistant",
95
+ "tool": "tool",
96
+ "model": "assistant",
97
+ }
98
+
99
+ def _get_client_params(self) -> Dict[str, Any]:
100
+ # Fetch API key from env if not already set
101
+ if not self.api_key:
102
+ self.api_key = getenv("OPENAI_API_KEY")
103
+ if not self.api_key:
104
+ log_error("OPENAI_API_KEY not set. Please set the OPENAI_API_KEY environment variable.")
105
+
106
+ # Define base client params
107
+ base_params = {
108
+ "api_key": self.api_key,
109
+ "organization": self.organization,
110
+ "base_url": self.base_url,
111
+ "timeout": self.timeout,
112
+ "max_retries": self.max_retries,
113
+ "default_headers": self.default_headers,
114
+ "default_query": self.default_query,
115
+ }
116
+
117
+ # Create client_params dict with non-None values
118
+ client_params = {k: v for k, v in base_params.items() if v is not None}
119
+
120
+ # Add additional client params if provided
121
+ if self.client_params:
122
+ client_params.update(self.client_params)
123
+ return client_params
124
+
125
+ def get_client(self) -> OpenAIClient:
126
+ """
127
+ Returns an OpenAI client.
128
+
129
+ Returns:
130
+ OpenAIClient: An instance of the OpenAI client.
131
+ """
132
+ if self.client and not self.client.is_closed():
133
+ return self.client
134
+
135
+ client_params: Dict[str, Any] = self._get_client_params()
136
+ if self.http_client:
137
+ if isinstance(self.http_client, httpx.Client):
138
+ client_params["http_client"] = self.http_client
139
+ else:
140
+ log_debug("http_client is not an instance of httpx.Client.")
141
+
142
+ self.client = OpenAIClient(**client_params)
143
+ return self.client
144
+
145
+ def get_async_client(self) -> AsyncOpenAIClient:
146
+ """
147
+ Returns an asynchronous OpenAI client.
148
+
149
+ Returns:
150
+ AsyncOpenAIClient: An instance of the asynchronous OpenAI client.
151
+ """
152
+ if self.async_client and not self.async_client.is_closed():
153
+ return self.async_client
154
+
155
+ 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
158
+ 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
+ )
165
+ self.async_client = AsyncOpenAIClient(**client_params)
166
+ return self.async_client
167
+
168
+ def get_request_params(
169
+ self,
170
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
171
+ tools: Optional[List[Dict[str, Any]]] = None,
172
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
173
+ run_response: Optional[Union[RunOutput, TeamRunOutput]] = None,
174
+ ) -> Dict[str, Any]:
175
+ """
176
+ Returns keyword arguments for API requests.
177
+
178
+ Returns:
179
+ Dict[str, Any]: A dictionary of keyword arguments for API requests.
180
+ """
181
+ # Define base request parameters
182
+ base_params = {
183
+ "store": self.store,
184
+ "reasoning_effort": self.reasoning_effort,
185
+ "verbosity": self.verbosity,
186
+ "frequency_penalty": self.frequency_penalty,
187
+ "logit_bias": self.logit_bias,
188
+ "logprobs": self.logprobs,
189
+ "top_logprobs": self.top_logprobs,
190
+ "max_tokens": self.max_tokens,
191
+ "max_completion_tokens": self.max_completion_tokens,
192
+ "modalities": self.modalities,
193
+ "audio": self.audio,
194
+ "presence_penalty": self.presence_penalty,
195
+ "seed": self.seed,
196
+ "stop": self.stop,
197
+ "temperature": self.temperature,
198
+ "user": self.user,
199
+ "top_p": self.top_p,
200
+ "extra_headers": self.extra_headers,
201
+ "extra_query": self.extra_query,
202
+ "extra_body": self.extra_body,
203
+ "metadata": self.metadata,
204
+ "service_tier": self.service_tier,
205
+ }
206
+
207
+ # Handle response format - always use JSON schema approach
208
+ if response_format is not None:
209
+ if isinstance(response_format, type) and issubclass(response_format, BaseModel):
210
+ # Convert Pydantic to JSON schema for regular endpoint
211
+ from agno.utils.models.schema_utils import get_response_schema_for_provider
212
+
213
+ schema = get_response_schema_for_provider(response_format, "openai")
214
+ base_params["response_format"] = {
215
+ "type": "json_schema",
216
+ "json_schema": {
217
+ "name": response_format.__name__,
218
+ "schema": schema,
219
+ "strict": self.strict_output,
220
+ },
221
+ }
222
+ else:
223
+ # Handle other response format types (like {"type": "json_object"})
224
+ base_params["response_format"] = response_format
225
+
226
+ # Filter out None values
227
+ request_params = {k: v for k, v in base_params.items() if v is not None}
228
+
229
+ # Add tools
230
+ if tools is not None and len(tools) > 0:
231
+ # Remove unsupported fields for OpenAILike models
232
+ if self.provider in ["AIMLAPI", "Fireworks", "Nvidia"]:
233
+ for tool in tools:
234
+ if tool.get("type") == "function":
235
+ if tool["function"].get("requires_confirmation") is not None:
236
+ del tool["function"]["requires_confirmation"]
237
+ if tool["function"].get("external_execution") is not None:
238
+ del tool["function"]["external_execution"]
239
+
240
+ request_params["tools"] = tools
241
+
242
+ if tool_choice is not None:
243
+ request_params["tool_choice"] = tool_choice
244
+
245
+ # Add additional request params if provided
246
+ if self.request_params:
247
+ request_params.update(self.request_params)
248
+
249
+ if request_params:
250
+ log_debug(f"Calling {self.provider} with request parameters: {request_params}", log_level=2)
251
+ return request_params
252
+
253
+ def to_dict(self) -> Dict[str, Any]:
254
+ """
255
+ Convert the model to a dictionary.
256
+
257
+ Returns:
258
+ Dict[str, Any]: The dictionary representation of the model.
259
+ """
260
+ model_dict = super().to_dict()
261
+ model_dict.update(
262
+ {
263
+ "store": self.store,
264
+ "reasoning_effort": self.reasoning_effort,
265
+ "verbosity": self.verbosity,
266
+ "frequency_penalty": self.frequency_penalty,
267
+ "logit_bias": self.logit_bias,
268
+ "logprobs": self.logprobs,
269
+ "top_logprobs": self.top_logprobs,
270
+ "max_tokens": self.max_tokens,
271
+ "max_completion_tokens": self.max_completion_tokens,
272
+ "modalities": self.modalities,
273
+ "audio": self.audio,
274
+ "presence_penalty": self.presence_penalty,
275
+ "seed": self.seed,
276
+ "stop": self.stop,
277
+ "temperature": self.temperature,
278
+ "top_p": self.top_p,
279
+ "user": self.user,
280
+ "extra_headers": self.extra_headers,
281
+ "extra_query": self.extra_query,
282
+ "extra_body": self.extra_body,
283
+ "service_tier": self.service_tier,
284
+ }
285
+ )
286
+ cleaned_dict = {k: v for k, v in model_dict.items() if v is not None}
287
+ return cleaned_dict
288
+
289
+ def _format_message(self, message: Message) -> Dict[str, Any]:
290
+ """
291
+ Format a message into the format expected by OpenAI.
292
+
293
+ Args:
294
+ message (Message): The message to format.
295
+
296
+ Returns:
297
+ Dict[str, Any]: The formatted message.
298
+ """
299
+ message_dict: Dict[str, Any] = {
300
+ "role": self.role_map[message.role] if self.role_map else self.default_role_map[message.role],
301
+ "content": message.content,
302
+ "name": message.name,
303
+ "tool_call_id": message.tool_call_id,
304
+ "tool_calls": message.tool_calls,
305
+ }
306
+ message_dict = {k: v for k, v in message_dict.items() if v is not None}
307
+
308
+ # Ignore non-string message content
309
+ # because we assume that the images/audio are already added to the message
310
+ if (message.images is not None and len(message.images) > 0) or (
311
+ message.audio is not None and len(message.audio) > 0
312
+ ):
313
+ # Ignore non-string message content
314
+ # because we assume that the images/audio are already added to the message
315
+ if isinstance(message.content, str):
316
+ message_dict["content"] = [{"type": "text", "text": message.content}]
317
+ if message.images is not None:
318
+ message_dict["content"].extend(images_to_message(images=message.images))
319
+
320
+ if message.audio is not None:
321
+ message_dict["content"].extend(audio_to_message(audio=message.audio))
322
+
323
+ if message.audio_output is not None:
324
+ message_dict["content"] = ""
325
+ message_dict["audio"] = {"id": message.audio_output.id}
326
+
327
+ if message.videos is not None and len(message.videos) > 0:
328
+ log_warning("Video input is currently unsupported.")
329
+
330
+ # OpenAI expects the tool_calls to be None if empty, not an empty list
331
+ if message.tool_calls is not None and len(message.tool_calls) == 0:
332
+ message_dict["tool_calls"] = None
333
+
334
+ if message.files is not None:
335
+ # Ensure content is a list of parts
336
+ content = message_dict.get("content")
337
+ if isinstance(content, str): # wrap existing text
338
+ text = content
339
+ message_dict["content"] = [{"type": "text", "text": text}]
340
+ elif content is None:
341
+ message_dict["content"] = []
342
+ # Insert each file part before text parts
343
+ for file in message.files:
344
+ file_part = _format_file_for_message(file)
345
+ if file_part:
346
+ message_dict["content"].insert(0, file_part)
347
+
348
+ # Manually add the content field even if it is None
349
+ if message.content is None:
350
+ message_dict["content"] = ""
351
+ return message_dict
352
+
353
+ def invoke(
354
+ self,
355
+ messages: List[Message],
356
+ assistant_message: Message,
357
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
358
+ tools: Optional[List[Dict[str, Any]]] = None,
359
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
360
+ run_response: Optional[Union[RunOutput, TeamRunOutput]] = None,
361
+ ) -> ModelResponse:
362
+ """
363
+ Send a chat completion request to the OpenAI API and parse the response.
364
+
365
+ Args:
366
+ messages (List[Message]): A list of messages to send to the model.
367
+ assistant_message (Message): The assistant message to populate.
368
+ response_format (Optional[Union[Dict, Type[BaseModel]]]): The response format to use.
369
+ tools (Optional[List[Dict[str, Any]]]): The tools to use.
370
+ tool_choice (Optional[Union[str, Dict[str, Any]]]): The tool choice to use.
371
+
372
+ Returns:
373
+ ModelResponse: The chat completion response from the API.
374
+ """
375
+ try:
376
+ if run_response and run_response.metrics:
377
+ run_response.metrics.set_time_to_first_token()
378
+
379
+ assistant_message.metrics.start_timer()
380
+
381
+ provider_response = self.get_client().chat.completions.create(
382
+ model=self.id,
383
+ messages=[self._format_message(m) for m in messages], # type: ignore
384
+ **self.get_request_params(
385
+ response_format=response_format, tools=tools, tool_choice=tool_choice, run_response=run_response
386
+ ),
387
+ )
388
+ assistant_message.metrics.stop_timer()
389
+
390
+ # Parse the response into an Agno ModelResponse object
391
+ model_response = self._parse_provider_response(provider_response, response_format=response_format)
392
+
393
+ return model_response
394
+
395
+ except RateLimitError as e:
396
+ log_error(f"Rate limit error from OpenAI API: {e}")
397
+ error_message = e.response.json().get("error", {})
398
+ error_message = (
399
+ error_message.get("message", "Unknown model error")
400
+ if isinstance(error_message, dict)
401
+ else error_message
402
+ )
403
+ raise ModelProviderError(
404
+ message=error_message,
405
+ status_code=e.response.status_code,
406
+ model_name=self.name,
407
+ model_id=self.id,
408
+ ) from e
409
+ except APIConnectionError as e:
410
+ log_error(f"API connection error from OpenAI API: {e}")
411
+ raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
412
+ except APIStatusError as e:
413
+ log_error(f"API status error from OpenAI API: {e}")
414
+ try:
415
+ error_message = e.response.json().get("error", {})
416
+ except Exception:
417
+ error_message = e.response.text
418
+ error_message = (
419
+ error_message.get("message", "Unknown model error")
420
+ if isinstance(error_message, dict)
421
+ else error_message
422
+ )
423
+ raise ModelProviderError(
424
+ message=error_message,
425
+ status_code=e.response.status_code,
426
+ model_name=self.name,
427
+ model_id=self.id,
428
+ ) from e
429
+ except Exception as e:
430
+ log_error(f"Error from OpenAI API: {e}")
431
+ raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
432
+
433
+ async def ainvoke(
434
+ self,
435
+ messages: List[Message],
436
+ assistant_message: Message,
437
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
438
+ tools: Optional[List[Dict[str, Any]]] = None,
439
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
440
+ run_response: Optional[Union[RunOutput, TeamRunOutput]] = None,
441
+ ) -> ModelResponse:
442
+ """
443
+ Sends an asynchronous chat completion request to the OpenAI API.
444
+
445
+ Args:
446
+ messages (List[Message]): A list of messages to send to the model.
447
+ assistant_message (Message): The assistant message to populate.
448
+ response_format (Optional[Union[Dict, Type[BaseModel]]]): The response format to use.
449
+ tools (Optional[List[Dict[str, Any]]]): The tools to use.
450
+ tool_choice (Optional[Union[str, Dict[str, Any]]]): The tool choice to use.
451
+
452
+ Returns:
453
+ ModelResponse: The chat completion response from the API.
454
+ """
455
+ try:
456
+ if run_response and run_response.metrics:
457
+ run_response.metrics.set_time_to_first_token()
458
+
459
+ assistant_message.metrics.start_timer()
460
+ response = await self.get_async_client().chat.completions.create(
461
+ model=self.id,
462
+ messages=[self._format_message(m) for m in messages], # type: ignore
463
+ **self.get_request_params(
464
+ response_format=response_format, tools=tools, tool_choice=tool_choice, run_response=run_response
465
+ ),
466
+ )
467
+ assistant_message.metrics.stop_timer()
468
+
469
+ # Parse the response into an Agno ModelResponse object
470
+ provider_response: ModelResponse = self._parse_provider_response(response, response_format=response_format)
471
+
472
+ return provider_response
473
+
474
+ except RateLimitError as e:
475
+ log_error(f"Rate limit error from OpenAI API: {e}")
476
+ error_message = e.response.json().get("error", {})
477
+ error_message = (
478
+ error_message.get("message", "Unknown model error")
479
+ if isinstance(error_message, dict)
480
+ else error_message
481
+ )
482
+ raise ModelProviderError(
483
+ message=error_message,
484
+ status_code=e.response.status_code,
485
+ model_name=self.name,
486
+ model_id=self.id,
487
+ ) from e
488
+ except APIConnectionError as e:
489
+ log_error(f"API connection error from OpenAI API: {e}")
490
+ raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
491
+ except APIStatusError as e:
492
+ log_error(f"API status error from OpenAI API: {e}")
493
+ try:
494
+ error_message = e.response.json().get("error", {})
495
+ except Exception:
496
+ error_message = e.response.text
497
+ error_message = (
498
+ error_message.get("message", "Unknown model error")
499
+ if isinstance(error_message, dict)
500
+ else error_message
501
+ )
502
+ raise ModelProviderError(
503
+ message=error_message,
504
+ status_code=e.response.status_code,
505
+ model_name=self.name,
506
+ model_id=self.id,
507
+ ) from e
508
+ except Exception as e:
509
+ log_error(f"Error from OpenAI API: {e}")
510
+ raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
511
+
512
+ def invoke_stream(
513
+ self,
514
+ messages: List[Message],
515
+ assistant_message: Message,
516
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
517
+ tools: Optional[List[Dict[str, Any]]] = None,
518
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
519
+ run_response: Optional[Union[RunOutput, TeamRunOutput]] = None,
520
+ ) -> Iterator[ModelResponse]:
521
+ """
522
+ Send a streaming chat completion request to the OpenAI API.
523
+
524
+ Args:
525
+ messages (List[Message]): A list of messages to send to the model.
526
+
527
+ Returns:
528
+ Iterator[ModelResponse]: An iterator of model responses.
529
+ """
530
+
531
+ try:
532
+ if run_response and run_response.metrics:
533
+ run_response.metrics.set_time_to_first_token()
534
+
535
+ assistant_message.metrics.start_timer()
536
+
537
+ for chunk in self.get_client().chat.completions.create(
538
+ model=self.id,
539
+ messages=[self._format_message(m) for m in messages], # type: ignore
540
+ stream=True,
541
+ stream_options={"include_usage": True},
542
+ **self.get_request_params(
543
+ response_format=response_format, tools=tools, tool_choice=tool_choice, run_response=run_response
544
+ ),
545
+ ):
546
+ yield self._parse_provider_response_delta(chunk)
547
+
548
+ assistant_message.metrics.stop_timer()
549
+
550
+ except RateLimitError as e:
551
+ log_error(f"Rate limit error from OpenAI API: {e}")
552
+ error_message = e.response.json().get("error", {})
553
+ error_message = (
554
+ error_message.get("message", "Unknown model error")
555
+ if isinstance(error_message, dict)
556
+ else error_message
557
+ )
558
+ raise ModelProviderError(
559
+ message=error_message,
560
+ status_code=e.response.status_code,
561
+ model_name=self.name,
562
+ model_id=self.id,
563
+ ) from e
564
+ except APIConnectionError as e:
565
+ log_error(f"API connection error from OpenAI API: {e}")
566
+ raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
567
+ except APIStatusError as e:
568
+ log_error(f"API status error from OpenAI API: {e}")
569
+ try:
570
+ error_message = e.response.json().get("error", {})
571
+ except Exception:
572
+ error_message = e.response.text
573
+ error_message = (
574
+ error_message.get("message", "Unknown model error")
575
+ if isinstance(error_message, dict)
576
+ else error_message
577
+ )
578
+ raise ModelProviderError(
579
+ message=error_message,
580
+ status_code=e.response.status_code,
581
+ model_name=self.name,
582
+ model_id=self.id,
583
+ ) from e
584
+ except Exception as e:
585
+ log_error(f"Error from OpenAI API: {e}")
586
+ raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
587
+
588
+ async def ainvoke_stream(
589
+ self,
590
+ messages: List[Message],
591
+ assistant_message: Message,
592
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
593
+ tools: Optional[List[Dict[str, Any]]] = None,
594
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
595
+ run_response: Optional[Union[RunOutput, TeamRunOutput]] = None,
596
+ ) -> AsyncIterator[ModelResponse]:
597
+ """
598
+ Sends an asynchronous streaming chat completion request to the OpenAI API.
599
+
600
+ Args:
601
+ messages (List[Message]): A list of messages to send to the model.
602
+
603
+ Returns:
604
+ Any: An asynchronous iterator of model responses.
605
+ """
606
+
607
+ try:
608
+ if run_response and run_response.metrics:
609
+ run_response.metrics.set_time_to_first_token()
610
+
611
+ assistant_message.metrics.start_timer()
612
+
613
+ async_stream = await self.get_async_client().chat.completions.create(
614
+ model=self.id,
615
+ messages=[self._format_message(m) for m in messages], # type: ignore
616
+ stream=True,
617
+ stream_options={"include_usage": True},
618
+ **self.get_request_params(
619
+ response_format=response_format, tools=tools, tool_choice=tool_choice, run_response=run_response
620
+ ),
621
+ )
622
+
623
+ async for chunk in async_stream:
624
+ yield self._parse_provider_response_delta(chunk)
625
+
626
+ assistant_message.metrics.stop_timer()
627
+
628
+ except RateLimitError as e:
629
+ log_error(f"Rate limit error from OpenAI API: {e}")
630
+ error_message = e.response.json().get("error", {})
631
+ error_message = (
632
+ error_message.get("message", "Unknown model error")
633
+ if isinstance(error_message, dict)
634
+ else error_message
635
+ )
636
+ raise ModelProviderError(
637
+ message=error_message,
638
+ status_code=e.response.status_code,
639
+ model_name=self.name,
640
+ model_id=self.id,
641
+ ) from e
642
+ except APIConnectionError as e:
643
+ log_error(f"API connection error from OpenAI API: {e}")
644
+ raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
645
+ except APIStatusError as e:
646
+ log_error(f"API status error from OpenAI API: {e}")
647
+ try:
648
+ error_message = e.response.json().get("error", {})
649
+ except Exception:
650
+ error_message = e.response.text
651
+ error_message = (
652
+ error_message.get("message", "Unknown model error")
653
+ if isinstance(error_message, dict)
654
+ else error_message
655
+ )
656
+ raise ModelProviderError(
657
+ message=error_message,
658
+ status_code=e.response.status_code,
659
+ model_name=self.name,
660
+ model_id=self.id,
661
+ ) from e
662
+ except Exception as e:
663
+ log_error(f"Error from OpenAI API: {e}")
664
+ raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
665
+
666
+ @staticmethod
667
+ def parse_tool_calls(tool_calls_data: List[ChoiceDeltaToolCall]) -> List[Dict[str, Any]]:
668
+ """
669
+ Build tool calls from streamed tool call data.
670
+
671
+ Args:
672
+ tool_calls_data (List[ChoiceDeltaToolCall]): The tool call data to build from.
673
+
674
+ Returns:
675
+ List[Dict[str, Any]]: The built tool calls.
676
+ """
677
+ tool_calls: List[Dict[str, Any]] = []
678
+ for _tool_call in tool_calls_data:
679
+ _index = _tool_call.index or 0
680
+ _tool_call_id = _tool_call.id
681
+ _tool_call_type = _tool_call.type
682
+ _function_name = _tool_call.function.name if _tool_call.function else None
683
+ _function_arguments = _tool_call.function.arguments if _tool_call.function else None
684
+
685
+ if len(tool_calls) <= _index:
686
+ tool_calls.extend([{}] * (_index - len(tool_calls) + 1))
687
+ tool_call_entry = tool_calls[_index]
688
+ if not tool_call_entry:
689
+ tool_call_entry["id"] = _tool_call_id
690
+ tool_call_entry["type"] = _tool_call_type
691
+ tool_call_entry["function"] = {
692
+ "name": _function_name or "",
693
+ "arguments": _function_arguments or "",
694
+ }
695
+ else:
696
+ if _function_name:
697
+ tool_call_entry["function"]["name"] += _function_name
698
+ if _function_arguments:
699
+ tool_call_entry["function"]["arguments"] += _function_arguments
700
+ if _tool_call_id:
701
+ tool_call_entry["id"] = _tool_call_id
702
+ if _tool_call_type:
703
+ tool_call_entry["type"] = _tool_call_type
704
+ return tool_calls
705
+
706
+ def _parse_provider_response(
707
+ self,
708
+ response: ChatCompletion,
709
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
710
+ ) -> ModelResponse:
711
+ """
712
+ Parse the OpenAI response into a ModelResponse.
713
+ """
714
+ model_response = ModelResponse()
715
+
716
+ if hasattr(response, "error") and response.error: # type: ignore
717
+ raise ModelProviderError(
718
+ message=response.error.get("message", "Unknown model error"), # type: ignore
719
+ model_name=self.name,
720
+ model_id=self.id,
721
+ )
722
+
723
+ # Get response message
724
+ response_message = response.choices[0].message
725
+
726
+ # Add role
727
+ if response_message.role is not None:
728
+ model_response.role = response_message.role
729
+
730
+ # Add content
731
+ if response_message.content is not None:
732
+ model_response.content = response_message.content
733
+
734
+ # Extract thinking content before any structured parsing
735
+ if model_response.content:
736
+ reasoning_content, output_content = extract_thinking_content(model_response.content)
737
+ if reasoning_content:
738
+ model_response.reasoning_content = reasoning_content
739
+ model_response.content = output_content
740
+ # Add tool calls
741
+ if response_message.tool_calls is not None and len(response_message.tool_calls) > 0:
742
+ try:
743
+ model_response.tool_calls = [t.model_dump() for t in response_message.tool_calls]
744
+ except Exception as e:
745
+ log_warning(f"Error processing tool calls: {e}")
746
+
747
+ # Add audio transcript to content if available
748
+ response_audio: Optional[ChatCompletionAudio] = response_message.audio
749
+ if response_audio and response_audio.transcript and not model_response.content:
750
+ model_response.content = response_audio.transcript
751
+
752
+ # Add audio if present
753
+ if hasattr(response_message, "audio") and response_message.audio is not None:
754
+ # If the audio output modality is requested, we can extract an audio response
755
+ try:
756
+ if isinstance(response_message.audio, dict):
757
+ model_response.audio = Audio(
758
+ id=response_message.audio.get("id"),
759
+ content=response_message.audio.get("data"),
760
+ expires_at=response_message.audio.get("expires_at"),
761
+ transcript=response_message.audio.get("transcript"),
762
+ )
763
+ else:
764
+ model_response.audio = Audio(
765
+ id=response_message.audio.id,
766
+ content=response_message.audio.data,
767
+ expires_at=response_message.audio.expires_at,
768
+ transcript=response_message.audio.transcript,
769
+ )
770
+ except Exception as e:
771
+ log_warning(f"Error processing audio: {e}")
772
+
773
+ if hasattr(response_message, "reasoning_content") and response_message.reasoning_content is not None: # type: ignore
774
+ model_response.reasoning_content = response_message.reasoning_content # type: ignore
775
+
776
+ if response.usage is not None:
777
+ model_response.response_usage = self._get_metrics(response.usage)
778
+
779
+ return model_response
780
+
781
+ def _parse_provider_response_delta(self, response_delta: ChatCompletionChunk) -> ModelResponse:
782
+ """
783
+ Parse the OpenAI streaming response into a ModelResponse.
784
+
785
+ Args:
786
+ response_delta: Raw response chunk from OpenAI
787
+
788
+ Returns:
789
+ ModelResponse: Parsed response data
790
+ """
791
+ model_response = ModelResponse()
792
+
793
+ if response_delta.choices and len(response_delta.choices) > 0:
794
+ choice_delta: ChoiceDelta = response_delta.choices[0].delta
795
+
796
+ if choice_delta:
797
+ # Add content
798
+ if choice_delta.content is not None:
799
+ model_response.content = choice_delta.content
800
+
801
+ # Add tool calls
802
+ if choice_delta.tool_calls is not None:
803
+ model_response.tool_calls = choice_delta.tool_calls # type: ignore
804
+
805
+ if hasattr(choice_delta, "reasoning_content") and choice_delta.reasoning_content is not None:
806
+ model_response.reasoning_content = choice_delta.reasoning_content
807
+
808
+ # Add audio if present
809
+ if hasattr(choice_delta, "audio") and choice_delta.audio is not None:
810
+ try:
811
+ audio_data = None
812
+ audio_id = None
813
+ audio_expires_at = None
814
+ audio_transcript = None
815
+
816
+ if isinstance(choice_delta.audio, dict):
817
+ audio_data = choice_delta.audio.get("data")
818
+ audio_id = choice_delta.audio.get("id")
819
+ audio_expires_at = choice_delta.audio.get("expires_at")
820
+ audio_transcript = choice_delta.audio.get("transcript")
821
+ else:
822
+ audio_data = choice_delta.audio.data
823
+ audio_id = choice_delta.audio.id
824
+ audio_expires_at = choice_delta.audio.expires_at
825
+ audio_transcript = choice_delta.audio.transcript
826
+
827
+ # Only create Audio object if there's actual content
828
+ if audio_data is not None:
829
+ model_response.audio = Audio(
830
+ id=audio_id,
831
+ content=audio_data,
832
+ expires_at=audio_expires_at,
833
+ transcript=audio_transcript,
834
+ sample_rate=24000,
835
+ mime_type="pcm16",
836
+ )
837
+ # If no content but there's transcript/metadata, create minimal Audio object
838
+ elif audio_transcript is not None or audio_id is not None:
839
+ model_response.audio = Audio(
840
+ id=audio_id or str(uuid4()),
841
+ content=b"",
842
+ expires_at=audio_expires_at,
843
+ transcript=audio_transcript,
844
+ sample_rate=24000,
845
+ mime_type="pcm16",
846
+ )
847
+ except Exception as e:
848
+ log_warning(f"Error processing audio: {e}")
849
+
850
+ # Add usage metrics if present
851
+ if response_delta.usage is not None:
852
+ model_response.response_usage = self._get_metrics(response_delta.usage)
853
+
854
+ return model_response
855
+
856
+ def _get_metrics(self, response_usage: CompletionUsage) -> Metrics:
857
+ """
858
+ Parse the given OpenAI-specific usage into an Agno Metrics object.
859
+
860
+ Args:
861
+ response_usage: Usage data from OpenAI
862
+
863
+ Returns:
864
+ Metrics: Parsed metrics data
865
+ """
866
+
867
+ metrics = Metrics()
868
+
869
+ metrics.input_tokens = response_usage.prompt_tokens or 0
870
+ metrics.output_tokens = response_usage.completion_tokens or 0
871
+ metrics.total_tokens = response_usage.total_tokens or 0
872
+
873
+ # Add the prompt_tokens_details field
874
+ if prompt_token_details := response_usage.prompt_tokens_details:
875
+ metrics.audio_input_tokens = prompt_token_details.audio_tokens or 0
876
+ metrics.cache_read_tokens = prompt_token_details.cached_tokens or 0
877
+
878
+ # Add the completion_tokens_details field
879
+ if completion_tokens_details := response_usage.completion_tokens_details:
880
+ metrics.audio_output_tokens = completion_tokens_details.audio_tokens or 0
881
+ metrics.reasoning_tokens = completion_tokens_details.reasoning_tokens or 0
882
+
883
+ return metrics