88 truncate_and_annotate_messages ,
99)
1010from sentry_sdk .consts import OP , SPANDATA
11+ from sentry_sdk .traces import StreamedSpan
12+ from sentry_sdk .tracing_utils import has_span_streaming_enabled
1113from sentry_sdk .utils import safe_serialize
1214
1315from ..consts import SPAN_ORIGIN
2729)
2830
2931if TYPE_CHECKING :
30- from typing import Any , Dict , List
32+ from typing import Any , Dict , List , Union
3133
3234 from pydantic_ai .messages import ModelMessage , SystemPromptPart # type: ignore
3335
@@ -97,7 +99,9 @@ def _get_system_instructions(
9799 return permanent_instructions , current_instructions
98100
99101
100- def _set_input_messages (span : "sentry_sdk.tracing.Span" , messages : "Any" ) -> None :
102+ def _set_input_messages (
103+ span : "Union[sentry_sdk.tracing.Span, StreamedSpan]" , messages : "Any"
104+ ) -> None :
101105 """Set input messages data on a span."""
102106 if not _should_send_prompts ():
103107 return
@@ -107,14 +111,24 @@ def _set_input_messages(span: "sentry_sdk.tracing.Span", messages: "Any") -> Non
107111
108112 permanent_instructions , current_instructions = _get_system_instructions (messages )
109113 if len (permanent_instructions ) > 0 or len (current_instructions ) > 0 :
110- span .set_data (
111- SPANDATA .GEN_AI_SYSTEM_INSTRUCTIONS ,
112- json .dumps (
113- _transform_system_instructions (
114- permanent_instructions , current_instructions
115- )
116- ),
117- )
114+ if isinstance (span , StreamedSpan ):
115+ span .set_attribute (
116+ SPANDATA .GEN_AI_SYSTEM_INSTRUCTIONS ,
117+ json .dumps (
118+ _transform_system_instructions (
119+ permanent_instructions , current_instructions
120+ )
121+ ),
122+ )
123+ else :
124+ span .set_data (
125+ SPANDATA .GEN_AI_SYSTEM_INSTRUCTIONS ,
126+ json .dumps (
127+ _transform_system_instructions (
128+ permanent_instructions , current_instructions
129+ )
130+ ),
131+ )
118132
119133 try :
120134 formatted_messages = []
@@ -198,15 +212,21 @@ def _set_input_messages(span: "sentry_sdk.tracing.Span", messages: "Any") -> Non
198212 pass
199213
200214
201- def _set_output_data (span : "sentry_sdk.tracing.Span" , response : "Any" ) -> None :
215+ def _set_output_data (
216+ span : "Union[sentry_sdk.tracing.Span, StreamedSpan]" , response : "Any"
217+ ) -> None :
202218 """Set output data on a span."""
203219 if not _should_send_prompts ():
204220 return
205221
206222 if not response :
207223 return
208224
209- span .set_data (SPANDATA .GEN_AI_RESPONSE_MODEL , response .model_name )
225+ set_on_span = (
226+ span .set_attribute if isinstance (span , StreamedSpan ) else span .set_data
227+ )
228+ set_on_span (SPANDATA .GEN_AI_RESPONSE_MODEL , response .model_name )
229+
210230 try :
211231 # Extract text from ModelResponse
212232 if hasattr (response , "parts" ):
@@ -230,7 +250,7 @@ def _set_output_data(span: "sentry_sdk.tracing.Span", response: "Any") -> None:
230250 set_data_normalized (span , SPANDATA .GEN_AI_RESPONSE_TEXT , texts )
231251
232252 if tool_calls :
233- span . set_data (
253+ set_on_span (
234254 SPANDATA .GEN_AI_RESPONSE_TOOL_CALLS , safe_serialize (tool_calls )
235255 )
236256
@@ -241,7 +261,7 @@ def _set_output_data(span: "sentry_sdk.tracing.Span", response: "Any") -> None:
241261
242262def ai_client_span (
243263 messages : "Any" , agent : "Any" , model : "Any" , model_settings : "Any"
244- ) -> "sentry_sdk.tracing.Span" :
264+ ) -> "Union[ sentry_sdk.tracing.Span, StreamedSpan] " :
245265 """Create a span for an AI client call (model request).
246266
247267 Args:
@@ -257,20 +277,31 @@ def ai_client_span(
257277
258278 model_name = _get_model_name (model_obj ) or "unknown"
259279
260- span = sentry_sdk .start_span (
261- op = OP .GEN_AI_CHAT ,
262- name = f"chat { model_name } " ,
263- origin = SPAN_ORIGIN ,
264- )
280+ span_streaming = has_span_streaming_enabled (sentry_sdk .get_client ().options )
281+ if span_streaming :
282+ span = sentry_sdk .traces .start_span (
283+ name = f"chat { model_name } " ,
284+ attributes = {
285+ "sentry.op" : OP .GEN_AI_CHAT ,
286+ "sentry.origin" : SPAN_ORIGIN ,
287+ SPANDATA .GEN_AI_OPERATION_NAME : "chat" ,
288+ SPANDATA .GEN_AI_RESPONSE_STREAMING : get_is_streaming (),
289+ },
290+ )
291+ else :
292+ span = sentry_sdk .start_span (
293+ op = OP .GEN_AI_CHAT ,
294+ name = f"chat { model_name } " ,
295+ origin = SPAN_ORIGIN ,
296+ )
265297
266- span .set_data (SPANDATA .GEN_AI_OPERATION_NAME , "chat" )
298+ span .set_data (SPANDATA .GEN_AI_OPERATION_NAME , "chat" )
299+ # Set streaming flag from contextvar
300+ span .set_data (SPANDATA .GEN_AI_RESPONSE_STREAMING , get_is_streaming ())
267301
268302 _set_agent_data (span , agent )
269303 _set_model_data (span , model , model_settings )
270304
271- # Set streaming flag from contextvar
272- span .set_data (SPANDATA .GEN_AI_RESPONSE_STREAMING , get_is_streaming ())
273-
274305 # Add available tools if agent is available
275306 agent_obj = agent or get_current_agent ()
276307 _set_available_tools (span , agent_obj )
@@ -283,7 +314,7 @@ def ai_client_span(
283314
284315
285316def update_ai_client_span (
286- span : "sentry_sdk.tracing.Span" , model_response : "Any"
317+ span : "Union[ sentry_sdk.tracing.Span, StreamedSpan] " , model_response : "Any"
287318) -> None :
288319 """Update the AI client span with response data."""
289320 if not span :
0 commit comments