Skip to content

Commit f2646de

Browse files
authored
fix: update ThinkingPart when delta contains signature (#2012)
1 parent 79561a4 commit f2646de

File tree

2 files changed

+45
-2
lines changed

2 files changed

+45
-2
lines changed

pydantic_ai_slim/pydantic_ai/messages.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -763,7 +763,9 @@ def apply(self, part: ModelResponsePart | ThinkingPartDelta) -> ThinkingPart | T
763763
ValueError: If `part` is not a `ThinkingPart`.
764764
"""
765765
if isinstance(part, ThinkingPart):
766-
return replace(part, content=part.content + self.content_delta if self.content_delta else None)
766+
new_content = part.content + self.content_delta if self.content_delta else part.content
767+
new_signature = self.signature_delta if self.signature_delta is not None else part.signature
768+
return replace(part, content=new_content, signature=new_signature)
767769
elif isinstance(part, ThinkingPartDelta):
768770
if self.content_delta is None and self.signature_delta is None:
769771
raise ValueError('Cannot apply ThinkingPartDelta with no content or signature')

tests/test_thinking_part.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
from __future__ import annotations as _annotations
22

33
import pytest
4+
from inline_snapshot import snapshot
45

56
from pydantic_ai._thinking_part import split_content_into_text_and_thinking
6-
from pydantic_ai.messages import ModelResponsePart, TextPart, ThinkingPart
7+
from pydantic_ai.messages import ModelResponsePart, TextPart, ThinkingPart, ThinkingPartDelta
78

89

910
@pytest.mark.parametrize(
@@ -26,3 +27,43 @@
2627
)
2728
def test_split_content_into_text_and_thinking(content: str, parts: list[ModelResponsePart]):
2829
assert split_content_into_text_and_thinking(content) == parts
30+
31+
32+
def test_thinking_part_delta_applies_both_content_and_signature():
33+
thinking_part = ThinkingPart(content='Initial content', signature='initial_sig')
34+
delta = ThinkingPartDelta(content_delta=' added', signature_delta='new_sig')
35+
36+
result = delta.apply(thinking_part)
37+
38+
# The content is appended, and the signature is updated.
39+
assert result == snapshot(ThinkingPart(content='Initial content added', signature='new_sig'))
40+
41+
42+
def test_thinking_part_delta_applies_signature_only():
43+
thinking_part = ThinkingPart(content='Initial content', signature='initial_sig')
44+
delta_sig_only = ThinkingPartDelta(content_delta=None, signature_delta='sig_only')
45+
46+
result_sig_only = delta_sig_only.apply(thinking_part)
47+
48+
# The content is unchanged, and the signature is updated.
49+
assert result_sig_only == snapshot(ThinkingPart(content='Initial content', signature='sig_only'))
50+
51+
52+
def test_thinking_part_delta_applies_content_only_preserves_signature():
53+
thinking_part = ThinkingPart(content='Initial content', signature='initial_sig')
54+
delta_content_only = ThinkingPartDelta(content_delta=' more', signature_delta=None)
55+
56+
result_content_only = delta_content_only.apply(thinking_part)
57+
58+
# The content is appended, and the signature is preserved.
59+
assert result_content_only == snapshot(ThinkingPart(content='Initial content more', signature='initial_sig'))
60+
61+
62+
def test_thinking_part_delta_applies_to_part_with_none_signature():
63+
thinking_part_no_sig = ThinkingPart(content='No sig content', signature=None)
64+
delta_to_none_sig = ThinkingPartDelta(content_delta=' extra', signature_delta='added_sig')
65+
66+
result_none_sig = delta_to_none_sig.apply(thinking_part_no_sig)
67+
68+
# The content is appended, and the signature is updated.
69+
assert result_none_sig == snapshot(ThinkingPart(content='No sig content extra', signature='added_sig'))

0 commit comments

Comments
 (0)