Skip to content

Commit dcfcd0c

Browse files
Merge branch 'webb/anthropic/streaming-reasoning' of github.com:getsentry/sentry-python into webb/anthropic/streaming-reasoning
2 parents 08af830 + 510b381 commit dcfcd0c

2 files changed

Lines changed: 501 additions & 583 deletions

File tree

‎sentry_sdk/integrations/anthropic.py‎

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
MessageDeltaEvent,
5858
MessageStartEvent,
5959
MessageStopEvent,
60+
TextBlock,
61+
ToolUseBlock,
6062
)
6163

6264
if TYPE_CHECKING:
@@ -65,10 +67,10 @@
6567
raise DidNotEnable("Anthropic not installed")
6668

6769
try:
68-
from anthropic.types import ThinkingBlock
70+
from anthropic.types import ThinkingBlock, ThinkingDelta
6971
except ImportError:
7072
ThinkingBlock = None # type: ignore[assignment,misc]
71-
73+
ThinkingDelta = None # type: ignore[assignment,misc]
7274

7375
if TYPE_CHECKING:
7476
from typing import (
@@ -99,7 +101,7 @@ class _PatchedRawMessageStream(Stream[Any]):
99101

100102
_model: Optional[ModelParam]
101103
_usage: "_RecordedUsage"
102-
_content_blocks: list[Any]
104+
_content_blocks: list[dict[str, str]]
103105
_response_id: Optional[str]
104106
_finish_reason: Optional[str]
105107

@@ -109,7 +111,7 @@ class _PatchedMessageStream(MessageStream):
109111

110112
_model: Optional[ModelParam]
111113
_usage: "_RecordedUsage"
112-
_content_blocks: list[Any]
114+
_content_blocks: list[dict[str, str]]
113115
_response_id: Optional[str]
114116
_finish_reason: Optional[str]
115117

@@ -119,7 +121,7 @@ class _PatchedRawAsyncMessageStream(AsyncStream[Any]):
119121

120122
_model: Optional[ModelParam]
121123
_usage: "_RecordedUsage"
122-
_content_blocks: list[Any]
124+
_content_blocks: list[dict[str, str]]
123125
_response_id: Optional[str]
124126
_finish_reason: Optional[str]
125127

@@ -129,7 +131,7 @@ class _PatchedAsyncMessageStream(AsyncMessageStream):
129131

130132
_model: Optional[ModelParam]
131133
_usage: "_RecordedUsage"
132-
_content_blocks: list[Any]
134+
_content_blocks: list[dict[str, str]]
133135
_response_id: Optional[str]
134136
_finish_reason: Optional[str]
135137

@@ -371,10 +373,10 @@ def _collect_ai_data(
371373
event: "MessageStreamEvent",
372374
model: "str | None",
373375
usage: "_RecordedUsage",
374-
content_blocks: "list[Any]",
376+
content_blocks: "list[dict[str, str]]",
375377
response_id: "str | None" = None,
376378
finish_reason: "str | None" = None,
377-
) -> "tuple[str | None, _RecordedUsage, list[Any], str | None, str | None]":
379+
) -> "tuple[str | None, _RecordedUsage, list[dict[str, str]], str | None, str | None]":
378380
"""
379381
Collect model information, token usage, and collect content blocks from the AI streaming response.
380382
"""
@@ -385,18 +387,16 @@ def _collect_ai_data(
385387
event.content_block, ThinkingBlock
386388
):
387389
content_blocks.append({"type": "thinking", "thinking": ""})
388-
else:
390+
elif isinstance(event.content_block, (TextBlock, ToolUseBlock)):
389391
content_blocks.append({"type": "text", "text": ""})
390392
elif event.type == "content_block_delta" and content_blocks:
391-
block = content_blocks[-1]
392-
if block["type"] == "thinking":
393-
block["thinking"] += event.delta.thinking
394-
else:
395-
chunk = getattr(event.delta, "text", None) or getattr(
396-
event.delta, "partial_json", None
397-
)
398-
if chunk:
399-
block["text"] += chunk
393+
delta = event.delta
394+
if ThinkingDelta is not None and isinstance(delta, ThinkingDelta):
395+
content_blocks[-1]["thinking"] += delta.thinking
396+
elif hasattr(event.delta, "text"):
397+
content_blocks[-1]["text"] += event.delta.text
398+
elif hasattr(event.delta, "partial_json"):
399+
content_blocks[-1]["text"] += event.delta.partial_json
400400
elif event.type == "content_block_stop":
401401
pass
402402

@@ -723,7 +723,7 @@ def _set_output_data(
723723
output_tokens: "int | None",
724724
cache_read_input_tokens: "int | None",
725725
cache_write_input_tokens: "int | None",
726-
content_blocks: "list[Any]",
726+
content_blocks: "list[dict[str, str]]",
727727
response_id: "str | None" = None,
728728
finish_reason: "str | None" = None,
729729
) -> None:
@@ -754,9 +754,7 @@ def _set_output_data(
754754
if output["type"] == "text":
755755
parts.append({"type": "text", "content": output["text"]})
756756
elif output["type"] == "thinking":
757-
parts.append(
758-
{"type": "reasoning", "content": output.get("thinking", "")}
759-
)
757+
parts.append({"type": "reasoning", "content": output.get("thinking")})
760758
elif output["type"] == "tool_use":
761759
tool_calls.append(output)
762760

@@ -1060,7 +1058,7 @@ def _set_streaming_output_data(
10601058
integration: "AnthropicIntegration",
10611059
model: "Optional[str]",
10621060
usage: "_RecordedUsage",
1063-
content_blocks: "list[Any]",
1061+
content_blocks: "list[dict[str, str]]",
10641062
response_id: "Optional[str]",
10651063
finish_reason: "Optional[str]",
10661064
) -> None:

0 commit comments

Comments
 (0)