From 660be3b28f7eb51e5ba087345ca0408c4b59d631 Mon Sep 17 00:00:00 2001 From: michi-okahata Date: Tue, 3 Jun 2025 09:33:34 -0700 Subject: [PATCH 01/13] input/output guardrail decorator #1003 --- agentops/__init__.py | 4 +++- agentops/sdk/decorators/__init__.py | 4 +++- agentops/sdk/decorators/factory.py | 20 ++++++++++---------- agentops/sdk/decorators/utility.py | 8 ++++---- agentops/semconv/span_attributes.py | 4 ++++ agentops/semconv/span_kinds.py | 2 ++ 6 files changed, 26 insertions(+), 16 deletions(-) diff --git a/agentops/__init__.py b/agentops/__init__.py index 3b252759a..d769b1961 100755 --- a/agentops/__init__.py +++ b/agentops/__init__.py @@ -15,7 +15,7 @@ from typing import List, Optional, Union, Dict, Any from agentops.client import Client from agentops.sdk.core import TracingCore, TraceContext -from agentops.sdk.decorators import trace, session, agent, task, workflow, operation +from agentops.sdk.decorators import trace, session, agent, task, workflow, operation, in_guardrail, out_guardrail from agentops.logging.config import logger @@ -247,4 +247,6 @@ def end_trace(trace_context: Optional[TraceContext] = None, end_state: str = "Su "task", "workflow", "operation", + "in_guardrail", + "out_guardrail", ] diff --git a/agentops/sdk/decorators/__init__.py b/agentops/sdk/decorators/__init__.py index f775b45d5..22667fa58 100644 --- a/agentops/sdk/decorators/__init__.py +++ b/agentops/sdk/decorators/__init__.py @@ -19,6 +19,8 @@ session = create_entity_decorator(SpanKind.SESSION) tool = create_entity_decorator(SpanKind.TOOL) operation = task +in_guardrail = create_entity_decorator(SpanKind.INPUT_GUARDRAIL) +out_guardrail = create_entity_decorator(SpanKind.OUTPUT_GUARDRAIL) # For backward compatibility: @session decorator calls @trace decorator @functools.wraps(trace) @@ -37,4 +39,4 @@ def session(*args, **kwargs): # For now, keeping the alias as it was, assuming it was intentional for `operation` to be `task`. operation = task -__all__ = ["agent", "task", "workflow", "trace", "session", "operation", "tool"] +__all__ = ["agent", "task", "workflow", "trace", "session", "operation", "tool", "in_guardrail", "out_guardrail"] diff --git a/agentops/sdk/decorators/factory.py b/agentops/sdk/decorators/factory.py index cbf0e7026..f345c9ca7 100644 --- a/agentops/sdk/decorators/factory.py +++ b/agentops/sdk/decorators/factory.py @@ -48,7 +48,7 @@ def __init__(self, *args: Any, **kwargs: Any): self._agentops_active_span = self._agentops_span_context_manager.__enter__() try: - _record_entity_input(self._agentops_active_span, args, kwargs) + _record_entity_input(self._agentops_active_span, args, kwargs, entity_kind=entity_kind) except Exception as e: logger.warning(f"Failed to record entity input for class {op_name}: {e}") super().__init__(*args, **kwargs) @@ -64,7 +64,7 @@ async def __aenter__(self) -> "WrappedClass": async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: if hasattr(self, "_agentops_active_span") and hasattr(self, "_agentops_span_context_manager"): try: - _record_entity_output(self._agentops_active_span, self) + _record_entity_output(self._agentops_active_span, self, entity_kind=entity_kind) except Exception as e: logger.warning(f"Failed to record entity output for class instance: {e}") self._agentops_span_context_manager.__exit__(exc_type, exc_val, exc_tb) @@ -107,12 +107,12 @@ async def _wrapped_session_async() -> Any: ) return await wrapped_func(*args, **kwargs) try: - _record_entity_input(trace_context.span, args, kwargs) + _record_entity_input(trace_context.span, args, kwargs, entity_kind=entity_kind) except Exception as e: logger.warning(f"Input recording failed for @trace '{operation_name}': {e}") result = await wrapped_func(*args, **kwargs) try: - _record_entity_output(trace_context.span, result) + _record_entity_output(trace_context.span, result, entity_kind=entity_kind) except Exception as e: logger.warning(f"Output recording failed for @trace '{operation_name}': {e}") TracingCore.get_instance().end_trace(trace_context, "Success") @@ -139,12 +139,12 @@ async def _wrapped_session_async() -> Any: ) return wrapped_func(*args, **kwargs) try: - _record_entity_input(trace_context.span, args, kwargs) + _record_entity_input(trace_context.span, args, kwargs, entity_kind=entity_kind) except Exception as e: logger.warning(f"Input recording failed for @trace '{operation_name}': {e}") result = wrapped_func(*args, **kwargs) try: - _record_entity_output(trace_context.span, result) + _record_entity_output(trace_context.span, result, entity_kind=entity_kind) except Exception as e: logger.warning(f"Output recording failed for @trace '{operation_name}': {e}") TracingCore.get_instance().end_trace(trace_context, "Success") @@ -203,7 +203,7 @@ async def _wrapped_async() -> Any: attributes={CoreAttributes.TAGS: tags} if tags else None, ) as span: try: - _record_entity_input(span, args, kwargs) + _record_entity_input(span, args, kwargs, entity_kind=entity_kind) # Set cost attribute if tool if entity_kind == "tool" and cost is not None: span.set_attribute(SpanAttributes.LLM_USAGE_TOOL_COST, cost) @@ -212,7 +212,7 @@ async def _wrapped_async() -> Any: try: result = await wrapped_func(*args, **kwargs) try: - _record_entity_output(span, result) + _record_entity_output(span, result, entity_kind=entity_kind) except Exception as e: logger.warning(f"Output recording failed for '{operation_name}': {e}") return result @@ -230,7 +230,7 @@ async def _wrapped_async() -> Any: attributes={CoreAttributes.TAGS: tags} if tags else None, ) as span: try: - _record_entity_input(span, args, kwargs) + _record_entity_input(span, args, kwargs, entity_kind=entity_kind) # Set cost attribute if tool if entity_kind == "tool" and cost is not None: span.set_attribute(SpanAttributes.LLM_USAGE_TOOL_COST, cost) @@ -239,7 +239,7 @@ async def _wrapped_async() -> Any: try: result = wrapped_func(*args, **kwargs) try: - _record_entity_output(span, result) + _record_entity_output(span, result, entity_kind=entity_kind) except Exception as e: logger.warning(f"Output recording failed for '{operation_name}': {e}") return result diff --git a/agentops/sdk/decorators/utility.py b/agentops/sdk/decorators/utility.py index 5dee1d412..c0be4cf8b 100644 --- a/agentops/sdk/decorators/utility.py +++ b/agentops/sdk/decorators/utility.py @@ -192,27 +192,27 @@ def _make_span( return span, ctx, token -def _record_entity_input(span: trace.Span, args: tuple, kwargs: Dict[str, Any]) -> None: +def _record_entity_input(span: trace.Span, args: tuple, kwargs: Dict[str, Any], entity_kind: str = "entity") -> None: """Record operation input parameters to span if content tracing is enabled""" try: input_data = {"args": args, "kwargs": kwargs} json_data = safe_serialize(input_data) if _check_content_size(json_data): - span.set_attribute(SpanAttributes.AGENTOPS_ENTITY_INPUT, json_data) + span.set_attribute(SpanAttributes.AGENTOPS_DECORATOR_INPUT.format(entity_kind=entity_kind), json_data) else: logger.debug("Operation input exceeds size limit, not recording") except Exception as err: logger.warning(f"Failed to serialize operation input: {err}") -def _record_entity_output(span: trace.Span, result: Any) -> None: +def _record_entity_output(span: trace.Span, result: Any, entity_kind: str = "entity") -> None: """Record operation output value to span if content tracing is enabled""" try: json_data = safe_serialize(result) if _check_content_size(json_data): - span.set_attribute(SpanAttributes.AGENTOPS_ENTITY_OUTPUT, json_data) + span.set_attribute(SpanAttributes.AGENTOPS_DECORATOR_OUTPUT.format(entity_kind=entity_kind), json_data) else: logger.debug("Operation output exceeds size limit, not recording") except Exception as err: diff --git a/agentops/semconv/span_attributes.py b/agentops/semconv/span_attributes.py index 67320bffd..a165df55a 100644 --- a/agentops/semconv/span_attributes.py +++ b/agentops/semconv/span_attributes.py @@ -89,6 +89,10 @@ class SpanAttributes: AGENTOPS_SPAN_KIND = "agentops.span.kind" AGENTOPS_ENTITY_NAME = "agentops.entity.name" + # Decorator + AGENTOPS_DECORATOR_INPUT = "agentops.{entity_kind}.input" + AGENTOPS_DECORATOR_OUTPUT = "agentops.{entity_kind}.output" + # Operation attributes OPERATION_NAME = "operation.name" OPERATION_VERSION = "operation.version" diff --git a/agentops/semconv/span_kinds.py b/agentops/semconv/span_kinds.py index 0d90a8cc9..bc8e806d2 100644 --- a/agentops/semconv/span_kinds.py +++ b/agentops/semconv/span_kinds.py @@ -27,6 +27,8 @@ class SpanKind: UNKNOWN = "unknown" CHAIN = "chain" TEXT = "text" + INPUT_GUARDRAIL = "input guardrail" + OUTPUT_GUARDRAIL = "output guardrail" class AgentOpsSpanKindValues(Enum): From 299db87946a7676fbdc6baf486b5ccf9e0f0370a Mon Sep 17 00:00:00 2001 From: michi-okahata Date: Thu, 5 Jun 2025 12:23:26 -0700 Subject: [PATCH 02/13] changed span_kinds name, confirmed no existing semconv for guardrails --- agentops/semconv/span_attributes.py | 2 -- agentops/semconv/span_kinds.py | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/agentops/semconv/span_attributes.py b/agentops/semconv/span_attributes.py index a165df55a..c2d1c64b8 100644 --- a/agentops/semconv/span_attributes.py +++ b/agentops/semconv/span_attributes.py @@ -88,8 +88,6 @@ class SpanAttributes: AGENTOPS_ENTITY_INPUT = "agentops.entity.input" AGENTOPS_SPAN_KIND = "agentops.span.kind" AGENTOPS_ENTITY_NAME = "agentops.entity.name" - - # Decorator AGENTOPS_DECORATOR_INPUT = "agentops.{entity_kind}.input" AGENTOPS_DECORATOR_OUTPUT = "agentops.{entity_kind}.output" diff --git a/agentops/semconv/span_kinds.py b/agentops/semconv/span_kinds.py index bc8e806d2..7e49d2b32 100644 --- a/agentops/semconv/span_kinds.py +++ b/agentops/semconv/span_kinds.py @@ -27,8 +27,8 @@ class SpanKind: UNKNOWN = "unknown" CHAIN = "chain" TEXT = "text" - INPUT_GUARDRAIL = "input guardrail" - OUTPUT_GUARDRAIL = "output guardrail" + INPUT_GUARDRAIL = "guardrail_input" + OUTPUT_GUARDRAIL = "guardrail_output" class AgentOpsSpanKindValues(Enum): From 291a3738e3dc4df1e023ee0f05c978116ef52fa7 Mon Sep 17 00:00:00 2001 From: michi-okahata Date: Fri, 6 Jun 2025 16:57:34 -0700 Subject: [PATCH 03/13] ruff fixes --- agentops/sdk/decorators/__init__.py | 1 + agentops/semconv/span_attributes.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/agentops/sdk/decorators/__init__.py b/agentops/sdk/decorators/__init__.py index 22667fa58..0f4e1c94e 100644 --- a/agentops/sdk/decorators/__init__.py +++ b/agentops/sdk/decorators/__init__.py @@ -22,6 +22,7 @@ in_guardrail = create_entity_decorator(SpanKind.INPUT_GUARDRAIL) out_guardrail = create_entity_decorator(SpanKind.OUTPUT_GUARDRAIL) + # For backward compatibility: @session decorator calls @trace decorator @functools.wraps(trace) def session(*args, **kwargs): diff --git a/agentops/semconv/span_attributes.py b/agentops/semconv/span_attributes.py index c2d1c64b8..7238112f1 100644 --- a/agentops/semconv/span_attributes.py +++ b/agentops/semconv/span_attributes.py @@ -90,7 +90,7 @@ class SpanAttributes: AGENTOPS_ENTITY_NAME = "agentops.entity.name" AGENTOPS_DECORATOR_INPUT = "agentops.{entity_kind}.input" AGENTOPS_DECORATOR_OUTPUT = "agentops.{entity_kind}.output" - + # Operation attributes OPERATION_NAME = "operation.name" OPERATION_VERSION = "operation.version" From 5e112681a42296c8e3ffa5afb9894f450a809ac7 Mon Sep 17 00:00:00 2001 From: michi-okahata Date: Fri, 6 Jun 2025 17:38:21 -0700 Subject: [PATCH 04/13] ruff fix 2 --- agentops/sdk/decorators/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/agentops/sdk/decorators/__init__.py b/agentops/sdk/decorators/__init__.py index 93289468d..3f75952aa 100644 --- a/agentops/sdk/decorators/__init__.py +++ b/agentops/sdk/decorators/__init__.py @@ -22,7 +22,6 @@ out_guardrail = create_entity_decorator(SpanKind.OUTPUT_GUARDRAIL) - # For backward compatibility: @session decorator calls @trace decorator @functools.wraps(trace) def session(*args, **kwargs): # noqa: F811 From 7eb13ed4769790c2f088878d81b90b5717b9cd72 Mon Sep 17 00:00:00 2001 From: michi-okahata Date: Mon, 9 Jun 2025 16:05:28 -0700 Subject: [PATCH 05/13] combined input/output into a guardrail decorator with parameter 'spec' --- agentops/__init__.py | 3 +-- agentops/sdk/decorators/__init__.py | 14 +++++++++--- agentops/sdk/decorators/factory.py | 35 +++++++++++++++++++++-------- agentops/semconv/span_attributes.py | 1 + agentops/semconv/span_kinds.py | 3 +-- 5 files changed, 40 insertions(+), 16 deletions(-) diff --git a/agentops/__init__.py b/agentops/__init__.py index 3803daf56..37f4bed9c 100755 --- a/agentops/__init__.py +++ b/agentops/__init__.py @@ -265,8 +265,7 @@ def end_trace( "task", "workflow", "operation", - "in_guardrail", - "out_guardrail", + "guardrail", "tracer", "tool", # Trace state enums diff --git a/agentops/sdk/decorators/__init__.py b/agentops/sdk/decorators/__init__.py index 3f75952aa..6d2963f90 100644 --- a/agentops/sdk/decorators/__init__.py +++ b/agentops/sdk/decorators/__init__.py @@ -18,8 +18,7 @@ trace = create_entity_decorator(SpanKind.SESSION) tool = create_entity_decorator(SpanKind.TOOL) operation = task -in_guardrail = create_entity_decorator(SpanKind.INPUT_GUARDRAIL) -out_guardrail = create_entity_decorator(SpanKind.OUTPUT_GUARDRAIL) +guardrail = create_entity_decorator(SpanKind.GUARDRAIL) # For backward compatibility: @session decorator calls @trace decorator @@ -39,4 +38,13 @@ def session(*args, **kwargs): # noqa: F811 # For now, keeping the alias as it was, assuming it was intentional for `operation` to be `task`. operation = task -__all__ = ["agent", "task", "workflow", "trace", "session", "operation", "tool", "in_guardrail", "out_guardrail"] +__all__ = [ + "agent", + "task", + "workflow", + "trace", + "session", + "operation", + "tool", + "guardrail", +] # "in_guardrail", "out_guardrail"] diff --git a/agentops/sdk/decorators/factory.py b/agentops/sdk/decorators/factory.py index 6a20e890a..283b79af9 100644 --- a/agentops/sdk/decorators/factory.py +++ b/agentops/sdk/decorators/factory.py @@ -33,9 +33,10 @@ def decorator( version: Optional[Any] = None, tags: Optional[Union[list, dict]] = None, cost=None, + spec=None, ) -> Callable[..., Any]: if wrapped is None: - return functools.partial(decorator, name=name, version=version, tags=tags, cost=cost) + return functools.partial(decorator, name=name, version=version, tags=tags, cost=cost, spec=spec) if inspect.isclass(wrapped): # Class decoration wraps __init__ and aenter/aexit for context management. @@ -47,7 +48,7 @@ def __init__(self, *args: Any, **kwargs: Any): self._agentops_active_span = self._agentops_span_context_manager.__enter__() try: - _record_entity_input(self._agentops_active_span, args, kwargs, entity_kind=entity_kind) + _record_entity_input(self._agentops_active_span, args, kwargs) except Exception as e: logger.warning(f"Failed to record entity input for class {op_name}: {e}") super().__init__(*args, **kwargs) @@ -63,7 +64,7 @@ async def __aenter__(self) -> "WrappedClass": async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: if hasattr(self, "_agentops_active_span") and hasattr(self, "_agentops_span_context_manager"): try: - _record_entity_output(self._agentops_active_span, self, entity_kind=entity_kind) + _record_entity_output(self._agentops_active_span, self) except Exception as e: logger.warning(f"Failed to record entity output for class instance: {e}") self._agentops_span_context_manager.__exit__(exc_type, exc_val, exc_tb) @@ -106,12 +107,12 @@ async def _wrapped_session_async() -> Any: ) return await wrapped_func(*args, **kwargs) try: - _record_entity_input(trace_context.span, args, kwargs, entity_kind=entity_kind) + _record_entity_input(trace_context.span, args, kwargs) except Exception as e: logger.warning(f"Input recording failed for @trace '{operation_name}': {e}") result = await wrapped_func(*args, **kwargs) try: - _record_entity_output(trace_context.span, result, entity_kind=entity_kind) + _record_entity_output(trace_context.span, result) except Exception as e: logger.warning(f"Output recording failed for @trace '{operation_name}': {e}") tracer.end_trace(trace_context, "Success") @@ -138,12 +139,12 @@ async def _wrapped_session_async() -> Any: ) return wrapped_func(*args, **kwargs) try: - _record_entity_input(trace_context.span, args, kwargs, entity_kind=entity_kind) + _record_entity_input(trace_context.span, args, kwargs) except Exception as e: logger.warning(f"Input recording failed for @trace '{operation_name}': {e}") result = wrapped_func(*args, **kwargs) try: - _record_entity_output(trace_context.span, result, entity_kind=entity_kind) + _record_entity_output(trace_context.span, result) except Exception as e: logger.warning(f"Output recording failed for @trace '{operation_name}': {e}") tracer.end_trace(trace_context, "Success") @@ -168,10 +169,13 @@ async def _wrapped_session_async() -> Any: attributes={CoreAttributes.TAGS: tags} if tags else None, ) try: - _record_entity_input(span, args, kwargs) + _record_entity_input(span, args, kwargs, entity_kind=entity_kind) # Set cost attribute if tool if entity_kind == "tool" and cost is not None: span.set_attribute(SpanAttributes.LLM_USAGE_TOOL_COST, cost) + # Set kind attribute if guardrail + if entity_kind == "guardrail" and (spec == "input" or spec == "output"): + span.set_attribute(SpanAttributes.AGENTOPS_DECORATOR_SPEC.format(entity_kind=entity_kind), spec) except Exception as e: logger.warning(f"Input recording failed for '{operation_name}': {e}") result = wrapped_func(*args, **kwargs) @@ -184,10 +188,13 @@ async def _wrapped_session_async() -> Any: attributes={CoreAttributes.TAGS: tags} if tags else None, ) try: - _record_entity_input(span, args, kwargs) + _record_entity_input(span, args, kwargs, entity_kind=entity_kind) # Set cost attribute if tool if entity_kind == "tool" and cost is not None: span.set_attribute(SpanAttributes.LLM_USAGE_TOOL_COST, cost) + # Set kind attribute if guardrail + if entity_kind == "guardrail" and (spec == "input" or spec == "output"): + span.set_attribute(SpanAttributes.AGENTOPS_DECORATOR_SPEC.format(entity_kind=entity_kind), spec) except Exception as e: logger.warning(f"Input recording failed for '{operation_name}': {e}") result = wrapped_func(*args, **kwargs) @@ -206,6 +213,11 @@ async def _wrapped_async() -> Any: # Set cost attribute if tool if entity_kind == "tool" and cost is not None: span.set_attribute(SpanAttributes.LLM_USAGE_TOOL_COST, cost) + # Set kind attribute if guardrail + if entity_kind == "guardrail" and (spec == "input" or spec == "output"): + span.set_attribute( + SpanAttributes.AGENTOPS_DECORATOR_SPEC.format(entity_kind=entity_kind), spec + ) except Exception as e: logger.warning(f"Input recording failed for '{operation_name}': {e}") try: @@ -233,6 +245,11 @@ async def _wrapped_async() -> Any: # Set cost attribute if tool if entity_kind == "tool" and cost is not None: span.set_attribute(SpanAttributes.LLM_USAGE_TOOL_COST, cost) + # Set kind attribute if guardrail + if entity_kind == "guardrail" and (spec == "input" or spec == "output"): + span.set_attribute( + SpanAttributes.AGENTOPS_DECORATOR_SPEC.format(entity_kind=entity_kind), spec + ) except Exception as e: logger.warning(f"Input recording failed for '{operation_name}': {e}") try: diff --git a/agentops/semconv/span_attributes.py b/agentops/semconv/span_attributes.py index 7238112f1..0daf0ddfc 100644 --- a/agentops/semconv/span_attributes.py +++ b/agentops/semconv/span_attributes.py @@ -88,6 +88,7 @@ class SpanAttributes: AGENTOPS_ENTITY_INPUT = "agentops.entity.input" AGENTOPS_SPAN_KIND = "agentops.span.kind" AGENTOPS_ENTITY_NAME = "agentops.entity.name" + AGENTOPS_DECORATOR_SPEC = "agentops.{entity_kind}.spec" AGENTOPS_DECORATOR_INPUT = "agentops.{entity_kind}.input" AGENTOPS_DECORATOR_OUTPUT = "agentops.{entity_kind}.output" diff --git a/agentops/semconv/span_kinds.py b/agentops/semconv/span_kinds.py index 7e49d2b32..5a75f6d76 100644 --- a/agentops/semconv/span_kinds.py +++ b/agentops/semconv/span_kinds.py @@ -27,8 +27,7 @@ class SpanKind: UNKNOWN = "unknown" CHAIN = "chain" TEXT = "text" - INPUT_GUARDRAIL = "guardrail_input" - OUTPUT_GUARDRAIL = "guardrail_output" + GUARDRAIL = "guardrail" class AgentOpsSpanKindValues(Enum): From d98daeae4b61c165dfbec4f3bfdd02ec8eb76702 Mon Sep 17 00:00:00 2001 From: michi-okahata Date: Tue, 10 Jun 2025 10:32:56 -0700 Subject: [PATCH 06/13] quick fix to comment --- agentops/sdk/decorators/factory.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/agentops/sdk/decorators/factory.py b/agentops/sdk/decorators/factory.py index 283b79af9..7be5c26bb 100644 --- a/agentops/sdk/decorators/factory.py +++ b/agentops/sdk/decorators/factory.py @@ -173,7 +173,7 @@ async def _wrapped_session_async() -> Any: # Set cost attribute if tool if entity_kind == "tool" and cost is not None: span.set_attribute(SpanAttributes.LLM_USAGE_TOOL_COST, cost) - # Set kind attribute if guardrail + # Set spec attribute if guardrail if entity_kind == "guardrail" and (spec == "input" or spec == "output"): span.set_attribute(SpanAttributes.AGENTOPS_DECORATOR_SPEC.format(entity_kind=entity_kind), spec) except Exception as e: @@ -192,7 +192,7 @@ async def _wrapped_session_async() -> Any: # Set cost attribute if tool if entity_kind == "tool" and cost is not None: span.set_attribute(SpanAttributes.LLM_USAGE_TOOL_COST, cost) - # Set kind attribute if guardrail + # Set spec attribute if guardrail if entity_kind == "guardrail" and (spec == "input" or spec == "output"): span.set_attribute(SpanAttributes.AGENTOPS_DECORATOR_SPEC.format(entity_kind=entity_kind), spec) except Exception as e: @@ -213,7 +213,7 @@ async def _wrapped_async() -> Any: # Set cost attribute if tool if entity_kind == "tool" and cost is not None: span.set_attribute(SpanAttributes.LLM_USAGE_TOOL_COST, cost) - # Set kind attribute if guardrail + # Set spec attribute if guardrail if entity_kind == "guardrail" and (spec == "input" or spec == "output"): span.set_attribute( SpanAttributes.AGENTOPS_DECORATOR_SPEC.format(entity_kind=entity_kind), spec @@ -245,7 +245,7 @@ async def _wrapped_async() -> Any: # Set cost attribute if tool if entity_kind == "tool" and cost is not None: span.set_attribute(SpanAttributes.LLM_USAGE_TOOL_COST, cost) - # Set kind attribute if guardrail + # Set spec attribute if guardrail if entity_kind == "guardrail" and (spec == "input" or spec == "output"): span.set_attribute( SpanAttributes.AGENTOPS_DECORATOR_SPEC.format(entity_kind=entity_kind), spec From 695750de983987dfaa587ba37210c299c559adec Mon Sep 17 00:00:00 2001 From: michi-okahata Date: Wed, 11 Jun 2025 14:09:32 -0700 Subject: [PATCH 07/13] fix init --- agentops/__init__.py | 2 +- agentops/sdk/decorators/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/agentops/__init__.py b/agentops/__init__.py index 37f4bed9c..2f10c1420 100755 --- a/agentops/__init__.py +++ b/agentops/__init__.py @@ -15,7 +15,7 @@ from typing import List, Optional, Union, Dict, Any from agentops.client import Client from agentops.sdk.core import TraceContext, tracer -from agentops.sdk.decorators import trace, session, agent, task, workflow, operation, tool +from agentops.sdk.decorators import trace, session, agent, task, workflow, operation, tool, guardrail from agentops.enums import TraceState, SUCCESS, ERROR, UNSET from opentelemetry.trace.status import StatusCode diff --git a/agentops/sdk/decorators/__init__.py b/agentops/sdk/decorators/__init__.py index 6d2963f90..c37f9b74e 100644 --- a/agentops/sdk/decorators/__init__.py +++ b/agentops/sdk/decorators/__init__.py @@ -47,4 +47,4 @@ def session(*args, **kwargs): # noqa: F811 "operation", "tool", "guardrail", -] # "in_guardrail", "out_guardrail"] +] \ No newline at end of file From bb13199b3fd30398b6cbcb340f89d41e564308f9 Mon Sep 17 00:00:00 2001 From: michi-okahata Date: Wed, 11 Jun 2025 14:09:43 -0700 Subject: [PATCH 08/13] guardrail examples --- examples/openai_agents/agent_guardrails.ipynb | 158 ++++++++++++++++++ examples/openai_agents/agent_guardrails.py | 77 +++++++++ 2 files changed, 235 insertions(+) create mode 100644 examples/openai_agents/agent_guardrails.ipynb create mode 100644 examples/openai_agents/agent_guardrails.py diff --git a/examples/openai_agents/agent_guardrails.ipynb b/examples/openai_agents/agent_guardrails.ipynb new file mode 100644 index 000000000..1deec6565 --- /dev/null +++ b/examples/openai_agents/agent_guardrails.ipynb @@ -0,0 +1,158 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f68ce4af", + "metadata": {}, + "source": [ + "# OpenAI Agents Guardrails Demonstration\n", + "\n", + "This notebook demonstrates guardrails using the Agents SDK and how one can observe them using the AgentOps platform." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10bcf29b", + "metadata": {}, + "outputs": [], + "source": [ + "# Install required packages\n", + "%pip install agentops\n", + "%pip install openai-agents\n", + "%pip install dotenv pydantic" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a3be4e68", + "metadata": {}, + "outputs": [], + "source": [ + "# Import dependencies\n", + "from pydantic import BaseModel\n", + "from agents import (\n", + " Agent,\n", + " GuardrailFunctionOutput,\n", + " InputGuardrailTripwireTriggered,\n", + " RunContextWrapper,\n", + " Runner,\n", + " TResponseInputItem,\n", + " input_guardrail,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2d0dddb6", + "metadata": {}, + "outputs": [], + "source": [ + "# Load API keys\n", + "import os\n", + "from dotenv import load_dotenv\n", + "\n", + "load_dotenv()\n", + "\n", + "os.environ[\"AGENTOPS_API_KEY\"] = os.getenv(\"AGENTOPS_API_KEY\", \"your_api_key_here\")\n", + "os.environ[\"OPENAI_API_KEY\"] = os.getenv(\"OPENAI_API_KEY\", \"your_openai_api_key_here\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "114e216b", + "metadata": {}, + "outputs": [], + "source": [ + "# Initialize agentops and import the guardrail decorator\n", + "import agentops\n", + "from agentops import guardrail\n", + "\n", + "agentops.init(api_key=os.environ[\"AGENTOPS_API_KEY\"], tags=[\"agentops-example\"])\n", + "agentops.start_trace()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0bf8b54d", + "metadata": {}, + "outputs": [], + "source": [ + "# OpenAI Agents SDK guardrail example with agentops guardrails decorator for observability\n", + "class MathHomeworkOutput(BaseModel):\n", + " is_math_homework: bool\n", + " reasoning: str\n", + "\n", + "guardrail_agent = Agent( \n", + " name=\"Guardrail check\",\n", + " instructions=\"Check if the user is asking you to do their math homework.\",\n", + " output_type=MathHomeworkOutput,\n", + ")\n", + "\n", + "@input_guardrail\n", + "@guardrail(spec=\"input\") # Specify guardrail type as input or output\n", + "async def math_guardrail( \n", + " ctx: RunContextWrapper[None], agent: Agent, input: str | list[TResponseInputItem]\n", + ") -> GuardrailFunctionOutput:\n", + " result = await Runner.run(guardrail_agent, input, context=ctx.context)\n", + "\n", + " return GuardrailFunctionOutput(\n", + " output_info=result.final_output, \n", + " tripwire_triggered=result.final_output.is_math_homework,\n", + " )\n", + "\n", + "agent = Agent( \n", + " name=\"Customer support agent\",\n", + " instructions=\"You are a customer support agent. You help customers with their questions.\",\n", + " input_guardrails=[math_guardrail],\n", + ")\n", + "\n", + "async def main():\n", + " # This should trip the guardrail\n", + " try:\n", + " await Runner.run(agent, \"Hello, can you help me solve for x: 2x + 3 = 11?\")\n", + " print(\"Guardrail didn't trip - this is unexpected\")\n", + "\n", + " except InputGuardrailTripwireTriggered:\n", + " print(\"Math homework guardrail tripped\")\n", + "\n", + "await main()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "63bf8e09", + "metadata": {}, + "outputs": [], + "source": [ + "agentops.end_trace()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/openai_agents/agent_guardrails.py b/examples/openai_agents/agent_guardrails.py new file mode 100644 index 000000000..20c1b7ac8 --- /dev/null +++ b/examples/openai_agents/agent_guardrails.py @@ -0,0 +1,77 @@ +# # OpenAI Agents Guardrails Demonstration +# +# This notebook demonstrates guardrails using the Agents SDK and how one can observe them using the AgentOps platform. + +# Install required packages +# %pip install agentops +# %pip install openai-agents +# %pip install dotenv pydantic + +# Import dependencies +from pydantic import BaseModel +from agents import ( + Agent, + GuardrailFunctionOutput, + InputGuardrailTripwireTriggered, + RunContextWrapper, + Runner, + TResponseInputItem, + input_guardrail, +) + +# Initialize agentops and import the guardrail decorator +import agentops +from agentops import guardrail + +# Load API keys +import os +from dotenv import load_dotenv +import asyncio + +load_dotenv() + +os.environ["AGENTOPS_API_KEY"] = os.getenv("AGENTOPS_API_KEY", "your_api_key_here") +os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY", "your_openai_api_key_here") + +agentops.init(api_key=os.environ["AGENTOPS_API_KEY"], tags=["agentops-example"]) + +# OpenAI Agents SDK guardrail example with agentops guardrails decorator for observability +class MathHomeworkOutput(BaseModel): + is_math_homework: bool + reasoning: str + +guardrail_agent = Agent( + name="Guardrail check", + instructions="Check if the user is asking you to do their math homework.", + output_type=MathHomeworkOutput, +) + +@input_guardrail +@guardrail(spec="input") # Specify guardrail type as input or output +async def math_guardrail( + ctx: RunContextWrapper[None], agent: Agent, input: str | list[TResponseInputItem] +) -> GuardrailFunctionOutput: + result = await Runner.run(guardrail_agent, input, context=ctx.context) + + return GuardrailFunctionOutput( + output_info=result.final_output, + tripwire_triggered=result.final_output.is_math_homework, + ) + +agent = Agent( + name="Customer support agent", + instructions="You are a customer support agent. You help customers with their questions.", + input_guardrails=[math_guardrail], +) + +async def main(): + # This should trip the guardrail + try: + await Runner.run(agent, "Hello, can you help me solve for x: 2x + 3 = 11?") + print("Guardrail didn't trip - this is unexpected") + + except InputGuardrailTripwireTriggered: + print("Math homework guardrail tripped") + +if __name__ == "__main__": + asyncio.run(main()) From fef633d4e796d32a96f538f32a17706ed1dc10b2 Mon Sep 17 00:00:00 2001 From: michi-okahata Date: Wed, 11 Jun 2025 15:06:55 -0700 Subject: [PATCH 09/13] add guardrail docs --- docs/v2/concepts/decorators.mdx | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/v2/concepts/decorators.mdx b/docs/v2/concepts/decorators.mdx index 4596e4193..7782175c3 100644 --- a/docs/v2/concepts/decorators.mdx +++ b/docs/v2/concepts/decorators.mdx @@ -14,6 +14,7 @@ AgentOps provides the following decorators: | `@workflow` | Track a sequence of operations | WORKFLOW span | | `@task` | Track smaller units of work (similar to operations) | TASK span | | `@tool` | Track tool usage and cost in agent operations | TOOL span | +| `@guardrail` | Track guardrail input and output | GUARDRAIL span | ## Decorator Hierarchy @@ -235,6 +236,27 @@ The tool decorator provides: - Support for all function types (sync, async, generator, async generator) - Cost accumulation in generator and async generator operations +### @guardrail + +The `@guardrail` decorator tracks guardrail input and output. You can specify the guardrail type (`"input"` or `"output"`) with the `spec` parameter. + +```python +from agentops.sdk.decorators import guardrail +import agentops +import re + +# Initialize AgentOps +agentops.init(api_key="YOUR_API_KEY") + +@guardrail(spec="input") +def secret_key_guardrail(input): + pattern = r'\bsk-[a-zA-Z0-9]{10,}\b' + result = True if re.search(pattern, input) else False + return { + "tripwire_triggered" : result + } +``` + ## Decorator Attributes You can pass additional attributes to decorators: From b4a8c818f22cf807cd4cd890156c9ba29b7bd463 Mon Sep 17 00:00:00 2001 From: michi-okahata Date: Wed, 11 Jun 2025 15:08:30 -0700 Subject: [PATCH 10/13] ruff fix --- agentops/client/api/base.py | 3 +-- agentops/client/client.py | 6 +++--- agentops/instrumentation/common/attributes.py | 3 +-- agentops/instrumentation/google_adk/patch.py | 18 +++++++++--------- .../openai_agents/attributes/completion.py | 6 +++--- agentops/sdk/decorators/__init__.py | 2 +- examples/openai_agents/agent_guardrails.ipynb | 15 ++++++++++----- examples/openai_agents/agent_guardrails.py | 18 ++++++++++++------ tests/unit/sdk/instrumentation_tester.py | 3 +-- 9 files changed, 41 insertions(+), 33 deletions(-) diff --git a/agentops/client/api/base.py b/agentops/client/api/base.py index 44140956e..4891e743f 100644 --- a/agentops/client/api/base.py +++ b/agentops/client/api/base.py @@ -15,8 +15,7 @@ class TokenFetcher(Protocol): """Protocol for token fetching functions""" - def __call__(self, api_key: str) -> str: - ... + def __call__(self, api_key: str) -> str: ... class BaseApiClient: diff --git a/agentops/client/client.py b/agentops/client/client.py index 0fe95b95c..6a2c8ad41 100644 --- a/agentops/client/client.py +++ b/agentops/client/client.py @@ -40,9 +40,9 @@ class Client: config: Config _initialized: bool _init_trace_context: Optional[TraceContext] = None # Stores the context of the auto-started trace - _legacy_session_for_init_trace: Optional[ - Session - ] = None # Stores the legacy Session wrapper for the auto-started trace + _legacy_session_for_init_trace: Optional[Session] = ( + None # Stores the legacy Session wrapper for the auto-started trace + ) __instance = None # Class variable for singleton pattern diff --git a/agentops/instrumentation/common/attributes.py b/agentops/instrumentation/common/attributes.py index f267d615e..da33fbd6c 100644 --- a/agentops/instrumentation/common/attributes.py +++ b/agentops/instrumentation/common/attributes.py @@ -98,8 +98,7 @@ class IndexedAttribute(Protocol): formatting of attribute keys based on the indices. """ - def format(self, *, i: int, j: Optional[int] = None) -> str: - ... + def format(self, *, i: int, j: Optional[int] = None) -> str: ... IndexedAttributeMap = Dict[IndexedAttribute, str] # target_attribute_key: source_attribute diff --git a/agentops/instrumentation/google_adk/patch.py b/agentops/instrumentation/google_adk/patch.py index 88d9aa2df..bb62c42f7 100644 --- a/agentops/instrumentation/google_adk/patch.py +++ b/agentops/instrumentation/google_adk/patch.py @@ -304,16 +304,16 @@ def _extract_llm_attributes(llm_request_dict: dict, llm_response: Any) -> dict: elif "function_call" in part: # This is a function call in the response func_call = part["function_call"] - attributes[ - MessageAttributes.COMPLETION_TOOL_CALL_NAME.format(i=0, j=tool_call_index) - ] = func_call.get("name", "") - attributes[ - MessageAttributes.COMPLETION_TOOL_CALL_ARGUMENTS.format(i=0, j=tool_call_index) - ] = json.dumps(func_call.get("args", {})) + attributes[MessageAttributes.COMPLETION_TOOL_CALL_NAME.format(i=0, j=tool_call_index)] = ( + func_call.get("name", "") + ) + attributes[MessageAttributes.COMPLETION_TOOL_CALL_ARGUMENTS.format(i=0, j=tool_call_index)] = ( + json.dumps(func_call.get("args", {})) + ) if "id" in func_call: - attributes[ - MessageAttributes.COMPLETION_TOOL_CALL_ID.format(i=0, j=tool_call_index) - ] = func_call["id"] + attributes[MessageAttributes.COMPLETION_TOOL_CALL_ID.format(i=0, j=tool_call_index)] = ( + func_call["id"] + ) tool_call_index += 1 if text_parts: diff --git a/agentops/instrumentation/openai_agents/attributes/completion.py b/agentops/instrumentation/openai_agents/attributes/completion.py index d035d6cff..10bd6bfdc 100644 --- a/agentops/instrumentation/openai_agents/attributes/completion.py +++ b/agentops/instrumentation/openai_agents/attributes/completion.py @@ -115,9 +115,9 @@ def get_raw_response_attributes(response: Dict[str, Any]) -> Dict[str, Any]: result[MessageAttributes.COMPLETION_TOOL_CALL_NAME.format(i=j, j=k)] = function.get( "name", "" ) - result[ - MessageAttributes.COMPLETION_TOOL_CALL_ARGUMENTS.format(i=j, j=k) - ] = function.get("arguments", "") + result[MessageAttributes.COMPLETION_TOOL_CALL_ARGUMENTS.format(i=j, j=k)] = ( + function.get("arguments", "") + ) return result diff --git a/agentops/sdk/decorators/__init__.py b/agentops/sdk/decorators/__init__.py index c37f9b74e..6881bc19a 100644 --- a/agentops/sdk/decorators/__init__.py +++ b/agentops/sdk/decorators/__init__.py @@ -47,4 +47,4 @@ def session(*args, **kwargs): # noqa: F811 "operation", "tool", "guardrail", -] \ No newline at end of file +] diff --git a/examples/openai_agents/agent_guardrails.ipynb b/examples/openai_agents/agent_guardrails.ipynb index 1deec6565..317a0073e 100644 --- a/examples/openai_agents/agent_guardrails.ipynb +++ b/examples/openai_agents/agent_guardrails.ipynb @@ -87,30 +87,34 @@ " is_math_homework: bool\n", " reasoning: str\n", "\n", - "guardrail_agent = Agent( \n", + "\n", + "guardrail_agent = Agent(\n", " name=\"Guardrail check\",\n", " instructions=\"Check if the user is asking you to do their math homework.\",\n", " output_type=MathHomeworkOutput,\n", ")\n", "\n", + "\n", "@input_guardrail\n", - "@guardrail(spec=\"input\") # Specify guardrail type as input or output\n", - "async def math_guardrail( \n", + "@guardrail(spec=\"input\") # Specify guardrail type as input or output\n", + "async def math_guardrail(\n", " ctx: RunContextWrapper[None], agent: Agent, input: str | list[TResponseInputItem]\n", ") -> GuardrailFunctionOutput:\n", " result = await Runner.run(guardrail_agent, input, context=ctx.context)\n", "\n", " return GuardrailFunctionOutput(\n", - " output_info=result.final_output, \n", + " output_info=result.final_output,\n", " tripwire_triggered=result.final_output.is_math_homework,\n", " )\n", "\n", - "agent = Agent( \n", + "\n", + "agent = Agent(\n", " name=\"Customer support agent\",\n", " instructions=\"You are a customer support agent. You help customers with their questions.\",\n", " input_guardrails=[math_guardrail],\n", ")\n", "\n", + "\n", "async def main():\n", " # This should trip the guardrail\n", " try:\n", @@ -120,6 +124,7 @@ " except InputGuardrailTripwireTriggered:\n", " print(\"Math homework guardrail tripped\")\n", "\n", + "\n", "await main()" ] }, diff --git a/examples/openai_agents/agent_guardrails.py b/examples/openai_agents/agent_guardrails.py index 20c1b7ac8..2429dab4b 100644 --- a/examples/openai_agents/agent_guardrails.py +++ b/examples/openai_agents/agent_guardrails.py @@ -1,5 +1,5 @@ # # OpenAI Agents Guardrails Demonstration -# +# # This notebook demonstrates guardrails using the Agents SDK and how one can observe them using the AgentOps platform. # Install required packages @@ -35,35 +35,40 @@ agentops.init(api_key=os.environ["AGENTOPS_API_KEY"], tags=["agentops-example"]) + # OpenAI Agents SDK guardrail example with agentops guardrails decorator for observability class MathHomeworkOutput(BaseModel): is_math_homework: bool reasoning: str -guardrail_agent = Agent( + +guardrail_agent = Agent( name="Guardrail check", instructions="Check if the user is asking you to do their math homework.", output_type=MathHomeworkOutput, ) + @input_guardrail -@guardrail(spec="input") # Specify guardrail type as input or output -async def math_guardrail( +@guardrail(spec="input") # Specify guardrail type as input or output +async def math_guardrail( ctx: RunContextWrapper[None], agent: Agent, input: str | list[TResponseInputItem] ) -> GuardrailFunctionOutput: result = await Runner.run(guardrail_agent, input, context=ctx.context) return GuardrailFunctionOutput( - output_info=result.final_output, + output_info=result.final_output, tripwire_triggered=result.final_output.is_math_homework, ) -agent = Agent( + +agent = Agent( name="Customer support agent", instructions="You are a customer support agent. You help customers with their questions.", input_guardrails=[math_guardrail], ) + async def main(): # This should trip the guardrail try: @@ -73,5 +78,6 @@ async def main(): except InputGuardrailTripwireTriggered: print("Math homework guardrail tripped") + if __name__ == "__main__": asyncio.run(main()) diff --git a/tests/unit/sdk/instrumentation_tester.py b/tests/unit/sdk/instrumentation_tester.py index 606a91bfb..4175270d6 100644 --- a/tests/unit/sdk/instrumentation_tester.py +++ b/tests/unit/sdk/instrumentation_tester.py @@ -45,8 +45,7 @@ def reset_trace_globals(): class HasAttributesViaProperty(Protocol): @property - def attributes(self) -> Attributes: - ... + def attributes(self) -> Attributes: ... class HasAttributesViaAttr(Protocol): From 2998198f6331ce1f86aba6ae1bc0c15e6dec24f0 Mon Sep 17 00:00:00 2001 From: michi-okahata Date: Wed, 11 Jun 2025 15:10:22 -0700 Subject: [PATCH 11/13] pc fix --- agentops/client/api/base.py | 3 ++- agentops/client/client.py | 6 +++--- agentops/instrumentation/common/attributes.py | 3 ++- agentops/instrumentation/google_adk/patch.py | 18 +++++++++--------- .../openai_agents/attributes/completion.py | 6 +++--- tests/unit/sdk/instrumentation_tester.py | 3 ++- 6 files changed, 21 insertions(+), 18 deletions(-) diff --git a/agentops/client/api/base.py b/agentops/client/api/base.py index 4891e743f..44140956e 100644 --- a/agentops/client/api/base.py +++ b/agentops/client/api/base.py @@ -15,7 +15,8 @@ class TokenFetcher(Protocol): """Protocol for token fetching functions""" - def __call__(self, api_key: str) -> str: ... + def __call__(self, api_key: str) -> str: + ... class BaseApiClient: diff --git a/agentops/client/client.py b/agentops/client/client.py index 6a2c8ad41..0fe95b95c 100644 --- a/agentops/client/client.py +++ b/agentops/client/client.py @@ -40,9 +40,9 @@ class Client: config: Config _initialized: bool _init_trace_context: Optional[TraceContext] = None # Stores the context of the auto-started trace - _legacy_session_for_init_trace: Optional[Session] = ( - None # Stores the legacy Session wrapper for the auto-started trace - ) + _legacy_session_for_init_trace: Optional[ + Session + ] = None # Stores the legacy Session wrapper for the auto-started trace __instance = None # Class variable for singleton pattern diff --git a/agentops/instrumentation/common/attributes.py b/agentops/instrumentation/common/attributes.py index da33fbd6c..f267d615e 100644 --- a/agentops/instrumentation/common/attributes.py +++ b/agentops/instrumentation/common/attributes.py @@ -98,7 +98,8 @@ class IndexedAttribute(Protocol): formatting of attribute keys based on the indices. """ - def format(self, *, i: int, j: Optional[int] = None) -> str: ... + def format(self, *, i: int, j: Optional[int] = None) -> str: + ... IndexedAttributeMap = Dict[IndexedAttribute, str] # target_attribute_key: source_attribute diff --git a/agentops/instrumentation/google_adk/patch.py b/agentops/instrumentation/google_adk/patch.py index bb62c42f7..88d9aa2df 100644 --- a/agentops/instrumentation/google_adk/patch.py +++ b/agentops/instrumentation/google_adk/patch.py @@ -304,16 +304,16 @@ def _extract_llm_attributes(llm_request_dict: dict, llm_response: Any) -> dict: elif "function_call" in part: # This is a function call in the response func_call = part["function_call"] - attributes[MessageAttributes.COMPLETION_TOOL_CALL_NAME.format(i=0, j=tool_call_index)] = ( - func_call.get("name", "") - ) - attributes[MessageAttributes.COMPLETION_TOOL_CALL_ARGUMENTS.format(i=0, j=tool_call_index)] = ( - json.dumps(func_call.get("args", {})) - ) + attributes[ + MessageAttributes.COMPLETION_TOOL_CALL_NAME.format(i=0, j=tool_call_index) + ] = func_call.get("name", "") + attributes[ + MessageAttributes.COMPLETION_TOOL_CALL_ARGUMENTS.format(i=0, j=tool_call_index) + ] = json.dumps(func_call.get("args", {})) if "id" in func_call: - attributes[MessageAttributes.COMPLETION_TOOL_CALL_ID.format(i=0, j=tool_call_index)] = ( - func_call["id"] - ) + attributes[ + MessageAttributes.COMPLETION_TOOL_CALL_ID.format(i=0, j=tool_call_index) + ] = func_call["id"] tool_call_index += 1 if text_parts: diff --git a/agentops/instrumentation/openai_agents/attributes/completion.py b/agentops/instrumentation/openai_agents/attributes/completion.py index 10bd6bfdc..d035d6cff 100644 --- a/agentops/instrumentation/openai_agents/attributes/completion.py +++ b/agentops/instrumentation/openai_agents/attributes/completion.py @@ -115,9 +115,9 @@ def get_raw_response_attributes(response: Dict[str, Any]) -> Dict[str, Any]: result[MessageAttributes.COMPLETION_TOOL_CALL_NAME.format(i=j, j=k)] = function.get( "name", "" ) - result[MessageAttributes.COMPLETION_TOOL_CALL_ARGUMENTS.format(i=j, j=k)] = ( - function.get("arguments", "") - ) + result[ + MessageAttributes.COMPLETION_TOOL_CALL_ARGUMENTS.format(i=j, j=k) + ] = function.get("arguments", "") return result diff --git a/tests/unit/sdk/instrumentation_tester.py b/tests/unit/sdk/instrumentation_tester.py index 4175270d6..606a91bfb 100644 --- a/tests/unit/sdk/instrumentation_tester.py +++ b/tests/unit/sdk/instrumentation_tester.py @@ -45,7 +45,8 @@ def reset_trace_globals(): class HasAttributesViaProperty(Protocol): @property - def attributes(self) -> Attributes: ... + def attributes(self) -> Attributes: + ... class HasAttributesViaAttr(Protocol): From 1a6445e31dd7962aba1466a8f778cef08b1ef823 Mon Sep 17 00:00:00 2001 From: Pratyush Shukla Date: Thu, 12 Jun 2025 03:51:17 +0530 Subject: [PATCH 12/13] fix agentops init in example notebook --- examples/openai_agents/agent_guardrails.ipynb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/openai_agents/agent_guardrails.ipynb b/examples/openai_agents/agent_guardrails.ipynb index 317a0073e..4c1739c30 100644 --- a/examples/openai_agents/agent_guardrails.ipynb +++ b/examples/openai_agents/agent_guardrails.ipynb @@ -71,8 +71,8 @@ "import agentops\n", "from agentops import guardrail\n", "\n", - "agentops.init(api_key=os.environ[\"AGENTOPS_API_KEY\"], tags=[\"agentops-example\"])\n", - "agentops.start_trace()" + "agentops.init(api_key=os.environ[\"AGENTOPS_API_KEY\"], tags=[\"agentops-example\"], auto_start_session=False)\n", + "tracer = agentops.start_trace(trace_name=\"OpenAI Agents Guardrail Example\")" ] }, { @@ -135,13 +135,13 @@ "metadata": {}, "outputs": [], "source": [ - "agentops.end_trace()" + "agentops.end_trace(tracer, end_state=\"Success\")" ] } ], "metadata": { "kernelspec": { - "display_name": ".venv", + "display_name": "agentops (3.11.11)", "language": "python", "name": "python3" }, From 5949af1f39cfc9c0892d22a5480754ea1c36c9df Mon Sep 17 00:00:00 2001 From: Pratyush Shukla Date: Thu, 12 Jun 2025 03:51:26 +0530 Subject: [PATCH 13/13] ruff again --- examples/google_adk/human_approval.ipynb | 2 +- examples/openai_agents/agent_patterns.ipynb | 3 +-- examples/openai_agents/agents_tools.ipynb | 7 ------- examples/openai_agents/customer_service_agent.ipynb | 2 -- 4 files changed, 2 insertions(+), 12 deletions(-) diff --git a/examples/google_adk/human_approval.ipynb b/examples/google_adk/human_approval.ipynb index 92a871781..982d80ad2 100644 --- a/examples/google_adk/human_approval.ipynb +++ b/examples/google_adk/human_approval.ipynb @@ -183,7 +183,7 @@ " \"\"\"\n", " Prompts for human approval and returns the decision as a JSON string.\n", " \"\"\"\n", - " print(f\"🔔 HUMAN APPROVAL REQUIRED:\")\n", + " print(\"🔔 HUMAN APPROVAL REQUIRED:\")\n", " print(f\" Amount: ${amount:,.2f}\")\n", " print(f\" Reason: {reason}\")\n", " decision = \"\"\n", diff --git a/examples/openai_agents/agent_patterns.ipynb b/examples/openai_agents/agent_patterns.ipynb index d3614c864..1af989db1 100644 --- a/examples/openai_agents/agent_patterns.ipynb +++ b/examples/openai_agents/agent_patterns.ipynb @@ -100,10 +100,9 @@ " OutputGuardrailTripwireTriggered,\n", " input_guardrail,\n", " output_guardrail,\n", - " RawResponsesStreamEvent,\n", ")\n", "\n", - "from openai.types.responses import ResponseContentPartDoneEvent, ResponseTextDeltaEvent" + "from openai.types.responses import ResponseTextDeltaEvent" ] }, { diff --git a/examples/openai_agents/agents_tools.ipynb b/examples/openai_agents/agents_tools.ipynb index a8bf70b68..b27830b06 100755 --- a/examples/openai_agents/agents_tools.ipynb +++ b/examples/openai_agents/agents_tools.ipynb @@ -79,24 +79,17 @@ "metadata": {}, "outputs": [], "source": [ - "import asyncio\n", "import base64\n", "import os\n", "import subprocess\n", "import sys\n", "import tempfile\n", - "from typing import Literal, Union\n", "\n", "from agents import (\n", " Agent,\n", - " AsyncComputer,\n", - " Button,\n", " CodeInterpreterTool,\n", - " ComputerTool,\n", - " Environment,\n", " FileSearchTool,\n", " ImageGenerationTool,\n", - " ModelSettings,\n", " Runner,\n", " WebSearchTool,\n", " trace,\n", diff --git a/examples/openai_agents/customer_service_agent.ipynb b/examples/openai_agents/customer_service_agent.ipynb index a873049a8..6135c44dd 100644 --- a/examples/openai_agents/customer_service_agent.ipynb +++ b/examples/openai_agents/customer_service_agent.ipynb @@ -67,10 +67,8 @@ "source": [ "from __future__ import annotations as _annotations # noqa: F404\n", "\n", - "import asyncio\n", "import random\n", "import uuid\n", - "import sys\n", "\n", "from pydantic import BaseModel\n", "import agentops\n",