sentry-sdk 3.0.0a1__py2.py3-none-any.whl → 3.0.0a3__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 (157) hide show
  1. sentry_sdk/__init__.py +2 -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 +11 -18
  8. sentry_sdk/_werkzeug.py +5 -7
  9. sentry_sdk/ai/monitoring.py +44 -31
  10. sentry_sdk/ai/utils.py +3 -4
  11. sentry_sdk/api.py +75 -87
  12. sentry_sdk/attachments.py +10 -12
  13. sentry_sdk/client.py +137 -155
  14. sentry_sdk/consts.py +430 -174
  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 +15 -18
  22. sentry_sdk/integrations/_wsgi_common.py +22 -33
  23. sentry_sdk/integrations/aiohttp.py +32 -30
  24. sentry_sdk/integrations/anthropic.py +42 -37
  25. sentry_sdk/integrations/argv.py +3 -4
  26. sentry_sdk/integrations/ariadne.py +16 -18
  27. sentry_sdk/integrations/arq.py +21 -29
  28. sentry_sdk/integrations/asgi.py +63 -37
  29. sentry_sdk/integrations/asyncio.py +14 -16
  30. sentry_sdk/integrations/atexit.py +6 -10
  31. sentry_sdk/integrations/aws_lambda.py +26 -36
  32. sentry_sdk/integrations/beam.py +10 -18
  33. sentry_sdk/integrations/boto3.py +18 -16
  34. sentry_sdk/integrations/bottle.py +25 -34
  35. sentry_sdk/integrations/celery/__init__.py +41 -61
  36. sentry_sdk/integrations/celery/beat.py +23 -27
  37. sentry_sdk/integrations/celery/utils.py +15 -17
  38. sentry_sdk/integrations/chalice.py +8 -10
  39. sentry_sdk/integrations/clickhouse_driver.py +21 -31
  40. sentry_sdk/integrations/cloud_resource_context.py +9 -16
  41. sentry_sdk/integrations/cohere.py +27 -33
  42. sentry_sdk/integrations/dedupe.py +5 -8
  43. sentry_sdk/integrations/django/__init__.py +57 -72
  44. sentry_sdk/integrations/django/asgi.py +26 -34
  45. sentry_sdk/integrations/django/caching.py +23 -19
  46. sentry_sdk/integrations/django/middleware.py +17 -20
  47. sentry_sdk/integrations/django/signals_handlers.py +11 -10
  48. sentry_sdk/integrations/django/templates.py +19 -16
  49. sentry_sdk/integrations/django/transactions.py +16 -11
  50. sentry_sdk/integrations/django/views.py +6 -10
  51. sentry_sdk/integrations/dramatiq.py +21 -21
  52. sentry_sdk/integrations/excepthook.py +10 -10
  53. sentry_sdk/integrations/executing.py +3 -4
  54. sentry_sdk/integrations/falcon.py +27 -42
  55. sentry_sdk/integrations/fastapi.py +13 -16
  56. sentry_sdk/integrations/flask.py +31 -38
  57. sentry_sdk/integrations/gcp.py +13 -16
  58. sentry_sdk/integrations/gnu_backtrace.py +4 -6
  59. sentry_sdk/integrations/gql.py +16 -17
  60. sentry_sdk/integrations/graphene.py +13 -12
  61. sentry_sdk/integrations/grpc/__init__.py +19 -1
  62. sentry_sdk/integrations/grpc/aio/server.py +15 -14
  63. sentry_sdk/integrations/grpc/client.py +19 -9
  64. sentry_sdk/integrations/grpc/consts.py +2 -0
  65. sentry_sdk/integrations/grpc/server.py +12 -8
  66. sentry_sdk/integrations/httpx.py +9 -12
  67. sentry_sdk/integrations/huey.py +13 -20
  68. sentry_sdk/integrations/huggingface_hub.py +18 -18
  69. sentry_sdk/integrations/langchain.py +203 -113
  70. sentry_sdk/integrations/launchdarkly.py +13 -10
  71. sentry_sdk/integrations/litestar.py +37 -35
  72. sentry_sdk/integrations/logging.py +52 -65
  73. sentry_sdk/integrations/loguru.py +127 -57
  74. sentry_sdk/integrations/modules.py +3 -4
  75. sentry_sdk/integrations/openai.py +100 -88
  76. sentry_sdk/integrations/openai_agents/__init__.py +49 -0
  77. sentry_sdk/integrations/openai_agents/consts.py +1 -0
  78. sentry_sdk/integrations/openai_agents/patches/__init__.py +4 -0
  79. sentry_sdk/integrations/openai_agents/patches/agent_run.py +152 -0
  80. sentry_sdk/integrations/openai_agents/patches/models.py +52 -0
  81. sentry_sdk/integrations/openai_agents/patches/runner.py +42 -0
  82. sentry_sdk/integrations/openai_agents/patches/tools.py +84 -0
  83. sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
  84. sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +20 -0
  85. sentry_sdk/integrations/openai_agents/spans/ai_client.py +46 -0
  86. sentry_sdk/integrations/openai_agents/spans/execute_tool.py +47 -0
  87. sentry_sdk/integrations/openai_agents/spans/handoff.py +24 -0
  88. sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +41 -0
  89. sentry_sdk/integrations/openai_agents/utils.py +201 -0
  90. sentry_sdk/integrations/openfeature.py +11 -6
  91. sentry_sdk/integrations/pure_eval.py +6 -10
  92. sentry_sdk/integrations/pymongo.py +13 -17
  93. sentry_sdk/integrations/pyramid.py +31 -36
  94. sentry_sdk/integrations/quart.py +23 -28
  95. sentry_sdk/integrations/ray.py +73 -64
  96. sentry_sdk/integrations/redis/__init__.py +7 -4
  97. sentry_sdk/integrations/redis/_async_common.py +25 -12
  98. sentry_sdk/integrations/redis/_sync_common.py +19 -13
  99. sentry_sdk/integrations/redis/modules/caches.py +17 -8
  100. sentry_sdk/integrations/redis/modules/queries.py +9 -8
  101. sentry_sdk/integrations/redis/rb.py +3 -2
  102. sentry_sdk/integrations/redis/redis.py +4 -4
  103. sentry_sdk/integrations/redis/redis_cluster.py +21 -13
  104. sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +3 -2
  105. sentry_sdk/integrations/redis/utils.py +23 -24
  106. sentry_sdk/integrations/rq.py +13 -16
  107. sentry_sdk/integrations/rust_tracing.py +9 -6
  108. sentry_sdk/integrations/sanic.py +34 -46
  109. sentry_sdk/integrations/serverless.py +22 -27
  110. sentry_sdk/integrations/socket.py +27 -15
  111. sentry_sdk/integrations/spark/__init__.py +1 -0
  112. sentry_sdk/integrations/spark/spark_driver.py +45 -83
  113. sentry_sdk/integrations/spark/spark_worker.py +7 -11
  114. sentry_sdk/integrations/sqlalchemy.py +22 -19
  115. sentry_sdk/integrations/starlette.py +86 -90
  116. sentry_sdk/integrations/starlite.py +28 -34
  117. sentry_sdk/integrations/statsig.py +5 -4
  118. sentry_sdk/integrations/stdlib.py +28 -24
  119. sentry_sdk/integrations/strawberry.py +62 -49
  120. sentry_sdk/integrations/sys_exit.py +7 -11
  121. sentry_sdk/integrations/threading.py +12 -14
  122. sentry_sdk/integrations/tornado.py +28 -32
  123. sentry_sdk/integrations/trytond.py +4 -3
  124. sentry_sdk/integrations/typer.py +8 -6
  125. sentry_sdk/integrations/unleash.py +5 -4
  126. sentry_sdk/integrations/wsgi.py +47 -46
  127. sentry_sdk/logger.py +41 -10
  128. sentry_sdk/monitor.py +16 -28
  129. sentry_sdk/opentelemetry/consts.py +11 -4
  130. sentry_sdk/opentelemetry/contextvars_context.py +26 -16
  131. sentry_sdk/opentelemetry/propagator.py +38 -21
  132. sentry_sdk/opentelemetry/sampler.py +51 -34
  133. sentry_sdk/opentelemetry/scope.py +36 -37
  134. sentry_sdk/opentelemetry/span_processor.py +48 -58
  135. sentry_sdk/opentelemetry/tracing.py +58 -14
  136. sentry_sdk/opentelemetry/utils.py +186 -194
  137. sentry_sdk/profiler/continuous_profiler.py +108 -97
  138. sentry_sdk/profiler/transaction_profiler.py +70 -97
  139. sentry_sdk/profiler/utils.py +11 -15
  140. sentry_sdk/scope.py +251 -273
  141. sentry_sdk/scrubber.py +22 -26
  142. sentry_sdk/serializer.py +40 -54
  143. sentry_sdk/session.py +44 -61
  144. sentry_sdk/sessions.py +35 -49
  145. sentry_sdk/spotlight.py +15 -21
  146. sentry_sdk/tracing.py +121 -187
  147. sentry_sdk/tracing_utils.py +104 -122
  148. sentry_sdk/transport.py +131 -157
  149. sentry_sdk/utils.py +232 -309
  150. sentry_sdk/worker.py +16 -28
  151. {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/METADATA +3 -3
  152. sentry_sdk-3.0.0a3.dist-info/RECORD +168 -0
  153. {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/WHEEL +1 -1
  154. sentry_sdk-3.0.0a1.dist-info/RECORD +0 -154
  155. {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/entry_points.txt +0 -0
  156. {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/licenses/LICENSE +0 -0
  157. {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/top_level.txt +0 -0
@@ -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
@@ -0,0 +1,201 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import sentry_sdk
5
+ from sentry_sdk.consts import SPANDATA
6
+ from sentry_sdk.integrations import DidNotEnable
7
+ from sentry_sdk.scope import should_send_default_pii
8
+ from sentry_sdk.utils import event_from_exception
9
+
10
+ from typing import TYPE_CHECKING
11
+
12
+ if TYPE_CHECKING:
13
+ from typing import Any
14
+ from typing import Union
15
+ from agents import Usage
16
+
17
+ try:
18
+ import agents
19
+
20
+ except ImportError:
21
+ raise DidNotEnable("OpenAI Agents not installed")
22
+
23
+
24
+ def _capture_exception(exc: Any) -> None:
25
+ event, hint = event_from_exception(
26
+ exc,
27
+ client_options=sentry_sdk.get_client().options,
28
+ mechanism={"type": "openai_agents", "handled": False},
29
+ )
30
+ sentry_sdk.capture_event(event, hint=hint)
31
+
32
+
33
+ def _set_agent_data(span: sentry_sdk.tracing.Span, agent: agents.Agent) -> None:
34
+ span.set_attribute(
35
+ SPANDATA.GEN_AI_SYSTEM, "openai"
36
+ ) # See footnote for https://opentelemetry.io/docs/specs/semconv/registry/attributes/gen-ai/#gen-ai-system for explanation why.
37
+
38
+ span.set_attribute(SPANDATA.GEN_AI_AGENT_NAME, agent.name)
39
+
40
+ if agent.model_settings.max_tokens:
41
+ span.set_attribute(
42
+ SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, agent.model_settings.max_tokens
43
+ )
44
+
45
+ if agent.model:
46
+ model_name = agent.model.model if hasattr(agent.model, "model") else agent.model
47
+ span.set_attribute(SPANDATA.GEN_AI_REQUEST_MODEL, model_name)
48
+
49
+ if agent.model_settings.presence_penalty:
50
+ span.set_attribute(
51
+ SPANDATA.GEN_AI_REQUEST_PRESENCE_PENALTY,
52
+ agent.model_settings.presence_penalty,
53
+ )
54
+
55
+ if agent.model_settings.temperature:
56
+ span.set_attribute(
57
+ SPANDATA.GEN_AI_REQUEST_TEMPERATURE, agent.model_settings.temperature
58
+ )
59
+
60
+ if agent.model_settings.top_p:
61
+ span.set_attribute(SPANDATA.GEN_AI_REQUEST_TOP_P, agent.model_settings.top_p)
62
+
63
+ if agent.model_settings.frequency_penalty:
64
+ span.set_attribute(
65
+ SPANDATA.GEN_AI_REQUEST_FREQUENCY_PENALTY,
66
+ agent.model_settings.frequency_penalty,
67
+ )
68
+
69
+ if len(agent.tools) > 0:
70
+ span.set_attribute(
71
+ SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS,
72
+ safe_serialize([vars(tool) for tool in agent.tools]),
73
+ )
74
+
75
+
76
+ def _set_usage_data(span: sentry_sdk.tracing.Span, usage: Usage) -> None:
77
+ span.set_attribute(SPANDATA.GEN_AI_USAGE_INPUT_TOKENS, usage.input_tokens)
78
+ span.set_attribute(
79
+ SPANDATA.GEN_AI_USAGE_INPUT_TOKENS_CACHED,
80
+ usage.input_tokens_details.cached_tokens,
81
+ )
82
+ span.set_attribute(SPANDATA.GEN_AI_USAGE_OUTPUT_TOKENS, usage.output_tokens)
83
+ span.set_attribute(
84
+ SPANDATA.GEN_AI_USAGE_OUTPUT_TOKENS_REASONING,
85
+ usage.output_tokens_details.reasoning_tokens,
86
+ )
87
+ span.set_attribute(SPANDATA.GEN_AI_USAGE_TOTAL_TOKENS, usage.total_tokens)
88
+
89
+
90
+ def _set_input_data(
91
+ span: sentry_sdk.tracing.Span, get_response_kwargs: dict[str, Any]
92
+ ) -> None:
93
+ if not should_send_default_pii():
94
+ return
95
+
96
+ messages_by_role: dict[str, list[Any]] = {
97
+ "system": [],
98
+ "user": [],
99
+ "assistant": [],
100
+ "tool": [],
101
+ }
102
+ system_instructions = get_response_kwargs.get("system_instructions")
103
+ if system_instructions:
104
+ messages_by_role["system"].append({"type": "text", "text": system_instructions})
105
+
106
+ for message in get_response_kwargs.get("input", []):
107
+ if "role" in message:
108
+ messages_by_role[message.get("role")].append(
109
+ {"type": "text", "text": message.get("content")}
110
+ )
111
+ else:
112
+ if message.get("type") == "function_call":
113
+ messages_by_role["assistant"].append(message)
114
+ elif message.get("type") == "function_call_output":
115
+ messages_by_role["tool"].append(message)
116
+
117
+ request_messages = []
118
+ for role, messages in messages_by_role.items():
119
+ if len(messages) > 0:
120
+ request_messages.append({"role": role, "content": messages})
121
+
122
+ span.set_attribute(
123
+ SPANDATA.GEN_AI_REQUEST_MESSAGES, safe_serialize(request_messages)
124
+ )
125
+
126
+
127
+ def _set_output_data(span: sentry_sdk.tracing.Span, result: Any) -> None:
128
+ if not should_send_default_pii():
129
+ return
130
+
131
+ output_messages: dict[str, list[Any]] = {
132
+ "response": [],
133
+ "tool": [],
134
+ }
135
+
136
+ for output in result.output:
137
+ if output.type == "function_call":
138
+ output_messages["tool"].append(output.model_dump())
139
+ elif output.type == "message":
140
+ for output_message in output.content:
141
+ try:
142
+ output_messages["response"].append(output_message.text)
143
+ except AttributeError:
144
+ # Unknown output message type, just return the json
145
+ output_messages["response"].append(output_message.dict())
146
+
147
+ if len(output_messages["tool"]) > 0:
148
+ span.set_attribute(
149
+ SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS, safe_serialize(output_messages["tool"])
150
+ )
151
+
152
+ if len(output_messages["response"]) > 0:
153
+ span.set_attribute(
154
+ SPANDATA.GEN_AI_RESPONSE_TEXT, safe_serialize(output_messages["response"])
155
+ )
156
+
157
+
158
+ def safe_serialize(data: Any) -> str:
159
+ """Safely serialize to a readable string."""
160
+
161
+ def serialize_item(
162
+ item: Any,
163
+ ) -> Union[str, dict[Any, Any], list[Any], tuple[Any, ...]]:
164
+ if callable(item):
165
+ try:
166
+ module = getattr(item, "__module__", None)
167
+ qualname = getattr(item, "__qualname__", None)
168
+ name = getattr(item, "__name__", "anonymous")
169
+
170
+ if module and qualname:
171
+ full_path = f"{module}.{qualname}"
172
+ elif module and name:
173
+ full_path = f"{module}.{name}"
174
+ else:
175
+ full_path = name
176
+
177
+ return f"<function {full_path}>"
178
+ except Exception:
179
+ return f"<callable {type(item).__name__}>"
180
+ elif isinstance(item, dict):
181
+ return {k: serialize_item(v) for k, v in item.items()}
182
+ elif isinstance(item, (list, tuple)):
183
+ return [serialize_item(x) for x in item]
184
+ elif hasattr(item, "__dict__"):
185
+ try:
186
+ attrs = {
187
+ k: serialize_item(v)
188
+ for k, v in vars(item).items()
189
+ if not k.startswith("_")
190
+ }
191
+ return f"<{type(item).__name__} {attrs}>"
192
+ except Exception:
193
+ return repr(item)
194
+ else:
195
+ return item
196
+
197
+ try:
198
+ serialized = serialize_item(data)
199
+ return json.dumps(serialized, default=str)
200
+ except Exception:
201
+ return str(data)
@@ -1,3 +1,4 @@
1
+ from __future__ import annotations
1
2
  from typing import TYPE_CHECKING
2
3
 
3
4
  from sentry_sdk.feature_flags import add_feature_flag
@@ -18,20 +19,24 @@ class OpenFeatureIntegration(Integration):
18
19
  identifier = "openfeature"
19
20
 
20
21
  @staticmethod
21
- def setup_once():
22
- # type: () -> None
22
+ def setup_once() -> None:
23
23
  # Register the hook within the global openfeature hooks list.
24
24
  api.add_hooks(hooks=[OpenFeatureHook()])
25
25
 
26
26
 
27
27
  class OpenFeatureHook(Hook):
28
28
 
29
- def after(self, hook_context, details, hints):
30
- # type: (HookContext, FlagEvaluationDetails[bool], HookHints) -> None
29
+ def after(
30
+ self,
31
+ hook_context: HookContext,
32
+ details: FlagEvaluationDetails[bool],
33
+ hints: HookHints,
34
+ ) -> None:
31
35
  if isinstance(details.value, bool):
32
36
  add_feature_flag(details.flag_key, details.value)
33
37
 
34
- def error(self, hook_context, exception, hints):
35
- # type: (HookContext, Exception, HookHints) -> None
38
+ def error(
39
+ self, hook_context: HookContext, exception: Exception, hints: HookHints
40
+ ) -> None:
36
41
  if isinstance(hook_context.default_value, bool):
37
42
  add_feature_flag(hook_context.flag_key, hook_context.default_value)
@@ -1,3 +1,4 @@
1
+ from __future__ import annotations
1
2
  import ast
2
3
 
3
4
  import sentry_sdk
@@ -35,12 +36,10 @@ class PureEvalIntegration(Integration):
35
36
  identifier = "pure_eval"
36
37
 
37
38
  @staticmethod
38
- def setup_once():
39
- # type: () -> None
39
+ def setup_once() -> None:
40
40
 
41
41
  @add_global_event_processor
42
- def add_executing_info(event, hint):
43
- # type: (Event, Optional[Hint]) -> Optional[Event]
42
+ def add_executing_info(event: Event, hint: Optional[Hint]) -> Optional[Event]:
44
43
  if sentry_sdk.get_client().get_integration(PureEvalIntegration) is None:
45
44
  return event
46
45
 
@@ -81,8 +80,7 @@ class PureEvalIntegration(Integration):
81
80
  return event
82
81
 
83
82
 
84
- def pure_eval_frame(frame):
85
- # type: (FrameType) -> Dict[str, Any]
83
+ def pure_eval_frame(frame: FrameType) -> Dict[str, Any]:
86
84
  source = executing.Source.for_frame(frame)
87
85
  if not source.tree:
88
86
  return {}
@@ -103,16 +101,14 @@ def pure_eval_frame(frame):
103
101
  evaluator = pure_eval.Evaluator.from_frame(frame)
104
102
  expressions = evaluator.interesting_expressions_grouped(scope)
105
103
 
106
- def closeness(expression):
107
- # type: (Tuple[List[Any], Any]) -> Tuple[int, int]
104
+ def closeness(expression: Tuple[List[Any], Any]) -> Tuple[int, int]:
108
105
  # Prioritise expressions with a node closer to the statement executed
109
106
  # without being after that statement
110
107
  # A higher return value is better - the expression will appear
111
108
  # earlier in the list of values and is less likely to be trimmed
112
109
  nodes, _value = expression
113
110
 
114
- def start(n):
115
- # type: (ast.expr) -> Tuple[int, int]
111
+ def start(n: ast.expr) -> Tuple[int, int]:
116
112
  return (n.lineno, n.col_offset)
117
113
 
118
114
  nodes_before_stmt = [
@@ -1,3 +1,4 @@
1
+ from __future__ import annotations
1
2
  import copy
2
3
 
3
4
  import sentry_sdk
@@ -41,8 +42,7 @@ SAFE_COMMAND_ATTRIBUTES = [
41
42
  ]
42
43
 
43
44
 
44
- def _strip_pii(command):
45
- # type: (Dict[str, Any]) -> Dict[str, Any]
45
+ def _strip_pii(command: Dict[str, Any]) -> Dict[str, Any]:
46
46
  for key in command:
47
47
  is_safe_field = key in SAFE_COMMAND_ATTRIBUTES
48
48
  if is_safe_field:
@@ -84,8 +84,7 @@ def _strip_pii(command):
84
84
  return command
85
85
 
86
86
 
87
- def _get_db_data(event):
88
- # type: (Any) -> Dict[str, Any]
87
+ def _get_db_data(event: Any) -> Dict[str, Any]:
89
88
  data = {}
90
89
 
91
90
  data[SPANDATA.DB_SYSTEM] = "mongodb"
@@ -106,16 +105,16 @@ def _get_db_data(event):
106
105
 
107
106
 
108
107
  class CommandTracer(monitoring.CommandListener):
109
- def __init__(self):
110
- # type: () -> None
111
- self._ongoing_operations = {} # type: Dict[int, Span]
108
+ def __init__(self) -> None:
109
+ self._ongoing_operations: Dict[int, Span] = {}
112
110
 
113
- def _operation_key(self, event):
114
- # type: (Union[CommandFailedEvent, CommandStartedEvent, CommandSucceededEvent]) -> int
111
+ def _operation_key(
112
+ self,
113
+ event: Union[CommandFailedEvent, CommandStartedEvent, CommandSucceededEvent],
114
+ ) -> int:
115
115
  return event.request_id
116
116
 
117
- def started(self, event):
118
- # type: (CommandStartedEvent) -> None
117
+ def started(self, event: CommandStartedEvent) -> None:
119
118
  if sentry_sdk.get_client().get_integration(PyMongoIntegration) is None:
120
119
  return
121
120
 
@@ -172,8 +171,7 @@ class CommandTracer(monitoring.CommandListener):
172
171
 
173
172
  self._ongoing_operations[self._operation_key(event)] = span.__enter__()
174
173
 
175
- def failed(self, event):
176
- # type: (CommandFailedEvent) -> None
174
+ def failed(self, event: CommandFailedEvent) -> None:
177
175
  if sentry_sdk.get_client().get_integration(PyMongoIntegration) is None:
178
176
  return
179
177
 
@@ -184,8 +182,7 @@ class CommandTracer(monitoring.CommandListener):
184
182
  except KeyError:
185
183
  return
186
184
 
187
- def succeeded(self, event):
188
- # type: (CommandSucceededEvent) -> None
185
+ def succeeded(self, event: CommandSucceededEvent) -> None:
189
186
  if sentry_sdk.get_client().get_integration(PyMongoIntegration) is None:
190
187
  return
191
188
 
@@ -202,6 +199,5 @@ class PyMongoIntegration(Integration):
202
199
  origin = f"auto.db.{identifier}"
203
200
 
204
201
  @staticmethod
205
- def setup_once():
206
- # type: () -> None
202
+ def setup_once() -> None:
207
203
  monitoring.register(CommandTracer())