agno 2.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 (314) hide show
  1. agno/agent/agent.py +5540 -2273
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/compression/__init__.py +3 -0
  5. agno/compression/manager.py +247 -0
  6. agno/culture/__init__.py +3 -0
  7. agno/culture/manager.py +956 -0
  8. agno/db/async_postgres/__init__.py +3 -0
  9. agno/db/base.py +689 -6
  10. agno/db/dynamo/dynamo.py +933 -37
  11. agno/db/dynamo/schemas.py +174 -10
  12. agno/db/dynamo/utils.py +63 -4
  13. agno/db/firestore/firestore.py +831 -9
  14. agno/db/firestore/schemas.py +51 -0
  15. agno/db/firestore/utils.py +102 -4
  16. agno/db/gcs_json/gcs_json_db.py +660 -12
  17. agno/db/gcs_json/utils.py +60 -26
  18. agno/db/in_memory/in_memory_db.py +287 -14
  19. agno/db/in_memory/utils.py +60 -2
  20. agno/db/json/json_db.py +590 -14
  21. agno/db/json/utils.py +60 -26
  22. agno/db/migrations/manager.py +199 -0
  23. agno/db/migrations/v1_to_v2.py +43 -13
  24. agno/db/migrations/versions/__init__.py +0 -0
  25. agno/db/migrations/versions/v2_3_0.py +938 -0
  26. agno/db/mongo/__init__.py +15 -1
  27. agno/db/mongo/async_mongo.py +2760 -0
  28. agno/db/mongo/mongo.py +879 -11
  29. agno/db/mongo/schemas.py +42 -0
  30. agno/db/mongo/utils.py +80 -8
  31. agno/db/mysql/__init__.py +2 -1
  32. agno/db/mysql/async_mysql.py +2912 -0
  33. agno/db/mysql/mysql.py +946 -68
  34. agno/db/mysql/schemas.py +72 -10
  35. agno/db/mysql/utils.py +198 -7
  36. agno/db/postgres/__init__.py +2 -1
  37. agno/db/postgres/async_postgres.py +2579 -0
  38. agno/db/postgres/postgres.py +942 -57
  39. agno/db/postgres/schemas.py +81 -18
  40. agno/db/postgres/utils.py +164 -2
  41. agno/db/redis/redis.py +671 -7
  42. agno/db/redis/schemas.py +50 -0
  43. agno/db/redis/utils.py +65 -7
  44. agno/db/schemas/__init__.py +2 -1
  45. agno/db/schemas/culture.py +120 -0
  46. agno/db/schemas/evals.py +1 -0
  47. agno/db/schemas/memory.py +17 -2
  48. agno/db/singlestore/schemas.py +63 -0
  49. agno/db/singlestore/singlestore.py +949 -83
  50. agno/db/singlestore/utils.py +60 -2
  51. agno/db/sqlite/__init__.py +2 -1
  52. agno/db/sqlite/async_sqlite.py +2911 -0
  53. agno/db/sqlite/schemas.py +62 -0
  54. agno/db/sqlite/sqlite.py +965 -46
  55. agno/db/sqlite/utils.py +169 -8
  56. agno/db/surrealdb/__init__.py +3 -0
  57. agno/db/surrealdb/metrics.py +292 -0
  58. agno/db/surrealdb/models.py +334 -0
  59. agno/db/surrealdb/queries.py +71 -0
  60. agno/db/surrealdb/surrealdb.py +1908 -0
  61. agno/db/surrealdb/utils.py +147 -0
  62. agno/db/utils.py +2 -0
  63. agno/eval/__init__.py +10 -0
  64. agno/eval/accuracy.py +75 -55
  65. agno/eval/agent_as_judge.py +861 -0
  66. agno/eval/base.py +29 -0
  67. agno/eval/performance.py +16 -7
  68. agno/eval/reliability.py +28 -16
  69. agno/eval/utils.py +35 -17
  70. agno/exceptions.py +27 -2
  71. agno/filters.py +354 -0
  72. agno/guardrails/prompt_injection.py +1 -0
  73. agno/hooks/__init__.py +3 -0
  74. agno/hooks/decorator.py +164 -0
  75. agno/integrations/discord/client.py +1 -1
  76. agno/knowledge/chunking/agentic.py +13 -10
  77. agno/knowledge/chunking/fixed.py +4 -1
  78. agno/knowledge/chunking/semantic.py +9 -4
  79. agno/knowledge/chunking/strategy.py +59 -15
  80. agno/knowledge/embedder/fastembed.py +1 -1
  81. agno/knowledge/embedder/nebius.py +1 -1
  82. agno/knowledge/embedder/ollama.py +8 -0
  83. agno/knowledge/embedder/openai.py +8 -8
  84. agno/knowledge/embedder/sentence_transformer.py +6 -2
  85. agno/knowledge/embedder/vllm.py +262 -0
  86. agno/knowledge/knowledge.py +1618 -318
  87. agno/knowledge/reader/base.py +6 -2
  88. agno/knowledge/reader/csv_reader.py +8 -10
  89. agno/knowledge/reader/docx_reader.py +5 -6
  90. agno/knowledge/reader/field_labeled_csv_reader.py +16 -20
  91. agno/knowledge/reader/json_reader.py +5 -4
  92. agno/knowledge/reader/markdown_reader.py +8 -8
  93. agno/knowledge/reader/pdf_reader.py +17 -19
  94. agno/knowledge/reader/pptx_reader.py +101 -0
  95. agno/knowledge/reader/reader_factory.py +32 -3
  96. agno/knowledge/reader/s3_reader.py +3 -3
  97. agno/knowledge/reader/tavily_reader.py +193 -0
  98. agno/knowledge/reader/text_reader.py +22 -10
  99. agno/knowledge/reader/web_search_reader.py +1 -48
  100. agno/knowledge/reader/website_reader.py +10 -10
  101. agno/knowledge/reader/wikipedia_reader.py +33 -1
  102. agno/knowledge/types.py +1 -0
  103. agno/knowledge/utils.py +72 -7
  104. agno/media.py +22 -6
  105. agno/memory/__init__.py +14 -1
  106. agno/memory/manager.py +544 -83
  107. agno/memory/strategies/__init__.py +15 -0
  108. agno/memory/strategies/base.py +66 -0
  109. agno/memory/strategies/summarize.py +196 -0
  110. agno/memory/strategies/types.py +37 -0
  111. agno/models/aimlapi/aimlapi.py +17 -0
  112. agno/models/anthropic/claude.py +515 -40
  113. agno/models/aws/bedrock.py +102 -21
  114. agno/models/aws/claude.py +131 -274
  115. agno/models/azure/ai_foundry.py +41 -19
  116. agno/models/azure/openai_chat.py +39 -8
  117. agno/models/base.py +1249 -525
  118. agno/models/cerebras/cerebras.py +91 -21
  119. agno/models/cerebras/cerebras_openai.py +21 -2
  120. agno/models/cohere/chat.py +40 -6
  121. agno/models/cometapi/cometapi.py +18 -1
  122. agno/models/dashscope/dashscope.py +2 -3
  123. agno/models/deepinfra/deepinfra.py +18 -1
  124. agno/models/deepseek/deepseek.py +69 -3
  125. agno/models/fireworks/fireworks.py +18 -1
  126. agno/models/google/gemini.py +877 -80
  127. agno/models/google/utils.py +22 -0
  128. agno/models/groq/groq.py +51 -18
  129. agno/models/huggingface/huggingface.py +17 -6
  130. agno/models/ibm/watsonx.py +16 -6
  131. agno/models/internlm/internlm.py +18 -1
  132. agno/models/langdb/langdb.py +13 -1
  133. agno/models/litellm/chat.py +44 -9
  134. agno/models/litellm/litellm_openai.py +18 -1
  135. agno/models/message.py +28 -5
  136. agno/models/meta/llama.py +47 -14
  137. agno/models/meta/llama_openai.py +22 -17
  138. agno/models/mistral/mistral.py +8 -4
  139. agno/models/nebius/nebius.py +6 -7
  140. agno/models/nvidia/nvidia.py +20 -3
  141. agno/models/ollama/chat.py +24 -8
  142. agno/models/openai/chat.py +104 -29
  143. agno/models/openai/responses.py +101 -81
  144. agno/models/openrouter/openrouter.py +60 -3
  145. agno/models/perplexity/perplexity.py +17 -1
  146. agno/models/portkey/portkey.py +7 -6
  147. agno/models/requesty/requesty.py +24 -4
  148. agno/models/response.py +73 -2
  149. agno/models/sambanova/sambanova.py +20 -3
  150. agno/models/siliconflow/siliconflow.py +19 -2
  151. agno/models/together/together.py +20 -3
  152. agno/models/utils.py +254 -8
  153. agno/models/vercel/v0.py +20 -3
  154. agno/models/vertexai/__init__.py +0 -0
  155. agno/models/vertexai/claude.py +190 -0
  156. agno/models/vllm/vllm.py +19 -14
  157. agno/models/xai/xai.py +19 -2
  158. agno/os/app.py +549 -152
  159. agno/os/auth.py +190 -3
  160. agno/os/config.py +23 -0
  161. agno/os/interfaces/a2a/router.py +8 -11
  162. agno/os/interfaces/a2a/utils.py +1 -1
  163. agno/os/interfaces/agui/router.py +18 -3
  164. agno/os/interfaces/agui/utils.py +152 -39
  165. agno/os/interfaces/slack/router.py +55 -37
  166. agno/os/interfaces/slack/slack.py +9 -1
  167. agno/os/interfaces/whatsapp/router.py +0 -1
  168. agno/os/interfaces/whatsapp/security.py +3 -1
  169. agno/os/mcp.py +110 -52
  170. agno/os/middleware/__init__.py +2 -0
  171. agno/os/middleware/jwt.py +676 -112
  172. agno/os/router.py +40 -1478
  173. agno/os/routers/agents/__init__.py +3 -0
  174. agno/os/routers/agents/router.py +599 -0
  175. agno/os/routers/agents/schema.py +261 -0
  176. agno/os/routers/evals/evals.py +96 -39
  177. agno/os/routers/evals/schemas.py +65 -33
  178. agno/os/routers/evals/utils.py +80 -10
  179. agno/os/routers/health.py +10 -4
  180. agno/os/routers/knowledge/knowledge.py +196 -38
  181. agno/os/routers/knowledge/schemas.py +82 -22
  182. agno/os/routers/memory/memory.py +279 -52
  183. agno/os/routers/memory/schemas.py +46 -17
  184. agno/os/routers/metrics/metrics.py +20 -8
  185. agno/os/routers/metrics/schemas.py +16 -16
  186. agno/os/routers/session/session.py +462 -34
  187. agno/os/routers/teams/__init__.py +3 -0
  188. agno/os/routers/teams/router.py +512 -0
  189. agno/os/routers/teams/schema.py +257 -0
  190. agno/os/routers/traces/__init__.py +3 -0
  191. agno/os/routers/traces/schemas.py +414 -0
  192. agno/os/routers/traces/traces.py +499 -0
  193. agno/os/routers/workflows/__init__.py +3 -0
  194. agno/os/routers/workflows/router.py +624 -0
  195. agno/os/routers/workflows/schema.py +75 -0
  196. agno/os/schema.py +256 -693
  197. agno/os/scopes.py +469 -0
  198. agno/os/utils.py +514 -36
  199. agno/reasoning/anthropic.py +80 -0
  200. agno/reasoning/gemini.py +73 -0
  201. agno/reasoning/openai.py +5 -0
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +155 -32
  205. agno/run/base.py +55 -3
  206. agno/run/requirement.py +181 -0
  207. agno/run/team.py +125 -38
  208. agno/run/workflow.py +72 -18
  209. agno/session/agent.py +102 -89
  210. agno/session/summary.py +56 -15
  211. agno/session/team.py +164 -90
  212. agno/session/workflow.py +405 -40
  213. agno/table.py +10 -0
  214. agno/team/team.py +3974 -1903
  215. agno/tools/dalle.py +2 -4
  216. agno/tools/eleven_labs.py +23 -25
  217. agno/tools/exa.py +21 -16
  218. agno/tools/file.py +153 -23
  219. agno/tools/file_generation.py +16 -10
  220. agno/tools/firecrawl.py +15 -7
  221. agno/tools/function.py +193 -38
  222. agno/tools/gmail.py +238 -14
  223. agno/tools/google_drive.py +271 -0
  224. agno/tools/googlecalendar.py +36 -8
  225. agno/tools/googlesheets.py +20 -5
  226. agno/tools/jira.py +20 -0
  227. agno/tools/mcp/__init__.py +10 -0
  228. agno/tools/mcp/mcp.py +331 -0
  229. agno/tools/mcp/multi_mcp.py +347 -0
  230. agno/tools/mcp/params.py +24 -0
  231. agno/tools/mcp_toolbox.py +3 -3
  232. agno/tools/models/nebius.py +5 -5
  233. agno/tools/models_labs.py +20 -10
  234. agno/tools/nano_banana.py +151 -0
  235. agno/tools/notion.py +204 -0
  236. agno/tools/parallel.py +314 -0
  237. agno/tools/postgres.py +76 -36
  238. agno/tools/redshift.py +406 -0
  239. agno/tools/scrapegraph.py +1 -1
  240. agno/tools/shopify.py +1519 -0
  241. agno/tools/slack.py +18 -3
  242. agno/tools/spotify.py +919 -0
  243. agno/tools/tavily.py +146 -0
  244. agno/tools/toolkit.py +25 -0
  245. agno/tools/workflow.py +8 -1
  246. agno/tools/yfinance.py +12 -11
  247. agno/tracing/__init__.py +12 -0
  248. agno/tracing/exporter.py +157 -0
  249. agno/tracing/schemas.py +276 -0
  250. agno/tracing/setup.py +111 -0
  251. agno/utils/agent.py +938 -0
  252. agno/utils/cryptography.py +22 -0
  253. agno/utils/dttm.py +33 -0
  254. agno/utils/events.py +151 -3
  255. agno/utils/gemini.py +15 -5
  256. agno/utils/hooks.py +118 -4
  257. agno/utils/http.py +113 -2
  258. agno/utils/knowledge.py +12 -5
  259. agno/utils/log.py +1 -0
  260. agno/utils/mcp.py +92 -2
  261. agno/utils/media.py +187 -1
  262. agno/utils/merge_dict.py +3 -3
  263. agno/utils/message.py +60 -0
  264. agno/utils/models/ai_foundry.py +9 -2
  265. agno/utils/models/claude.py +49 -14
  266. agno/utils/models/cohere.py +9 -2
  267. agno/utils/models/llama.py +9 -2
  268. agno/utils/models/mistral.py +4 -2
  269. agno/utils/print_response/agent.py +109 -16
  270. agno/utils/print_response/team.py +223 -30
  271. agno/utils/print_response/workflow.py +251 -34
  272. agno/utils/streamlit.py +1 -1
  273. agno/utils/team.py +98 -9
  274. agno/utils/tokens.py +657 -0
  275. agno/vectordb/base.py +39 -7
  276. agno/vectordb/cassandra/cassandra.py +21 -5
  277. agno/vectordb/chroma/chromadb.py +43 -12
  278. agno/vectordb/clickhouse/clickhousedb.py +21 -5
  279. agno/vectordb/couchbase/couchbase.py +29 -5
  280. agno/vectordb/lancedb/lance_db.py +92 -181
  281. agno/vectordb/langchaindb/langchaindb.py +24 -4
  282. agno/vectordb/lightrag/lightrag.py +17 -3
  283. agno/vectordb/llamaindex/llamaindexdb.py +25 -5
  284. agno/vectordb/milvus/milvus.py +50 -37
  285. agno/vectordb/mongodb/__init__.py +7 -1
  286. agno/vectordb/mongodb/mongodb.py +36 -30
  287. agno/vectordb/pgvector/pgvector.py +201 -77
  288. agno/vectordb/pineconedb/pineconedb.py +41 -23
  289. agno/vectordb/qdrant/qdrant.py +67 -54
  290. agno/vectordb/redis/__init__.py +9 -0
  291. agno/vectordb/redis/redisdb.py +682 -0
  292. agno/vectordb/singlestore/singlestore.py +50 -29
  293. agno/vectordb/surrealdb/surrealdb.py +31 -41
  294. agno/vectordb/upstashdb/upstashdb.py +34 -6
  295. agno/vectordb/weaviate/weaviate.py +53 -14
  296. agno/workflow/__init__.py +2 -0
  297. agno/workflow/agent.py +299 -0
  298. agno/workflow/condition.py +120 -18
  299. agno/workflow/loop.py +77 -10
  300. agno/workflow/parallel.py +231 -143
  301. agno/workflow/router.py +118 -17
  302. agno/workflow/step.py +609 -170
  303. agno/workflow/steps.py +73 -6
  304. agno/workflow/types.py +96 -21
  305. agno/workflow/workflow.py +2039 -262
  306. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/METADATA +201 -66
  307. agno-2.3.13.dist-info/RECORD +613 -0
  308. agno/tools/googlesearch.py +0 -98
  309. agno/tools/mcp.py +0 -679
  310. agno/tools/memori.py +0 -339
  311. agno-2.1.2.dist-info/RECORD +0 -543
  312. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +0 -0
  313. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/licenses/LICENSE +0 -0
  314. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
