Skip to content

Commit 005eb66

Browse files
merge
2 parents dd26abc + 0aeec72 commit 005eb66

File tree

4 files changed

+59
-21
lines changed

4 files changed

+59
-21
lines changed

sentry_sdk/integrations/anthropic.py

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,8 @@ def _collect_ai_data(
217217
usage: "_RecordedUsage",
218218
content_blocks: "list[str]",
219219
response_id: "str | None" = None,
220-
) -> "tuple[str | None, _RecordedUsage, list[str], str | None]":
220+
finish_reason: "str | None" = None,
221+
) -> "tuple[str | None, _RecordedUsage, list[str], str | None, str | None]":
221222
"""
222223
Collect model information, token usage, and collect content blocks from the AI streaming response.
223224
"""
@@ -255,6 +256,7 @@ def _collect_ai_data(
255256
usage,
256257
content_blocks,
257258
response_id,
259+
finish_reason,
258260
)
259261

260262
# Counterintuitive, but message_delta contains cumulative token counts :)
@@ -279,18 +281,17 @@ def _collect_ai_data(
279281
usage.cache_read_input_tokens = cache_read_input_tokens
280282
# TODO: Record event.usage.server_tool_use
281283

282-
return (
283-
model,
284-
usage,
285-
content_blocks,
286-
response_id,
287-
)
284+
if event.delta.stop_reason is not None:
285+
finish_reason = event.delta.stop_reason
286+
287+
return (model, usage, content_blocks, response_id, finish_reason)
288288

289289
return (
290290
model,
291291
usage,
292292
content_blocks,
293293
response_id,
294+
finish_reason,
294295
)
295296

296297

@@ -499,6 +500,7 @@ def _wrap_synchronous_message_iterator(
499500
stream._usage,
500501
stream._content_blocks,
501502
stream._response_id,
503+
stream._finish_reason,
502504
)
503505
del stream._span
504506

@@ -547,6 +549,7 @@ async def _wrap_asynchronous_message_iterator(
547549
stream._usage,
548550
stream._content_blocks,
549551
stream._response_id,
552+
stream._finish_reason,
550553
)
551554
del stream._span
552555

@@ -562,12 +565,15 @@ def _set_output_data(
562565
content_blocks: "list[Any]",
563566
finish_span: bool = False,
564567
response_id: "str | None" = None,
568+
finish_reason: "str | None" = None,
565569
) -> None:
566570
"""
567571
Set output data for the span based on the AI response."""
568572
span.set_data(SPANDATA.GEN_AI_RESPONSE_MODEL, model)
569573
if response_id is not None:
570574
span.set_data(SPANDATA.GEN_AI_RESPONSE_ID, response_id)
575+
if finish_reason is not None:
576+
span.set_data(SPANDATA.GEN_AI_RESPONSE_FINISH_REASONS, [finish_reason])
571577
if should_send_default_pii() and integration.include_prompts:
572578
output_messages: "dict[str, list[Any]]" = {
573579
"response": [],
@@ -665,6 +671,7 @@ def _sentry_patched_create_common(f: "Any", *args: "Any", **kwargs: "Any") -> "A
665671
content_blocks=content_blocks,
666672
finish_span=True,
667673
response_id=getattr(result, "id", None),
674+
finish_reason=getattr(result, "stop_reason", None),
668675
)
669676
else:
670677
span.set_data("unknown_response", True)
@@ -720,6 +727,7 @@ def _initialize_data_accumulation_state(stream: "Union[Stream, MessageStream]")
720727
stream._usage = _RecordedUsage()
721728
stream._content_blocks = []
722729
stream._response_id = None
730+
stream._finish_reason = None
723731

724732

725733
def _accumulate_event_data(
@@ -729,18 +737,20 @@ def _accumulate_event_data(
729737
"""
730738
Update accumulated output from a single stream event.
731739
"""
732-
(model, usage, content_blocks, response_id) = _collect_ai_data(
740+
(model, usage, content_blocks, response_id, finish_reason) = _collect_ai_data(
733741
event,
734742
stream._model,
735743
stream._usage,
736744
stream._content_blocks,
737745
stream._response_id,
746+
stream._finish_reason,
738747
)
739748

740749
stream._model = model
741750
stream._usage = usage
742751
stream._content_blocks = content_blocks
743752
stream._response_id = response_id
753+
stream._finish_reason = finish_reason
744754

745755

746756
def _finish_streaming_span(
@@ -750,6 +760,7 @@ def _finish_streaming_span(
750760
usage: "_RecordedUsage",
751761
content_blocks: "list[str]",
752762
response_id: "Optional[str]",
763+
finish_reason: "Optional[str]",
753764
) -> None:
754765
"""
755766
Set output attributes on the AI Client Span and end the span.
@@ -773,6 +784,7 @@ def _finish_streaming_span(
773784
content_blocks=[{"text": "".join(content_blocks), "type": "text"}],
774785
finish_span=True,
775786
response_id=response_id,
787+
finish_reason=finish_reason,
776788
)
777789

778790

@@ -822,6 +834,7 @@ def __next__(self: "Stream") -> "RawMessageStreamEvent":
822834
self._usage,
823835
self._content_blocks,
824836
self._response_id,
837+
self._finish_reason,
825838
)
826839
del self._span
827840
reraise(*exc_info)
@@ -854,6 +867,7 @@ def close(self: "Stream") -> None:
854867
self._usage,
855868
self._content_blocks,
856869
self._response_id,
870+
self._finish_reason,
857871
)
858872
del self._span
859873

@@ -1111,6 +1125,7 @@ def __next__(self: "MessageStream") -> "MessageStreamEvent":
11111125
self._usage,
11121126
self._content_blocks,
11131127
self._response_id,
1128+
self._finish_reason,
11141129
)
11151130
del self._span
11161131
reraise(*exc_info)
@@ -1143,6 +1158,7 @@ def close(self: "MessageStream") -> None:
11431158
self._usage,
11441159
self._content_blocks,
11451160
self._response_id,
1161+
self._finish_reason,
11461162
)
11471163
del self._span
11481164

@@ -1282,6 +1298,7 @@ async def __anext__(self: "AsyncMessageStream") -> "MessageStreamEvent":
12821298
self._usage,
12831299
self._content_blocks,
12841300
self._response_id,
1301+
self._finish_reason,
12851302
)
12861303
del self._span
12871304
reraise(*exc_info)
@@ -1314,6 +1331,7 @@ async def close(self: "AsyncMessageStream") -> None:
13141331
self._usage,
13151332
self._content_blocks,
13161333
self._response_id,
1334+
self._finish_reason,
13171335
)
13181336
del self._span
13191337

sentry_sdk/integrations/langchain.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,8 @@ def on_llm_end(
554554
finish_reason = generation.generation_info.get("finish_reason")
555555
if finish_reason is not None:
556556
span.set_data(
557-
SPANDATA.GEN_AI_RESPONSE_FINISH_REASONS, finish_reason
557+
SPANDATA.GEN_AI_RESPONSE_FINISH_REASONS,
558+
[finish_reason],
558559
)
559560
except AttributeError:
560561
pass

tests/integrations/anthropic/test_anthropic.py

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ async def __call__(self, *args, **kwargs):
6969
role="assistant",
7070
content=[TextBlock(type="text", text="Hi, I'm Claude.")],
7171
type="message",
72+
stop_reason="end_turn",
7273
usage=Usage(input_tokens=10, output_tokens=20),
7374
)
7475

@@ -142,6 +143,7 @@ def test_nonstreaming_create_message(
142143
assert span["data"][SPANDATA.GEN_AI_USAGE_TOTAL_TOKENS] == 30
143144
assert span["data"][SPANDATA.GEN_AI_RESPONSE_STREAMING] is False
144145
assert span["data"][SPANDATA.GEN_AI_RESPONSE_ID] == "msg_01XFDUDYJgAACzvnptvVoYEL"
146+
assert span["data"][SPANDATA.GEN_AI_RESPONSE_FINISH_REASONS] == ["end_turn"]
145147

146148

147149
@pytest.mark.asyncio
@@ -264,7 +266,7 @@ def test_streaming_create_message(
264266
),
265267
ContentBlockStopEvent(type="content_block_stop", index=0),
266268
MessageDeltaEvent(
267-
delta=Delta(),
269+
delta=Delta(stop_reason="max_tokens"),
268270
usage=MessageDeltaUsage(output_tokens=10),
269271
type="message_delta",
270272
),
@@ -329,6 +331,7 @@ def test_streaming_create_message(
329331
assert span["data"][SPANDATA.GEN_AI_USAGE_TOTAL_TOKENS] == 20
330332
assert span["data"][SPANDATA.GEN_AI_RESPONSE_STREAMING] is True
331333
assert span["data"][SPANDATA.GEN_AI_RESPONSE_ID] == "msg_01XFDUDYJgAACzvnptvVoYEL"
334+
assert span["data"][SPANDATA.GEN_AI_RESPONSE_FINISH_REASONS] == ["max_tokens"]
332335

333336

334337
def test_streaming_create_message_next_consumption(
@@ -368,7 +371,7 @@ def test_streaming_create_message_next_consumption(
368371
),
369372
ContentBlockStopEvent(type="content_block_stop", index=0),
370373
MessageDeltaEvent(
371-
delta=Delta(),
374+
delta=Delta(stop_reason="max_tokens"),
372375
usage=MessageDeltaUsage(output_tokens=10),
373376
type="message_delta",
374377
),
@@ -428,6 +431,7 @@ def test_streaming_create_message_next_consumption(
428431
assert span["data"][SPANDATA.GEN_AI_USAGE_TOTAL_TOKENS] == 20
429432
assert span["data"][SPANDATA.GEN_AI_RESPONSE_STREAMING] is True
430433
assert span["data"][SPANDATA.GEN_AI_RESPONSE_ID] == "msg_01XFDUDYJgAACzvnptvVoYEL"
434+
assert span["data"][SPANDATA.GEN_AI_RESPONSE_FINISH_REASONS] == ["max_tokens"]
431435

432436

433437
def test_streaming_create_message_iterator_methods(
@@ -467,7 +471,7 @@ def test_streaming_create_message_iterator_methods(
467471
),
468472
ContentBlockStopEvent(type="content_block_stop", index=0),
469473
MessageDeltaEvent(
470-
delta=Delta(),
474+
delta=Delta(stop_reason="max_tokens"),
471475
usage=MessageDeltaUsage(output_tokens=10),
472476
type="message_delta",
473477
),
@@ -580,7 +584,7 @@ def test_stream_messages(
580584
),
581585
ContentBlockStopEvent(type="content_block_stop", index=0),
582586
MessageDeltaEvent(
583-
delta=Delta(),
587+
delta=Delta(stop_reason="max_tokens"),
584588
usage=MessageDeltaUsage(output_tokens=10),
585589
type="message_delta",
586590
),
@@ -646,6 +650,7 @@ def test_stream_messages(
646650
assert span["data"][SPANDATA.GEN_AI_USAGE_TOTAL_TOKENS] == 20
647651
assert span["data"][SPANDATA.GEN_AI_RESPONSE_STREAMING] is True
648652
assert span["data"][SPANDATA.GEN_AI_RESPONSE_ID] == "msg_01XFDUDYJgAACzvnptvVoYEL"
653+
assert span["data"][SPANDATA.GEN_AI_RESPONSE_FINISH_REASONS] == ["max_tokens"]
649654

650655

651656
def test_stream_messages_next_consumption(
@@ -685,7 +690,7 @@ def test_stream_messages_next_consumption(
685690
),
686691
ContentBlockStopEvent(type="content_block_stop", index=0),
687692
MessageDeltaEvent(
688-
delta=Delta(),
693+
delta=Delta(stop_reason="max_tokens"),
689694
usage=MessageDeltaUsage(output_tokens=10),
690695
type="message_delta",
691696
),
@@ -746,6 +751,7 @@ def test_stream_messages_next_consumption(
746751
assert span["data"][SPANDATA.GEN_AI_USAGE_TOTAL_TOKENS] == 20
747752
assert span["data"][SPANDATA.GEN_AI_RESPONSE_STREAMING] is True
748753
assert span["data"][SPANDATA.GEN_AI_RESPONSE_ID] == "msg_01XFDUDYJgAACzvnptvVoYEL"
754+
assert span["data"][SPANDATA.GEN_AI_RESPONSE_FINISH_REASONS] == ["max_tokens"]
749755

750756

751757
def test_stream_messages_iterator_methods(
@@ -785,7 +791,7 @@ def test_stream_messages_iterator_methods(
785791
),
786792
ContentBlockStopEvent(type="content_block_stop", index=0),
787793
MessageDeltaEvent(
788-
delta=Delta(),
794+
delta=Delta(stop_reason="max_tokens"),
789795
usage=MessageDeltaUsage(output_tokens=10),
790796
type="message_delta",
791797
),
@@ -905,7 +911,7 @@ async def test_streaming_create_message_async(
905911
),
906912
ContentBlockStopEvent(type="content_block_stop", index=0),
907913
MessageDeltaEvent(
908-
delta=Delta(),
914+
delta=Delta(stop_reason="max_tokens"),
909915
usage=MessageDeltaUsage(output_tokens=10),
910916
type="message_delta",
911917
),
@@ -917,6 +923,7 @@ async def test_streaming_create_message_async(
917923
sentry_init(
918924
integrations=[AnthropicIntegration(include_prompts=include_prompts)],
919925
traces_sample_rate=1.0,
926+
default_integrations=False,
920927
send_default_pii=send_default_pii,
921928
)
922929
events = capture_events()
@@ -972,6 +979,7 @@ async def test_streaming_create_message_async(
972979
assert span["data"][SPANDATA.GEN_AI_USAGE_TOTAL_TOKENS] == 20
973980
assert span["data"][SPANDATA.GEN_AI_RESPONSE_STREAMING] is True
974981
assert span["data"][SPANDATA.GEN_AI_RESPONSE_ID] == "msg_01XFDUDYJgAACzvnptvVoYEL"
982+
assert span["data"][SPANDATA.GEN_AI_RESPONSE_FINISH_REASONS] == ["max_tokens"]
975983

976984

977985
@pytest.mark.asyncio
@@ -1014,7 +1022,7 @@ async def test_streaming_create_message_async_next_consumption(
10141022
),
10151023
ContentBlockStopEvent(type="content_block_stop", index=0),
10161024
MessageDeltaEvent(
1017-
delta=Delta(),
1025+
delta=Delta(stop_reason="max_tokens"),
10181026
usage=MessageDeltaUsage(output_tokens=10),
10191027
type="message_delta",
10201028
),
@@ -1117,7 +1125,7 @@ async def test_streaming_create_message_async_iterator_methods(
11171125
),
11181126
ContentBlockStopEvent(type="content_block_stop", index=0),
11191127
MessageDeltaEvent(
1120-
delta=Delta(),
1128+
delta=Delta(stop_reason="max_tokens"),
11211129
usage=MessageDeltaUsage(output_tokens=10),
11221130
type="message_delta",
11231131
),
@@ -1184,6 +1192,7 @@ async def test_streaming_create_message_async_iterator_methods(
11841192
assert span["data"][SPANDATA.GEN_AI_USAGE_TOTAL_TOKENS] == 30
11851193
assert span["data"][SPANDATA.GEN_AI_RESPONSE_STREAMING] is True
11861194
assert span["data"][SPANDATA.GEN_AI_RESPONSE_ID] == "msg_01XFDUDYJgAACzvnptvVoYEL"
1195+
assert span["data"][SPANDATA.GEN_AI_RESPONSE_FINISH_REASONS] == ["max_tokens"]
11871196

11881197

11891198
@pytest.mark.asyncio
@@ -1347,7 +1356,7 @@ async def test_stream_messages_async_next_consumption(
13471356
),
13481357
ContentBlockStopEvent(type="content_block_stop", index=0),
13491358
MessageDeltaEvent(
1350-
delta=Delta(),
1359+
delta=Delta(stop_reason="max_tokens"),
13511360
usage=MessageDeltaUsage(output_tokens=10),
13521361
type="message_delta",
13531362
),
@@ -1451,7 +1460,7 @@ async def test_stream_messages_async_iterator_methods(
14511460
),
14521461
ContentBlockStopEvent(type="content_block_stop", index=0),
14531462
MessageDeltaEvent(
1454-
delta=Delta(),
1463+
delta=Delta(stop_reason="max_tokens"),
14551464
usage=MessageDeltaUsage(output_tokens=10),
14561465
type="message_delta",
14571466
),
@@ -1524,6 +1533,7 @@ async def test_stream_messages_async_iterator_methods(
15241533
assert span["data"][SPANDATA.GEN_AI_USAGE_TOTAL_TOKENS] == 30
15251534
assert span["data"][SPANDATA.GEN_AI_RESPONSE_STREAMING] is True
15261535
assert span["data"][SPANDATA.GEN_AI_RESPONSE_ID] == "msg_01XFDUDYJgAACzvnptvVoYEL"
1536+
assert span["data"][SPANDATA.GEN_AI_RESPONSE_FINISH_REASONS] == ["max_tokens"]
15271537

15281538

15291539
@pytest.mark.skipif(
@@ -2315,14 +2325,15 @@ def test_collect_ai_data_with_input_json_delta():
23152325

23162326
content_blocks = []
23172327

2318-
model, new_usage, new_content_blocks, response_id = _collect_ai_data(
2328+
model, new_usage, new_content_blocks, response_id, finish_reason = _collect_ai_data(
23192329
event, model, usage, content_blocks
23202330
)
23212331
assert model is None
23222332
assert new_usage.input_tokens == usage.input_tokens
23232333
assert new_usage.output_tokens == usage.output_tokens
23242334
assert new_content_blocks == ["test"]
23252335
assert response_id is None
2336+
assert finish_reason is None
23262337

23272338

23282339
@pytest.mark.skipif(
@@ -2610,6 +2621,7 @@ def test_nonstreaming_create_message_with_system_prompt(
26102621
assert span["data"][SPANDATA.GEN_AI_USAGE_OUTPUT_TOKENS] == 20
26112622
assert span["data"][SPANDATA.GEN_AI_USAGE_TOTAL_TOKENS] == 30
26122623
assert span["data"][SPANDATA.GEN_AI_RESPONSE_STREAMING] is False
2624+
assert span["data"][SPANDATA.GEN_AI_RESPONSE_FINISH_REASONS] == ["end_turn"]
26132625

26142626

26152627
@pytest.mark.asyncio
@@ -2695,6 +2707,7 @@ async def test_nonstreaming_create_message_with_system_prompt_async(
26952707
assert span["data"][SPANDATA.GEN_AI_USAGE_OUTPUT_TOKENS] == 20
26962708
assert span["data"][SPANDATA.GEN_AI_USAGE_TOTAL_TOKENS] == 30
26972709
assert span["data"][SPANDATA.GEN_AI_RESPONSE_STREAMING] is False
2710+
assert span["data"][SPANDATA.GEN_AI_RESPONSE_FINISH_REASONS] == ["end_turn"]
26982711

26992712

27002713
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)