diff --git a/src/strands/telemetry/tracer.py b/src/strands/telemetry/tracer.py index a68aad8b7..efddb0cae 100644 --- a/src/strands/telemetry/tracer.py +++ b/src/strands/telemetry/tracer.py @@ -80,10 +80,6 @@ class Tracer: When the OTEL_EXPORTER_OTLP_ENDPOINT environment variable is set, traces are sent to the OTLP endpoint. - Attributes: - use_latest_genai_conventions: If True, uses the latest experimental GenAI semantic conventions. - include_tool_definitions: If True, includes detailed tool definitions in the agent trace span. - Both attributes are controlled by including "gen_ai_latest_experimental" or "gen_ai_tool_definitions", respectively, in the OTEL_SEMCONV_STABILITY_OPT_IN environment variable. """ @@ -98,8 +94,8 @@ def __init__(self) -> None: # Read OTEL_SEMCONV_STABILITY_OPT_IN environment variable opt_in_values = self._parse_semconv_opt_in() - self.use_latest_genai_conventions = "gen_ai_latest_experimental" in opt_in_values - self.include_tool_definitions = "gen_ai_tool_definitions" in opt_in_values + self._use_latest_genai_conventions = "gen_ai_latest_experimental" in opt_in_values + self._include_tool_definitions = "gen_ai_tool_definitions" in opt_in_values def _parse_semconv_opt_in(self) -> set[str]: """Parse the OTEL_SEMCONV_STABILITY_OPT_IN environment variable. @@ -334,7 +330,7 @@ def end_model_invoke_span( # Add optional attributes if they have values self._add_optional_usage_and_metrics_attributes(attributes, usage, metrics) - if self.use_latest_genai_conventions: + if self._use_latest_genai_conventions: self._add_event( span, "gen_ai.client.inference.operation.details", @@ -384,7 +380,7 @@ def start_tool_call_span(self, tool: ToolUse, parent_span: Optional[Span] = None span_name = f"execute_tool {tool['name']}" span = self._start_span(span_name, parent_span, attributes=attributes, span_kind=trace_api.SpanKind.INTERNAL) - if self.use_latest_genai_conventions: + if self._use_latest_genai_conventions: self._add_event( span, "gen_ai.client.inference.operation.details", @@ -440,7 +436,7 @@ def end_tool_call_span( } ) - if self.use_latest_genai_conventions: + if self._use_latest_genai_conventions: self._add_event( span, "gen_ai.client.inference.operation.details", @@ -531,7 +527,7 @@ def end_event_loop_cycle_span( if tool_result_message: event_attributes["tool.result"] = serialize(tool_result_message["content"]) - if self.use_latest_genai_conventions: + if self._use_latest_genai_conventions: self._add_event( span, "gen_ai.client.inference.operation.details", @@ -587,7 +583,7 @@ def start_agent_span( if tools: attributes["gen_ai.agent.tools"] = serialize(tools) - if self.include_tool_definitions and tools_config: + if self._include_tool_definitions and tools_config: try: tool_definitions = self._construct_tool_definitions(tools_config) attributes["gen_ai.tool.definitions"] = serialize(tool_definitions) @@ -625,7 +621,7 @@ def end_agent_span( attributes: Dict[str, AttributeValue] = {} if response: - if self.use_latest_genai_conventions: + if self._use_latest_genai_conventions: self._add_event( span, "gen_ai.client.inference.operation.details", @@ -692,7 +688,7 @@ def start_multiagent_span( span = self._start_span(operation, attributes=attributes, span_kind=trace_api.SpanKind.CLIENT) - if self.use_latest_genai_conventions: + if self._use_latest_genai_conventions: parts: list[dict[str, Any]] = [] if isinstance(task, list): parts = self._map_content_blocks_to_otel_parts(task) @@ -719,7 +715,7 @@ def end_swarm_span( ) -> None: """End a swarm span with results.""" if result: - if self.use_latest_genai_conventions: + if self._use_latest_genai_conventions: self._add_event( span, "gen_ai.client.inference.operation.details", @@ -754,7 +750,7 @@ def _get_common_attributes( A dictionary of attributes following the appropriate GenAI conventions. """ common_attributes = {"gen_ai.operation.name": operation_name} - if self.use_latest_genai_conventions: + if self._use_latest_genai_conventions: common_attributes.update( { "gen_ai.provider.name": "strands-agents", @@ -775,7 +771,7 @@ def _add_event_messages(self, span: Span, messages: Messages) -> None: span: The span to which events will be added. messages: List of messages being sent to the agent. """ - if self.use_latest_genai_conventions: + if self._use_latest_genai_conventions: input_messages: list = [] for message in messages: input_messages.append( diff --git a/tests/strands/telemetry/test_tracer.py b/tests/strands/telemetry/test_tracer.py index 25d477588..98cfb459f 100644 --- a/tests/strands/telemetry/test_tracer.py +++ b/tests/strands/telemetry/test_tracer.py @@ -163,11 +163,11 @@ def test_start_model_invoke_span(mock_tracer): assert span is not None -def test_start_model_invoke_span_latest_conventions(mock_tracer): +def test_start_model_invoke_span_latest_conventions(mock_tracer, monkeypatch): """Test starting a model invoke span with the latest semantic conventions.""" with mock.patch("strands.telemetry.tracer.trace_api.get_tracer", return_value=mock_tracer): + monkeypatch.setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "gen_ai_latest_experimental") tracer = Tracer() - tracer.use_latest_genai_conventions = True tracer.tracer = mock_tracer mock_span = mock.MagicMock() @@ -244,11 +244,11 @@ def test_end_model_invoke_span(mock_span): mock_span.end.assert_called_once() -def test_end_model_invoke_span_latest_conventions(mock_span): +def test_end_model_invoke_span_latest_conventions(mock_span, monkeypatch): """Test ending a model invoke span with the latest semantic conventions.""" with mock.patch("strands.telemetry.tracer.trace_api.get_tracer", return_value=mock_tracer): + monkeypatch.setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "gen_ai_latest_experimental") tracer = Tracer() - tracer.use_latest_genai_conventions = True message = {"role": "assistant", "content": [{"text": "Response"}]} usage = Usage(inputTokens=10, outputTokens=20, totalTokens=30) metrics = Metrics(latencyMs=20, timeToFirstByteMs=10) @@ -307,11 +307,11 @@ def test_start_tool_call_span(mock_tracer): assert span is not None -def test_start_tool_call_span_latest_conventions(mock_tracer): +def test_start_tool_call_span_latest_conventions(mock_tracer, monkeypatch): """Test starting a tool call span with the latest semantic conventions.""" with mock.patch("strands.telemetry.tracer.trace_api.get_tracer", return_value=mock_tracer): + monkeypatch.setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "gen_ai_latest_experimental") tracer = Tracer() - tracer.use_latest_genai_conventions = True tracer.tracer = mock_tracer mock_span = mock.MagicMock() @@ -396,11 +396,11 @@ def test_start_swarm_span_with_contentblock_task(mock_tracer): assert span is not None -def test_start_swarm_span_with_contentblock_task_latest_conventions(mock_tracer): +def test_start_swarm_span_with_contentblock_task_latest_conventions(mock_tracer, monkeypatch): """Test starting a swarm call span with task as list of contentBlock with latest semantic conventions.""" with mock.patch("strands.telemetry.tracer.trace_api.get_tracer", return_value=mock_tracer): + monkeypatch.setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "gen_ai_latest_experimental") tracer = Tracer() - tracer.use_latest_genai_conventions = True tracer.tracer = mock_tracer mock_span = mock.MagicMock() @@ -439,10 +439,10 @@ def test_end_swarm_span(mock_span): ) -def test_end_swarm_span_latest_conventions(mock_span): +def test_end_swarm_span_latest_conventions(mock_span, monkeypatch): """Test ending a tool call span with latest semantic conventions.""" + monkeypatch.setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "gen_ai_latest_experimental") tracer = Tracer() - tracer.use_latest_genai_conventions = True swarm_final_reuslt = "foo bar bar" tracer.end_swarm_span(mock_span, swarm_final_reuslt) @@ -503,10 +503,10 @@ def test_end_tool_call_span(mock_span): mock_span.end.assert_called_once() -def test_end_tool_call_span_latest_conventions(mock_span): +def test_end_tool_call_span_latest_conventions(mock_span, monkeypatch): """Test ending a tool call span with the latest semantic conventions.""" + monkeypatch.setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "gen_ai_latest_experimental") tracer = Tracer() - tracer.use_latest_genai_conventions = True tool_result = {"status": "success", "content": [{"text": "Tool result"}, {"json": {"foo": "bar"}}]} tracer.end_tool_call_span(mock_span, tool_result) @@ -558,11 +558,11 @@ def test_start_event_loop_cycle_span(mock_tracer): assert span is not None -def test_start_event_loop_cycle_span_latest_conventions(mock_tracer): +def test_start_event_loop_cycle_span_latest_conventions(mock_tracer, monkeypatch): """Test starting an event loop cycle span with the latest semantic conventions.""" with mock.patch("strands.telemetry.tracer.trace_api.get_tracer", return_value=mock_tracer): + monkeypatch.setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "gen_ai_latest_experimental") tracer = Tracer() - tracer.use_latest_genai_conventions = True tracer.tracer = mock_tracer mock_span = mock.MagicMock() @@ -609,10 +609,10 @@ def test_end_event_loop_cycle_span(mock_span): mock_span.end.assert_called_once() -def test_end_event_loop_cycle_span_latest_conventions(mock_span): +def test_end_event_loop_cycle_span_latest_conventions(mock_span, monkeypatch): """Test ending an event loop cycle span with the latest semantic conventions.""" + monkeypatch.setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "gen_ai_latest_experimental") tracer = Tracer() - tracer.use_latest_genai_conventions = True message = {"role": "assistant", "content": [{"text": "Response"}]} tool_result_message = { "role": "assistant", @@ -679,11 +679,11 @@ def test_start_agent_span(mock_tracer): assert span is not None -def test_start_agent_span_latest_conventions(mock_tracer): +def test_start_agent_span_latest_conventions(mock_tracer, monkeypatch): """Test starting an agent span with the latest semantic conventions.""" with mock.patch("strands.telemetry.tracer.trace_api.get_tracer", return_value=mock_tracer): + monkeypatch.setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "gen_ai_latest_experimental") tracer = Tracer() - tracer.use_latest_genai_conventions = True tracer.tracer = mock_tracer mock_span = mock.MagicMock() @@ -749,10 +749,10 @@ def test_end_agent_span(mock_span): mock_span.end.assert_called_once() -def test_end_agent_span_latest_conventions(mock_span): +def test_end_agent_span_latest_conventions(mock_span, monkeypatch): """Test ending an agent span with the latest semantic conventions.""" + monkeypatch.setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "gen_ai_latest_experimental") tracer = Tracer() - tracer.use_latest_genai_conventions = True # Mock AgentResult with metrics mock_metrics = mock.MagicMock() @@ -1329,7 +1329,6 @@ def test_start_event_loop_cycle_span_with_tool_result_message(mock_tracer): def test_start_agent_span_does_not_include_tool_definitions_by_default(): """Verify that start_agent_span does not include tool definitions by default.""" tracer = Tracer() - tracer.include_tool_definitions = False tracer._start_span = mock.MagicMock() tools_config = { @@ -1349,10 +1348,10 @@ def test_start_agent_span_does_not_include_tool_definitions_by_default(): assert "gen_ai.tool.definitions" not in attributes -def test_start_agent_span_includes_tool_definitions_when_enabled(): +def test_start_agent_span_includes_tool_definitions_when_enabled(monkeypatch): """Verify that start_agent_span includes tool definitions when enabled.""" + monkeypatch.setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "gen_ai_tool_definitions") tracer = Tracer() - tracer.include_tool_definitions = True tracer._start_span = mock.MagicMock() tools_config = {