Skip to content

Commit b25da57

Browse files
committed
fix coverage and fix cassette
1 parent cbcb783 commit b25da57

File tree

4 files changed

+204
-13
lines changed

4 files changed

+204
-13
lines changed

pydantic_ai_slim/pydantic_ai/models/anthropic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -614,7 +614,7 @@ def _prepare_betas_and_headers(
614614
beta_value = extra_headers['anthropic-beta']
615615
for beta in beta_value.split(','):
616616
beta_stripped = beta.strip()
617-
if beta_stripped:
617+
if beta_stripped: # pragma: no branch
618618
betas.add(beta_stripped)
619619
del extra_headers['anthropic-beta']
620620

tests/models/anthropic/cassettes/test_output/test_anthropic_native_output_multiple_language.yaml

Lines changed: 161 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,18 +90,171 @@ interactions:
9090
connection:
9191
- keep-alive
9292
content-length:
93-
- '130'
93+
- '477'
9494
content-type:
9595
- application/json
96+
retry-after:
97+
- '18'
9698
strict-transport-security:
9799
- max-age=31536000; includeSubDomains; preload
100+
transfer-encoding:
101+
- chunked
98102
parsed_body:
99-
error:
100-
message: invalid x-api-key
101-
type: authentication_error
102-
request_id: req_011CVKJbTXMN68Pzm15QEJpK
103-
type: error
103+
content:
104+
- id: toolu_019JQjJerVPXLfa3AcmLrjuH
105+
input: {}
106+
name: get_user_country
107+
type: tool_use
108+
id: msg_01DhgrVv5EJDsccFZ587B9bN
109+
model: claude-sonnet-4-5-20250929
110+
role: assistant
111+
stop_reason: tool_use
112+
stop_sequence: null
113+
type: message
114+
usage:
115+
cache_creation:
116+
ephemeral_1h_input_tokens: 0
117+
ephemeral_5m_input_tokens: 0
118+
cache_creation_input_tokens: 0
119+
cache_read_input_tokens: 0
120+
input_tokens: 1047
121+
output_tokens: 38
122+
service_tier: standard
123+
status:
124+
code: 200
125+
message: OK
126+
- request:
127+
headers:
128+
accept:
129+
- application/json
130+
accept-encoding:
131+
- gzip, deflate
132+
connection:
133+
- keep-alive
134+
content-length:
135+
- '1424'
136+
content-type:
137+
- application/json
138+
host:
139+
- api.anthropic.com
140+
method: POST
141+
parsed_body:
142+
max_tokens: 4096
143+
messages:
144+
- content:
145+
- text: What language is spoken in the user country?
146+
type: text
147+
role: user
148+
- content:
149+
- id: toolu_019JQjJerVPXLfa3AcmLrjuH
150+
input: {}
151+
name: get_user_country
152+
type: tool_use
153+
role: assistant
154+
- content:
155+
- content: France
156+
is_error: false
157+
tool_use_id: toolu_019JQjJerVPXLfa3AcmLrjuH
158+
type: tool_result
159+
role: user
160+
model: claude-sonnet-4-5
161+
output_format:
162+
schema:
163+
additionalProperties: false
164+
properties:
165+
result:
166+
anyOf:
167+
- additionalProperties: false
168+
description: A city and its country.
169+
properties:
170+
data:
171+
additionalProperties: false
172+
properties:
173+
city:
174+
type: string
175+
country:
176+
type: string
177+
required:
178+
- city
179+
- country
180+
type: object
181+
kind:
182+
description: '{const: CityLocation}'
183+
type: string
184+
required:
185+
- kind
186+
- data
187+
type: object
188+
- additionalProperties: false
189+
properties:
190+
data:
191+
additionalProperties: false
192+
properties:
193+
country:
194+
type: string
195+
language:
196+
type: string
197+
required:
198+
- country
199+
- language
200+
type: object
201+
kind:
202+
description: '{const: CountryLanguage}'
203+
type: string
204+
required:
205+
- kind
206+
- data
207+
type: object
208+
required:
209+
- result
210+
type: object
211+
type: json_schema
212+
stream: false
213+
tool_choice:
214+
type: auto
215+
tools:
216+
- description: ''
217+
input_schema:
218+
additionalProperties: false
219+
properties: {}
220+
type: object
221+
name: get_user_country
222+
strict: true
223+
uri: https://api.anthropic.com/v1/messages?beta=true
224+
response:
225+
headers:
226+
connection:
227+
- keep-alive
228+
content-length:
229+
- '509'
230+
content-type:
231+
- application/json
232+
retry-after:
233+
- '15'
234+
strict-transport-security:
235+
- max-age=31536000; includeSubDomains; preload
236+
transfer-encoding:
237+
- chunked
238+
parsed_body:
239+
content:
240+
- text: '{"result":{"kind":"CountryLanguage","data":{"country":"France","language":"French"}}}'
241+
type: text
242+
id: msg_01LeExcS3t3YwwYs1tram43F
243+
model: claude-sonnet-4-5-20250929
244+
role: assistant
245+
stop_reason: end_turn
246+
stop_sequence: null
247+
type: message
248+
usage:
249+
cache_creation:
250+
ephemeral_1h_input_tokens: 0
251+
ephemeral_5m_input_tokens: 0
252+
cache_creation_input_tokens: 0
253+
cache_read_input_tokens: 0
254+
input_tokens: 1099
255+
output_tokens: 26
256+
service_tier: standard
104257
status:
105-
code: 401
106-
message: Unauthorized
258+
code: 200
259+
message: OK
107260
version: 1

tests/models/anthropic/test_output.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -359,12 +359,31 @@ async def get_user_country() -> str:
359359
result = await agent.run('What is the capital of the user country?')
360360
# Should return CityLocation since we asked about capital
361361
assert isinstance(result.output, city_location_schema | country_language_schema)
362-
if isinstance(result.output, city_location_schema):
362+
if isinstance(result.output, city_location_schema): # pragma: no branch
363363
assert result.output.city == 'Paris' # type: ignore[attr-defined]
364364
assert result.output.country == 'France' # type: ignore[attr-defined]
365-
else: # pragma: no cover
366-
# This branch is not hit in this test, but we keep the structure for completeness
367-
pass
365+
366+
367+
async def test_anthropic_native_output_multiple_language(
368+
allow_model_requests: None,
369+
anthropic_sonnet_4_5: AnthropicModel,
370+
city_location_schema: type[BaseModel],
371+
country_language_schema: type[BaseModel],
372+
make_agent: MakeAgentType,
373+
):
374+
"""Test native output with union returns the second schema type."""
375+
agent = make_agent(anthropic_sonnet_4_5, output_type=NativeOutput([city_location_schema, country_language_schema]))
376+
377+
@agent.tool_plain
378+
async def get_user_country() -> str:
379+
return 'France'
380+
381+
result = await agent.run('What language is spoken in the user country?')
382+
# Should return CountryLanguage since we asked about language
383+
assert isinstance(result.output, city_location_schema | country_language_schema)
384+
if isinstance(result.output, country_language_schema):
385+
assert result.output.country == 'France' # type: ignore[attr-defined]
386+
assert result.output.language == 'French' # type: ignore[attr-defined]
368387

369388

370389
async def test_anthropic_auto_mode_sonnet_4_5(
@@ -375,6 +394,7 @@ async def test_anthropic_auto_mode_sonnet_4_5(
375394
):
376395
"""Test auto mode with sonnet-4.5 (should use native output automatically)."""
377396
agent = make_agent(anthropic_sonnet_4_5, output_type=city_location_schema)
397+
assert agent.model.profile.supports_json_schema_output # pyright: ignore[reportUnknownMemberType,reportAttributeAccessIssue,reportOptionalMemberAccess]
378398

379399
result = await agent.run('What is the capital of France?')
380400
assert result.output == snapshot(city_location_schema(city='Paris', country='France'))
@@ -388,6 +408,7 @@ async def test_anthropic_auto_mode_sonnet_4_0(
388408
):
389409
"""Test auto mode with sonnet-4.0 (should fall back to prompted output)."""
390410
agent = make_agent(anthropic_sonnet_4_0, output_type=city_location_schema)
411+
assert agent.model.profile.supports_json_schema_output is False # pyright: ignore[reportUnknownMemberType,reportAttributeAccessIssue,reportOptionalMemberAccess]
391412

392413
result = await agent.run('What is the capital of France?')
393414
assert result.output == snapshot(city_location_schema(city='Paris', country='France'))

tests/models/test_anthropic.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6518,6 +6518,23 @@ async def test_anthropic_model_usage_limit_not_exceeded(
65186518
)
65196519

65206520

6521+
async def test_anthropic_count_tokens_with_mock(allow_model_requests: None):
6522+
"""Test that count_tokens is called on the mock client."""
6523+
c = completion_message(
6524+
[BetaTextBlock(text='hello world', type='text')], BetaUsage(input_tokens=5, output_tokens=10)
6525+
)
6526+
mock_client = MockAnthropic.create_mock(c)
6527+
m = AnthropicModel('claude-haiku-4-5', provider=AnthropicProvider(anthropic_client=mock_client))
6528+
agent = Agent(m)
6529+
6530+
result = await agent.run('hello', usage_limits=UsageLimits(input_tokens_limit=20, count_tokens_before_request=True))
6531+
assert result.output == 'hello world'
6532+
assert len(mock_client.chat_completion_kwargs) == 2 # type: ignore
6533+
count_tokens_kwargs = mock_client.chat_completion_kwargs[0] # type: ignore
6534+
assert 'model' in count_tokens_kwargs
6535+
assert 'messages' in count_tokens_kwargs
6536+
6537+
65216538
@pytest.mark.vcr()
65226539
async def test_anthropic_count_tokens_error(allow_model_requests: None, anthropic_api_key: str):
65236540
"""Test that errors convert to ModelHTTPError."""

0 commit comments

Comments
 (0)