Skip to content

Commit 6204164

Browse files
committed
chore(trace): updated semantic conventions with tool mappings
1 parent ad87f9e commit 6204164

File tree

2 files changed

+73
-20
lines changed

2 files changed

+73
-20
lines changed

src/strands/telemetry/tracer.py

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ def end_model_invoke_span(
316316
[
317317
{
318318
"role": message["role"],
319-
"parts": [{"type": "text", "content": message["content"]}],
319+
"parts": self._map_content_blocks_to_otel_parts(message["content"]),
320320
"finish_reason": str(stop_reason),
321321
}
322322
]
@@ -371,7 +371,7 @@ def start_tool_call_span(self, tool: ToolUse, parent_span: Optional[Span] = None
371371
"type": "tool_call",
372372
"name": tool["name"],
373373
"id": tool["toolUseId"],
374-
"arguments": [{"content": tool["input"]}],
374+
"arguments": tool["input"],
375375
}
376376
],
377377
}
@@ -426,7 +426,7 @@ def end_tool_call_span(
426426
{
427427
"type": "tool_call_response",
428428
"id": tool_result.get("toolUseId", ""),
429-
"result": tool_result.get("content"),
429+
"response": tool_result.get("content"),
430430
}
431431
],
432432
}
@@ -513,7 +513,7 @@ def end_event_loop_cycle_span(
513513
[
514514
{
515515
"role": tool_result_message["role"],
516-
"parts": [{"type": "text", "content": tool_result_message["content"]}],
516+
"parts": self._map_content_blocks_to_otel_parts(tool_result_message["content"]),
517517
}
518518
]
519519
)
@@ -643,19 +643,23 @@ def start_multiagent_span(
643643
)
644644

645645
span = self._start_span(operation, attributes=attributes, span_kind=trace_api.SpanKind.CLIENT)
646-
content = serialize(task) if isinstance(task, list) else task
647646

648647
if self.use_latest_genai_conventions:
648+
parts: list[dict[str, Any]] = []
649+
if isinstance(task, list):
650+
parts = self._map_content_blocks_to_otel_parts(task)
651+
else:
652+
parts = [{"type": "text", "content": task}]
649653
self._add_event(
650654
span,
651655
"gen_ai.client.inference.operation.details",
652-
{"gen_ai.input.messages": serialize([{"role": "user", "parts": [{"type": "text", "content": task}]}])},
656+
{"gen_ai.input.messages": serialize([{"role": "user", "parts": parts}])},
653657
)
654658
else:
655659
self._add_event(
656660
span,
657661
"gen_ai.user.message",
658-
event_attributes={"content": content},
662+
event_attributes={"content": serialize(task) if isinstance(task, list) else task},
659663
)
660664

661665
return span
@@ -727,7 +731,7 @@ def _add_event_messages(self, span: Span, messages: Messages) -> None:
727731
input_messages: list = []
728732
for message in messages:
729733
input_messages.append(
730-
{"role": message["role"], "parts": [{"type": "text", "content": message["content"]}]}
734+
{"role": message["role"], "parts": self._map_content_blocks_to_otel_parts(message["content"])}
731735
)
732736
self._add_event(
733737
span, "gen_ai.client.inference.operation.details", {"gen_ai.input.messages": serialize(input_messages)}
@@ -740,6 +744,41 @@ def _add_event_messages(self, span: Span, messages: Messages) -> None:
740744
{"content": serialize(message["content"])},
741745
)
742746

747+
def _map_content_blocks_to_otel_parts(self, content_blocks: list[ContentBlock]) -> list[dict[str, Any]]:
748+
"""Map ContentBlock objects to OpenTelemetry parts format."""
749+
parts: list[dict[str, Any]] = []
750+
751+
for block in content_blocks:
752+
if "text" in block:
753+
# Standard TextPart
754+
parts.append({"type": "text", "content": block["text"]})
755+
elif "toolUse" in block:
756+
# Standard ToolCallRequestPart
757+
tool_use = block["toolUse"]
758+
parts.append(
759+
{
760+
"type": "tool_call",
761+
"name": tool_use["name"],
762+
"id": tool_use["toolUseId"],
763+
"arguments": tool_use["input"],
764+
}
765+
)
766+
elif "toolResult" in block:
767+
# Standard ToolCallResponsePart
768+
tool_result = block["toolResult"]
769+
parts.append(
770+
{
771+
"type": "tool_call_response",
772+
"id": tool_result["toolUseId"],
773+
"response": tool_result["content"],
774+
}
775+
)
776+
else:
777+
# For all other ContentBlock types, use the key as type and value as content
778+
for key, value in block.items():
779+
parts.append({"type": key, "content": value})
780+
return parts
781+
743782

744783
# Singleton instance for global access
745784
_tracer_instance = None

