Skip to content

Commit b0d3ebd

Browse files
authored
Generate ToolCallPart.tool_call_id when OpenAI-compatible API returned an empty string (#1892)
1 parent b201dcd commit b0d3ebd

File tree

3 files changed

+184
-1
lines changed

3 files changed

+184
-1
lines changed

pydantic_ai_slim/pydantic_ai/models/openai.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,9 @@ def _process_response(self, response: chat.ChatCompletion) -> ModelResponse:
334334
items.append(TextPart(choice.message.content))
335335
if choice.message.tool_calls is not None:
336336
for c in choice.message.tool_calls:
337-
items.append(ToolCallPart(c.function.name, c.function.arguments, tool_call_id=c.id))
337+
part = ToolCallPart(c.function.name, c.function.arguments, tool_call_id=c.id)
338+
part.tool_call_id = _guard_tool_call_id(part)
339+
items.append(part)
338340
return ModelResponse(
339341
items,
340342
usage=_map_usage(response),
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
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+
- '326'
12+
content-type:
13+
- application/json
14+
host:
15+
- generativelanguage.googleapis.com
16+
method: POST
17+
parsed_body:
18+
messages:
19+
- content: What is the current time?
20+
role: user
21+
model: gemini-2.5-pro-preview-05-06
22+
stream: false
23+
tool_choice: auto
24+
tools:
25+
- function:
26+
description: Get the current time.
27+
name: get_current_time
28+
parameters:
29+
additionalProperties: false
30+
properties: {}
31+
type: object
32+
type: function
33+
uri: https://generativelanguage.googleapis.com/v1beta/openai/chat/completions
34+
response:
35+
headers:
36+
alt-svc:
37+
- h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
38+
content-length:
39+
- '1166'
40+
content-type:
41+
- application/json
42+
server-timing:
43+
- gfet4t7; dur=1609
44+
transfer-encoding:
45+
- chunked
46+
vary:
47+
- Origin
48+
- X-Origin
49+
- Referer
50+
parsed_body:
51+
choices:
52+
- finish_reason: tool_calls
53+
index: 0
54+
message:
55+
extra_content:
56+
google:
57+
thought: true
58+
thought_signature: AVSoXO4AzAXs7GGvOY63fp8CwJK3yR8HbUPhxhfN2HaPvJnscmZCkaWvckz5NL3nIMK+si/baQcsM2Q8ME9V1RQrb3w1IKceWfjO3kHPL11odY/p6Us4GkkvJqKU/OgUnbAMbuvNdX1pyXWZUrQ7WyXZ5F4mjbxBSCLiVOTdFlK53zn+ajq5JIuG9AYHgwE/sJxUUpvNd6RcWvZR3fQb8gufjCspiO2ZdInRcdGsz/+XftFHxFbXkdtCRAw74AtjlN5osb+KgDYojdohKIEit9DcTBe7hI7oEHWMfnqYSgGrrad4FJpNB3jXmSFevE2iYYKUBzWvxJNj8fIYrCC0g4rJ1aJvuoU=
59+
role: assistant
60+
thought_signature: AVSoXO4AzAXs7GGvOY63fp8CwJK3yR8HbUPhxhfN2HaPvJnscmZCkaWvckz5NL3nIMK+si/baQcsM2Q8ME9V1RQrb3w1IKceWfjO3kHPL11odY/p6Us4GkkvJqKU/OgUnbAMbuvNdX1pyXWZUrQ7WyXZ5F4mjbxBSCLiVOTdFlK53zn+ajq5JIuG9AYHgwE/sJxUUpvNd6RcWvZR3fQb8gufjCspiO2ZdInRcdGsz/+XftFHxFbXkdtCRAw74AtjlN5osb+KgDYojdohKIEit9DcTBe7hI7oEHWMfnqYSgGrrad4FJpNB3jXmSFevE2iYYKUBzWvxJNj8fIYrCC0g4rJ1aJvuoU=
61+
tool_calls:
62+
- function:
63+
arguments: '{}'
64+
name: get_current_time
65+
id: ''
66+
type: function
67+
created: 1748902365
68+
id: 3SE-aKjdCcCEz7IPxpqjCA
69+
model: gemini-2.5-pro-preview-05-06
70+
object: chat.completion
71+
usage:
72+
completion_tokens: 12
73+
prompt_tokens: 35
74+
total_tokens: 109
75+
status:
76+
code: 200
77+
message: OK
78+
- request:
79+
headers:
80+
accept:
81+
- application/json
82+
accept-encoding:
83+
- gzip, deflate
84+
connection:
85+
- keep-alive
86+
content-length:
87+
- '575'
88+
content-type:
89+
- application/json
90+
host:
91+
- generativelanguage.googleapis.com
92+
method: POST
93+
parsed_body:
94+
messages:
95+
- content: What is the current time?
96+
role: user
97+
- role: assistant
98+
tool_calls:
99+
- function:
100+
arguments: '{}'
101+
name: get_current_time
102+
id: pyd_ai_cee885c699414386a7e14b7ec43cadbc
103+
type: function
104+
- content: Noon
105+
role: tool
106+
tool_call_id: pyd_ai_cee885c699414386a7e14b7ec43cadbc
107+
model: gemini-2.5-pro-preview-05-06
108+
stream: false
109+
tool_choice: auto
110+
tools:
111+
- function:
112+
description: Get the current time.
113+
name: get_current_time
114+
parameters:
115+
additionalProperties: false
116+
properties: {}
117+
type: object
118+
type: function
119+
uri: https://generativelanguage.googleapis.com/v1beta/openai/chat/completions
120+
response:
121+
headers:
122+
alt-svc:
123+
- h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
124+
content-length:
125+
- '755'
126+
content-type:
127+
- application/json
128+
server-timing:
129+
- gfet4t7; dur=1097
130+
transfer-encoding:
131+
- chunked
132+
vary:
133+
- Origin
134+
- X-Origin
135+
- Referer
136+
parsed_body:
137+
choices:
138+
- finish_reason: stop
139+
index: 0
140+
message:
141+
content: The current time is Noon.
142+
extra_content:
143+
google:
144+
thought: true
145+
thought_signature: AVSoXO4/lu90Bn7IxVcWAjD6KH3ZHMmsCX1tnPJERDI6SZb63hrSEtmJT/v+sn2SzlecMoXBVmtcrd3keFszUgDpLjFm1gB+uMzLS1IqPdEAh+m5S71k1hfStNMFen63UnphYHWt4UrjVHXckysRLVJjCuMmE01hQXcVh9b3YXvfWfZEFA==
146+
role: assistant
147+
thought_signature: AVSoXO4/lu90Bn7IxVcWAjD6KH3ZHMmsCX1tnPJERDI6SZb63hrSEtmJT/v+sn2SzlecMoXBVmtcrd3keFszUgDpLjFm1gB+uMzLS1IqPdEAh+m5S71k1hfStNMFen63UnphYHWt4UrjVHXckysRLVJjCuMmE01hQXcVh9b3YXvfWfZEFA==
148+
created: 1748902366
149+
id: 3iE-aNK3EIGJz7IPt_mYoAs
150+
model: gemini-2.5-pro-preview-05-06
151+
object: chat.completion
152+
usage:
153+
completion_tokens: 6
154+
prompt_tokens: 66
155+
total_tokens: 100
156+
status:
157+
code: 200
158+
message: OK
159+
version: 1

tests/models/test_openai.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1710,3 +1710,25 @@ def test_model_profile_strict_not_supported():
17101710
},
17111711
}
17121712
)
1713+
1714+
1715+
@pytest.mark.vcr
1716+
async def test_compatible_api_with_tool_calls_without_id(allow_model_requests: None, gemini_api_key: str):
1717+
provider = OpenAIProvider(
1718+
openai_client=AsyncOpenAI(
1719+
base_url='https://generativelanguage.googleapis.com/v1beta/openai/',
1720+
api_key=gemini_api_key,
1721+
)
1722+
)
1723+
1724+
model = OpenAIModel('gemini-2.5-pro-preview-05-06', provider=provider)
1725+
1726+
agent = Agent(model)
1727+
1728+
@agent.tool_plain
1729+
def get_current_time() -> str:
1730+
"""Get the current time."""
1731+
return 'Noon'
1732+
1733+
response = await agent.run('What is the current time?')
1734+
assert response.output == snapshot('The current time is Noon.')

0 commit comments

Comments
 (0)