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,93 +1,66 @@
1
1
  import json
2
- from dataclasses import dataclass, field
2
+ from collections.abc import AsyncIterator
3
+ from dataclasses import asdict, dataclass
3
4
  from os import getenv
4
- from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
5
-
6
- from agno.media import Image
7
- from agno.models.base import Metrics, Model
8
- from agno.models.message import Message
9
- from agno.models.response import ModelResponse, ModelResponseEvent
10
- from agno.utils.log import logger
5
+ from typing import Any, Dict, List, Optional, Type, Union
6
+
7
+ import httpx
8
+ from pydantic import BaseModel, ValidationError
9
+
10
+ from agno.exceptions import ModelProviderError, ModelRateLimitError
11
+ from agno.models.base import Model
12
+ from agno.models.message import Citations, DocumentCitation, Message, UrlCitation
13
+ from agno.models.metrics import Metrics
14
+ from agno.models.response import ModelResponse
15
+ from agno.run.agent import RunOutput
16
+ from agno.tools.function import Function
17
+ from agno.utils.http import get_default_async_client, get_default_sync_client
18
+ from agno.utils.log import log_debug, log_error, log_warning
19
+ from agno.utils.models.claude import MCPServerConfiguration, format_messages, format_tools_for_model
20
+ from agno.utils.tokens import count_schema_tokens
11
21
 
12
22
  try:
13
23
  from anthropic import Anthropic as AnthropicClient
