Skip to content

Commit 64a1186

Browse files
authored
fix(anthropic): send ThinkingPart back when using tool calls (#2072)
1 parent cbe0a92 commit 64a1186

File tree

3 files changed

+199
-14
lines changed

3 files changed

+199
-14
lines changed

pydantic_ai_slim/pydantic_ai/models/anthropic.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -342,15 +342,13 @@ async def _map_message(self, messages: list[ModelMessage]) -> tuple[str, list[Be
342342
if response_part.content: # Only add non-empty text
343343
assistant_content_params.append(BetaTextBlockParam(text=response_part.content, type='text'))
344344
elif isinstance(response_part, ThinkingPart):
345-
# NOTE: We don't send ThinkingPart to the providers yet. If you are unsatisfied with this,
346-
# please open an issue. The below code is the code to send thinking to the provider.
347-
# assert response_part.signature is not None, 'Thinking part must have a signature'
348-
# assistant_content_params.append(
349-
# BetaThinkingBlockParam(
350-
# thinking=response_part.content, signature=response_part.signature, type='thinking'
351-
# )
352-
# )
353-
pass
345+
# NOTE: We only send thinking part back for Anthropic, otherwise they raise an error.
346+
if response_part.signature is not None: # pragma: no branch
347+
assistant_content_params.append(
348+
BetaThinkingBlockParam(
349+
thinking=response_part.content, signature=response_part.signature, type='thinking'
350+
)
351+
)
354352
else:
355353
tool_use_block_param = BetaToolUseBlockParam(
356354
id=_guard_tool_call_id(t=response_part),
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
interactions:
2+
- request:
3+
headers:
4+
accept:
5+
- application/json
6+
accept-encoding:
7+
- gzip, deflate
8+
connection:
9+
- keep-alive
10+
content-length:
11+
- '388'
12+
content-type:
13+
- application/json
14+
host:
15+
- api.anthropic.com
16+
method: POST
17+
parsed_body:
18+
max_tokens: 4096
19+
messages:
20+
- content:
21+
- text: What is the largest city in the user country?
22+
type: text
23+
role: user
24+
model: claude-sonnet-4-0
25+
stream: false
26+
thinking:
27+
budget_tokens: 3000
28+
type: enabled
29+
tool_choice:
30+
type: auto
31+
tools:
32+
- description: ''
33+
input_schema:
34+
additionalProperties: false
35+
properties: {}
36+
type: object
37+
name: get_user_country
38+
uri: https://api.anthropic.com/v1/messages?beta=true
39+
response:
40+
headers:
41+
connection:
42+
- keep-alive
43+
content-length:
44+
- '1690'
45+
content-type:
46+
- application/json
47+
strict-transport-security:
48+
- max-age=31536000; includeSubDomains; preload
49+
transfer-encoding:
50+
- chunked
51+
parsed_body:
52+
content:
53+
- signature: EqIECkYIBBgCKkC3YeluAxT1ZvKgL7zILjOUS+XrvAMrNCGhYQkSWCLo5jQ4hXNnnht77qVzdcdMu0YoXmEM/vTGZ2ibi8FSim41EgyvPwDsa4PKH+ZFuzEaDDNjzy/pLEbhbHFgqSIwEba/zTH5lZ6umMgWI/XKQwINUpqIRf2n6DCPpppSbV1VaZJRepT+hCJbPriicNDMKokDakK6RapHz23G6iWyJON4/PLfhUgjLgAxjk8kuVM3pH/i/m2ZpYndCJoIDX2q/UffytbMl8tb4h1bQW7XqOCERGHoF6yXiqgYs+aFaUBRqM6NvWXzPBqSUkX2zvr4FP0fEKbiAawu7JuBGXq5TuXlpRlJKEimlGDySFb5o4mW6jd60NTZaO5ULCdZUleBfnDsosHqDMJohUKyIE5kNEamEw4ZAjdGi3IkJ79L3CBaWUvnRh4e4z2UMqpztFKHvozcziMl0D1dhLI1omF+9RA8ZZYeA+vA2jgIQz6UlSe7rmcZYM0msrLZGNqu5FNYTuU+OMbYsLC6CkRnT6gvcG/7W7QlKFDYY5fpq72LoSyMrXSKJKKlA9q3PEcDvKfGOau3rxjrmEQY9y9WU+Sl/pUHMVME/VfgAPKPPGKiDiMkOa87LqN2FN1mmNVIP9H8c2sSdWmRtssR8t0EKqP2844kTEZKyJImGT/OsSUVuAMeaZkZlEbBXHowqHO5nL0UEdNPPjqwA634CEA/GAE=
54+
thinking: |-
55+
The user is asking about the largest city in "the user country". To answer this question, I need to first determine what country the user is in. I have a function called "get_user_country" that can help me find out the user's country. Once I have that information, I can then tell them what the largest city in that country is.
56+
57+
Let me call the get_user_country function first.
58+
type: thinking
59+
- text: I'll help you find the largest city in your country. Let me first determine which country you're in.
60+
type: text
61+
- id: toolu_01S1p6wo8sLNhu2gNHJMRk7D
62+
input: {}
63+
name: get_user_country
64+
type: tool_use
65+
id: msg_0171WD3hcwFRMzBbm5CNZ8sR
66+
model: claude-sonnet-4-20250514
67+
role: assistant
68+
stop_reason: tool_use
69+
stop_sequence: null
70+
type: message
71+
usage:
72+
cache_creation_input_tokens: 0
73+
cache_read_input_tokens: 0
74+
input_tokens: 397
75+
output_tokens: 157
76+
service_tier: standard
77+
status:
78+
code: 200
79+
message: OK
80+
- request:
81+
headers:
82+
accept:
83+
- application/json
84+
accept-encoding:
85+
- gzip, deflate
86+
connection:
87+
- keep-alive
88+
content-length:
89+
- '1944'
90+
content-type:
91+
- application/json
92+
host:
93+
- api.anthropic.com
94+
method: POST
95+
parsed_body:
96+
max_tokens: 4096
97+
messages:
98+
- content:
99+
- text: What is the largest city in the user country?
100+
type: text
101+
role: user
102+
- content:
103+
- signature: EqIECkYIBBgCKkC3YeluAxT1ZvKgL7zILjOUS+XrvAMrNCGhYQkSWCLo5jQ4hXNnnht77qVzdcdMu0YoXmEM/vTGZ2ibi8FSim41EgyvPwDsa4PKH+ZFuzEaDDNjzy/pLEbhbHFgqSIwEba/zTH5lZ6umMgWI/XKQwINUpqIRf2n6DCPpppSbV1VaZJRepT+hCJbPriicNDMKokDakK6RapHz23G6iWyJON4/PLfhUgjLgAxjk8kuVM3pH/i/m2ZpYndCJoIDX2q/UffytbMl8tb4h1bQW7XqOCERGHoF6yXiqgYs+aFaUBRqM6NvWXzPBqSUkX2zvr4FP0fEKbiAawu7JuBGXq5TuXlpRlJKEimlGDySFb5o4mW6jd60NTZaO5ULCdZUleBfnDsosHqDMJohUKyIE5kNEamEw4ZAjdGi3IkJ79L3CBaWUvnRh4e4z2UMqpztFKHvozcziMl0D1dhLI1omF+9RA8ZZYeA+vA2jgIQz6UlSe7rmcZYM0msrLZGNqu5FNYTuU+OMbYsLC6CkRnT6gvcG/7W7QlKFDYY5fpq72LoSyMrXSKJKKlA9q3PEcDvKfGOau3rxjrmEQY9y9WU+Sl/pUHMVME/VfgAPKPPGKiDiMkOa87LqN2FN1mmNVIP9H8c2sSdWmRtssR8t0EKqP2844kTEZKyJImGT/OsSUVuAMeaZkZlEbBXHowqHO5nL0UEdNPPjqwA634CEA/GAE=
104+
thinking: |-
105+
The user is asking about the largest city in "the user country". To answer this question, I need to first determine what country the user is in. I have a function called "get_user_country" that can help me find out the user's country. Once I have that information, I can then tell them what the largest city in that country is.
106+
107+
Let me call the get_user_country function first.
108+
type: thinking
109+
- text: I'll help you find the largest city in your country. Let me first determine which country you're in.
110+
type: text
111+
- id: toolu_01S1p6wo8sLNhu2gNHJMRk7D
112+
input: {}
113+
name: get_user_country
114+
type: tool_use
115+
role: assistant
116+
- content:
117+
- content: Mexico
118+
is_error: false
119+
tool_use_id: toolu_01S1p6wo8sLNhu2gNHJMRk7D
120+
type: tool_result
121+
role: user
122+
model: claude-sonnet-4-0
123+
stream: false
124+
thinking:
125+
budget_tokens: 3000
126+
type: enabled
127+
tool_choice:
128+
type: auto
129+
tools:
130+
- description: ''
131+
input_schema:
132+
additionalProperties: false
133+
properties: {}
134+
type: object
135+
name: get_user_country
136+
uri: https://api.anthropic.com/v1/messages?beta=true
137+
response:
138+
headers:
139+
connection:
140+
- keep-alive
141+
content-length:
142+
- '769'
143+
content-type:
144+
- application/json
145+
strict-transport-security:
146+
- max-age=31536000; includeSubDomains; preload
147+
transfer-encoding:
148+
- chunked
149+
parsed_body:
150+
content:
151+
- text: "Based on the information that you're in Mexico, the largest city in your country is **Mexico City** (Ciudad
152+
de México). \n\nMexico City is not only the largest city in Mexico but also one of the largest metropolitan areas
153+
in the world, with a metropolitan population of over 21 million people. The city proper has a population of approximately
154+
9 million people and serves as the capital and political, cultural, and economic center of Mexico."
155+
type: text
156+
id: msg_01GrmzEM2LkXfRpLSjXSNMDa
157+
model: claude-sonnet-4-20250514
158+
role: assistant
159+
stop_reason: end_turn
160+
stop_sequence: null
161+
type: message
162+
usage:
163+
cache_creation_input_tokens: 0
164+
cache_read_input_tokens: 0
165+
input_tokens: 567
166+
output_tokens: 94
167+
service_tier: standard
168+
status:
169+
code: 200
170+
message: OK
171+
version: 1

tests/models/test_anthropic.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1356,7 +1356,6 @@ async def test_anthropic_empty_content_filtering(env: TestEnv):
13561356
assert len(anthropic_messages) == 0 # No messages should be added
13571357

13581358

1359-
@pytest.mark.vcr()
13601359
async def test_anthropic_tool_output(allow_model_requests: None, anthropic_api_key: str):
13611360
m = AnthropicModel('claude-3-5-sonnet-latest', provider=AnthropicProvider(api_key=anthropic_api_key))
13621361

@@ -1451,7 +1450,6 @@ async def get_user_country() -> str:
14511450
)
14521451

14531452

1454-
@pytest.mark.vcr()
14551453
async def test_anthropic_text_output_function(allow_model_requests: None, anthropic_api_key: str):
14561454
m = AnthropicModel('claude-3-5-sonnet-latest', provider=AnthropicProvider(api_key=anthropic_api_key))
14571455

@@ -1540,7 +1538,6 @@ async def get_user_country() -> str:
15401538
)
15411539

15421540

1543-
@pytest.mark.vcr()
15441541
async def test_anthropic_prompted_output(allow_model_requests: None, anthropic_api_key: str):
15451542
m = AnthropicModel('claude-3-5-sonnet-latest', provider=AnthropicProvider(api_key=anthropic_api_key))
15461543

@@ -1635,7 +1632,6 @@ async def get_user_country() -> str:
16351632
)
16361633