@@ -4,7 +4,8 @@ from dataclasses import asdict, dataclass
4
4
  from os import getenv
5
5
  from typing import Any, Dict, List, Optional, Type, Union
6
6
 
7
- from pydantic import BaseModel
7
+ import httpx
8
+ from pydantic import BaseModel, ValidationError
8
9
 
9
10
  from agno.exceptions import ModelProviderError, ModelRateLimitError
10
11
  from agno.models.base import Model
@@ -12,8 +13,11 @@ from agno.models.message import Citations, DocumentCitation, Message, UrlCitatio
12
13
  from agno.models.metrics import Metrics
13
14
  from agno.models.response import ModelResponse
14
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
15
18
  from agno.utils.log import log_debug, log_error, log_warning
16
19
  from agno.utils.models.claude import MCPServerConfiguration, format_messages, format_tools_for_model
20
+ from agno.utils.tokens import count_schema_tokens
17
21
 
18
22
  try:
19
23
  from anthropic import Anthropic as AnthropicClient
@@ -25,6 +29,11 @@ try:
25
29
  from anthropic import (
26
30
  AsyncAnthropic as AsyncAnthropicClient,
27
31
  )
32
+ from anthropic.lib.streaming._beta_types import (
33
+ BetaRawContentBlockStartEvent,
34
+ ParsedBetaContentBlockStopEvent,
35
+ ParsedBetaMessageStopEvent,
36
+ )
28
37
  from anthropic.types import (
29
38
  CitationPageLocation,
30
39
  CitationsWebSearchResultLocation,
@@ -39,12 +48,15 @@ try:
39
48
  from anthropic.types import (
40
49
  Message as AnthropicMessage,
41
50
  )
51
+
42
52
  except ImportError as e:
43
53
  raise ImportError("`anthropic` not installed. Please install it with `pip install anthropic`") from e
44
54
 
45
55
  # Import Beta types
46
56
  try:
47
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
48
60
  except ImportError as e:
49
61
  raise ImportError(
50
62
  "`anthropic` not installed or missing beta components. Please install with `pip install anthropic`"
@@ -59,12 +71,47 @@ class Claude(Model):
59
71
  For more information, see: https://docs.anthropic.com/en/api/messages
60
72
  """
61
73
 
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
+
62
109
  id: str = "claude-sonnet-4-5-20250929"
63
110
  name: str = "Claude"
64
111
  provider: str = "Anthropic"
65
112
 
66
113
  # Request parameters
67
- max_tokens: Optional[int] = 4096
114
+ max_tokens: Optional[int] = 8192
68
115
  thinking: Optional[Dict[str, Any]] = None
69
116
  temperature: Optional[float] = None
70
117
  stop_sequences: Optional[List[str]] = None
@@ -73,27 +120,51 @@ class Claude(Model):
73
120
  cache_system_prompt: Optional[bool] = False
74
121
  extended_cache_time: Optional[bool] = False
75
122
  request_params: Optional[Dict[str, Any]] = None
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
76
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
+ )
77
131
 
78
132
  # Client parameters
79
133
  api_key: Optional[str] = None
134
+ auth_token: Optional[str] = None
80
135
  default_headers: Optional[Dict[str, Any]] = None
81
136
  timeout: Optional[float] = None
137
+ http_client: Optional[Union[httpx.Client, httpx.AsyncClient]] = None
82
138
  client_params: Optional[Dict[str, Any]] = None
83
139
 
84
- # Anthropic clients
85
140
  client: Optional[AnthropicClient] = None
86
141
  async_client: Optional[AsyncAnthropicClient] = None
87
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
+
88
155
  def _get_client_params(self) -> Dict[str, Any]:
89
156
  client_params: Dict[str, Any] = {}
90
157
 
91
158
  self.api_key = self.api_key or getenv("ANTHROPIC_API_KEY")
92
- if not self.api_key:
93
- log_error("ANTHROPIC_API_KEY not set. Please set the ANTHROPIC_API_KEY environment variable.")
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
+ )
94
164
 
95
165
  # Add API key to client parameters
96
166
  client_params["api_key"] = self.api_key
167
+ client_params["auth_token"] = self.auth_token
97
168
  if self.timeout is not None:
98
169
  client_params["timeout"] = self.timeout
99
170
 
@@ -104,6 +175,188 @@ class Claude(Model):
104
175
  client_params["default_headers"] = self.default_headers
105
176
  return client_params
106
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
+ )
359
+
107
360
  def get_client(self) -> AnthropicClient:
108
361
  """
109
362
  Returns an instance of the Anthropic client.
@@ -112,6 +365,16 @@ class Claude(Model):
112
365
  return self.client
113
366
 
114
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()
115
378
  self.client = AnthropicClient(**_client_params)
116
379
  return self.client
117
380
 
@@ -119,17 +382,79 @@ class Claude(Model):
119
382
  """
120
383
  Returns an instance of the async Anthropic client.
121
384
  """
122
- if self.async_client:
385
+ if self.async_client and not self.async_client.is_closed():
123
386
  return self.async_client
124
387
 
125
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()
126
401
  self.async_client = AsyncAnthropicClient(**_client_params)
127
402
  return self.async_client
128
403
 
129
- def get_request_params(self) -> Dict[str, Any]:
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]:
130
451
  """
131
452
  Generate keyword arguments for API requests.
132
453
  """
454
+ # Validate thinking support if thinking is enabled
455
+ if self.thinking:
456
+ self._validate_thinking_support()
457
+
133
458
  _request_params: Dict[str, Any] = {}
134
459
  if self.max_tokens:
135
460
  _request_params["max_tokens"] = self.max_tokens
@@ -143,28 +468,55 @@ class Claude(Model):
143
468
  _request_params["top_p"] = self.top_p
144
469
  if self.top_k:
145
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
146
487
  if self.mcp_servers:
147
488
  _request_params["mcp_servers"] = [
148
489
  {k: v for k, v in asdict(server).items() if v is not None} for server in self.mcp_servers
149
490
  ]
491
+ if self.skills:
492
+ _request_params["container"] = {"skills": self.skills}
150
493
  if self.request_params:
151
494
  _request_params.update(self.request_params)
152
495
 
153
496
  return _request_params
154
497
 
155
498
  def _prepare_request_kwargs(
156
- self, system_message: str, tools: Optional[List[Dict[str, Any]]] = None
499
+ self,
500
+ system_message: str,
501
+ tools: Optional[List[Dict[str, Any]]] = None,
502
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
157
503
  ) -> Dict[str, Any]:
158
504
  """
159
505
  Prepare the request keyword arguments for the API call.
160
506
 
161
507
  Args:
162
508
  system_message (str): The concatenated system messages.
509
+ tools: Optional list of tools
510
+ response_format: Optional response format (Pydantic model or dict)
163
511
 
164
512
  Returns:
165
513
  Dict[str, Any]: The request keyword arguments.
166
514
  """
167
- request_kwargs = self.get_request_params().copy()
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()
168
520
  if system_message:
169
521
  if self.cache_system_prompt:
170
522
  cache_control = (
@@ -176,9 +528,24 @@ class Claude(Model):
176
528
  else:
177
529
  request_kwargs["system"] = [{"text": system_message, "type": "text"}]
178
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]
539
+
540
+ # Format tools (this will handle strict mode)
179
541
  if tools:
180
542
  request_kwargs["tools"] = format_tools_for_model(tools)
181
543
 
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
548
+
182
549
  if request_kwargs:
183
550
  log_debug(f"Calling {self.provider} with request parameters: {request_kwargs}", log_level=2)
184
551
  return request_kwargs
@@ -191,6 +558,7 @@ class Claude(Model):
191
558
  tools: Optional[List[Dict[str, Any]]] = None,
192
559
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
193
560
  run_response: Optional[RunOutput] = None,
561
+ compress_tool_results: bool = False,
194
562
  ) -> ModelResponse:
