Skip to content

Commit f20a405

Browse files
authored
chore: updated semantic conventions on Generative AI spans (#319)
1 parent 49461e5 commit f20a405

File tree

3 files changed

+134
-58
lines changed

3 files changed

+134
-58
lines changed

src/strands/event_loop/event_loop.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def event_loop_cycle(
102102
# Create tracer span for this event loop cycle
103103
tracer = get_tracer()
104104
cycle_span = tracer.start_event_loop_cycle_span(
105-
event_loop_kwargs=kwargs, parent_span=event_loop_parent_span, messages=messages
105+
event_loop_kwargs=kwargs, messages=messages, parent_span=event_loop_parent_span
106106
)
107107
kwargs["event_loop_cycle_span"] = cycle_span
108108

@@ -124,8 +124,8 @@ def event_loop_cycle(
124124
for attempt in range(MAX_ATTEMPTS):
125125
model_id = model.config.get("model_id") if hasattr(model, "config") else None
126126
model_invoke_span = tracer.start_model_invoke_span(
127-
parent_span=cycle_span,
128127
messages=messages,
128+
parent_span=cycle_span,
129129
model_id=model_id,
130130
)
131131

@@ -140,7 +140,7 @@ def event_loop_cycle(
140140
kwargs.setdefault("request_state", {})
141141

142142
if model_invoke_span:
143-
tracer.end_model_invoke_span(model_invoke_span, message, usage)
143+
tracer.end_model_invoke_span(model_invoke_span, message, usage, stop_reason)
144144
break # Success! Break out of retry loop
145145

146146
except ContextWindowOverflowException as e:

src/strands/telemetry/tracer.py

Lines changed: 89 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from ..agent.agent_result import AgentResult
1616
from ..types.content import Message, Messages
17-
from ..types.streaming import Usage
17+
from ..types.streaming import StopReason, Usage
1818
from ..types.tools import ToolResult, ToolUse
1919
from ..types.traces import AttributeValue
2020

@@ -196,20 +196,31 @@ def end_span_with_error(self, span: Span, error_message: str, exception: Optiona
196196
error = exception or Exception(error_message)
197197
self._end_span(span, error=error)
198198

199+
def _add_event(self, span: Optional[Span], event_name: str, event_attributes: Dict[str, AttributeValue]) -> None:
200+
"""Add an event with attributes to a span.
201+
202+
Args:
203+
span: The span to add the event to
204+
event_name: Name of the event
205+
event_attributes: Dictionary of attributes to set on the event
206+
"""
207+
if not span:
208+
return
209+
210+
span.add_event(event_name, attributes=event_attributes)
211+
199212
def start_model_invoke_span(
200213
self,
214+
messages: Messages,
201215
parent_span: Optional[Span] = None,
202-
agent_name: str = "Strands Agent",
203-
messages: Optional[Messages] = None,
204216
model_id: Optional[str] = None,
205217
**kwargs: Any,
206218
) -> Optional[Span]:
207219
"""Start a new span for a model invocation.
208220
209221
Args:
222+
messages: Messages being sent to the model.
210223
parent_span: Optional parent span to link this span to.
211-
agent_name: Name of the agent making the model call.
212-
messages: Optional messages being sent to the model.
213224
model_id: Optional identifier for the model being invoked.
214225
**kwargs: Additional attributes to add to the span.
215226
@@ -219,8 +230,6 @@ def start_model_invoke_span(
219230
attributes: Dict[str, AttributeValue] = {
220231
"gen_ai.system": "strands-agents",
221232
"gen_ai.operation.name": "chat",
222-
"gen_ai.agent.name": agent_name,
223-
"gen_ai.prompt": serialize(messages),
224233
}
225234

226235
if model_id:
@@ -229,28 +238,41 @@ def start_model_invoke_span(
229238
# Add additional kwargs as attributes
230239
attributes.update({k: v for k, v in kwargs.items() if isinstance(v, (str, int, float, bool))})
231240

232-
return self._start_span("Model invoke", parent_span, attributes, span_kind=trace_api.SpanKind.CLIENT)
241+
span = self._start_span("Model invoke", parent_span, attributes, span_kind=trace_api.SpanKind.CLIENT)
242+
for message in messages:
243+
self._add_event(
244+
span,
245+
f"gen_ai.{message['role']}.message",
246+
{"content": serialize(message["content"])},
247+
)
248+
return span
233249

234250
def end_model_invoke_span(
235-
self, span: Span, message: Message, usage: Usage, error: Optional[Exception] = None
251+
self, span: Span, message: Message, usage: Usage, stop_reason: StopReason, error: Optional[Exception] = None
236252
) -> None:
237253
"""End a model invocation span with results and metrics.
238254
239255
Args:
240256
span: The span to end.
241257
message: The message response from the model.
242258
usage: Token usage information from the model call.
259+
stop_reason (StopReason): The reason the model stopped generating.
243260
error: Optional exception if the model call failed.
244261
"""
245262
attributes: Dict[str, AttributeValue] = {
246-
"gen_ai.completion": serialize(message["content"]),
247263
"gen_ai.usage.prompt_tokens": usage["inputTokens"],
248264
"gen_ai.usage.input_tokens": usage["inputTokens"],
249265
"gen_ai.usage.completion_tokens": usage["outputTokens"],
250266
"gen_ai.usage.output_tokens": usage["outputTokens"],
251267
"gen_ai.usage.total_tokens": usage["totalTokens"],
252268
}
253269

270+
self._add_event(
271+
span,
272+
"gen_ai.choice",
273+
event_attributes={"finish_reason": str(stop_reason), "message": serialize(message["content"])},
274+
)
275+
254276
self._end_span(span, attributes, error)
255277

256278
def start_tool_call_span(self, tool: ToolUse, parent_span: Optional[Span] = None, **kwargs: Any) -> Optional[Span]:
@@ -265,18 +287,29 @@ def start_tool_call_span(self, tool: ToolUse, parent_span: Optional[Span] = None
265287
The created span, or None if tracing is not enabled.
266288
"""
267289
attributes: Dict[str, AttributeValue] = {
268-
"gen_ai.prompt": serialize(tool),
290+
"gen_ai.operation.name": "execute_tool",
269291
"gen_ai.system": "strands-agents",
270-
"tool.name": tool["name"],
271-
"tool.id": tool["toolUseId"],
272-
"tool.parameters": serialize(tool["input"]),
292+
"gen_ai.tool.name": tool["name"],
293+
"gen_ai.tool.call.id": tool["toolUseId"],
273294
}
274295

275296
# Add additional kwargs as attributes
276297
attributes.update(kwargs)
277298

278299
span_name = f"Tool: {tool['name']}"
279-
return self._start_span(span_name, parent_span, attributes, span_kind=trace_api.SpanKind.INTERNAL)
300+
span = self._start_span(span_name, parent_span, attributes, span_kind=trace_api.SpanKind.INTERNAL)
301+
302+
self._add_event(
303+
span,
304+
"gen_ai.tool.message",
305+
event_attributes={
306+
"role": "tool",
307+
"content": serialize(tool["input"]),
308+
"id": tool["toolUseId"],
309+
},
310+
)
311+
312+
return span
280313

281314
def end_tool_call_span(
282315
self, span: Span, tool_result: Optional[ToolResult], error: Optional[Exception] = None
@@ -293,30 +326,36 @@ def end_tool_call_span(
293326
status = tool_result.get("status")
294327
status_str = str(status) if status is not None else ""
295328

296-
tool_result_content_json = serialize(tool_result.get("content"))
297329
attributes.update(
298330
{
299-
"tool.result": tool_result_content_json,
300-
"gen_ai.completion": tool_result_content_json,
301331
"tool.status": status_str,
302332
}
303333
)
304334

335+
self._add_event(
336+
span,
337+
"gen_ai.choice",
338+
event_attributes={
339+
"message": serialize(tool_result.get("content")),
340+
"id": tool_result.get("toolUseId", ""),
341+
},
342+
)
343+
305344
self._end_span(span, attributes, error)
306345

307346
def start_event_loop_cycle_span(
308347
self,
309348
event_loop_kwargs: Any,
349+
messages: Messages,
310350
parent_span: Optional[Span] = None,
311-
messages: Optional[Messages] = None,
312351
**kwargs: Any,
313352
) -> Optional[Span]:
314353
"""Start a new span for an event loop cycle.
315354
316355
Args:
317356
event_loop_kwargs: Arguments for the event loop cycle.
318357
parent_span: Optional parent span to link this span to.
319-
messages: Optional messages being processed in this cycle.
358+
messages: Messages being processed in this cycle.
320359
**kwargs: Additional attributes to add to the span.
321360
322361
Returns:
@@ -326,7 +365,6 @@ def start_event_loop_cycle_span(
326365
parent_span = parent_span if parent_span else event_loop_kwargs.get("event_loop_parent_span")
327366

328367
attributes: Dict[str, AttributeValue] = {
329-
"gen_ai.prompt": serialize(messages),
330368
"event_loop.cycle_id": event_loop_cycle_id,
331369
}
332370

@@ -337,7 +375,15 @@ def start_event_loop_cycle_span(
337375
attributes.update({k: v for k, v in kwargs.items() if isinstance(v, (str, int, float, bool))})
338376

339377
span_name = f"Cycle {event_loop_cycle_id}"
340-
return self._start_span(span_name, parent_span, attributes, span_kind=trace_api.SpanKind.INTERNAL)
378+
span = self._start_span(span_name, parent_span, attributes)
379+
for message in messages or []:
380+
self._add_event(
381+
span,
382+
f"gen_ai.{message['role']}.message",
383+
{"content": serialize(message["content"])},
384+
)
385+
386+
return span
341387

342388
def end_event_loop_cycle_span(
343389
self,
@@ -354,13 +400,12 @@ def end_event_loop_cycle_span(
354400
tool_result_message: Optional tool result message if a tool was called.
355401
error: Optional exception if the cycle failed.
356402
"""
357-
attributes: Dict[str, AttributeValue] = {
358-
"gen_ai.completion": serialize(message["content"]),
359-
}
403+
attributes: Dict[str, AttributeValue] = {}
404+
event_attributes: Dict[str, AttributeValue] = {"message": serialize(message["content"])}
360405

361406
if tool_result_message:
362-
attributes["tool.result"] = serialize(tool_result_message["content"])
363-
407+
event_attributes["tool.result"] = serialize(tool_result_message["content"])
408+
self._add_event(span, "gen_ai.choice", event_attributes=event_attributes)
364409
self._end_span(span, attributes, error)
365410

366411
def start_agent_span(
@@ -387,17 +432,15 @@ def start_agent_span(
387432
"""
388433
attributes: Dict[str, AttributeValue] = {
389434
"gen_ai.system": "strands-agents",
390-
"agent.name": agent_name,
391435
"gen_ai.agent.name": agent_name,
392-
"gen_ai.prompt": prompt,
436+
"gen_ai.operation.name": "invoke_agent",
393437
}
394438

395439
if model_id:
396440
attributes["gen_ai.request.model"] = model_id
397441

398442
if tools:
399443
tools_json = serialize(tools)
400-
attributes["agent.tools"] = tools_json
401444
attributes["gen_ai.agent.tools"] = tools_json
402445

403446
# Add custom trace attributes if provided
@@ -407,7 +450,18 @@ def start_agent_span(
407450
# Add additional kwargs as attributes
408451
attributes.update({k: v for k, v in kwargs.items() if isinstance(v, (str, int, float, bool))})
409452

410-
return self._start_span(agent_name, attributes=attributes, span_kind=trace_api.SpanKind.CLIENT)
453+
span = self._start_span(
454+
f"invoke_agent {agent_name}", attributes=attributes, span_kind=trace_api.SpanKind.CLIENT
455+
)
456+
self._add_event(
457+
span,
458+
"gen_ai.user.message",
459+
event_attributes={
460+
"content": prompt,
461+
},
462+
)
463+
464+
return span
411465

412466
def end_agent_span(
413467
self,
@@ -426,10 +480,10 @@ def end_agent_span(
426480
attributes: Dict[str, AttributeValue] = {}
427481

428482
if response:
429-
attributes.update(
430-
{
431-
"gen_ai.completion": str(response),
432-
}
483+
self._add_event(
484+
span,
485+
"gen_ai.choice",
486+
event_attributes={"message": str(response), "finish_reason": str(response.stop_reason)},
433487
)
434488

435489
if hasattr(response, "metrics") and hasattr(response.metrics, "accumulated_usage"):

0 commit comments

Comments
 (0)