16371634

1638-
@pytest.mark.vcr()
16391635
async def test_anthropic_prompted_output_multiple(allow_model_requests: None, anthropic_api_key: str):
16401636
m = AnthropicModel('claude-3-5-sonnet-latest', provider=AnthropicProvider(api_key=anthropic_api_key))
16411637

@@ -1695,7 +1691,6 @@ class CountryLanguage(BaseModel):
16951691
)
16961692

16971693

1698-
@pytest.mark.vcr()
16991694
async def test_anthropic_native_output(allow_model_requests: None, anthropic_api_key: str):
17001695
m = AnthropicModel('claude-3-5-sonnet-latest', provider=AnthropicProvider(api_key=anthropic_api_key))
17011696

@@ -1707,3 +1702,24 @@ class CityLocation(BaseModel):
17071702

17081703
with pytest.raises(UserError, match='Structured output is not supported by the model.'):
17091704
await agent.run('What is the largest city in the user country?')
1705+
1706+
1707+
async def test_anthropic_tool_with_thinking(allow_model_requests: None, anthropic_api_key: str):
1708+
"""When using thinking with tool calls in Anthropic, we need to send the thinking part back to the provider.
1709+
1710+
This tests the issue raised in https://github.com/pydantic/pydantic-ai/issues/2040.
1711+
"""
1712+
m = AnthropicModel('claude-sonnet-4-0', provider=AnthropicProvider(api_key=anthropic_api_key))
1713+
settings = AnthropicModelSettings(anthropic_thinking={'type': 'enabled', 'budget_tokens': 3000})
1714+
agent = Agent(m, model_settings=settings)
1715+
1716+
@agent.tool_plain
1717+
async def get_user_country() -> str:
1718+
return 'Mexico'
1719+
1720+
result = await agent.run('What is the largest city in the user country?')
1721+
assert result.output == snapshot("""\
1722+
Based on the information that you're in Mexico, the largest city in your country is **Mexico City** (Ciudad de México). \n\
1723+
1724+
Mexico City is not only the largest city in Mexico but also one of the largest metropolitan areas in the world, with a metropolitan population of over 21 million people. The city proper has a population of approximately 9 million people and serves as the capital and political, cultural, and economic center of Mexico.\
1725+
""")

0 commit comments

Comments
 (0)