Skip to content
Open
Changes from 2 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
31 changes: 30 additions & 1 deletion sentry_sdk/integrations/langchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,14 @@ def on_chat_model_start(self, serialized, messages, *, run_id, **kwargs):
elif "openai" in ai_type:
span.set_data(SPANDATA.GEN_AI_SYSTEM, "openai")

agent_name = (
sentry_sdk.get_current_scope()
._contexts.get("langchain_agent", {})
.get("agent_name")
)
if agent_name:
span.set_data(SPANDATA.GEN_AI_AGENT_NAME, agent_name)

for key, attribute in DATA_FIELDS.items():
if key in all_params and all_params[key] is not None:
set_data_normalized(span, attribute, all_params[key], unpack=False)
Expand Down Expand Up @@ -428,6 +436,14 @@ def on_tool_start(self, serialized, input_str, *, run_id, **kwargs):
if tool_description is not None:
span.set_data(SPANDATA.GEN_AI_TOOL_DESCRIPTION, tool_description)

agent_name = (
sentry_sdk.get_current_scope()
._contexts.get("langchain_agent", {})
.get("agent_name")
)
if agent_name:
span.set_data(SPANDATA.GEN_AI_AGENT_NAME, agent_name)

if should_send_default_pii() and self.include_prompts:
set_data_normalized(
span,
Expand Down Expand Up @@ -756,6 +772,9 @@ def new_invoke(self, *args, **kwargs):
name=f"invoke_agent {agent_name}" if agent_name else "invoke_agent",
origin=LangchainIntegration.origin,
) as span:
sentry_sdk.get_current_scope().set_context(
"langchain_agent", {"agent_name": agent_name, "tools": tools}
)
if agent_name:
span.set_data(SPANDATA.GEN_AI_AGENT_NAME, agent_name)

Expand Down Expand Up @@ -794,6 +813,8 @@ def new_invoke(self, *args, **kwargs):
):
set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, output)

sentry_sdk.get_current_scope().remove_context("langchain_agent")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Context Leak in Exception Handling

Context leak in _wrap_agent_executor_invoke: if f(self, *args, **kwargs) throws an exception after the context is set (line 775-777), the remove_context call at line 816 will never execute, causing the "langchain_agent" context to persist beyond its intended scope.

Fix in Cursor Fix in Web


return result

return new_invoke
Expand All @@ -814,11 +835,15 @@ def new_stream(self, *args, **kwargs):

span = start_span_function(
op=OP.GEN_AI_INVOKE_AGENT,
name=f"invoke_agent {agent_name}".strip(),
name=f"invoke_agent {agent_name}" if agent_name else "invoke_agent",
origin=LangchainIntegration.origin,
)
span.__enter__()

sentry_sdk.get_current_scope().set_context(
"langchain_agent", {"agent_name": agent_name, "tools": tools}
)

if agent_name:
span.set_data(SPANDATA.GEN_AI_AGENT_NAME, agent_name)

Expand Down Expand Up @@ -868,6 +893,8 @@ def new_iterator():
):
set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, output)

sentry_sdk.get_current_scope().remove_context("langchain_agent")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Context Leak in Exception Handling

Context leak in _wrap_agent_executor_stream: if f(self, *args, **kwargs) throws an exception after the context is set (line 843-845), the iterators containing the remove_context calls (lines 896, 917) are never created, causing the "langchain_agent" context to leak permanently.

Fix in Cursor Fix in Web


span.__exit__(None, None, None)

async def new_iterator_async():
Expand All @@ -887,6 +914,8 @@ async def new_iterator_async():
):
set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, output)

sentry_sdk.get_current_scope().remove_context("langchain_agent")

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Agent Context Stack: Unbalanced Push/Pop

In _wrap_agent_executor_stream, _push_agent is called immediately at line 868, but _pop_agent is only called inside the iterator functions (lines 919 and 940) which execute lazily when the iterator is consumed. If the returned iterator is never consumed or only partially consumed, the agent name remains on the contextvar stack indefinitely, causing a memory leak and incorrect agent name propagation to unrelated spans in the same async context.

Fix in Cursor Fix in Web

span.__exit__(None, None, None)

if str(type(result)) == "<class 'async_generator'>":
Expand Down