Skip to content

Commit 059c68e

Browse files
feat(langgraph): Support span streaming (#6406)
Do not create spans for `StateGraph.compile()` when the streaming trace lifecycle is selected.
1 parent 0e237dd commit 059c68e

2 files changed

Lines changed: 137 additions & 24 deletions

File tree

sentry_sdk/integrations/langgraph.py

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,11 @@ def _parse_langgraph_messages(state: "Any") -> "Optional[List[Any]]":
112112
def _wrap_state_graph_compile(f: "Callable[..., Any]") -> "Callable[..., Any]":
113113
@wraps(f)
114114
def new_compile(self: "Any", *args: "Any", **kwargs: "Any") -> "Any":
115-
integration = sentry_sdk.get_client().get_integration(LanggraphIntegration)
116-
if integration is None:
115+
client = sentry_sdk.get_client()
116+
integration = client.get_integration(LanggraphIntegration)
117+
if integration is None or has_span_streaming_enabled(client.options):
117118
return f(self, *args, **kwargs)
119+
118120
with sentry_sdk.start_span(
119121
op=OP.GEN_AI_CREATE_AGENT,
120122
origin=LanggraphIntegration.origin,
@@ -242,11 +244,11 @@ def new_invoke(self: "Any", *args: "Any", **kwargs: "Any") -> "Any":
242244
client = sentry_sdk.get_client()
243245
scope = sentry_sdk.get_current_scope()
244246
messages_data = (
245-
normalized_input_messages
246-
if client.options.get("stream_gen_ai_spans", False)
247-
else truncate_and_annotate_messages(
247+
truncate_and_annotate_messages(
248248
normalized_input_messages, span, scope
249249
)
250+
if should_truncate_gen_ai_input(client.options)
251+
else normalized_input_messages
250252
)
251253
if messages_data is not None:
252254
set_data_normalized(
@@ -268,7 +270,8 @@ def new_invoke(self: "Any", *args: "Any", **kwargs: "Any") -> "Any":
268270
def _wrap_pregel_ainvoke(f: "Callable[..., Any]") -> "Callable[..., Any]":
269271
@wraps(f)
270272
async def new_ainvoke(self: "Any", *args: "Any", **kwargs: "Any") -> "Any":
271-
integration = sentry_sdk.get_client().get_integration(LanggraphIntegration)
273+
client = sentry_sdk.get_client()
274+
integration = client.get_integration(LanggraphIntegration)
272275
if integration is None:
273276
return await f(self, *args, **kwargs)
274277

@@ -277,6 +280,54 @@ async def new_ainvoke(self: "Any", *args: "Any", **kwargs: "Any") -> "Any":
277280
f"invoke_agent {graph_name}".strip() if graph_name else "invoke_agent"
278281
)
279282

283+
if has_span_streaming_enabled(client.options):
284+
with sentry_sdk.traces.start_span(
285+
name=span_name,
286+
attributes={
287+
"sentry.op": OP.GEN_AI_INVOKE_AGENT,
288+
"sentry.origin": LanggraphIntegration.origin,
289+
SPANDATA.GEN_AI_OPERATION_NAME: "invoke_agent",
290+
},
291+
) as span:
292+
if graph_name:
293+
span.set_attribute(SPANDATA.GEN_AI_PIPELINE_NAME, graph_name)
294+
span.set_attribute(SPANDATA.GEN_AI_AGENT_NAME, graph_name)
295+
296+
input_messages = None
297+
if (
298+
len(args) > 0
299+
and should_send_default_pii()
300+
and integration.include_prompts
301+
):
302+
input_messages = _parse_langgraph_messages(args[0])
303+
if input_messages:
304+
normalized_input_messages = normalize_message_roles(
305+
input_messages
306+
)
307+
308+
client = sentry_sdk.get_client()
309+
scope = sentry_sdk.get_current_scope()
310+
messages_data = (
311+
truncate_and_annotate_messages(
312+
normalized_input_messages, span, scope
313+
)
314+
if should_truncate_gen_ai_input(client.options)
315+
else normalized_input_messages
316+
)
317+
if messages_data is not None:
318+
set_data_normalized(
319+
span,
320+
SPANDATA.GEN_AI_REQUEST_MESSAGES,
321+
messages_data,
322+
unpack=False,
323+
)
324+
325+
result = await f(self, *args, **kwargs)
326+
327+
_set_response_attributes(span, input_messages, result, integration)
328+
329+
return result
330+
280331
with get_start_span_function()(
281332
op=OP.GEN_AI_INVOKE_AGENT,
282333
name=span_name,
@@ -301,11 +352,11 @@ async def new_ainvoke(self: "Any", *args: "Any", **kwargs: "Any") -> "Any":
301352
client = sentry_sdk.get_client()
302353
scope = sentry_sdk.get_current_scope()
303354
messages_data = (
304-
normalized_input_messages
305-
if client.options.get("stream_gen_ai_spans", False)
306-
else truncate_and_annotate_messages(
355+
truncate_and_annotate_messages(
307356
normalized_input_messages, span, scope
308357
)
358+
if should_truncate_gen_ai_input(client.options)
359+
else normalized_input_messages
309360
)
310361
if messages_data is not None:
311362
set_data_normalized(

0 commit comments

Comments
 (0)