1515
1616from collections .abc import Hashable
1717from dataclasses import dataclass , field , replace
18- from typing import Any , Literal , Union , overload
18+ from typing import Any , Union
1919
2020from pydantic_ai ._thinking_part import END_THINK_TAG , START_THINK_TAG
2121from pydantic_ai .exceptions import UnexpectedModelBehavior
@@ -67,23 +67,6 @@ def get_parts(self) -> list[ModelResponsePart]:
6767 """
6868 return [p for p in self ._parts if not isinstance (p , ToolCallPartDelta )]
6969
70- @overload
71- def handle_text_delta (
72- self ,
73- * ,
74- vendor_part_id : VendorId | None ,
75- content : str ,
76- ) -> ModelResponseStreamEvent : ...
77-
78- @overload
79- def handle_text_delta (
80- self ,
81- * ,
82- vendor_part_id : VendorId ,
83- content : str ,
84- extract_think_tags : Literal [True ],
85- ) -> ModelResponseStreamEvent | None : ...
86-
8770 def handle_text_delta (
8871 self ,
8972 * ,
@@ -105,7 +88,9 @@ def handle_text_delta(
10588 extract_think_tags: Whether to extract `<think>` tags from the text content and handle them as thinking parts.
10689
10790 Returns:
108- A `PartStartEvent` if a new part was created, or a `PartDeltaEvent` if an existing part was updated.
91+ - A `PartStartEvent` if a new part was created.
92+ - A `PartDeltaEvent` if an existing part was updated.
93+ - `None` if no new event is emitted (e.g., the first text part was all whitespace).
10994
11095 Raises:
11196 UnexpectedModelBehavior: If attempting to apply text content to a part that is not a TextPart.
@@ -144,6 +129,12 @@ def handle_text_delta(
144129 return self .handle_thinking_delta (vendor_part_id = vendor_part_id , content = '' )
145130
146131 if existing_text_part_and_index is None :
132+ # If the first text delta is all whitespace, don't emit a new part yet.
133+ # This is a workaround for models that emit `<think>\n</think>\n\n` ahead of tool calls (e.g. Ollama + Qwen3),
134+ # which we don't want to end up treating as a final result.
135+ if content .isspace ():
136+ return None
137+
147138 # There is no existing text part that should be updated, so create a new one
148139 new_part_index = len (self ._parts )
149140 part = TextPart (content = content )
0 commit comments