Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 12 additions & 16 deletions src/strands/telemetry/tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""
Expand All @@ -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.
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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)
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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(
Expand Down
45 changes: 22 additions & 23 deletions tests/strands/telemetry/test_tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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 = {
Expand All @@ -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 = {
Expand Down
Loading