Skip to content

Commit a19e73d

Browse files
fix(anthropic): avoid Pydantic warnings for message_stop events (#2044)
Co-authored-by: Strands Agent <217235299+strands-agent@users.noreply.github.com>
1 parent 1682a0c commit a19e73d

2 files changed

Lines changed: 65 additions & 1 deletion

File tree

src/strands/models/anthropic.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,15 @@ async def stream(
407407
logger.debug("got response from model")
408408
async for event in stream:
409409
if event.type in AnthropicModel.EVENT_TYPES:
410-
yield self.format_chunk(event.model_dump())
410+
if event.type == "message_stop":
411+
# Build dict directly to avoid Pydantic serialization warnings
412+
# when the message contains ParsedTextBlock objects (issue #1746)
413+
yield self.format_chunk({
414+
"type": "message_stop",
415+
"message": {"stop_reason": event.message.stop_reason},
416+
})
417+
else:
418+
yield self.format_chunk(event.model_dump())
411419

412420
usage = event.message.usage # type: ignore
413421
yield self.format_chunk({"type": "metadata", "usage": usage.model_dump()})

tests/strands/models/test_anthropic.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import logging
22
import unittest.mock
3+
import warnings
34

45
import anthropic
56
import pydantic
@@ -811,6 +812,7 @@ async def test_structured_output(anthropic_client, model, test_output_model_cls,
811812
),
812813
unittest.mock.Mock(
813814
type="message_stop",
815+
message=unittest.mock.Mock(stop_reason="tool_use"),
814816
model_dump=unittest.mock.Mock(
815817
return_value={"type": "message_stop", "message": {"stop_reason": "tool_use"}}
816818
),
@@ -933,3 +935,57 @@ def test_format_request_filters_location_source_document(model, model_id, max_to
933935
]
934936
assert tru_request["messages"] == exp_messages
935937
assert "Location sources are not supported by Anthropic" in caplog.text
938+
939+
940+
@pytest.mark.asyncio
941+
async def test_stream_message_stop_no_pydantic_warnings(anthropic_client, model, agenerator, alist):
942+
"""Verify no Pydantic serialization warnings are emitted for message_stop events.
943+
944+
Regression test for https://github.com/strands-agents/sdk-python/issues/1746.
945+
"""
946+
# Create a mock message_stop event where model_dump() would emit warnings
947+
# The key is that the event has a .message attribute with .stop_reason
948+
mock_message_stop = unittest.mock.Mock()
949+
mock_message_stop.type = "message_stop"
950+
mock_message_stop.message = unittest.mock.Mock()
951+
mock_message_stop.message.stop_reason = "end_turn"
952+
953+
# Make model_dump() emit a warning to simulate the problematic behavior
954+
def model_dump_with_warning():
955+
warnings.warn(
956+
"PydanticSerializationUnexpectedValue(Expected `ParsedTextBlock[TypeVar]`)",
957+
UserWarning,
958+
stacklevel=2,
959+
)
960+
return {"type": mock_message_stop.type, "message": {"stop_reason": mock_message_stop.message.stop_reason}}
961+
962+
mock_message_stop.model_dump = model_dump_with_warning
963+
964+
mock_event_usage = unittest.mock.Mock(
965+
message=unittest.mock.Mock(
966+
usage=unittest.mock.Mock(
967+
model_dump=lambda: {"input_tokens": 1, "output_tokens": 2},
968+
)
969+
),
970+
)
971+
972+
mock_context = unittest.mock.AsyncMock()
973+
mock_context.__aenter__.return_value = agenerator([mock_message_stop, mock_event_usage])
974+
anthropic_client.messages.stream.return_value = mock_context
975+
976+
messages = [{"role": "user", "content": [{"text": "hello"}]}]
977+
978+
# Capture warnings during streaming
979+
with warnings.catch_warnings(record=True) as caught_warnings:
980+
warnings.simplefilter("always")
981+
response = model.stream(messages, None, None)
982+
events = await alist(response)
983+
984+
# Verify no Pydantic serialization warnings were emitted
985+
pydantic_warnings = [
986+
w for w in caught_warnings if "PydanticSerializationUnexpectedValue" in str(w.message)
987+
]
988+
assert len(pydantic_warnings) == 0, f"Unexpected Pydantic warnings: {pydantic_warnings}"
989+
990+
# Verify the message_stop event was still processed correctly
991+
assert {"messageStop": {"stopReason": mock_message_stop.message.stop_reason}} in events

0 commit comments

Comments
 (0)