diff --git a/instrumentation-genai/opentelemetry-instrumentation-openai-agents-v2/CHANGELOG.md b/instrumentation-genai/opentelemetry-instrumentation-openai-agents-v2/CHANGELOG.md index 90f8c11e01..eed69af472 100644 --- a/instrumentation-genai/opentelemetry-instrumentation-openai-agents-v2/CHANGELOG.md +++ b/instrumentation-genai/opentelemetry-instrumentation-openai-agents-v2/CHANGELOG.md @@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased + +- Import `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` from + `opentelemetry.util.genai.environment_variables` instead of re-defining it locally; + add `meter_provider` parameter to `GenAISemanticProcessor` so a caller-supplied + provider is no longer silently ignored; use `create_duration_histogram` and + `create_token_histogram` from `opentelemetry.util.genai.instruments` to apply + semconv-specified histogram bucket boundaries. + ([#4456](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4456)) - Align AgentSpanData test stubs and span processor with real OpenAI Agents SDK; remove non-existent `operation`, `description`, `agent_id`, and `model` fields. ([#4229](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4229)) diff --git a/instrumentation-genai/opentelemetry-instrumentation-openai-agents-v2/src/opentelemetry/instrumentation/openai_agents/__init__.py b/instrumentation-genai/opentelemetry-instrumentation-openai-agents-v2/src/opentelemetry/instrumentation/openai_agents/__init__.py index 339ccc5ba2..3dc868d3a4 100644 --- a/instrumentation-genai/opentelemetry-instrumentation-openai-agents-v2/src/opentelemetry/instrumentation/openai_agents/__init__.py +++ b/instrumentation-genai/opentelemetry-instrumentation-openai-agents-v2/src/opentelemetry/instrumentation/openai_agents/__init__.py @@ -15,6 +15,9 @@ ) from opentelemetry.semconv.schemas import Schemas from opentelemetry.trace import get_tracer +from opentelemetry.util.genai.environment_variables import ( + OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT, +) from .package import _instruments from .span_processor import ( @@ -38,7 +41,6 @@ logger = logging.getLogger(__name__) -_CONTENT_CAPTURE_ENV = "OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT" _SYSTEM_OVERRIDE_ENV = "OTEL_INSTRUMENTATION_OPENAI_AGENTS_SYSTEM" _CAPTURE_CONTENT_ENV = "OTEL_INSTRUMENTATION_OPENAI_AGENTS_CAPTURE_CONTENT" _CAPTURE_METRICS_ENV = "OTEL_INSTRUMENTATION_OPENAI_AGENTS_CAPTURE_METRICS" @@ -147,9 +149,9 @@ def _instrument(self, **kwargs) -> None: content_override = kwargs.get("capture_message_content") if content_override is None: - content_override = os.getenv(_CONTENT_CAPTURE_ENV) or os.getenv( - _CAPTURE_CONTENT_ENV - ) + content_override = os.getenv( + OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT + ) or os.getenv(_CAPTURE_CONTENT_ENV) content_mode = _resolve_content_mode(content_override) metrics_override = kwargs.get("capture_metrics") @@ -157,6 +159,7 @@ def _instrument(self, **kwargs) -> None: metrics_override = os.getenv(_CAPTURE_METRICS_ENV) metrics_enabled = _resolve_bool(metrics_override, default=True) + meter_provider = kwargs.get("meter_provider") agent_name = kwargs.get("agent_name") agent_id = kwargs.get("agent_id") agent_description = kwargs.get("agent_description") @@ -170,6 +173,7 @@ def _instrument(self, **kwargs) -> None: include_sensitive_data=content_mode != ContentCaptureMode.NO_CONTENT, content_mode=content_mode, + meter_provider=meter_provider, metrics_enabled=metrics_enabled, agent_name=agent_name, agent_id=agent_id, diff --git a/instrumentation-genai/opentelemetry-instrumentation-openai-agents-v2/src/opentelemetry/instrumentation/openai_agents/span_processor.py b/instrumentation-genai/opentelemetry-instrumentation-openai-agents-v2/src/opentelemetry/instrumentation/openai_agents/span_processor.py index 55539a4931..eaff6a04fc 100644 --- a/instrumentation-genai/opentelemetry-instrumentation-openai-agents-v2/src/opentelemetry/instrumentation/openai_agents/span_processor.py +++ b/instrumentation-genai/opentelemetry-instrumentation-openai-agents-v2/src/opentelemetry/instrumentation/openai_agents/span_processor.py @@ -24,8 +24,6 @@ from typing import TYPE_CHECKING, Any, Dict, Iterator, Optional, Sequence from urllib.parse import urlparse -from opentelemetry.util.genai.utils import gen_ai_json_dumps - try: from agents.tracing import Span, Trace, TracingProcessor from agents.tracing.span_data import ( @@ -55,7 +53,7 @@ ) # type: ignore[assignment] from opentelemetry.context import attach, detach -from opentelemetry.metrics import Histogram, get_meter +from opentelemetry.metrics import MeterProvider, get_meter from opentelemetry.semconv._incubating.attributes import ( gen_ai_attributes as GenAIAttributes, ) @@ -70,8 +68,15 @@ Tracer, set_span_in_context, ) +from opentelemetry.util.genai.instruments import ( + create_duration_histogram, + create_token_histogram, +) +from opentelemetry.util.genai.utils import gen_ai_json_dumps from opentelemetry.util.types import AttributeValue +from .version import __version__ + # Import all semantic convention constants # ---- GenAI semantic convention helpers (embedded from constants.py) ---- @@ -439,6 +444,7 @@ def __init__( agent_description: Optional[str] = None, server_address: Optional[str] = None, server_port: Optional[int] = None, + meter_provider: Optional[MeterProvider] = None, metrics_enabled: bool = True, agent_name_default: Optional[str] = None, agent_id_default: Optional[str] = None, @@ -459,6 +465,7 @@ def __init__( agent_description: Description of the agent (can be overridden by env var) server_address: Server address (can be overridden by env var or base_url) server_port: Server port (can be overridden by env var or base_url) + meter_provider: Optional MeterProvider for metrics """ self._tracer = tracer self.system_name = normalize_provider(system_name) or system_name @@ -515,10 +522,10 @@ def __init__( # Metrics configuration self._metrics_enabled = metrics_enabled self._meter = None - self._duration_histogram: Optional[Histogram] = None - self._token_usage_histogram: Optional[Histogram] = None + self._duration_histogram: Optional[object] = None + self._token_usage_histogram: Optional[object] = None if self._metrics_enabled: - self._init_metrics() + self._init_metrics(meter_provider) def _get_server_attributes(self) -> dict[str, Any]: """Get server attributes from configured values.""" @@ -529,25 +536,15 @@ def _get_server_attributes(self) -> dict[str, Any]: attrs[ServerAttributes.SERVER_PORT] = self.server_port return attrs - def _init_metrics(self): + def _init_metrics(self, meter_provider: Optional[MeterProvider] = None): """Initialize metric instruments.""" self._meter = get_meter( - "opentelemetry.instrumentation.openai_agents", "0.1.0" - ) - - # Operation duration histogram - self._duration_histogram = self._meter.create_histogram( - name="gen_ai.client.operation.duration", - description="GenAI operation duration", - unit="s", - ) - - # Token usage histogram - self._token_usage_histogram = self._meter.create_histogram( - name="gen_ai.client.token.usage", - description="Number of input and output tokens used", - unit="{token}", + "opentelemetry.instrumentation.openai_agents", + __version__, + meter_provider, ) + self._duration_histogram = create_duration_histogram(self._meter) + self._token_usage_histogram = create_token_histogram(self._meter) def _record_metrics( self, span: Span[Any], attributes: dict[str, AttributeValue]