-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
Initial Checks
- I confirm that I'm using the latest version of Pydantic AI
- I confirm that I searched for my issue in https://github.com/pydantic/pydantic-ai/issues before opening this issue
Description
Related to issue #2687 and PR #2855
I've been digging into an issue I ran into after upgrading from (my very slightly modified) v1.0.2 to v1.0.3.
It seems there's a regression that causes a crash during streaming whenever the model includes a reasoning step in its response via the OpenAI Responses API.
The specific error I'm seeing is pydantic_ai.exceptions.UnexpectedModelBehavior: Cannot create a ThinkingPart with no content
.
What's Happening
It looks like the crash is triggered by new logic introduced in v1.0.3 to handle the ResponseOutputItemDoneEvent
for a ResponseReasoningItem
.
Here's what I've traced so far:
- In
pydantic_ai/models/openai.py
, inside theOpenAIResponsesStreamedResponse._get_event_iterator
method, there's a new block forResponseOutputItemDoneEvent
pydantic-ai/pydantic_ai_slim/pydantic_ai/models/openai.py
Lines 1372 to 1388 in bee76e6
warnings.warn( # pragma: no cover | |
f'Handling of this item type is not yet implemented. Please report on our GitHub: {chunk}', | |
UserWarning, | |
) | |
elif isinstance(chunk, responses.ResponseOutputItemDoneEvent): | |
if isinstance(chunk.item, responses.ResponseReasoningItem): | |
# Add the signature to the part corresponding to the first summary item | |
signature = chunk.item.encrypted_content | |
yield self._parts_manager.handle_thinking_delta( | |
vendor_part_id=f'{chunk.item.id}-0', | |
id=chunk.item.id, | |
signature=signature, | |
provider_name=self.provider_name if signature else None, | |
) | |
pass | |
-
When this event is for a
ResponseReasoningItem
, the new code callsself._parts_manager.handle_thinking_delta
, passing in asignature
but leaving thecontent
argument asNone
. -
The problem is that in
pydantic_ai/_parts_manager.py
, thehandle_thinking_delta
function has a strict check. If it tries to create a newThinkingPart
(because one doesn't already exist for thatvendor_part_id
), it requires thecontent
to not beNone
([around line 210 in the 1.0.3 release]
pydantic-ai/pydantic_ai_slim/pydantic_ai/_parts_manager.py
Lines 217 to 224 in bee76e6
) | |
self._parts[part_index] = part_delta.apply(existing_thinking_part) | |
return PartDeltaEvent(index=part_index, delta=part_delta) | |
else: | |
raise UnexpectedModelBehavior('Cannot update a ThinkingPart with no content or signature') | |
def handle_tool_call_delta( | |
self, |
This mismatch leads to the UnexpectedModelBehavior
exception, which stops the stream. This doesn't happen in v1.0.2 because that version doesn't have the code to handle the ResponseOutputItemDoneEvent
, so the problematic call is never made.
Possible Fix
It seems like the fix might be to either:
- Make the validation in
handle_thinking_delta
a bit more flexible, allowing aThinkingPart
to be created with just asignature
. - Or, ensure that a
ThinkingPart
is always created with some initial content before theResponseOutputItemDoneEvent
is processed, so the call becomes an update rather than a creation.
Let me know if you need any more info.
Example Code
Python, Pydantic AI & LLM client version
pydantic-ai-slim==1.0.3
openai==1.107.1
ag-ui-protocol==0.1.8