@@ -190,7 +190,8 @@ def _collect_ai_data(
190190 usage : "_RecordedUsage" ,
191191 content_blocks : "list[str]" ,
192192 response_id : "str | None" = None ,
193- ) -> "tuple[str | None, _RecordedUsage, list[str], str | None]" :
193+ finish_reason : "str | None" = None ,
194+ ) -> "tuple[str | None, _RecordedUsage, list[str], str | None, str | None]" :
194195 """
195196 Collect model information, token usage, and collect content blocks from the AI streaming response.
196197 """
@@ -228,6 +229,7 @@ def _collect_ai_data(
228229 usage ,
229230 content_blocks ,
230231 response_id ,
232+ finish_reason ,
231233 )
232234
233235 # Counterintuitive, but message_delta contains cumulative token counts :)
@@ -252,18 +254,17 @@ def _collect_ai_data(
252254 usage .cache_read_input_tokens = cache_read_input_tokens
253255 # TODO: Record event.usage.server_tool_use
254256
255- return (
256- model ,
257- usage ,
258- content_blocks ,
259- response_id ,
260- )
257+ if event .delta .stop_reason is not None :
258+ finish_reason = event .delta .stop_reason
259+
260+ return (model , usage , content_blocks , response_id , finish_reason )
261261
262262 return (
263263 model ,
264264 usage ,
265265 content_blocks ,
266266 response_id ,
267+ finish_reason ,
267268 )
268269
269270
@@ -472,6 +473,7 @@ def _wrap_synchronous_message_iterator(
472473 stream ._usage ,
473474 stream ._content_blocks ,
474475 stream ._response_id ,
476+ stream ._finish_reason ,
475477 )
476478 del stream ._span
477479
@@ -489,6 +491,7 @@ async def _wrap_asynchronous_message_iterator(
489491 usage = _RecordedUsage ()
490492 content_blocks : "list[str]" = []
491493 response_id = None
494+ finish_reason = None
492495
493496 try :
494497 async for event in iterator :
@@ -513,12 +516,14 @@ async def _wrap_asynchronous_message_iterator(
513516 usage ,
514517 content_blocks ,
515518 response_id ,
519+ finish_reason ,
516520 ) = _collect_ai_data (
517521 event ,
518522 model ,
519523 usage ,
520524 content_blocks ,
521525 response_id ,
526+ finish_reason ,
522527 )
523528 yield event
524529 finally :
@@ -542,6 +547,7 @@ async def _wrap_asynchronous_message_iterator(
542547 content_blocks = [{"text" : "" .join (content_blocks ), "type" : "text" }],
543548 finish_span = True ,
544549 response_id = response_id ,
550+ finish_reason = finish_reason ,
545551 )
546552
547553
@@ -556,12 +562,15 @@ def _set_output_data(
556562 content_blocks : "list[Any]" ,
557563 finish_span : bool = False ,
558564 response_id : "str | None" = None ,
565+ finish_reason : "str | None" = None ,
559566) -> None :
560567 """
561568 Set output data for the span based on the AI response."""
562569 span .set_data (SPANDATA .GEN_AI_RESPONSE_MODEL , model )
563570 if response_id is not None :
564571 span .set_data (SPANDATA .GEN_AI_RESPONSE_ID , response_id )
572+ if finish_reason is not None :
573+ span .set_data (SPANDATA .GEN_AI_RESPONSE_FINISH_REASONS , [finish_reason ])
565574 if should_send_default_pii () and integration .include_prompts :
566575 output_messages : "dict[str, list[Any]]" = {
567576 "response" : [],
@@ -665,6 +674,7 @@ def _sentry_patched_create_common(f: "Any", *args: "Any", **kwargs: "Any") -> "A
665674 content_blocks = content_blocks ,
666675 finish_span = True ,
667676 response_id = getattr (result , "id" , None ),
677+ finish_reason = getattr (result , "stop_reason" , None ),
668678 )
669679 else :
670680 span .set_data ("unknown_response" , True )
@@ -720,6 +730,7 @@ def _initialize_data_accumulation_state(stream: "Union[Stream, MessageStream]")
720730 stream ._usage = _RecordedUsage ()
721731 stream ._content_blocks = []
722732 stream ._response_id = None
733+ stream ._finish_reason = None
723734
724735
725736def _accumulate_event_data (
@@ -729,18 +740,20 @@ def _accumulate_event_data(
729740 """
730741 Update accumulated output from a single stream event.
731742 """
732- (model , usage , content_blocks , response_id ) = _collect_ai_data (
743+ (model , usage , content_blocks , response_id , finish_reason ) = _collect_ai_data (
733744 event ,
734745 stream ._model ,
735746 stream ._usage ,
736747 stream ._content_blocks ,
737748 stream ._response_id ,
749+ stream ._finish_reason ,
738750 )
739751
740752 stream ._model = model
741753 stream ._usage = usage
742754 stream ._content_blocks = content_blocks
743755 stream ._response_id = response_id
756+ stream ._finish_reason = finish_reason
744757
745758
746759def _finish_streaming_span (
@@ -750,6 +763,7 @@ def _finish_streaming_span(
750763 usage : "_RecordedUsage" ,
751764 content_blocks : "list[str]" ,
752765 response_id : "Optional[str]" ,
766+ finish_reason : "Optional[str]" ,
753767) -> None :
754768 """
755769 Set output attributes on the AI Client Span and end the span.
@@ -773,6 +787,7 @@ def _finish_streaming_span(
773787 content_blocks = [{"text" : "" .join (content_blocks ), "type" : "text" }],
774788 finish_span = True ,
775789 response_id = response_id ,
790+ finish_reason = finish_reason ,
776791 )
777792
778793
@@ -822,6 +837,7 @@ def __next__(self: "Stream") -> "RawMessageStreamEvent":
822837 self ._usage ,
823838 self ._content_blocks ,
824839 self ._response_id ,
840+ self ._finish_reason ,
825841 )
826842 del self ._span
827843 reraise (* exc_info )
@@ -854,6 +870,7 @@ def close(self: "Stream") -> None:
854870 self ._usage ,
855871 self ._content_blocks ,
856872 self ._response_id ,
873+ self ._finish_reason ,
857874 )
858875 del self ._span
859876
@@ -1023,6 +1040,7 @@ def __next__(self: "MessageStream") -> "MessageStreamEvent":
10231040 self ._usage ,
10241041 self ._content_blocks ,
10251042 self ._response_id ,
1043+ self ._finish_reason ,
10261044 )
10271045 del self ._span
10281046 reraise (* exc_info )
@@ -1055,6 +1073,7 @@ def close(self: "MessageStream") -> None:
10551073 self ._usage ,
10561074 self ._content_blocks ,
10571075 self ._response_id ,
1076+ self ._finish_reason ,
10581077 )
10591078 del self ._span
10601079
0 commit comments