Skip to content

[BUG] PydanticSerializationUnexpectedValue warnings when Anthropic SDK returns ParsedTextBlock in message content #1746

@tomkitewing

Description

@tomkitewing

Checks

  • I have updated to the lastest minor and patch version of Strands
  • I have checked the documentation and this is not expected behavior
  • I have searched ./issues and there are no duplicates of my issue

Strands Version

1.27.0

Python Version

3.13

Operating System

Debian 13.1

Installation Method

pip

Steps to Reproduce

Install Strands with the anthopic extra version 0.83.0. Then run:

import os

from strands import Agent
from strands.models.anthropic import AnthropicModel

api_key = os.environ.get("ANTHROPIC_API_KEY")

model = AnthropicModel(
  client_args={"api_key": api_key},
  model_id="claude-sonnet-4-20250514",
  max_tokens=1024,
)
agent = Agent(model=model, callback_handler=None)

result = agent("Say hello in one sentence.")

Running that script will print a bunch of pydantic warnings.

Expected Behavior

No warnings printed. I don't think this is breaking anything.

Actual Behavior

Here are the warnings:

/workspace/.venv/lib/python3.13/site-packages/pydantic/main.py:464: UserWarning: Pydantic serializer warnings:
  PydanticSerializationUnexpectedValue(Expected `ParsedTextBlock[TypeVar]` - serialized value may not be as expected [field_name='content', input_value=ParsedTextBlock(citations...xt', parsed_output=None), input_type=ParsedTextBlock])
  PydanticSerializationUnexpectedValue(Expected `ThinkingBlock` - serialized value may not be as expected [field_name='content', input_value=ParsedTextBlock(citations...xt', parsed_output=None), input_type=ParsedTextBlock])
  PydanticSerializationUnexpectedValue(Expected `RedactedThinkingBlock` - serialized value may not be as expected [field_name='content', input_value=ParsedTextBlock(citations...xt', parsed_output=None), input_type=ParsedTextBlock])
  PydanticSerializationUnexpectedValue(Expected `ToolUseBlock` - serialized value may not be as expected [field_name='content', input_value=ParsedTextBlock(citations...xt', parsed_output=None), input_type=ParsedTextBlock])
  PydanticSerializationUnexpectedValue(Expected `ServerToolUseBlock` - serialized value may not be as expected [field_name='content', input_value=ParsedTextBlock(citations...xt', parsed_output=None), input_type=ParsedTextBlock])
  PydanticSerializationUnexpectedValue(Expected `WebSearchToolResultBlock` - serialized value may not be as expected [field_name='content', input_value=ParsedTextBlock(citations...xt', parsed_output=None), input_type=ParsedTextBlock])
  return self.__pydantic_serializer__.to_python(

anthropic.types.ParsedTextBlock is a Pydantic BaseModel (inherits from TextBlock), while ContentBlock is a TypedDict. Pydantic's serializer doesn't know how to match a BaseModel instance against a TypedDict variant in a union, so it tries every variant and warns for each one.

Additional Context

No response

Possible Solution

No response

Related Issues

No response


Implementation Requirements

Root Cause

The issue originates in src/strands/models/anthropic.py in the stream() method (line ~410):

async for event in stream:
    if event.type in AnthropicModel.EVENT_TYPES:
        yield self.format_chunk(event.model_dump())  # Problem is here

For message_stop events, calling event.model_dump() serializes the entire event including event.message, which contains a content field with ParsedTextBlock objects. Pydantic can't cleanly serialize ParsedTextBlock (a Pydantic BaseModel) against the TypedDict union variants, generating warnings.

Key insight: The format_chunk method for message_stop only needs stop_reason:

case "message_stop":
    message = event["message"]
    return {"messageStop": {"stopReason": message["stop_reason"]}}

Technical Approach - Fix the Root Cause

Instead of suppressing warnings, avoid the unnecessary serialization entirely. For message_stop events, build the dict directly from the event attributes instead of calling model_dump().

# Before (causes warnings - serializes entire message including content)
async for event in stream:
    if event.type in AnthropicModel.EVENT_TYPES:
        yield self.format_chunk(event.model_dump())

# After (fixes root cause - only extracts what we need)
async for event in stream:
    if event.type in AnthropicModel.EVENT_TYPES:
        if event.type == "message_stop":
            # Build dict directly - avoids serializing problematic content field
            yield self.format_chunk({
                "type": "message_stop",
                "message": {"stop_reason": event.message.stop_reason}
            })
        else:
            yield self.format_chunk(event.model_dump())

Why This Approach Is Best

Aspect warnings=False Targeted Filter Root Cause Fix
Fixes the actual problem ❌ Workaround ❌ Workaround ✅ Yes
No warning suppression
Risk of masking issues ⚠️ High ⚠️ Medium ✅ None
More efficient ✅ Less data serialized
Clean/explicit ⚠️ ✅ Yes

Files to Modify

  1. src/strands/models/anthropic.py

    • Lines ~408-410: Add conditional handling for message_stop events
    • Build dict directly from event.message.stop_reason instead of calling model_dump()
  2. tests/strands/models/test_anthropic.py

    • Add test to verify no warnings are emitted during streaming with message_stop events
    • Verify the message_stop event is still processed correctly

Code Change

# In stream() method, replace:
async for event in stream:
    if event.type in AnthropicModel.EVENT_TYPES:
        yield self.format_chunk(event.model_dump())

# With:
async for event in stream:
    if event.type in AnthropicModel.EVENT_TYPES:
        if event.type == "message_stop":
            yield self.format_chunk({
                "type": "message_stop",
                "message": {"stop_reason": event.message.stop_reason}
            })
        else:
            yield self.format_chunk(event.model_dump())

Acceptance Criteria

  • No PydanticSerializationUnexpectedValue warnings when using AnthropicModel
  • No warning suppression code added (clean fix)
  • All existing unit tests pass
  • Integration tests with Anthropic models pass
  • New unit test verifies message_stop handling without warnings
  • Code follows repository style guidelines

Testing Notes

  • Use warnings.catch_warnings(record=True) in tests to verify no warnings
  • Test should use mock events that simulate the message_stop event structure
  • Verify the formatted output is identical to before (same stopReason value)

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