Skip to content

Commit 3d097c4

Browse files
[O11y] Adds channel data to inference and tool scopes. (#89)
* Adds channel data to inference and tool scopes. * Fixes formatting. * Fixes for tests. * Correctly sets OTel trace provider.
1 parent ba82f54 commit 3d097c4

File tree

5 files changed

+264
-63
lines changed

5 files changed

+264
-63
lines changed

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from .constants import (
66
EXECUTE_TOOL_OPERATION_NAME,
77
GEN_AI_EVENT_CONTENT,
8+
GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY,
9+
GEN_AI_EXECUTION_SOURCE_NAME_KEY,
810
GEN_AI_TOOL_ARGS_KEY,
911
GEN_AI_TOOL_CALL_ID_KEY,
1012
GEN_AI_TOOL_DESCRIPTION_KEY,
@@ -14,6 +16,7 @@
1416
SERVER_PORT_KEY,
1517
)
1618
from .opentelemetry_scope import OpenTelemetryScope
19+
from .request import Request
1720
from .tenant_details import TenantDetails
1821
from .tool_call_details import ToolCallDetails
1922

@@ -26,31 +29,35 @@ def start(
2629
details: ToolCallDetails,
2730
agent_details: AgentDetails,
2831
tenant_details: TenantDetails,
32+
request: Request | None = None,
2933
) -> "ExecuteToolScope":
3034
"""Creates and starts a new scope for tool execution tracing.
3135
3236
Args:
3337
details: The details of the tool call
3438
agent_details: The details of the agent making the call
3539
tenant_details: The details of the tenant
40+
request: Optional request details for additional context
3641
3742
Returns:
3843
A new ExecuteToolScope instance
3944
"""
40-
return ExecuteToolScope(details, agent_details, tenant_details)
45+
return ExecuteToolScope(details, agent_details, tenant_details, request)
4146

4247
def __init__(
4348
self,
4449
details: ToolCallDetails,
4550
agent_details: AgentDetails,
4651
tenant_details: TenantDetails,
52+
request: Request | None = None,
4753
):
4854
"""Initialize the tool execution scope.
4955
5056
Args:
5157
details: The details of the tool call
5258
agent_details: The details of the agent making the call
5359
tenant_details: The details of the tenant
60+
request: Optional request details for additional context
5461
"""
5562
super().__init__(
5663
kind="Internal",
@@ -79,6 +86,13 @@ def __init__(
7986
if endpoint.port and endpoint.port != 443:
8087
self.set_tag_maybe(SERVER_PORT_KEY, endpoint.port)
8188

89+
# Set request metadata if provided
90+
if request and request.source_metadata:
91+
self.set_tag_maybe(GEN_AI_EXECUTION_SOURCE_NAME_KEY, request.source_metadata.name)
92+
self.set_tag_maybe(
93+
GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, request.source_metadata.description
94+
)
95+
8296
def record_response(self, response: str) -> None:
8397
"""Records response information for telemetry tracking.
8498

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
from .agent_details import AgentDetails
77
from .constants import (
8+
GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY,
9+
GEN_AI_EXECUTION_SOURCE_NAME_KEY,
810
GEN_AI_INPUT_MESSAGES_KEY,
911
GEN_AI_OPERATION_NAME_KEY,
1012
GEN_AI_OUTPUT_MESSAGES_KEY,
@@ -90,6 +92,13 @@ def __init__(
9092
)
9193
self.set_tag_maybe(GEN_AI_RESPONSE_ID_KEY, details.responseId)
9294

95+
# Set request metadata if provided
96+
if request and request.source_metadata:
97+
self.set_tag_maybe(GEN_AI_EXECUTION_SOURCE_NAME_KEY, request.source_metadata.name)
98+
self.set_tag_maybe(
99+
GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, request.source_metadata.description
100+
)
101+
93102
def record_input_messages(self, messages: List[str]) -> None:
94103
"""Records the input messages for telemetry tracking.
95104

tests/observability/core/test_execute_tool_scope.py

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,29 @@
11
# Copyright (c) Microsoft Corporation.
22
# Licensed under the MIT License.
33

4+
import os
5+
from pathlib import Path
6+
import sys
47
import unittest
8+
import pytest
59

610
from microsoft_agents_a365.observability.core import (
711
AgentDetails,
12+
ExecutionType,
813
ExecuteToolScope,
14+
Request,
15+
SourceMetadata,
916
TenantDetails,
1017
ToolCallDetails,
1118
configure,
19+
get_tracer_provider,
1220
)
21+
from microsoft_agents_a365.observability.core.constants import (
22+
GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY,
23+
GEN_AI_EXECUTION_SOURCE_NAME_KEY,
24+
)
25+
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
26+
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
1327

1428

1529
class TestExecuteToolScope(unittest.TestCase):
@@ -19,6 +33,8 @@ class TestExecuteToolScope(unittest.TestCase):
1933
def setUpClass(cls):
2034
"""Set up test environment once for all tests."""
2135
# Configure Microsoft Agent 365 for testing
36+
os.environ["ENABLE_A365_OBSERVABILITY"] = "true"
37+
2238
configure(
2339
service_name="test-execute-tool-service",
2440
service_namespace="test-namespace",
@@ -37,6 +53,19 @@ def setUpClass(cls):
3753
description="Get current weather information for a location",
3854
)
3955

56+
def setUp(self):
57+
super().setUp()
58+
59+
# Set up tracer to capture spans
60+
self.span_exporter = InMemorySpanExporter()
61+
tracer_provider = get_tracer_provider()
62+
tracer_provider.add_span_processor(SimpleSpanProcessor(self.span_exporter))
63+
64+
def tearDown(self):
65+
super().tearDown()
66+
67+
self.span_exporter.clear()
68+
4069
def test_record_response_method_exists(self):
4170
"""Test that record_response method exists on ExecuteToolScope."""
4271
scope = ExecuteToolScope.start(self.tool_details, self.agent_details, self.tenant_details)
@@ -47,6 +76,49 @@ def test_record_response_method_exists(self):
4776
self.assertTrue(callable(scope.record_response))
4877
scope.dispose()
4978

79+
def test_request_metadata_set_on_span(self):
80+
"""Test that request source metadata is set on span attributes."""
81+
request = Request(
82+
content="Execute tool with request metadata",
83+
execution_type=ExecutionType.AGENT_TO_AGENT,
84+
session_id="session-xyz",
85+
source_metadata=SourceMetadata(name="Channel 1", description="Link to channel"),
86+
)
87+
88+
scope = ExecuteToolScope.start(
89+
self.tool_details, self.agent_details, self.tenant_details, request
90+
)
91+
92+
if scope is not None:
93+
scope.dispose()
94+
95+
finished_spans = self.span_exporter.get_finished_spans()
96+
self.assertTrue(finished_spans, "Expected at least one span to be created")
97+
98+
span = finished_spans[-1]
99+
span_attributes = getattr(span, "attributes", {}) or {}
100+
101+
self.assertIn(
102+
GEN_AI_EXECUTION_SOURCE_NAME_KEY,
103+
span_attributes,
104+
"Expected source name to be set on span",
105+
)
106+
self.assertEqual(
107+
span_attributes[GEN_AI_EXECUTION_SOURCE_NAME_KEY],
108+
request.source_metadata.name,
109+
)
110+
111+
self.assertIn(
112+
GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY,
113+
span_attributes,
114+
"Expected source description to be set on span",
115+
)
116+
self.assertEqual(
117+
span_attributes[GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY],
118+
request.source_metadata.description,
119+
)
120+
50121

51122
if __name__ == "__main__":
52-
unittest.main(verbosity=2)
123+
# Run pytest only on the current file
124+
sys.exit(pytest.main([str(Path(__file__))] + sys.argv[1:]))

0 commit comments

Comments
 (0)