From 0c46af1769b394b3ef864eeeebba9203896c844f Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Tue, 23 Sep 2025 16:28:31 +0200 Subject: [PATCH 1/3] Attributes on spans --- sentry_sdk/consts.py | 8 +++ .../openai_agents/patches/agent_run.py | 49 +++++++++++++++++ .../openai_agents/spans/guardrail.py | 55 +++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 sentry_sdk/integrations/openai_agents/spans/guardrail.py diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index 91a1740526..078a797555 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -467,6 +467,12 @@ class SPANDATA: Example: "The weather in Paris is rainy and overcast, with temperatures around 57°F" """ + GEN_AI_GUARDRAIL_TRIPWIRE_TRIGGERED = "gen_ai.guardrail.tripwire_triggered" + """ + Whether the guardrail tripwire was triggered. + Example: true + """ + GEN_AI_OPERATION_NAME = "gen_ai.operation.name" """ The name of the operation being performed. @@ -796,6 +802,8 @@ class OP: GEN_AI_EMBEDDINGS = "gen_ai.embeddings" GEN_AI_EXECUTE_TOOL = "gen_ai.execute_tool" GEN_AI_GENERATE_TEXT = "gen_ai.generate_text" + GEN_AI_GUARDRAIL_INPUT = "gen_ai.guardrail.input" + GEN_AI_GUARDRAIL_OUTPUT = "gen_ai.guardrail.output" GEN_AI_HANDOFF = "gen_ai.handoff" GEN_AI_PIPELINE = "gen_ai.pipeline" GEN_AI_INVOKE_AGENT = "gen_ai.invoke_agent" diff --git a/sentry_sdk/integrations/openai_agents/patches/agent_run.py b/sentry_sdk/integrations/openai_agents/patches/agent_run.py index 5473915b48..48b4c568ba 100644 --- a/sentry_sdk/integrations/openai_agents/patches/agent_run.py +++ b/sentry_sdk/integrations/openai_agents/patches/agent_run.py @@ -1,6 +1,10 @@ from functools import wraps from sentry_sdk.integrations import DidNotEnable +from sentry_sdk.integrations.openai_agents.spans.guardrail import ( + guardrail_span, + update_guardrail_span, +) from ..spans import invoke_agent_span, update_invoke_agent_span, handoff_span from typing import TYPE_CHECKING @@ -26,6 +30,13 @@ def _patch_agent_run(): original_execute_handoffs = agents._run_impl.RunImpl.execute_handoffs original_execute_final_output = agents._run_impl.RunImpl.execute_final_output + original_run_single_input_guardrail = ( + agents._run_impl.RunImpl.run_single_input_guardrail + ) + original_run_single_output_guardrail = ( + agents._run_impl.RunImpl.run_single_output_guardrail + ) + def _start_invoke_agent_span(context_wrapper, agent, kwargs): # type: (agents.RunContextWrapper, agents.Agent, dict[str, Any]) -> None """Start an agent invocation span""" @@ -132,9 +143,47 @@ async def patched_execute_final_output(cls, *args, **kwargs): return result + @wraps( + original_run_single_input_guardrail.__func__ + if hasattr(original_run_single_input_guardrail, "__func__") + else original_run_single_input_guardrail + ) + async def patched_run_single_input_guardrail(cls, *args, **kwargs): + # type: (agents.Runner, *Any, **Any) -> Any + agent = args[0] + guardrail = args[1] + + with guardrail_span(guardrail, "input", args, kwargs) as span: + result = await original_run_single_input_guardrail(*args, **kwargs) + update_guardrail_span(span, agent, guardrail, "input", result) + + return result + + @wraps( + original_run_single_output_guardrail.__func__ + if hasattr(original_run_single_output_guardrail, "__func__") + else original_run_single_output_guardrail + ) + async def patched_run_single_output_guardrail(cls, *args, **kwargs): + # type: (agents.Runner, *Any, **Any) -> Any + guardrail = args[0] + agent = args[1] + + with guardrail_span(guardrail, "output", args, kwargs) as span: + result = await original_run_single_output_guardrail(*args, **kwargs) + update_guardrail_span(span, agent, guardrail, "output", result) + + return result + # Apply patches agents.run.AgentRunner._run_single_turn = classmethod(patched_run_single_turn) agents._run_impl.RunImpl.execute_handoffs = classmethod(patched_execute_handoffs) agents._run_impl.RunImpl.execute_final_output = classmethod( patched_execute_final_output ) + agents._run_impl.RunImpl.run_single_input_guardrail = classmethod( + patched_run_single_input_guardrail + ) + agents._run_impl.RunImpl.run_single_output_guardrail = classmethod( + patched_run_single_output_guardrail + ) diff --git a/sentry_sdk/integrations/openai_agents/spans/guardrail.py b/sentry_sdk/integrations/openai_agents/spans/guardrail.py new file mode 100644 index 0000000000..6c3b771ce7 --- /dev/null +++ b/sentry_sdk/integrations/openai_agents/spans/guardrail.py @@ -0,0 +1,55 @@ +import sentry_sdk +from sentry_sdk.consts import OP, SPANDATA + +from ..consts import SPAN_ORIGIN + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + import agents + from typing import Any + + +def guardrail_span(guardrail, guardrail_type, args, kwargs): + # type: (agents.Guardrail, tuple[Any, ...], dict[str, Any]) -> sentry_sdk.tracing.Span + op = ( + OP.GEN_AI_GUARDRAIL_OUTPUT + if guardrail_type == "output" + else OP.GEN_AI_GUARDRAIL_INPUT + ) + + span = sentry_sdk.start_span( + op=op, + name=f"guardrail {guardrail.name or ''}".strip(), + origin=SPAN_ORIGIN, + ) + + span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "guardrail") + span.set_data(SPANDATA.GEN_AI_TOOL_TYPE, f"guardrail.{guardrail_type}") + + if guardrail.name is not None: + span.set_data(SPANDATA.GEN_AI_TOOL_NAME, guardrail.name) + + try: + input = args[2] + except IndexError: + input = None + + if input is not None: + span.set_data(SPANDATA.GEN_AI_TOOL_INPUT, input) + + return span + + +def update_guardrail_span(span, agent, guardrail, guardrail_type, result): + # type: (sentry_sdk.tracing.Span, agents.Agent, agents.Guardrail, Any) -> None + if agent.name is not None: + span.set_data(SPANDATA.GEN_AI_AGENT_NAME, agent.name) + + output = result.output.output_info.get("reason") + if output is not None: + span.set_data(SPANDATA.GEN_AI_TOOL_OUTPUT, output) + + tripwire_triggered = result.output.tripwire_triggered + if tripwire_triggered is not None: + span.set_data(SPANDATA.GEN_AI_GUARDRAIL_TRIPWIRE_TRIGGERED, tripwire_triggered) From 3c6cbf86e30f0133b6155bdabeeeb74dcc854072 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Tue, 23 Sep 2025 16:30:36 +0200 Subject: [PATCH 2/3] typing --- sentry_sdk/integrations/openai_agents/spans/guardrail.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/openai_agents/spans/guardrail.py b/sentry_sdk/integrations/openai_agents/spans/guardrail.py index 6c3b771ce7..2d014e92cd 100644 --- a/sentry_sdk/integrations/openai_agents/spans/guardrail.py +++ b/sentry_sdk/integrations/openai_agents/spans/guardrail.py @@ -11,7 +11,7 @@ def guardrail_span(guardrail, guardrail_type, args, kwargs): - # type: (agents.Guardrail, tuple[Any, ...], dict[str, Any]) -> sentry_sdk.tracing.Span + # type: (agents.Guardrail, str, tuple[Any, ...], dict[str, Any]) -> sentry_sdk.tracing.Span op = ( OP.GEN_AI_GUARDRAIL_OUTPUT if guardrail_type == "output" @@ -42,7 +42,7 @@ def guardrail_span(guardrail, guardrail_type, args, kwargs): def update_guardrail_span(span, agent, guardrail, guardrail_type, result): - # type: (sentry_sdk.tracing.Span, agents.Agent, agents.Guardrail, Any) -> None + # type: (sentry_sdk.tracing.Span, agents.Agent, agents.Guardrail, str, Any) -> None if agent.name is not None: span.set_data(SPANDATA.GEN_AI_AGENT_NAME, agent.name) From d1e56ad572e6050509313689639489109a29a448 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Wed, 24 Sep 2025 08:46:32 +0200 Subject: [PATCH 3/3] pii in guardrail spans --- sentry_sdk/integrations/openai_agents/spans/guardrail.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/openai_agents/spans/guardrail.py b/sentry_sdk/integrations/openai_agents/spans/guardrail.py index 2d014e92cd..a4c2983fcb 100644 --- a/sentry_sdk/integrations/openai_agents/spans/guardrail.py +++ b/sentry_sdk/integrations/openai_agents/spans/guardrail.py @@ -1,5 +1,6 @@ import sentry_sdk from sentry_sdk.consts import OP, SPANDATA +from sentry_sdk.scope import should_send_default_pii from ..consts import SPAN_ORIGIN @@ -35,7 +36,7 @@ def guardrail_span(guardrail, guardrail_type, args, kwargs): except IndexError: input = None - if input is not None: + if should_send_default_pii() and input is not None: span.set_data(SPANDATA.GEN_AI_TOOL_INPUT, input) return span @@ -47,7 +48,7 @@ def update_guardrail_span(span, agent, guardrail, guardrail_type, result): span.set_data(SPANDATA.GEN_AI_AGENT_NAME, agent.name) output = result.output.output_info.get("reason") - if output is not None: + if should_send_default_pii() and output is not None: span.set_data(SPANDATA.GEN_AI_TOOL_OUTPUT, output) tripwire_triggered = result.output.tripwire_triggered