Skip to content

Commit 22fa807

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

File tree

2 files changed

+65
-16
lines changed

2 files changed

+65
-16
lines changed

src/strands/telemetry/tracer.py

Lines changed: 40 additions & 5 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
)
@@ -727,7 +727,7 @@ def _add_event_messages(self, span: Span, messages: Messages) -> None:
727727
input_messages: list = []
728728
for message in messages:
729729
input_messages.append(
730-
{"role": message["role"], "parts": [{"type": "text", "content": message["content"]}]}
730+
{"role": message["role"], "parts": self._map_content_blocks_to_otel_parts(message["content"])}
731731
)
732732
self._add_event(
733733
span, "gen_ai.client.inference.operation.details", {"gen_ai.input.messages": serialize(input_messages)}
@@ -740,6 +740,41 @@ def _add_event_messages(self, span: Span, messages: Messages) -> None:
740740
{"content": serialize(message["content"])},
741741
)
742742

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

744779
# Singleton instance for global access
745780
_tracer_instance = None

tests/strands/telemetry/test_tracer.py

Lines changed: 25 additions & 11 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
}
@@ -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)