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.
- sentry_sdk/__init__.py +2 -0
- sentry_sdk/_compat.py +5 -12
- sentry_sdk/_init_implementation.py +7 -7
- sentry_sdk/_log_batcher.py +17 -29
- sentry_sdk/_lru_cache.py +7 -9
- sentry_sdk/_queue.py +2 -4
- sentry_sdk/_types.py +11 -18
- sentry_sdk/_werkzeug.py +5 -7
- sentry_sdk/ai/monitoring.py +44 -31
- sentry_sdk/ai/utils.py +3 -4
- sentry_sdk/api.py +75 -87
- sentry_sdk/attachments.py +10 -12
- sentry_sdk/client.py +137 -155
- sentry_sdk/consts.py +430 -174
- sentry_sdk/crons/api.py +16 -17
- sentry_sdk/crons/decorator.py +25 -27
- sentry_sdk/debug.py +4 -6
- sentry_sdk/envelope.py +46 -112
- sentry_sdk/feature_flags.py +9 -15
- sentry_sdk/integrations/__init__.py +24 -19
- sentry_sdk/integrations/_asgi_common.py +15 -18
- sentry_sdk/integrations/_wsgi_common.py +22 -33
- sentry_sdk/integrations/aiohttp.py +32 -30
- sentry_sdk/integrations/anthropic.py +42 -37
- sentry_sdk/integrations/argv.py +3 -4
- sentry_sdk/integrations/ariadne.py +16 -18
- sentry_sdk/integrations/arq.py +21 -29
- sentry_sdk/integrations/asgi.py +63 -37
- sentry_sdk/integrations/asyncio.py +14 -16
- sentry_sdk/integrations/atexit.py +6 -10
- sentry_sdk/integrations/aws_lambda.py +26 -36
- sentry_sdk/integrations/beam.py +10 -18
- sentry_sdk/integrations/boto3.py +18 -16
- sentry_sdk/integrations/bottle.py +25 -34
- sentry_sdk/integrations/celery/__init__.py +41 -61
- sentry_sdk/integrations/celery/beat.py +23 -27
- sentry_sdk/integrations/celery/utils.py +15 -17
- sentry_sdk/integrations/chalice.py +8 -10
- sentry_sdk/integrations/clickhouse_driver.py +21 -31
- sentry_sdk/integrations/cloud_resource_context.py +9 -16
- sentry_sdk/integrations/cohere.py +27 -33
- sentry_sdk/integrations/dedupe.py +5 -8
- sentry_sdk/integrations/django/__init__.py +57 -72
- sentry_sdk/integrations/django/asgi.py +26 -34
- sentry_sdk/integrations/django/caching.py +23 -19
- sentry_sdk/integrations/django/middleware.py +17 -20
- sentry_sdk/integrations/django/signals_handlers.py +11 -10
- sentry_sdk/integrations/django/templates.py +19 -16
- sentry_sdk/integrations/django/transactions.py +16 -11
- sentry_sdk/integrations/django/views.py +6 -10
- sentry_sdk/integrations/dramatiq.py +21 -21
- sentry_sdk/integrations/excepthook.py +10 -10
- sentry_sdk/integrations/executing.py +3 -4
- sentry_sdk/integrations/falcon.py +27 -42
- sentry_sdk/integrations/fastapi.py +13 -16
- sentry_sdk/integrations/flask.py +31 -38
- sentry_sdk/integrations/gcp.py +13 -16
- sentry_sdk/integrations/gnu_backtrace.py +4 -6
- sentry_sdk/integrations/gql.py +16 -17
- sentry_sdk/integrations/graphene.py +13 -12
- sentry_sdk/integrations/grpc/__init__.py +19 -1
- sentry_sdk/integrations/grpc/aio/server.py +15 -14
- sentry_sdk/integrations/grpc/client.py +19 -9
- sentry_sdk/integrations/grpc/consts.py +2 -0
- sentry_sdk/integrations/grpc/server.py +12 -8
- sentry_sdk/integrations/httpx.py +9 -12
- sentry_sdk/integrations/huey.py +13 -20
- sentry_sdk/integrations/huggingface_hub.py +18 -18
- sentry_sdk/integrations/langchain.py +203 -113
- sentry_sdk/integrations/launchdarkly.py +13 -10
- sentry_sdk/integrations/litestar.py +37 -35
- sentry_sdk/integrations/logging.py +52 -65
- sentry_sdk/integrations/loguru.py +127 -57
- sentry_sdk/integrations/modules.py +3 -4
- sentry_sdk/integrations/openai.py +100 -88
- sentry_sdk/integrations/openai_agents/__init__.py +49 -0
- sentry_sdk/integrations/openai_agents/consts.py +1 -0
- sentry_sdk/integrations/openai_agents/patches/__init__.py +4 -0
- sentry_sdk/integrations/openai_agents/patches/agent_run.py +152 -0
- sentry_sdk/integrations/openai_agents/patches/models.py +52 -0
- sentry_sdk/integrations/openai_agents/patches/runner.py +42 -0
- sentry_sdk/integrations/openai_agents/patches/tools.py +84 -0
- sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
- sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +20 -0
- sentry_sdk/integrations/openai_agents/spans/ai_client.py +46 -0
- sentry_sdk/integrations/openai_agents/spans/execute_tool.py +47 -0
- sentry_sdk/integrations/openai_agents/spans/handoff.py +24 -0
- sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +41 -0
- sentry_sdk/integrations/openai_agents/utils.py +201 -0
- sentry_sdk/integrations/openfeature.py +11 -6
- sentry_sdk/integrations/pure_eval.py +6 -10
- sentry_sdk/integrations/pymongo.py +13 -17
- sentry_sdk/integrations/pyramid.py +31 -36
- sentry_sdk/integrations/quart.py +23 -28
- sentry_sdk/integrations/ray.py +73 -64
- sentry_sdk/integrations/redis/__init__.py +7 -4
- sentry_sdk/integrations/redis/_async_common.py +25 -12
- sentry_sdk/integrations/redis/_sync_common.py +19 -13
- sentry_sdk/integrations/redis/modules/caches.py +17 -8
- sentry_sdk/integrations/redis/modules/queries.py +9 -8
- sentry_sdk/integrations/redis/rb.py +3 -2
- sentry_sdk/integrations/redis/redis.py +4 -4
- sentry_sdk/integrations/redis/redis_cluster.py +21 -13
- sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +3 -2
- sentry_sdk/integrations/redis/utils.py +23 -24
- sentry_sdk/integrations/rq.py +13 -16
- sentry_sdk/integrations/rust_tracing.py +9 -6
- sentry_sdk/integrations/sanic.py +34 -46
- sentry_sdk/integrations/serverless.py +22 -27
- sentry_sdk/integrations/socket.py +27 -15
- sentry_sdk/integrations/spark/__init__.py +1 -0
- sentry_sdk/integrations/spark/spark_driver.py +45 -83
- sentry_sdk/integrations/spark/spark_worker.py +7 -11
- sentry_sdk/integrations/sqlalchemy.py +22 -19
- sentry_sdk/integrations/starlette.py +86 -90
- sentry_sdk/integrations/starlite.py +28 -34
- sentry_sdk/integrations/statsig.py +5 -4
- sentry_sdk/integrations/stdlib.py +28 -24
- sentry_sdk/integrations/strawberry.py +62 -49
- sentry_sdk/integrations/sys_exit.py +7 -11
- sentry_sdk/integrations/threading.py +12 -14
- sentry_sdk/integrations/tornado.py +28 -32
- sentry_sdk/integrations/trytond.py +4 -3
- sentry_sdk/integrations/typer.py +8 -6
- sentry_sdk/integrations/unleash.py +5 -4
- sentry_sdk/integrations/wsgi.py +47 -46
- sentry_sdk/logger.py +41 -10
- sentry_sdk/monitor.py +16 -28
- sentry_sdk/opentelemetry/consts.py +11 -4
- sentry_sdk/opentelemetry/contextvars_context.py +26 -16
- sentry_sdk/opentelemetry/propagator.py +38 -21
- sentry_sdk/opentelemetry/sampler.py +51 -34
- sentry_sdk/opentelemetry/scope.py +36 -37
- sentry_sdk/opentelemetry/span_processor.py +48 -58
- sentry_sdk/opentelemetry/tracing.py +58 -14
- sentry_sdk/opentelemetry/utils.py +186 -194
- sentry_sdk/profiler/continuous_profiler.py +108 -97
- sentry_sdk/profiler/transaction_profiler.py +70 -97
- sentry_sdk/profiler/utils.py +11 -15
- sentry_sdk/scope.py +251 -273
- sentry_sdk/scrubber.py +22 -26
- sentry_sdk/serializer.py +40 -54
- sentry_sdk/session.py +44 -61
- sentry_sdk/sessions.py +35 -49
- sentry_sdk/spotlight.py +15 -21
- sentry_sdk/tracing.py +121 -187
- sentry_sdk/tracing_utils.py +104 -122
- sentry_sdk/transport.py +131 -157
- sentry_sdk/utils.py +232 -309
- sentry_sdk/worker.py +16 -28
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/METADATA +3 -3
- sentry_sdk-3.0.0a3.dist-info/RECORD +168 -0
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/WHEEL +1 -1
- sentry_sdk-3.0.0a1.dist-info/RECORD +0 -154
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/entry_points.txt +0 -0
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/licenses/LICENSE +0 -0
- {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(
|
|
30
|
-
|
|
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(
|
|
35
|
-
|
|
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
|
-
|
|
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(
|
|
114
|
-
|
|
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())
|