195
563
  """
196
564
  Send a request to the Anthropic API to generate a response.
@@ -199,15 +567,15 @@ class Claude(Model):
199
567
  if run_response and run_response.metrics:
200
568
  run_response.metrics.set_time_to_first_token()
201
569
 
202
- chat_messages, system_message = format_messages(messages)
203
- request_kwargs = self._prepare_request_kwargs(system_message, tools)
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)
204
572
 
205
- if self.mcp_servers is not None:
573
+ if self._has_beta_features(response_format=response_format, tools=tools):
206
574
  assistant_message.metrics.start_timer()
207
575
  provider_response = self.get_client().beta.messages.create(
208
576
  model=self.id,
209
577
  messages=chat_messages, # type: ignore
210
- **self.get_request_params(),
578
+ **request_kwargs,
211
579
  )
212
580
  else:
213
581
  assistant_message.metrics.start_timer()
@@ -247,6 +615,7 @@ class Claude(Model):
247
615
  tools: Optional[List[Dict[str, Any]]] = None,
248
616
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
249
617
  run_response: Optional[RunOutput] = None,
618
+ compress_tool_results: bool = False,
250
619
  ) -> Any:
251
620
  """
252
621
  Stream a response from the Anthropic API.
@@ -262,14 +631,15 @@ class Claude(Model):
262
631
  RateLimitError: If the API rate limit is exceeded
