Skip to content

Commit c650c5b

Browse files
fix(pydantic-ai): Remove agent stack context variable
1 parent d7483fa commit c650c5b

5 files changed

Lines changed: 17 additions & 80 deletions

File tree

sentry_sdk/integrations/pydantic_ai/patches/agent_run.py

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from sentry_sdk.utils import capture_internal_exceptions, reraise
77

88
from ..spans import invoke_agent_span, update_invoke_agent_span
9-
from ..utils import _capture_exception, pop_agent, push_agent
9+
from ..utils import _capture_exception
1010

1111
from typing import TYPE_CHECKING
1212

@@ -56,10 +56,6 @@ async def __aenter__(self) -> "Any":
5656
)
5757
self._span.__enter__()
5858

59-
# Push agent to contextvar stack after span is successfully created and entered
60-
# This ensures proper pairing with pop_agent() in __aexit__ even if exceptions occur
61-
push_agent(self.agent, self.is_streaming)
62-
6359
# Enter the original context manager
6460
result = await self.original_ctx_manager.__aenter__()
6561
self._result = result
@@ -74,9 +70,6 @@ async def __aexit__(self, exc_type: "Any", exc_val: "Any", exc_tb: "Any") -> Non
7470
if exc_type is None and self._result and self._span is not None:
7571
update_invoke_agent_span(self._span, self._result)
7672
finally:
77-
# Pop agent from contextvar stack
78-
pop_agent()
79-
8073
# Clean up invoke span
8174
if self._span:
8275
self._span.__exit__(exc_type, exc_val, exc_tb)
@@ -111,10 +104,6 @@ async def wrapper(self: "Any", *args: "Any", **kwargs: "Any") -> "Any":
111104
with invoke_agent_span(
112105
user_prompt, self, model, model_settings, is_streaming
113106
) as span:
114-
# Push agent to contextvar stack after span is successfully created and entered
115-
# This ensures proper pairing with pop_agent() in finally even if exceptions occur
116-
push_agent(self, is_streaming)
117-
118107
try:
119108
result = await original_func(self, *args, **kwargs)
120109

@@ -127,9 +116,6 @@ async def wrapper(self: "Any", *args: "Any", **kwargs: "Any") -> "Any":
127116
with capture_internal_exceptions():
128117
_capture_exception(exc)
129118
reraise(*exc_info)
130-
finally:
131-
# Pop agent from contextvar stack
132-
pop_agent()
133119

134120
return wrapper
135121

sentry_sdk/integrations/pydantic_ai/patches/tools.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from sentry_sdk.utils import capture_internal_exceptions, reraise
77

88
from ..spans import execute_tool_span, update_execute_tool_span
9-
from ..utils import _capture_exception, get_current_agent
9+
from ..utils import _capture_exception
1010

1111
from typing import TYPE_CHECKING
1212

