sentry-sdk 0.18.0__py2.py3-none-any.whl → 2.46.0__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.
Files changed (193) hide show
  1. sentry_sdk/__init__.py +48 -6
  2. sentry_sdk/_compat.py +64 -56
  3. sentry_sdk/_init_implementation.py +84 -0
  4. sentry_sdk/_log_batcher.py +172 -0
  5. sentry_sdk/_lru_cache.py +47 -0
  6. sentry_sdk/_metrics_batcher.py +167 -0
  7. sentry_sdk/_queue.py +81 -19
  8. sentry_sdk/_types.py +311 -11
  9. sentry_sdk/_werkzeug.py +98 -0
  10. sentry_sdk/ai/__init__.py +7 -0
  11. sentry_sdk/ai/monitoring.py +137 -0
  12. sentry_sdk/ai/utils.py +144 -0
  13. sentry_sdk/api.py +409 -67
  14. sentry_sdk/attachments.py +75 -0
  15. sentry_sdk/client.py +849 -103
  16. sentry_sdk/consts.py +1389 -34
  17. sentry_sdk/crons/__init__.py +10 -0
  18. sentry_sdk/crons/api.py +62 -0
  19. sentry_sdk/crons/consts.py +4 -0
  20. sentry_sdk/crons/decorator.py +135 -0
  21. sentry_sdk/debug.py +12 -15
  22. sentry_sdk/envelope.py +112 -61
  23. sentry_sdk/feature_flags.py +71 -0
  24. sentry_sdk/hub.py +442 -386
  25. sentry_sdk/integrations/__init__.py +228 -58
  26. sentry_sdk/integrations/_asgi_common.py +108 -0
  27. sentry_sdk/integrations/_wsgi_common.py +131 -40
  28. sentry_sdk/integrations/aiohttp.py +221 -72
  29. sentry_sdk/integrations/anthropic.py +439 -0
  30. sentry_sdk/integrations/argv.py +4 -6
  31. sentry_sdk/integrations/ariadne.py +161 -0
  32. sentry_sdk/integrations/arq.py +247 -0
  33. sentry_sdk/integrations/asgi.py +237 -135
  34. sentry_sdk/integrations/asyncio.py +144 -0
  35. sentry_sdk/integrations/asyncpg.py +208 -0
  36. sentry_sdk/integrations/atexit.py +13 -18
  37. sentry_sdk/integrations/aws_lambda.py +233 -80
  38. sentry_sdk/integrations/beam.py +27 -35
  39. sentry_sdk/integrations/boto3.py +137 -0
  40. sentry_sdk/integrations/bottle.py +91 -69
  41. sentry_sdk/integrations/celery/__init__.py +529 -0
  42. sentry_sdk/integrations/celery/beat.py +293 -0
  43. sentry_sdk/integrations/celery/utils.py +43 -0
  44. sentry_sdk/integrations/chalice.py +35 -28
  45. sentry_sdk/integrations/clickhouse_driver.py +177 -0
  46. sentry_sdk/integrations/cloud_resource_context.py +280 -0
  47. sentry_sdk/integrations/cohere.py +274 -0
  48. sentry_sdk/integrations/dedupe.py +32 -8
  49. sentry_sdk/integrations/django/__init__.py +343 -89
  50. sentry_sdk/integrations/django/asgi.py +201 -22
  51. sentry_sdk/integrations/django/caching.py +204 -0
  52. sentry_sdk/integrations/django/middleware.py +80 -32
  53. sentry_sdk/integrations/django/signals_handlers.py +91 -0
  54. sentry_sdk/integrations/django/templates.py +69 -2
  55. sentry_sdk/integrations/django/transactions.py +39 -14
  56. sentry_sdk/integrations/django/views.py +69 -16
  57. sentry_sdk/integrations/dramatiq.py +226 -0
  58. sentry_sdk/integrations/excepthook.py +19 -13
  59. sentry_sdk/integrations/executing.py +5 -6
  60. sentry_sdk/integrations/falcon.py +128 -65
  61. sentry_sdk/integrations/fastapi.py +141 -0
  62. sentry_sdk/integrations/flask.py +114 -75
  63. sentry_sdk/integrations/gcp.py +67 -36
  64. sentry_sdk/integrations/gnu_backtrace.py +14 -22
  65. sentry_sdk/integrations/google_genai/__init__.py +301 -0
  66. sentry_sdk/integrations/google_genai/consts.py +16 -0
  67. sentry_sdk/integrations/google_genai/streaming.py +155 -0
  68. sentry_sdk/integrations/google_genai/utils.py +576 -0
  69. sentry_sdk/integrations/gql.py +162 -0
  70. sentry_sdk/integrations/graphene.py +151 -0
  71. sentry_sdk/integrations/grpc/__init__.py +168 -0
  72. sentry_sdk/integrations/grpc/aio/__init__.py +7 -0
  73. sentry_sdk/integrations/grpc/aio/client.py +95 -0
  74. sentry_sdk/integrations/grpc/aio/server.py +100 -0
  75. sentry_sdk/integrations/grpc/client.py +91 -0
  76. sentry_sdk/integrations/grpc/consts.py +1 -0
  77. sentry_sdk/integrations/grpc/server.py +66 -0
  78. sentry_sdk/integrations/httpx.py +178 -0
  79. sentry_sdk/integrations/huey.py +174 -0
  80. sentry_sdk/integrations/huggingface_hub.py +378 -0
  81. sentry_sdk/integrations/langchain.py +1132 -0
  82. sentry_sdk/integrations/langgraph.py +337 -0
  83. sentry_sdk/integrations/launchdarkly.py +61 -0
  84. sentry_sdk/integrations/litellm.py +287 -0
  85. sentry_sdk/integrations/litestar.py +315 -0
  86. sentry_sdk/integrations/logging.py +261 -85
  87. sentry_sdk/integrations/loguru.py +213 -0
  88. sentry_sdk/integrations/mcp.py +566 -0
  89. sentry_sdk/integrations/modules.py +6 -33
  90. sentry_sdk/integrations/openai.py +725 -0
  91. sentry_sdk/integrations/openai_agents/__init__.py +61 -0
  92. sentry_sdk/integrations/openai_agents/consts.py +1 -0
  93. sentry_sdk/integrations/openai_agents/patches/__init__.py +5 -0
  94. sentry_sdk/integrations/openai_agents/patches/agent_run.py +140 -0
  95. sentry_sdk/integrations/openai_agents/patches/error_tracing.py +77 -0
  96. sentry_sdk/integrations/openai_agents/patches/models.py +50 -0
  97. sentry_sdk/integrations/openai_agents/patches/runner.py +45 -0
  98. sentry_sdk/integrations/openai_agents/patches/tools.py +77 -0
  99. sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
  100. sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +21 -0
  101. sentry_sdk/integrations/openai_agents/spans/ai_client.py +42 -0
  102. sentry_sdk/integrations/openai_agents/spans/execute_tool.py +48 -0
  103. sentry_sdk/integrations/openai_agents/spans/handoff.py +19 -0
  104. sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +86 -0
  105. sentry_sdk/integrations/openai_agents/utils.py +199 -0
  106. sentry_sdk/integrations/openfeature.py +35 -0
  107. sentry_sdk/integrations/opentelemetry/__init__.py +7 -0
  108. sentry_sdk/integrations/opentelemetry/consts.py +5 -0
  109. sentry_sdk/integrations/opentelemetry/integration.py +58 -0
  110. sentry_sdk/integrations/opentelemetry/propagator.py +117 -0
  111. sentry_sdk/integrations/opentelemetry/span_processor.py +391 -0
  112. sentry_sdk/integrations/otlp.py +82 -0
  113. sentry_sdk/integrations/pure_eval.py +20 -11
  114. sentry_sdk/integrations/pydantic_ai/__init__.py +47 -0
  115. sentry_sdk/integrations/pydantic_ai/consts.py +1 -0
  116. sentry_sdk/integrations/pydantic_ai/patches/__init__.py +4 -0
  117. sentry_sdk/integrations/pydantic_ai/patches/agent_run.py +215 -0
  118. sentry_sdk/integrations/pydantic_ai/patches/graph_nodes.py +110 -0
  119. sentry_sdk/integrations/pydantic_ai/patches/model_request.py +40 -0
  120. sentry_sdk/integrations/pydantic_ai/patches/tools.py +98 -0
  121. sentry_sdk/integrations/pydantic_ai/spans/__init__.py +3 -0
  122. sentry_sdk/integrations/pydantic_ai/spans/ai_client.py +246 -0
  123. sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py +49 -0
  124. sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py +112 -0
  125. sentry_sdk/integrations/pydantic_ai/utils.py +223 -0
  126. sentry_sdk/integrations/pymongo.py +214 -0
  127. sentry_sdk/integrations/pyramid.py +71 -60
  128. sentry_sdk/integrations/quart.py +237 -0
  129. sentry_sdk/integrations/ray.py +165 -0
  130. sentry_sdk/integrations/redis/__init__.py +48 -0
  131. sentry_sdk/integrations/redis/_async_common.py +116 -0
  132. sentry_sdk/integrations/redis/_sync_common.py +119 -0
  133. sentry_sdk/integrations/redis/consts.py +19 -0
  134. sentry_sdk/integrations/redis/modules/__init__.py +0 -0
  135. sentry_sdk/integrations/redis/modules/caches.py +118 -0
  136. sentry_sdk/integrations/redis/modules/queries.py +65 -0
  137. sentry_sdk/integrations/redis/rb.py +32 -0
  138. sentry_sdk/integrations/redis/redis.py +69 -0
  139. sentry_sdk/integrations/redis/redis_cluster.py +107 -0
  140. sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +50 -0
  141. sentry_sdk/integrations/redis/utils.py +148 -0
  142. sentry_sdk/integrations/rq.py +62 -52
  143. sentry_sdk/integrations/rust_tracing.py +284 -0
  144. sentry_sdk/integrations/sanic.py +248 -114
  145. sentry_sdk/integrations/serverless.py +13 -22
  146. sentry_sdk/integrations/socket.py +96 -0
  147. sentry_sdk/integrations/spark/spark_driver.py +115 -62
  148. sentry_sdk/integrations/spark/spark_worker.py +42 -50
  149. sentry_sdk/integrations/sqlalchemy.py +82 -37
  150. sentry_sdk/integrations/starlette.py +737 -0
  151. sentry_sdk/integrations/starlite.py +292 -0
  152. sentry_sdk/integrations/statsig.py +37 -0
  153. sentry_sdk/integrations/stdlib.py +100 -58
  154. sentry_sdk/integrations/strawberry.py +394 -0
  155. sentry_sdk/integrations/sys_exit.py +70 -0
  156. sentry_sdk/integrations/threading.py +142 -38
  157. sentry_sdk/integrations/tornado.py +68 -53
  158. sentry_sdk/integrations/trytond.py +15 -20
  159. sentry_sdk/integrations/typer.py +60 -0
  160. sentry_sdk/integrations/unleash.py +33 -0
  161. sentry_sdk/integrations/unraisablehook.py +53 -0
  162. sentry_sdk/integrations/wsgi.py +126 -125
  163. sentry_sdk/logger.py +96 -0
  164. sentry_sdk/metrics.py +81 -0
  165. sentry_sdk/monitor.py +120 -0
  166. sentry_sdk/profiler/__init__.py +49 -0
  167. sentry_sdk/profiler/continuous_profiler.py +730 -0
  168. sentry_sdk/profiler/transaction_profiler.py +839 -0
  169. sentry_sdk/profiler/utils.py +195 -0
  170. sentry_sdk/scope.py +1542 -112
  171. sentry_sdk/scrubber.py +177 -0
  172. sentry_sdk/serializer.py +152 -210
  173. sentry_sdk/session.py +177 -0
  174. sentry_sdk/sessions.py +202 -179
  175. sentry_sdk/spotlight.py +242 -0
  176. sentry_sdk/tracing.py +1202 -294
  177. sentry_sdk/tracing_utils.py +1236 -0
  178. sentry_sdk/transport.py +693 -189
  179. sentry_sdk/types.py +52 -0
  180. sentry_sdk/utils.py +1395 -228
  181. sentry_sdk/worker.py +30 -17
  182. sentry_sdk-2.46.0.dist-info/METADATA +268 -0
  183. sentry_sdk-2.46.0.dist-info/RECORD +189 -0
  184. {sentry_sdk-0.18.0.dist-info → sentry_sdk-2.46.0.dist-info}/WHEEL +1 -1
  185. sentry_sdk-2.46.0.dist-info/entry_points.txt +2 -0
  186. sentry_sdk-2.46.0.dist-info/licenses/LICENSE +21 -0
  187. sentry_sdk/_functools.py +0 -66
  188. sentry_sdk/integrations/celery.py +0 -275
  189. sentry_sdk/integrations/redis.py +0 -103
  190. sentry_sdk-0.18.0.dist-info/LICENSE +0 -9
  191. sentry_sdk-0.18.0.dist-info/METADATA +0 -66
  192. sentry_sdk-0.18.0.dist-info/RECORD +0 -65
  193. {sentry_sdk-0.18.0.dist-info → sentry_sdk-2.46.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,61 @@
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
+ _patch_error_tracing,
9
+ )
10
+
11
+ try:
12
+ # "agents" is too generic. If someone has an agents.py file in their project
13
+ # or another package that's importable via "agents", no ImportError would not
14
+ # be thrown and the integration would enable itself even if openai-agents is
15
+ # not installed. That's why we're adding the second, more specific import
16
+ # after it, even if we don't use it.
17
+ import agents
18
+ from agents.run import DEFAULT_AGENT_RUNNER
19
+
20
+ except ImportError:
21
+ raise DidNotEnable("OpenAI Agents not installed")
22
+
23
+
24
+ def _patch_runner():
25
+ # type: () -> None
26
+ # Create the root span for one full agent run (including eventual handoffs)
27
+ # Note agents.run.DEFAULT_AGENT_RUNNER.run_sync is a wrapper around
28
+ # agents.run.DEFAULT_AGENT_RUNNER.run. It does not need to be wrapped separately.
29
+ # TODO-anton: Also patch streaming runner: agents.Runner.run_streamed
30
+ agents.run.DEFAULT_AGENT_RUNNER.run = _create_run_wrapper(
31
+ agents.run.DEFAULT_AGENT_RUNNER.run
32
+ )
33
+
34
+ # Creating the actual spans for each agent run.
35
+ _patch_agent_run()
36
+
37
+
38
+ def _patch_model():
39
+ # type: () -> None
40
+ agents.run.AgentRunner._get_model = classmethod(
41
+ _create_get_model_wrapper(agents.run.AgentRunner._get_model),
42
+ )
43
+
44
+
45
+ def _patch_tools():
46
+ # type: () -> None
47
+ agents.run.AgentRunner._get_all_tools = classmethod(
48
+ _create_get_all_tools_wrapper(agents.run.AgentRunner._get_all_tools),
49
+ )
50
+
51
+
52
+ class OpenAIAgentsIntegration(Integration):
53
+ identifier = "openai_agents"
54
+
55
+ @staticmethod
56
+ def setup_once():
57
+ # type: () -> None
58
+ _patch_error_tracing()
59
+ _patch_tools()
60
+ _patch_model()
61
+ _patch_runner()
@@ -0,0 +1 @@
1
+ SPAN_ORIGIN = "auto.ai.openai_agents"
@@ -0,0 +1,5 @@
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
5
+ from .error_tracing import _patch_error_tracing # noqa: F401
@@ -0,0 +1,140 @@
1
+ from functools import wraps
2
+
3
+ from sentry_sdk.integrations import DidNotEnable
4
+ from ..spans import invoke_agent_span, update_invoke_agent_span, handoff_span
5
+
6
+ from typing import TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ from typing import Any, Optional
10
+
11
+ try:
12
+ import agents
13
+ except ImportError:
14
+ raise DidNotEnable("OpenAI Agents not installed")
15
+
16
+
17
+ def _patch_agent_run():
18
+ # type: () -> None
19
+ """
20
+ Patches AgentRunner methods to create agent invocation spans.
21
+ This directly patches the execution flow to track when agents start and stop.
22
+ """
23
+
24
+ # Store original methods
25
+ original_run_single_turn = agents.run.AgentRunner._run_single_turn
26
+ original_execute_handoffs = agents._run_impl.RunImpl.execute_handoffs
27
+ original_execute_final_output = agents._run_impl.RunImpl.execute_final_output
28
+
29
+ def _start_invoke_agent_span(context_wrapper, agent, kwargs):
30
+ # type: (agents.RunContextWrapper, agents.Agent, dict[str, Any]) -> None
31
+ """Start an agent invocation span"""
32
+ # Store the agent on the context wrapper so we can access it later
33
+ context_wrapper._sentry_current_agent = agent
34
+ invoke_agent_span(context_wrapper, agent, kwargs)
35
+
36
+ def _end_invoke_agent_span(context_wrapper, agent, output=None):
37
+ # type: (agents.RunContextWrapper, agents.Agent, Optional[Any]) -> None
38
+ """End the agent invocation span"""
39
+ # Clear the stored agent
40
+ if hasattr(context_wrapper, "_sentry_current_agent"):
41
+ delattr(context_wrapper, "_sentry_current_agent")
42
+
43
+ update_invoke_agent_span(context_wrapper, agent, output)
44
+
45
+ def _has_active_agent_span(context_wrapper):
46
+ # type: (agents.RunContextWrapper) -> bool
47
+ """Check if there's an active agent span for this context"""
48
+ return getattr(context_wrapper, "_sentry_current_agent", None) is not None
49
+
50
+ def _get_current_agent(context_wrapper):
51
+ # type: (agents.RunContextWrapper) -> Optional[agents.Agent]
52
+ """Get the current agent from context wrapper"""
53
+ return getattr(context_wrapper, "_sentry_current_agent", None)
54
+
55
+ @wraps(
56
+ original_run_single_turn.__func__
57
+ if hasattr(original_run_single_turn, "__func__")
58
+ else original_run_single_turn
59
+ )
60
+ async def patched_run_single_turn(cls, *args, **kwargs):
61
+ # type: (agents.Runner, *Any, **Any) -> Any
62
+ """Patched _run_single_turn that creates agent invocation spans"""
63
+ agent = kwargs.get("agent")
64
+ context_wrapper = kwargs.get("context_wrapper")
65
+ should_run_agent_start_hooks = kwargs.get("should_run_agent_start_hooks")
66
+
67
+ # Start agent span when agent starts (but only once per agent)
68
+ if should_run_agent_start_hooks and agent and context_wrapper:
69
+ # End any existing span for a different agent
70
+ if _has_active_agent_span(context_wrapper):
71
+ current_agent = _get_current_agent(context_wrapper)
72
+ if current_agent and current_agent != agent:
73
+ _end_invoke_agent_span(context_wrapper, current_agent)
74
+
75
+ _start_invoke_agent_span(context_wrapper, agent, kwargs)
76
+
77
+ # Call original method with all the correct parameters
78
+ result = await original_run_single_turn(*args, **kwargs)
79
+
80
+ return result
81
+
82
+ @wraps(
83
+ original_execute_handoffs.__func__
84
+ if hasattr(original_execute_handoffs, "__func__")
85
+ else original_execute_handoffs
86
+ )
87
+ async def patched_execute_handoffs(cls, *args, **kwargs):
88
+ # type: (agents.Runner, *Any, **Any) -> Any
89
+ """Patched execute_handoffs that creates handoff spans and ends agent span for handoffs"""
90
+
91
+ context_wrapper = kwargs.get("context_wrapper")
92
+ run_handoffs = kwargs.get("run_handoffs")
93
+ agent = kwargs.get("agent")
94
+
95
+ # Create Sentry handoff span for the first handoff (agents library only processes the first one)
96
+ if run_handoffs:
97
+ first_handoff = run_handoffs[0]
98
+ handoff_agent_name = first_handoff.handoff.agent_name
99
+ handoff_span(context_wrapper, agent, handoff_agent_name)
100
+
101
+ # Call original method with all parameters
102
+ try:
103
+ result = await original_execute_handoffs(*args, **kwargs)
104
+
105
+ finally:
106
+ # End span for current agent after handoff processing is complete
107
+ if agent and context_wrapper and _has_active_agent_span(context_wrapper):
108
+ _end_invoke_agent_span(context_wrapper, agent)
109
+
110
+ return result
111
+
112
+ @wraps(
113
+ original_execute_final_output.__func__
114
+ if hasattr(original_execute_final_output, "__func__")
115
+ else original_execute_final_output
116
+ )
117
+ async def patched_execute_final_output(cls, *args, **kwargs):
118
+ # type: (agents.Runner, *Any, **Any) -> Any
119
+ """Patched execute_final_output that ends agent span for final outputs"""
120
+
121
+ agent = kwargs.get("agent")
122
+ context_wrapper = kwargs.get("context_wrapper")
123
+ final_output = kwargs.get("final_output")
124
+
125
+ # Call original method with all parameters
126
+ try:
127
+ result = await original_execute_final_output(*args, **kwargs)
128
+ finally:
129
+ # End span for current agent after final output processing is complete
130
+ if agent and context_wrapper and _has_active_agent_span(context_wrapper):
131
+ _end_invoke_agent_span(context_wrapper, agent, final_output)
132
+
133
+ return result
134
+
135
+ # Apply patches
136
+ agents.run.AgentRunner._run_single_turn = classmethod(patched_run_single_turn)
137
+ agents._run_impl.RunImpl.execute_handoffs = classmethod(patched_execute_handoffs)
138
+ agents._run_impl.RunImpl.execute_final_output = classmethod(
139
+ patched_execute_final_output
140
+ )
@@ -0,0 +1,77 @@
1
+ from functools import wraps
2
+
3
+ import sentry_sdk
4
+ from sentry_sdk.consts import SPANSTATUS
5
+ from sentry_sdk.tracing_utils import set_span_errored
6
+
7
+ from typing import TYPE_CHECKING
8
+
9
+ if TYPE_CHECKING:
10
+ from typing import Any, Callable, Optional
11
+
12
+
13
+ def _patch_error_tracing():
14
+ # type: () -> None
15
+ """
16
+ Patches agents error tracing function to inject our span error logic
17
+ when a tool execution fails.
18
+
19
+ In newer versions, the function is at: agents.util._error_tracing.attach_error_to_current_span
20
+ In older versions, it was at: agents._utils.attach_error_to_current_span
21
+
22
+ This works even when the module or function doesn't exist.
23
+ """
24
+ error_tracing_module = None
25
+
26
+ # Try newer location first (agents.util._error_tracing)
27
+ try:
28
+ from agents.util import _error_tracing
29
+
30
+ error_tracing_module = _error_tracing
31
+ except (ImportError, AttributeError):
32
+ pass
33
+
34
+ # Try older location (agents._utils)
35
+ if error_tracing_module is None:
36
+ try:
37
+ import agents._utils
38
+
39
+ error_tracing_module = agents._utils
40
+ except (ImportError, AttributeError):
41
+ # Module doesn't exist in either location, nothing to patch
42
+ return
43
+
44
+ # Check if the function exists
45
+ if not hasattr(error_tracing_module, "attach_error_to_current_span"):
46
+ return
47
+
48
+ original_attach_error = error_tracing_module.attach_error_to_current_span
49
+
50
+ @wraps(original_attach_error)
51
+ def sentry_attach_error_to_current_span(error, *args, **kwargs):
52
+ # type: (Any, *Any, **Any) -> Any
53
+ """
54
+ Wraps agents' error attachment to also set Sentry span status to error.
55
+ This allows us to properly track tool execution errors even though
56
+ the agents library swallows exceptions.
57
+ """
58
+ # Set the current Sentry span to errored
59
+ current_span = sentry_sdk.get_current_span()
60
+ if current_span is not None:
61
+ set_span_errored(current_span)
62
+ current_span.set_data("span.status", "error")
63
+
64
+ # Optionally capture the error details if we have them
65
+ if hasattr(error, "__class__"):
66
+ current_span.set_data("error.type", error.__class__.__name__)
67
+ if hasattr(error, "__str__"):
68
+ error_message = str(error)
69
+ if error_message:
70
+ current_span.set_data("error.message", error_message)
71
+
72
+ # Call the original function
73
+ return original_attach_error(error, *args, **kwargs)
74
+
75
+ error_tracing_module.attach_error_to_current_span = (
76
+ sentry_attach_error_to_current_span
77
+ )
@@ -0,0 +1,50 @@
1
+ from functools import wraps
2
+
3
+ from sentry_sdk.integrations import DidNotEnable
4
+
5
+ from ..spans import ai_client_span, update_ai_client_span
6
+
7
+ from typing import TYPE_CHECKING
8
+
9
+ if TYPE_CHECKING:
10
+ from typing import Any, Callable
11
+
12
+
13
+ try:
14
+ import agents
15
+ except ImportError:
16
+ raise DidNotEnable("OpenAI Agents not installed")
17
+
18
+
19
+ def _create_get_model_wrapper(original_get_model):
20
+ # type: (Callable[..., Any]) -> Callable[..., Any]
21
+ """
22
+ Wraps the agents.Runner._get_model method to wrap the get_response method of the model to create a AI client span.
23
+ """
24
+
25
+ @wraps(
26
+ original_get_model.__func__
27
+ if hasattr(original_get_model, "__func__")
28
+ else original_get_model
29
+ )
30
+ def wrapped_get_model(cls, agent, run_config):
31
+ # type: (agents.Runner, agents.Agent, agents.RunConfig) -> agents.Model
32
+
33
+ model = original_get_model(agent, run_config)
34
+ original_get_response = model.get_response
35
+
36
+ @wraps(original_get_response)
37
+ async def wrapped_get_response(*args, **kwargs):
38
+ # type: (*Any, **Any) -> Any
39
+ with ai_client_span(agent, kwargs) as span:
40
+ result = await original_get_response(*args, **kwargs)
41
+
42
+ update_ai_client_span(span, agent, kwargs, result)
43
+
44
+ return result
45
+
46
+ model.get_response = wrapped_get_response
47
+
48
+ return model
49
+
50
+ return wrapped_get_model
@@ -0,0 +1,45 @@
1
+ from functools import wraps
2
+
3
+ import sentry_sdk
4
+
5
+ from ..spans import agent_workflow_span
6
+ from ..utils import _capture_exception
7
+
8
+ from typing import TYPE_CHECKING
9
+
10
+ if TYPE_CHECKING:
11
+ from typing import Any, Callable
12
+
13
+
14
+ def _create_run_wrapper(original_func):
15
+ # type: (Callable[..., Any]) -> Callable[..., Any]
16
+ """
17
+ Wraps the agents.Runner.run methods to create a root span for the agent workflow runs.
18
+
19
+ Note agents.Runner.run_sync() is a wrapper around agents.Runner.run(),
20
+ so it does not need to be wrapped separately.
21
+ """
22
+
23
+ @wraps(original_func)
24
+ async def wrapper(*args, **kwargs):
25
+ # type: (*Any, **Any) -> Any
26
+ # Isolate each workflow so that when agents are run in asyncio tasks they
27
+ # don't touch each other's scopes
28
+ with sentry_sdk.isolation_scope():
29
+ agent = args[0]
30
+ with agent_workflow_span(agent):
31
+ result = None
32
+ try:
33
+ result = await original_func(*args, **kwargs)
34
+ return result
35
+ except Exception as exc:
36
+ _capture_exception(exc)
37
+
38
+ # It could be that there is a "invoke agent" span still open
39
+ current_span = sentry_sdk.get_current_span()
40
+ if current_span is not None and current_span.timestamp is None:
41
+ current_span.__exit__(None, None, None)
42
+
43
+ raise exc from None
44
+
45
+ return wrapper
@@ -0,0 +1,77 @@
1
+ from functools import wraps
2
+
3
+ from sentry_sdk.integrations import DidNotEnable
4
+
5
+ from ..spans import execute_tool_span, update_execute_tool_span
6
+
7
+ from typing import TYPE_CHECKING
8
+
9
+ if TYPE_CHECKING:
10
+ from typing import Any, Callable
11
+
12
+ try:
13
+ import agents
14
+ except ImportError:
15
+ raise DidNotEnable("OpenAI Agents not installed")
16
+
17
+
18
+ def _create_get_all_tools_wrapper(original_get_all_tools):
19
+ # type: (Callable[..., Any]) -> Callable[..., Any]
20
+ """
21
+ Wraps the agents.Runner._get_all_tools method of the Runner class to wrap all function tools with Sentry instrumentation.
22
+ """
23
+
24
+ @wraps(
25
+ original_get_all_tools.__func__
26
+ if hasattr(original_get_all_tools, "__func__")
27
+ else original_get_all_tools
28
+ )
29
+ async def wrapped_get_all_tools(cls, agent, context_wrapper):
30
+ # type: (agents.Runner, agents.Agent, agents.RunContextWrapper) -> list[agents.Tool]
31
+
32
+ # Get the original tools
33
+ tools = await original_get_all_tools(agent, context_wrapper)
34
+
35
+ wrapped_tools = []
36
+ for tool in tools:
37
+ # Wrap only the function tools (for now)
38
+ if tool.__class__.__name__ != "FunctionTool":
39
+ wrapped_tools.append(tool)
40
+ continue
41
+
42
+ # Create a new FunctionTool with our wrapped invoke method
43
+ original_on_invoke = tool.on_invoke_tool
44
+
45
+ def create_wrapped_invoke(current_tool, current_on_invoke):
46
+ # type: (agents.Tool, Callable[..., Any]) -> Callable[..., Any]
47
+ @wraps(current_on_invoke)
48
+ async def sentry_wrapped_on_invoke_tool(*args, **kwargs):
49
+ # type: (*Any, **Any) -> Any
50
+ with execute_tool_span(current_tool, *args, **kwargs) as span:
51
+ # We can not capture exceptions in tool execution here because
52
+ # `_on_invoke_tool` is swallowing the exception here:
53
+ # https://github.com/openai/openai-agents-python/blob/main/src/agents/tool.py#L409-L422
54
+ # And because function_tool is a decorator with `default_tool_error_function` set as a default parameter
55
+ # I was unable to monkey patch it because those are evaluated at module import time
56
+ # and the SDK is too late to patch it. I was also unable to patch `_on_invoke_tool_impl`
57
+ # because it is nested inside this import time code. As if they made it hard to patch on purpose...
58
+ result = await current_on_invoke(*args, **kwargs)
59
+ update_execute_tool_span(span, agent, current_tool, result)
60
+
61
+ return result
62
+
63
+ return sentry_wrapped_on_invoke_tool
64
+
65
+ wrapped_tool = agents.FunctionTool(
66
+ name=tool.name,
67
+ description=tool.description,
68
+ params_json_schema=tool.params_json_schema,
69
+ on_invoke_tool=create_wrapped_invoke(tool, original_on_invoke),
70
+ strict_json_schema=tool.strict_json_schema,
71
+ is_enabled=tool.is_enabled,
72
+ )
73
+ wrapped_tools.append(wrapped_tool)
74
+
75
+ return wrapped_tools
76
+
77
+ 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,21 @@
1
+ import sentry_sdk
2
+ from sentry_sdk.ai.utils import get_start_span_function
3
+
4
+ from ..consts import SPAN_ORIGIN
5
+
6
+ from typing import TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ import agents
10
+
11
+
12
+ def agent_workflow_span(agent):
13
+ # type: (agents.Agent) -> sentry_sdk.tracing.Span
14
+
15
+ # Create a transaction or a span if an transaction is already active
16
+ span = get_start_span_function()(
17
+ name=f"{agent.name} workflow",
18
+ origin=SPAN_ORIGIN,
19
+ )
20
+
21
+ return span
@@ -0,0 +1,42 @@
1
+ import sentry_sdk
2
+ from sentry_sdk.consts import OP, SPANDATA
3
+
4
+ from ..consts import SPAN_ORIGIN
5
+ from ..utils import (
6
+ _set_agent_data,
7
+ _set_input_data,
8
+ _set_output_data,
9
+ _set_usage_data,
10
+ _create_mcp_execute_tool_spans,
11
+ )
12
+
13
+ from typing import TYPE_CHECKING
14
+
15
+ if TYPE_CHECKING:
16
+ from agents import Agent
17
+ from typing import Any
18
+
19
+
20
+ def ai_client_span(agent, get_response_kwargs):
21
+ # type: (Agent, dict[str, Any]) -> sentry_sdk.tracing.Span
22
+ # TODO-anton: implement other types of operations. Now "chat" is hardcoded.
23
+ model_name = agent.model.model if hasattr(agent.model, "model") else agent.model
24
+ span = sentry_sdk.start_span(
25
+ op=OP.GEN_AI_CHAT,
26
+ description=f"chat {model_name}",
27
+ origin=SPAN_ORIGIN,
28
+ )
29
+ # TODO-anton: remove hardcoded stuff and replace something that also works for embedding and so on
30
+ span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "chat")
31
+
32
+ _set_agent_data(span, agent)
33
+ _set_input_data(span, get_response_kwargs)
34
+
35
+ return span
36
+
37
+
38
+ def update_ai_client_span(span, agent, get_response_kwargs, result):
39
+ # type: (sentry_sdk.tracing.Span, Agent, dict[str, Any], Any) -> None
40
+ _set_usage_data(span, result.usage)
41
+ _set_output_data(span, result)
42
+ _create_mcp_execute_tool_spans(span, result)
@@ -0,0 +1,48 @@
1
+ import sentry_sdk
2
+ from sentry_sdk.consts import OP, SPANDATA, SPANSTATUS
3
+ from sentry_sdk.scope import should_send_default_pii
4
+
5
+ from ..consts import SPAN_ORIGIN
6
+ from ..utils import _set_agent_data
7
+
8
+ from typing import TYPE_CHECKING
9
+
10
+ if TYPE_CHECKING:
11
+ import agents
12
+ from typing import Any
13
+
14
+
15
+ def execute_tool_span(tool, *args, **kwargs):
16
+ # type: (agents.Tool, *Any, **Any) -> sentry_sdk.tracing.Span
17
+ span = sentry_sdk.start_span(
18
+ op=OP.GEN_AI_EXECUTE_TOOL,
19
+ name=f"execute_tool {tool.name}",
20
+ origin=SPAN_ORIGIN,
21
+ )
22
+
23
+ span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "execute_tool")
24
+
25
+ if tool.__class__.__name__ == "FunctionTool":
26
+ span.set_data(SPANDATA.GEN_AI_TOOL_TYPE, "function")
27
+
28
+ span.set_data(SPANDATA.GEN_AI_TOOL_NAME, tool.name)
29
+ span.set_data(SPANDATA.GEN_AI_TOOL_DESCRIPTION, tool.description)
30
+
31
+ if should_send_default_pii():
32
+ input = args[1]
33
+ span.set_data(SPANDATA.GEN_AI_TOOL_INPUT, input)
34
+
35
+ return span
36
+
37
+
38
+ def update_execute_tool_span(span, agent, tool, result):
39
+ # type: (sentry_sdk.tracing.Span, agents.Agent, agents.Tool, Any) -> None
40
+ _set_agent_data(span, agent)
41
+
42
+ if isinstance(result, str) and result.startswith(
43
+ "An error occurred while running the tool"
44
+ ):
45
+ span.set_status(SPANSTATUS.ERROR)
46
+
47
+ if should_send_default_pii():
48
+ span.set_data(SPANDATA.GEN_AI_TOOL_OUTPUT, result)
@@ -0,0 +1,19 @@
1
+ import sentry_sdk
2
+ from sentry_sdk.consts import OP, SPANDATA
3
+
4
+ from ..consts import SPAN_ORIGIN
5
+
6
+ from typing import TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ import agents
10
+
11
+
12
+ def handoff_span(context, from_agent, to_agent_name):
13
+ # type: (agents.RunContextWrapper, agents.Agent, str) -> None
14
+ with sentry_sdk.start_span(
15
+ op=OP.GEN_AI_HANDOFF,
16
+ name=f"handoff from {from_agent.name} to {to_agent_name}",
17
+ origin=SPAN_ORIGIN,
18
+ ) as span:
19
+ span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "handoff")