agno 1.8.1__py3-none-any.whl → 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (590) hide show
  1. agno/__init__.py +8 -0
  2. agno/agent/__init__.py +19 -27
  3. agno/agent/agent.py +3143 -4170
  4. agno/api/agent.py +11 -67
  5. agno/api/api.py +5 -46
  6. agno/api/evals.py +8 -19
  7. agno/api/os.py +17 -0
  8. agno/api/routes.py +6 -41
  9. agno/api/schemas/__init__.py +9 -0
  10. agno/api/schemas/agent.py +5 -21
  11. agno/api/schemas/evals.py +7 -16
  12. agno/api/schemas/os.py +14 -0
  13. agno/api/schemas/team.py +5 -21
  14. agno/api/schemas/utils.py +21 -0
  15. agno/api/schemas/workflows.py +11 -7
  16. agno/api/settings.py +53 -0
  17. agno/api/team.py +11 -66
  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/db/__init__.py +24 -0
  25. agno/db/base.py +245 -0
  26. agno/db/dynamo/__init__.py +3 -0
  27. agno/db/dynamo/dynamo.py +1743 -0
  28. agno/db/dynamo/schemas.py +278 -0
  29. agno/db/dynamo/utils.py +684 -0
  30. agno/db/firestore/__init__.py +3 -0
  31. agno/db/firestore/firestore.py +1432 -0
  32. agno/db/firestore/schemas.py +130 -0
  33. agno/db/firestore/utils.py +278 -0
  34. agno/db/gcs_json/__init__.py +3 -0
  35. agno/db/gcs_json/gcs_json_db.py +1001 -0
  36. agno/db/gcs_json/utils.py +194 -0
  37. agno/db/in_memory/__init__.py +3 -0
  38. agno/db/in_memory/in_memory_db.py +882 -0
  39. agno/db/in_memory/utils.py +172 -0
  40. agno/db/json/__init__.py +3 -0
  41. agno/db/json/json_db.py +1045 -0
  42. agno/db/json/utils.py +196 -0
  43. agno/db/migrations/v1_to_v2.py +162 -0
  44. agno/db/mongo/__init__.py +3 -0
  45. agno/db/mongo/mongo.py +1416 -0
  46. agno/db/mongo/schemas.py +77 -0
  47. agno/db/mongo/utils.py +204 -0
  48. agno/db/mysql/__init__.py +3 -0
  49. agno/db/mysql/mysql.py +1719 -0
  50. agno/db/mysql/schemas.py +124 -0
  51. agno/db/mysql/utils.py +297 -0
  52. agno/db/postgres/__init__.py +3 -0
  53. agno/db/postgres/postgres.py +1710 -0
  54. agno/db/postgres/schemas.py +124 -0
  55. agno/db/postgres/utils.py +280 -0
  56. agno/db/redis/__init__.py +3 -0
  57. agno/db/redis/redis.py +1367 -0
  58. agno/db/redis/schemas.py +109 -0
  59. agno/db/redis/utils.py +288 -0
  60. agno/db/schemas/__init__.py +3 -0
  61. agno/db/schemas/evals.py +33 -0
  62. agno/db/schemas/knowledge.py +40 -0
  63. agno/db/schemas/memory.py +46 -0
  64. agno/db/singlestore/__init__.py +3 -0
  65. agno/db/singlestore/schemas.py +116 -0
  66. agno/db/singlestore/singlestore.py +1712 -0
  67. agno/db/singlestore/utils.py +326 -0
  68. agno/db/sqlite/__init__.py +3 -0
  69. agno/db/sqlite/schemas.py +119 -0
  70. agno/db/sqlite/sqlite.py +1676 -0
  71. agno/db/sqlite/utils.py +268 -0
  72. agno/db/utils.py +88 -0
  73. agno/eval/__init__.py +14 -0
  74. agno/eval/accuracy.py +154 -48
  75. agno/eval/performance.py +88 -23
  76. agno/eval/reliability.py +73 -20
  77. agno/eval/utils.py +23 -13
  78. agno/integrations/discord/__init__.py +3 -0
  79. agno/{app → integrations}/discord/client.py +15 -11
  80. agno/knowledge/__init__.py +2 -2
  81. agno/{document → knowledge}/chunking/agentic.py +2 -2
  82. agno/{document → knowledge}/chunking/document.py +2 -2
  83. agno/{document → knowledge}/chunking/fixed.py +3 -3
  84. agno/{document → knowledge}/chunking/markdown.py +2 -2
  85. agno/{document → knowledge}/chunking/recursive.py +2 -2
  86. agno/{document → knowledge}/chunking/row.py +2 -2
  87. agno/knowledge/chunking/semantic.py +59 -0
  88. agno/knowledge/chunking/strategy.py +121 -0
  89. agno/knowledge/content.py +74 -0
  90. agno/knowledge/document/__init__.py +5 -0
  91. agno/{document → knowledge/document}/base.py +12 -2
  92. agno/knowledge/embedder/__init__.py +5 -0
  93. agno/{embedder → knowledge/embedder}/aws_bedrock.py +127 -1
  94. agno/{embedder → knowledge/embedder}/azure_openai.py +65 -1
  95. agno/{embedder → knowledge/embedder}/base.py +6 -0
  96. agno/{embedder → knowledge/embedder}/cohere.py +72 -1
  97. agno/{embedder → knowledge/embedder}/fastembed.py +17 -1
  98. agno/{embedder → knowledge/embedder}/fireworks.py +1 -1
  99. agno/{embedder → knowledge/embedder}/google.py +74 -1
  100. agno/{embedder → knowledge/embedder}/huggingface.py +36 -2
  101. agno/{embedder → knowledge/embedder}/jina.py +48 -2
  102. agno/knowledge/embedder/langdb.py +22 -0
  103. agno/knowledge/embedder/mistral.py +139 -0
  104. agno/{embedder → knowledge/embedder}/nebius.py +1 -1
  105. agno/{embedder → knowledge/embedder}/ollama.py +54 -3
  106. agno/knowledge/embedder/openai.py +223 -0
  107. agno/{embedder → knowledge/embedder}/sentence_transformer.py +16 -1
  108. agno/{embedder → knowledge/embedder}/together.py +1 -1
  109. agno/{embedder → knowledge/embedder}/voyageai.py +49 -1
  110. agno/knowledge/knowledge.py +1551 -0
  111. agno/knowledge/reader/__init__.py +7 -0
  112. agno/{document → knowledge}/reader/arxiv_reader.py +32 -4
  113. agno/knowledge/reader/base.py +88 -0
  114. agno/{document → knowledge}/reader/csv_reader.py +47 -65
  115. agno/knowledge/reader/docx_reader.py +83 -0
  116. agno/{document → knowledge}/reader/firecrawl_reader.py +42 -21
  117. agno/{document → knowledge}/reader/json_reader.py +30 -9
  118. agno/{document → knowledge}/reader/markdown_reader.py +58 -9
  119. agno/{document → knowledge}/reader/pdf_reader.py +71 -126
  120. agno/knowledge/reader/reader_factory.py +268 -0
  121. agno/knowledge/reader/s3_reader.py +101 -0
  122. agno/{document → knowledge}/reader/text_reader.py +31 -10
  123. agno/knowledge/reader/url_reader.py +128 -0
  124. agno/knowledge/reader/web_search_reader.py +366 -0
  125. agno/{document → knowledge}/reader/website_reader.py +37 -10
  126. agno/knowledge/reader/wikipedia_reader.py +59 -0
  127. agno/knowledge/reader/youtube_reader.py +78 -0
  128. agno/knowledge/remote_content/remote_content.py +88 -0
  129. agno/{reranker → knowledge/reranker}/base.py +1 -1
  130. agno/{reranker → knowledge/reranker}/cohere.py +2 -2
  131. agno/{reranker → knowledge/reranker}/infinity.py +2 -2
  132. agno/{reranker → knowledge/reranker}/sentence_transformer.py +2 -2
  133. agno/knowledge/types.py +30 -0
  134. agno/knowledge/utils.py +169 -0
  135. agno/media.py +269 -268
  136. agno/memory/__init__.py +2 -10
  137. agno/memory/manager.py +1003 -148
  138. agno/models/aimlapi/__init__.py +2 -2
  139. agno/models/aimlapi/aimlapi.py +6 -6
  140. agno/models/anthropic/claude.py +131 -131
  141. agno/models/aws/bedrock.py +110 -182
  142. agno/models/aws/claude.py +64 -18
  143. agno/models/azure/ai_foundry.py +73 -23
  144. agno/models/base.py +346 -290
  145. agno/models/cerebras/cerebras.py +84 -27
  146. agno/models/cohere/chat.py +106 -98
  147. agno/models/google/gemini.py +105 -46
  148. agno/models/groq/groq.py +97 -35
  149. agno/models/huggingface/huggingface.py +92 -27
  150. agno/models/ibm/watsonx.py +72 -13
  151. agno/models/litellm/chat.py +85 -13
  152. agno/models/message.py +46 -151
  153. agno/models/meta/llama.py +85 -49
  154. agno/models/metrics.py +120 -0
  155. agno/models/mistral/mistral.py +90 -21
  156. agno/models/ollama/__init__.py +0 -2
  157. agno/models/ollama/chat.py +85 -47
  158. agno/models/openai/chat.py +154 -37
  159. agno/models/openai/responses.py +178 -105
  160. agno/models/perplexity/perplexity.py +26 -2
  161. agno/models/portkey/portkey.py +0 -7
  162. agno/models/response.py +15 -9
  163. agno/models/utils.py +20 -0
  164. agno/models/vercel/__init__.py +2 -2
  165. agno/models/vercel/v0.py +1 -1
  166. agno/models/vllm/__init__.py +2 -2
  167. agno/models/vllm/vllm.py +3 -3
  168. agno/models/xai/xai.py +10 -10
  169. agno/os/__init__.py +3 -0
  170. agno/os/app.py +497 -0
  171. agno/os/auth.py +47 -0
  172. agno/os/config.py +103 -0
  173. agno/os/interfaces/agui/__init__.py +3 -0
  174. agno/os/interfaces/agui/agui.py +31 -0
  175. agno/{app/agui/async_router.py → os/interfaces/agui/router.py} +16 -16
  176. agno/{app → os/interfaces}/agui/utils.py +77 -33
  177. agno/os/interfaces/base.py +21 -0
  178. agno/os/interfaces/slack/__init__.py +3 -0
  179. agno/{app/slack/async_router.py → os/interfaces/slack/router.py} +3 -5
  180. agno/os/interfaces/slack/slack.py +32 -0
  181. agno/os/interfaces/whatsapp/__init__.py +3 -0
  182. agno/{app/whatsapp/async_router.py → os/interfaces/whatsapp/router.py} +4 -7
  183. agno/os/interfaces/whatsapp/whatsapp.py +29 -0
  184. agno/os/mcp.py +235 -0
  185. agno/os/router.py +1400 -0
  186. agno/os/routers/__init__.py +3 -0
  187. agno/os/routers/evals/__init__.py +3 -0
  188. agno/os/routers/evals/evals.py +393 -0
  189. agno/os/routers/evals/schemas.py +142 -0
  190. agno/os/routers/evals/utils.py +161 -0
  191. agno/os/routers/knowledge/__init__.py +3 -0
  192. agno/os/routers/knowledge/knowledge.py +850 -0
  193. agno/os/routers/knowledge/schemas.py +118 -0
  194. agno/os/routers/memory/__init__.py +3 -0
  195. agno/os/routers/memory/memory.py +410 -0
  196. agno/os/routers/memory/schemas.py +58 -0
  197. agno/os/routers/metrics/__init__.py +3 -0
  198. agno/os/routers/metrics/metrics.py +178 -0
  199. agno/os/routers/metrics/schemas.py +47 -0
  200. agno/os/routers/session/__init__.py +3 -0
  201. agno/os/routers/session/session.py +536 -0
  202. agno/os/schema.py +945 -0
  203. agno/{app/playground → os}/settings.py +7 -15
  204. agno/os/utils.py +270 -0
  205. agno/reasoning/azure_ai_foundry.py +4 -4
  206. agno/reasoning/deepseek.py +4 -4
  207. agno/reasoning/default.py +6 -11
  208. agno/reasoning/groq.py +4 -4
  209. agno/reasoning/helpers.py +4 -6
  210. agno/reasoning/ollama.py +4 -4
  211. agno/reasoning/openai.py +4 -4
  212. agno/run/agent.py +633 -0
  213. agno/run/base.py +53 -77
  214. agno/run/cancel.py +81 -0
  215. agno/run/team.py +243 -96
  216. agno/run/workflow.py +550 -12
  217. agno/session/__init__.py +10 -0
  218. agno/session/agent.py +244 -0
  219. agno/session/summary.py +225 -0
  220. agno/session/team.py +262 -0
  221. agno/{storage/session/v2 → session}/workflow.py +47 -24
  222. agno/team/__init__.py +15 -16
  223. agno/team/team.py +3260 -4824
  224. agno/tools/agentql.py +14 -5
  225. agno/tools/airflow.py +9 -4
  226. agno/tools/api.py +7 -3
  227. agno/tools/apify.py +2 -46
  228. agno/tools/arxiv.py +8 -3
  229. agno/tools/aws_lambda.py +7 -5
  230. agno/tools/aws_ses.py +7 -1
  231. agno/tools/baidusearch.py +4 -1
  232. agno/tools/bitbucket.py +4 -4
  233. agno/tools/brandfetch.py +14 -11
  234. agno/tools/bravesearch.py +4 -1
  235. agno/tools/brightdata.py +43 -23
  236. agno/tools/browserbase.py +13 -4
  237. agno/tools/calcom.py +12 -10
  238. agno/tools/calculator.py +10 -27
  239. agno/tools/cartesia.py +20 -17
  240. agno/tools/{clickup_tool.py → clickup.py} +12 -25
  241. agno/tools/confluence.py +8 -8
  242. agno/tools/crawl4ai.py +7 -1
  243. agno/tools/csv_toolkit.py +9 -8
  244. agno/tools/dalle.py +22 -12
  245. agno/tools/daytona.py +13 -16
  246. agno/tools/decorator.py +6 -3
  247. agno/tools/desi_vocal.py +17 -8
  248. agno/tools/discord.py +11 -8
  249. agno/tools/docker.py +30 -42
  250. agno/tools/duckdb.py +34 -53
  251. agno/tools/duckduckgo.py +8 -7
  252. agno/tools/e2b.py +62 -62
  253. agno/tools/eleven_labs.py +36 -29
  254. agno/tools/email.py +4 -1
  255. agno/tools/evm.py +7 -1
  256. agno/tools/exa.py +19 -14
  257. agno/tools/fal.py +30 -30
  258. agno/tools/file.py +9 -8
  259. agno/tools/financial_datasets.py +25 -44
  260. agno/tools/firecrawl.py +22 -22
  261. agno/tools/function.py +127 -18
  262. agno/tools/giphy.py +23 -11
  263. agno/tools/github.py +48 -126
  264. agno/tools/gmail.py +45 -61
  265. agno/tools/google_bigquery.py +7 -6
  266. agno/tools/google_maps.py +11 -26
  267. agno/tools/googlesearch.py +7 -2
  268. agno/tools/googlesheets.py +21 -17
  269. agno/tools/hackernews.py +9 -5
  270. agno/tools/jina.py +5 -4
  271. agno/tools/jira.py +18 -9
  272. agno/tools/knowledge.py +31 -32
  273. agno/tools/linear.py +19 -34
  274. agno/tools/linkup.py +5 -1
  275. agno/tools/local_file_system.py +8 -5
  276. agno/tools/lumalab.py +32 -20
  277. agno/tools/mcp.py +1 -2
  278. agno/tools/mem0.py +18 -12
  279. agno/tools/memori.py +14 -10
  280. agno/tools/mlx_transcribe.py +3 -2
  281. agno/tools/models/azure_openai.py +33 -15
  282. agno/tools/models/gemini.py +59 -32
  283. agno/tools/models/groq.py +30 -23
  284. agno/tools/models/nebius.py +28 -12
  285. agno/tools/models_labs.py +40 -16
  286. agno/tools/moviepy_video.py +7 -6
  287. agno/tools/neo4j.py +10 -8
  288. agno/tools/newspaper.py +7 -2
  289. agno/tools/newspaper4k.py +8 -3
  290. agno/tools/openai.py +58 -32
  291. agno/tools/openbb.py +12 -11
  292. agno/tools/opencv.py +63 -47
  293. agno/tools/openweather.py +14 -12
  294. agno/tools/pandas.py +11 -3
  295. agno/tools/postgres.py +4 -12
  296. agno/tools/pubmed.py +4 -1
  297. agno/tools/python.py +9 -22
  298. agno/tools/reasoning.py +35 -27
  299. agno/tools/reddit.py +11 -26
  300. agno/tools/replicate.py +55 -42
  301. agno/tools/resend.py +4 -1
  302. agno/tools/scrapegraph.py +15 -14
  303. agno/tools/searxng.py +10 -23
  304. agno/tools/serpapi.py +6 -3
  305. agno/tools/serper.py +13 -4
  306. agno/tools/shell.py +9 -2
  307. agno/tools/slack.py +12 -11
  308. agno/tools/sleep.py +3 -2
  309. agno/tools/spider.py +24 -4
  310. agno/tools/sql.py +7 -6
  311. agno/tools/tavily.py +6 -4
  312. agno/tools/telegram.py +12 -4
  313. agno/tools/todoist.py +11 -31
  314. agno/tools/toolkit.py +1 -1
  315. agno/tools/trafilatura.py +22 -6
  316. agno/tools/trello.py +9 -22
  317. agno/tools/twilio.py +10 -3
  318. agno/tools/user_control_flow.py +6 -1
  319. agno/tools/valyu.py +34 -5
  320. agno/tools/visualization.py +19 -28
  321. agno/tools/webbrowser.py +4 -3
  322. agno/tools/webex.py +11 -7
  323. agno/tools/website.py +15 -46
  324. agno/tools/webtools.py +12 -4
  325. agno/tools/whatsapp.py +5 -9
  326. agno/tools/wikipedia.py +20 -13
  327. agno/tools/x.py +14 -13
  328. agno/tools/yfinance.py +13 -40
  329. agno/tools/youtube.py +26 -20
  330. agno/tools/zendesk.py +7 -2
  331. agno/tools/zep.py +10 -7
  332. agno/tools/zoom.py +10 -9
  333. agno/utils/common.py +1 -19
  334. agno/utils/events.py +100 -123
  335. agno/utils/gemini.py +32 -2
  336. agno/utils/knowledge.py +29 -0
  337. agno/utils/log.py +54 -4
  338. agno/utils/mcp.py +68 -10
  339. agno/utils/media.py +39 -0
  340. agno/utils/message.py +12 -1
  341. agno/utils/models/aws_claude.py +1 -1
  342. agno/utils/models/claude.py +47 -4
  343. agno/utils/models/cohere.py +1 -1
  344. agno/utils/models/mistral.py +8 -7
  345. agno/utils/models/schema_utils.py +3 -3
  346. agno/utils/models/watsonx.py +1 -1
  347. agno/utils/openai.py +1 -1
  348. agno/utils/pprint.py +33 -32
  349. agno/utils/print_response/agent.py +779 -0
  350. agno/utils/print_response/team.py +1669 -0
  351. agno/utils/print_response/workflow.py +1451 -0
  352. agno/utils/prompts.py +14 -14
  353. agno/utils/reasoning.py +87 -0
  354. agno/utils/response.py +42 -42
  355. agno/utils/streamlit.py +481 -0
  356. agno/utils/string.py +8 -22
  357. agno/utils/team.py +50 -0
  358. agno/utils/timer.py +2 -2
  359. agno/vectordb/base.py +33 -21
  360. agno/vectordb/cassandra/cassandra.py +287 -23
  361. agno/vectordb/chroma/chromadb.py +482 -59
  362. agno/vectordb/clickhouse/clickhousedb.py +270 -63
  363. agno/vectordb/couchbase/couchbase.py +309 -29
  364. agno/vectordb/lancedb/lance_db.py +360 -21
  365. agno/vectordb/langchaindb/__init__.py +5 -0
  366. agno/vectordb/langchaindb/langchaindb.py +145 -0
  367. agno/vectordb/lightrag/__init__.py +5 -0
  368. agno/vectordb/lightrag/lightrag.py +374 -0
  369. agno/vectordb/llamaindex/llamaindexdb.py +127 -0
  370. agno/vectordb/milvus/milvus.py +242 -32
  371. agno/vectordb/mongodb/mongodb.py +200 -24
  372. agno/vectordb/pgvector/pgvector.py +319 -37
  373. agno/vectordb/pineconedb/pineconedb.py +221 -27
  374. agno/vectordb/qdrant/qdrant.py +334 -14
  375. agno/vectordb/singlestore/singlestore.py +286 -29
  376. agno/vectordb/surrealdb/surrealdb.py +187 -7
  377. agno/vectordb/upstashdb/upstashdb.py +342 -26
  378. agno/vectordb/weaviate/weaviate.py +227 -165
  379. agno/workflow/__init__.py +17 -13
  380. agno/workflow/{v2/condition.py → condition.py} +135 -32
  381. agno/workflow/{v2/loop.py → loop.py} +115 -28
  382. agno/workflow/{v2/parallel.py → parallel.py} +138 -108
  383. agno/workflow/{v2/router.py → router.py} +133 -32
  384. agno/workflow/{v2/step.py → step.py} +207 -49
  385. agno/workflow/{v2/steps.py → steps.py} +147 -66
  386. agno/workflow/types.py +482 -0
  387. agno/workflow/workflow.py +2410 -696
  388. agno-2.0.0.dist-info/METADATA +494 -0
  389. agno-2.0.0.dist-info/RECORD +515 -0
  390. agno-2.0.0.dist-info/licenses/LICENSE +201 -0
  391. agno/agent/metrics.py +0 -107
  392. agno/api/app.py +0 -35
  393. agno/api/playground.py +0 -92
  394. agno/api/schemas/app.py +0 -12
  395. agno/api/schemas/playground.py +0 -22
  396. agno/api/schemas/user.py +0 -35
  397. agno/api/schemas/workspace.py +0 -46
  398. agno/api/user.py +0 -160
  399. agno/api/workflows.py +0 -33
  400. agno/api/workspace.py +0 -175
  401. agno/app/agui/__init__.py +0 -3
  402. agno/app/agui/app.py +0 -17
  403. agno/app/agui/sync_router.py +0 -120
  404. agno/app/base.py +0 -186
  405. agno/app/discord/__init__.py +0 -3
  406. agno/app/fastapi/__init__.py +0 -3
  407. agno/app/fastapi/app.py +0 -107
  408. agno/app/fastapi/async_router.py +0 -457
  409. agno/app/fastapi/sync_router.py +0 -448
  410. agno/app/playground/app.py +0 -228
  411. agno/app/playground/async_router.py +0 -1050
  412. agno/app/playground/deploy.py +0 -249
  413. agno/app/playground/operator.py +0 -183
  414. agno/app/playground/schemas.py +0 -220
  415. agno/app/playground/serve.py +0 -55
  416. agno/app/playground/sync_router.py +0 -1042
  417. agno/app/playground/utils.py +0 -46
  418. agno/app/settings.py +0 -15
  419. agno/app/slack/__init__.py +0 -3
  420. agno/app/slack/app.py +0 -19
  421. agno/app/slack/sync_router.py +0 -92
  422. agno/app/utils.py +0 -54
  423. agno/app/whatsapp/__init__.py +0 -3
  424. agno/app/whatsapp/app.py +0 -15
  425. agno/app/whatsapp/sync_router.py +0 -197
  426. agno/cli/auth_server.py +0 -249
  427. agno/cli/config.py +0 -274
  428. agno/cli/console.py +0 -88
  429. agno/cli/credentials.py +0 -23
  430. agno/cli/entrypoint.py +0 -571
  431. agno/cli/operator.py +0 -357
  432. agno/cli/settings.py +0 -96
  433. agno/cli/ws/ws_cli.py +0 -817
  434. agno/constants.py +0 -13
  435. agno/document/__init__.py +0 -5
  436. agno/document/chunking/semantic.py +0 -45
  437. agno/document/chunking/strategy.py +0 -31
  438. agno/document/reader/__init__.py +0 -5
  439. agno/document/reader/base.py +0 -47
  440. agno/document/reader/docx_reader.py +0 -60
  441. agno/document/reader/gcs/pdf_reader.py +0 -44
  442. agno/document/reader/s3/pdf_reader.py +0 -59
  443. agno/document/reader/s3/text_reader.py +0 -63
  444. agno/document/reader/url_reader.py +0 -59
  445. agno/document/reader/youtube_reader.py +0 -58
  446. agno/embedder/__init__.py +0 -5
  447. agno/embedder/langdb.py +0 -80
  448. agno/embedder/mistral.py +0 -82
  449. agno/embedder/openai.py +0 -78
  450. agno/file/__init__.py +0 -5
  451. agno/file/file.py +0 -16
  452. agno/file/local/csv.py +0 -32
  453. agno/file/local/txt.py +0 -19
  454. agno/infra/app.py +0 -240
  455. agno/infra/base.py +0 -144
  456. agno/infra/context.py +0 -20
  457. agno/infra/db_app.py +0 -52
  458. agno/infra/resource.py +0 -205
  459. agno/infra/resources.py +0 -55
  460. agno/knowledge/agent.py +0 -702
  461. agno/knowledge/arxiv.py +0 -33
  462. agno/knowledge/combined.py +0 -36
  463. agno/knowledge/csv.py +0 -144
  464. agno/knowledge/csv_url.py +0 -124
  465. agno/knowledge/document.py +0 -223
  466. agno/knowledge/docx.py +0 -137
  467. agno/knowledge/firecrawl.py +0 -34
  468. agno/knowledge/gcs/__init__.py +0 -0
  469. agno/knowledge/gcs/base.py +0 -39
  470. agno/knowledge/gcs/pdf.py +0 -125
  471. agno/knowledge/json.py +0 -137
  472. agno/knowledge/langchain.py +0 -71
  473. agno/knowledge/light_rag.py +0 -273
  474. agno/knowledge/llamaindex.py +0 -66
  475. agno/knowledge/markdown.py +0 -154
  476. agno/knowledge/pdf.py +0 -164
  477. agno/knowledge/pdf_bytes.py +0 -42
  478. agno/knowledge/pdf_url.py +0 -148
  479. agno/knowledge/s3/__init__.py +0 -0
  480. agno/knowledge/s3/base.py +0 -64
  481. agno/knowledge/s3/pdf.py +0 -33
  482. agno/knowledge/s3/text.py +0 -34
  483. agno/knowledge/text.py +0 -141
  484. agno/knowledge/url.py +0 -46
  485. agno/knowledge/website.py +0 -179
  486. agno/knowledge/wikipedia.py +0 -32
  487. agno/knowledge/youtube.py +0 -35
  488. agno/memory/agent.py +0 -423
  489. agno/memory/classifier.py +0 -104
  490. agno/memory/db/__init__.py +0 -5
  491. agno/memory/db/base.py +0 -42
  492. agno/memory/db/mongodb.py +0 -189
  493. agno/memory/db/postgres.py +0 -203
  494. agno/memory/db/sqlite.py +0 -193
  495. agno/memory/memory.py +0 -22
  496. agno/memory/row.py +0 -36
  497. agno/memory/summarizer.py +0 -201
  498. agno/memory/summary.py +0 -19
  499. agno/memory/team.py +0 -415
  500. agno/memory/v2/__init__.py +0 -2
  501. agno/memory/v2/db/__init__.py +0 -1
  502. agno/memory/v2/db/base.py +0 -42
  503. agno/memory/v2/db/firestore.py +0 -339
  504. agno/memory/v2/db/mongodb.py +0 -196
  505. agno/memory/v2/db/postgres.py +0 -214
  506. agno/memory/v2/db/redis.py +0 -187
  507. agno/memory/v2/db/schema.py +0 -54
  508. agno/memory/v2/db/sqlite.py +0 -209
  509. agno/memory/v2/manager.py +0 -437
  510. agno/memory/v2/memory.py +0 -1097
  511. agno/memory/v2/schema.py +0 -55
  512. agno/memory/v2/summarizer.py +0 -215
  513. agno/memory/workflow.py +0 -38
  514. agno/models/ollama/tools.py +0 -430
  515. agno/models/qwen/__init__.py +0 -5
  516. agno/playground/__init__.py +0 -10
  517. agno/playground/deploy.py +0 -3
  518. agno/playground/playground.py +0 -3
  519. agno/playground/serve.py +0 -3
  520. agno/playground/settings.py +0 -3
  521. agno/reranker/__init__.py +0 -0
  522. agno/run/response.py +0 -467
  523. agno/run/v2/__init__.py +0 -0
  524. agno/run/v2/workflow.py +0 -567
  525. agno/storage/__init__.py +0 -0
  526. agno/storage/agent/__init__.py +0 -0
  527. agno/storage/agent/dynamodb.py +0 -1
  528. agno/storage/agent/json.py +0 -1
  529. agno/storage/agent/mongodb.py +0 -1
  530. agno/storage/agent/postgres.py +0 -1
  531. agno/storage/agent/singlestore.py +0 -1
  532. agno/storage/agent/sqlite.py +0 -1
  533. agno/storage/agent/yaml.py +0 -1
  534. agno/storage/base.py +0 -60
  535. agno/storage/dynamodb.py +0 -673
  536. agno/storage/firestore.py +0 -297
  537. agno/storage/gcs_json.py +0 -261
  538. agno/storage/in_memory.py +0 -234
  539. agno/storage/json.py +0 -237
  540. agno/storage/mongodb.py +0 -328
  541. agno/storage/mysql.py +0 -685
  542. agno/storage/postgres.py +0 -682
  543. agno/storage/redis.py +0 -336
  544. agno/storage/session/__init__.py +0 -16
  545. agno/storage/session/agent.py +0 -64
  546. agno/storage/session/team.py +0 -63
  547. agno/storage/session/v2/__init__.py +0 -5
  548. agno/storage/session/workflow.py +0 -61
  549. agno/storage/singlestore.py +0 -606
  550. agno/storage/sqlite.py +0 -646
  551. agno/storage/workflow/__init__.py +0 -0
  552. agno/storage/workflow/mongodb.py +0 -1
  553. agno/storage/workflow/postgres.py +0 -1
  554. agno/storage/workflow/sqlite.py +0 -1
  555. agno/storage/yaml.py +0 -241
  556. agno/tools/thinking.py +0 -73
  557. agno/utils/defaults.py +0 -57
  558. agno/utils/filesystem.py +0 -39
  559. agno/utils/git.py +0 -52
  560. agno/utils/json_io.py +0 -30
  561. agno/utils/load_env.py +0 -19
  562. agno/utils/py_io.py +0 -19
  563. agno/utils/pyproject.py +0 -18
  564. agno/utils/resource_filter.py +0 -31
  565. agno/workflow/v2/__init__.py +0 -21
  566. agno/workflow/v2/types.py +0 -357
  567. agno/workflow/v2/workflow.py +0 -3312
  568. agno/workspace/__init__.py +0 -0
  569. agno/workspace/config.py +0 -325
  570. agno/workspace/enums.py +0 -6
  571. agno/workspace/helpers.py +0 -52
  572. agno/workspace/operator.py +0 -757
  573. agno/workspace/settings.py +0 -158
  574. agno-1.8.1.dist-info/METADATA +0 -982
  575. agno-1.8.1.dist-info/RECORD +0 -566
  576. agno-1.8.1.dist-info/entry_points.txt +0 -3
  577. agno-1.8.1.dist-info/licenses/LICENSE +0 -375
  578. /agno/{app → db/migrations}/__init__.py +0 -0
  579. /agno/{app/playground/__init__.py → db/schemas/metrics.py} +0 -0
  580. /agno/{cli → integrations}/__init__.py +0 -0
  581. /agno/{cli/ws → knowledge/chunking}/__init__.py +0 -0
  582. /agno/{document/chunking → knowledge/remote_content}/__init__.py +0 -0
  583. /agno/{document/reader/gcs → knowledge/reranker}/__init__.py +0 -0
  584. /agno/{document/reader/s3 → os/interfaces}/__init__.py +0 -0
  585. /agno/{app → os/interfaces}/slack/security.py +0 -0
  586. /agno/{app → os/interfaces}/whatsapp/security.py +0 -0
  587. /agno/{file/local → utils/print_response}/__init__.py +0 -0
  588. /agno/{infra → vectordb/llamaindex}/__init__.py +0 -0
  589. {agno-1.8.1.dist-info → agno-2.0.0.dist-info}/WHEEL +0 -0
  590. {agno-1.8.1.dist-info → agno-2.0.0.dist-info}/top_level.txt +0 -0
