Skip to content

Commit 7f41d79

Browse files
authored
chore: update spanKind and attributes for tokens (#296)
* chore: update spanKind * chore: added additional token semantic conventions
1 parent 94b99eb commit 7f41d79

File tree

2 files changed

+22
-11
lines changed

2 files changed

+22
-11
lines changed

src/strands/telemetry/tracer.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,6 @@ class Tracer:
7777
7878
When the OTEL_EXPORTER_OTLP_ENDPOINT environment variable is set, traces
7979
are sent to the OTLP endpoint.
80-
81-
When the STRANDS_OTEL_ENABLE_CONSOLE_EXPORT environment variable is set,
82-
traces are printed to the console.
8380
"""
8481

8582
def __init__(
@@ -103,13 +100,15 @@ def _start_span(
103100
span_name: str,
104101
parent_span: Optional[Span] = None,
105102
attributes: Optional[Dict[str, AttributeValue]] = None,
103+
span_kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
106104
) -> Optional[Span]:
107105
"""Generic helper method to start a span with common attributes.
108106
109107
Args:
110108
span_name: Name of the span to create
111109
parent_span: Optional parent span to link this span to
112110
attributes: Dictionary of attributes to set on the span
111+
span_kind: enum of OptenTelemetry SpanKind
113112
114113
Returns:
115114
The created span, or None if tracing is not enabled
@@ -118,7 +117,7 @@ def _start_span(
118117
return None
119118

120119
context = trace_api.set_span_in_context(parent_span) if parent_span else None
121-
span = self.tracer.start_span(name=span_name, context=context)
120+
span = self.tracer.start_span(name=span_name, context=context, kind=span_kind)
122121

123122
# Set start time as a common attribute
124123
span.set_attribute("gen_ai.event.start_time", datetime.now(timezone.utc).isoformat())
@@ -219,7 +218,7 @@ def start_model_invoke_span(
219218
"""
220219
attributes: Dict[str, AttributeValue] = {
221220
"gen_ai.system": "strands-agents",
222-
"agent.name": agent_name,
221+
"gen_ai.operation.name": "chat",
223222
"gen_ai.agent.name": agent_name,
224223
"gen_ai.prompt": serialize(messages),
225224
}
@@ -230,7 +229,7 @@ def start_model_invoke_span(
230229
# Add additional kwargs as attributes
231230
attributes.update({k: v for k, v in kwargs.items() if isinstance(v, (str, int, float, bool))})
232231

233-
return self._start_span("Model invoke", parent_span, attributes)
232+
return self._start_span("Model invoke", parent_span, attributes, span_kind=trace_api.SpanKind.CLIENT)
234233

235234
def end_model_invoke_span(
236235
self, span: Span, message: Message, usage: Usage, error: Optional[Exception] = None
@@ -246,7 +245,9 @@ def end_model_invoke_span(
246245
attributes: Dict[str, AttributeValue] = {
247246
"gen_ai.completion": serialize(message["content"]),
248247
"gen_ai.usage.prompt_tokens": usage["inputTokens"],
248+
"gen_ai.usage.input_tokens": usage["inputTokens"],
249249
"gen_ai.usage.completion_tokens": usage["outputTokens"],
250+
"gen_ai.usage.output_tokens": usage["outputTokens"],
250251
"gen_ai.usage.total_tokens": usage["totalTokens"],
251252
}
252253

@@ -265,6 +266,7 @@ def start_tool_call_span(self, tool: ToolUse, parent_span: Optional[Span] = None
265266
"""
266267
attributes: Dict[str, AttributeValue] = {
267268
"gen_ai.prompt": serialize(tool),
269+
"gen_ai.system": "strands-agents",
268270
"tool.name": tool["name"],
269271
"tool.id": tool["toolUseId"],
270272
"tool.parameters": serialize(tool["input"]),
@@ -274,7 +276,7 @@ def start_tool_call_span(self, tool: ToolUse, parent_span: Optional[Span] = None
274276
attributes.update(kwargs)
275277

276278
span_name = f"Tool: {tool['name']}"
277-
return self._start_span(span_name, parent_span, attributes)
279+
return self._start_span(span_name, parent_span, attributes, span_kind=trace_api.SpanKind.INTERNAL)
278280

279281
def end_tool_call_span(
280282
self, span: Span, tool_result: Optional[ToolResult], error: Optional[Exception] = None
@@ -335,7 +337,7 @@ def start_event_loop_cycle_span(
335337
attributes.update({k: v for k, v in kwargs.items() if isinstance(v, (str, int, float, bool))})
336338

337339
span_name = f"Cycle {event_loop_cycle_id}"
338-
return self._start_span(span_name, parent_span, attributes)
340+
return self._start_span(span_name, parent_span, attributes, span_kind=trace_api.SpanKind.INTERNAL)
339341

340342
def end_event_loop_cycle_span(
341343
self,
@@ -405,7 +407,7 @@ def start_agent_span(
405407
# Add additional kwargs as attributes
406408
attributes.update({k: v for k, v in kwargs.items() if isinstance(v, (str, int, float, bool))})
407409

408-
return self._start_span(agent_name, attributes=attributes)
410+
return self._start_span(agent_name, attributes=attributes, span_kind=trace_api.SpanKind.CLIENT)
409411

410412
def end_agent_span(
411413
self,
@@ -436,6 +438,8 @@ def end_agent_span(
436438
{
437439
"gen_ai.usage.prompt_tokens": accumulated_usage["inputTokens"],
438440
"gen_ai.usage.completion_tokens": accumulated_usage["outputTokens"],
441+
"gen_ai.usage.input_tokens": accumulated_usage["inputTokens"],
442+
"gen_ai.usage.output_tokens": accumulated_usage["outputTokens"],
439443
"gen_ai.usage.total_tokens": accumulated_usage["totalTokens"],
440444
}
441445
)

tests/strands/telemetry/test_tracer.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import pytest
77
from opentelemetry.trace import (
8+
SpanKind,
89
StatusCode, # type: ignore
910
)
1011

@@ -75,7 +76,7 @@ def test_start_span(mock_tracer):
7576

7677
span = tracer._start_span("test_span", attributes={"key": "value"})
7778

78-
mock_tracer.start_span.assert_called_once_with(name="test_span", context=None)
79+
mock_tracer.start_span.assert_called_once_with(name="test_span", context=None, kind=SpanKind.INTERNAL)
7980
mock_span.set_attribute.assert_any_call("key", "value")
8081
assert span is not None
8182

@@ -151,8 +152,10 @@ def test_start_model_invoke_span(mock_tracer):
151152

152153
mock_tracer.start_span.assert_called_once()
153154
assert mock_tracer.start_span.call_args[1]["name"] == "Model invoke"
155+
assert mock_tracer.start_span.call_args[1]["kind"] == SpanKind.CLIENT
154156
mock_span.set_attribute.assert_any_call("gen_ai.system", "strands-agents")
155-
mock_span.set_attribute.assert_any_call("agent.name", "TestAgent")
157+
mock_span.set_attribute.assert_any_call("gen_ai.operation.name", "chat")
158+
mock_span.set_attribute.assert_any_call("gen_ai.agent.name", "TestAgent")
156159
mock_span.set_attribute.assert_any_call("gen_ai.request.model", model_id)
157160
assert span is not None
158161

@@ -167,7 +170,9 @@ def test_end_model_invoke_span(mock_span):
167170

168171
mock_span.set_attribute.assert_any_call("gen_ai.completion", json.dumps(message["content"]))
169172
mock_span.set_attribute.assert_any_call("gen_ai.usage.prompt_tokens", 10)
173+
mock_span.set_attribute.assert_any_call("gen_ai.usage.input_tokens", 10)
170174
mock_span.set_attribute.assert_any_call("gen_ai.usage.completion_tokens", 20)
175+
mock_span.set_attribute.assert_any_call("gen_ai.usage.output_tokens", 20)
171176
mock_span.set_attribute.assert_any_call("gen_ai.usage.total_tokens", 30)
172177
mock_span.set_status.assert_called_once_with(StatusCode.OK)
173178
mock_span.end.assert_called_once()
@@ -294,7 +299,9 @@ def test_end_agent_span(mock_span):
294299

295300
mock_span.set_attribute.assert_any_call("gen_ai.completion", "Agent response")
296301
mock_span.set_attribute.assert_any_call("gen_ai.usage.prompt_tokens", 50)
302+
mock_span.set_attribute.assert_any_call("gen_ai.usage.input_tokens", 50)
297303
mock_span.set_attribute.assert_any_call("gen_ai.usage.completion_tokens", 100)
304+
mock_span.set_attribute.assert_any_call("gen_ai.usage.output_tokens", 100)
298305
mock_span.set_attribute.assert_any_call("gen_ai.usage.total_tokens", 150)
299306
mock_span.set_status.assert_called_once_with(StatusCode.OK)
300307
mock_span.end.assert_called_once()

0 commit comments

Comments
 (0)