From 4cf7e5ae1a9e6bb709ee0af266125daff6a1b73c Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 27 Feb 2026 10:51:45 +0100 Subject: [PATCH 01/12] ref(anthropic): Factor out streamed result handling --- sentry_sdk/integrations/anthropic.py | 184 ++++++++++++++------------- 1 file changed, 98 insertions(+), 86 deletions(-) diff --git a/sentry_sdk/integrations/anthropic.py b/sentry_sdk/integrations/anthropic.py index eca9e8bd3e..ada57aebd7 100644 --- a/sentry_sdk/integrations/anthropic.py +++ b/sentry_sdk/integrations/anthropic.py @@ -49,6 +49,9 @@ from sentry_sdk.tracing import Span from sentry_sdk._types import TextPart + from anthropic import AsyncStream + from anthropic.types import RawMessageStreamEvent + class _RecordedUsage: output_tokens: int = 0 @@ -389,6 +392,96 @@ def _set_output_data( span.__exit__(None, None, None) +def _set_streaming_output_data( + result: "AsyncStream[RawMessageStreamEvent]", + span: "sentry_sdk.tracing.Span", +): + integration = sentry_sdk.get_client().get_integration(AnthropicIntegration) + + old_iterator = result._iterator + + def new_iterator() -> "Iterator[MessageStreamEvent]": + model = None + usage = _RecordedUsage() + content_blocks: "list[str]" = [] + + for event in old_iterator: + ( + model, + usage, + content_blocks, + ) = _collect_ai_data( + event, + model, + usage, + content_blocks, + ) + yield event + + # Anthropic's input_tokens excludes cached/cache_write tokens. + # Normalize to total input tokens for correct cost calculations. + total_input = ( + usage.input_tokens + + (usage.cache_read_input_tokens or 0) + + (usage.cache_write_input_tokens or 0) + ) + + _set_output_data( + span=span, + integration=integration, + model=model, + input_tokens=total_input, + output_tokens=usage.output_tokens, + cache_read_input_tokens=usage.cache_read_input_tokens, + cache_write_input_tokens=usage.cache_write_input_tokens, + content_blocks=[{"text": "".join(content_blocks), "type": "text"}], + finish_span=True, + ) + + async def new_iterator_async() -> "AsyncIterator[MessageStreamEvent]": + model = None + usage = _RecordedUsage() + content_blocks: "list[str]" = [] + + async for event in old_iterator: + ( + model, + usage, + content_blocks, + ) = _collect_ai_data( + event, + model, + usage, + content_blocks, + ) + yield event + + # Anthropic's input_tokens excludes cached/cache_write tokens. + # Normalize to total input tokens for correct cost calculations. + total_input = ( + usage.input_tokens + + (usage.cache_read_input_tokens or 0) + + (usage.cache_write_input_tokens or 0) + ) + + _set_output_data( + span=span, + integration=integration, + model=model, + input_tokens=total_input, + output_tokens=usage.output_tokens, + cache_read_input_tokens=usage.cache_read_input_tokens, + cache_write_input_tokens=usage.cache_write_input_tokens, + content_blocks=[{"text": "".join(content_blocks), "type": "text"}], + finish_span=True, + ) + + if str(type(result._iterator)) == "": + result._iterator = new_iterator_async() + else: + result._iterator = new_iterator() + + def _sentry_patched_create_common(f: "Any", *args: "Any", **kwargs: "Any") -> "Any": integration = kwargs.pop("integration") if integration is None: @@ -415,6 +508,11 @@ def _sentry_patched_create_common(f: "Any", *args: "Any", **kwargs: "Any") -> "A result = yield f, args, kwargs + is_streaming_response = kwargs.get("stream", False) + if is_streaming_response: + _set_streaming_output_data(result, span) + return result + with capture_internal_exceptions(): if hasattr(result, "content"): ( @@ -444,92 +542,6 @@ def _sentry_patched_create_common(f: "Any", *args: "Any", **kwargs: "Any") -> "A content_blocks=content_blocks, finish_span=True, ) - - # Streaming response - elif hasattr(result, "_iterator"): - old_iterator = result._iterator - - def new_iterator() -> "Iterator[MessageStreamEvent]": - model = None - usage = _RecordedUsage() - content_blocks: "list[str]" = [] - - for event in old_iterator: - ( - model, - usage, - content_blocks, - ) = _collect_ai_data( - event, - model, - usage, - content_blocks, - ) - yield event - - # Anthropic's input_tokens excludes cached/cache_write tokens. - # Normalize to total input tokens for correct cost calculations. - total_input = ( - usage.input_tokens - + (usage.cache_read_input_tokens or 0) - + (usage.cache_write_input_tokens or 0) - ) - - _set_output_data( - span=span, - integration=integration, - model=model, - input_tokens=total_input, - output_tokens=usage.output_tokens, - cache_read_input_tokens=usage.cache_read_input_tokens, - cache_write_input_tokens=usage.cache_write_input_tokens, - content_blocks=[{"text": "".join(content_blocks), "type": "text"}], - finish_span=True, - ) - - async def new_iterator_async() -> "AsyncIterator[MessageStreamEvent]": - model = None - usage = _RecordedUsage() - content_blocks: "list[str]" = [] - - async for event in old_iterator: - ( - model, - usage, - content_blocks, - ) = _collect_ai_data( - event, - model, - usage, - content_blocks, - ) - yield event - - # Anthropic's input_tokens excludes cached/cache_write tokens. - # Normalize to total input tokens for correct cost calculations. - total_input = ( - usage.input_tokens - + (usage.cache_read_input_tokens or 0) - + (usage.cache_write_input_tokens or 0) - ) - - _set_output_data( - span=span, - integration=integration, - model=model, - input_tokens=total_input, - output_tokens=usage.output_tokens, - cache_read_input_tokens=usage.cache_read_input_tokens, - cache_write_input_tokens=usage.cache_write_input_tokens, - content_blocks=[{"text": "".join(content_blocks), "type": "text"}], - finish_span=True, - ) - - if str(type(result._iterator)) == "": - result._iterator = new_iterator_async() - else: - result._iterator = new_iterator() - else: span.set_data("unknown_response", True) span.__exit__(None, None, None) From 6cdab1673211cae9482d3ba93b50ca76dabb8865 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 27 Feb 2026 13:29:09 +0100 Subject: [PATCH 02/12] . --- sentry_sdk/integrations/anthropic.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/anthropic.py b/sentry_sdk/integrations/anthropic.py index ada57aebd7..7379d54311 100644 --- a/sentry_sdk/integrations/anthropic.py +++ b/sentry_sdk/integrations/anthropic.py @@ -392,10 +392,13 @@ def _set_output_data( span.__exit__(None, None, None) -def _set_streaming_output_data( +def _patch_streaming_response_iterator( result: "AsyncStream[RawMessageStreamEvent]", span: "sentry_sdk.tracing.Span", ): + """ + Responsible for closing the `gen_ai.chat` span and setting attributes acquired during response consumption. + """ integration = sentry_sdk.get_client().get_integration(AnthropicIntegration) old_iterator = result._iterator @@ -510,7 +513,7 @@ def _sentry_patched_create_common(f: "Any", *args: "Any", **kwargs: "Any") -> "A is_streaming_response = kwargs.get("stream", False) if is_streaming_response: - _set_streaming_output_data(result, span) + _patch_streaming_response_iterator(result, span) return result with capture_internal_exceptions(): From db5dbb921f70944c46d65dbfe870fad4a8cae565 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 27 Feb 2026 13:31:22 +0100 Subject: [PATCH 03/12] . --- sentry_sdk/integrations/anthropic.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sentry_sdk/integrations/anthropic.py b/sentry_sdk/integrations/anthropic.py index 7379d54311..16e14c3500 100644 --- a/sentry_sdk/integrations/anthropic.py +++ b/sentry_sdk/integrations/anthropic.py @@ -395,12 +395,11 @@ def _set_output_data( def _patch_streaming_response_iterator( result: "AsyncStream[RawMessageStreamEvent]", span: "sentry_sdk.tracing.Span", -): + integration: "AnthropicIntegration", +) -> None: """ Responsible for closing the `gen_ai.chat` span and setting attributes acquired during response consumption. """ - integration = sentry_sdk.get_client().get_integration(AnthropicIntegration) - old_iterator = result._iterator def new_iterator() -> "Iterator[MessageStreamEvent]": @@ -513,7 +512,7 @@ def _sentry_patched_create_common(f: "Any", *args: "Any", **kwargs: "Any") -> "A is_streaming_response = kwargs.get("stream", False) if is_streaming_response: - _patch_streaming_response_iterator(result, span) + _patch_streaming_response_iterator(result, span, integration) return result with capture_internal_exceptions(): From 53859e4762f4bec7e6d411738398740d59a2137e Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 10 Mar 2026 14:38:18 +0100 Subject: [PATCH 04/12] . --- sentry_sdk/integrations/anthropic.py | 214 +++++++++++++++------------ 1 file changed, 117 insertions(+), 97 deletions(-) diff --git a/sentry_sdk/integrations/anthropic.py b/sentry_sdk/integrations/anthropic.py index 16e14c3500..a21f79e4a6 100644 --- a/sentry_sdk/integrations/anthropic.py +++ b/sentry_sdk/integrations/anthropic.py @@ -1,5 +1,6 @@ import sys import json +import inspect from collections.abc import Iterable from functools import wraps from typing import TYPE_CHECKING @@ -37,6 +38,7 @@ except ImportError: Omit = None + from anthropic import Stream, AsyncStream from anthropic.resources import AsyncMessages, Messages if TYPE_CHECKING: @@ -45,11 +47,10 @@ raise DidNotEnable("Anthropic not installed") if TYPE_CHECKING: - from typing import Any, AsyncIterator, Iterator, List, Optional, Union + from typing import Any, AsyncIterator, Iterator, List, Optional, Union, Callable from sentry_sdk.tracing import Span from sentry_sdk._types import TextPart - from anthropic import AsyncStream from anthropic.types import RawMessageStreamEvent @@ -75,6 +76,117 @@ def setup_once() -> None: Messages.create = _wrap_message_create(Messages.create) AsyncMessages.create = _wrap_message_create_async(AsyncMessages.create) + Stream.__iter__ = _wrap_stream_iter(Stream.__iter__) + AsyncStream.__aiter__ = _wrap_async_stream_aiter(AsyncStream.__aiter__) + + +def _wrap_stream_iter( + f: "Callable[..., Iterator[RawMessageStreamEvent]]", +) -> "Callable[..., Iterator[RawMessageStreamEvent]]": + @wraps(f) + def _patched_iter(self: "Stream") -> "Iterator[RawMessageStreamEvent]": + if not hasattr(self, "_sentry_span"): + for event in f(self): + yield event + + model = None + usage = _RecordedUsage() + content_blocks: "list[str]" = [] + + for event in f(self): + ( + model, + usage, + content_blocks, + ) = _collect_ai_data( + event, + model, + usage, + content_blocks, + ) + yield event + + # Anthropic's input_tokens excludes cached/cache_write tokens. + # Normalize to total input tokens for correct cost calculations. + total_input = ( + usage.input_tokens + + (usage.cache_read_input_tokens or 0) + + (usage.cache_write_input_tokens or 0) + ) + + span = self._sentry_span + integration = self._integration + + _set_output_data( + span=span, + integration=integration, + model=model, + input_tokens=total_input, + output_tokens=usage.output_tokens, + cache_read_input_tokens=usage.cache_read_input_tokens, + cache_write_input_tokens=usage.cache_write_input_tokens, + content_blocks=[{"text": "".join(content_blocks), "type": "text"}], + finish_span=True, + ) + + return f(self) + + return _patched_iter + + +def _wrap_async_stream_aiter( + f: "Callable[..., AsyncIterator[RawMessageStreamEvent]]", +) -> "Callable[..., AsyncIterator[RawMessageStreamEvent]]": + @wraps(f) + async def _patched_aiter( + self: "AsyncStream", + ) -> "AsyncIterator[RawMessageStreamEvent]": + if not hasattr(self, "_sentry_span"): + async for event in f(self): + yield event + + model = None + usage = _RecordedUsage() + content_blocks: "list[str]" = [] + + async for event in f(self): + ( + model, + usage, + content_blocks, + ) = _collect_ai_data( + event, + model, + usage, + content_blocks, + ) + yield event + + # Anthropic's input_tokens excludes cached/cache_write tokens. + # Normalize to total input tokens for correct cost calculations. + total_input = ( + usage.input_tokens + + (usage.cache_read_input_tokens or 0) + + (usage.cache_write_input_tokens or 0) + ) + + span = self._sentry_span + integration = self._integration + + _set_output_data( + span=span, + integration=integration, + model=model, + input_tokens=total_input, + output_tokens=usage.output_tokens, + cache_read_input_tokens=usage.cache_read_input_tokens, + cache_write_input_tokens=usage.cache_write_input_tokens, + content_blocks=[{"text": "".join(content_blocks), "type": "text"}], + finish_span=True, + ) + + return _patched_aiter + def _capture_exception(exc: "Any") -> None: set_span_errored() @@ -392,98 +504,6 @@ def _set_output_data( span.__exit__(None, None, None) -def _patch_streaming_response_iterator( - result: "AsyncStream[RawMessageStreamEvent]", - span: "sentry_sdk.tracing.Span", - integration: "AnthropicIntegration", -) -> None: - """ - Responsible for closing the `gen_ai.chat` span and setting attributes acquired during response consumption. - """ - old_iterator = result._iterator - - def new_iterator() -> "Iterator[MessageStreamEvent]": - model = None - usage = _RecordedUsage() - content_blocks: "list[str]" = [] - - for event in old_iterator: - ( - model, - usage, - content_blocks, - ) = _collect_ai_data( - event, - model, - usage, - content_blocks, - ) - yield event - - # Anthropic's input_tokens excludes cached/cache_write tokens. - # Normalize to total input tokens for correct cost calculations. - total_input = ( - usage.input_tokens - + (usage.cache_read_input_tokens or 0) - + (usage.cache_write_input_tokens or 0) - ) - - _set_output_data( - span=span, - integration=integration, - model=model, - input_tokens=total_input, - output_tokens=usage.output_tokens, - cache_read_input_tokens=usage.cache_read_input_tokens, - cache_write_input_tokens=usage.cache_write_input_tokens, - content_blocks=[{"text": "".join(content_blocks), "type": "text"}], - finish_span=True, - ) - - async def new_iterator_async() -> "AsyncIterator[MessageStreamEvent]": - model = None - usage = _RecordedUsage() - content_blocks: "list[str]" = [] - - async for event in old_iterator: - ( - model, - usage, - content_blocks, - ) = _collect_ai_data( - event, - model, - usage, - content_blocks, - ) - yield event - - # Anthropic's input_tokens excludes cached/cache_write tokens. - # Normalize to total input tokens for correct cost calculations. - total_input = ( - usage.input_tokens - + (usage.cache_read_input_tokens or 0) - + (usage.cache_write_input_tokens or 0) - ) - - _set_output_data( - span=span, - integration=integration, - model=model, - input_tokens=total_input, - output_tokens=usage.output_tokens, - cache_read_input_tokens=usage.cache_read_input_tokens, - cache_write_input_tokens=usage.cache_write_input_tokens, - content_blocks=[{"text": "".join(content_blocks), "type": "text"}], - finish_span=True, - ) - - if str(type(result._iterator)) == "": - result._iterator = new_iterator_async() - else: - result._iterator = new_iterator() - - def _sentry_patched_create_common(f: "Any", *args: "Any", **kwargs: "Any") -> "Any": integration = kwargs.pop("integration") if integration is None: @@ -510,9 +530,9 @@ def _sentry_patched_create_common(f: "Any", *args: "Any", **kwargs: "Any") -> "A result = yield f, args, kwargs - is_streaming_response = kwargs.get("stream", False) - if is_streaming_response: - _patch_streaming_response_iterator(result, span, integration) + if isinstance(result, Stream) or isinstance(result, AsyncStream): + result._sentry_span = span + result._integration = integration return result with capture_internal_exceptions(): From e2d6d78204d619e9ae6f96215a072e421abb5d14 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 10 Mar 2026 14:39:10 +0100 Subject: [PATCH 05/12] remove unused import --- sentry_sdk/integrations/anthropic.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sentry_sdk/integrations/anthropic.py b/sentry_sdk/integrations/anthropic.py index a21f79e4a6..845fd6e781 100644 --- a/sentry_sdk/integrations/anthropic.py +++ b/sentry_sdk/integrations/anthropic.py @@ -1,6 +1,5 @@ import sys import json -import inspect from collections.abc import Iterable from functools import wraps from typing import TYPE_CHECKING From a01f7c1698aeed44085ef2f6b73c2c9546b8649d Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 10 Mar 2026 14:41:17 +0100 Subject: [PATCH 06/12] add docstring --- sentry_sdk/integrations/anthropic.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sentry_sdk/integrations/anthropic.py b/sentry_sdk/integrations/anthropic.py index 845fd6e781..72f4b9b406 100644 --- a/sentry_sdk/integrations/anthropic.py +++ b/sentry_sdk/integrations/anthropic.py @@ -82,6 +82,11 @@ def setup_once() -> None: def _wrap_stream_iter( f: "Callable[..., Iterator[RawMessageStreamEvent]]", ) -> "Callable[..., Iterator[RawMessageStreamEvent]]": + """ + Sets information received while iterating the response stream on the AI Client Span. + Responsible for closing the AI Client Span. + """ + @wraps(f) def _patched_iter(self: "Stream") -> "Iterator[RawMessageStreamEvent]": if not hasattr(self, "_sentry_span"): @@ -136,6 +141,11 @@ def _patched_iter(self: "Stream") -> "Iterator[RawMessageStreamEvent]": def _wrap_async_stream_aiter( f: "Callable[..., AsyncIterator[RawMessageStreamEvent]]", ) -> "Callable[..., AsyncIterator[RawMessageStreamEvent]]": + """ + Sets information received while iterating the response stream on the AI Client Span. + Responsible for closing the AI Client Span. + """ + @wraps(f) async def _patched_aiter( self: "AsyncStream", From 7837439b46c89287d1d82380e5696211dc7d68d5 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 10 Mar 2026 14:42:05 +0100 Subject: [PATCH 07/12] add return --- sentry_sdk/integrations/anthropic.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sentry_sdk/integrations/anthropic.py b/sentry_sdk/integrations/anthropic.py index 72f4b9b406..9fdc8b4bb4 100644 --- a/sentry_sdk/integrations/anthropic.py +++ b/sentry_sdk/integrations/anthropic.py @@ -92,6 +92,7 @@ def _patched_iter(self: "Stream") -> "Iterator[RawMessageStreamEvent]": if not hasattr(self, "_sentry_span"): for event in f(self): yield event + return model = None usage = _RecordedUsage() @@ -153,6 +154,7 @@ async def _patched_aiter( if not hasattr(self, "_sentry_span"): async for event in f(self): yield event + return model = None usage = _RecordedUsage() From 0e06f49dfc34fdbd0ce5e37b6f92af680730b277 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 10 Mar 2026 14:42:29 +0100 Subject: [PATCH 08/12] remove return statement --- sentry_sdk/integrations/anthropic.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/sentry_sdk/integrations/anthropic.py b/sentry_sdk/integrations/anthropic.py index 9fdc8b4bb4..8acf892d6c 100644 --- a/sentry_sdk/integrations/anthropic.py +++ b/sentry_sdk/integrations/anthropic.py @@ -134,8 +134,6 @@ def _patched_iter(self: "Stream") -> "Iterator[RawMessageStreamEvent]": finish_span=True, ) - return f(self) - return _patched_iter From 917b8d48752c26b5deab09f83b6e61ca0fe82007 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 11 Mar 2026 11:52:05 +0100 Subject: [PATCH 09/12] . --- sentry_sdk/integrations/anthropic.py | 108 ++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 3 deletions(-) diff --git a/sentry_sdk/integrations/anthropic.py b/sentry_sdk/integrations/anthropic.py index 8acf892d6c..734d932f57 100644 --- a/sentry_sdk/integrations/anthropic.py +++ b/sentry_sdk/integrations/anthropic.py @@ -462,6 +462,101 @@ def _set_input_data( span.set_data(SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS, safe_serialize(tools)) +def _wrap_synchronous_message_iterator( + iterator: "Iterator[RawMessageStreamEvent]", + span: "Span", + integration: "AnthropicIntegration", +) -> "Iterator[RawMessageStreamEvent]": + """ + Sets information received while iterating the response stream on the AI Client Span. + Responsible for closing the AI Client Span. + """ + + model = None + usage = _RecordedUsage() + content_blocks: "list[str]" = [] + + for event in iterator: + ( + model, + usage, + content_blocks, + ) = _collect_ai_data( + event, + model, + usage, + content_blocks, + ) + yield event + + # Anthropic's input_tokens excludes cached/cache_write tokens. + # Normalize to total input tokens for correct cost calculations. + total_input = ( + usage.input_tokens + + (usage.cache_read_input_tokens or 0) + + (usage.cache_write_input_tokens or 0) + ) + + _set_output_data( + span=span, + integration=integration, + model=model, + input_tokens=total_input, + output_tokens=usage.output_tokens, + cache_read_input_tokens=usage.cache_read_input_tokens, + cache_write_input_tokens=usage.cache_write_input_tokens, + content_blocks=[{"text": "".join(content_blocks), "type": "text"}], + finish_span=True, + ) + + +async def _wrap_asynchronous_message_iterator( + iterator: "Iterator[RawMessageStreamEvent]", + span: "Span", + integration: "AnthropicIntegration", +) -> "Iterator[RawMessageStreamEvent]": + """ + Sets information received while iterating the response stream on the AI Client Span. + Responsible for closing the AI Client Span. + """ + model = None + usage = _RecordedUsage() + content_blocks: "list[str]" = [] + + async for event in iterator: + ( + model, + usage, + content_blocks, + ) = _collect_ai_data( + event, + model, + usage, + content_blocks, + ) + yield event + + # Anthropic's input_tokens excludes cached/cache_write tokens. + # Normalize to total input tokens for correct cost calculations. + total_input = ( + usage.input_tokens + + (usage.cache_read_input_tokens or 0) + + (usage.cache_write_input_tokens or 0) + ) + + _set_output_data( + span=span, + integration=integration, + model=model, + input_tokens=total_input, + output_tokens=usage.output_tokens, + cache_read_input_tokens=usage.cache_read_input_tokens, + cache_write_input_tokens=usage.cache_write_input_tokens, + content_blocks=[{"text": "".join(content_blocks), "type": "text"}], + finish_span=True, + ) + + def _set_output_data( span: "Span", integration: "AnthropicIntegration", @@ -539,9 +634,16 @@ def _sentry_patched_create_common(f: "Any", *args: "Any", **kwargs: "Any") -> "A result = yield f, args, kwargs - if isinstance(result, Stream) or isinstance(result, AsyncStream): - result._sentry_span = span - result._integration = integration + if isinstance(result, Stream): + result._iterator = _wrap_synchronous_message_iterator( + result._iterator, span, integration + ) + return result + + if isinstance(result, AsyncStream): + result._iterator = _wrap_asynchronous_message_iterator( + result._iterator, span, integration + ) return result with capture_internal_exceptions(): From aa6e58ae700af254e9f42740333d08436367ebe1 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 11 Mar 2026 11:53:09 +0100 Subject: [PATCH 10/12] . --- sentry_sdk/integrations/anthropic.py | 121 --------------------------- 1 file changed, 121 deletions(-) diff --git a/sentry_sdk/integrations/anthropic.py b/sentry_sdk/integrations/anthropic.py index 734d932f57..b8f9ac1a24 100644 --- a/sentry_sdk/integrations/anthropic.py +++ b/sentry_sdk/integrations/anthropic.py @@ -75,127 +75,6 @@ def setup_once() -> None: Messages.create = _wrap_message_create(Messages.create) AsyncMessages.create = _wrap_message_create_async(AsyncMessages.create) - Stream.__iter__ = _wrap_stream_iter(Stream.__iter__) - AsyncStream.__aiter__ = _wrap_async_stream_aiter(AsyncStream.__aiter__) - - -def _wrap_stream_iter( - f: "Callable[..., Iterator[RawMessageStreamEvent]]", -) -> "Callable[..., Iterator[RawMessageStreamEvent]]": - """ - Sets information received while iterating the response stream on the AI Client Span. - Responsible for closing the AI Client Span. - """ - - @wraps(f) - def _patched_iter(self: "Stream") -> "Iterator[RawMessageStreamEvent]": - if not hasattr(self, "_sentry_span"): - for event in f(self): - yield event - return - - model = None - usage = _RecordedUsage() - content_blocks: "list[str]" = [] - - for event in f(self): - ( - model, - usage, - content_blocks, - ) = _collect_ai_data( - event, - model, - usage, - content_blocks, - ) - yield event - - # Anthropic's input_tokens excludes cached/cache_write tokens. - # Normalize to total input tokens for correct cost calculations. - total_input = ( - usage.input_tokens - + (usage.cache_read_input_tokens or 0) - + (usage.cache_write_input_tokens or 0) - ) - - span = self._sentry_span - integration = self._integration - - _set_output_data( - span=span, - integration=integration, - model=model, - input_tokens=total_input, - output_tokens=usage.output_tokens, - cache_read_input_tokens=usage.cache_read_input_tokens, - cache_write_input_tokens=usage.cache_write_input_tokens, - content_blocks=[{"text": "".join(content_blocks), "type": "text"}], - finish_span=True, - ) - - return _patched_iter - - -def _wrap_async_stream_aiter( - f: "Callable[..., AsyncIterator[RawMessageStreamEvent]]", -) -> "Callable[..., AsyncIterator[RawMessageStreamEvent]]": - """ - Sets information received while iterating the response stream on the AI Client Span. - Responsible for closing the AI Client Span. - """ - - @wraps(f) - async def _patched_aiter( - self: "AsyncStream", - ) -> "AsyncIterator[RawMessageStreamEvent]": - if not hasattr(self, "_sentry_span"): - async for event in f(self): - yield event - return - - model = None - usage = _RecordedUsage() - content_blocks: "list[str]" = [] - - async for event in f(self): - ( - model, - usage, - content_blocks, - ) = _collect_ai_data( - event, - model, - usage, - content_blocks, - ) - yield event - - # Anthropic's input_tokens excludes cached/cache_write tokens. - # Normalize to total input tokens for correct cost calculations. - total_input = ( - usage.input_tokens - + (usage.cache_read_input_tokens or 0) - + (usage.cache_write_input_tokens or 0) - ) - - span = self._sentry_span - integration = self._integration - - _set_output_data( - span=span, - integration=integration, - model=model, - input_tokens=total_input, - output_tokens=usage.output_tokens, - cache_read_input_tokens=usage.cache_read_input_tokens, - cache_write_input_tokens=usage.cache_write_input_tokens, - content_blocks=[{"text": "".join(content_blocks), "type": "text"}], - finish_span=True, - ) - - return _patched_aiter - def _capture_exception(exc: "Any") -> None: set_span_errored() From bd00e4eeb2ac5ca25569f87f0b52f508589e3330 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 11 Mar 2026 11:53:40 +0100 Subject: [PATCH 11/12] . --- sentry_sdk/integrations/anthropic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/anthropic.py b/sentry_sdk/integrations/anthropic.py index b8f9ac1a24..7d90e6ae8c 100644 --- a/sentry_sdk/integrations/anthropic.py +++ b/sentry_sdk/integrations/anthropic.py @@ -46,7 +46,7 @@ raise DidNotEnable("Anthropic not installed") if TYPE_CHECKING: - from typing import Any, AsyncIterator, Iterator, List, Optional, Union, Callable + from typing import Any, AsyncIterator, Iterator, List, Optional, Union from sentry_sdk.tracing import Span from sentry_sdk._types import TextPart From c988c263421cd4c6008251c2921237d835d8baa3 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 11 Mar 2026 11:58:22 +0100 Subject: [PATCH 12/12] fix type --- sentry_sdk/integrations/anthropic.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/anthropic.py b/sentry_sdk/integrations/anthropic.py index 7d90e6ae8c..a67f3c3909 100644 --- a/sentry_sdk/integrations/anthropic.py +++ b/sentry_sdk/integrations/anthropic.py @@ -390,10 +390,10 @@ def _wrap_synchronous_message_iterator( async def _wrap_asynchronous_message_iterator( - iterator: "Iterator[RawMessageStreamEvent]", + iterator: "AsyncIterator[RawMessageStreamEvent]", span: "Span", integration: "AnthropicIntegration", -) -> "Iterator[RawMessageStreamEvent]": +) -> "AsyncIterator[RawMessageStreamEvent]": """ Sets information received while iterating the response stream on the AI Client Span. Responsible for closing the AI Client Span.