agno 0.1.2__py3-none-any.whl → 2.3.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (723) hide show
  1. agno/__init__.py +8 -0
  2. agno/agent/__init__.py +44 -5
  3. agno/agent/agent.py +10531 -2975
  4. agno/api/agent.py +14 -53
  5. agno/api/api.py +7 -46
  6. agno/api/evals.py +22 -0
  7. agno/api/os.py +17 -0
  8. agno/api/routes.py +6 -25
  9. agno/api/schemas/__init__.py +9 -0
  10. agno/api/schemas/agent.py +6 -9
  11. agno/api/schemas/evals.py +16 -0
  12. agno/api/schemas/os.py +14 -0
  13. agno/api/schemas/team.py +10 -10
  14. agno/api/schemas/utils.py +21 -0
  15. agno/api/schemas/workflows.py +16 -0
  16. agno/api/settings.py +53 -0
  17. agno/api/team.py +22 -26
  18. agno/api/workflow.py +28 -0
  19. agno/cloud/aws/base.py +214 -0
  20. agno/cloud/aws/s3/__init__.py +2 -0
  21. agno/cloud/aws/s3/api_client.py +43 -0
  22. agno/cloud/aws/s3/bucket.py +195 -0
  23. agno/cloud/aws/s3/object.py +57 -0
  24. agno/compression/__init__.py +3 -0
  25. agno/compression/manager.py +247 -0
  26. agno/culture/__init__.py +3 -0
  27. agno/culture/manager.py +956 -0
  28. agno/db/__init__.py +24 -0
  29. agno/db/async_postgres/__init__.py +3 -0
  30. agno/db/base.py +946 -0
  31. agno/db/dynamo/__init__.py +3 -0
  32. agno/db/dynamo/dynamo.py +2781 -0
  33. agno/db/dynamo/schemas.py +442 -0
  34. agno/db/dynamo/utils.py +743 -0
  35. agno/db/firestore/__init__.py +3 -0
  36. agno/db/firestore/firestore.py +2379 -0
  37. agno/db/firestore/schemas.py +181 -0
  38. agno/db/firestore/utils.py +376 -0
  39. agno/db/gcs_json/__init__.py +3 -0
  40. agno/db/gcs_json/gcs_json_db.py +1791 -0
  41. agno/db/gcs_json/utils.py +228 -0
  42. agno/db/in_memory/__init__.py +3 -0
  43. agno/db/in_memory/in_memory_db.py +1312 -0
  44. agno/db/in_memory/utils.py +230 -0
  45. agno/db/json/__init__.py +3 -0
  46. agno/db/json/json_db.py +1777 -0
  47. agno/db/json/utils.py +230 -0
  48. agno/db/migrations/manager.py +199 -0
  49. agno/db/migrations/v1_to_v2.py +635 -0
  50. agno/db/migrations/versions/v2_3_0.py +938 -0
  51. agno/db/mongo/__init__.py +17 -0
  52. agno/db/mongo/async_mongo.py +2760 -0
  53. agno/db/mongo/mongo.py +2597 -0
  54. agno/db/mongo/schemas.py +119 -0
  55. agno/db/mongo/utils.py +276 -0
  56. agno/db/mysql/__init__.py +4 -0
  57. agno/db/mysql/async_mysql.py +2912 -0
  58. agno/db/mysql/mysql.py +2923 -0
  59. agno/db/mysql/schemas.py +186 -0
  60. agno/db/mysql/utils.py +488 -0
  61. agno/db/postgres/__init__.py +4 -0
  62. agno/db/postgres/async_postgres.py +2579 -0
  63. agno/db/postgres/postgres.py +2870 -0
  64. agno/db/postgres/schemas.py +187 -0
  65. agno/db/postgres/utils.py +442 -0
  66. agno/db/redis/__init__.py +3 -0
  67. agno/db/redis/redis.py +2141 -0
  68. agno/db/redis/schemas.py +159 -0
  69. agno/db/redis/utils.py +346 -0
  70. agno/db/schemas/__init__.py +4 -0
  71. agno/db/schemas/culture.py +120 -0
  72. agno/db/schemas/evals.py +34 -0
  73. agno/db/schemas/knowledge.py +40 -0
  74. agno/db/schemas/memory.py +61 -0
  75. agno/db/singlestore/__init__.py +3 -0
  76. agno/db/singlestore/schemas.py +179 -0
  77. agno/db/singlestore/singlestore.py +2877 -0
  78. agno/db/singlestore/utils.py +384 -0
  79. agno/db/sqlite/__init__.py +4 -0
  80. agno/db/sqlite/async_sqlite.py +2911 -0
  81. agno/db/sqlite/schemas.py +181 -0
  82. agno/db/sqlite/sqlite.py +2908 -0
  83. agno/db/sqlite/utils.py +429 -0
  84. agno/db/surrealdb/__init__.py +3 -0
  85. agno/db/surrealdb/metrics.py +292 -0
  86. agno/db/surrealdb/models.py +334 -0
  87. agno/db/surrealdb/queries.py +71 -0
  88. agno/db/surrealdb/surrealdb.py +1908 -0
  89. agno/db/surrealdb/utils.py +147 -0
  90. agno/db/utils.py +118 -0
  91. agno/eval/__init__.py +24 -0
  92. agno/eval/accuracy.py +666 -276
  93. agno/eval/agent_as_judge.py +861 -0
  94. agno/eval/base.py +29 -0
  95. agno/eval/performance.py +779 -0
  96. agno/eval/reliability.py +241 -62
  97. agno/eval/utils.py +120 -0
  98. agno/exceptions.py +143 -1
  99. agno/filters.py +354 -0
  100. agno/guardrails/__init__.py +6 -0
  101. agno/guardrails/base.py +19 -0
  102. agno/guardrails/openai.py +144 -0
  103. agno/guardrails/pii.py +94 -0
  104. agno/guardrails/prompt_injection.py +52 -0
  105. agno/hooks/__init__.py +3 -0
  106. agno/hooks/decorator.py +164 -0
  107. agno/integrations/discord/__init__.py +3 -0
  108. agno/integrations/discord/client.py +203 -0
  109. agno/knowledge/__init__.py +5 -1
  110. agno/{document → knowledge}/chunking/agentic.py +22 -14
  111. agno/{document → knowledge}/chunking/document.py +2 -2
  112. agno/{document → knowledge}/chunking/fixed.py +7 -6
  113. agno/knowledge/chunking/markdown.py +151 -0
  114. agno/{document → knowledge}/chunking/recursive.py +15 -3
  115. agno/knowledge/chunking/row.py +39 -0
  116. agno/knowledge/chunking/semantic.py +91 -0
  117. agno/knowledge/chunking/strategy.py +165 -0
  118. agno/knowledge/content.py +74 -0
  119. agno/knowledge/document/__init__.py +5 -0
  120. agno/{document → knowledge/document}/base.py +12 -2
  121. agno/knowledge/embedder/__init__.py +5 -0
  122. agno/knowledge/embedder/aws_bedrock.py +343 -0
  123. agno/knowledge/embedder/azure_openai.py +210 -0
  124. agno/{embedder → knowledge/embedder}/base.py +8 -0
  125. agno/knowledge/embedder/cohere.py +323 -0
  126. agno/knowledge/embedder/fastembed.py +62 -0
  127. agno/{embedder → knowledge/embedder}/fireworks.py +1 -1
  128. agno/knowledge/embedder/google.py +258 -0
  129. agno/knowledge/embedder/huggingface.py +94 -0
  130. agno/knowledge/embedder/jina.py +182 -0
  131. agno/knowledge/embedder/langdb.py +22 -0
  132. agno/knowledge/embedder/mistral.py +206 -0
  133. agno/knowledge/embedder/nebius.py +13 -0
  134. agno/knowledge/embedder/ollama.py +154 -0
  135. agno/knowledge/embedder/openai.py +195 -0
  136. agno/knowledge/embedder/sentence_transformer.py +63 -0
  137. agno/{embedder → knowledge/embedder}/together.py +1 -1
  138. agno/knowledge/embedder/vllm.py +262 -0
  139. agno/knowledge/embedder/voyageai.py +165 -0
  140. agno/knowledge/knowledge.py +3006 -0
  141. agno/knowledge/reader/__init__.py +7 -0
  142. agno/knowledge/reader/arxiv_reader.py +81 -0
  143. agno/knowledge/reader/base.py +95 -0
  144. agno/knowledge/reader/csv_reader.py +164 -0
  145. agno/knowledge/reader/docx_reader.py +82 -0
  146. agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
  147. agno/knowledge/reader/firecrawl_reader.py +201 -0
  148. agno/knowledge/reader/json_reader.py +88 -0
  149. agno/knowledge/reader/markdown_reader.py +137 -0
  150. agno/knowledge/reader/pdf_reader.py +431 -0
  151. agno/knowledge/reader/pptx_reader.py +101 -0
  152. agno/knowledge/reader/reader_factory.py +313 -0
  153. agno/knowledge/reader/s3_reader.py +89 -0
  154. agno/knowledge/reader/tavily_reader.py +193 -0
  155. agno/knowledge/reader/text_reader.py +127 -0
  156. agno/knowledge/reader/web_search_reader.py +325 -0
  157. agno/knowledge/reader/website_reader.py +455 -0
  158. agno/knowledge/reader/wikipedia_reader.py +91 -0
  159. agno/knowledge/reader/youtube_reader.py +78 -0
  160. agno/knowledge/remote_content/remote_content.py +88 -0
  161. agno/knowledge/reranker/__init__.py +3 -0
  162. agno/{reranker → knowledge/reranker}/base.py +1 -1
  163. agno/{reranker → knowledge/reranker}/cohere.py +2 -2
  164. agno/knowledge/reranker/infinity.py +195 -0
  165. agno/knowledge/reranker/sentence_transformer.py +54 -0
  166. agno/knowledge/types.py +39 -0
  167. agno/knowledge/utils.py +234 -0
  168. agno/media.py +439 -95
  169. agno/memory/__init__.py +16 -3
  170. agno/memory/manager.py +1474 -123
  171. agno/memory/strategies/__init__.py +15 -0
  172. agno/memory/strategies/base.py +66 -0
  173. agno/memory/strategies/summarize.py +196 -0
  174. agno/memory/strategies/types.py +37 -0
  175. agno/models/aimlapi/__init__.py +5 -0
  176. agno/models/aimlapi/aimlapi.py +62 -0
  177. agno/models/anthropic/__init__.py +4 -0
  178. agno/models/anthropic/claude.py +960 -496
  179. agno/models/aws/__init__.py +15 -0
  180. agno/models/aws/bedrock.py +686 -451
  181. agno/models/aws/claude.py +190 -183
  182. agno/models/azure/__init__.py +18 -1
  183. agno/models/azure/ai_foundry.py +489 -0
  184. agno/models/azure/openai_chat.py +89 -40
  185. agno/models/base.py +2477 -550
  186. agno/models/cerebras/__init__.py +12 -0
  187. agno/models/cerebras/cerebras.py +565 -0
  188. agno/models/cerebras/cerebras_openai.py +131 -0
  189. agno/models/cohere/__init__.py +4 -0
  190. agno/models/cohere/chat.py +306 -492
  191. agno/models/cometapi/__init__.py +5 -0
  192. agno/models/cometapi/cometapi.py +74 -0
  193. agno/models/dashscope/__init__.py +5 -0
  194. agno/models/dashscope/dashscope.py +90 -0
  195. agno/models/deepinfra/__init__.py +5 -0
  196. agno/models/deepinfra/deepinfra.py +45 -0
  197. agno/models/deepseek/__init__.py +4 -0
  198. agno/models/deepseek/deepseek.py +110 -9
  199. agno/models/fireworks/__init__.py +4 -0
  200. agno/models/fireworks/fireworks.py +19 -22
  201. agno/models/google/__init__.py +3 -7
  202. agno/models/google/gemini.py +1717 -662
  203. agno/models/google/utils.py +22 -0
  204. agno/models/groq/__init__.py +4 -0
  205. agno/models/groq/groq.py +391 -666
  206. agno/models/huggingface/__init__.py +4 -0
  207. agno/models/huggingface/huggingface.py +266 -538
  208. agno/models/ibm/__init__.py +5 -0
  209. agno/models/ibm/watsonx.py +432 -0
  210. agno/models/internlm/__init__.py +3 -0
  211. agno/models/internlm/internlm.py +20 -3
  212. agno/models/langdb/__init__.py +1 -0
  213. agno/models/langdb/langdb.py +60 -0
  214. agno/models/litellm/__init__.py +14 -0
  215. agno/models/litellm/chat.py +503 -0
  216. agno/models/litellm/litellm_openai.py +42 -0
  217. agno/models/llama_cpp/__init__.py +5 -0
  218. agno/models/llama_cpp/llama_cpp.py +22 -0
  219. agno/models/lmstudio/__init__.py +5 -0
  220. agno/models/lmstudio/lmstudio.py +25 -0
  221. agno/models/message.py +361 -39
  222. agno/models/meta/__init__.py +12 -0
  223. agno/models/meta/llama.py +502 -0
  224. agno/models/meta/llama_openai.py +79 -0
  225. agno/models/metrics.py +120 -0
  226. agno/models/mistral/__init__.py +4 -0
  227. agno/models/mistral/mistral.py +293 -393
  228. agno/models/nebius/__init__.py +3 -0
  229. agno/models/nebius/nebius.py +53 -0
  230. agno/models/nexus/__init__.py +3 -0
  231. agno/models/nexus/nexus.py +22 -0
  232. agno/models/nvidia/__init__.py +4 -0
  233. agno/models/nvidia/nvidia.py +22 -3
  234. agno/models/ollama/__init__.py +4 -2
  235. agno/models/ollama/chat.py +257 -492
  236. agno/models/openai/__init__.py +7 -0
  237. agno/models/openai/chat.py +725 -770
  238. agno/models/openai/like.py +16 -2
  239. agno/models/openai/responses.py +1121 -0
  240. agno/models/openrouter/__init__.py +4 -0
  241. agno/models/openrouter/openrouter.py +62 -5
  242. agno/models/perplexity/__init__.py +5 -0
  243. agno/models/perplexity/perplexity.py +203 -0
  244. agno/models/portkey/__init__.py +3 -0
  245. agno/models/portkey/portkey.py +82 -0
  246. agno/models/requesty/__init__.py +5 -0
  247. agno/models/requesty/requesty.py +69 -0
  248. agno/models/response.py +177 -7
  249. agno/models/sambanova/__init__.py +4 -0
  250. agno/models/sambanova/sambanova.py +23 -4
  251. agno/models/siliconflow/__init__.py +5 -0
  252. agno/models/siliconflow/siliconflow.py +42 -0
  253. agno/models/together/__init__.py +4 -0
  254. agno/models/together/together.py +21 -164
  255. agno/models/utils.py +266 -0
  256. agno/models/vercel/__init__.py +3 -0
  257. agno/models/vercel/v0.py +43 -0
  258. agno/models/vertexai/__init__.py +0 -1
  259. agno/models/vertexai/claude.py +190 -0
  260. agno/models/vllm/__init__.py +3 -0
  261. agno/models/vllm/vllm.py +83 -0
  262. agno/models/xai/__init__.py +2 -0
  263. agno/models/xai/xai.py +111 -7
  264. agno/os/__init__.py +3 -0
  265. agno/os/app.py +1027 -0
  266. agno/os/auth.py +244 -0
  267. agno/os/config.py +126 -0
  268. agno/os/interfaces/__init__.py +1 -0
  269. agno/os/interfaces/a2a/__init__.py +3 -0
  270. agno/os/interfaces/a2a/a2a.py +42 -0
  271. agno/os/interfaces/a2a/router.py +249 -0
  272. agno/os/interfaces/a2a/utils.py +924 -0
  273. agno/os/interfaces/agui/__init__.py +3 -0
  274. agno/os/interfaces/agui/agui.py +47 -0
  275. agno/os/interfaces/agui/router.py +147 -0
  276. agno/os/interfaces/agui/utils.py +574 -0
  277. agno/os/interfaces/base.py +25 -0
  278. agno/os/interfaces/slack/__init__.py +3 -0
  279. agno/os/interfaces/slack/router.py +148 -0
  280. agno/os/interfaces/slack/security.py +30 -0
  281. agno/os/interfaces/slack/slack.py +47 -0
  282. agno/os/interfaces/whatsapp/__init__.py +3 -0
  283. agno/os/interfaces/whatsapp/router.py +210 -0
  284. agno/os/interfaces/whatsapp/security.py +55 -0
  285. agno/os/interfaces/whatsapp/whatsapp.py +36 -0
  286. agno/os/mcp.py +293 -0
  287. agno/os/middleware/__init__.py +9 -0
  288. agno/os/middleware/jwt.py +797 -0
  289. agno/os/router.py +258 -0
  290. agno/os/routers/__init__.py +3 -0
  291. agno/os/routers/agents/__init__.py +3 -0
  292. agno/os/routers/agents/router.py +599 -0
  293. agno/os/routers/agents/schema.py +261 -0
  294. agno/os/routers/evals/__init__.py +3 -0
  295. agno/os/routers/evals/evals.py +450 -0
  296. agno/os/routers/evals/schemas.py +174 -0
  297. agno/os/routers/evals/utils.py +231 -0
  298. agno/os/routers/health.py +31 -0
  299. agno/os/routers/home.py +52 -0
  300. agno/os/routers/knowledge/__init__.py +3 -0
  301. agno/os/routers/knowledge/knowledge.py +1008 -0
  302. agno/os/routers/knowledge/schemas.py +178 -0
  303. agno/os/routers/memory/__init__.py +3 -0
  304. agno/os/routers/memory/memory.py +661 -0
  305. agno/os/routers/memory/schemas.py +88 -0
  306. agno/os/routers/metrics/__init__.py +3 -0
  307. agno/os/routers/metrics/metrics.py +190 -0
  308. agno/os/routers/metrics/schemas.py +47 -0
  309. agno/os/routers/session/__init__.py +3 -0
  310. agno/os/routers/session/session.py +997 -0
  311. agno/os/routers/teams/__init__.py +3 -0
  312. agno/os/routers/teams/router.py +512 -0
  313. agno/os/routers/teams/schema.py +257 -0
  314. agno/os/routers/traces/__init__.py +3 -0
  315. agno/os/routers/traces/schemas.py +414 -0
  316. agno/os/routers/traces/traces.py +499 -0
  317. agno/os/routers/workflows/__init__.py +3 -0
  318. agno/os/routers/workflows/router.py +624 -0
  319. agno/os/routers/workflows/schema.py +75 -0
  320. agno/os/schema.py +534 -0
  321. agno/os/scopes.py +469 -0
  322. agno/{playground → os}/settings.py +7 -15
  323. agno/os/utils.py +973 -0
  324. agno/reasoning/anthropic.py +80 -0
  325. agno/reasoning/azure_ai_foundry.py +67 -0
  326. agno/reasoning/deepseek.py +63 -0
  327. agno/reasoning/default.py +97 -0
  328. agno/reasoning/gemini.py +73 -0
  329. agno/reasoning/groq.py +71 -0
  330. agno/reasoning/helpers.py +24 -1
  331. agno/reasoning/ollama.py +67 -0
  332. agno/reasoning/openai.py +86 -0
  333. agno/reasoning/step.py +2 -1
  334. agno/reasoning/vertexai.py +76 -0
  335. agno/run/__init__.py +6 -0
  336. agno/run/agent.py +822 -0
  337. agno/run/base.py +247 -0
  338. agno/run/cancel.py +81 -0
  339. agno/run/requirement.py +181 -0
  340. agno/run/team.py +767 -0
  341. agno/run/workflow.py +708 -0
  342. agno/session/__init__.py +10 -0
  343. agno/session/agent.py +260 -0
  344. agno/session/summary.py +265 -0
  345. agno/session/team.py +342 -0
  346. agno/session/workflow.py +501 -0
  347. agno/table.py +10 -0
  348. agno/team/__init__.py +37 -0
  349. agno/team/team.py +9536 -0
  350. agno/tools/__init__.py +7 -0
  351. agno/tools/agentql.py +120 -0
  352. agno/tools/airflow.py +22 -12
  353. agno/tools/api.py +122 -0
  354. agno/tools/apify.py +276 -83
  355. agno/tools/{arxiv_toolkit.py → arxiv.py} +20 -12
  356. agno/tools/aws_lambda.py +28 -7
  357. agno/tools/aws_ses.py +66 -0
  358. agno/tools/baidusearch.py +11 -4
  359. agno/tools/bitbucket.py +292 -0
  360. agno/tools/brandfetch.py +213 -0
  361. agno/tools/bravesearch.py +106 -0
  362. agno/tools/brightdata.py +367 -0
  363. agno/tools/browserbase.py +209 -0
  364. agno/tools/calcom.py +32 -23
  365. agno/tools/calculator.py +24 -37
  366. agno/tools/cartesia.py +187 -0
  367. agno/tools/{clickup_tool.py → clickup.py} +17 -28
  368. agno/tools/confluence.py +91 -26
  369. agno/tools/crawl4ai.py +139 -43
  370. agno/tools/csv_toolkit.py +28 -22
  371. agno/tools/dalle.py +36 -22
  372. agno/tools/daytona.py +475 -0
  373. agno/tools/decorator.py +169 -14
  374. agno/tools/desi_vocal.py +23 -11
  375. agno/tools/discord.py +32 -29
  376. agno/tools/docker.py +716 -0
  377. agno/tools/duckdb.py +76 -81
  378. agno/tools/duckduckgo.py +43 -40
  379. agno/tools/e2b.py +703 -0
  380. agno/tools/eleven_labs.py +65 -54
  381. agno/tools/email.py +13 -5
  382. agno/tools/evm.py +129 -0
  383. agno/tools/exa.py +324 -42
  384. agno/tools/fal.py +39 -35
  385. agno/tools/file.py +196 -30
  386. agno/tools/file_generation.py +356 -0
  387. agno/tools/financial_datasets.py +288 -0
  388. agno/tools/firecrawl.py +108 -33
  389. agno/tools/function.py +960 -122
  390. agno/tools/giphy.py +34 -12
  391. agno/tools/github.py +1294 -97
  392. agno/tools/gmail.py +922 -0
  393. agno/tools/google_bigquery.py +117 -0
  394. agno/tools/google_drive.py +271 -0
  395. agno/tools/google_maps.py +253 -0
  396. agno/tools/googlecalendar.py +607 -107
  397. agno/tools/googlesheets.py +377 -0
  398. agno/tools/hackernews.py +20 -12
  399. agno/tools/jina.py +24 -14
  400. agno/tools/jira.py +48 -19
  401. agno/tools/knowledge.py +218 -0
  402. agno/tools/linear.py +82 -43
  403. agno/tools/linkup.py +58 -0
  404. agno/tools/local_file_system.py +15 -7
  405. agno/tools/lumalab.py +41 -26
  406. agno/tools/mcp/__init__.py +10 -0
  407. agno/tools/mcp/mcp.py +331 -0
  408. agno/tools/mcp/multi_mcp.py +347 -0
  409. agno/tools/mcp/params.py +24 -0
  410. agno/tools/mcp_toolbox.py +284 -0
  411. agno/tools/mem0.py +193 -0
  412. agno/tools/memory.py +419 -0
  413. agno/tools/mlx_transcribe.py +11 -9
  414. agno/tools/models/azure_openai.py +190 -0
  415. agno/tools/models/gemini.py +203 -0
  416. agno/tools/models/groq.py +158 -0
  417. agno/tools/models/morph.py +186 -0
  418. agno/tools/models/nebius.py +124 -0
  419. agno/tools/models_labs.py +163 -82
  420. agno/tools/moviepy_video.py +18 -13
  421. agno/tools/nano_banana.py +151 -0
  422. agno/tools/neo4j.py +134 -0
  423. agno/tools/newspaper.py +15 -4
  424. agno/tools/newspaper4k.py +19 -6
  425. agno/tools/notion.py +204 -0
  426. agno/tools/openai.py +181 -17
  427. agno/tools/openbb.py +27 -20
  428. agno/tools/opencv.py +321 -0
  429. agno/tools/openweather.py +233 -0
  430. agno/tools/oxylabs.py +385 -0
  431. agno/tools/pandas.py +25 -15
  432. agno/tools/parallel.py +314 -0
  433. agno/tools/postgres.py +238 -185
  434. agno/tools/pubmed.py +125 -13
  435. agno/tools/python.py +48 -35
  436. agno/tools/reasoning.py +283 -0
  437. agno/tools/reddit.py +207 -29
  438. agno/tools/redshift.py +406 -0
  439. agno/tools/replicate.py +69 -26
  440. agno/tools/resend.py +11 -6
  441. agno/tools/scrapegraph.py +179 -19
  442. agno/tools/searxng.py +23 -31
  443. agno/tools/serpapi.py +15 -10
  444. agno/tools/serper.py +255 -0
  445. agno/tools/shell.py +23 -12
  446. agno/tools/shopify.py +1519 -0
  447. agno/tools/slack.py +56 -14
  448. agno/tools/sleep.py +8 -6
  449. agno/tools/spider.py +35 -11
  450. agno/tools/spotify.py +919 -0
  451. agno/tools/sql.py +34 -19
  452. agno/tools/tavily.py +158 -8
  453. agno/tools/telegram.py +18 -8
  454. agno/tools/todoist.py +218 -0
  455. agno/tools/toolkit.py +134 -9
  456. agno/tools/trafilatura.py +388 -0
  457. agno/tools/trello.py +25 -28
  458. agno/tools/twilio.py +18 -9
  459. agno/tools/user_control_flow.py +78 -0
  460. agno/tools/valyu.py +228 -0
  461. agno/tools/visualization.py +467 -0
  462. agno/tools/webbrowser.py +28 -0
  463. agno/tools/webex.py +76 -0
  464. agno/tools/website.py +23 -19
  465. agno/tools/webtools.py +45 -0
  466. agno/tools/whatsapp.py +286 -0
  467. agno/tools/wikipedia.py +28 -19
  468. agno/tools/workflow.py +285 -0
  469. agno/tools/{twitter.py → x.py} +142 -46
  470. agno/tools/yfinance.py +41 -39
  471. agno/tools/youtube.py +34 -17
  472. agno/tools/zendesk.py +15 -5
  473. agno/tools/zep.py +454 -0
  474. agno/tools/zoom.py +86 -37
  475. agno/tracing/__init__.py +12 -0
  476. agno/tracing/exporter.py +157 -0
  477. agno/tracing/schemas.py +276 -0
  478. agno/tracing/setup.py +111 -0
  479. agno/utils/agent.py +938 -0
  480. agno/utils/audio.py +37 -1
  481. agno/utils/certs.py +27 -0
  482. agno/utils/code_execution.py +11 -0
  483. agno/utils/common.py +103 -20
  484. agno/utils/cryptography.py +22 -0
  485. agno/utils/dttm.py +33 -0
  486. agno/utils/events.py +700 -0
  487. agno/utils/functions.py +107 -37
  488. agno/utils/gemini.py +426 -0
  489. agno/utils/hooks.py +171 -0
  490. agno/utils/http.py +185 -0
  491. agno/utils/json_schema.py +159 -37
  492. agno/utils/knowledge.py +36 -0
  493. agno/utils/location.py +19 -0
  494. agno/utils/log.py +221 -8
  495. agno/utils/mcp.py +214 -0
  496. agno/utils/media.py +335 -14
  497. agno/utils/merge_dict.py +22 -1
  498. agno/utils/message.py +77 -2
  499. agno/utils/models/ai_foundry.py +50 -0
  500. agno/utils/models/claude.py +373 -0
  501. agno/utils/models/cohere.py +94 -0
  502. agno/utils/models/llama.py +85 -0
  503. agno/utils/models/mistral.py +100 -0
  504. agno/utils/models/openai_responses.py +140 -0
  505. agno/utils/models/schema_utils.py +153 -0
  506. agno/utils/models/watsonx.py +41 -0
  507. agno/utils/openai.py +257 -0
  508. agno/utils/pickle.py +1 -1
  509. agno/utils/pprint.py +124 -8
  510. agno/utils/print_response/agent.py +930 -0
  511. agno/utils/print_response/team.py +1914 -0
  512. agno/utils/print_response/workflow.py +1668 -0
  513. agno/utils/prompts.py +111 -0
  514. agno/utils/reasoning.py +108 -0
  515. agno/utils/response.py +163 -0
  516. agno/utils/serialize.py +32 -0
  517. agno/utils/shell.py +4 -4
  518. agno/utils/streamlit.py +487 -0
  519. agno/utils/string.py +204 -51
  520. agno/utils/team.py +139 -0
  521. agno/utils/timer.py +9 -2
  522. agno/utils/tokens.py +657 -0
  523. agno/utils/tools.py +19 -1
  524. agno/utils/whatsapp.py +305 -0
  525. agno/utils/yaml_io.py +3 -3
  526. agno/vectordb/__init__.py +2 -0
  527. agno/vectordb/base.py +87 -9
  528. agno/vectordb/cassandra/__init__.py +5 -1
  529. agno/vectordb/cassandra/cassandra.py +383 -27
  530. agno/vectordb/chroma/__init__.py +4 -0
  531. agno/vectordb/chroma/chromadb.py +748 -83
  532. agno/vectordb/clickhouse/__init__.py +7 -1
  533. agno/vectordb/clickhouse/clickhousedb.py +554 -53
  534. agno/vectordb/couchbase/__init__.py +3 -0
  535. agno/vectordb/couchbase/couchbase.py +1446 -0
  536. agno/vectordb/lancedb/__init__.py +5 -0
  537. agno/vectordb/lancedb/lance_db.py +730 -98
  538. agno/vectordb/langchaindb/__init__.py +5 -0
  539. agno/vectordb/langchaindb/langchaindb.py +163 -0
  540. agno/vectordb/lightrag/__init__.py +5 -0
  541. agno/vectordb/lightrag/lightrag.py +388 -0
  542. agno/vectordb/llamaindex/__init__.py +3 -0
  543. agno/vectordb/llamaindex/llamaindexdb.py +166 -0
  544. agno/vectordb/milvus/__init__.py +3 -0
  545. agno/vectordb/milvus/milvus.py +966 -78
  546. agno/vectordb/mongodb/__init__.py +9 -1
  547. agno/vectordb/mongodb/mongodb.py +1175 -172
  548. agno/vectordb/pgvector/__init__.py +8 -0
  549. agno/vectordb/pgvector/pgvector.py +599 -115
  550. agno/vectordb/pineconedb/__init__.py +5 -1
  551. agno/vectordb/pineconedb/pineconedb.py +406 -43
  552. agno/vectordb/qdrant/__init__.py +4 -0
  553. agno/vectordb/qdrant/qdrant.py +914 -61
  554. agno/vectordb/redis/__init__.py +9 -0
  555. agno/vectordb/redis/redisdb.py +682 -0
  556. agno/vectordb/singlestore/__init__.py +8 -1
  557. agno/vectordb/singlestore/singlestore.py +771 -0
  558. agno/vectordb/surrealdb/__init__.py +3 -0
  559. agno/vectordb/surrealdb/surrealdb.py +663 -0
  560. agno/vectordb/upstashdb/__init__.py +5 -0
  561. agno/vectordb/upstashdb/upstashdb.py +718 -0
  562. agno/vectordb/weaviate/__init__.py +8 -0
  563. agno/vectordb/weaviate/index.py +15 -0
  564. agno/vectordb/weaviate/weaviate.py +1009 -0
  565. agno/workflow/__init__.py +23 -1
  566. agno/workflow/agent.py +299 -0
  567. agno/workflow/condition.py +759 -0
  568. agno/workflow/loop.py +756 -0
  569. agno/workflow/parallel.py +853 -0
  570. agno/workflow/router.py +723 -0
  571. agno/workflow/step.py +1564 -0
  572. agno/workflow/steps.py +613 -0
  573. agno/workflow/types.py +556 -0
  574. agno/workflow/workflow.py +4327 -514
  575. agno-2.3.13.dist-info/METADATA +639 -0
  576. agno-2.3.13.dist-info/RECORD +613 -0
  577. {agno-0.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +1 -1
  578. agno-2.3.13.dist-info/licenses/LICENSE +201 -0
  579. agno/api/playground.py +0 -91
  580. agno/api/schemas/playground.py +0 -22
  581. agno/api/schemas/user.py +0 -22
  582. agno/api/schemas/workspace.py +0 -46
  583. agno/api/user.py +0 -160
  584. agno/api/workspace.py +0 -151
  585. agno/cli/auth_server.py +0 -118
  586. agno/cli/config.py +0 -275
  587. agno/cli/console.py +0 -88
  588. agno/cli/credentials.py +0 -23
  589. agno/cli/entrypoint.py +0 -571
  590. agno/cli/operator.py +0 -355
  591. agno/cli/settings.py +0 -85
  592. agno/cli/ws/ws_cli.py +0 -817
  593. agno/constants.py +0 -13
  594. agno/document/__init__.py +0 -1
  595. agno/document/chunking/semantic.py +0 -47
  596. agno/document/chunking/strategy.py +0 -31
  597. agno/document/reader/__init__.py +0 -1
  598. agno/document/reader/arxiv_reader.py +0 -41
  599. agno/document/reader/base.py +0 -22
  600. agno/document/reader/csv_reader.py +0 -84
  601. agno/document/reader/docx_reader.py +0 -46
  602. agno/document/reader/firecrawl_reader.py +0 -99
  603. agno/document/reader/json_reader.py +0 -43
  604. agno/document/reader/pdf_reader.py +0 -219
  605. agno/document/reader/s3/pdf_reader.py +0 -46
  606. agno/document/reader/s3/text_reader.py +0 -51
  607. agno/document/reader/text_reader.py +0 -41
  608. agno/document/reader/website_reader.py +0 -175
  609. agno/document/reader/youtube_reader.py +0 -50
  610. agno/embedder/__init__.py +0 -1
  611. agno/embedder/azure_openai.py +0 -86
  612. agno/embedder/cohere.py +0 -72
  613. agno/embedder/fastembed.py +0 -37
  614. agno/embedder/google.py +0 -73
  615. agno/embedder/huggingface.py +0 -54
  616. agno/embedder/mistral.py +0 -80
  617. agno/embedder/ollama.py +0 -57
  618. agno/embedder/openai.py +0 -74
  619. agno/embedder/sentence_transformer.py +0 -38
  620. agno/embedder/voyageai.py +0 -64
  621. agno/eval/perf.py +0 -201
  622. agno/file/__init__.py +0 -1
  623. agno/file/file.py +0 -16
  624. agno/file/local/csv.py +0 -32
  625. agno/file/local/txt.py +0 -19
  626. agno/infra/app.py +0 -240
  627. agno/infra/base.py +0 -144
  628. agno/infra/context.py +0 -20
  629. agno/infra/db_app.py +0 -52
  630. agno/infra/resource.py +0 -205
  631. agno/infra/resources.py +0 -55
  632. agno/knowledge/agent.py +0 -230
  633. agno/knowledge/arxiv.py +0 -22
  634. agno/knowledge/combined.py +0 -22
  635. agno/knowledge/csv.py +0 -28
  636. agno/knowledge/csv_url.py +0 -19
  637. agno/knowledge/document.py +0 -20
  638. agno/knowledge/docx.py +0 -30
  639. agno/knowledge/json.py +0 -28
  640. agno/knowledge/langchain.py +0 -71
  641. agno/knowledge/llamaindex.py +0 -66
  642. agno/knowledge/pdf.py +0 -28
  643. agno/knowledge/pdf_url.py +0 -26
  644. agno/knowledge/s3/base.py +0 -60
  645. agno/knowledge/s3/pdf.py +0 -21
  646. agno/knowledge/s3/text.py +0 -23
  647. agno/knowledge/text.py +0 -30
  648. agno/knowledge/website.py +0 -88
  649. agno/knowledge/wikipedia.py +0 -31
  650. agno/knowledge/youtube.py +0 -22
  651. agno/memory/agent.py +0 -392
  652. agno/memory/classifier.py +0 -104
  653. agno/memory/db/__init__.py +0 -1
  654. agno/memory/db/base.py +0 -42
  655. agno/memory/db/mongodb.py +0 -189
  656. agno/memory/db/postgres.py +0 -203
  657. agno/memory/db/sqlite.py +0 -193
  658. agno/memory/memory.py +0 -15
  659. agno/memory/row.py +0 -36
  660. agno/memory/summarizer.py +0 -192
  661. agno/memory/summary.py +0 -19
  662. agno/memory/workflow.py +0 -38
  663. agno/models/google/gemini_openai.py +0 -26
  664. agno/models/ollama/hermes.py +0 -221
  665. agno/models/ollama/tools.py +0 -362
  666. agno/models/vertexai/gemini.py +0 -595
  667. agno/playground/__init__.py +0 -3
  668. agno/playground/async_router.py +0 -421
  669. agno/playground/deploy.py +0 -249
  670. agno/playground/operator.py +0 -92
  671. agno/playground/playground.py +0 -91
  672. agno/playground/schemas.py +0 -76
  673. agno/playground/serve.py +0 -55
  674. agno/playground/sync_router.py +0 -405
  675. agno/reasoning/agent.py +0 -68
  676. agno/run/response.py +0 -112
  677. agno/storage/agent/__init__.py +0 -0
  678. agno/storage/agent/base.py +0 -38
  679. agno/storage/agent/dynamodb.py +0 -350
  680. agno/storage/agent/json.py +0 -92
  681. agno/storage/agent/mongodb.py +0 -228
  682. agno/storage/agent/postgres.py +0 -367
  683. agno/storage/agent/session.py +0 -79
  684. agno/storage/agent/singlestore.py +0 -303
  685. agno/storage/agent/sqlite.py +0 -357
  686. agno/storage/agent/yaml.py +0 -93
  687. agno/storage/workflow/__init__.py +0 -0
  688. agno/storage/workflow/base.py +0 -40
  689. agno/storage/workflow/mongodb.py +0 -233
  690. agno/storage/workflow/postgres.py +0 -366
  691. agno/storage/workflow/session.py +0 -60
  692. agno/storage/workflow/sqlite.py +0 -359
  693. agno/tools/googlesearch.py +0 -88
  694. agno/utils/defaults.py +0 -57
  695. agno/utils/filesystem.py +0 -39
  696. agno/utils/git.py +0 -52
  697. agno/utils/json_io.py +0 -30
  698. agno/utils/load_env.py +0 -19
  699. agno/utils/py_io.py +0 -19
  700. agno/utils/pyproject.py +0 -18
  701. agno/utils/resource_filter.py +0 -31
  702. agno/vectordb/singlestore/s2vectordb.py +0 -390
  703. agno/vectordb/singlestore/s2vectordb2.py +0 -355
  704. agno/workspace/__init__.py +0 -0
  705. agno/workspace/config.py +0 -325
  706. agno/workspace/enums.py +0 -6
  707. agno/workspace/helpers.py +0 -48
  708. agno/workspace/operator.py +0 -758
  709. agno/workspace/settings.py +0 -63
  710. agno-0.1.2.dist-info/LICENSE +0 -375
  711. agno-0.1.2.dist-info/METADATA +0 -502
  712. agno-0.1.2.dist-info/RECORD +0 -352
  713. agno-0.1.2.dist-info/entry_points.txt +0 -3
  714. /agno/{cli → db/migrations}/__init__.py +0 -0
  715. /agno/{cli/ws → db/migrations/versions}/__init__.py +0 -0
  716. /agno/{document/chunking/__init__.py → db/schemas/metrics.py} +0 -0
  717. /agno/{document/reader/s3 → integrations}/__init__.py +0 -0
  718. /agno/{file/local → knowledge/chunking}/__init__.py +0 -0
  719. /agno/{infra → knowledge/remote_content}/__init__.py +0 -0
  720. /agno/{knowledge/s3 → tools/models}/__init__.py +0 -0
  721. /agno/{reranker → utils/models}/__init__.py +0 -0
  722. /agno/{storage → utils/print_response}/__init__.py +0 -0
  723. {agno-0.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
@@ -1,34 +1,27 @@
1
1
  import json
2
- from dataclasses import asdict, dataclass, field
3
- from typing import Any, Dict, Iterator, List, Mapping, Optional, Union
2
+ from dataclasses import dataclass, field
3
+ from os import getenv
4
+ from typing import Any, AsyncIterator, Dict, Iterator, List, Optional, Type, Union
4
5
 
5
6
  from pydantic import BaseModel
6
7
 
7
- from agno.models.base import Metrics, Model
8
+ from agno.agent import RunOutput
9
+ from agno.models.base import Model
8
10
  from agno.models.message import Message
9
- from agno.models.response import ModelResponse, ModelResponseEvent
10
- from agno.utils.log import logger
11
+ from agno.models.metrics import Metrics
12
+ from agno.models.response import ModelResponse
13
+ from agno.utils.log import log_debug, log_warning
14
+ from agno.utils.reasoning import extract_thinking_content
11
15
 
12
16
  try:
13
17
  from ollama import AsyncClient as AsyncOllamaClient
14
18
  from ollama import Client as OllamaClient
15
- except (ModuleNotFoundError, ImportError):
19
+ from ollama._types import ChatResponse
20
+ from ollama._types import Message as OllamaMessage
21
+ except ImportError:
16
22
  raise ImportError("`ollama` not installed. Please install using `pip install ollama`")
17
23
 
18
24
 
19
- @dataclass
20
- class MessageData:
21
- response_role: Optional[str] = None
22
- response_message: Optional[Dict[str, Any]] = None
23
- response_content: Any = ""
24
- response_content_chunk: str = ""
25
- tool_calls: List[Dict[str, Any]] = field(default_factory=list)
26
- tool_call_blocks: Any = field(default_factory=list)
27
- tool_call_chunk: str = ""
28
- in_tool_call: bool = False
29
- response_usage: Optional[Mapping[str, Any]] = None
30
-
31
-
32
25
  @dataclass
33
26
  class Ollama(Model):
34
27
  """
@@ -40,7 +33,8 @@ class Ollama(Model):
40
33
  id: str = "llama3.1"
41
34
  name: str = "Ollama"
42
35
  provider: str = "Ollama"
43
- supports_structured_outputs: bool = True
36
+
37
+ supports_native_structured_outputs: bool = True
44
38
 
45
39
  # Request parameters
46
40
  format: Optional[Any] = None
@@ -51,23 +45,35 @@ class Ollama(Model):
51
45
  # Client parameters
52
46
  host: Optional[str] = None
53
47
  timeout: Optional[Any] = None
48
+ api_key: Optional[str] = field(default_factory=lambda: getenv("OLLAMA_API_KEY"))
54
49
  client_params: Optional[Dict[str, Any]] = None
55
50
 
56
51
  # Ollama clients
57
52
  client: Optional[OllamaClient] = None
58
53
  async_client: Optional[AsyncOllamaClient] = None
59
54
 
60
- # Internal parameters. Not used for API requests
61
- # Whether to use the structured outputs with this Model.
62
- structured_outputs: bool = False
63
-
64
- def get_client_params(self) -> Dict[str, Any]:
65
- client_params: Dict[str, Any] = {}
66
- if self.host is not None:
67
- client_params["host"] = self.host
68
- if self.timeout is not None:
69
- client_params["timeout"] = self.timeout
70
- if self.client_params is not None:
55
+ def _get_client_params(self) -> Dict[str, Any]:
56
+ host = self.host
57
+ headers = {}
58
+
59
+ if self.api_key:
60
+ if not host:
61
+ host = "https://ollama.com"
62
+ headers["authorization"] = f"Bearer {self.api_key}"
63
+ log_debug(f"Using Ollama cloud endpoint: {host}")
64
+
65
+ base_params = {
66
+ "host": host,
67
+ "timeout": self.timeout,
68
+ }
69
+
70
+ if headers:
71
+ base_params["headers"] = headers
72
+
73
+ # Create client_params dict with non-None values
74
+ client_params = {k: v for k, v in base_params.items() if v is not None}
75
+ # Add additional client params if provided
76
+ if self.client_params:
71
77
  client_params.update(self.client_params)
72
78
  return client_params
73
79
 
@@ -81,7 +87,8 @@ class Ollama(Model):
81
87
  if self.client is not None:
82
88
  return self.client
83
89
 
84
- return OllamaClient(**self.get_client_params())
90
+ self.client = OllamaClient(**self._get_client_params())
91
+ return self.client
85
92
 
86
93
  def get_async_client(self) -> AsyncOllamaClient:
87
94
  """
@@ -93,32 +100,32 @@ class Ollama(Model):
93
100
  if self.async_client is not None:
94
101
  return self.async_client
95
102
 
96
- return AsyncOllamaClient(**self.get_client_params())
103
+ self.async_client = AsyncOllamaClient(**self._get_client_params())
104
+ return self.async_client
97
105
 
98
- @property
99
- def request_kwargs(self) -> Dict[str, Any]:
106
+ def get_request_params(
107
+ self,
108
+ tools: Optional[List[Dict[str, Any]]] = None,
109
+ ) -> Dict[str, Any]:
100
110
  """
101
111
  Returns keyword arguments for API requests.
102
112
 
103
113
  Returns:
104
114
  Dict[str, Any]: The API kwargs for the model.
105
115
  """
106
- request_params: Dict[str, Any] = {}
107
- if self.format is not None:
108
- request_params["format"] = self.format
109
- if self.options is not None:
110
- request_params["options"] = self.options
111
- if self.keep_alive is not None:
112
- request_params["keep_alive"] = self.keep_alive
113
- if self.tools is not None:
114
- request_params["tools"] = self.tools
115
- # Ensure types are valid strings
116
- for tool in request_params["tools"]:
117
- for prop, obj in tool["function"]["parameters"]["properties"].items():
118
- if isinstance(obj["type"], list):
119
- obj["type"] = obj["type"][0]
120
- if self.request_params is not None:
116
+ base_params = {"format": self.format, "options": self.options, "keep_alive": self.keep_alive}
117
+ # Filter out None values
118
+ request_params = {k: v for k, v in base_params.items() if v is not None}
119
+ # Add tools
120
+ if tools is not None and len(tools) > 0:
121
+ request_params["tools"] = tools
122
+
123
+ # Add additional request params if provided
124
+ if self.request_params:
121
125
  request_params.update(self.request_params)
126
+
127
+ if request_params:
128
+ log_debug(f"Calling {self.provider} with request parameters: {request_params}", log_level=2)
122
129
  return request_params
123
130
 
124
131
  def to_dict(self) -> Dict[str, Any]:
@@ -140,226 +147,232 @@ class Ollama(Model):
140
147
  cleaned_dict = {k: v for k, v in model_dict.items() if v is not None}
141
148
  return cleaned_dict
142
149
 
143
- def format_message(self, message: Message) -> Dict[str, Any]:
150
+ def _format_message(self, message: Message, compress_tool_results: bool = False) -> Dict[str, Any]:
144
151
  """
145
152
  Format a message into the format expected by Ollama.
146
153
 
147
154
  Args:
148
155
  message (Message): The message to format.
156
+ compress_tool_results: Whether to compress tool results.
149
157
 
150
158
  Returns:
151
159
  Dict[str, Any]: The formatted message.
152
160
  """
161
+ # Use compressed content for tool messages if compression is active
162
+ if message.role == "tool":
163
+ content = message.get_content(use_compressed_content=compress_tool_results)
164
+ else:
165
+ content = message.content
166
+
153
167
  _message: Dict[str, Any] = {
154
168
  "role": message.role,
155
- "content": message.content,
169
+ "content": content,
156
170
  }
171
+
172
+ if message.role == "assistant" and message.tool_calls is not None:
173
+ # Format tool calls for assistant messages
174
+ formatted_tool_calls = []
175
+ for tool_call in message.tool_calls:
176
+ if "function" in tool_call:
177
+ function_data = tool_call["function"]
178
+ formatted_tool_call = {
179
+ "id": tool_call.get("id"),
180
+ "type": "function",
181
+ "function": {
182
+ "name": function_data["name"],
183
+ "arguments": json.loads(function_data["arguments"])
184
+ if isinstance(function_data["arguments"], str)
185
+ else function_data["arguments"],
186
+ },
187
+ }
188
+ formatted_tool_calls.append(formatted_tool_call)
189
+
190
+ if formatted_tool_calls:
191
+ _message["tool_calls"] = formatted_tool_calls
192
+
157
193
  if message.role == "user":
158
194
  if message.images is not None:
159
195
  message_images = []
160
196
  for image in message.images:
161
197
  if image.url is not None:
162
- message_images.append(image.image_url_content)
198
+ message_images.append(image.get_content_bytes())
163
199
  if image.filepath is not None:
164
200
  message_images.append(image.filepath) # type: ignore
165
201
  if image.content is not None and isinstance(image.content, bytes):
166
202
  message_images.append(image.content)
167
203
  if message_images:
168
204
  _message["images"] = message_images
205
+
206
+ if message.audio is not None and len(message.audio) > 0:
207
+ log_warning("Audio input is currently unsupported.")
208
+
209
+ if message.files is not None and len(message.files) > 0:
210
+ log_warning("File input is currently unsupported.")
211
+
212
+ if message.videos is not None and len(message.videos) > 0:
213
+ log_warning("Video input is currently unsupported.")
214
+
169
215
  return _message
170
216
 
171
- def _prepare_request_kwargs_for_invoke(self) -> Dict[str, Any]:
172
- request_kwargs = self.request_kwargs
173
- if self.response_format is not None and self.structured_outputs:
174
- if isinstance(self.response_format, type) and issubclass(self.response_format, BaseModel):
175
- logger.debug("Using structured outputs")
176
- format_schema = self.response_format.model_json_schema()
177
- if "format" not in request_kwargs:
178
- request_kwargs["format"] = format_schema
217
+ def _prepare_request_kwargs_for_invoke(
218
+ self,
219
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
220
+ tools: Optional[List[Dict[str, Any]]] = None,
221
+ ) -> Dict[str, Any]:
222
+ request_kwargs = self.get_request_params(tools=tools)
223
+ if response_format is not None and isinstance(response_format, type) and issubclass(response_format, BaseModel):
224
+ log_debug("Using structured outputs")
225
+ format_schema = response_format.model_json_schema()
226
+ if "format" not in request_kwargs:
227
+ request_kwargs["format"] = format_schema
179
228
  return request_kwargs
180
229
 
181
- def invoke(self, messages: List[Message]) -> Mapping[str, Any]:
230
+ def invoke(
231
+ self,
232
+ messages: List[Message],
233
+ assistant_message: Message,
234
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
235
+ tools: Optional[List[Dict[str, Any]]] = None,
236
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
237
+ run_response: Optional[RunOutput] = None,
238
+ compress_tool_results: bool = False,
239
+ ) -> ModelResponse:
182
240
  """
183
241
  Send a chat request to the Ollama API.
242
+ """
243
+ request_kwargs = self._prepare_request_kwargs_for_invoke(response_format=response_format, tools=tools)
184
244
 
185
- Args:
186
- messages (List[Message]): A list of messages to send to the model.
245
+ if run_response and run_response.metrics:
246
+ run_response.metrics.set_time_to_first_token()
187
247
 
188
- Returns:
189
- Mapping[str, Any]: The response from the API.
190
- """
191
- request_kwargs = self._prepare_request_kwargs_for_invoke()
248
+ assistant_message.metrics.start_timer()
192
249
 
193
- return self.get_client().chat(
250
+ provider_response = self.get_client().chat(
194
251
  model=self.id.strip(),
195
- messages=[self.format_message(m) for m in messages], # type: ignore
252
+ messages=[self._format_message(m, compress_tool_results) for m in messages], # type: ignore
196
253
  **request_kwargs,
197
254
  ) # type: ignore
198
255
 
199
- async def ainvoke(self, messages: List[Message]) -> Mapping[str, Any]:
256
+ assistant_message.metrics.stop_timer()
257
+
258
+ model_response = self._parse_provider_response(provider_response) # type: ignore
259
+ return model_response
260
+
261
+ async def ainvoke(
262
+ self,
263
+ messages: List[Message],
264
+ assistant_message: Message,
265
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
266
+ tools: Optional[List[Dict[str, Any]]] = None,
267
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
268
+ run_response: Optional[RunOutput] = None,
269
+ compress_tool_results: bool = False,
270
+ ) -> ModelResponse:
200
271
  """
201
272
  Sends an asynchronous chat request to the Ollama API.
273
+ """
274
+ request_kwargs = self._prepare_request_kwargs_for_invoke(response_format=response_format, tools=tools)
202
275
 
203
- Args:
204
- messages (List[Message]): A list of messages to send to the model.
276
+ if run_response and run_response.metrics:
277
+ run_response.metrics.set_time_to_first_token()
205
278
 
206
- Returns:
207
- Mapping[str, Any]: The response from the API.
208
- """
209
- request_kwargs = self._prepare_request_kwargs_for_invoke()
279
+ assistant_message.metrics.start_timer()
210
280
 
211
- return await self.get_async_client().chat(
281
+ provider_response = await self.get_async_client().chat(
212
282
  model=self.id.strip(),
213
- messages=[self.format_message(m) for m in messages], # type: ignore
283
+ messages=[self._format_message(m, compress_tool_results) for m in messages], # type: ignore
214
284
  **request_kwargs,
215
285
  ) # type: ignore
216
286
 
217
- def invoke_stream(self, messages: List[Message]) -> Iterator[Mapping[str, Any]]:
218
- """
219
- Sends a streaming chat request to the Ollama API.
287
+ assistant_message.metrics.stop_timer()
220
288
 
221
- Args:
222
- messages (List[Message]): A list of messages to send to the model.
289
+ model_response = self._parse_provider_response(provider_response) # type: ignore
290
+ return model_response
223
291
 
224
- Returns:
225
- Iterator[Mapping[str, Any]]: An iterator of chunks from the API.
292
+ def invoke_stream(
293
+ self,
294
+ messages: List[Message],
295
+ assistant_message: Message,
296
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
297
+ tools: Optional[List[Dict[str, Any]]] = None,
298
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
299
+ run_response: Optional[RunOutput] = None,
300
+ compress_tool_results: bool = False,
301
+ ) -> Iterator[ModelResponse]:
226
302
  """
227
- yield from self.get_client().chat(
228
- model=self.id,
229
- messages=[self.format_message(m) for m in messages], # type: ignore
230
- stream=True,
231
- **self.request_kwargs,
232
- ) # type: ignore
233
-
234
- async def ainvoke_stream(self, messages: List[Message]) -> Any:
303
+ Sends a streaming chat request to the Ollama API.
235
304
  """
236
- Sends an asynchronous streaming chat completion request to the Ollama API.
305
+ if run_response and run_response.metrics:
306
+ run_response.metrics.set_time_to_first_token()
237
307
 
238
- Args:
239
- messages (List[Message]): A list of messages to send to the model.
308
+ assistant_message.metrics.start_timer()
240
309
 
241
- Returns:
242
- Any: An asynchronous iterator of chunks from the API.
243
- """
244
- async_stream = await self.get_async_client().chat(
245
- model=self.id.strip(),
246
- messages=[self.format_message(m) for m in messages], # type: ignore
310
+ for chunk in self.get_client().chat(
311
+ model=self.id,
312
+ messages=[self._format_message(m, compress_tool_results) for m in messages], # type: ignore
247
313
  stream=True,
248
- **self.request_kwargs,
249
- )
250
- async for chunk in async_stream: # type: ignore
251
- yield chunk
314
+ **self.get_request_params(tools=tools),
315
+ ):
316
+ yield self._parse_provider_response_delta(chunk)
252
317
 
253
- def handle_tool_calls(
318
+ assistant_message.metrics.stop_timer()
319
+
320
+ async def ainvoke_stream(
254
321
  self,
255
- assistant_message: Message,
256
322
  messages: List[Message],
257
- model_response: ModelResponse,
258
- ) -> Optional[ModelResponse]:
323
+ assistant_message: Message,
324
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
325
+ tools: Optional[List[Dict[str, Any]]] = None,
326
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
327
+ run_response: Optional[RunOutput] = None,
328
+ compress_tool_results: bool = False,
329
+ ) -> AsyncIterator[ModelResponse]:
259
330
  """
260
- Handle tool calls in the assistant message.
261
-
262
- Args:
263
- assistant_message (Message): The assistant message.
264
- messages (List[Message]): The list of messages.
265
- model_response (ModelResponse): The model response.
266
-
267
- Returns:
268
- Optional[ModelResponse]: The model response.
331
+ Sends an asynchronous streaming chat completion request to the Ollama API.
269
332
  """
270
- if assistant_message.tool_calls is not None and len(assistant_message.tool_calls) > 0:
271
- if model_response.tool_calls is None:
272
- model_response.tool_calls = []
333
+ if run_response and run_response.metrics:
334
+ run_response.metrics.set_time_to_first_token()
273
335
 
274
- model_response.content = assistant_message.get_content_string()
275
- model_response.content += "\n\n"
276
- function_calls_to_run = self._get_function_calls_to_run(assistant_message, messages)
277
- function_call_results: List[Message] = []
278
-
279
- if self.show_tool_calls:
280
- if len(function_calls_to_run) == 1:
281
- model_response.content += f" - Running: {function_calls_to_run[0].get_call_str()}\n\n"
282
- elif len(function_calls_to_run) > 1:
283
- model_response.content += "Running:"
284
- for _f in function_calls_to_run:
285
- model_response.content += f"\n - {_f.get_call_str()}"
286
- model_response.content += "\n\n"
287
-
288
- for function_call_response in self.run_function_calls(
289
- function_calls=function_calls_to_run,
290
- function_call_results=function_call_results,
291
- ):
292
- if (
293
- function_call_response.event == ModelResponseEvent.tool_call_completed.value
294
- and function_call_response.tool_calls is not None
295
- ):
296
- model_response.tool_calls.extend(function_call_response.tool_calls)
297
-
298
- self.format_function_call_results(function_call_results, messages)
299
-
300
- return model_response
301
- return None
302
-
303
- def update_usage_metrics(
304
- self,
305
- assistant_message: Message,
306
- metrics: Metrics,
307
- response: Optional[Mapping[str, Any]] = None,
308
- ) -> None:
309
- """
310
- Update usage metrics for the assistant message.
336
+ assistant_message.metrics.start_timer()
311
337
 
312
- Args:
313
- assistant_message (Message): The assistant message.
314
- metrics (Optional[Metrics]): The metrics for this response.
315
- response (Optional[Mapping[str, Any]]): The response from Ollama.
316
- """
317
- # Update time taken to generate response
318
- if response:
319
- metrics.input_tokens = response.get("prompt_eval_count", 0)
320
- metrics.output_tokens = response.get("eval_count", 0)
321
- metrics.total_tokens = metrics.input_tokens + metrics.output_tokens
338
+ async for chunk in await self.get_async_client().chat(
339
+ model=self.id.strip(),
340
+ messages=[self._format_message(m, compress_tool_results) for m in messages], # type: ignore
341
+ stream=True,
342
+ **self.get_request_params(tools=tools),
343
+ ):
344
+ yield self._parse_provider_response_delta(chunk)
322
345
 
323
- self._update_model_metrics(metrics_for_run=metrics)
324
- self._update_assistant_message_metrics(assistant_message=assistant_message, metrics_for_run=metrics)
346
+ assistant_message.metrics.stop_timer()
325
347
 
326
- def format_function_call_results(self, function_call_results: List[Message], messages: List[Message]) -> None:
348
+ def _parse_provider_response(self, response: dict) -> ModelResponse:
327
349
  """
328
- Format the function call results and append them to the messages.
329
-
330
- Args:
331
- function_call_results (List[Message]): The list of function call results.
332
- messages (List[Message]): The list of messages.
350
+ Parse the provider response.
333
351
  """
334
- if len(function_call_results) > 0:
335
- for _fcr in function_call_results:
336
- messages.append(_fcr)
352
+ model_response = ModelResponse()
353
+ # Get response message
354
+ response_message: OllamaMessage = response.get("message") # type: ignore
337
355
 
338
- def create_assistant_message(self, response: Mapping[str, Any], metrics: Metrics) -> Message:
339
- """
340
- Create an assistant message from the response.
356
+ if response_message.get("role") is not None:
357
+ model_response.role = response_message.get("role")
341
358
 
342
- Args:
343
- response: The response from Ollama.
344
- metrics: The metrics for this response.
359
+ if response_message.get("content") is not None:
360
+ model_response.content = response_message.get("content")
345
361
 
346
- Returns:
347
- Message: The assistant message.
348
- """
349
- message_data = MessageData()
362
+ # Extract thinking content between <think> tags if present
363
+ if model_response.content and model_response.content.find("<think>") != -1:
364
+ reasoning_content, clean_content = extract_thinking_content(model_response.content)
350
365
 
351
- message_data.response_message = response.get("message")
352
- if message_data.response_message:
353
- message_data.response_content = message_data.response_message.get("content")
354
- message_data.response_role = message_data.response_message.get("role")
355
- message_data.tool_call_blocks = message_data.response_message.get("tool_calls")
366
+ if reasoning_content:
367
+ # Store extracted thinking content separately
368
+ model_response.reasoning_content = reasoning_content
369
+ # Update main content with clean version
370
+ model_response.content = clean_content
356
371
 
357
- assistant_message = Message(
358
- role=message_data.response_role or "assistant",
359
- content=message_data.response_content,
360
- )
361
- if message_data.tool_call_blocks is not None:
362
- for block in message_data.tool_call_blocks:
372
+ if response_message.get("tool_calls") is not None:
373
+ if model_response.tool_calls is None:
374
+ model_response.tool_calls = []
375
+ for block in response_message.get("tool_calls", []):
363
376
  tool_call = block.get("function")
364
377
  tool_name = tool_call.get("name")
365
378
  tool_args = tool_call.get("arguments")
@@ -368,320 +381,72 @@ class Ollama(Model):
368
381
  "name": tool_name,
369
382
  "arguments": (json.dumps(tool_args) if tool_args is not None else None),
370
383
  }
371
- message_data.tool_calls.append({"type": "function", "function": function_def})
372
-
373
- if message_data.tool_calls is not None:
374
- assistant_message.tool_calls = message_data.tool_calls
375
-
376
- # TODO: Handle Audio
377
-
378
- # Update metrics
379
- self.update_usage_metrics(assistant_message=assistant_message, metrics=metrics, response=response)
380
- return assistant_message
381
-
382
- def _parse_structured_outputs(self, response: Mapping[str, Any], model_response: ModelResponse) -> None:
383
- try:
384
- if (
385
- self.response_format is not None
386
- and self.structured_outputs
387
- and issubclass(self.response_format, BaseModel)
388
- ):
389
- parsed_object = self.response_format.model_validate_json(response.get("message", {}).get("content", ""))
390
- if parsed_object is not None:
391
- model_response.parsed = parsed_object.model_dump_json()
392
- except Exception as e:
393
- logger.warning(f"Error parsing structured outputs: {e}")
394
-
395
- def response(self, messages: List[Message]) -> ModelResponse:
396
- """
397
- Generate a response from Ollama.
384
+ model_response.tool_calls.append({"type": "function", "function": function_def})
398
385
 
399
- Args:
400
- messages (List[Message]): A list of messages.
386
+ # if response_message.get("images") is not None:
387
+ # model_response.images = response_message.get("images")
401
388
 
402
- Returns:
403
- ModelResponse: The model response.
404
- """
405
- logger.debug("---------- Ollama Response Start ----------")
406
- self._log_messages(messages)
407
- model_response = ModelResponse()
408
- metrics = Metrics()
389
+ # Get response usage
390
+ if response.get("done"):
391
+ model_response.response_usage = self._get_metrics(response)
409
392
 
410
- # -*- Generate response
411
- metrics.start_response_timer()
412
- response: Mapping[str, Any] = self.invoke(messages=messages)
413
- metrics.stop_response_timer()
414
-
415
- # -*- Parse structured outputs
416
- self._parse_structured_outputs(response=response, model_response=model_response)
417
-
418
- # -*- Create assistant message
419
- assistant_message = self.create_assistant_message(response=response, metrics=metrics)
420
-
421
- # -*- Add assistant message to messages
422
- messages.append(assistant_message)
423
-
424
- # -*- Log response and metrics
425
- assistant_message.log()
426
- metrics.log()
427
-
428
- # -*- Update model response with assistant message content and audio
429
- if assistant_message.content is not None:
430
- # add the content to the model response
431
- model_response.content = assistant_message.get_content_string()
432
- # TODO: Handle audio
433
- # if assistant_message.audio is not None:
434
- # # add the audio to the model response
435
- # model_response.audio = assistant_message.audio
436
-
437
- # -*- Handle tool calls
438
- if (
439
- self.handle_tool_calls(
440
- assistant_message=assistant_message,
441
- messages=messages,
442
- model_response=model_response,
443
- )
444
- is not None
445
- ):
446
- return self.handle_post_tool_call_messages(messages=messages, model_response=model_response)
447
-
448
- logger.debug("---------- Ollama Response End ----------")
449
- return model_response
450
-
451
- async def aresponse(self, messages: List[Message]) -> ModelResponse:
452
- """
453
- Generate an asynchronous response from Ollama.
454
-
455
- Args:
456
- messages (List[Message]): A list of messages.
457
-
458
- Returns:
459
- ModelResponse: The model response.
460
- """
461
- logger.debug("---------- Ollama Async Response Start ----------")
462
- self._log_messages(messages)
463
- model_response = ModelResponse()
464
- metrics = Metrics()
465
-
466
- # -*- Generate response
467
- metrics.start_response_timer()
468
- response: Mapping[str, Any] = await self.ainvoke(messages=messages)
469
- metrics.stop_response_timer()
470
-
471
- # -*- Parse structured outputs
472
- self._parse_structured_outputs(response=response, model_response=model_response)
473
-
474
- # -*- Create assistant message
475
- assistant_message = self.create_assistant_message(response=response, metrics=metrics)
476
-
477
- # -*- Add assistant message to messages
478
- messages.append(assistant_message)
479
-
480
- # -*- Log response and metrics
481
- assistant_message.log()
482
- metrics.log()
483
-
484
- # -*- Update model response with assistant message content and audio
485
- if assistant_message.content is not None:
486
- # add the content to the model response
487
- model_response.content = assistant_message.get_content_string()
488
- # if assistant_message.audio is not None
489
- # # add the audio to the model response
490
- # model_response.audio = assistant_message.audio
491
-
492
- # -*- Handle tool calls
493
- if (
494
- self.handle_tool_calls(
495
- assistant_message=assistant_message,
496
- messages=messages,
497
- model_response=model_response,
498
- )
499
- is not None
500
- ):
501
- return await self.ahandle_post_tool_call_messages(messages=messages, model_response=model_response)
502
-
503
- logger.debug("---------- Ollama Async Response End ----------")
504
393
  return model_response
505
394
 
506
- def handle_stream_tool_calls(
507
- self,
508
- assistant_message: Message,
509
- messages: List[Message],
510
- ) -> Iterator[ModelResponse]:
395
+ def _parse_provider_response_delta(self, response: ChatResponse) -> ModelResponse:
511
396
  """
512
- Handle tool calls for response stream.
397
+ Parse the provider response delta.
513
398
 
514
399
  Args:
515
- assistant_message (Message): The assistant message.
516
- messages (List[Message]): The list of messages.
400
+ response (ChatResponse): The response from the provider.
517
401
 
518
402
  Returns:
519
403
  Iterator[ModelResponse]: An iterator of the model response.
520
404
  """
521
- if assistant_message.tool_calls is not None and len(assistant_message.tool_calls) > 0:
522
- yield ModelResponse(content="\n\n")
523
- function_calls_to_run = self._get_function_calls_to_run(assistant_message, messages)
524
- function_call_results: List[Message] = []
525
-
526
- if self.show_tool_calls:
527
- if len(function_calls_to_run) == 1:
528
- yield ModelResponse(content=f" - Running: {function_calls_to_run[0].get_call_str()}\n\n")
529
- elif len(function_calls_to_run) > 1:
530
- yield ModelResponse(content="Running:")
531
- for _f in function_calls_to_run:
532
- yield ModelResponse(content=f"\n - {_f.get_call_str()}")
533
- yield ModelResponse(content="\n\n")
534
-
535
- for intermediate_model_response in self.run_function_calls(
536
- function_calls=function_calls_to_run,
537
- function_call_results=function_call_results,
538
- ):
539
- yield intermediate_model_response
540
-
541
- self.format_function_call_results(function_call_results, messages)
542
-
543
- def response_stream(self, messages: List[Message]) -> Iterator[ModelResponse]:
544
- """
545
- Generate a streaming response from Ollama.
405
+ model_response = ModelResponse()
546
406
 
547
- Args:
548
- messages (List[Message]): A list of messages.
407
+ response_message = response.get("message")
549
408
 
550
- Returns:
551
- Iterator[ModelResponse]: An iterator of the model responses.
552
- """
553
- logger.debug("---------- Ollama Response Start ----------")
554
- self._log_messages(messages)
555
- message_data = MessageData()
556
- metrics: Metrics = Metrics()
557
-
558
- # -*- Generate response
559
- metrics.start_response_timer()
560
- for response in self.invoke_stream(messages=messages):
561
- message_data.response_message = response.get("message", {})
562
- if message_data.response_message:
563
- metrics.output_tokens += 1
564
- if metrics.output_tokens == 1:
565
- metrics.time_to_first_token = metrics.response_timer.elapsed
566
-
567
- message_data.response_content_chunk = message_data.response_message.get("content", "")
568
- if message_data.response_content_chunk is not None and message_data.response_content_chunk != "":
569
- message_data.response_content += message_data.response_content_chunk
570
- yield ModelResponse(content=message_data.response_content_chunk)
571
-
572
- message_data.tool_call_blocks = message_data.response_message.get("tool_calls") # type: ignore
573
- if message_data.tool_call_blocks is not None:
574
- for block in message_data.tool_call_blocks:
575
- tool_call = block.get("function")
576
- tool_name = tool_call.get("name")
577
- tool_args = tool_call.get("arguments")
578
- function_def = {
579
- "name": tool_name,
580
- "arguments": json.dumps(tool_args) if tool_args is not None else None,
581
- }
582
- message_data.tool_calls.append({"type": "function", "function": function_def})
583
-
584
- if response.get("done"):
585
- message_data.response_usage = response
586
- metrics.stop_response_timer()
587
-
588
- # -*- Create assistant message
589
- assistant_message = Message(role="assistant", content=message_data.response_content)
590
-
591
- if len(message_data.tool_calls) > 0:
592
- assistant_message.tool_calls = message_data.tool_calls
593
-
594
- # -*- Update usage metrics
595
- self.update_usage_metrics(
596
- assistant_message=assistant_message, metrics=metrics, response=message_data.response_usage
597
- )
409
+ if response_message is not None:
410
+ content_delta = response_message.get("content")
411
+ if content_delta is not None and content_delta != "":
412
+ model_response.content = content_delta
598
413
 
599
- # -*- Add assistant message to messages
600
- messages.append(assistant_message)
414
+ tool_calls = response_message.get("tool_calls")
415
+ if tool_calls is not None:
416
+ for tool_call in tool_calls:
417
+ tc = tool_call.get("function")
418
+ tool_name = tc.get("name")
419
+ tool_args = tc.get("arguments")
420
+ function_def = {
421
+ "name": tool_name,
422
+ "arguments": json.dumps(tool_args) if tool_args is not None else None,
423
+ }
424
+ model_response.tool_calls.append({"type": "function", "function": function_def})
601
425
 
602
- # -*- Log response and metrics
603
- assistant_message.log()
604
- metrics.log()
426
+ if response.get("done"):
427
+ model_response.response_usage = self._get_metrics(response)
605
428
 
606
- # -*- Handle tool calls
607
- if assistant_message.tool_calls is not None and len(assistant_message.tool_calls) > 0:
608
- yield from self.handle_stream_tool_calls(assistant_message, messages)
609
- yield from self.handle_post_tool_call_messages_stream(messages=messages)
610
- logger.debug("---------- Ollama Response End ----------")
429
+ return model_response
611
430
 
612
- async def aresponse_stream(self, messages: List[Message]) -> Any:
431
+ def _get_metrics(self, response: Union[dict, ChatResponse]) -> Metrics:
613
432
  """
614
- Generate an asynchronous streaming response from Ollama.
433
+ Parse the given Ollama usage into an Agno Metrics object.
615
434
 
616
435
  Args:
617
- messages (List[Message]): A list of messages.
436
+ response: The response from the provider.
618
437
 
619
438
  Returns:
620
- Any: An asynchronous iterator of the model responses.
439
+ Metrics: Parsed metrics data
621
440
  """
622
- logger.debug("---------- Ollama Async Response Start ----------")
623
- self._log_messages(messages)
624
- message_data = MessageData()
625
- metrics: Metrics = Metrics()
626
-
627
- # -*- Generate response
628
- metrics.start_response_timer()
629
- async for response in self.ainvoke_stream(messages=messages):
630
- message_data.response_message = response.get("message", {})
631
- if message_data.response_message:
632
- metrics.output_tokens += 1
633
- if metrics.output_tokens == 1:
634
- metrics.time_to_first_token = metrics.response_timer.elapsed
635
-
636
- message_data.response_content_chunk = message_data.response_message.get("content", "")
637
- if message_data.response_content_chunk is not None and message_data.response_content_chunk != "":
638
- message_data.response_content += message_data.response_content_chunk
639
- yield ModelResponse(content=message_data.response_content_chunk)
640
-
641
- message_data.tool_call_blocks = message_data.response_message.get("tool_calls")
642
- if message_data.tool_call_blocks is not None:
643
- for block in message_data.tool_call_blocks:
644
- tool_call = block.get("function")
645
- tool_name = tool_call.get("name")
646
- tool_args = tool_call.get("arguments")
647
- function_def = {
648
- "name": tool_name,
649
- "arguments": json.dumps(tool_args) if tool_args is not None else None,
650
- }
651
- message_data.tool_calls.append({"type": "function", "function": function_def})
652
-
653
- if response.get("done"):
654
- message_data.response_usage = response
655
- metrics.stop_response_timer()
656
-
657
- # -*- Create assistant message
658
- assistant_message = Message(role="assistant", content=message_data.response_content)
659
-
660
- if len(message_data.tool_calls) > 0:
661
- assistant_message.tool_calls = message_data.tool_calls
662
-
663
- # -*- Update usage metrics
664
- self.update_usage_metrics(
665
- assistant_message=assistant_message, metrics=metrics, response=message_data.response_usage
666
- )
667
-
668
- # -*- Add assistant message to messages
669
- messages.append(assistant_message)
670
-
671
- # -*- Log response and metrics
672
- assistant_message.log()
673
- metrics.log()
441
+ metrics = Metrics()
674
442
 
675
- # -*- Handle tool calls
676
- if assistant_message.tool_calls is not None and len(assistant_message.tool_calls) > 0:
677
- for tool_call_response in self.handle_stream_tool_calls(assistant_message, messages):
678
- yield tool_call_response
679
- async for post_tool_call_response in self.ahandle_post_tool_call_messages_stream(messages=messages):
680
- yield post_tool_call_response
681
- logger.debug("---------- Ollama Async Response End ----------")
443
+ # Safely handle None values from Ollama Cloud responses
444
+ input_tokens = response.get("prompt_eval_count")
445
+ output_tokens = response.get("eval_count")
682
446
 
683
- def model_copy(self, *, update: Optional[Mapping[str, Any]] = None, deep: bool = False) -> "Ollama":
684
- data = asdict(self)
685
- data.pop("client", None)
447
+ # Default to 0 if None
448
+ metrics.input_tokens = input_tokens if input_tokens is not None else 0
449
+ metrics.output_tokens = output_tokens if output_tokens is not None else 0
450
+ metrics.total_tokens = metrics.input_tokens + metrics.output_tokens
686
451
 
687
- return Ollama(client=self.client, **data)
452
+ return metrics