Skip to content

ThinkingPart not handling separate OpenAI thinking events #2149

@omer-go

Description

@omer-go

Initial Checks

Description

Another issue I've spotted is with the ThinkingPart. When OpenAi streams the thinking events they create a thinking part and then they split it into separate thinking parts where each part starts and ends, but pydantic_ai doesn't maintain this separation so for example when there's more than one thinking part we will get something like this:

2025-07-07 12:48:18,418 - INFO - Part delta: PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' package'))
2025-07-07 12:48:18,419 - INFO - Part delta: PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' everything'))
2025-07-07 12:48:18,473 - INFO - Part delta: PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' into'))
2025-07-07 12:48:18,474 - INFO - Part delta: PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' a'))
2025-07-07 12:48:18,475 - INFO - Part delta: PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' JSON'))
2025-07-07 12:48:18,542 - INFO - Part delta: PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' structure'))
2025-07-07 12:48:18,544 - INFO - Part delta: PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' as'))
2025-07-07 12:48:18,546 - INFO - Part delta: PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' requested'))
2025-07-07 12:48:18,553 - INFO - Part delta: PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='.'))
2025-07-07 12:48:20,609 - INFO - Part delta: PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='**Form'))
2025-07-07 12:48:20,612 - INFO - Part delta: PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='ulating'))
2025-07-07 12:48:20,670 - INFO - Part delta: PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' the'))
2025-07-07 12:48:20,670 - INFO - Part delta: PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' summary'))
2025-07-07 12:48:20,734 - INFO - Part delta: PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta="**\n\nI'm"))
2025-07-07 12:48:20,736 - INFO - Part delta: PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' counting'))
2025-07-07 12:48:20,738 - INFO - Part delta: PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' words'))
2025-07-07 12:48:20,785 - INFO - Part delta: PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' to'))
2025-07-07 12:48:20,786 - INFO - Part delta: PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' ensure'))

You can see that the "**Form" chunk is the start of a new thinking component, but when streaming there's no "\n\n" before it because OpenAI has built-in separation for these events.
Look here:
https://platform.openai.com/docs/api-reference/responses-streaming/response/reasoning_summary_part
The could be several reasoning_summary events under the same reasoning_summmary part that each will start with a delta event and end with a done event, which we are missing here.

Here's your response (from @DouweM):

https://pydanticlogfire.slack.com/archives/C083V7PMHHA/p1751909463414199?thread_ts=1751559059.148199&cid=C083V7PMHHA

Example Code

Python, Pydantic AI & LLM client version

Python 3.11
Pydantic 0.3.5
OpenAI

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions