|
1 | 1 | import logging |
2 | 2 | import unittest.mock |
| 3 | +import warnings |
3 | 4 |
|
4 | 5 | import anthropic |
5 | 6 | import pydantic |
@@ -811,6 +812,7 @@ async def test_structured_output(anthropic_client, model, test_output_model_cls, |
811 | 812 | ), |
812 | 813 | unittest.mock.Mock( |
813 | 814 | type="message_stop", |
| 815 | + message=unittest.mock.Mock(stop_reason="tool_use"), |
814 | 816 | model_dump=unittest.mock.Mock( |
815 | 817 | return_value={"type": "message_stop", "message": {"stop_reason": "tool_use"}} |
816 | 818 | ), |
@@ -933,3 +935,57 @@ def test_format_request_filters_location_source_document(model, model_id, max_to |
933 | 935 | ] |
934 | 936 | assert tru_request["messages"] == exp_messages |
935 | 937 | 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