Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 53 additions & 49 deletions sentry_sdk/integrations/langgraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
has_span_streaming_enabled,
should_truncate_gen_ai_input,
)
from sentry_sdk.utils import safe_serialize
from sentry_sdk.utils import has_data_collection_enabled, safe_serialize

try:
from langgraph.errors import GraphBubbleUp
Expand Down Expand Up @@ -53,6 +53,24 @@
Pregel.ainvoke = _wrap_pregel_ainvoke(Pregel.ainvoke)


def _should_record_inputs(integration: "LanggraphIntegration") -> bool:
client = sentry_sdk.get_client()
if has_data_collection_enabled(client.options):
return bool(client.options["data_collection"]["gen_ai"]["inputs"])

# To remove once data collection has been fully rolled out
return should_send_default_pii() and integration.include_prompts


def _should_record_outputs(integration: "LanggraphIntegration") -> bool:
client = sentry_sdk.get_client()
if has_data_collection_enabled(client.options):
return bool(client.options["data_collection"]["gen_ai"]["outputs"])

# To remove once data collection has been fully rolled out
return should_send_default_pii() and integration.include_prompts


def _get_graph_name(graph_obj: "Any") -> "Optional[str]":
for attr in ["name", "graph_name", "__name__", "_name"]:
if hasattr(graph_obj, attr):
Expand Down Expand Up @@ -153,7 +171,13 @@
tools = list(data.tools_by_name.keys())

if tools is not None:
span.set_data(SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS, tools)
# Available tools aren't gated on the legacy PII settings, so they're
# only gated when data collection has been configured.
if has_data_collection_enabled(client.options):
if client.options["data_collection"]["gen_ai"]["inputs"]:
span.set_data(SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS, tools)
else:
span.set_data(SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS, tools)

return compiled_graph

Expand Down Expand Up @@ -188,18 +212,13 @@

# Store input messages to later compare with output
input_messages = None
if (
len(args) > 0
and should_send_default_pii()
and integration.include_prompts
):
if len(args) > 0:
input_messages = _parse_langgraph_messages(args[0])
if input_messages:
if input_messages and _should_record_inputs(integration):
normalized_input_messages = normalize_message_roles(
input_messages
)

client = sentry_sdk.get_client()
scope = sentry_sdk.get_current_scope()
messages_data = (
truncate_and_annotate_messages(
Expand Down Expand Up @@ -235,18 +254,13 @@

# Store input messages to later compare with output
input_messages = None
if (
len(args) > 0
and should_send_default_pii()
and integration.include_prompts
):
if len(args) > 0:
input_messages = _parse_langgraph_messages(args[0])
if input_messages:
if input_messages and _should_record_inputs(integration):
normalized_input_messages = normalize_message_roles(
input_messages
)

client = sentry_sdk.get_client()
scope = sentry_sdk.get_current_scope()
messages_data = (
truncate_and_annotate_messages(
Expand Down Expand Up @@ -299,18 +313,13 @@
span.set_attribute(SPANDATA.GEN_AI_AGENT_NAME, graph_name)

input_messages = None
if (
len(args) > 0
and should_send_default_pii()
and integration.include_prompts
):
if len(args) > 0:
input_messages = _parse_langgraph_messages(args[0])
if input_messages:
if input_messages and _should_record_inputs(integration):
normalized_input_messages = normalize_message_roles(
input_messages
)

client = sentry_sdk.get_client()
scope = sentry_sdk.get_current_scope()
messages_data = (
truncate_and_annotate_messages(
Expand Down Expand Up @@ -345,16 +354,11 @@
span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "invoke_agent")

input_messages = None
if (
len(args) > 0
and should_send_default_pii()
and integration.include_prompts
):
if len(args) > 0:
input_messages = _parse_langgraph_messages(args[0])
if input_messages:
if input_messages and _should_record_inputs(integration):
normalized_input_messages = normalize_message_roles(input_messages)

client = sentry_sdk.get_client()
scope = sentry_sdk.get_current_scope()
messages_data = (
truncate_and_annotate_messages(
Expand Down Expand Up @@ -494,22 +498,22 @@
_set_usage_data(span, new_messages)
_set_response_model_name(span, new_messages)

if not (should_send_default_pii() and integration.include_prompts):
return

llm_response_text = _extract_llm_response_text(new_messages)
if llm_response_text:
set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, llm_response_text)
elif new_messages:
set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, new_messages)
else:
set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, result)

tool_calls = _extract_tool_calls(new_messages)
if tool_calls:
set_data_normalized(
span,
SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS,
safe_serialize(tool_calls),
unpack=False,
)
if _should_record_outputs(integration):
llm_response_text = _extract_llm_response_text(new_messages)
if llm_response_text:
set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, llm_response_text)
elif new_messages:
set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, new_messages)

Check warning on line 506 in sentry_sdk/integrations/langgraph.py

View check run for this annotation

@sentry/warden / warden: code-review

Response text fallback can leak gated tool calls

When `_should_record_inputs` is False and an assistant message has empty text content, the `GEN_AI_RESPONSE_TEXT` fallback stores the full messages list, which may include tool calls that should be gated on the inputs setting.
Comment thread
sentry-warden[bot] marked this conversation as resolved.
else:
set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, result)

# Tool calls are an input to the model, so they're gated on inputs
if _should_record_inputs(integration):
tool_calls = _extract_tool_calls(new_messages)
if tool_calls:
set_data_normalized(
span,
SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS,
safe_serialize(tool_calls),
unpack=False,
)
Loading
Loading