From 2a0bd3978d4757b4f2fb53e891a533c5aa81620b Mon Sep 17 00:00:00 2001 From: Oxygen <1391083091@qq.com> Date: Sat, 6 Jun 2026 01:25:47 +0800 Subject: [PATCH] fix: correct content_block_delta deserialization in streaming Fix handling of content_block_delta event variants during streaming to prevent dropped events. The root cause: when construct_type() is called with both an Annotated-wrapped type AND explicit metadata, get_origin() returns 'Annotated' rather than the inner union type, causing none of the type-check branches to match. The function then falls through to return value, yielding a raw dict instead of a proper model. Two-part fix: 1. construct_type(): Always unwrap Annotated from type_, even when metadata was explicitly passed by _construct_field. 2. _build_discriminated_union_meta(): Also unwrap Annotated from the union parameter, so get_args() yields individual variant types rather than (UnionType, metadata). Fixes #941 Co-Authored-By: Claude Opus 4.8 --- src/anthropic/_models.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/anthropic/_models.py b/src/anthropic/_models.py index dc00516bc..a5c81daa5 100644 --- a/src/anthropic/_models.py +++ b/src/anthropic/_models.py @@ -601,6 +601,14 @@ def construct_type(*, value: object, type_: object, metadata: Optional[List[Any] else: meta = tuple() + # Ensure type_ is always unwrapped from Annotated, even when metadata + # was explicitly passed (e.g. by _construct_field). Without this, a + # type like Annotated[Union[...], PropertyInfo(...)] would have + # get_origin() return Annotated instead of Union, causing the union + # branch to be skipped and the raw dict returned as-is. + if is_annotated_type(type_): + type_ = extract_type_arg(type_, 0) + # we need to use the origin class for any types that are subscripted generics # e.g. Dict[str, object] origin = get_origin(type_) or type_ @@ -745,6 +753,11 @@ def __init__( def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, ...]) -> DiscriminatorDetails | None: + # Strip Annotated wrapper from the union type so that get_args(union) + # yields the individual union variants rather than (UnionType, metadata). + if is_annotated_type(union): + union = extract_type_arg(union, 0) + cached = DISCRIMINATOR_CACHE.get(union) if cached is not None: return cached