263
632
  APIStatusError: For other API-related errors
264
633
  """
265
- chat_messages, system_message = format_messages(messages)
266
- request_kwargs = self._prepare_request_kwargs(system_message, tools)
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)
267
636
 
268
637
  try:
269
638
  if run_response and run_response.metrics:
270
639
  run_response.metrics.set_time_to_first_token()
271
640
 
272
- if self.mcp_servers is not None:
641
+ # Beta features
642
+ if self._has_beta_features(response_format=response_format, tools=tools):
273
643
  assistant_message.metrics.start_timer()
274
644
  with self.get_client().beta.messages.stream(
275
645
  model=self.id,
@@ -277,7 +647,7 @@ class Claude(Model):
277
647
  **request_kwargs,
278
648
  ) as stream:
279
649
  for chunk in stream:
280
- yield self._parse_provider_response_delta(chunk) # type: ignore
650
+ yield self._parse_provider_response_delta(chunk, response_format=response_format) # type: ignore
281
651
  else:
282
652
  assistant_message.metrics.start_timer()
283
653
  with self.get_client().messages.stream(
@@ -286,7 +656,7 @@ class Claude(Model):
286
656
  **request_kwargs,
287
657
  ) as stream:
288
658
  for chunk in stream: # type: ignore
289
- yield self._parse_provider_response_delta(chunk) # type: ignore
659
+ yield self._parse_provider_response_delta(chunk, response_format=response_format) # type: ignore
290
660
 
291
661
  assistant_message.metrics.stop_timer()
292
662
 
@@ -313,6 +683,7 @@ class Claude(Model):
313
683
  tools: Optional[List[Dict[str, Any]]] = None,
314
684
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
315
685
  run_response: Optional[RunOutput] = None,
686
+ compress_tool_results: bool = False,
316
687
  ) -> ModelResponse:
317
688
  """
