2525AsyncAnthropicClient : TypeAlias = AsyncAnthropic | AsyncAnthropicBedrock | AsyncAnthropicVertex
2626
2727
28+ @dataclass (init = False )
29+ class AnthropicJsonSchemaTransformer (JsonSchemaTransformer ):
30+ """Transforms schemas to the subset supported by Anthropic structured outputs.
31+
32+ Transformation is applied when:
33+ - `NativeOutput` is used as the `output_type` of the Agent
34+ - `strict=True` is set on the `Tool`
35+
36+ The behavior of this transformer differs from the OpenAI one in that it sets `Tool.strict=False` by default when not explicitly set to True.
37+
38+ Example:
39+ ```python
40+ from pydantic_ai import Agent
41+
42+ agent = Agent('anthropic:claude-sonnet-4-5')
43+
44+ @agent.tool_plain # -> defaults to strict=False
45+ def my_tool(x: str) -> dict[str, int]:
46+ ...
47+ ```
48+
49+ Anthropic's SDK `transform_schema()` automatically:
50+ - Adds `additionalProperties: false` to all objects (required by API)
51+ - Removes unsupported constraints (minLength, pattern, etc.)
52+ - Moves removed constraints to description field
53+ - Removes title and $schema fields
54+ """
55+
56+ def walk (self ) -> JsonSchema :
57+ from anthropic import transform_schema
58+
59+ schema = super ().walk ()
60+
61+ # The caller (pydantic_ai.models._customize_tool_def or _customize_output_object) coalesces
62+ # - output_object.strict = self.is_strict_compatible
63+ # - tool_def.strict = self.is_strict_compatible
64+ # the reason we don't default to `strict=True` is that the transformation could be lossy
65+ # so in order to change the behavior (default to True), we need to come up with logic that will check for lossiness
66+ # https://github.com/pydantic/pydantic-ai/issues/3541
67+ self .is_strict_compatible = self .strict is True # not compatible when strict is False/None
68+
69+ if self .strict is True :
70+ from anthropic import transform_schema
71+
72+ return transform_schema (schema )
73+ else :
74+ return schema
75+
76+ def transform (self , schema : JsonSchema ) -> JsonSchema :
77+ schema .pop ('title' , None )
78+ schema .pop ('$schema' , None )
79+ return schema
80+
81+
2882class AnthropicProvider (Provider [AsyncAnthropicClient ]):
2983 """Provider for Anthropic API."""
3084
@@ -41,7 +95,7 @@ def client(self) -> AsyncAnthropicClient:
4195 return self ._client
4296
4397 def model_profile (self , model_name : str ) -> ModelProfile | None :
44- return anthropic_model_profile (model_name )
98+ return anthropic_model_profile (model_name , AnthropicJsonSchemaTransformer )
4599
46100 @overload
47101 def __init__ (self , * , anthropic_client : AsyncAnthropicClient | None = None ) -> None : ...
@@ -85,57 +139,3 @@ def __init__(
85139 else :
86140 http_client = cached_async_http_client (provider = 'anthropic' )
87141 self ._client = AsyncAnthropic (api_key = api_key , base_url = base_url , http_client = http_client )
88-
89-
90- @dataclass (init = False )
91- class AnthropicJsonSchemaTransformer (JsonSchemaTransformer ):
92- """Transforms schemas to the subset supported by Anthropic structured outputs.
93-
94- Transformation is applied when:
95- - `NativeOutput` is used as the `output_type` of the Agent
96- - `strict=True` is set on the `Tool`
97-
98- The behavior of this transformer differs from the OpenAI one in that it sets `Tool.strict=False` by default when not explicitly set to True.
99-
100- Example:
101- ```python
102- from pydantic_ai import Agent
103-
104- agent = Agent('anthropic:claude-sonnet-4-5')
105-
106- @agent.tool_plain # -> defaults to strict=False
107- def my_tool(x: str) -> dict[str, int]:
108- ...
109- ```
110-
111- Anthropic's SDK `transform_schema()` automatically:
112- - Adds `additionalProperties: false` to all objects (required by API)
113- - Removes unsupported constraints (minLength, pattern, etc.)
114- - Moves removed constraints to description field
115- - Removes title and $schema fields
116- """
117-
118- def walk (self ) -> JsonSchema :
119- from anthropic import transform_schema
120-
121- schema = super ().walk ()
122-
123- # The caller (pydantic_ai.models._customize_tool_def or _customize_output_object) coalesces
124- # - output_object.strict = self.is_strict_compatible
125- # - tool_def.strict = self.is_strict_compatible
126- # the reason we don't default to `strict=True` is that the transformation could be lossy
127- # so in order to change the behavior (default to True), we need to come up with logic that will check for lossiness
128- # https://github.com/pydantic/pydantic-ai/issues/3541
129- self .is_strict_compatible = self .strict is True # not compatible when strict is False/None
130-
131- if self .strict is True :
132- from anthropic import transform_schema
133-
134- return transform_schema (schema )
135- else :
136- return schema
137-
138- def transform (self , schema : JsonSchema ) -> JsonSchema :
139- schema .pop ('title' , None )
140- schema .pop ('$schema' , None )
141- return schema
0 commit comments