14
- from anthropic.lib.streaming._types import (
24
+ from anthropic import (
25
+ APIConnectionError,
26
+ APIStatusError,
27
+ RateLimitError,
28
+ )
29
+ from anthropic import (
30
+ AsyncAnthropic as AsyncAnthropicClient,
31
+ )
32
+ from anthropic.lib.streaming._beta_types import (
33
+ BetaRawContentBlockStartEvent,
34
+ ParsedBetaContentBlockStopEvent,
35
+ ParsedBetaMessageStopEvent,
36
+ )
37
+ from anthropic.types import (
38
+ CitationPageLocation,
39
+ CitationsWebSearchResultLocation,
40
+ ContentBlockDeltaEvent,
41
+ ContentBlockStartEvent,
15
42
  ContentBlockStopEvent,
43
+ MessageDeltaUsage,
44
+ # MessageDeltaEvent, # Currently broken
16
45
  MessageStopEvent,
17
- RawContentBlockDeltaEvent,
46
+ Usage,
47
+ )
48
+ from anthropic.types import (
49
+ Message as AnthropicMessage,
18
50
  )
19
- from anthropic.types import Message as AnthropicMessage
20
- from anthropic.types import TextBlock, TextDelta, ToolUseBlock, Usage
21
- except (ModuleNotFoundError, ImportError):
22
- raise ImportError("`anthropic` not installed. Please install using `pip install anthropic`")
23
-
24
-
25
- @dataclass
26
- class MessageData:
27
- response_content: str = ""
28
- response_block: List[Union[TextBlock, ToolUseBlock]] = field(default_factory=list)
29
- response_block_content: Optional[Union[TextBlock, ToolUseBlock]] = None
30
- response_usage: Optional[Usage] = None
31
- tool_calls: List[Dict[str, Any]] = field(default_factory=list)
32
- tool_ids: List[str] = field(default_factory=list)
33
-
34
-
35
- def format_image_for_message(image: Image) -> Optional[Dict[str, Any]]:
36
- """
37
- Add an image to a message by converting it to base64 encoded format.
38
- """
39
- import base64
40
- import imghdr
41
-
42
- type_mapping = {"jpeg": "image/jpeg", "png": "image/png", "gif": "image/gif", "webp": "image/webp"}
43
-
44
- try:
45
- # Case 1: Image is a URL
46
- if image.url is not None:
47
- content_bytes = image.image_url_content
48
-
49
- # Case 2: Image is a local file path
50
- elif image.filepath is not None:
51
- from pathlib import Path
52
-
53
- path = Path(image.filepath)
54
- if path.exists() and path.is_file():
55
- with open(image.filepath, "rb") as f:
56
- content_bytes = f.read()
57
- else:
58
- logger.error(f"Image file not found: {image}")
59
- return None
60
-
61
- # Case 3: Image is a bytes object
62
- elif image.content is not None:
63
- content_bytes = image.content
64
-
65
- else:
66
- logger.error(f"Unsupported image type: {type(image)}")
67
- return None
68
-
69
- img_type = imghdr.what(None, h=content_bytes) # type: ignore
70
- if not img_type:
71
- logger.error("Unable to determine image type")
72
- return None
73
51
 
74
- media_type = type_mapping.get(img_type)
75
- if not media_type:
76
- logger.error(f"Unsupported image type: {img_type}")
77
- return None
52
+ except ImportError as e:
53
+ raise ImportError("`anthropic` not installed. Please install it with `pip install anthropic`") from e
78
54
 
79
- return {
80
- "type": "image",
81
- "source": {
82
- "type": "base64",
83
- "media_type": media_type,
84
- "data": base64.b64encode(content_bytes).decode("utf-8"), # type: ignore
85
- },
86
- }
87
-
88
- except Exception as e:
89
- logger.error(f"Error processing image: {e}")
90
- return None
55
+ # Import Beta types
56
+ try:
57
+ from anthropic.types.beta import BetaRawContentBlockDeltaEvent, BetaTextDelta
58
+ from anthropic.types.beta.beta_message import BetaMessage
59
+ from anthropic.types.beta.beta_usage import BetaUsage
60
+ except ImportError as e:
61
+ raise ImportError(
62
+ "`anthropic` not installed or missing beta components. Please install with `pip install anthropic`"
63
+ ) from e
91
64
 
92
65
 
93
66
  @dataclass
@@ -98,52 +71,395 @@ class Claude(Model):
98
71
  For more information, see: https://docs.anthropic.com/en/api/messages
99
72
  """
100
73
 
101
- id: str = "claude-3-5-sonnet-20241022"
74
+ # Models that DO NOT support extended thinking
75
+ # All future models are assumed to support thinking
76
+ # Based on official Anthropic documentation: https://docs.claude.com/en/docs/about-claude/models/overview
77
+ NON_THINKING_MODELS = {
78
+ # Claude Haiku 3 family (does not support thinking)
79
+ "claude-3-haiku-20240307",
80
+ # Claude Haiku 3.5 family (does not support thinking)
81
+ "claude-3-5-haiku-20241022",
82
+ "claude-3-5-haiku-latest",
83
+ }
84
+
85
+ # Models that DO NOT support native structured outputs
86
+ # All future models are assumed to support structured outputs
87
+ NON_STRUCTURED_OUTPUT_MODELS = {
88
+ # Claude 3.x family (all versions)
89
+ "claude-3-opus-20240229",
90
+ "claude-3-sonnet-20240229",
91
+ "claude-3-haiku-20240307",
92
+ "claude-3-opus",
93
+ "claude-3-sonnet",
94
+ "claude-3-haiku",
95
+ # Claude 3.5 family (all versions except Sonnet 4.5)
96
+ "claude-3-5-sonnet-20240620",
97
+ "claude-3-5-sonnet-20241022",
98
+ "claude-3-5-sonnet",
99
+ "claude-3-5-haiku-20241022",
100
+ "claude-3-5-haiku-latest",
101
+ "claude-3-5-haiku",
102
+ # Claude Sonnet 4.x family (versions before 4.5)
103
+ "claude-sonnet-4-20250514",
104
+ "claude-sonnet-4",
105
+ # Claude Opus 4.x family (versions before 4.1)
106
+ # (Add any Opus 4.x models released before 4.1 if they exist)
107
+ }
108
+
109
+ id: str = "claude-sonnet-4-5-20250929"
102
110
  name: str = "Claude"
103
111
  provider: str = "Anthropic"
104
112
 
105
113
  # Request parameters
106
- max_tokens: Optional[int] = 1024
114
+ max_tokens: Optional[int] = 8192
115
+ thinking: Optional[Dict[str, Any]] = None
107
116
  temperature: Optional[float] = None
108
117
  stop_sequences: Optional[List[str]] = None
109
118
  top_p: Optional[float] = None
110
119
  top_k: Optional[int] = None
120
+ cache_system_prompt: Optional[bool] = False
121
+ extended_cache_time: Optional[bool] = False
111
122
  request_params: Optional[Dict[str, Any]] = None
112
123
 
124
+ # Anthropic beta and experimental features
125
+ betas: Optional[List[str]] = None # Enables specific experimental or newly released features.
126
+ context_management: Optional[Dict[str, Any]] = None
127
+ mcp_servers: Optional[List[MCPServerConfiguration]] = None
128
+ skills: Optional[List[Dict[str, str]]] = (
129
+ None # e.g., [{"type": "anthropic", "skill_id": "pptx", "version": "latest"}]
130
+ )
131
+
113
132
  # Client parameters
114
133
  api_key: Optional[str] = None
134
+ auth_token: Optional[str] = None
135
+ default_headers: Optional[Dict[str, Any]] = None
136
+ timeout: Optional[float] = None
137
+ http_client: Optional[Union[httpx.Client, httpx.AsyncClient]] = None
115
138
  client_params: Optional[Dict[str, Any]] = None
116
139
 
117
- # Anthropic client
118
140
  client: Optional[AnthropicClient] = None
141
+ async_client: Optional[AsyncAnthropicClient] = None
142
+
143
+ def __post_init__(self):
144
+ """Validate model configuration after initialization"""
145
+ # Validate thinking support immediately at model creation
146
+ if self.thinking:
147
+ self._validate_thinking_support()
148
+ # Set structured outputs capability flag for supported models
149
+ if self._supports_structured_outputs():
150
+ self.supports_native_structured_outputs = True
151
+ # Set up skills configuration if skills are enabled
152
+ if self.skills:
153
+ self._setup_skills_configuration()
154
+
155
+ def _get_client_params(self) -> Dict[str, Any]:
156
+ client_params: Dict[str, Any] = {}
157
+
158
+ self.api_key = self.api_key or getenv("ANTHROPIC_API_KEY")
159
+ self.auth_token = self.auth_token or getenv("ANTHROPIC_AUTH_TOKEN")
160
+ if not (self.api_key or self.auth_token):
161
+ log_error(
162
+ "ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN not set. Please set the ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN environment variable."
163
+ )
164
+
165
+ # Add API key to client parameters
166
+ client_params["api_key"] = self.api_key
167
+ client_params["auth_token"] = self.auth_token
168
+ if self.timeout is not None:
169
+ client_params["timeout"] = self.timeout
170
+
171
+ # Add additional client parameters
172
+ if self.client_params is not None:
173
+ client_params.update(self.client_params)
174
+ if self.default_headers is not None:
175
+ client_params["default_headers"] = self.default_headers
176
+ return client_params
177
+
178
+ def _supports_structured_outputs(self) -> bool:
179
+ """
180
+ Check if the current model supports native structured outputs.
181
+
182
+ Returns:
183
+ bool: True if model supports structured outputs
184
+ """
185
+ # If model is in blacklist, it doesn't support structured outputs
186
+ if self.id in self.NON_STRUCTURED_OUTPUT_MODELS:
187
+ return False
188
+
189
+ # Check for legacy model patterns which don't support structured outputs
190
+ if self.id.startswith("claude-3-"):
191
+ return False
192
+ if self.id.startswith("claude-sonnet-4-") and not self.id.startswith("claude-sonnet-4-5"):
193
+ return False
194
+ if self.id.startswith("claude-opus-4-") and not self.id.startswith("claude-opus-4-1"):
195
+ return False
196
+
197
+ return True
198
+
199
+ def _using_structured_outputs(
200
+ self,
201
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
202
+ tools: Optional[List[Dict[str, Any]]] = None,
203
+ ) -> bool:
204
+ """
205
+ Check if structured outputs are being used in this request.
206
+
207
+ Args:
208
+ response_format: Response format parameter
209
+ tools: Tools list to check for strict mode
210
+
211
+ Returns:
212
+ bool: True if structured outputs are in use
213
+ """
214
+ # Check for output_format usage
215
+ if response_format is not None:
216
+ if self._supports_structured_outputs():
217
+ return True
218
+ else:
219
+ log_warning(
220
+ f"Model '{self.id}' does not support structured outputs. "
221
+ "Structured output features will not be available for this model."
222
+ )
223
+
224
+ # Check for strict tools
225
+ if tools:
226
+ for tool in tools:
227
+ if tool.get("type") == "function":
228
+ func_def = tool.get("function", {})
229
+ if func_def.get("strict") is True:
230
+ return True
231
+
232
+ return False
233
+
234
+ def _validate_thinking_support(self) -> None:
235
+ """
236
+ Validate that the current model supports extended thinking.
237
+
238
+ Raises:
239
+ ValueError: If thinking is enabled but the model doesn't support it
240
+ """
241
+ if self.thinking and self.id in self.NON_THINKING_MODELS:
242
+ non_thinking_models = "\n - ".join(sorted(self.NON_THINKING_MODELS))
243
+ raise ValueError(
244
+ f"Model '{self.id}' does not support extended thinking.\n\n"
245
+ f"The following models do NOT support thinking:\n - {non_thinking_models}\n\n"
246
+ f"All other Claude models support extended thinking by default.\n"
247
+ f"For more information, see: https://docs.anthropic.com/en/docs/about-claude/models/overview"
248
+ )
249
+
250
+ def _setup_skills_configuration(self) -> None:
251
+ """
252
+ Set up configuration for Claude Agent Skills.
253
+ Automatically configures betas array with required values.
254
+
255
+ Skills enable document creation capabilities (PowerPoint, Excel, Word, PDF).
256
+ For more information, see: https://docs.claude.com/en/docs/agents-and-tools/agent-skills/quickstart
257
+ """
258
+ # Required betas for skills
259
+ required_betas = ["code-execution-2025-08-25", "skills-2025-10-02"]
260
+
261
+ # Initialize or merge betas
262
+ if self.betas is None:
263
+ self.betas = required_betas
264
+ else:
265
+ # Add required betas if not present
266
+ for beta in required_betas:
267
+ if beta not in self.betas:
268
+ self.betas.append(beta)
269
+
270
+ def _ensure_additional_properties_false(self, schema: Dict[str, Any]) -> None:
271
+ """
272
+ Recursively ensure all object types have additionalProperties: false.
273
+ """
274
+ if isinstance(schema, dict):
275
+ if schema.get("type") == "object":
276
+ schema["additionalProperties"] = False
277
+
278
+ # Recursively process nested schemas
279
+ for key, value in schema.items():
280
+ if key in ["properties", "items", "allOf", "anyOf", "oneOf"]:
281
+ if isinstance(value, dict):
282
+ self._ensure_additional_properties_false(value)
283
+ elif isinstance(value, list):
284
+ for item in value:
285
+ if isinstance(item, dict):
286
+ self._ensure_additional_properties_false(item)
287
+
288
+ def _build_output_format(self, response_format: Optional[Union[Dict, Type[BaseModel]]]) -> Optional[Dict[str, Any]]:
289
+ """
290
+ Build Anthropic output_format parameter from response_format.
291
+
292
+ Args:
293
+ response_format: Pydantic model or dict format
294
+
295
+ Returns:
296
+ Dict with output_format structure or None
297
+ """
298
+ if response_format is None:
299
+ return None
300
+
301
+ if not self._supports_structured_outputs():
302
+ return None
303
+
304
+ # Handle Pydantic BaseModel
305
+ if isinstance(response_format, type) and issubclass(response_format, BaseModel):
306
+ try:
307
+ # Try to use Anthropic SDK's transform_schema helper if available
308
+ from anthropic import transform_schema
309
+
310
+ schema = transform_schema(response_format.model_json_schema())
311
+ except (ImportError, AttributeError):
312
+ # Fallback to direct schema conversion
313
+ schema = response_format.model_json_schema()
314
+ # Ensure additionalProperties is False
315
+ if isinstance(schema, dict):
316
+ if "additionalProperties" not in schema:
317
+ schema["additionalProperties"] = False
318
+ # Recursively ensure all object types have additionalProperties: false
319
+ self._ensure_additional_properties_false(schema)
320
+
321
+ return {"type": "json_schema", "schema": schema}
322
+
323
+ # Handle dict format (already in correct structure)
324
+ elif isinstance(response_format, dict):
325
+ return response_format
326
+
327
+ return None
328
+
329
+ def _validate_structured_outputs_usage(
330
+ self,
331
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
332
+ tools: Optional[List[Dict[str, Any]]] = None,
333
+ ) -> None:
334
+ """
335
+ Validate that structured outputs are only used with supported models.
336
+
337
+ Raises:
338
+ ValueError: If structured outputs are used with unsupported model
339
+ """
340
+ if not self._using_structured_outputs(response_format, tools):
341
+ return
342
+
343
+ if not self._supports_structured_outputs():
344
+ raise ValueError(f"Model '{self.id}' does not support structured outputs.\n\n")
345
+
346
+ def _has_beta_features(
347
+ self,
348
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
349
+ tools: Optional[List[Dict[str, Any]]] = None,
350
+ ) -> bool:
351
+ """Check if the model has any Anthropic beta features enabled."""
352
+ return (
353
+ self.mcp_servers is not None
354
+ or self.context_management is not None
355
+ or self.skills is not None
356
+ or self.betas is not None
357
+ or self._using_structured_outputs(response_format, tools)
358
+ )
119
359
 
120
360
  def get_client(self) -> AnthropicClient:
121
361
  """
122
362
  Returns an instance of the Anthropic client.
123
363
  """
124
- if self.client:
364
+ if self.client and not self.client.is_closed():
125
365
  return self.client
126
366
 
127
- self.api_key = self.api_key or getenv("ANTHROPIC_API_KEY")
128
- if not self.api_key:
129
- logger.error("ANTHROPIC_API_KEY not set. Please set the ANTHROPIC_API_KEY environment variable.")
367
+ _client_params = self._get_client_params()
368
+ if self.http_client:
369
+ if isinstance(self.http_client, httpx.Client):
370
+ _client_params["http_client"] = self.http_client
371
+ else:
372
+ log_warning("http_client is not an instance of httpx.Client. Using default global httpx.Client.")
373
+ # Use global sync client when user http_client is invalid
374
+ _client_params["http_client"] = get_default_sync_client()
375
+ else:
376
+ # Use global sync client when no custom http_client is provided
377
+ _client_params["http_client"] = get_default_sync_client()
378
+ self.client = AnthropicClient(**_client_params)
379
+ return self.client
130
380
 
131
- _client_params: Dict[str, Any] = {}
132
- # Set client parameters if they are provided
133
- if self.api_key:
134
- _client_params["api_key"] = self.api_key
135
- if self.client_params:
136
- _client_params.update(self.client_params)
137
- return AnthropicClient(**_client_params)
381
+ def get_async_client(self) -> AsyncAnthropicClient:
382
+ """
383
+ Returns an instance of the async Anthropic client.
384
+ """
385
+ if self.async_client and not self.async_client.is_closed():
386
+ return self.async_client
138
387
 
139
- @property
140
- def request_kwargs(self) -> Dict[str, Any]:
388
+ _client_params = self._get_client_params()
389
+ if self.http_client:
390
+ if isinstance(self.http_client, httpx.AsyncClient):
391
+ _client_params["http_client"] = self.http_client
392
+ else:
393
+ log_warning(
394
+ "http_client is not an instance of httpx.AsyncClient. Using default global httpx.AsyncClient."
395
+ )
396
+ # Use global async client when user http_client is invalid
397
+ _client_params["http_client"] = get_default_async_client()
398
+ else:
399
+ # Use global async client when no custom http_client is provided
400
+ _client_params["http_client"] = get_default_async_client()
401
+ self.async_client = AsyncAnthropicClient(**_client_params)
402
+ return self.async_client
403
+
404
+ def count_tokens(
405
+ self,
406
+ messages: List[Message],
407
+ tools: Optional[List[Union[Function, Dict[str, Any]]]] = None,
408
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
409
+ ) -> int:
410
+ anthropic_messages, system_prompt = format_messages(messages, compress_tool_results=True)
411
+ anthropic_tools = None
412
+ if tools:
413
+ formatted_tools = self._format_tools(tools)
414
+ anthropic_tools = format_tools_for_model(formatted_tools)
415
+
416
+ kwargs: Dict[str, Any] = {"messages": anthropic_messages, "model": self.id}
417
+ if system_prompt:
418
+ kwargs["system"] = system_prompt
419
+ if anthropic_tools:
420
+ kwargs["tools"] = anthropic_tools
421
+
422
+ response = self.get_client().messages.count_tokens(**kwargs)
423
+ return response.input_tokens + count_schema_tokens(response_format, self.id)
424
+
425
+ async def acount_tokens(
426
+ self,
427
+ messages: List[Message],
428
+ tools: Optional[List[Union[Function, Dict[str, Any]]]] = None,
429
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
430
+ ) -> int:
431
+ anthropic_messages, system_prompt = format_messages(messages, compress_tool_results=True)
432
+ anthropic_tools = None
433
+ if tools:
434
+ formatted_tools = self._format_tools(tools)
435
+ anthropic_tools = format_tools_for_model(formatted_tools)
436
+
437
+ kwargs: Dict[str, Any] = {"messages": anthropic_messages, "model": self.id}
438
+ if system_prompt:
439
+ kwargs["system"] = system_prompt
440
+ if anthropic_tools:
441
+ kwargs["tools"] = anthropic_tools
442
+
443
+ response = await self.get_async_client().messages.count_tokens(**kwargs)
444
+ return response.input_tokens + count_schema_tokens(response_format, self.id)
445
+
446
+ def get_request_params(
447
+ self,
448
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
449
+ tools: Optional[List[Dict[str, Any]]] = None,
450
+ ) -> Dict[str, Any]:
141
451
  """
142
452
  Generate keyword arguments for API requests.
143
453
  """
454
+ # Validate thinking support if thinking is enabled
455
+ if self.thinking:
456
+ self._validate_thinking_support()
457
+
144
458
  _request_params: Dict[str, Any] = {}
145
459
  if self.max_tokens:
146
460
  _request_params["max_tokens"] = self.max_tokens
461
+ if self.thinking:
462
+ _request_params["thinking"] = self.thinking
147
463
  if self.temperature:
148
464
  _request_params["temperature"] = self.temperature
149
465
  if self.stop_sequences:
@@ -152,137 +468,155 @@ class Claude(Model):
152
468
  _request_params["top_p"] = self.top_p
153
469
  if self.top_k:
154
470
  _request_params["top_k"] = self.top_k
471
+
472
+ # Build betas list - include existing betas and add new one if needed
473
+ betas_list = list(self.betas) if self.betas else []
474
+
475
+ # Add structured outputs beta header if using structured outputs
476
+ if self._using_structured_outputs(response_format, tools):
477
+ beta_header = "structured-outputs-2025-11-13"
478
+ if beta_header not in betas_list:
479
+ betas_list.append(beta_header)
480
+
481
+ # Include betas if any are present
482
+ if betas_list:
483
+ _request_params["betas"] = betas_list
484
+
485
+ if self.context_management:
486
+ _request_params["context_management"] = self.context_management
487
+ if self.mcp_servers:
488
+ _request_params["mcp_servers"] = [
489
+ {k: v for k, v in asdict(server).items() if v is not None} for server in self.mcp_servers
490
+ ]
491
+ if self.skills:
492
+ _request_params["container"] = {"skills": self.skills}
155
493
  if self.request_params:
156
494
  _request_params.update(self.request_params)
157
- return _request_params
158
-
159
- def format_messages(self, messages: List[Message]) -> Tuple[List[Dict[str, str]], str]:
160
- """
161
- Process the list of messages and separate them into API messages and system messages.
162
-
163
- Args:
164
- messages (List[Message]): The list of messages to process.
165
-
166
- Returns:
167
- Tuple[List[Dict[str, str]], str]: A tuple containing the list of API messages and the concatenated system messages.
168
- """
169
- chat_messages: List[Dict[str, str]] = []
170
- system_messages: List[str] = []
171
-
172
- for idx, message in enumerate(messages):
173
- content = message.content or ""
174
- if message.role == "system" or (message.role != "user" and idx in [0, 1]):
175
- system_messages.append(content) # type: ignore
176
- continue
177
- elif message.role == "user":
178
- if isinstance(content, str):
179
- content = [{"type": "text", "text": content}]
180
-
181
- if message.images is not None:
182
- for image in message.images:
183
- image_content = format_image_for_message(image)
184
- if image_content:
185
- content.append(image_content)
186
-
187
- # Handle tool calls from history
188
- elif message.role == "assistant" and isinstance(message.content, str) and message.tool_calls:
189
- if message.content:
190
- content = [TextBlock(text=message.content, type="text")]
191
- else:
192
- content = []
193
- for tool_call in message.tool_calls:
194
- content.append(
195
- ToolUseBlock(
196
- id=tool_call["id"],
197
- input=json.loads(tool_call["function"]["arguments"]),
198
- name=tool_call["function"]["name"],
199
- type="tool_use",
200
- )
201
- )
202
495
 
203
- chat_messages.append({"role": message.role, "content": content}) # type: ignore
204
- return chat_messages, " ".join(system_messages)
496
+ return _request_params
205
497
 
206
- def prepare_request_kwargs(self, system_message: str) -> Dict[str, Any]:
498
+ def _prepare_request_kwargs(
499
+ self,
500
+ system_message: str,
501
+ tools: Optional[List[Dict[str, Any]]] = None,
502
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
503
+ ) -> Dict[str, Any]:
207
504
  """
208
505
  Prepare the request keyword arguments for the API call.
209
506
 
210
507
  Args:
211
508
  system_message (str): The concatenated system messages.
509
+ tools: Optional list of tools
510
+ response_format: Optional response format (Pydantic model or dict)
212
511
 
213
512
  Returns:
214
513
  Dict[str, Any]: The request keyword arguments.
215
514
  """
216
- request_kwargs = self.request_kwargs.copy()
217
- request_kwargs["system"] = system_message
218
-
219
- if self.tools:
220
- request_kwargs["tools"] = self.format_tools_for_model()
221
- return request_kwargs
222
-
223
- def format_tools_for_model(self) -> Optional[List[Dict[str, Any]]]:
224
- """
225
- Transforms function definitions into a format accepted by the Anthropic API.
226
-
227
- Returns:
228
- Optional[List[Dict[str, Any]]]: A list of tools formatted for the API, or None if no functions are defined.
229
- """
230
- if not self._functions:
231
- return None
515
+ # Validate structured outputs usage
516
+ self._validate_structured_outputs_usage(response_format, tools)
517
+
518
+ # Pass response_format and tools to get_request_params for beta header handling
519
+ request_kwargs = self.get_request_params(response_format=response_format, tools=tools).copy()
520
+ if system_message:
521
+ if self.cache_system_prompt:
522
+ cache_control = (
523
+ {"type": "ephemeral", "ttl": "1h"}
524
+ if self.extended_cache_time is not None and self.extended_cache_time is True
525
+ else {"type": "ephemeral"}
526
+ )
527
+ request_kwargs["system"] = [{"text": system_message, "type": "text", "cache_control": cache_control}]
528
+ else:
529
+ request_kwargs["system"] = [{"text": system_message, "type": "text"}]
530
+
531
+ # Add code execution tool if skills are enabled
532
+ if self.skills:
533
+ code_execution_tool = {"type": "code_execution_20250825", "name": "code_execution"}
534
+ if tools:
535
+ # Add code_execution to existing tools, code execution is needed for generating and processing files
536
+ tools = tools + [code_execution_tool]
537
+ else:
538
+ tools = [code_execution_tool]
232
539
 
233
- tools: List[Dict[str, Any]] = []
234
- for func_name, func_def in self._functions.items():
235
- parameters: Dict[str, Any] = func_def.parameters or {}
236
- properties: Dict[str, Any] = parameters.get("properties", {})
237
- required_params: List[str] = []
540
+ # Format tools (this will handle strict mode)
541
+ if tools:
542
+ request_kwargs["tools"] = format_tools_for_model(tools)
238
543
 
239
- for param_name, param_info in properties.items():
240
- param_type = param_info.get("type", "")
241
- param_type_list: List[str] = [param_type] if isinstance(param_type, str) else param_type or []
544
+ # Build output_format if response_format is provided
545
+ output_format = self._build_output_format(response_format)
546
+ if output_format:
547
+ request_kwargs["output_format"] = output_format
242
548
 
243
- if "null" not in param_type_list:
244
- required_params.append(param_name)
549
+ if request_kwargs:
550
+ log_debug(f"Calling {self.provider} with request parameters: {request_kwargs}", log_level=2)
551
+ return request_kwargs
245
552
 
246
- input_properties: Dict[str, Dict[str, Union[str, List[str]]]] = {
247
- param_name: {
248
- "type": param_info.get("type", ""),
249
- "description": param_info.get("description", ""),
250
- }
251
- for param_name, param_info in properties.items()
252
- }
253
-
254
- tool = {
255
- "name": func_name,
256
- "description": func_def.description or "",
257
- "input_schema": {
258
- "type": parameters.get("type", "object"),
259
- "properties": input_properties,
260
- "required": required_params,
261
- },
262
- }
263
- tools.append(tool)
264
- return tools
265
-
266
- def invoke(self, messages: List[Message]) -> AnthropicMessage:
553
+ def invoke(
554
+ self,
555
+ messages: List[Message],
556
+ assistant_message: Message,
557
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
558
+ tools: Optional[List[Dict[str, Any]]] = None,
559
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
560
+ run_response: Optional[RunOutput] = None,
561
+ compress_tool_results: bool = False,
562
+ ) -> ModelResponse:
267
563
  """
268
564
  Send a request to the Anthropic API to generate a response.
565
+ """
566
+ try:
567
+ if run_response and run_response.metrics:
568
+ run_response.metrics.set_time_to_first_token()
569
+
570
+ chat_messages, system_message = format_messages(messages, compress_tool_results=compress_tool_results)
571
+ request_kwargs = self._prepare_request_kwargs(system_message, tools=tools, response_format=response_format)
572
+
573
+ if self._has_beta_features(response_format=response_format, tools=tools):
574
+ assistant_message.metrics.start_timer()
575
+ provider_response = self.get_client().beta.messages.create(
576
+ model=self.id,
577
+ messages=chat_messages, # type: ignore
578
+ **request_kwargs,
579
+ )
580
+ else:
581
+ assistant_message.metrics.start_timer()
582
+ provider_response = self.get_client().messages.create(
583
+ model=self.id,
584
+ messages=chat_messages, # type: ignore
585
+ **request_kwargs,
586
+ )
269
587
 
270
- Args:
271
- messages (List[Message]): A list of messages to send to the model.
588
+ assistant_message.metrics.stop_timer()
272
589
 
273
- Returns:
274
- AnthropicMessage: The response from the model.
275
- """
276
- chat_messages, system_message = self.format_messages(messages)
277
- request_kwargs = self.prepare_request_kwargs(system_message)
590
+ # Parse the response into an Agno ModelResponse object
591
+ model_response = self._parse_provider_response(provider_response, response_format=response_format) # type: ignore
278
592
 
279
- return self.get_client().messages.create(
280
- model=self.id,
281
- messages=chat_messages, # type: ignore
282
- **request_kwargs,
283
- )
593
+ return model_response
284
594
 
285
- def invoke_stream(self, messages: List[Message]) -> Any:
595
+ except APIConnectionError as e:
596
+ log_error(f"Connection error while calling Claude API: {str(e)}")
597
+ raise ModelProviderError(message=e.message, model_name=self.name, model_id=self.id) from e
598
+ except RateLimitError as e:
599
+ log_warning(f"Rate limit exceeded: {str(e)}")
600
+ raise ModelRateLimitError(message=e.message, model_name=self.name, model_id=self.id) from e
601
+ except APIStatusError as e:
602
+ log_error(f"Claude API error (status {e.status_code}): {str(e)}")
603
+ raise ModelProviderError(
604
+ message=e.message, status_code=e.status_code, model_name=self.name, model_id=self.id
605
+ ) from e
606
+ except Exception as e:
607
+ log_error(f"Unexpected error calling Claude API: {str(e)}")
608
+ raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
609
+
610
+ def invoke_stream(
611
+ self,
612
+ messages: List[Message],
613
+ assistant_message: Message,
614
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
615
+ tools: Optional[List[Dict[str, Any]]] = None,
616
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
617
+ run_response: Optional[RunOutput] = None,
618
+ compress_tool_results: bool = False,
619
+ ) -> Any:
286
620
  """
287
621
  Stream a response from the Anthropic API.
288
622
 
@@ -291,349 +625,479 @@ class Claude(Model):
291
625
 
292
626
  Returns:
293
627
  Any: The streamed response from the model.
628
+
629
+ Raises:
630
+ APIConnectionError: If there are network connectivity issues
631
+ RateLimitError: If the API rate limit is exceeded
632
+ APIStatusError: For other API-related errors
294
633
  """
295
- chat_messages, system_message = self.format_messages(messages)
296
- request_kwargs = self.prepare_request_kwargs(system_message)
634
+ chat_messages, system_message = format_messages(messages, compress_tool_results=compress_tool_results)
635
+ request_kwargs = self._prepare_request_kwargs(system_message, tools=tools, response_format=response_format)
636
+
637
+ try:
638
+ if run_response and run_response.metrics:
639
+ run_response.metrics.set_time_to_first_token()
640
+
641
+ # Beta features
642
+ if self._has_beta_features(response_format=response_format, tools=tools):
643
+ assistant_message.metrics.start_timer()
644
+ with self.get_client().beta.messages.stream(
645
+ model=self.id,
646
+ messages=chat_messages, # type: ignore
647
+ **request_kwargs,
648
+ ) as stream:
649
+ for chunk in stream:
650
+ yield self._parse_provider_response_delta(chunk, response_format=response_format) # type: ignore
651
+ else:
652
+ assistant_message.metrics.start_timer()
653
+ with self.get_client().messages.stream(
654
+ model=self.id,
655
+ messages=chat_messages, # type: ignore
656
+ **request_kwargs,
657
+ ) as stream:
658
+ for chunk in stream: # type: ignore
659
+ yield self._parse_provider_response_delta(chunk, response_format=response_format) # type: ignore
660
+
661
+ assistant_message.metrics.stop_timer()
662
+
663
+ except APIConnectionError as e:
664
+ log_error(f"Connection error while calling Claude API: {str(e)}")
665
+ raise ModelProviderError(message=e.message, model_name=self.name, model_id=self.id) from e
666
+ except RateLimitError as e:
667
+ log_warning(f"Rate limit exceeded: {str(e)}")
668
+ raise ModelRateLimitError(message=e.message, model_name=self.name, model_id=self.id) from e
669
+ except APIStatusError as e:
670
+ log_error(f"Claude API error (status {e.status_code}): {str(e)}")
671
+ raise ModelProviderError(
672
+ message=e.message, status_code=e.status_code, model_name=self.name, model_id=self.id
673
+ ) from e
674
+ except Exception as e:
675
+ log_error(f"Unexpected error calling Claude API: {str(e)}")
676
+ raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
677
+
678
+ async def ainvoke(
679
+ self,
680
+ messages: List[Message],
681
+ assistant_message: Message,
682
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
683
+ tools: Optional[List[Dict[str, Any]]] = None,
684
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
685
+ run_response: Optional[RunOutput] = None,
686
+ compress_tool_results: bool = False,
687
+ ) -> ModelResponse:
688
+ """
689
+ Send an asynchronous request to the Anthropic API to generate a response.
690
+ """
691
+ try:
692
+ if run_response and run_response.metrics:
693
+ run_response.metrics.set_time_to_first_token()
694
+
695
+ chat_messages, system_message = format_messages(messages, compress_tool_results=compress_tool_results)
696
+ request_kwargs = self._prepare_request_kwargs(system_message, tools=tools, response_format=response_format)
697
+
698
+ # Beta features
699
+ if self._has_beta_features(response_format=response_format, tools=tools):
700
+ assistant_message.metrics.start_timer()
701
+ provider_response = await self.get_async_client().beta.messages.create(
702
+ model=self.id,
703
+ messages=chat_messages, # type: ignore
704
+ **request_kwargs,
705
+ )
706
+ else:
707
+ assistant_message.metrics.start_timer()
708
+ provider_response = await self.get_async_client().messages.create(
709
+ model=self.id,
710
+ messages=chat_messages, # type: ignore
711
+ **request_kwargs,
712
+ )
297
713
 
298
- return self.get_client().messages.stream(
299
- model=self.id,
300
- messages=chat_messages, # type: ignore
301
- **request_kwargs,
302
- )
714
+ assistant_message.metrics.stop_timer()
303
715
 
304
- def update_usage_metrics(
716
+ # Parse the response into an Agno ModelResponse object
717
+ model_response = self._parse_provider_response(provider_response, response_format=response_format) # type: ignore
718
+
719
+ return model_response
720
+
721
+ except APIConnectionError as e:
722
+ log_error(f"Connection error while calling Claude API: {str(e)}")
723
+ raise ModelProviderError(message=e.message, model_name=self.name, model_id=self.id) from e
724
+ except RateLimitError as e:
725
+ log_warning(f"Rate limit exceeded: {str(e)}")
726
+ raise ModelRateLimitError(message=e.message, model_name=self.name, model_id=self.id) from e
727
+ except APIStatusError as e:
728
+ log_error(f"Claude API error (status {e.status_code}): {str(e)}")
729
+ raise ModelProviderError(
730
+ message=e.message, status_code=e.status_code, model_name=self.name, model_id=self.id
731
+ ) from e
732
+ except Exception as e:
733
+ log_error(f"Unexpected error calling Claude API: {str(e)}")
734
+ raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
735
+
736
+ async def ainvoke_stream(
305
737
  self,
738
+ messages: List[Message],
306
739
  assistant_message: Message,
307
- usage: Optional[Usage] = None,
308
- metrics: Metrics = Metrics(),
309
- ) -> None:
740
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
741
+ tools: Optional[List[Dict[str, Any]]] = None,
742
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
743
+ run_response: Optional[RunOutput] = None,
744
+ compress_tool_results: bool = False,
745
+ ) -> AsyncIterator[ModelResponse]:
310
746
  """
311
- Update the usage metrics for the assistant message.
312
-
747
+ Stream an asynchronous response from the Anthropic API.
313
748
  Args:
314
- assistant_message (Message): The assistant message.
315
- usage (Optional[Usage]): The usage metrics returned by the model.
316
- metrics (Metrics): The metrics to update.
749
+ messages (List[Message]): A list of messages to send to the model.
750
+ Returns:
751
+ AsyncIterator[ModelResponse]: An async iterator of processed model responses.
752
+ Raises:
753
+ APIConnectionError: If there are network connectivity issues
754
+ RateLimitError: If the API rate limit is exceeded
755
+ APIStatusError: For other API-related errors
317
756
  """
318
- if usage:
319
- metrics.input_tokens = usage.input_tokens or 0
320
- metrics.output_tokens = usage.output_tokens or 0
321
- metrics.total_tokens = metrics.input_tokens + metrics.output_tokens
322
-
323
- self._update_model_metrics(metrics_for_run=metrics)
324
- self._update_assistant_message_metrics(assistant_message=assistant_message, metrics_for_run=metrics)
757
+ try:
758
+ if run_response and run_response.metrics:
759
+ run_response.metrics.set_time_to_first_token()
760
+
761
+ chat_messages, system_message = format_messages(messages, compress_tool_results=compress_tool_results)
762
+ request_kwargs = self._prepare_request_kwargs(system_message, tools=tools, response_format=response_format)
763
+
764
+ if self._has_beta_features(response_format=response_format, tools=tools):
765
+ assistant_message.metrics.start_timer()
766
+ async with self.get_async_client().beta.messages.stream(
767
+ model=self.id,
768
+ messages=chat_messages, # type: ignore
769
+ **request_kwargs,
770
+ ) as stream:
771
+ async for chunk in stream:
772
+ yield self._parse_provider_response_delta(chunk, response_format=response_format) # type: ignore
773
+ else:
774
+ assistant_message.metrics.start_timer()
775
+ async with self.get_async_client().messages.stream(
776
+ model=self.id,
777
+ messages=chat_messages, # type: ignore
778
+ **request_kwargs,
779
+ ) as stream:
780
+ async for chunk in stream: # type: ignore
781
+ yield self._parse_provider_response_delta(chunk, response_format=response_format) # type: ignore
782
+
783
+ assistant_message.metrics.stop_timer()
784
+
785
+ except APIConnectionError as e:
786
+ log_error(f"Connection error while calling Claude API: {str(e)}")
787
+ raise ModelProviderError(message=e.message, model_name=self.name, model_id=self.id) from e
788
+ except RateLimitError as e:
789
+ log_warning(f"Rate limit exceeded: {str(e)}")
790
+ raise ModelRateLimitError(message=e.message, model_name=self.name, model_id=self.id) from e
791
+ except APIStatusError as e:
792
+ log_error(f"Claude API error (status {e.status_code}): {str(e)}")
793
+ raise ModelProviderError(
794
+ message=e.message, status_code=e.status_code, model_name=self.name, model_id=self.id
795
+ ) from e
796
+ except Exception as e:
797
+ log_error(f"Unexpected error calling Claude API: {str(e)}")
798
+ raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e
799
+
800
+ def get_system_message_for_model(self, tools: Optional[List[Any]] = None) -> Optional[str]:
801
+ if tools is not None and len(tools) > 0:
802
+ tool_call_prompt = "Do not reflect on the quality of the returned search results in your response\n\n"
803
+ return tool_call_prompt
804
+ return None
325
805
 
326
- def create_assistant_message(self, response: AnthropicMessage, metrics: Metrics) -> Tuple[Message, str, List[str]]:
806
+ def _parse_provider_response(
807
+ self,
808
+ response: Union[AnthropicMessage, BetaMessage],
809
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
810
+ **kwargs,
811
+ ) -> ModelResponse:
327
812
  """
328
- Create an assistant message from the response.
813
+ Parse the Claude response into a ModelResponse.
329
814
 
330
815
  Args:
331
- response (AnthropicMessage): The response from the model.
332
- metrics (Metrics): The metrics for the response.
816
+ response: Raw response from Anthropic
817
+ response_format: Optional response format for structured output parsing
333
818
 
334
819
  Returns:
335
- Tuple[Message, str, List[str]]: A tuple containing the assistant message, the response content, and the tool ids.
820
+ ModelResponse: Parsed response data
336
821
  """
337
- message_data = MessageData()
822
+ model_response = ModelResponse()
823
+
824
+ # Add role (Claude always uses 'assistant')
825
+ model_response.role = response.role or "assistant"
338
826
 
339
827
  if response.content:
340
- message_data.response_block = response.content
341
- message_data.response_block_content = response.content[0]
342
- message_data.response_usage = response.usage
343
-
344
- # -*- Extract response content
345
- if message_data.response_block_content is not None:
346
- if isinstance(message_data.response_block_content, TextBlock):
347
- message_data.response_content = message_data.response_block_content.text
348
- elif isinstance(message_data.response_block_content, ToolUseBlock):
349
- tool_block_input = message_data.response_block_content.input
350
- if tool_block_input and isinstance(tool_block_input, dict):
351
- message_data.response_content = tool_block_input.get("query", "")
352
-
353
- # -*- Extract tool calls from the response
828
+ for block in response.content:
829
+ if block.type == "text":
830
+ text_content = block.text
831
+
832
+ if model_response.content is None:
833
+ model_response.content = text_content
834
+ else:
835
+ model_response.content += text_content
836
+
837
+ # Handle structured outputs (JSON outputs)
838
+ if (
839
+ response_format is not None
840
+ and isinstance(response_format, type)
841
+ and issubclass(response_format, BaseModel)
842
+ ):
843
+ if text_content:
844
+ try:
845
+ # Parse JSON from text content
846
+ parsed_data = json.loads(text_content)
847
+ # Validate against Pydantic model
848
+ model_response.parsed = response_format.model_validate(parsed_data)
849
+ log_debug(f"Successfully parsed structured output: {model_response.parsed}")
850
+ except json.JSONDecodeError as e:
851
+ log_warning(f"Failed to parse JSON from structured output: {e}")
852
+ except ValidationError as e:
853
+ log_warning(f"Failed to validate structured output against schema: {e}")
854
+ except Exception as e:
855
+ log_warning(f"Unexpected error parsing structured output: {e}")
856
+
857
+ # Capture citations from the response
858
+ if block.citations is not None:
859
+ if model_response.citations is None:
860
+ model_response.citations = Citations(raw=[], urls=[], documents=[])
861
+ for citation in block.citations:
862
+ model_response.citations.raw.append(citation.model_dump()) # type: ignore
863
+ # Web search citations
864
+ if isinstance(citation, CitationsWebSearchResultLocation):
865
+ model_response.citations.urls.append( # type: ignore
866
+ UrlCitation(url=citation.url, title=citation.cited_text)
867
+ )
868
+ # Document citations
869
+ elif isinstance(citation, CitationPageLocation):
870
+ model_response.citations.documents.append( # type: ignore
871
+ DocumentCitation(
872
+ document_title=citation.document_title,
873
+ cited_text=citation.cited_text,
874
+ )
875
+ )
876
+ elif block.type == "thinking":
877
+ model_response.reasoning_content = block.thinking
878
+ model_response.provider_data = {
879
+ "signature": block.signature,
880
+ }
881
+ elif block.type == "redacted_thinking":
882
+ model_response.redacted_reasoning_content = block.data
883
+
884
+ # Extract tool calls from the response
354
885
  if response.stop_reason == "tool_use":
355
- for block in message_data.response_block:
356
- if isinstance(block, ToolUseBlock):
357
- tool_use: ToolUseBlock = block
358
- tool_name = tool_use.name
359
- tool_input = tool_use.input
360
- message_data.tool_ids.append(tool_use.id)
886
+ for block in response.content:
887
+ if block.type == "tool_use":
888
+ tool_name = block.name
889
+ tool_input = block.input
361
890
 
362
891
  function_def = {"name": tool_name}
363
892
  if tool_input:
364
893
  function_def["arguments"] = json.dumps(tool_input)
365
- message_data.tool_calls.append(
894
+
895
+ model_response.extra = model_response.extra or {}
896
+
897
+ model_response.tool_calls.append(
366
898
  {
367
- "id": tool_use.id,
899
+ "id": block.id,
368
900
  "type": "function",
369
901
  "function": function_def,
370
902
  }
371
903
  )
372
904
 
373
- # -*- Create assistant message
374
- assistant_message = Message(
375
- role=response.role or "assistant",
376
- content=message_data.response_content,
377
- )
378
-
379
- # -*- Update assistant message if tool calls are present
380
- if len(message_data.tool_calls) > 0:
381
- assistant_message.tool_calls = message_data.tool_calls
905
+ # Add usage metrics
906
+ if response.usage is not None:
907
+ model_response.response_usage = self._get_metrics(response.usage)
382
908
 
383
- # -*- Update usage metrics
384
- self.update_usage_metrics(assistant_message, message_data.response_usage, metrics)
385
-
386
- return assistant_message, message_data.response_content, message_data.tool_ids
387
-
388
- def format_function_call_results(
389
- self, function_call_results: List[Message], tool_ids: List[str], messages: List[Message]
390
- ) -> None:
391
- """
392
- Handle the results of function calls.
909
+ # Capture context management information if present
910
+ if self.context_management is not None and hasattr(response, "context_management"):
911
+ if response.context_management is not None: # type: ignore
912
+ model_response.provider_data = model_response.provider_data or {}
913
+ if hasattr(response.context_management, "model_dump"):
914
+ model_response.provider_data["context_management"] = response.context_management.model_dump() # type: ignore
915
+ else:
916
+ model_response.provider_data["context_management"] = response.context_management # type: ignore
917
+ # Extract file IDs if skills are enabled
918
+ if self.skills and response.content:
919
+ file_ids: List[str] = []
920
+ for block in response.content:
921
+ if block.type == "bash_code_execution_tool_result":
922
+ if hasattr(block, "content") and hasattr(block.content, "content"):
923
+ if isinstance(block.content.content, list):
924
+ for output_block in block.content.content:
925
+ if hasattr(output_block, "file_id"):
926
+ file_ids.append(output_block.file_id)
927
+
928
+ if file_ids:
929
+ if model_response.provider_data is None:
930
+ model_response.provider_data = {}
931
+ model_response.provider_data["file_ids"] = file_ids
393
932
 
394
- Args:
395
- function_call_results (List[Message]): The results of the function calls.
396
- tool_ids (List[str]): The tool ids.
397
- messages (List[Message]): The list of conversation messages.
398
- """
399
- if len(function_call_results) > 0:
400
- fc_responses: List = []
401
- for _fc_message_index, _fc_message in enumerate(function_call_results):
402
- fc_responses.append(
403
- {
404
- "type": "tool_result",
405
- "tool_use_id": tool_ids[_fc_message_index],
406
- "content": _fc_message.content,
407
- }
408
- )
409
- messages.append(Message(role="user", content=fc_responses))
933
+ return model_response
410
934
 
411
- def handle_tool_calls(
935
+ def _parse_provider_response_delta(
412
936
  self,
413
- assistant_message: Message,
414
- messages: List[Message],
415
- model_response: ModelResponse,
416
- response_content: str,
417
- tool_ids: List[str],
418
- ) -> Optional[ModelResponse]:
937
+ response: Union[
938
+ ContentBlockStartEvent,
939
+ ContentBlockDeltaEvent,
940
+ ContentBlockStopEvent,
941
+ MessageStopEvent,
942
+ BetaRawContentBlockDeltaEvent,
943
+ BetaRawContentBlockStartEvent,
944
+ ParsedBetaContentBlockStopEvent,
945
+ ParsedBetaMessageStopEvent,
946
+ ],
947
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
948
+ ) -> ModelResponse:
419
949
  """
420
- Handle tool calls in the assistant message.
950
+ Parse the Claude streaming response into ModelProviderResponse objects.
421
951
 
422
952
  Args:
423
- assistant_message (Message): The assistant message.
424
- messages (List[Message]): A list of messages.
425
- model_response [ModelResponse]: The model response.
426
- response_content (str): The response content.
427
- tool_ids (List[str]): The tool ids.
953
+ response: Raw response chunk from Anthropic
954
+ response_format: Optional response format for structured output parsing
428
955
 
429
956
  Returns:
430
- Optional[ModelResponse]: The model response.
431
- """
432
- if assistant_message.tool_calls is not None and len(assistant_message.tool_calls) > 0:
433
- if model_response.tool_calls is None:
434
- model_response.tool_calls = []
435
-
436
- model_response.content = str(response_content)
437
- model_response.content += "\n\n"
438
-
439
- function_calls_to_run = self._get_function_calls_to_run(assistant_message, messages)
440
- function_call_results: List[Message] = []
441
-
442
- if self.show_tool_calls:
443
- if len(function_calls_to_run) == 1:
444
- model_response.content += f" - Running: {function_calls_to_run[0].get_call_str()}\n\n"
445
- elif len(function_calls_to_run) > 1:
446
- model_response.content += "Running:"
447
- for _f in function_calls_to_run:
448
- model_response.content += f"\n - {_f.get_call_str()}"
449
- model_response.content += "\n\n"
450
-
451
- for function_call_response in self.run_function_calls(
452
- function_calls=function_calls_to_run,
453
- function_call_results=function_call_results,
454
- ):
455
- if (
456
- function_call_response.event == ModelResponseEvent.tool_call_completed.value
457
- and function_call_response.tool_calls is not None
458
- ):
459
- model_response.tool_calls.extend(function_call_response.tool_calls)
460
-
461
- self.format_function_call_results(function_call_results, tool_ids, messages)
462
-
463
- return model_response
464
- return None
465
-
466
- def response(self, messages: List[Message]) -> ModelResponse:
467
- """
468
- Send a chat completion request to the Anthropic API.
469
-
470
- Args:
471
- messages (List[Message]): A list of messages to send to the model.
472
-
473
- Returns:
474
- ModelResponse: The response from the model.
957
+ ModelResponse: Iterator of parsed response data
475
958
  """
476
- logger.debug("---------- Claude Response Start ----------")
477
- self._log_messages(messages)
478
959
  model_response = ModelResponse()
479
- metrics_for_run = Metrics()
480
960
 
481
- metrics_for_run.start_response_timer()
482
- response: AnthropicMessage = self.invoke(messages=messages)
483
- metrics_for_run.stop_response_timer()
961
+ if isinstance(response, (ContentBlockStartEvent, BetaRawContentBlockStartEvent)):
962
+ if response.content_block.type == "redacted_reasoning_content":
963
+ model_response.redacted_reasoning_content = response.content_block.data
964
+
965
+ if isinstance(response, (ContentBlockDeltaEvent, BetaRawContentBlockDeltaEvent)):
966
+ # Handle text content
967
+ if response.delta.type == "text_delta":
968
+ model_response.content = response.delta.text
969
+ # Handle thinking content
970
+ elif response.delta.type == "thinking_delta":
971
+ model_response.reasoning_content = response.delta.thinking
972
+ elif response.delta.type == "signature_delta":
973
+ model_response.provider_data = {
974
+ "signature": response.delta.signature,
975
+ }
484
976
 
485
- # -*- Create assistant message
486
- assistant_message, response_content, tool_ids = self.create_assistant_message(
487
- response=response, metrics=metrics_for_run
488
- )
977
+ elif isinstance(response, (ContentBlockStopEvent, ParsedBetaContentBlockStopEvent)):
978
+ if response.content_block.type == "tool_use": # type: ignore
979
+ tool_use = response.content_block # type: ignore
980
+ tool_name = tool_use.name # type: ignore
981
+ tool_input = tool_use.input # type: ignore
489
982
 
490
- # -*- Add assistant message to messages
491
- messages.append(assistant_message)
983
+ function_def = {"name": tool_name}
984
+ if tool_input:
985
+ function_def["arguments"] = json.dumps(tool_input)
492
986
 
493
- # -*- Log response and metrics
494
- assistant_message.log()
495
- metrics_for_run.log()
987
+ model_response.extra = model_response.extra or {}
496
988
 
497
- # -*- Handle tool calls
498
- if self.handle_tool_calls(assistant_message, messages, model_response, response_content, tool_ids):
499
- response_after_tool_calls = self.response(messages=messages)
500
- if response_after_tool_calls.content is not None:
501
- if model_response.content is None:
502
- model_response.content = ""
503
- model_response.content += response_after_tool_calls.content
504
- return model_response
989
+ model_response.tool_calls = [
990
+ {
991
+ "id": tool_use.id, # type: ignore
992
+ "type": "function",
993
+ "function": function_def,
994
+ }
995
+ ]
996
+
997
+ # Capture citations from the final response and handle structured outputs
998
+ elif isinstance(response, (MessageStopEvent, ParsedBetaMessageStopEvent)):
999
+ # In streaming mode, content has already been emitted via ContentBlockDeltaEvent chunks
1000
+ # Setting content here would cause duplication since _populate_stream_data accumulates with +=
1001
+ # Keep content empty to avoid duplication
1002
+ model_response.content = ""
1003
+ model_response.citations = Citations(raw=[], urls=[], documents=[])
1004
+
1005
+ # Accumulate text content for structured output parsing (but don't set model_response.content)
1006
+ # The text was already streamed via ContentBlockDeltaEvent chunks
1007
+ accumulated_text = ""
1008
+
1009
+ for block in response.message.content: # type: ignore
1010
+ # Handle text blocks for structured output parsing
1011
+ if block.type == "text":
1012
+ accumulated_text += block.text # type: ignore
1013
+
1014
+ # Handle citations
1015
+ citations = getattr(block, "citations", None)
1016
+ if not citations:
1017
+ continue
1018
+ for citation in citations:
1019
+ model_response.citations.raw.append(citation.model_dump()) # type: ignore
1020
+ # Web search citations
1021
+ if isinstance(citation, CitationsWebSearchResultLocation):
1022
+ model_response.citations.urls.append(UrlCitation(url=citation.url, title=citation.cited_text)) # type: ignore
1023
+ # Document citations
1024
+ elif isinstance(citation, CitationPageLocation):
1025
+ model_response.citations.documents.append( # type: ignore
1026
+ DocumentCitation(document_title=citation.document_title, cited_text=citation.cited_text)
1027
+ )
505
1028
 
506
- # -*- Update model response
507
- if assistant_message.content is not None:
508
- model_response.content = assistant_message.get_content_string()
1029
+ # Handle structured outputs (JSON outputs) from accumulated text
1030
+ # Note: We parse from accumulated_text but don't set model_response.content to avoid duplication
1031
+ # The content was already streamed via ContentBlockDeltaEvent chunks
1032
+ if (
1033
+ response_format is not None
1034
+ and isinstance(response_format, type)
1035
+ and issubclass(response_format, BaseModel)
1036
+ ):
1037
+ if accumulated_text:
1038
+ try:
1039
+ # Parse JSON from accumulated text content
1040
+ parsed_data = json.loads(accumulated_text)
1041
+ # Validate against Pydantic model
1042
+ model_response.parsed = response_format.model_validate(parsed_data)
1043
+ log_debug(f"Successfully parsed structured output from stream: {model_response.parsed}")
1044
+ except json.JSONDecodeError as e:
1045
+ log_warning(f"Failed to parse JSON from structured output in stream: {e}")
1046
+ except ValidationError as e:
1047
+ log_warning(f"Failed to validate structured output against schema in stream: {e}")
1048
+ except Exception as e:
1049
+ log_warning(f"Unexpected error parsing structured output in stream: {e}")
1050
+
1051
+ # Capture context management information if present
1052
+ if self.context_management is not None and hasattr(response.message, "context_management"): # type: ignore
1053
+ context_mgmt = response.message.context_management # type: ignore
1054
+ if context_mgmt is not None:
1055
+ model_response.provider_data = model_response.provider_data or {}
1056
+ if hasattr(context_mgmt, "model_dump"):
1057
+ model_response.provider_data["context_management"] = context_mgmt.model_dump()
1058
+ else:
1059
+ model_response.provider_data["context_management"] = context_mgmt
1060
+
1061
+ if hasattr(response, "message") and hasattr(response.message, "usage") and response.message.usage is not None: # type: ignore
1062
+ model_response.response_usage = self._get_metrics(response.message.usage) # type: ignore
1063
+
1064
+ # Capture the Beta response
1065
+ try:
1066
+ if (
1067
+ isinstance(response, BetaRawContentBlockDeltaEvent)
1068
+ and isinstance(response.delta, BetaTextDelta)
1069
+ and response.delta.text is not None
1070
+ ):
1071
+ model_response.content = response.delta.text
1072
+ except Exception as e:
1073
+ log_error(f"Error parsing Beta response: {e}")
509
1074
 
510
- logger.debug("---------- Claude Response End ----------")
511
1075
  return model_response
512
1076
 
513
- def handle_stream_tool_calls(
514
- self,
515
- assistant_message: Message,
516
- messages: List[Message],
517
- tool_ids: List[str],
518
- ) -> Iterator[ModelResponse]:
1077
+ def _get_metrics(self, response_usage: Union[Usage, MessageDeltaUsage, BetaUsage]) -> Metrics:
519
1078
  """
520
- Parse and run function calls from the assistant message.
1079
+ Parse the given Anthropic-specific usage into an Agno Metrics object.
521
1080
 
522
1081
  Args:
523
- assistant_message (Message): The assistant message containing tool calls.
524
- messages (List[Message]): The list of conversation messages.
525
- tool_ids (List[str]): The list of tool IDs.
526
-
527
- Yields:
528
- Iterator[ModelResponse]: Yields model responses during function execution.
529
- """
530
- if assistant_message.tool_calls is not None and len(assistant_message.tool_calls) > 0:
531
- yield ModelResponse(content="\n\n")
532
- function_calls_to_run = self._get_function_calls_to_run(assistant_message, messages)
533
- function_call_results: List[Message] = []
534
-
535
- if self.show_tool_calls:
536
- if len(function_calls_to_run) == 1:
537
- yield ModelResponse(content=f" - Running: {function_calls_to_run[0].get_call_str()}\n\n")
538
- elif len(function_calls_to_run) > 1:
539
- yield ModelResponse(content="Running:")
540
- for _f in function_calls_to_run:
541
- yield ModelResponse(content=f"\n - {_f.get_call_str()}")
542
- yield ModelResponse(content="\n\n")
543
-
544
- for intermediate_model_response in self.run_function_calls(
545
- function_calls=function_calls_to_run, function_call_results=function_call_results
546
- ):
547
- yield intermediate_model_response
1082
+ response_usage: Usage data from Anthropic
548
1083
 
549
- self.format_function_call_results(function_call_results, tool_ids, messages)
550
-
551
- def response_stream(self, messages: List[Message]) -> Iterator[ModelResponse]:
552
- logger.debug("---------- Claude Response Start ----------")
553
- self._log_messages(messages)
554
- message_data = MessageData()
1084
+ Returns:
1085
+ Metrics: Parsed metrics data
1086
+ """
555
1087
  metrics = Metrics()
556
1088
 
557
- # -*- Generate response
558
- metrics.start_response_timer()
559
- response = self.invoke_stream(messages=messages)
560
- with response as stream:
561
- for delta in stream:
562
- if isinstance(delta, RawContentBlockDeltaEvent):
563
- if isinstance(delta.delta, TextDelta):
564
- yield ModelResponse(content=delta.delta.text)
565
- message_data.response_content += delta.delta.text
566
- metrics.output_tokens += 1
567
- if metrics.output_tokens == 1:
568
- metrics.time_to_first_token = metrics.response_timer.elapsed
569
-
570
- if isinstance(delta, ContentBlockStopEvent):
571
- if isinstance(delta.content_block, ToolUseBlock):
572
- tool_use = delta.content_block
573
- tool_name = tool_use.name
574
- tool_input = tool_use.input
575
- message_data.tool_ids.append(tool_use.id)
576
-
577
- function_def = {"name": tool_name}
578
- if tool_input:
579
- function_def["arguments"] = json.dumps(tool_input)
580
- message_data.tool_calls.append(
581
- {
582
- "id": tool_use.id,
583
- "type": "function",
584
- "function": function_def,
585
- }
586
- )
587
- message_data.response_block.append(delta.content_block)
588
-
589
- if isinstance(delta, MessageStopEvent):
590
- message_data.response_usage = delta.message.usage
591
- yield ModelResponse(content="\n\n")
592
-
593
- metrics.stop_response_timer()
594
-
595
- # -*- Create assistant message
596
- assistant_message = Message(
597
- role="assistant",
598
- content=message_data.response_content,
599
- )
600
-
601
- # -*- Update assistant message if tool calls are present
602
- if len(message_data.tool_calls) > 0:
603
- assistant_message.tool_calls = message_data.tool_calls
604
-
605
- # -*- Update usage metrics
606
- self.update_usage_metrics(assistant_message, message_data.response_usage, metrics)
607
-
608
- # -*- Add assistant message to messages
609
- messages.append(assistant_message)
610
-
611
- # -*- Log response and metrics
612
- assistant_message.log()
613
- metrics.log()
614
-
615
- if assistant_message.tool_calls is not None and len(assistant_message.tool_calls) > 0:
616
- yield from self.handle_stream_tool_calls(assistant_message, messages, message_data.tool_ids)
617
- yield from self.response_stream(messages=messages)
618
- logger.debug("---------- Claude Response End ----------")
619
-
620
- def get_tool_call_prompt(self) -> Optional[str]:
621
- if self._functions is not None and len(self._functions) > 0:
622
- tool_call_prompt = "Do not reflect on the quality of the returned search results in your response"
623
- return tool_call_prompt
624
- return None
625
-
626
- def get_system_message_for_model(self) -> Optional[str]:
627
- return self.get_tool_call_prompt()
628
-
629
- async def ainvoke(self, *args, **kwargs) -> Any:
630
- raise NotImplementedError(f"Async not supported on {self.name}.")
631
-
632
- async def ainvoke_stream(self, *args, **kwargs) -> Any:
633
- raise NotImplementedError(f"Async not supported on {self.name}.")
634
-
635
- async def aresponse(self, messages: List[Message]) -> ModelResponse:
636
- raise NotImplementedError(f"Async not supported on {self.name}.")
637
-
638
- async def aresponse_stream(self, messages: List[Message]) -> ModelResponse:
639
- raise NotImplementedError(f"Async not supported on {self.name}.")
1089
+ metrics.input_tokens = response_usage.input_tokens or 0
1090
+ metrics.output_tokens = response_usage.output_tokens or 0
1091
+ metrics.total_tokens = metrics.input_tokens + metrics.output_tokens
1092
+ metrics.cache_read_tokens = response_usage.cache_read_input_tokens or 0
1093
+ metrics.cache_write_tokens = response_usage.cache_creation_input_tokens or 0
1094
+
1095
+ # Anthropic-specific additional fields
1096
+ if response_usage.server_tool_use:
1097
+ metrics.provider_metrics = {"server_tool_use": response_usage.server_tool_use.model_dump()}
1098
+ if isinstance(response_usage, Usage):
1099
+ if response_usage.service_tier:
1100
+ metrics.provider_metrics = metrics.provider_metrics or {}
1101
+ metrics.provider_metrics["service_tier"] = response_usage.service_tier
1102
+
1103
+ return metrics