agno/models/base.py CHANGED
@@ -5,7 +5,6 @@ from dataclasses import dataclass, field
5
5
  from types import AsyncGeneratorType, GeneratorType
6
6
  from typing import (
7
7
  Any,
8
- AsyncGenerator,
9
8
  AsyncIterator,
10
9
  Dict,
11
10
  Iterator,
@@ -22,12 +21,13 @@ from uuid import uuid4
22
21
  from pydantic import BaseModel
23
22
 
24
23
  from agno.exceptions import AgentRunException
25
- from agno.media import AudioResponse, ImageArtifact
26
- from agno.models.message import Citations, Message, MessageMetrics
24
+ from agno.media import Audio, Image, Video
25
+ from agno.models.message import Citations, Message
26
+ from agno.models.metrics import Metrics
27
27
  from agno.models.response import ModelResponse, ModelResponseEvent, ToolExecution
28
- from agno.run.response import RunResponseContentEvent, RunResponseEvent
29
- from agno.run.team import RunResponseContentEvent as TeamRunResponseContentEvent
30
- from agno.run.team import TeamRunResponseEvent
28
+ from agno.run.agent import CustomEvent, RunContentEvent, RunOutput, RunOutputEvent
29
+ from agno.run.team import RunContentEvent as TeamRunContentEvent
30
+ from agno.run.team import TeamRunOutputEvent
31
31
  from agno.tools.function import Function, FunctionCall, FunctionExecutionResult, UserInputField
32
32
  from agno.utils.log import log_debug, log_error, log_warning
33
33
  from agno.utils.timer import Timer
@@ -38,13 +38,14 @@ from agno.utils.tools import get_function_call_for_tool_call, get_function_call_
38
38
  class MessageData:
39
39
  response_role: Optional[Literal["system", "user", "assistant", "tool"]] = None
40
40
  response_content: Any = ""
41
- response_thinking: Any = ""
42
- response_redacted_thinking: Any = ""
41
+ response_reasoning_content: Any = ""
42
+ response_redacted_reasoning_content: Any = ""
43
43
  response_citations: Optional[Citations] = None
44
44
  response_tool_calls: List[Dict[str, Any]] = field(default_factory=list)
45
45
 
46
- response_audio: Optional[AudioResponse] = None
47
- response_image: Optional[ImageArtifact] = None
46
+ response_audio: Optional[Audio] = None
47
+ response_image: Optional[Image] = None
48
+ response_video: Optional[Video] = None
48
49
 
49
50
  # Data from the provider that we might need on subsequent messages
50
51
  response_provider_data: Optional[Dict[str, Any]] = None
@@ -61,143 +62,17 @@ def _log_messages(messages: List[Message]) -> None:
61
62
  m.log(metrics=False)
62
63
 
63
64
 
64
- def _add_usage_metrics_to_assistant_message(assistant_message: Message, response_usage: Any) -> None:
65
- """
66
- Add usage metrics from the model provider to the assistant message.
67
-
68
- Args:
69
- assistant_message: Message to update with metrics
70
- response_usage: Usage data from model provider
71
- """
72
-
73
- # Standard token metrics
74
- if isinstance(response_usage, dict):
75
- if "input_tokens" in response_usage and response_usage.get("input_tokens") is not None:
76
- assistant_message.metrics.input_tokens = response_usage.get("input_tokens", 0)
77
- if "output_tokens" in response_usage and response_usage.get("output_tokens") is not None:
78
- assistant_message.metrics.output_tokens = response_usage.get("output_tokens", 0)
79
- if "prompt_tokens" in response_usage and response_usage.get("prompt_tokens") is not None:
80
- assistant_message.metrics.input_tokens = response_usage.get("prompt_tokens", 0)
81
- if "completion_tokens" in response_usage and response_usage.get("completion_tokens") is not None:
82
- assistant_message.metrics.output_tokens = response_usage.get("completion_tokens", 0)
83
- if "cached_tokens" in response_usage and response_usage.get("cached_tokens") is not None:
84
- assistant_message.metrics.cached_tokens = response_usage.get("cached_tokens", 0)
85
- if "cache_write_tokens" in response_usage and response_usage.get("cache_write_tokens") is not None:
86
- assistant_message.metrics.cache_write_tokens = response_usage.get("cache_write_tokens", 0)
87
- if "total_tokens" in response_usage and response_usage.get("total_tokens") is not None:
88
- assistant_message.metrics.total_tokens = response_usage.get("total_tokens", 0)
89
- else:
90
- assistant_message.metrics.total_tokens = (
91
- assistant_message.metrics.input_tokens + assistant_message.metrics.output_tokens
92
- )
93
- else:
94
- if hasattr(response_usage, "input_tokens") and response_usage.input_tokens:
95
- assistant_message.metrics.input_tokens = response_usage.input_tokens
96
- if hasattr(response_usage, "output_tokens") and response_usage.output_tokens:
97
- assistant_message.metrics.output_tokens = response_usage.output_tokens
98
- if hasattr(response_usage, "prompt_tokens") and response_usage.prompt_tokens is not None:
99
- assistant_message.metrics.input_tokens = response_usage.prompt_tokens
100
- assistant_message.metrics.prompt_tokens = response_usage.prompt_tokens
101
- if hasattr(response_usage, "completion_tokens") and response_usage.completion_tokens is not None:
102
- assistant_message.metrics.output_tokens = response_usage.completion_tokens
103
- assistant_message.metrics.completion_tokens = response_usage.completion_tokens
104
- if hasattr(response_usage, "total_tokens") and response_usage.total_tokens is not None:
105
- assistant_message.metrics.total_tokens = response_usage.total_tokens
106
- if hasattr(response_usage, "cached_tokens") and response_usage.cached_tokens is not None:
107
- assistant_message.metrics.cached_tokens = response_usage.cached_tokens
108
- if hasattr(response_usage, "cache_write_tokens") and response_usage.cache_write_tokens is not None:
109
- assistant_message.metrics.cache_write_tokens = response_usage.cache_write_tokens
110
-
111
- # If you didn't capture any total tokens
112
- if not assistant_message.metrics.total_tokens:
113
- if assistant_message.metrics.input_tokens is None:
114
- assistant_message.metrics.input_tokens = 0
115
- if assistant_message.metrics.output_tokens is None:
116
- assistant_message.metrics.output_tokens = 0
117
-
118
- assistant_message.metrics.total_tokens = (
119
- assistant_message.metrics.input_tokens + assistant_message.metrics.output_tokens
120
- )
121
-
122
- # Additional metrics (e.g., from Groq, Ollama)
123
- if isinstance(response_usage, dict) and "additional_metrics" in response_usage:
124
- assistant_message.metrics.additional_metrics = response_usage["additional_metrics"]
125
-
126
- # Token details (e.g., from OpenAI)
127
- if hasattr(response_usage, "prompt_tokens_details"):
128
- if isinstance(response_usage.prompt_tokens_details, dict):
129
- assistant_message.metrics.prompt_tokens_details = response_usage.prompt_tokens_details
130
- if (
131
- "audio_tokens" in response_usage.prompt_tokens_details
132
- and response_usage.prompt_tokens_details["audio_tokens"] is not None
133
- ):
134
- assistant_message.metrics.input_audio_tokens = response_usage.prompt_tokens_details["audio_tokens"]
135
- if (
136
- "cached_tokens" in response_usage.prompt_tokens_details
137
- and response_usage.prompt_tokens_details["cached_tokens"] is not None
138
- ):
139
- assistant_message.metrics.cached_tokens = response_usage.prompt_tokens_details["cached_tokens"]
140
- elif hasattr(response_usage.prompt_tokens_details, "model_dump"):
141
- assistant_message.metrics.prompt_tokens_details = response_usage.prompt_tokens_details.model_dump(
142
- exclude_none=True
143
- )
144
- if (
145
- hasattr(response_usage.prompt_tokens_details, "audio_tokens")
146
- and response_usage.prompt_tokens_details.audio_tokens is not None
147
- ):
148
- assistant_message.metrics.input_audio_tokens = response_usage.prompt_tokens_details.audio_tokens
149
- if (
150
- hasattr(response_usage.prompt_tokens_details, "cached_tokens")
151
- and response_usage.prompt_tokens_details.cached_tokens is not None
152
- ):
153
- assistant_message.metrics.cached_tokens = response_usage.prompt_tokens_details.cached_tokens
154
-
155
- if hasattr(response_usage, "completion_tokens_details"):
156
- if isinstance(response_usage.completion_tokens_details, dict):
157
- assistant_message.metrics.completion_tokens_details = response_usage.completion_tokens_details
158
- if (
159
- "audio_tokens" in response_usage.completion_tokens_details
160
- and response_usage.completion_tokens_details["audio_tokens"] is not None
161
- ):
162
- assistant_message.metrics.output_audio_tokens = response_usage.completion_tokens_details["audio_tokens"]
163
- if (
164
- "reasoning_tokens" in response_usage.completion_tokens_details
165
- and response_usage.completion_tokens_details["reasoning_tokens"] is not None
166
- ):
167
- assistant_message.metrics.reasoning_tokens = response_usage.completion_tokens_details[
168
- "reasoning_tokens"
169
- ]
170
- elif hasattr(response_usage.completion_tokens_details, "model_dump"):
171
- assistant_message.metrics.completion_tokens_details = response_usage.completion_tokens_details.model_dump(
172
- exclude_none=True
173
- )
174
- if (
175
- hasattr(response_usage.completion_tokens_details, "audio_tokens")
176
- and response_usage.completion_tokens_details.audio_tokens is not None
177
- ):
178
- assistant_message.metrics.output_audio_tokens = response_usage.completion_tokens_details.audio_tokens
179
- if (
180
- hasattr(response_usage.completion_tokens_details, "reasoning_tokens")
181
- and response_usage.completion_tokens_details.reasoning_tokens is not None
182
- ):
183
- assistant_message.metrics.reasoning_tokens = response_usage.completion_tokens_details.reasoning_tokens
184
-
185
- assistant_message.metrics.audio_tokens = (
186
- assistant_message.metrics.input_audio_tokens + assistant_message.metrics.output_audio_tokens
187
- )
188
-
189
-
190
- def _handle_agent_exception(a_exc: AgentRunException, additional_messages: Optional[List[Message]] = None) -> None:
65
+ def _handle_agent_exception(a_exc: AgentRunException, additional_input: Optional[List[Message]] = None) -> None:
191
66
  """Handle AgentRunException and collect additional messages."""
192
- if additional_messages is None:
193
- additional_messages = []
67
+ if additional_input is None:
68
+ additional_input = []
194
69
  if a_exc.user_message is not None:
195
70
  msg = (
196
71
  Message(role="user", content=a_exc.user_message)
197
72
  if isinstance(a_exc.user_message, str)
198
73
  else a_exc.user_message
199
74
  )
200
- additional_messages.append(msg)
75
+ additional_input.append(msg)
201
76
 
202
77
  if a_exc.agent_message is not None:
203
78
  msg = (
@@ -205,20 +80,20 @@ def _handle_agent_exception(a_exc: AgentRunException, additional_messages: Optio
205
80
  if isinstance(a_exc.agent_message, str)
206
81
  else a_exc.agent_message
207
82
  )
208
- additional_messages.append(msg)
83
+ additional_input.append(msg)
209
84
 
210
85
  if a_exc.messages:
211
86
  for m in a_exc.messages:
212
87
  if isinstance(m, Message):
213
- additional_messages.append(m)
88
+ additional_input.append(m)
214
89
  elif isinstance(m, dict):
215
90
  try:
216
- additional_messages.append(Message(**m))
91
+ additional_input.append(Message(**m))
217
92
  except Exception as e:
218
93
  log_warning(f"Failed to convert dict to Message: {e}")
219
94
 
220
95
  if a_exc.stop_execution:
221
- for m in additional_messages:
96
+ for m in additional_input:
222
97
  m.stop_after_tool_call = True
223
98
 
224
99
 
@@ -270,23 +145,23 @@ class Model(ABC):
270
145
  return self.provider or self.name or self.__class__.__name__
271
146
 
272
147
  @abstractmethod
273
- def invoke(self, *args, **kwargs) -> Any:
148
+ def invoke(self, *args, **kwargs) -> ModelResponse:
274
149
  pass
275
150
 
276
151
  @abstractmethod
277
- async def ainvoke(self, *args, **kwargs) -> Any:
152
+ async def ainvoke(self, *args, **kwargs) -> ModelResponse:
278
153
  pass
279
154
 
280
155
  @abstractmethod
281
- def invoke_stream(self, *args, **kwargs) -> Iterator[Any]:
156
+ def invoke_stream(self, *args, **kwargs) -> Iterator[ModelResponse]:
282
157
  pass
283
158
 
284
159
  @abstractmethod
285
- async def ainvoke_stream(self, *args, **kwargs) -> AsyncGenerator[Any, None]:
160
+ def ainvoke_stream(self, *args, **kwargs) -> AsyncIterator[ModelResponse]:
286
161
  pass
287
162
 
288
163
  @abstractmethod
289
- def parse_provider_response(self, response: Any, **kwargs) -> ModelResponse:
164
+ def _parse_provider_response(self, response: Any, **kwargs) -> ModelResponse:
290
165
  """
291
166
  Parse the raw response from the model provider into a ModelResponse.
292
167
 
@@ -299,7 +174,7 @@ class Model(ABC):
299
174
  pass
300
175
 
301
176
  @abstractmethod
302
- def parse_provider_response_delta(self, response: Any) -> ModelResponse:
177
+ def _parse_provider_response_delta(self, response: Any) -> ModelResponse:
303
178
  """
304
179
  Parse the streaming response from the model provider into ModelResponse objects.
305
180
 
@@ -319,6 +194,7 @@ class Model(ABC):
319
194
  functions: Optional[Dict[str, Function]] = None,
320
195
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
321
196
  tool_call_limit: Optional[int] = None,
197
+ run_response: Optional[RunOutput] = None,
322
198
  ) -> ModelResponse:
323
199
  """
324
200
  Generate a response from the model.
@@ -342,6 +218,7 @@ class Model(ABC):
342
218
  response_format=response_format,
343
219
  tools=tools,
344
220
  tool_choice=tool_choice or self._tool_choice,
221
+ run_response=run_response,
345
222
  )
346
223
 
347
224
  # Add assistant message to messages
@@ -369,6 +246,26 @@ class Model(ABC):
369
246
  function_call_limit=tool_call_limit,
370
247
  ):
371
248
  if isinstance(function_call_response, ModelResponse):
249
+ # The session state is updated by the function call
250
+ if function_call_response.updated_session_state is not None:
251
+ model_response.updated_session_state = function_call_response.updated_session_state
252
+
253
+ # Media artifacts are generated by the function call
254
+ if function_call_response.images is not None:
255
+ if model_response.images is None:
256
+ model_response.images = []
257
+ model_response.images.extend(function_call_response.images)
258
+
259
+ if function_call_response.audios is not None:
260
+ if model_response.audios is None:
261
+ model_response.audios = []
262
+ model_response.audios.extend(function_call_response.audios)
263
+
264
+ if function_call_response.videos is not None:
265
+ if model_response.videos is None:
266
+ model_response.videos = []
267
+ model_response.videos.extend(function_call_response.videos)
268
+
372
269
  if (
373
270
  function_call_response.event
374
271
  in [
@@ -395,6 +292,11 @@ class Model(ABC):
395
292
  self.format_function_call_results(
396
293
  messages=messages, function_call_results=function_call_results, **model_response.extra or {}
397
294
  )
295
+
296
+ if any(msg.images or msg.videos or msg.audio for msg in function_call_results):
297
+ # Handle function call media
298
+ self._handle_function_call_media(messages=messages, function_call_results=function_call_results)
299
+
398
300
  for function_call_result in function_call_results:
399
301
  function_call_result.log(metrics=True)
400
302
 
@@ -480,6 +382,26 @@ class Model(ABC):
480
382
  function_call_limit=tool_call_limit,
481
383
  ):
482
384
  if isinstance(function_call_response, ModelResponse):
385
+ # The session state is updated by the function call
386
+ if function_call_response.updated_session_state is not None:
387
+ model_response.updated_session_state = function_call_response.updated_session_state
388
+
389
+ # Media artifacts are generated by the function call
390
+ if function_call_response.images is not None:
391
+ if model_response.images is None:
392
+ model_response.images = []
393
+ model_response.images.extend(function_call_response.images)
394
+
395
+ if function_call_response.audios is not None:
396
+ if model_response.audios is None:
397
+ model_response.audios = []
398
+ model_response.audios.extend(function_call_response.audios)
399
+
400
+ if function_call_response.videos is not None:
401
+ if model_response.videos is None:
402
+ model_response.videos = []
403
+ model_response.videos.extend(function_call_response.videos)
404
+
483
405
  if (
484
406
  function_call_response.event
485
407
  in [
@@ -505,6 +427,11 @@ class Model(ABC):
505
427
  self.format_function_call_results(
506
428
  messages=messages, function_call_results=function_call_results, **model_response.extra or {}
507
429
  )
430
+
431
+ if any(msg.images or msg.videos or msg.audio for msg in function_call_results):
432
+ # Handle function call media
433
+ self._handle_function_call_media(messages=messages, function_call_results=function_call_results)
434
+
508
435
  for function_call_result in function_call_results:
509
436
  function_call_result.log(metrics=True)
510
437
 
@@ -541,6 +468,7 @@ class Model(ABC):
541
468
  response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
542
469
  tools: Optional[List[Dict[str, Any]]] = None,
543
470
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
471
+ run_response: Optional[RunOutput] = None,
544
472
  ) -> None:
545
473
  """
546
474
  Process a single model response and return the assistant message and whether to continue.
@@ -549,21 +477,14 @@ class Model(ABC):
549
477
  Tuple[Message, bool]: (assistant_message, should_continue)
550
478
  """
551
479
  # Generate response
552
- assistant_message.metrics.start_timer()
553
- response = self.invoke(
480
+ provider_response = self.invoke(
481
+ assistant_message=assistant_message,
554
482
  messages=messages,
555
483
  response_format=response_format,
556
484
  tools=tools,
557
485
  tool_choice=tool_choice or self._tool_choice,
486
+ run_response=run_response,
558
487
  )
559
- assistant_message.metrics.stop_timer()
560
-
561
- # Parse provider response
562
- provider_response: ModelResponse = self.parse_provider_response(response, response_format=response_format)
563
-
564
- # Add parsed data to model response
565
- if provider_response.parsed is not None:
566
- model_response.parsed = provider_response.parsed
567
488
 
568
489
  # Populate the assistant message
569
490
  self._populate_assistant_message(assistant_message=assistant_message, provider_response=provider_response)
@@ -574,16 +495,19 @@ class Model(ABC):
574
495
  model_response.content = assistant_message.get_content_string()
575
496
  else:
576
497
  model_response.content += assistant_message.get_content_string()
577
- if assistant_message.thinking is not None:
578
- model_response.thinking = assistant_message.thinking
579
- if assistant_message.redacted_thinking is not None:
580
- model_response.redacted_thinking = assistant_message.redacted_thinking
498
+ if assistant_message.reasoning_content is not None:
499
+ model_response.reasoning_content = assistant_message.reasoning_content
500
+ if assistant_message.redacted_reasoning_content is not None:
501
+ model_response.redacted_reasoning_content = assistant_message.redacted_reasoning_content
581
502
  if assistant_message.citations is not None:
582
503
  model_response.citations = assistant_message.citations
583
504
  if assistant_message.audio_output is not None:
584
- model_response.audio = assistant_message.audio_output
505
+ if isinstance(assistant_message.audio_output, Audio):
506
+ model_response.audio = assistant_message.audio_output
585
507
  if assistant_message.image_output is not None:
586
- model_response.image = assistant_message.image_output
508
+ model_response.images = [assistant_message.image_output]
509
+ if assistant_message.video_output is not None:
510
+ model_response.videos = [assistant_message.video_output]
587
511
  if provider_response.extra is not None:
588
512
  if model_response.extra is None:
589
513
  model_response.extra = {}
@@ -597,6 +521,7 @@ class Model(ABC):
597
521
  response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
598
522
  tools: Optional[List[Dict[str, Any]]] = None,
599
523
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
524
+ run_response: Optional[RunOutput] = None,
600
525
  ) -> None:
601
526
  """
602
527
  Process a single async model response and return the assistant message and whether to continue.
@@ -605,21 +530,14 @@ class Model(ABC):
605
530
  Tuple[Message, bool]: (assistant_message, should_continue)
606
531
  """
607
532
  # Generate response
608
- assistant_message.metrics.start_timer()
609
- response = await self.ainvoke(
533
+ provider_response = await self.ainvoke(
610
534
  messages=messages,
611
535
  response_format=response_format,
612
536
  tools=tools,
613
537
  tool_choice=tool_choice or self._tool_choice,
538
+ assistant_message=assistant_message,
539
+ run_response=run_response,
614
540
  )
615
- assistant_message.metrics.stop_timer()
616
-
617
- # Parse provider response
618
- provider_response: ModelResponse = self.parse_provider_response(response, response_format=response_format)
619
-
620
- # Add parsed data to model response
621
- if provider_response.parsed is not None:
622
- model_response.parsed = provider_response.parsed
623
541
 
624
542
  # Populate the assistant message
625
543
  self._populate_assistant_message(assistant_message=assistant_message, provider_response=provider_response)
@@ -630,16 +548,19 @@ class Model(ABC):
630
548
  model_response.content = assistant_message.get_content_string()
631
549
  else:
632
550
  model_response.content += assistant_message.get_content_string()
633
- if assistant_message.thinking is not None:
634
- model_response.thinking = assistant_message.thinking
635
- if assistant_message.redacted_thinking is not None:
636
- model_response.redacted_thinking = assistant_message.redacted_thinking
551
+ if assistant_message.reasoning_content is not None:
552
+ model_response.reasoning_content = assistant_message.reasoning_content
553
+ if assistant_message.redacted_reasoning_content is not None:
554
+ model_response.redacted_reasoning_content = assistant_message.redacted_reasoning_content
637
555
  if assistant_message.citations is not None:
638
556
  model_response.citations = assistant_message.citations
639
557
  if assistant_message.audio_output is not None:
640
- model_response.audio = assistant_message.audio_output
558
+ if isinstance(assistant_message.audio_output, Audio):
559
+ model_response.audio = assistant_message.audio_output
641
560
  if assistant_message.image_output is not None:
642
- model_response.image = assistant_message.image_output
561
+ model_response.images = [assistant_message.image_output]
562
+ if assistant_message.video_output is not None:
563
+ model_response.videos = [assistant_message.video_output]
643
564
  if provider_response.extra is not None:
644
565
  if model_response.extra is None:
645
566
  model_response.extra = {}
@@ -677,16 +598,22 @@ class Model(ABC):
677
598
  assistant_message.audio_output = provider_response.audio
678
599
 
679
600
  # Add image to assistant message
680
- if provider_response.image is not None:
681
- assistant_message.image_output = provider_response.image
601
+ if provider_response.images is not None:
602
+ if provider_response.images:
603
+ assistant_message.image_output = provider_response.images[-1] # Taking last (most recent) image
604
+
605
+ # Add video to assistant message
606
+ if provider_response.videos is not None:
607
+ if provider_response.videos:
608
+ assistant_message.video_output = provider_response.videos[-1] # Taking last (most recent) video
682
609
 
683
- # Add thinking content to assistant message
684
- if provider_response.thinking is not None:
685
- assistant_message.thinking = provider_response.thinking
610
+ if provider_response.audios is not None:
611
+ if provider_response.audios:
612
+ assistant_message.audio_output = provider_response.audios[-1] # Taking last (most recent) audio
686
613
 
687
614
  # Add redacted thinking content to assistant message
688
- if provider_response.redacted_thinking is not None:
689
- assistant_message.redacted_thinking = provider_response.redacted_thinking
615
+ if provider_response.redacted_reasoning_content is not None:
616
+ assistant_message.redacted_reasoning_content = provider_response.redacted_reasoning_content
690
617
 
691
618
  # Add reasoning content to assistant message
692
619
  if provider_response.reasoning_content is not None:
@@ -702,9 +629,7 @@ class Model(ABC):
702
629
 
703
630
  # Add usage metrics if provided
704
631
  if provider_response.response_usage is not None:
705
- _add_usage_metrics_to_assistant_message(
706
- assistant_message=assistant_message, response_usage=provider_response.response_usage
707
- )
632
+ assistant_message.metrics += provider_response.response_usage
708
633
 
709
634
  return assistant_message
710
635
 
@@ -716,22 +641,28 @@ class Model(ABC):
716
641
  response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
717
642
  tools: Optional[List[Dict[str, Any]]] = None,
718
643
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
644
+ run_response: Optional[RunOutput] = None,
719
645
  ) -> Iterator[ModelResponse]:
720
646
  """
721
647
  Process a streaming response from the model.
722
648
  """
723
- assistant_message.metrics.start_timer()
649
+
724
650
  for response_delta in self.invoke_stream(
725
651
  messages=messages,
652
+ assistant_message=assistant_message,
726
653
  response_format=response_format,
727
654
  tools=tools,
728
655
  tool_choice=tool_choice or self._tool_choice,
656
+ run_response=run_response,
729
657
  ):
730
- model_response_delta = self.parse_provider_response_delta(response_delta)
731
658
  yield from self._populate_stream_data_and_assistant_message(
732
- stream_data=stream_data, assistant_message=assistant_message, model_response_delta=model_response_delta
659
+ stream_data=stream_data,
660
+ assistant_message=assistant_message,
661
+ model_response_delta=response_delta,
733
662
  )
734
- assistant_message.metrics.stop_timer()
663
+
664
+ # Add final metrics to assistant message
665
+ self._populate_assistant_message(assistant_message=assistant_message, provider_response=response_delta)
735
666
 
736
667
  def response_stream(
737
668
  self,
@@ -742,7 +673,8 @@ class Model(ABC):
742
673
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
743
674
  tool_call_limit: Optional[int] = None,
744
675
  stream_model_response: bool = True,
745
- ) -> Iterator[Union[ModelResponse, RunResponseEvent, TeamRunResponseEvent]]:
676
+ run_response: Optional[RunOutput] = None,
677
+ ) -> Iterator[Union[ModelResponse, RunOutputEvent, TeamRunOutputEvent]]:
746
678
  """
747
679
  Generate a streaming response from the model.
748
680
  """
@@ -766,15 +698,16 @@ class Model(ABC):
766
698
  response_format=response_format,
767
699
  tools=tools,
768
700
  tool_choice=tool_choice or self._tool_choice,
701
+ run_response=run_response,
769
702
  )
770
703
 
771
704
  # Populate assistant message from stream data
772
705
  if stream_data.response_content:
773
706
  assistant_message.content = stream_data.response_content
774
- if stream_data.response_thinking:
775
- assistant_message.thinking = stream_data.response_thinking
776
- if stream_data.response_redacted_thinking:
777
- assistant_message.redacted_thinking = stream_data.response_redacted_thinking
707
+ if stream_data.response_reasoning_content:
708
+ assistant_message.reasoning_content = stream_data.response_reasoning_content
709
+ if stream_data.response_redacted_reasoning_content:
710
+ assistant_message.redacted_reasoning_content = stream_data.response_redacted_reasoning_content
778
711
  if stream_data.response_provider_data:
779
712
  assistant_message.provider_data = stream_data.response_provider_data
780
713
  if stream_data.response_citations:
@@ -828,6 +761,10 @@ class Model(ABC):
828
761
  else:
829
762
  self.format_function_call_results(messages=messages, function_call_results=function_call_results)
830
763
 
764
+ # Handle function call media
765
+ if any(msg.images or msg.videos or msg.audio for msg in function_call_results):
766
+ self._handle_function_call_media(messages=messages, function_call_results=function_call_results)
767
+
831
768
  for function_call_result in function_call_results:
832
769
  function_call_result.log(metrics=True)
833
770
 
@@ -863,23 +800,28 @@ class Model(ABC):
863
800
  response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
864
801
  tools: Optional[List[Dict[str, Any]]] = None,
865
802
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
803
+ run_response: Optional[RunOutput] = None,
866
804
  ) -> AsyncIterator[ModelResponse]:
867
805
  """
868
806
  Process a streaming response from the model.
869
807
  """
870
- assistant_message.metrics.start_timer()
871
808
  async for response_delta in self.ainvoke_stream(
872
809
  messages=messages,
810
+ assistant_message=assistant_message,
873
811
  response_format=response_format,
874
812
  tools=tools,
875
813
  tool_choice=tool_choice or self._tool_choice,
814
+ run_response=run_response,
876
815
  ): # type: ignore
877
- model_response_delta = self.parse_provider_response_delta(response_delta)
878
816
  for model_response in self._populate_stream_data_and_assistant_message(
879
- stream_data=stream_data, assistant_message=assistant_message, model_response_delta=model_response_delta
817
+ stream_data=stream_data,
818
+ assistant_message=assistant_message,
819
+ model_response_delta=response_delta,
880
820
  ):
881
821
  yield model_response
882
- assistant_message.metrics.stop_timer()
822
+
823
+ # Populate the assistant message
824
+ self._populate_assistant_message(assistant_message=assistant_message, provider_response=model_response)
883
825
 
884
826
  async def aresponse_stream(
885
827
  self,
@@ -890,7 +832,8 @@ class Model(ABC):
890
832
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
891
833
  tool_call_limit: Optional[int] = None,
892
834
  stream_model_response: bool = True,
893
- ) -> AsyncIterator[Union[ModelResponse, RunResponseEvent, TeamRunResponseEvent]]:
835
+ run_response: Optional[RunOutput] = None,
836
+ ) -> AsyncIterator[Union[ModelResponse, RunOutputEvent, TeamRunOutputEvent]]:
894
837
  """
895
838
  Generate an asynchronous streaming response from the model.
896
839
  """
@@ -914,16 +857,17 @@ class Model(ABC):
914
857
  response_format=response_format,
915
858
  tools=tools,
916
859
  tool_choice=tool_choice or self._tool_choice,
860
+ run_response=run_response,
917
861
  ):
918
862
  yield response
919
863
 
920
864
  # Populate assistant message from stream data
921
865
  if stream_data.response_content:
922
866
  assistant_message.content = stream_data.response_content
923
- if stream_data.response_thinking:
924
- assistant_message.thinking = stream_data.response_thinking
925
- if stream_data.response_redacted_thinking:
926
- assistant_message.redacted_thinking = stream_data.response_redacted_thinking
867
+ if stream_data.response_reasoning_content:
868
+ assistant_message.reasoning_content = stream_data.response_reasoning_content
869
+ if stream_data.response_redacted_reasoning_content:
870
+ assistant_message.redacted_reasoning_content = stream_data.response_redacted_reasoning_content
927
871
  if stream_data.response_provider_data:
928
872
  assistant_message.provider_data = stream_data.response_provider_data
929
873
  if stream_data.response_audio:
@@ -940,6 +884,7 @@ class Model(ABC):
940
884
  response_format=response_format,
941
885
  tools=tools,
942
886
  tool_choice=tool_choice or self._tool_choice,
887
+ run_response=run_response,
943
888
  )
944
889
  yield model_response
945
890
 
@@ -975,6 +920,10 @@ class Model(ABC):
975
920
  else:
976
921
  self.format_function_call_results(messages=messages, function_call_results=function_call_results)
977
922
 
923
+ # Handle function call media
924
+ if any(msg.images or msg.videos or msg.audio for msg in function_call_results):
925
+ self._handle_function_call_media(messages=messages, function_call_results=function_call_results)
926
+
978
927
  for function_call_result in function_call_results:
979
928
  function_call_result.log(metrics=True)
980
929
 
@@ -1006,11 +955,6 @@ class Model(ABC):
1006
955
  self, stream_data: MessageData, assistant_message: Message, model_response_delta: ModelResponse
1007
956
  ) -> Iterator[ModelResponse]:
1008
957
  """Update the stream data and assistant message with the model response."""
1009
-
1010
- # Update metrics
1011
- if not assistant_message.metrics.time_to_first_token:
1012
- assistant_message.metrics.set_time_to_first_token()
1013
-
1014
958
  # Add role to assistant message
1015
959
  if model_response_delta.role is not None:
1016
960
  assistant_message.role = model_response_delta.role
@@ -1021,16 +965,12 @@ class Model(ABC):
1021
965
  stream_data.response_content += model_response_delta.content
1022
966
  should_yield = True
1023
967
 
1024
- if model_response_delta.thinking is not None:
1025
- stream_data.response_thinking += model_response_delta.thinking
1026
- should_yield = True
1027
-
1028
968
  if model_response_delta.reasoning_content is not None:
1029
- stream_data.response_thinking += model_response_delta.reasoning_content
969
+ stream_data.response_reasoning_content += model_response_delta.reasoning_content
1030
970
  should_yield = True
1031
971
 
1032
- if model_response_delta.redacted_thinking is not None:
1033
- stream_data.response_redacted_thinking += model_response_delta.redacted_thinking
972
+ if model_response_delta.redacted_reasoning_content is not None:
973
+ stream_data.response_redacted_reasoning_content += model_response_delta.redacted_reasoning_content
1034
974
  should_yield = True
1035
975
 
1036
976
  if model_response_delta.citations is not None:
@@ -1049,40 +989,45 @@ class Model(ABC):
1049
989
  stream_data.response_tool_calls.extend(model_response_delta.tool_calls)
1050
990
  should_yield = True
1051
991
 
1052
- if model_response_delta.audio is not None:
992
+ if model_response_delta.audio is not None and isinstance(model_response_delta.audio, Audio):
1053
993
  if stream_data.response_audio is None:
1054
- stream_data.response_audio = AudioResponse(id=str(uuid4()), content="", transcript="")
994
+ stream_data.response_audio = Audio(id=str(uuid4()), content="", transcript="")
995
+
996
+ from typing import cast
997
+
998
+ audio_response = cast(Audio, model_response_delta.audio)
1055
999
 
1056
1000
  # Update the stream data with audio information
1057
- if model_response_delta.audio.id is not None:
1058
- stream_data.response_audio.id = model_response_delta.audio.id # type: ignore
1059
- if model_response_delta.audio.content is not None:
1060
- stream_data.response_audio.content += model_response_delta.audio.content # type: ignore
1061
- if model_response_delta.audio.transcript is not None:
1062
- stream_data.response_audio.transcript += model_response_delta.audio.transcript # type: ignore
1063
- if model_response_delta.audio.expires_at is not None:
1064
- stream_data.response_audio.expires_at = model_response_delta.audio.expires_at
1065
- if model_response_delta.audio.mime_type is not None:
1066
- stream_data.response_audio.mime_type = model_response_delta.audio.mime_type
1067
- stream_data.response_audio.sample_rate = model_response_delta.audio.sample_rate
1068
- stream_data.response_audio.channels = model_response_delta.audio.channels
1001
+ if audio_response.id is not None:
1002
+ stream_data.response_audio.id = audio_response.id # type: ignore
1003
+ if audio_response.content is not None:
1004
+ stream_data.response_audio.content += audio_response.content # type: ignore
1005
+ if audio_response.transcript is not None:
1006
+ stream_data.response_audio.transcript += audio_response.transcript # type: ignore
1007
+ if audio_response.expires_at is not None:
1008
+ stream_data.response_audio.expires_at = audio_response.expires_at
1009
+ if audio_response.mime_type is not None:
1010
+ stream_data.response_audio.mime_type = audio_response.mime_type
1011
+ stream_data.response_audio.sample_rate = audio_response.sample_rate
1012
+ stream_data.response_audio.channels = audio_response.channels
1069
1013
 
1070
1014
  should_yield = True
1071
1015
 
1072
- if model_response_delta.image:
1016
+ if model_response_delta.images:
1073
1017
  if stream_data.response_image is None:
1074
- stream_data.response_image = model_response_delta.image
1018
+ stream_data.response_image = model_response_delta.images[-1]
1019
+ should_yield = True
1020
+
1021
+ if model_response_delta.videos:
1022
+ if stream_data.response_video is None:
1023
+ stream_data.response_video = model_response_delta.videos[-1]
1024
+ should_yield = True
1075
1025
 
1076
1026
  if model_response_delta.extra is not None:
1077
1027
  if stream_data.extra is None:
1078
1028
  stream_data.extra = {}
1079
1029
  stream_data.extra.update(model_response_delta.extra)
1080
1030
 
1081
- if model_response_delta.response_usage is not None:
1082
- _add_usage_metrics_to_assistant_message(
1083
- assistant_message=assistant_message, response_usage=model_response_delta.response_usage
1084
- )
1085
-
1086
1031
  if should_yield:
1087
1032
  yield model_response_delta
1088
1033
 
@@ -1142,11 +1087,24 @@ class Model(ABC):
1142
1087
  success: bool,
1143
1088
  output: Optional[Union[List[Any], str]] = None,
1144
1089
  timer: Optional[Timer] = None,
1090
+ function_execution_result: Optional[FunctionExecutionResult] = None,
1145
1091
  ) -> Message:
1146
1092
  """Create a function call result message."""
1147
1093
  kwargs = {}
1148
1094
  if timer is not None:
1149
- kwargs["metrics"] = MessageMetrics(time=timer.elapsed)
1095
+ kwargs["metrics"] = Metrics(duration=timer.elapsed)
1096
+
1097
+ # Include media artifacts from function execution result in the tool message
1098
+ images = None
1099
+ videos = None
1100
+ audios = None
1101
+
1102
+ if success and function_execution_result:
1103
+ # With unified classes, no conversion needed - use directly
1104
+ images = function_execution_result.images
1105
+ videos = function_execution_result.videos
1106
+ audios = function_execution_result.audios
1107
+
1150
1108
  return Message(
1151
1109
  role=self.tool_message_role,
1152
1110
  content=output if success else function_call.error,
@@ -1155,7 +1113,10 @@ class Model(ABC):
1155
1113
  tool_args=function_call.arguments,
1156
1114
  tool_call_error=not success,
1157
1115
  stop_after_tool_call=function_call.function.stop_after_tool_call,
1158
- **kwargs,
1116
+ images=images,
1117
+ videos=videos,
1118
+ audio=audios,
1119
+ **kwargs, # type: ignore
1159
1120
  )
1160
1121
 
1161
1122
  def create_tool_call_limit_error_result(self, function_call: FunctionCall) -> Message:
@@ -1172,8 +1133,8 @@ class Model(ABC):
1172
1133
  self,
1173
1134
  function_call: FunctionCall,
1174
1135
  function_call_results: List[Message],
1175
- additional_messages: Optional[List[Message]] = None,
1176
- ) -> Iterator[Union[ModelResponse, RunResponseEvent, TeamRunResponseEvent]]:
1136
+ additional_input: Optional[List[Message]] = None,
1137
+ ) -> Iterator[Union[ModelResponse, RunOutputEvent, TeamRunOutputEvent]]:
1177
1138
  # Start function call
1178
1139
  function_call_timer = Timer()
1179
1140
  function_call_timer.start()
@@ -1196,7 +1157,7 @@ class Model(ABC):
1196
1157
  function_execution_result = function_call.execute()
1197
1158
  except AgentRunException as a_exc:
1198
1159
  # Update additional messages from function call
1199
- _handle_agent_exception(a_exc, additional_messages)
1160
+ _handle_agent_exception(a_exc, additional_input)
1200
1161
  # Set function call success to False if an exception occurred
1201
1162
  except Exception as e:
1202
1163
  log_error(f"Error executing function {function_call.function.name}: {e}")
@@ -1210,14 +1171,14 @@ class Model(ABC):
1210
1171
  # Process function call output
1211
1172
  function_call_output: str = ""
1212
1173
 
1213
- if isinstance(function_call.result, (GeneratorType, collections.abc.Iterator)):
1214
- for item in function_call.result:
1174
+ if isinstance(function_execution_result.result, (GeneratorType, collections.abc.Iterator)):
1175
+ for item in function_execution_result.result:
1215
1176
  # This function yields agent/team run events
1216
- if isinstance(item, tuple(get_args(RunResponseEvent))) or isinstance(
1217
- item, tuple(get_args(TeamRunResponseEvent))
1177
+ if isinstance(item, tuple(get_args(RunOutputEvent))) or isinstance(
1178
+ item, tuple(get_args(TeamRunOutputEvent))
1218
1179
  ):
1219
1180
  # We only capture content events
1220
- if isinstance(item, RunResponseContentEvent) or isinstance(item, TeamRunResponseContentEvent):
1181
+ if isinstance(item, RunContentEvent) or isinstance(item, TeamRunContentEvent):
1221
1182
  if item.content is not None and isinstance(item.content, BaseModel):
1222
1183
  function_call_output += item.content.model_dump_json()
1223
1184
  else:
@@ -1227,6 +1188,9 @@ class Model(ABC):
1227
1188
  if function_call.function.show_result:
1228
1189
  yield ModelResponse(content=item.content)
1229
1190
 
1191
+ if isinstance(item, CustomEvent):
1192
+ function_call_output += str(item)
1193
+
1230
1194
  # Yield the event itself to bubble it up
1231
1195
  yield item
1232
1196
 
@@ -1235,13 +1199,33 @@ class Model(ABC):
1235
1199
  if function_call.function.show_result:
1236
1200
  yield ModelResponse(content=str(item))
1237
1201
  else:
1238
- function_call_output = str(function_call.result)
1202
+ from agno.tools.function import ToolResult
1203
+
1204
+ if isinstance(function_execution_result.result, ToolResult):
1205
+ # Extract content and media from ToolResult
1206
+ tool_result = function_execution_result.result
1207
+ function_call_output = tool_result.content
1208
+
1209
+ # Transfer media from ToolResult to FunctionExecutionResult
1210
+ if tool_result.images:
1211
+ function_execution_result.images = tool_result.images
1212
+ if tool_result.videos:
1213
+ function_execution_result.videos = tool_result.videos
1214
+ if tool_result.audios:
1215
+ function_execution_result.audios = tool_result.audios
1216
+ else:
1217
+ function_call_output = str(function_execution_result.result) if function_execution_result.result else ""
1218
+
1239
1219
  if function_call.function.show_result:
1240
1220
  yield ModelResponse(content=function_call_output)
1241
1221
 
1242
1222
  # Create and yield function call result
1243
1223
  function_call_result = self.create_function_call_result(
1244
- function_call, success=function_call_success, output=function_call_output, timer=function_call_timer
1224
+ function_call,
1225
+ success=function_call_success,
1226
+ output=function_call_output,
1227
+ timer=function_call_timer,
1228
+ function_execution_result=function_execution_result,
1245
1229
  )
1246
1230
  yield ModelResponse(
1247
1231
  content=f"{function_call.get_call_str()} completed in {function_call_timer.elapsed:.4f}s.",
@@ -1257,6 +1241,11 @@ class Model(ABC):
1257
1241
  )
1258
1242
  ],
1259
1243
  event=ModelResponseEvent.tool_call_completed.value,
1244
+ updated_session_state=function_execution_result.updated_session_state,
1245
+ # Add media artifacts from function execution
1246
+ images=function_execution_result.images,
1247
+ videos=function_execution_result.videos,
1248
+ audios=function_execution_result.audios,
1260
1249
  )
1261
1250
 
1262
1251
  # Add function call to function call results
@@ -1266,13 +1255,13 @@ class Model(ABC):
1266
1255
  self,
1267
1256
  function_calls: List[FunctionCall],
1268
1257
  function_call_results: List[Message],
1269
- additional_messages: Optional[List[Message]] = None,
1258
+ additional_input: Optional[List[Message]] = None,
1270
1259
  current_function_call_count: int = 0,
1271
1260
  function_call_limit: Optional[int] = None,
1272
- ) -> Iterator[Union[ModelResponse, RunResponseEvent, TeamRunResponseEvent]]:
1261
+ ) -> Iterator[Union[ModelResponse, RunOutputEvent, TeamRunOutputEvent]]:
1273
1262
  # Additional messages from function calls that will be added to the function call results
1274
- if additional_messages is None:
1275
- additional_messages = []
1263
+ if additional_input is None:
1264
+ additional_input = []
1276
1265
 
1277
1266
  for fc in function_calls:
1278
1267
  if function_call_limit is not None:
@@ -1358,17 +1347,17 @@ class Model(ABC):
1358
1347
  continue
1359
1348
 
1360
1349
  yield from self.run_function_call(
1361
- function_call=fc, function_call_results=function_call_results, additional_messages=additional_messages
1350
+ function_call=fc, function_call_results=function_call_results, additional_input=additional_input
1362
1351
  )
1363
1352
 
1364
1353
  # Add any additional messages at the end
1365
- if additional_messages:
1366
- function_call_results.extend(additional_messages)
1354
+ if additional_input:
1355
+ function_call_results.extend(additional_input)
1367
1356
 
1368
1357
  async def arun_function_call(
1369
1358
  self,
1370
1359
  function_call: FunctionCall,
1371
- ) -> Tuple[Union[bool, AgentRunException], Timer, FunctionCall]:
1360
+ ) -> Tuple[Union[bool, AgentRunException], Timer, FunctionCall, FunctionExecutionResult]:
1372
1361
  """Run a single function call and return its success status, timer, and the FunctionCall object."""
1373
1362
  from inspect import isasyncgenfunction, iscoroutine, iscoroutinefunction
1374
1363
 
@@ -1402,20 +1391,20 @@ class Model(ABC):
1402
1391
  raise e
1403
1392
 
1404
1393
  function_call_timer.stop()
1405
- return success, function_call_timer, function_call
1394
+ return success, function_call_timer, function_call, result
1406
1395
 
1407
1396
  async def arun_function_calls(
1408
1397
  self,
1409
1398
  function_calls: List[FunctionCall],
1410
1399
  function_call_results: List[Message],
1411
- additional_messages: Optional[List[Message]] = None,
1400
+ additional_input: Optional[List[Message]] = None,
1412
1401
  current_function_call_count: int = 0,
1413
1402
  function_call_limit: Optional[int] = None,
1414
1403
  skip_pause_check: bool = False,
1415
- ) -> AsyncIterator[Union[ModelResponse, RunResponseEvent, TeamRunResponseEvent]]:
1404
+ ) -> AsyncIterator[Union[ModelResponse, RunOutputEvent, TeamRunOutputEvent]]:
1416
1405
  # Additional messages from function calls that will be added to the function call results
1417
- if additional_messages is None:
1418
- additional_messages = []
1406
+ if additional_input is None:
1407
+ additional_input = []
1419
1408
 
1420
1409
  function_calls_to_run = []
1421
1410
  for fc in function_calls:
@@ -1548,33 +1537,35 @@ class Model(ABC):
1548
1537
  raise result
1549
1538
 
1550
1539
  # Unpack result
1551
- function_call_success, function_call_timer, fc = result
1540
+ function_call_success, function_call_timer, function_call, function_execution_result = result
1541
+
1542
+ updated_session_state = function_execution_result.updated_session_state
1552
1543
 
1553
1544
  # Handle AgentRunException
1554
1545
  if isinstance(function_call_success, AgentRunException):
1555
1546
  a_exc = function_call_success
1556
1547
  # Update additional messages from function call
1557
- _handle_agent_exception(a_exc, additional_messages)
1548
+ _handle_agent_exception(a_exc, additional_input)
1558
1549
  # Set function call success to False if an exception occurred
1559
1550
  function_call_success = False
1560
1551
 
1561
1552
  # Process function call output
1562
1553
  function_call_output: str = ""
1563
- if isinstance(fc.result, (GeneratorType, collections.abc.Iterator)):
1564
- for item in fc.result:
1554
+ if isinstance(function_call.result, (GeneratorType, collections.abc.Iterator)):
1555
+ for item in function_call.result:
1565
1556
  # This function yields agent/team run events
1566
- if isinstance(item, tuple(get_args(RunResponseEvent))) or isinstance(
1567
- item, tuple(get_args(TeamRunResponseEvent))
1557
+ if isinstance(item, tuple(get_args(RunOutputEvent))) or isinstance(
1558
+ item, tuple(get_args(TeamRunOutputEvent))
1568
1559
  ):
1569
1560
  # We only capture content events
1570
- if isinstance(item, RunResponseContentEvent) or isinstance(item, TeamRunResponseContentEvent):
1561
+ if isinstance(item, RunContentEvent) or isinstance(item, TeamRunContentEvent):
1571
1562
  if item.content is not None and isinstance(item.content, BaseModel):
1572
1563
  function_call_output += item.content.model_dump_json()
1573
1564
  else:
1574
1565
  # Capture output
1575
1566
  function_call_output += item.content or ""
1576
1567
 
1577
- if fc.function.show_result:
1568
+ if function_call.function.show_result:
1578
1569
  yield ModelResponse(content=item.content)
1579
1570
  continue
1580
1571
 
@@ -1582,43 +1573,66 @@ class Model(ABC):
1582
1573
  yield item
1583
1574
  else:
1584
1575
  function_call_output += str(item)
1585
- if fc.function.show_result:
1576
+ if function_call.function.show_result:
1586
1577
  yield ModelResponse(content=str(item))
1587
- elif isinstance(fc.result, (AsyncGeneratorType, collections.abc.AsyncIterator)):
1588
- async for item in fc.result:
1578
+ elif isinstance(function_call.result, (AsyncGeneratorType, collections.abc.AsyncIterator)):
1579
+ async for item in function_call.result:
1589
1580
  # This function yields agent/team run events
1590
- if isinstance(item, tuple(get_args(RunResponseEvent))) or isinstance(
1591
- item, tuple(get_args(TeamRunResponseEvent))
1581
+ if isinstance(item, tuple(get_args(RunOutputEvent))) or isinstance(
1582
+ item, tuple(get_args(TeamRunOutputEvent))
1592
1583
  ):
1593
1584
  # We only capture content events
1594
- if isinstance(item, RunResponseContentEvent) or isinstance(item, TeamRunResponseContentEvent):
1585
+ if isinstance(item, RunContentEvent) or isinstance(item, TeamRunContentEvent):
1595
1586
  if item.content is not None and isinstance(item.content, BaseModel):
1596
1587
  function_call_output += item.content.model_dump_json()
1597
1588
  else:
1598
1589
  # Capture output
1599
1590
  function_call_output += item.content or ""
1600
1591
 
1601
- if fc.function.show_result:
1592
+ if function_call.function.show_result:
1602
1593
  yield ModelResponse(content=item.content)
1603
1594
  continue
1604
1595
 
1596
+ if isinstance(item, CustomEvent):
1597
+ function_call_output += str(item)
1598
+
1605
1599
  # Yield the event itself to bubble it up
1606
1600
  yield item
1601
+
1602
+ # Yield custom events emitted by the tool
1607
1603
  else:
1608
1604
  function_call_output += str(item)
1609
- if fc.function.show_result:
1605
+ if function_call.function.show_result:
1610
1606
  yield ModelResponse(content=str(item))
1611
1607
  else:
1612
- function_call_output = str(fc.result)
1613
- if fc.function.show_result:
1608
+ from agno.tools.function import ToolResult
1609
+
1610
+ if isinstance(function_execution_result.result, ToolResult):
1611
+ tool_result = function_execution_result.result
1612
+ function_call_output = tool_result.content
1613
+
1614
+ if tool_result.images:
1615
+ function_execution_result.images = tool_result.images
1616
+ if tool_result.videos:
1617
+ function_execution_result.videos = tool_result.videos
1618
+ if tool_result.audios:
1619
+ function_execution_result.audios = tool_result.audios
1620
+ else:
1621
+ function_call_output = str(function_call.result)
1622
+
1623
+ if function_call.function.show_result:
1614
1624
  yield ModelResponse(content=function_call_output)
1615
1625
 
1616
1626
  # Create and yield function call result
1617
1627
  function_call_result = self.create_function_call_result(
1618
- fc, success=function_call_success, output=function_call_output, timer=function_call_timer
1628
+ function_call,
1629
+ success=function_call_success,
1630
+ output=function_call_output,
1631
+ timer=function_call_timer,
1632
+ function_execution_result=function_execution_result,
1619
1633
  )
1620
1634
  yield ModelResponse(
1621
- content=f"{fc.get_call_str()} completed in {function_call_timer.elapsed:.4f}s.",
1635
+ content=f"{function_call.get_call_str()} completed in {function_call_timer.elapsed:.4f}s.",
1622
1636
  tool_executions=[
1623
1637
  ToolExecution(
1624
1638
  tool_call_id=function_call_result.tool_call_id,
@@ -1631,14 +1645,18 @@ class Model(ABC):
1631
1645
  )
1632
1646
  ],
1633
1647
  event=ModelResponseEvent.tool_call_completed.value,
1648
+ updated_session_state=updated_session_state,
1649
+ images=function_execution_result.images,
1650
+ videos=function_execution_result.videos,
1651
+ audios=function_execution_result.audios,
1634
1652
  )
1635
1653
 
1636
1654
  # Add function call result to function call results
1637
1655
  function_call_results.append(function_call_result)
1638
1656
 
1639
1657
  # Add any additional messages at the end
1640
- if additional_messages:
1641
- function_call_results.extend(additional_messages)
1658
+ if additional_input:
1659
+ function_call_results.extend(additional_input)
1642
1660
 
1643
1661
  def _prepare_function_calls(
1644
1662
  self,
@@ -1669,6 +1687,44 @@ class Model(ABC):
1669
1687
  if len(function_call_results) > 0:
1670
1688
  messages.extend(function_call_results)
1671
1689
 
1690
+ def _handle_function_call_media(self, messages: List[Message], function_call_results: List[Message]) -> None:
1691
+ """
1692
+ Handle media artifacts from function calls by adding follow-up user messages for generated media if needed.
1693
+ """
1694
+ if not function_call_results:
1695
+ return
1696
+
1697
+ # Collect all media artifacts from function calls
1698
+ all_images: List[Image] = []
1699
+ all_videos: List[Video] = []
1700
+ all_audio: List[Audio] = []
1701
+
1702
+ for result_message in function_call_results:
1703
+ if result_message.images:
1704
+ all_images.extend(result_message.images)
1705
+ # Remove images from tool message to avoid errors on the LLMs
1706
+ result_message.images = None
1707
+
1708
+ if result_message.videos:
1709
+ all_videos.extend(result_message.videos)
1710
+ result_message.videos = None
1711
+
1712
+ if result_message.audio:
1713
+ all_audio.extend(result_message.audio)
1714
+ result_message.audio = None
1715
+
1716
+ # If we have media artifacts, add a follow-up "user" message instead of a "tool"
1717
+ # message with the media artifacts which throws error for some models
1718
+ if all_images or all_videos or all_audio:
1719
+ media_message = Message(
1720
+ role="user",
1721
+ content="Take note of the following content",
1722
+ images=all_images if all_images else None,
1723
+ videos=all_videos if all_videos else None,
1724
+ audio=all_audio if all_audio else None,
1725
+ )
1726
+ messages.append(media_message)
1727
+
1672
1728
  def get_system_message_for_model(self, tools: Optional[List[Any]] = None) -> Optional[str]:
1673
1729
  return self.system_prompt
1674
1730