Summary
The Bedrock stream decoder (anthropic/lib/bedrock/_stream_decoder.py) hardcodes ServerSentEvent(event="completion") for every chunk, discarding the real event type. Downstream, the "completion" branch in _streaming.py::__stream__ never backfills data["type"] (unlike the direct-SSE branch). Chunks without a type field — notably Bedrock's amazon-bedrock-invocationMetrics trailer — then hit non-validating construct_type against the stream-event union, which falls back to the first union member and yields RawMessageStartEvent(message=None) — an object that violates the SDK's own type contract (message is typed as non-optional).
Reproduction (anthropic 0.105.2, offline)
from anthropic._models import construct_type
from anthropic.types.beta import BetaRawMessageStreamEvent
event = construct_type(
value={"amazon-bedrock-invocationMetrics": {"inputTokenCount": 1}},
type_=BetaRawMessageStreamEvent,
)
print(type(event).__name__, event.message)
# BetaRawMessageStartEvent None
Any consumer that trusts the type annotations (e.g. event.message.usage) crashes with AttributeError: 'NoneType' object has no attribute 'usage'. We hit this in production via pydantic-ai's streaming path on AsyncAnthropicBedrock — intermittent 500s whenever a stream contained the invocationMetrics trailer.
Expected
Either:
- the Bedrock decoder preserves/backfills the event type from the chunk payload's
type field (the payloads do carry one for real events), and drops/ignores non-Messages chunks like amazon-bedrock-invocationMetrics, or
- stream-event construction validates and raises a clear error instead of silently producing a contract-violating first-union-member object.
Notes
Summary
The Bedrock stream decoder (
anthropic/lib/bedrock/_stream_decoder.py) hardcodesServerSentEvent(event="completion")for every chunk, discarding the real event type. Downstream, the"completion"branch in_streaming.py::__stream__never backfillsdata["type"](unlike the direct-SSE branch). Chunks without atypefield — notably Bedrock'samazon-bedrock-invocationMetricstrailer — then hit non-validatingconstruct_typeagainst the stream-event union, which falls back to the first union member and yieldsRawMessageStartEvent(message=None)— an object that violates the SDK's own type contract (messageis typed as non-optional).Reproduction (anthropic 0.105.2, offline)
Any consumer that trusts the type annotations (e.g.
event.message.usage) crashes withAttributeError: 'NoneType' object has no attribute 'usage'. We hit this in production via pydantic-ai's streaming path onAsyncAnthropicBedrock— intermittent 500s whenever a stream contained the invocationMetrics trailer.Expected
Either:
typefield (the payloads do carry one for real events), and drops/ignores non-Messages chunks likeamazon-bedrock-invocationMetrics, orNotes
ValueErroron error SSE events) — same file, different gap: this one is about successful streams containing type-less chunks.data["type"].