sentry-sdk 3.0.0a2__py2.py3-none-any.whl → 3.0.0a4__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of sentry-sdk might be problematic. Click here for more details.

Files changed (159) hide show
  1. sentry_sdk/__init__.py +4 -0
  2. sentry_sdk/_compat.py +5 -12
  3. sentry_sdk/_init_implementation.py +7 -7
  4. sentry_sdk/_log_batcher.py +17 -29
  5. sentry_sdk/_lru_cache.py +7 -9
  6. sentry_sdk/_queue.py +2 -4
  7. sentry_sdk/_types.py +9 -16
  8. sentry_sdk/_werkzeug.py +5 -7
  9. sentry_sdk/ai/monitoring.py +45 -33
  10. sentry_sdk/ai/utils.py +8 -5
  11. sentry_sdk/api.py +91 -87
  12. sentry_sdk/attachments.py +10 -12
  13. sentry_sdk/client.py +119 -159
  14. sentry_sdk/consts.py +432 -223
  15. sentry_sdk/crons/api.py +16 -17
  16. sentry_sdk/crons/decorator.py +25 -27
  17. sentry_sdk/debug.py +4 -6
  18. sentry_sdk/envelope.py +46 -112
  19. sentry_sdk/feature_flags.py +9 -15
  20. sentry_sdk/integrations/__init__.py +24 -19
  21. sentry_sdk/integrations/_asgi_common.py +16 -18
  22. sentry_sdk/integrations/_wsgi_common.py +22 -33
  23. sentry_sdk/integrations/aiohttp.py +33 -31
  24. sentry_sdk/integrations/anthropic.py +43 -38
  25. sentry_sdk/integrations/argv.py +3 -4
  26. sentry_sdk/integrations/ariadne.py +16 -18
  27. sentry_sdk/integrations/arq.py +20 -29
  28. sentry_sdk/integrations/asgi.py +63 -37
  29. sentry_sdk/integrations/asyncio.py +15 -17
  30. sentry_sdk/integrations/asyncpg.py +1 -1
  31. sentry_sdk/integrations/atexit.py +6 -10
  32. sentry_sdk/integrations/aws_lambda.py +26 -36
  33. sentry_sdk/integrations/beam.py +10 -18
  34. sentry_sdk/integrations/boto3.py +20 -18
  35. sentry_sdk/integrations/bottle.py +25 -34
  36. sentry_sdk/integrations/celery/__init__.py +40 -59
  37. sentry_sdk/integrations/celery/beat.py +22 -26
  38. sentry_sdk/integrations/celery/utils.py +15 -17
  39. sentry_sdk/integrations/chalice.py +8 -10
  40. sentry_sdk/integrations/clickhouse_driver.py +22 -32
  41. sentry_sdk/integrations/cloud_resource_context.py +9 -16
  42. sentry_sdk/integrations/cohere.py +19 -25
  43. sentry_sdk/integrations/dedupe.py +5 -8
  44. sentry_sdk/integrations/django/__init__.py +69 -74
  45. sentry_sdk/integrations/django/asgi.py +25 -33
  46. sentry_sdk/integrations/django/caching.py +24 -20
  47. sentry_sdk/integrations/django/middleware.py +18 -21
  48. sentry_sdk/integrations/django/signals_handlers.py +12 -11
  49. sentry_sdk/integrations/django/templates.py +21 -18
  50. sentry_sdk/integrations/django/transactions.py +16 -11
  51. sentry_sdk/integrations/django/views.py +8 -12
  52. sentry_sdk/integrations/dramatiq.py +21 -21
  53. sentry_sdk/integrations/excepthook.py +10 -10
  54. sentry_sdk/integrations/executing.py +3 -4
  55. sentry_sdk/integrations/falcon.py +27 -42
  56. sentry_sdk/integrations/fastapi.py +13 -16
  57. sentry_sdk/integrations/flask.py +31 -38
  58. sentry_sdk/integrations/gcp.py +13 -16
  59. sentry_sdk/integrations/gnu_backtrace.py +7 -20
  60. sentry_sdk/integrations/gql.py +16 -17
  61. sentry_sdk/integrations/graphene.py +14 -13
  62. sentry_sdk/integrations/grpc/__init__.py +3 -2
  63. sentry_sdk/integrations/grpc/aio/client.py +2 -2
  64. sentry_sdk/integrations/grpc/aio/server.py +15 -14
  65. sentry_sdk/integrations/grpc/client.py +21 -11
  66. sentry_sdk/integrations/grpc/consts.py +2 -0
  67. sentry_sdk/integrations/grpc/server.py +12 -8
  68. sentry_sdk/integrations/httpx.py +11 -14
  69. sentry_sdk/integrations/huey.py +14 -21
  70. sentry_sdk/integrations/huggingface_hub.py +17 -17
  71. sentry_sdk/integrations/langchain.py +204 -114
  72. sentry_sdk/integrations/launchdarkly.py +13 -10
  73. sentry_sdk/integrations/litestar.py +40 -38
  74. sentry_sdk/integrations/logging.py +29 -36
  75. sentry_sdk/integrations/loguru.py +16 -20
  76. sentry_sdk/integrations/modules.py +3 -4
  77. sentry_sdk/integrations/openai.py +421 -204
  78. sentry_sdk/integrations/openai_agents/__init__.py +49 -0
  79. sentry_sdk/integrations/openai_agents/consts.py +1 -0
  80. sentry_sdk/integrations/openai_agents/patches/__init__.py +4 -0
  81. sentry_sdk/integrations/openai_agents/patches/agent_run.py +152 -0
  82. sentry_sdk/integrations/openai_agents/patches/models.py +52 -0
  83. sentry_sdk/integrations/openai_agents/patches/runner.py +42 -0
  84. sentry_sdk/integrations/openai_agents/patches/tools.py +84 -0
  85. sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
  86. sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +20 -0
  87. sentry_sdk/integrations/openai_agents/spans/ai_client.py +46 -0
  88. sentry_sdk/integrations/openai_agents/spans/execute_tool.py +47 -0
  89. sentry_sdk/integrations/openai_agents/spans/handoff.py +24 -0
  90. sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +41 -0
  91. sentry_sdk/integrations/openai_agents/utils.py +153 -0
  92. sentry_sdk/integrations/openfeature.py +12 -8
  93. sentry_sdk/integrations/pure_eval.py +6 -10
  94. sentry_sdk/integrations/pymongo.py +14 -18
  95. sentry_sdk/integrations/pyramid.py +31 -36
  96. sentry_sdk/integrations/quart.py +23 -28
  97. sentry_sdk/integrations/ray.py +73 -64
  98. sentry_sdk/integrations/redis/__init__.py +7 -4
  99. sentry_sdk/integrations/redis/_async_common.py +18 -12
  100. sentry_sdk/integrations/redis/_sync_common.py +16 -15
  101. sentry_sdk/integrations/redis/modules/caches.py +17 -8
  102. sentry_sdk/integrations/redis/modules/queries.py +9 -8
  103. sentry_sdk/integrations/redis/rb.py +3 -2
  104. sentry_sdk/integrations/redis/redis.py +4 -4
  105. sentry_sdk/integrations/redis/redis_cluster.py +10 -8
  106. sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +3 -2
  107. sentry_sdk/integrations/redis/utils.py +21 -22
  108. sentry_sdk/integrations/rq.py +13 -16
  109. sentry_sdk/integrations/rust_tracing.py +10 -7
  110. sentry_sdk/integrations/sanic.py +34 -46
  111. sentry_sdk/integrations/serverless.py +22 -27
  112. sentry_sdk/integrations/socket.py +29 -17
  113. sentry_sdk/integrations/spark/__init__.py +1 -0
  114. sentry_sdk/integrations/spark/spark_driver.py +45 -83
  115. sentry_sdk/integrations/spark/spark_worker.py +7 -11
  116. sentry_sdk/integrations/sqlalchemy.py +22 -19
  117. sentry_sdk/integrations/starlette.py +89 -93
  118. sentry_sdk/integrations/starlite.py +31 -37
  119. sentry_sdk/integrations/statsig.py +5 -4
  120. sentry_sdk/integrations/stdlib.py +32 -28
  121. sentry_sdk/integrations/strawberry.py +63 -50
  122. sentry_sdk/integrations/sys_exit.py +7 -11
  123. sentry_sdk/integrations/threading.py +13 -15
  124. sentry_sdk/integrations/tornado.py +28 -32
  125. sentry_sdk/integrations/trytond.py +4 -3
  126. sentry_sdk/integrations/typer.py +8 -6
  127. sentry_sdk/integrations/unleash.py +5 -4
  128. sentry_sdk/integrations/wsgi.py +47 -46
  129. sentry_sdk/logger.py +13 -9
  130. sentry_sdk/monitor.py +16 -28
  131. sentry_sdk/opentelemetry/consts.py +11 -4
  132. sentry_sdk/opentelemetry/contextvars_context.py +17 -15
  133. sentry_sdk/opentelemetry/propagator.py +38 -21
  134. sentry_sdk/opentelemetry/sampler.py +51 -34
  135. sentry_sdk/opentelemetry/scope.py +46 -37
  136. sentry_sdk/opentelemetry/span_processor.py +43 -59
  137. sentry_sdk/opentelemetry/tracing.py +32 -12
  138. sentry_sdk/opentelemetry/utils.py +180 -196
  139. sentry_sdk/profiler/continuous_profiler.py +108 -97
  140. sentry_sdk/profiler/transaction_profiler.py +70 -97
  141. sentry_sdk/profiler/utils.py +11 -15
  142. sentry_sdk/scope.py +251 -264
  143. sentry_sdk/scrubber.py +22 -26
  144. sentry_sdk/serializer.py +48 -65
  145. sentry_sdk/session.py +44 -61
  146. sentry_sdk/sessions.py +35 -49
  147. sentry_sdk/spotlight.py +15 -21
  148. sentry_sdk/tracing.py +118 -184
  149. sentry_sdk/tracing_utils.py +103 -123
  150. sentry_sdk/transport.py +131 -157
  151. sentry_sdk/utils.py +278 -309
  152. sentry_sdk/worker.py +16 -28
  153. {sentry_sdk-3.0.0a2.dist-info → sentry_sdk-3.0.0a4.dist-info}/METADATA +1 -1
  154. sentry_sdk-3.0.0a4.dist-info/RECORD +168 -0
  155. sentry_sdk-3.0.0a2.dist-info/RECORD +0 -154
  156. {sentry_sdk-3.0.0a2.dist-info → sentry_sdk-3.0.0a4.dist-info}/WHEEL +0 -0
  157. {sentry_sdk-3.0.0a2.dist-info → sentry_sdk-3.0.0a4.dist-info}/entry_points.txt +0 -0
  158. {sentry_sdk-3.0.0a2.dist-info → sentry_sdk-3.0.0a4.dist-info}/licenses/LICENSE +0 -0
  159. {sentry_sdk-3.0.0a2.dist-info → sentry_sdk-3.0.0a4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,49 @@
1
+ from sentry_sdk.integrations import DidNotEnable, Integration
2
+
3
+ from .patches import (
4
+ _create_get_model_wrapper,
5
+ _create_get_all_tools_wrapper,
6
+ _create_run_wrapper,
7
+ _patch_agent_run,
8
+ )
9
+
10
+ try:
11
+ import agents
12
+
13
+ except ImportError:
14
+ raise DidNotEnable("OpenAI Agents not installed")
15
+
16
+
17
+ def _patch_runner() -> None:
18
+ # Create the root span for one full agent run (including eventual handoffs)
19
+ # Note agents.run.DEFAULT_AGENT_RUNNER.run_sync is a wrapper around
20
+ # agents.run.DEFAULT_AGENT_RUNNER.run. It does not need to be wrapped separately.
21
+ # TODO-anton: Also patch streaming runner: agents.Runner.run_streamed
22
+ agents.run.DEFAULT_AGENT_RUNNER.run = _create_run_wrapper(
23
+ agents.run.DEFAULT_AGENT_RUNNER.run
24
+ )
25
+
26
+ # Creating the actual spans for each agent run.
27
+ _patch_agent_run()
28
+
29
+
30
+ def _patch_model() -> None:
31
+ agents.run.AgentRunner._get_model = classmethod(
32
+ _create_get_model_wrapper(agents.run.AgentRunner._get_model),
33
+ )
34
+
35
+
36
+ def _patch_tools() -> None:
37
+ agents.run.AgentRunner._get_all_tools = classmethod(
38
+ _create_get_all_tools_wrapper(agents.run.AgentRunner._get_all_tools),
39
+ )
40
+
41
+
42
+ class OpenAIAgentsIntegration(Integration):
43
+ identifier = "openai_agents"
44
+
45
+ @staticmethod
46
+ def setup_once() -> None:
47
+ _patch_tools()
48
+ _patch_model()
49
+ _patch_runner()
@@ -0,0 +1 @@
1
+ SPAN_ORIGIN = "auto.ai.openai_agents"
@@ -0,0 +1,4 @@
1
+ from .models import _create_get_model_wrapper # noqa: F401
2
+ from .tools import _create_get_all_tools_wrapper # noqa: F401
3
+ from .runner import _create_run_wrapper # noqa: F401
4
+ from .agent_run import _patch_agent_run # noqa: F401
@@ -0,0 +1,152 @@
1
+ from __future__ import annotations
2
+
3
+ from functools import wraps
4
+
5
+ from sentry_sdk.integrations import DidNotEnable
6
+
7
+ from ..spans import invoke_agent_span, update_invoke_agent_span, handoff_span
8
+
9
+ from typing import TYPE_CHECKING
10
+
11
+ if TYPE_CHECKING:
12
+ from typing import Any, Optional
13
+
14
+
15
+ try:
16
+ import agents
17
+ except ImportError:
18
+ raise DidNotEnable("OpenAI Agents not installed")
19
+
20
+
21
+ def _patch_agent_run() -> None:
22
+ """
23
+ Patches AgentRunner methods to create agent invocation spans.
24
+ This directly patches the execution flow to track when agents start and stop.
25
+ """
26
+
27
+ # Store original methods
28
+ original_run_single_turn = agents.run.AgentRunner._run_single_turn
29
+ original_execute_handoffs = agents._run_impl.RunImpl.execute_handoffs
30
+ original_execute_final_output = agents._run_impl.RunImpl.execute_final_output
31
+
32
+ def _start_invoke_agent_span(
33
+ context_wrapper: agents.RunContextWrapper, agent: agents.Agent
34
+ ) -> None:
35
+ """Start an agent invocation span"""
36
+ # Store the agent on the context wrapper so we can access it later
37
+ context_wrapper._sentry_current_agent = agent
38
+ invoke_agent_span(context_wrapper, agent)
39
+
40
+ def _end_invoke_agent_span(
41
+ context_wrapper: agents.RunContextWrapper,
42
+ agent: agents.Agent,
43
+ output: Optional[Any] = None,
44
+ ) -> None:
45
+ """End the agent invocation span"""
46
+ # Clear the stored agent
47
+ if hasattr(context_wrapper, "_sentry_current_agent"):
48
+ delattr(context_wrapper, "_sentry_current_agent")
49
+
50
+ update_invoke_agent_span(context_wrapper, agent, output)
51
+
52
+ def _has_active_agent_span(context_wrapper: agents.RunContextWrapper) -> bool:
53
+ """Check if there's an active agent span for this context"""
54
+ return getattr(context_wrapper, "_sentry_current_agent", None) is not None
55
+
56
+ def _get_current_agent(
57
+ context_wrapper: agents.RunContextWrapper,
58
+ ) -> Optional[agents.Agent]:
59
+ """Get the current agent from context wrapper"""
60
+ return getattr(context_wrapper, "_sentry_current_agent", None)
61
+
62
+ @wraps(
63
+ original_run_single_turn.__func__
64
+ if hasattr(original_run_single_turn, "__func__")
65
+ else original_run_single_turn
66
+ )
67
+ async def patched_run_single_turn(
68
+ cls: agents.Runner, *args: Any, **kwargs: Any
69
+ ) -> Any:
70
+ """Patched _run_single_turn that creates agent invocation spans"""
71
+ agent = kwargs.get("agent")
72
+ context_wrapper = kwargs.get("context_wrapper")
73
+ should_run_agent_start_hooks = kwargs.get("should_run_agent_start_hooks")
74
+
75
+ # Start agent span when agent starts (but only once per agent)
76
+ if should_run_agent_start_hooks and agent and context_wrapper:
77
+ # End any existing span for a different agent
78
+ if _has_active_agent_span(context_wrapper):
79
+ current_agent = _get_current_agent(context_wrapper)
80
+ if current_agent and current_agent != agent:
81
+ _end_invoke_agent_span(context_wrapper, current_agent)
82
+
83
+ _start_invoke_agent_span(context_wrapper, agent)
84
+
85
+ # Call original method with all the correct parameters
86
+ try:
87
+ result = await original_run_single_turn(*args, **kwargs)
88
+ finally:
89
+ if agent and context_wrapper and _has_active_agent_span(context_wrapper):
90
+ _end_invoke_agent_span(context_wrapper, agent)
91
+
92
+ return result
93
+
94
+ @wraps(
95
+ original_execute_handoffs.__func__
96
+ if hasattr(original_execute_handoffs, "__func__")
97
+ else original_execute_handoffs
98
+ )
99
+ async def patched_execute_handoffs(
100
+ cls: agents.Runner, *args: Any, **kwargs: Any
101
+ ) -> Any:
102
+ """Patched execute_handoffs that creates handoff spans and ends agent span for handoffs"""
103
+ context_wrapper = kwargs.get("context_wrapper")
104
+ run_handoffs = kwargs.get("run_handoffs")
105
+ agent = kwargs.get("agent")
106
+
107
+ # Create Sentry handoff span for the first handoff (agents library only processes the first one)
108
+ if run_handoffs:
109
+ first_handoff = run_handoffs[0]
110
+ handoff_agent_name = first_handoff.handoff.agent_name
111
+ handoff_span(context_wrapper, agent, handoff_agent_name)
112
+
113
+ # Call original method with all parameters
114
+ try:
115
+ result = await original_execute_handoffs(*args, **kwargs)
116
+
117
+ finally:
118
+ # End span for current agent after handoff processing is complete
119
+ if agent and context_wrapper and _has_active_agent_span(context_wrapper):
120
+ _end_invoke_agent_span(context_wrapper, agent)
121
+
122
+ return result
123
+
124
+ @wraps(
125
+ original_execute_final_output.__func__
126
+ if hasattr(original_execute_final_output, "__func__")
127
+ else original_execute_final_output
128
+ )
129
+ async def patched_execute_final_output(
130
+ cls: agents.Runner, *args: Any, **kwargs: Any
131
+ ) -> Any:
132
+ """Patched execute_final_output that ends agent span for final outputs"""
133
+ agent = kwargs.get("agent")
134
+ context_wrapper = kwargs.get("context_wrapper")
135
+ final_output = kwargs.get("final_output")
136
+
137
+ # Call original method with all parameters
138
+ try:
139
+ result = await original_execute_final_output(*args, **kwargs)
140
+ finally:
141
+ # End span for current agent after final output processing is complete
142
+ if agent and context_wrapper and _has_active_agent_span(context_wrapper):
143
+ _end_invoke_agent_span(context_wrapper, agent, final_output)
144
+
145
+ return result
146
+
147
+ # Apply patches
148
+ agents.run.AgentRunner._run_single_turn = classmethod(patched_run_single_turn)
149
+ agents._run_impl.RunImpl.execute_handoffs = classmethod(patched_execute_handoffs)
150
+ agents._run_impl.RunImpl.execute_final_output = classmethod(
151
+ patched_execute_final_output
152
+ )
@@ -0,0 +1,52 @@
1
+ from __future__ import annotations
2
+
3
+ from functools import wraps
4
+
5
+ from sentry_sdk.integrations import DidNotEnable
6
+
7
+ from ..spans import ai_client_span, update_ai_client_span
8
+
9
+ from typing import TYPE_CHECKING
10
+
11
+ if TYPE_CHECKING:
12
+ from typing import Any, Callable
13
+
14
+
15
+ try:
16
+ import agents
17
+ except ImportError:
18
+ raise DidNotEnable("OpenAI Agents not installed")
19
+
20
+
21
+ def _create_get_model_wrapper(
22
+ original_get_model: Callable[..., Any],
23
+ ) -> Callable[..., Any]:
24
+ """
25
+ Wraps the agents.Runner._get_model method to wrap the get_response method of the model to create a AI client span.
26
+ """
27
+
28
+ @wraps(
29
+ original_get_model.__func__
30
+ if hasattr(original_get_model, "__func__")
31
+ else original_get_model
32
+ )
33
+ def wrapped_get_model(
34
+ cls: agents.Runner, agent: agents.Agent, run_config: agents.RunConfig
35
+ ) -> agents.Model:
36
+ model = original_get_model(agent, run_config)
37
+ original_get_response = model.get_response
38
+
39
+ @wraps(original_get_response)
40
+ async def wrapped_get_response(*args: Any, **kwargs: Any) -> Any:
41
+ with ai_client_span(agent, kwargs) as span:
42
+ result = await original_get_response(*args, **kwargs)
43
+
44
+ update_ai_client_span(span, agent, kwargs, result)
45
+
46
+ return result
47
+
48
+ model.get_response = wrapped_get_response
49
+
50
+ return model
51
+
52
+ return wrapped_get_model
@@ -0,0 +1,42 @@
1
+ from __future__ import annotations
2
+
3
+ from functools import wraps
4
+
5
+ import sentry_sdk
6
+
7
+ from ..spans import agent_workflow_span
8
+ from ..utils import _capture_exception
9
+
10
+ from typing import TYPE_CHECKING
11
+
12
+ if TYPE_CHECKING:
13
+ from typing import Any, Callable
14
+
15
+
16
+ def _create_run_wrapper(original_func: Callable[..., Any]) -> Callable[..., Any]:
17
+ """
18
+ Wraps the agents.Runner.run methods to create a root span for the agent workflow runs.
19
+
20
+ Note agents.Runner.run_sync() is a wrapper around agents.Runner.run(),
21
+ so it does not need to be wrapped separately.
22
+ """
23
+
24
+ @wraps(original_func)
25
+ async def wrapper(*args: Any, **kwargs: Any) -> Any:
26
+ agent = args[0]
27
+ with agent_workflow_span(agent):
28
+ result = None
29
+ try:
30
+ result = await original_func(*args, **kwargs)
31
+ return result
32
+ except Exception as exc:
33
+ _capture_exception(exc)
34
+
35
+ # It could be that there is a "invoke agent" span still open
36
+ span = sentry_sdk.get_current_span()
37
+ if span is not None and span.timestamp is None:
38
+ span.__exit__(None, None, None)
39
+
40
+ raise exc from None
41
+
42
+ return wrapper
@@ -0,0 +1,84 @@
1
+ from __future__ import annotations
2
+
3
+ from functools import wraps
4
+
5
+ from sentry_sdk.integrations import DidNotEnable
6
+
7
+ from ..spans import execute_tool_span, update_execute_tool_span
8
+
9
+ from typing import TYPE_CHECKING
10
+
11
+ if TYPE_CHECKING:
12
+ from typing import Any, Callable
13
+
14
+ try:
15
+ import agents
16
+ except ImportError:
17
+ raise DidNotEnable("OpenAI Agents not installed")
18
+
19
+
20
+ def _create_get_all_tools_wrapper(
21
+ original_get_all_tools: Callable[..., Any],
22
+ ) -> Callable[..., Any]:
23
+ """
24
+ Wraps the agents.Runner._get_all_tools method of the Runner class to wrap all function tools with Sentry instrumentation.
25
+ """
26
+
27
+ @wraps(
28
+ original_get_all_tools.__func__
29
+ if hasattr(original_get_all_tools, "__func__")
30
+ else original_get_all_tools
31
+ )
32
+ async def wrapped_get_all_tools(
33
+ cls: agents.Runner,
34
+ agent: agents.Agent,
35
+ context_wrapper: agents.RunContextWrapper,
36
+ ) -> list[agents.Tool]:
37
+ # Get the original tools
38
+ tools = await original_get_all_tools(agent, context_wrapper)
39
+
40
+ wrapped_tools = []
41
+ for tool in tools:
42
+ # Wrap only the function tools (for now)
43
+ if tool.__class__.__name__ != "FunctionTool":
44
+ wrapped_tools.append(tool)
45
+ continue
46
+
47
+ # Create a new FunctionTool with our wrapped invoke method
48
+ original_on_invoke = tool.on_invoke_tool
49
+
50
+ def create_wrapped_invoke(
51
+ current_tool: agents.Tool, current_on_invoke: Callable[..., Any]
52
+ ) -> Callable[..., Any]:
53
+ @wraps(current_on_invoke)
54
+ async def sentry_wrapped_on_invoke_tool(
55
+ *args: Any, **kwargs: Any
56
+ ) -> Any:
57
+ with execute_tool_span(current_tool, *args, **kwargs) as span:
58
+ # We can not capture exceptions in tool execution here because
59
+ # `_on_invoke_tool` is swallowing the exception here:
60
+ # https://github.com/openai/openai-agents-python/blob/main/src/agents/tool.py#L409-L422
61
+ # And because function_tool is a decorator with `default_tool_error_function` set as a default parameter
62
+ # I was unable to monkey patch it because those are evaluated at module import time
63
+ # and the SDK is too late to patch it. I was also unable to patch `_on_invoke_tool_impl`
64
+ # because it is nested inside this import time code. As if they made it hard to patch on purpose...
65
+ result = await current_on_invoke(*args, **kwargs)
66
+ update_execute_tool_span(span, agent, current_tool, result)
67
+
68
+ return result
69
+
70
+ return sentry_wrapped_on_invoke_tool
71
+
72
+ wrapped_tool = agents.FunctionTool(
73
+ name=tool.name,
74
+ description=tool.description,
75
+ params_json_schema=tool.params_json_schema,
76
+ on_invoke_tool=create_wrapped_invoke(tool, original_on_invoke),
77
+ strict_json_schema=tool.strict_json_schema,
78
+ is_enabled=tool.is_enabled,
79
+ )
80
+ wrapped_tools.append(wrapped_tool)
81
+
82
+ return wrapped_tools
83
+
84
+ return wrapped_get_all_tools
@@ -0,0 +1,5 @@
1
+ from .agent_workflow import agent_workflow_span # noqa: F401
2
+ from .ai_client import ai_client_span, update_ai_client_span # noqa: F401
3
+ from .execute_tool import execute_tool_span, update_execute_tool_span # noqa: F401
4
+ from .handoff import handoff_span # noqa: F401
5
+ from .invoke_agent import invoke_agent_span, update_invoke_agent_span # noqa: F401
@@ -0,0 +1,20 @@
1
+ from __future__ import annotations
2
+
3
+ import sentry_sdk
4
+
5
+ from ..consts import SPAN_ORIGIN
6
+
7
+ from typing import TYPE_CHECKING
8
+
9
+ if TYPE_CHECKING:
10
+ import agents
11
+
12
+
13
+ def agent_workflow_span(agent: agents.Agent) -> sentry_sdk.tracing.Span:
14
+ # Create a transaction or a span if an transaction is already active
15
+ span = sentry_sdk.start_span(
16
+ name=f"{agent.name} workflow",
17
+ origin=SPAN_ORIGIN,
18
+ )
19
+
20
+ return span
@@ -0,0 +1,46 @@
1
+ from __future__ import annotations
2
+
3
+ import sentry_sdk
4
+ from sentry_sdk.consts import OP, SPANDATA
5
+
6
+ from ..consts import SPAN_ORIGIN
7
+ from ..utils import (
8
+ _set_agent_data,
9
+ _set_input_data,
10
+ _set_output_data,
11
+ _set_usage_data,
12
+ )
13
+
14
+ from typing import TYPE_CHECKING
15
+
16
+ if TYPE_CHECKING:
17
+ from agents import Agent
18
+ from typing import Any
19
+
20
+
21
+ def ai_client_span(
22
+ agent: Agent, get_response_kwargs: dict[str, Any]
23
+ ) -> sentry_sdk.tracing.Span:
24
+ model_name = agent.model.model if hasattr(agent.model, "model") else agent.model
25
+ # TODO-anton: implement other types of operations. Now "chat" is hardcoded.
26
+ span = sentry_sdk.start_span(
27
+ op=OP.GEN_AI_CHAT,
28
+ description=f"chat {model_name}",
29
+ origin=SPAN_ORIGIN,
30
+ )
31
+ # TODO-anton: remove hardcoded stuff and replace something that also works for embedding and so on
32
+ span.set_attribute(SPANDATA.GEN_AI_OPERATION_NAME, "chat")
33
+
34
+ return span
35
+
36
+
37
+ def update_ai_client_span(
38
+ span: sentry_sdk.tracing.Span,
39
+ agent: Agent,
40
+ get_response_kwargs: dict[str, Any],
41
+ result: Any,
42
+ ) -> None:
43
+ _set_agent_data(span, agent)
44
+ _set_usage_data(span, result.usage)
45
+ _set_input_data(span, get_response_kwargs)
46
+ _set_output_data(span, result)
@@ -0,0 +1,47 @@
1
+ from __future__ import annotations
2
+
3
+ import sentry_sdk
4
+ from sentry_sdk.consts import OP, SPANDATA
5
+ from sentry_sdk.scope import should_send_default_pii
6
+
7
+ from ..consts import SPAN_ORIGIN
8
+ from ..utils import _set_agent_data
9
+
10
+ from typing import TYPE_CHECKING
11
+
12
+ if TYPE_CHECKING:
13
+ import agents
14
+ from typing import Any
15
+
16
+
17
+ def execute_tool_span(
18
+ tool: agents.Tool, *args: Any, **kwargs: Any
19
+ ) -> sentry_sdk.tracing.Span:
20
+ span = sentry_sdk.start_span(
21
+ op=OP.GEN_AI_EXECUTE_TOOL,
22
+ name=f"execute_tool {tool.name}",
23
+ origin=SPAN_ORIGIN,
24
+ )
25
+
26
+ span.set_attribute(SPANDATA.GEN_AI_OPERATION_NAME, "execute_tool")
27
+
28
+ if tool.__class__.__name__ == "FunctionTool":
29
+ span.set_attribute(SPANDATA.GEN_AI_TOOL_TYPE, "function")
30
+
31
+ span.set_attribute(SPANDATA.GEN_AI_TOOL_NAME, tool.name)
32
+ span.set_attribute(SPANDATA.GEN_AI_TOOL_DESCRIPTION, tool.description)
33
+
34
+ if should_send_default_pii():
35
+ input = args[1]
36
+ span.set_attribute(SPANDATA.GEN_AI_TOOL_INPUT, input)
37
+
38
+ return span
39
+
40
+
41
+ def update_execute_tool_span(
42
+ span: sentry_sdk.tracing.Span, agent: agents.Agent, tool: agents.Tool, result: Any
43
+ ) -> None:
44
+ _set_agent_data(span, agent)
45
+
46
+ if should_send_default_pii():
47
+ span.set_attribute(SPANDATA.GEN_AI_TOOL_OUTPUT, result)
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ import sentry_sdk
4
+ from sentry_sdk.consts import OP, SPANDATA
5
+
6
+ from ..consts import SPAN_ORIGIN
7
+
8
+ from typing import TYPE_CHECKING
9
+
10
+ if TYPE_CHECKING:
11
+ import agents
12
+
13
+
14
+ def handoff_span(
15
+ context_wrapper: agents.RunContextWrapper,
16
+ from_agent: agents.Agent,
17
+ to_agent_name: str,
18
+ ) -> None:
19
+ with sentry_sdk.start_span(
20
+ op=OP.GEN_AI_HANDOFF,
21
+ name=f"handoff from {from_agent.name} to {to_agent_name}",
22
+ origin=SPAN_ORIGIN,
23
+ ) as span:
24
+ span.set_attribute(SPANDATA.GEN_AI_OPERATION_NAME, "handoff")
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ import sentry_sdk
4
+ from sentry_sdk.consts import OP, SPANDATA
5
+
6
+ from ..consts import SPAN_ORIGIN
7
+ from ..utils import _set_agent_data
8
+
9
+ from typing import TYPE_CHECKING
10
+
11
+ if TYPE_CHECKING:
12
+ import agents
13
+ from typing import Any
14
+
15
+
16
+ def invoke_agent_span(
17
+ context_wrapper: agents.RunContextWrapper, agent: agents.Agent
18
+ ) -> sentry_sdk.tracing.Span:
19
+ span = sentry_sdk.start_span(
20
+ op=OP.GEN_AI_INVOKE_AGENT,
21
+ name=f"invoke_agent {agent.name}",
22
+ origin=SPAN_ORIGIN,
23
+ )
24
+ span.__enter__()
25
+
26
+ span.set_attribute(SPANDATA.GEN_AI_OPERATION_NAME, "invoke_agent")
27
+
28
+ _set_agent_data(span, agent)
29
+
30
+ context_wrapper._sentry_invoke_agent_span = span
31
+
32
+ return span
33
+
34
+
35
+ def update_invoke_agent_span(
36
+ context_wrapper: agents.RunContextWrapper, agent: agents.Agent, output: Any
37
+ ) -> None:
38
+ span = getattr(context_wrapper, "_sentry_invoke_agent_span", None)
39
+ if span is not None:
40
+ span.__exit__(None, None, None)
41
+ del context_wrapper._sentry_invoke_agent_span