Skip to content

Commit 29f873b

Browse files
add support for tool call message in output scope
1 parent 302c3e2 commit 29f873b

4 files changed

Lines changed: 41 additions & 41 deletions

File tree

libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/execute_tool_scope.py

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
USER_NAME_KEY,
2424
)
2525
from .message_utils import serialize_messages
26-
from .models.messages import ToolInputMessages, ToolOutputMessages
26+
from .models.messages import ToolOutputMessages
2727
from .models.user_details import UserDetails
2828
from .opentelemetry_scope import OpenTelemetryScope
2929
from .request import Request
@@ -112,7 +112,7 @@ def __init__(
112112

113113
self.set_tag_maybe(GEN_AI_TOOL_NAME_KEY, tool_name)
114114
if arguments is not None:
115-
self.record_tool_input(arguments)
115+
self.set_tag_maybe(GEN_AI_TOOL_ARGS_KEY, serialize_messages(arguments))
116116
self.set_tag_maybe(GEN_AI_TOOL_TYPE_KEY, tool_type)
117117
self.set_tag_maybe(GEN_AI_TOOL_CALL_ID_KEY, tool_call_id)
118118
self.set_tag_maybe(GEN_AI_TOOL_DESCRIPTION_KEY, description)
@@ -138,15 +138,7 @@ def __init__(
138138
validate_and_normalize_ip(user_details.user_client_ip),
139139
)
140140

141-
def record_tool_input(self, messages: ToolInputMessages) -> None:
142-
"""Record the tool input for telemetry tracking.
143-
144-
Args:
145-
messages: A ToolInputMessages wrapper containing tool call requests
146-
"""
147-
self.set_tag_maybe(GEN_AI_TOOL_ARGS_KEY, serialize_messages(messages))
148-
149-
def record_tool_output(self, messages: ToolOutputMessages) -> None:
141+
def record_response(self, messages: ToolOutputMessages) -> None:
150142
"""Record the tool output for telemetry tracking.
151143
152144
Args:

libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/models/response.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,23 @@
44
from __future__ import annotations
55

66
from dataclasses import dataclass
7+
from typing import Union
78

8-
from .messages import OutputMessagesParam
9+
from .messages import OutputMessagesParam, ToolOutputMessages
10+
11+
ResponseMessagesParam = Union[OutputMessagesParam, ToolOutputMessages]
12+
"""Accepted type for Response.messages.
13+
14+
Supports plain strings, OutputMessages, or ToolOutputMessages.
15+
"""
916

1017

1118
@dataclass
1219
class Response:
1320
"""Response details from agent execution.
1421
15-
Accepts plain strings (backward compat) or structured OTEL OutputMessages.
22+
Accepts plain strings (backward compat), structured OTEL OutputMessages,
23+
or ToolOutputMessages.
1624
"""
1725

18-
messages: OutputMessagesParam
26+
messages: ResponseMessagesParam

libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/spans_scopes/output_scope.py

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
from ..models.messages import (
1717
OutputMessage,
1818
OutputMessages,
19-
OutputMessagesParam,
19+
ToolOutputMessages,
2020
)
21-
from ..models.response import Response
21+
from ..models.response import Response, ResponseMessagesParam
2222
from ..models.user_details import UserDetails
2323
from ..opentelemetry_scope import OpenTelemetryScope
2424
from ..request import Request
@@ -94,14 +94,19 @@ def __init__(
9494

9595
self.set_tag_maybe(GEN_AI_CONVERSATION_ID_KEY, request.conversation_id)
9696

97-
# Normalize response messages and extract inner messages for accumulation
98-
normalized = normalize_output_messages(response.messages)
99-
self._output_messages: list[OutputMessage] = list(normalized.messages)
100-
self._output_messages_dirty = False
101-
102-
# Set initial output messages attribute as the full versioned wrapper
103-
wrapper = OutputMessages(messages=self._output_messages)
104-
self.set_tag_maybe(GEN_AI_OUTPUT_MESSAGES_KEY, serialize_messages(wrapper))
97+
# Handle tool output messages vs regular output messages
98+
if isinstance(response.messages, ToolOutputMessages):
99+
# Tool output: serialize directly, no accumulation
100+
self._output_messages: list[OutputMessage] = []
101+
self._output_messages_dirty = False
102+
self.set_tag_maybe(GEN_AI_OUTPUT_MESSAGES_KEY, serialize_messages(response.messages))
103+
else:
104+
# Regular output: normalize and set up accumulation
105+
normalized = normalize_output_messages(response.messages)
106+
self._output_messages = list(normalized.messages)
107+
self._output_messages_dirty = False
108+
wrapper = OutputMessages(messages=self._output_messages)
109+
self.set_tag_maybe(GEN_AI_OUTPUT_MESSAGES_KEY, serialize_messages(wrapper))
105110

106111
# Set user details if provided
107112
if user_details:
@@ -113,18 +118,22 @@ def __init__(
113118
validate_and_normalize_ip(user_details.user_client_ip),
114119
)
115120

116-
def record_output_messages(self, messages: OutputMessagesParam) -> None:
121+
def record_output_messages(self, messages: ResponseMessagesParam) -> None:
117122
"""Records the output messages for telemetry tracking.
118123
119124
Appends the provided messages to the accumulated output messages list.
120-
Accepts plain strings (auto-wrapped as OTEL OutputMessage) or a versioned
121-
OutputMessages wrapper.
125+
Accepts plain strings (auto-wrapped as OTEL OutputMessage), a versioned
126+
OutputMessages wrapper, or a ToolOutputMessages wrapper.
122127
The list is capped at _MAX_OUTPUT_MESSAGES to prevent unbounded memory growth.
123128
The updated attribute is flushed when the scope is disposed.
124129
125130
Args:
126-
messages: List of output message strings or an OutputMessages wrapper to append
131+
messages: Plain strings, OutputMessages, or ToolOutputMessages to append
127132
"""
133+
if isinstance(messages, ToolOutputMessages):
134+
# Tool output: serialize directly, overwrite attribute
135+
self.set_tag_maybe(GEN_AI_OUTPUT_MESSAGES_KEY, serialize_messages(messages))
136+
return
128137
normalized = normalize_output_messages(messages)
129138
self._output_messages.extend(normalized.messages)
130139
if len(self._output_messages) > self._MAX_OUTPUT_MESSAGES:

tests/observability/core/test_execute_tool_scope.py

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -97,22 +97,13 @@ def tearDown(self):
9797

9898
self.span_exporter.clear()
9999

100-
def test_record_tool_input_method_exists(self):
101-
"""Test that record_tool_input method exists on ExecuteToolScope."""
100+
def test_record_response_method_exists(self):
101+
"""Test that record_response method exists on ExecuteToolScope."""
102102
scope = ExecuteToolScope.start(Request(), self.tool_details, self.agent_details)
103103

104104
if scope is not None:
105-
self.assertTrue(hasattr(scope, "record_tool_input"))
106-
self.assertTrue(callable(scope.record_tool_input))
107-
scope.dispose()
108-
109-
def test_record_tool_output_method_exists(self):
110-
"""Test that record_tool_output method exists on ExecuteToolScope."""
111-
scope = ExecuteToolScope.start(Request(), self.tool_details, self.agent_details)
112-
113-
if scope is not None:
114-
self.assertTrue(hasattr(scope, "record_tool_output"))
115-
self.assertTrue(callable(scope.record_tool_output))
105+
self.assertTrue(hasattr(scope, "record_response"))
106+
self.assertTrue(callable(scope.record_response))
116107
scope.dispose()
117108

118109
def test_request_metadata_set_on_span(self):

0 commit comments

Comments
 (0)