318
689
  Send an asynchronous request to the Anthropic API to generate a response.
@@ -321,15 +692,16 @@ class Claude(Model):
321
692
  if run_response and run_response.metrics:
322
693
  run_response.metrics.set_time_to_first_token()
323
694
 
324
- chat_messages, system_message = format_messages(messages)
325
- request_kwargs = self._prepare_request_kwargs(system_message, tools)
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)
326
697
 
327
- if self.mcp_servers is not None:
698
+ # Beta features
699
+ if self._has_beta_features(response_format=response_format, tools=tools):
328
700
  assistant_message.metrics.start_timer()
329
701
  provider_response = await self.get_async_client().beta.messages.create(
330
702
  model=self.id,
331
703
  messages=chat_messages, # type: ignore
332
- **self.get_request_params(),
704
+ **request_kwargs,
333
705
  )
334
706
  else:
335
707
  assistant_message.metrics.start_timer()
@@ -369,6 +741,7 @@ class Claude(Model):
369
741
  tools: Optional[List[Dict[str, Any]]] = None,
370
742
  tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
371
743
  run_response: Optional[RunOutput] = None,
744
+ compress_tool_results: bool = False,
372
745
  ) -> AsyncIterator[ModelResponse]:
373
746
  """
374
747
  Stream an asynchronous response from the Anthropic API.
@@ -385,10 +758,10 @@ class Claude(Model):
385
758
  if run_response and run_response.metrics:
386
759
  run_response.metrics.set_time_to_first_token()
387
760
 
388
- chat_messages, system_message = format_messages(messages)
389
- request_kwargs = self._prepare_request_kwargs(system_message, tools)
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)
390
763
 
391
- if self.mcp_servers is not None:
764
+ if self._has_beta_features(response_format=response_format, tools=tools):
392
765
  assistant_message.metrics.start_timer()
393
766
  async with self.get_async_client().beta.messages.stream(
394
767
  model=self.id,
@@ -396,7 +769,7 @@ class Claude(Model):
396
769
  **request_kwargs,
397
770
  ) as stream:
398
771
  async for chunk in stream:
399
- yield self._parse_provider_response_delta(chunk) # type: ignore
772
+ yield self._parse_provider_response_delta(chunk, response_format=response_format) # type: ignore
400
773
  else:
401
774
  assistant_message.metrics.start_timer()
402
775
  async with self.get_async_client().messages.stream(
@@ -405,7 +778,7 @@ class Claude(Model):
405
778
  **request_kwargs,
406
779
  ) as stream:
407
780
  async for chunk in stream: # type: ignore
408
- yield self._parse_provider_response_delta(chunk) # type: ignore
781
+ yield self._parse_provider_response_delta(chunk, response_format=response_format) # type: ignore
409
782
 
410
783
  assistant_message.metrics.stop_timer()
411
784
 
@@ -430,12 +803,18 @@ class Claude(Model):
430
803
  return tool_call_prompt
431
804
  return None
432
805
 
433
- def _parse_provider_response(self, response: AnthropicMessage, **kwargs) -> ModelResponse:
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:
434
812
  """
