Skip to content

Commit 8654fe3

Browse files
ericapisanisentrivana
authored andcommitted
feat(langgraph): Gate prompt/response collection on data_collection option (#7175)
Modify the LangGraph integration to respect the data_collection config for controlling whether prompts, responses, tool calls, and available tools are captured in spans. When data collection is enabled, the gen_ai.inputs flag controls request messages, tool calls, and available tools, while gen_ai.outputs controls the response text. Tool calls are gated on inputs because they are fed back to the model as input. Available tools are only gated once data collection is configured, since they were never gated on the legacy PII settings. When data collection is not configured, falls back to legacy send_default_pii and include_prompts settings for compatibility. Refs #6748 <br>Refs #6748
1 parent 15fff61 commit 8654fe3

2 files changed

Lines changed: 593 additions & 49 deletions

File tree

sentry_sdk/integrations/langgraph.py

Lines changed: 57 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@
1919
has_span_streaming_enabled,
2020
should_truncate_gen_ai_input,
2121
)
22-
from sentry_sdk.utils import package_version, safe_serialize
22+
from sentry_sdk.utils import (
23+
has_data_collection_enabled,
24+
package_version,
25+
safe_serialize,
26+
)
2327

2428
try:
2529
from langgraph.errors import GraphBubbleUp
@@ -56,6 +60,24 @@ def setup_once() -> None:
5660
Pregel.ainvoke = _wrap_pregel_ainvoke(Pregel.ainvoke)
5761

5862

63+
def _should_record_inputs(integration: "LanggraphIntegration") -> bool:
64+
client = sentry_sdk.get_client()
65+
if has_data_collection_enabled(client.options):
66+
return bool(client.options["data_collection"]["gen_ai"]["inputs"])
67+
68+
# To remove once data collection has been fully rolled out
69+
return should_send_default_pii() and integration.include_prompts
70+
71+
72+
def _should_record_outputs(integration: "LanggraphIntegration") -> bool:
73+
client = sentry_sdk.get_client()
74+
if has_data_collection_enabled(client.options):
75+
return bool(client.options["data_collection"]["gen_ai"]["outputs"])
76+
77+
# To remove once data collection has been fully rolled out
78+
return should_send_default_pii() and integration.include_prompts
79+
80+
5981
def _get_graph_name(graph_obj: "Any") -> "Optional[str]":
6082
for attr in ["name", "graph_name", "__name__", "_name"]:
6183
if hasattr(graph_obj, attr):
@@ -156,7 +178,13 @@ def new_compile(self: "Any", *args: "Any", **kwargs: "Any") -> "Any":
156178
tools = list(data.tools_by_name.keys())
157179

158180
if tools is not None:
159-
span.set_data(SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS, tools)
181+
# Available tools aren't gated on the legacy PII settings, so they're
182+
# only gated when data collection has been configured.
183+
if has_data_collection_enabled(client.options):
184+
if client.options["data_collection"]["gen_ai"]["inputs"]:
185+
span.set_data(SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS, tools)
186+
else:
187+
span.set_data(SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS, tools)
160188

161189
return compiled_graph
162190

@@ -191,18 +219,13 @@ def new_invoke(self: "Any", *args: "Any", **kwargs: "Any") -> "Any":
191219

192220
# Store input messages to later compare with output
193221
input_messages = None
194-
if (
195-
len(args) > 0
196-
and should_send_default_pii()
197-
and integration.include_prompts
198-
):
222+
if len(args) > 0:
199223
input_messages = _parse_langgraph_messages(args[0])
200-
if input_messages:
224+
if input_messages and _should_record_inputs(integration):
201225
normalized_input_messages = normalize_message_roles(
202226
input_messages
203227
)
204228

205-
client = sentry_sdk.get_client()
206229
scope = sentry_sdk.get_current_scope()
207230
messages_data = (
208231
truncate_and_annotate_messages(
@@ -238,18 +261,13 @@ def new_invoke(self: "Any", *args: "Any", **kwargs: "Any") -> "Any":
238261

239262
# Store input messages to later compare with output
240263
input_messages = None
241-
if (
242-
len(args) > 0
243-
and should_send_default_pii()
244-
and integration.include_prompts
245-
):
264+
if len(args) > 0:
246265
input_messages = _parse_langgraph_messages(args[0])
247-
if input_messages:
266+
if input_messages and _should_record_inputs(integration):
248267
normalized_input_messages = normalize_message_roles(
249268
input_messages
250269
)
251270

252-
client = sentry_sdk.get_client()
253271
scope = sentry_sdk.get_current_scope()
254272
messages_data = (
255273
truncate_and_annotate_messages(
@@ -302,18 +320,13 @@ async def new_ainvoke(self: "Any", *args: "Any", **kwargs: "Any") -> "Any":
302320
span.set_attribute(SPANDATA.GEN_AI_AGENT_NAME, graph_name)
303321

304322
input_messages = None
305-
if (
306-
len(args) > 0
307-
and should_send_default_pii()
308-
and integration.include_prompts
309-
):
323+
if len(args) > 0:
310324
input_messages = _parse_langgraph_messages(args[0])
311-
if input_messages:
325+
if input_messages and _should_record_inputs(integration):
312326
normalized_input_messages = normalize_message_roles(
313327
input_messages
314328
)
315329

316-
client = sentry_sdk.get_client()
317330
scope = sentry_sdk.get_current_scope()
318331
messages_data = (
319332
truncate_and_annotate_messages(
@@ -348,16 +361,11 @@ async def new_ainvoke(self: "Any", *args: "Any", **kwargs: "Any") -> "Any":
348361
span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "invoke_agent")
349362

350363
input_messages = None
351-
if (
352-
len(args) > 0
353-
and should_send_default_pii()
354-
and integration.include_prompts
355-
):
364+
if len(args) > 0:
356365
input_messages = _parse_langgraph_messages(args[0])
357-
if input_messages:
366+
if input_messages and _should_record_inputs(integration):
358367
normalized_input_messages = normalize_message_roles(input_messages)
359368

360-
client = sentry_sdk.get_client()
361369
scope = sentry_sdk.get_current_scope()
362370
messages_data = (
363371
truncate_and_annotate_messages(
@@ -497,22 +505,22 @@ def _set_response_attributes(
497505
_set_usage_data(span, new_messages)
498506
_set_response_model_name(span, new_messages)
499507

500-
if not (should_send_default_pii() and integration.include_prompts):
501-
return
502-
503-
llm_response_text = _extract_llm_response_text(new_messages)
504-
if llm_response_text:
505-
set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, llm_response_text)
506-
elif new_messages:
507-
set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, new_messages)
508-
else:
509-
set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, result)
510-
511-
tool_calls = _extract_tool_calls(new_messages)
512-
if tool_calls:
513-
set_data_normalized(
514-
span,
515-
SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS,
516-
safe_serialize(tool_calls),
517-
unpack=False,
518-
)
508+
if _should_record_outputs(integration):
509+
llm_response_text = _extract_llm_response_text(new_messages)
510+
if llm_response_text:
511+
set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, llm_response_text)
512+
elif new_messages:
513+
set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, new_messages)
514+
else:
515+
set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, result)
516+
517+
# Tool calls are an input to the model, so they're gated on inputs
518+
if _should_record_inputs(integration):
519+
tool_calls = _extract_tool_calls(new_messages)
520+
if tool_calls:
521+
set_data_normalized(
522+
span,
523+
SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS,
524+
safe_serialize(tool_calls),
525+
unpack=False,
526+
)

0 commit comments

Comments
 (0)