Skip to content

fix(streaming): add direct type-map fallback for raw dict events#1287

Open
gambletan wants to merge 1 commit intoanthropics:mainfrom
gambletan:fix/streaming-delta-deserialization
Open

fix(streaming): add direct type-map fallback for raw dict events#1287
gambletan wants to merge 1 commit intoanthropics:mainfrom
gambletan:fix/streaming-delta-deserialization

Conversation

@gambletan
Copy link
Copy Markdown

Summary

Fixes #941content_block_delta SSE events fail to deserialize with "Unexpected event runtime type, after deserialising twice" in some environments.

Root cause

accumulate_event in _messages.py (and _beta_messages.py) receives events from Stream[RawMessageStreamEvent]. In certain environments (older pydantic versions, or edge cases in the Annotated-union discriminator path), construct_type_unchecked silently returns the raw dict instead of a typed BaseModel. The previous code retried once then raised TypeError.

Fix

Add a third-tier fallback: if both the initial isinstance check and the construct_type_unchecked retry fail to produce a BaseModel, look up the event's "type" field in an explicit dict[str, type[BaseModel]] map and call model_construct directly. This guarantees that any well-formed event dict (including content_block_delta) is always promoted to the correct typed object.

The same fix is applied to the beta messages streaming path (_beta_messages.py).

Changes

  • src/anthropic/lib/streaming/_messages.py — import concrete event classes, add _RAW_EVENT_TYPE_MAP, add fallback in accumulate_event
  • src/anthropic/lib/streaming/_beta_messages.py — same for the beta path
  • tests/lib/streaming/test_messages.py — regression test that passes a raw dict event directly to accumulate_event and asserts it succeeds

Test plan

  • New regression test test_accumulate_event_handles_raw_dict_content_block_delta passes raw dict events to accumulate_event and asserts they are handled correctly
  • All existing streaming tests pass (tests/lib/streaming/ — 26 tests)

🤖 Generated with Claude Code

…hropics#941)

When the union discriminator deserialization in construct_type_unchecked
silently returns a raw dict instead of a typed BaseModel (observed in some
environments and pydantic versions), accumulate_event now falls back to a
direct lookup table keyed on the 'type' field to construct the correct
event model.

This ensures well-formed content_block_delta events (and all other
RawMessageStreamEvent variants) are always promoted to their typed
BaseModel even if the generic Annotated-union deserialization path fails.

Fixes: anthropics#941

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@gambletan gambletan requested a review from a team as a code owner March 24, 2026 05:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

content_block_delta event not deserialized correctly during streaming

1 participant