435
813
  Parse the Claude response into a ModelResponse.
436
814
 
437
815
  Args:
438
816
  response: Raw response from Anthropic
817
+ response_format: Optional response format for structured output parsing
439
818
 
440
819
  Returns:
441
820
  ModelResponse: Parsed response data
@@ -448,10 +827,32 @@ class Claude(Model):
448
827
  if response.content:
449
828
  for block in response.content:
450
829
  if block.type == "text":
830
+ text_content = block.text
831
+
451
832
  if model_response.content is None:
452
- model_response.content = block.text
833
+ model_response.content = text_content
453
834
  else:
454
- model_response.content += block.text
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}")
455
856
 
456
857
  # Capture citations from the response
457
858
  if block.citations is not None:
@@ -505,6 +906,30 @@ class Claude(Model):
505
906
  if response.usage is not None:
506
907
  model_response.response_usage = self._get_metrics(response.usage)
507
908
 
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
932
+
508
933
  return model_response
509
934
 
510
935
  def _parse_provider_response_delta(
@@ -515,24 +940,29 @@ class Claude(Model):
515
940
  ContentBlockStopEvent,
516
941
  MessageStopEvent,
517
942
  BetaRawContentBlockDeltaEvent,
943
+ BetaRawContentBlockStartEvent,
944
+ ParsedBetaContentBlockStopEvent,
945
+ ParsedBetaMessageStopEvent,
518
946
  ],