tests/strands/telemetry/test_tracer.py

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ def test_start_model_invoke_span_latest_conventions(mock_tracer):
191191
[
192192
{
193193
"role": messages[0]["role"],
194-
"parts": [{"type": "text", "content": messages[0]["content"]}],
194+
"parts": [{"type": "text", "content": "Hello"}],
195195
}
196196
]
197197
)
@@ -255,7 +255,7 @@ def test_end_model_invoke_span_latest_conventions(mock_span):
255255
[
256256
{
257257
"role": "assistant",
258-
"parts": [{"type": "text", "content": message["content"]}],
258+
"parts": [{"type": "text", "content": "Response"}],
259259
"finish_reason": "end_turn",
260260
}
261261
]
@@ -324,7 +324,7 @@ def test_start_tool_call_span_latest_conventions(mock_tracer):
324324
"type": "tool_call",
325325
"name": tool["name"],
326326
"id": tool["toolUseId"],
327-
"arguments": [{"content": tool["input"]}],
327+
"arguments": tool["input"],
328328
}
329329
],
330330
}
@@ -404,7 +404,7 @@ def test_start_swarm_span_with_contentblock_task_latest_conventions(mock_tracer)
404404
"gen_ai.client.inference.operation.details",
405405
attributes={
406406
"gen_ai.input.messages": serialize(
407-
[{"role": "user", "parts": [{"type": "text", "content": [{"text": "Original Task: foo bar"}]}]}]
407+
[{"role": "user", "parts": [{"type": "text", "content": "Original Task: foo bar"}]}]
408408
)
409409
},
410410
)
@@ -508,7 +508,7 @@ def test_end_tool_call_span_latest_conventions(mock_span):
508508
{
509509
"type": "tool_call_response",
510510
"id": tool_result.get("toolUseId", ""),
511-
"result": tool_result.get("content"),
511+
"response": tool_result.get("content"),
512512
}
513513
],
514514
}
@@ -564,9 +564,7 @@ def test_start_event_loop_cycle_span_latest_conventions(mock_tracer):
564564
mock_span.add_event.assert_any_call(
565565
"gen_ai.client.inference.operation.details",
566566
attributes={
567-
"gen_ai.input.messages": serialize(
568-
[{"role": "user", "parts": [{"type": "text", "content": messages[0]["content"]}]}]
569-
)
567+
"gen_ai.input.messages": serialize([{"role": "user", "parts": [{"type": "text", "content": "Hello"}]}])
570568
},
571569
)
572570
assert span is not None
@@ -576,7 +574,12 @@ def test_end_event_loop_cycle_span(mock_span):
576574
"""Test ending an event loop cycle span."""
577575
tracer = Tracer()
578576
message = {"role": "assistant", "content": [{"text": "Response"}]}
579-
tool_result_message = {"role": "assistant", "content": [{"toolResult": {"response": "Success"}}]}
577+
tool_result_message = {
578+
"role": "assistant",
579+
"content": [
580+
{"toolResult": {"toolUseId": "123", "status": "success", "content": [{"text": "Weather is sunny"}]}}
581+
],
582+
}
580583

581584
tracer.end_event_loop_cycle_span(mock_span, message, tool_result_message)
582585

@@ -596,7 +599,12 @@ def test_end_event_loop_cycle_span_latest_conventions(mock_span):
596599
tracer = Tracer()
597600
tracer.use_latest_genai_conventions = True
598601
message = {"role": "assistant", "content": [{"text": "Response"}]}
599-
tool_result_message = {"role": "assistant", "content": [{"toolResult": {"response": "Success"}}]}
602+
tool_result_message = {
603+
"role": "assistant",
604+
"content": [
605+
{"toolResult": {"toolUseId": "123", "status": "success", "content": [{"text": "Weather is sunny"}]}}
606+
],
607+
}
600608

601609
tracer.end_event_loop_cycle_span(mock_span, message, tool_result_message)
602610

@@ -607,7 +615,13 @@ def test_end_event_loop_cycle_span_latest_conventions(mock_span):
607615
[
608616
{
609617
"role": "assistant",
610-
"parts": [{"type": "text", "content": tool_result_message["content"]}],
618+
"parts": [
619+
{
620+
"type": "tool_call_response",
621+
"id": "123",
622+
"response": [{"text": "Weather is sunny"}],
623+
}
624+
],
611625
}
612626
]
613627
)
@@ -682,7 +696,7 @@ def test_start_agent_span_latest_conventions(mock_tracer):
682696
"gen_ai.client.inference.operation.details",
683697
attributes={
684698
"gen_ai.input.messages": serialize(
685-
[{"role": "user", "parts": [{"type": "text", "content": [{"text": "test prompt"}]}]}]
699+
[{"role": "user", "parts": [{"type": "text", "content": "test prompt"}]}]
686700
)
687701
},
688702
)

0 commit comments

Comments
 (0)