Skip to content

Commit 0b6a2d2

Browse files
committed
feat: update toolcall types to match semconv
1 parent 1eca3e6 commit 0b6a2d2

2 files changed

Lines changed: 167 additions & 6 deletions

File tree

util/opentelemetry-util-genai/src/opentelemetry/util/genai/types.py

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,53 @@ class ContentCapturingMode(Enum):
4343

4444
@dataclass()
4545
class ToolCall:
46-
"""Represents a tool call requested by the model
47-
48-
This model is specified as part of semconv in `GenAI messages Python models - ToolCallRequestPart
49-
<https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/non-normative/models.ipynb>`__.
46+
"""Represents a tool call with dual usage: message part and execution tracking.
47+
48+
This type serves two purposes as defined in OpenTelemetry semantic conventions:
49+
50+
1. Message Part (ToolCallRequestPart):
51+
Represents a tool call requested by the model as part of a message.
52+
Reference: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/non-normative/models.ipynb
53+
54+
2. Tool Execution (execute_tool spans):
55+
Represents the actual execution of a tool call, tracked via spans and metrics.
56+
Reference: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/gen-ai-spans.md#execute-tool-span
57+
58+
The execution-only fields (tool_type, tool_description, tool_result, error_type)
59+
are used when tracking tool execution via spans, but are typically not present when
60+
this type is used as a message part in InputMessage or OutputMessage.
61+
62+
Semantic convention attributes for execute_tool spans:
63+
- gen_ai.operation.name: "execute_tool" (Required)
64+
- gen_ai.tool.name: Name of the tool (Recommended)
65+
- gen_ai.tool.call.id: Tool call identifier (Recommended if available)
66+
- gen_ai.tool.type: Type classification - "function", "extension", or "datastore" (Recommended if available)
67+
- gen_ai.tool.description: Tool description (Recommended if available)
68+
- gen_ai.tool.call.arguments: Parameters passed to tool (Opt-In, may contain sensitive data)
69+
- gen_ai.tool.call.result: Result returned by tool (Opt-In, may contain sensitive data)
70+
- error.type: Error type if operation failed (Conditionally Required)
5071
"""
5172

52-
arguments: Any
73+
# Fields used for both message part and execution tracking:
74+
# gen_ai.tool.name - Name of the tool
5375
name: str
54-
id: str | None
76+
# gen_ai.tool.call.arguments - Arguments passed to the tool (Opt-In, may contain sensitive data)
77+
arguments: Any = None
78+
# gen_ai.tool.call.id - Unique identifier for the tool call
79+
id: str | None = None
80+
# Message part type identifier
5581
type: Literal["tool_call"] = "tool_call"
5682

83+
# Execution-only fields (used for execute_tool spans, not typically in messages):
84+
# gen_ai.tool.type - Tool type: "function", "extension", or "datastore"
85+
tool_type: str | None = None
86+
# gen_ai.tool.description - Description of what the tool does
87+
tool_description: str | None = None
88+
# gen_ai.tool.call.result - Result returned by the tool (Opt-In, may contain sensitive data)
89+
tool_result: Any = None
90+
# error.type - Error type if the tool call failed
91+
error_type: str | None = None
92+
5793

5894
@dataclass()
5995
class ToolCallResponse:
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Tests for Enhanced ToolCall Type Definition"""
16+
17+
import pytest
18+
19+
from opentelemetry.util.genai.types import (
20+
InputMessage,
21+
OutputMessage,
22+
ToolCall,
23+
)
24+
25+
26+
class TestToolCallEnhancements:
27+
"""Test suite for Enhanced ToolCall Type Definition"""
28+
29+
def test_toolcall_backward_compatibility(self):
30+
"""Test backward compatibility as message part"""
31+
tc = ToolCall(
32+
name="get_weather",
33+
arguments={"location": "Paris"},
34+
id="call_123",
35+
)
36+
assert tc.name == "get_weather"
37+
assert tc.arguments == {"location": "Paris"}
38+
assert tc.id == "call_123"
39+
assert tc.type == "tool_call"
40+
41+
def test_toolcall_in_message(self):
42+
"""Test ToolCall works as message part in InputMessage"""
43+
tc = ToolCall(name="get_weather", arguments={"location": "Paris"})
44+
msg = InputMessage(role="user", parts=[tc])
45+
assert len(msg.parts) == 1
46+
assert msg.parts[0] == tc
47+
48+
def test_toolcall_full_lifecycle(self):
49+
"""Test complete tool call lifecycle with all fields"""
50+
# Start with tool call request
51+
tc = ToolCall(
52+
name="get_weather",
53+
arguments={"location": "Paris", "units": "metric"},
54+
id="call_abc123",
55+
tool_type="function",
56+
tool_description="Retrieves current weather for a location",
57+
)
58+
59+
# Simulate successful execution - set result
60+
tc.tool_result = {"temperature": 15, "condition": "cloudy"}
61+
62+
assert tc.name == "get_weather"
63+
assert tc.tool_type == "function"
64+
assert tc.tool_result is not None
65+
assert tc.error_type is None
66+
67+
# Simulate failed execution - set error
68+
tc_failed = ToolCall(
69+
name="get_weather",
70+
arguments={"location": "Invalid"},
71+
id="call_xyz789",
72+
tool_type="function",
73+
)
74+
tc_failed.error_type = "InvalidLocationError"
75+
76+
assert tc_failed.error_type == "InvalidLocationError"
77+
assert tc_failed.tool_result is None
78+
79+
def test_toolcall_with_output_message(self):
80+
"""Test ToolCall in OutputMessage (backward compatibility)"""
81+
tc = ToolCall(
82+
name="get_weather",
83+
arguments={"location": "Paris"},
84+
id="call_123",
85+
)
86+
msg = OutputMessage(
87+
role="assistant", parts=[tc], finish_reason="tool_calls"
88+
)
89+
90+
assert len(msg.parts) == 1
91+
assert msg.parts[0].name == "get_weather"
92+
assert msg.finish_reason == "tool_calls"
93+
94+
def test_toolcall_field_values(self):
95+
"""Test that ToolCall fields can be set and retrieved correctly"""
96+
tc = ToolCall(
97+
name="get_weather",
98+
id="call_123",
99+
tool_type="function",
100+
tool_description="Weather tool",
101+
arguments={"location": "Paris"},
102+
tool_result={"temp": 20},
103+
)
104+
105+
# Verify all field values are set correctly
106+
assert tc.name == "get_weather"
107+
assert tc.id == "call_123"
108+
assert tc.tool_type == "function"
109+
assert tc.tool_description == "Weather tool"
110+
assert tc.arguments == {"location": "Paris"}
111+
assert tc.tool_result == {"temp": 20}
112+
assert tc.error_type is None
113+
114+
# Verify these fields map to semantic convention attributes:
115+
# - name -> gen_ai.tool.name
116+
# - id -> gen_ai.tool.call.id
117+
# - tool_type -> gen_ai.tool.type
118+
# - tool_description -> gen_ai.tool.description
119+
# - arguments -> gen_ai.tool.call.arguments (Opt-In)
120+
# - tool_result -> gen_ai.tool.call.result (Opt-In)
121+
# - error_type -> error.type
122+
123+
124+
if __name__ == "__main__":
125+
pytest.main([__file__, "-v"])

0 commit comments

Comments
 (0)