947
+ response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
519
948
  ) -> ModelResponse:
520
949
  """
521
950
  Parse the Claude streaming response into ModelProviderResponse objects.
522
951
 
523
952
  Args:
524
953
  response: Raw response chunk from Anthropic
954
+ response_format: Optional response format for structured output parsing
525
955
 
526
956
  Returns:
527
957
  ModelResponse: Iterator of parsed response data
528
958
  """
529
959
  model_response = ModelResponse()
530
960
 
531
- if isinstance(response, ContentBlockStartEvent):
961
+ if isinstance(response, (ContentBlockStartEvent, BetaRawContentBlockStartEvent)):
532
962
  if response.content_block.type == "redacted_reasoning_content":
533
963
  model_response.redacted_reasoning_content = response.content_block.data
534
964
 
535
- if isinstance(response, ContentBlockDeltaEvent):
965
+ if isinstance(response, (ContentBlockDeltaEvent, BetaRawContentBlockDeltaEvent)):
536
966
  # Handle text content
537
967
  if response.delta.type == "text_delta":
538
968
  model_response.content = response.delta.text
@@ -544,11 +974,11 @@ class Claude(Model):
544
974
  "signature": response.delta.signature,
545
975
  }
546
976
 
547
- elif isinstance(response, ContentBlockStopEvent):
977
+ elif isinstance(response, (ContentBlockStopEvent, ParsedBetaContentBlockStopEvent)):
548
978
  if response.content_block.type == "tool_use": # type: ignore
