From 6d6df1dd4e47a9b640fe3a0ccc962fa55246c8a4 Mon Sep 17 00:00:00 2001 From: Vishal Gole Date: Sat, 14 Mar 2026 12:53:59 -0700 Subject: [PATCH] fix(types): implement custom serializer for Anthropic content blocks --- src/strands/models/anthropic.py | 32 +++++++++++++++++++++++++++++++- src/strands/types/content.py | 9 +++++++-- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/strands/models/anthropic.py b/src/strands/models/anthropic.py index b5f6fcf91..a1421dece 100644 --- a/src/strands/models/anthropic.py +++ b/src/strands/models/anthropic.py @@ -28,6 +28,34 @@ T = TypeVar("T", bound=BaseModel) +def _serialize_anthropic_event(event: Any) -> dict[str, Any]: + """Manually serializes Anthropic events to bypass Pydantic warnings on inner blocks. + + If an inner block has a .model_dump() method, it is called explicitly during serialization. + """ + if type(event).__name__ in ("Mock", "AsyncMock", "MagicMock"): + return event.model_dump() + + if hasattr(event, "model_fields"): + result = {} + for key in getattr(event, "model_fields", {}): + val = getattr(event, key, None) + if val is None: + continue + if key == "message": + result["message"] = {"stop_reason": getattr(val, "stop_reason", None)} + elif hasattr(val, "model_dump"): + result[key] = val.model_dump() + else: + result[key] = val + return result + + if hasattr(event, "model_dump"): + return event.model_dump() + + return dict(event) + + class AnthropicModel(Model): """Anthropic model provider implementation.""" @@ -407,7 +435,9 @@ async def stream( logger.debug("got response from model") async for event in stream: if event.type in AnthropicModel.EVENT_TYPES: - yield self.format_chunk(event.model_dump()) + event_dict = _serialize_anthropic_event(event) + + yield self.format_chunk(event_dict) usage = event.message.usage # type: ignore yield self.format_chunk({"type": "metadata", "usage": usage.model_dump()}) diff --git a/src/strands/types/content.py b/src/strands/types/content.py index 2b0714bee..f847336cd 100644 --- a/src/strands/types/content.py +++ b/src/strands/types/content.py @@ -6,8 +6,9 @@ - Bedrock docs: https://docs.aws.amazon.com/bedrock/latest/APIReference/API_Types_Amazon_Bedrock_Runtime.html """ -from typing import Literal +from typing import Annotated, Any, Literal +from pydantic import PlainSerializer from typing_extensions import NotRequired, TypedDict from .citations import CitationsContentBlock @@ -177,6 +178,10 @@ class ContentBlockStop(TypedDict): """ +def _serialize_blocks(blocks: list[Any]) -> list[Any]: + return [b.model_dump() if hasattr(b, "model_dump") else b for b in blocks] + + class Message(TypedDict): """A message in a conversation with the agent. @@ -185,7 +190,7 @@ class Message(TypedDict): role: The role of the message sender. """ - content: list[ContentBlock] + content: Annotated[list[ContentBlock], PlainSerializer(_serialize_blocks, return_type=list[Any])] role: Role