@@ -57,10 +57,7 @@ async def wrapped_execute_tool_call(
5757
if tool and HAS_MCP and isinstance(tool.toolset, MCPServer):
5858
tool_type = "mcp"
5959

60-
# Get agent from contextvar
61-
agent = get_current_agent()
62-
63-
if agent and tool:
60+
if tool:
6461
try:
6562
args_dict = call.args_as_dict()
6663
except Exception:
@@ -72,7 +69,7 @@ async def wrapped_execute_tool_call(
7269
with execute_tool_span(
7370
name,
7471
args_dict,
75-
agent,
72+
validated.ctx.agent,
7673
tool_type=tool_type,
7774
tool_definition=selected_tool_definition,
7875
) as span:
@@ -136,10 +133,7 @@ async def wrapped_call_tool(
136133
if tool and HAS_MCP and isinstance(tool.toolset, MCPServer):
137134
tool_type = "mcp"
138135

139-
# Get agent from contextvar
140-
agent = get_current_agent()
141-
142-
if agent and tool:
136+
if tool:
143137
try:
144138
args_dict = call.args_as_dict()
145139
except Exception:
@@ -151,7 +145,7 @@ async def wrapped_call_tool(
151145
with execute_tool_span(
152146
name,
153147
args_dict,
154-
agent,
148+
call.ctx.agent,
155149
tool_type=tool_type,
156150
tool_definition=selected_tool_definition,
157151
) as span:

sentry_sdk/integrations/pydantic_ai/spans/ai_client.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
_set_model_data,
1717
_should_send_prompts,
1818
_get_model_name,
19-
get_current_agent,
2019
)
2120
from .utils import (
2221
_serialize_binary_content_item,
@@ -261,11 +260,9 @@ def ai_client_span(
261260
span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "chat")
262261

263262
_set_agent_data(span, agent)
264-
_set_model_data(span, model, model_settings)
263+
_set_model_data(span, agent, model, model_settings)
265264

266-
# Add available tools if agent is available
267-
agent_obj = agent or get_current_agent()
268-
_set_available_tools(span, agent_obj)
265+
_set_available_tools(span, agent)
269266

270267
# Set input messages (full conversation history)
271268
if messages:

sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def invoke_agent_span(
5454
span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "invoke_agent")
5555

5656
_set_agent_data(span, agent)
57-
_set_model_data(span, model, model_settings)
57+
_set_model_data(span, agent, model, model_settings)
5858
_set_available_tools(span, agent)
5959

6060
# Add user prompt and system prompts if available and prompts are enabled

sentry_sdk/integrations/pydantic_ai/utils.py

Lines changed: 8 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import sentry_sdk
2-
from contextvars import ContextVar
32
from sentry_sdk.consts import SPANDATA
43
from sentry_sdk.scope import should_send_default_pii
54
from sentry_sdk.tracing_utils import set_span_errored
@@ -11,36 +10,6 @@
1110
from typing import Any, Optional
1211

1312

14-
# Store the current agent context in a contextvar for re-entrant safety
15-
# Using a list as a stack to support nested agent calls
16-
_agent_context_stack: "ContextVar[list[dict[str, Any]]]" = ContextVar(
17-
"pydantic_ai_agent_context_stack", default=[]
18-
)
19-
20-
21-
def push_agent(agent: "Any", is_streaming: bool = False) -> None:
22-
"""Push an agent context onto the stack along with its streaming flag."""
23-
stack = _agent_context_stack.get().copy()
24-
stack.append({"agent": agent, "is_streaming": is_streaming})
25-
_agent_context_stack.set(stack)
26-
27-
28-
def pop_agent() -> None:
29-
"""Pop an agent context from the stack."""
30-
stack = _agent_context_stack.get().copy()
31-
if stack:
32-
stack.pop()
33-
_agent_context_stack.set(stack)
34-
35-
36-
def get_current_agent() -> "Any":
37-
"""Get the current agent from the contextvar stack."""
38-
stack = _agent_context_stack.get()
39-
if stack:
40-
return stack[-1]["agent"]
41-
return None
42-
43-
4413
def _should_send_prompts() -> bool:
4514
"""
4615
Check if prompts should be sent to Sentry.
@@ -66,16 +35,10 @@ def _set_agent_data(span: "sentry_sdk.tracing.Span", agent: "Any") -> None:
6635
6736
Args:
6837
span: The span to set data on
69-
agent: Agent object (can be None, will try to get from contextvar if not provided)
38+
agent: Agent object
7039
"""
71-
# Extract agent name from agent object or contextvar
72-
agent_obj = agent
73-
if not agent_obj:
74-
# Try to get from contextvar
75-
agent_obj = get_current_agent()
76-
77-
if agent_obj and hasattr(agent_obj, "name") and agent_obj.name:
78-
span.set_data(SPANDATA.GEN_AI_AGENT_NAME, agent_obj.name)
40+
if agent and hasattr(agent, "name") and agent.name:
41+
span.set_data(SPANDATA.GEN_AI_AGENT_NAME, agent.name)
7942

8043

8144
def _get_model_name(model_obj: "Any") -> "Optional[str]":
@@ -104,7 +67,7 @@ def _get_model_name(model_obj: "Any") -> "Optional[str]":
10467

10568

10669
def _set_model_data(
107-
span: "sentry_sdk.tracing.Span", model: "Any", model_settings: "Any"
70+
span: "sentry_sdk.tracing.Span", agent: "Any", model: "Any", model_settings: "Any"
10871
) -> None:
10972
"""Set model-related data on a span.
11073
@@ -113,13 +76,10 @@ def _set_model_data(
11376
model: Model object (can be None, will try to get from agent if not provided)
11477
model_settings: Model settings (can be None, will try to get from agent if not provided)
11578
"""
116-
# Try to get agent from contextvar if we need it
117-
agent_obj = get_current_agent()
118-
11979
# Extract model information
12080
model_obj = model
121-
if not model_obj and agent_obj and hasattr(agent_obj, "model"):
122-
model_obj = agent_obj.model
81+
if not model_obj and agent and hasattr(agent, "model"):
82+
model_obj = agent.model
12383

12484
if model_obj:
12585
# Set system from model
@@ -133,8 +93,8 @@ def _set_model_data(
13393

13494
# Extract model settings
13595
settings = model_settings
136-
if not settings and agent_obj and hasattr(agent_obj, "model_settings"):
137-
settings = agent_obj.model_settings
96+
if not settings and agent and hasattr(agent, "model_settings"):
97+
settings = agent.model_settings
13898

13999
if settings:
140100
settings_map = {

0 commit comments

Comments
 (0)