549
979
  tool_use = response.content_block # type: ignore
550
- tool_name = tool_use.name
551
- tool_input = tool_use.input
980
+ tool_name = tool_use.name # type: ignore
981
+ tool_input = tool_use.input # type: ignore
552
982
 
553
983
  function_def = {"name": tool_name}
554
984
  if tool_input:
@@ -558,17 +988,30 @@ class Claude(Model):
558
988
 
559
989
  model_response.tool_calls = [
560
990
  {
561
- "id": tool_use.id,
991
+ "id": tool_use.id, # type: ignore
562
992
  "type": "function",
563
993
  "function": function_def,
564
994
  }
565
995
  ]
566
996
 
567
- # Capture citations from the final response
568
- elif isinstance(response, MessageStopEvent):
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
569
1002
  model_response.content = ""
570
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
+
571
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
572
1015
  citations = getattr(block, "citations", None)
573
1016
  if not citations:
574
1017
  continue
@@ -583,6 +1026,38 @@ class Claude(Model):
583
1026
  DocumentCitation(document_title=citation.document_title, cited_text=citation.cited_text)
584
1027
  )
585
1028
 
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
+
586
1061
  if hasattr(response, "message") and hasattr(response.message, "usage") and response.message.usage is not None: # type: ignore
587
1062
  model_response.response_usage = self._get_metrics(response.message.usage) # type: ignore
588
1063
 
@@ -599,7 +1074,7 @@ class Claude(Model):
599
1074
 
600
1075
  return model_response
601
1076
 
602
- def _get_metrics(self, response_usage: Union[Usage, MessageDeltaUsage]) -> Metrics:
1077
+ def _get_metrics(self, response_usage: Union[Usage, MessageDeltaUsage, BetaUsage]) -> Metrics:
603
1078
  """
604
1079
  Parse the given Anthropic-specific usage into an Agno Metrics object.
605
1080
 
@@ -619,7 +1094,7 @@ class Claude(Model):
619
1094
 
620
1095
  # Anthropic-specific additional fields
621
1096
  if response_usage.server_tool_use:
622
- metrics.provider_metrics = {"server_tool_use": response_usage.server_tool_use}
1097
+ metrics.provider_metrics = {"server_tool_use": response_usage.server_tool_use.model_dump()}
623
1098
  if isinstance(response_usage, Usage):
624
1099
  if response_usage.service_tier:
625
1100
  metrics.provider_metrics = metrics.provider_metrics or {}