Skip to content

Commit 0f23458

Browse files
authored
Merge branch 'main' into dependabot/uv/langgraph-1.0.10rc1
2 parents 45a68b1 + 402f822 commit 0f23458

33 files changed

Lines changed: 1867 additions & 269 deletions

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

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,37 @@
1919
unregister_span_enricher,
2020
)
2121
from .exporters.spectra_exporter_options import SpectraExporterOptions
22-
from .inference_call_details import InferenceCallDetails, ServiceEndpoint
22+
from .inference_call_details import InferenceCallDetails
23+
from .models.service_endpoint import ServiceEndpoint
2324
from .inference_operation_type import InferenceOperationType
2425
from .inference_scope import InferenceScope
2526
from .invoke_agent_details import InvokeAgentScopeDetails
2627
from .invoke_agent_scope import InvokeAgentScope
2728
from .middleware.baggage_builder import BaggageBuilder
2829
from .models.caller_details import CallerDetails
30+
from .models.messages import (
31+
BlobPart,
32+
ChatMessage,
33+
FilePart,
34+
FinishReason,
35+
GenericPart,
36+
InputMessages,
37+
InputMessagesParam,
38+
MessagePart,
39+
MessageRole,
40+
Modality,
41+
OutputMessage,
42+
OutputMessages,
43+
OutputMessagesParam,
44+
ReasoningPart,
45+
ServerToolCallPart,
46+
ServerToolCallResponsePart,
47+
TextPart,
48+
ToolCallRequestPart,
49+
ToolCallResponsePart,
50+
UriPart,
51+
)
52+
from .models.response import Response
2953
from .models.user_details import UserDetails
3054
from .opentelemetry_scope import OpenTelemetryScope
3155
from .request import Request
@@ -70,12 +94,34 @@
7094
"ToolCallDetails",
7195
"Channel",
7296
"Request",
97+
"Response",
7398
"SpanDetails",
7499
"InferenceCallDetails",
75100
"ServiceEndpoint",
76101
# Enums
77102
"InferenceOperationType",
78103
"ToolType",
104+
# OTEL gen-ai message format types
105+
"MessageRole",
106+
"FinishReason",
107+
"Modality",
108+
"TextPart",
109+
"ToolCallRequestPart",
110+
"ToolCallResponsePart",
111+
"ReasoningPart",
112+
"BlobPart",
113+
"FilePart",
114+
"UriPart",
115+
"ServerToolCallPart",
116+
"ServerToolCallResponsePart",
117+
"GenericPart",
118+
"MessagePart",
119+
"ChatMessage",
120+
"OutputMessage",
121+
"InputMessages",
122+
"OutputMessages",
123+
"InputMessagesParam",
124+
"OutputMessagesParam",
79125
# Utility functions
80126
"extract_context_from_headers",
81127
"get_traceparent",

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,6 @@ class AgentDetails:
3838

3939
provider_name: Optional[str] = None
4040
"""The provider name (e.g., openai, anthropic)."""
41+
42+
agent_version: Optional[str] = None
43+
"""Optional version of the agent (e.g., "1.0.0", "2025-05-01")."""

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
GEN_AI_AGENT_ID_KEY = "gen_ai.agent.id"
3939
GEN_AI_AGENT_NAME_KEY = "gen_ai.agent.name"
4040
GEN_AI_AGENT_DESCRIPTION_KEY = "gen_ai.agent.description"
41+
GEN_AI_AGENT_VERSION_KEY = "gen_ai.agent.version"
4142
GEN_AI_AGENT_PLATFORM_ID_KEY = "microsoft.a365.agent.platform.id"
4243
GEN_AI_AGENT_THOUGHT_PROCESS_KEY = "microsoft.a365.agent.thought.process"
4344
GEN_AI_CONVERSATION_ID_KEY = "gen_ai.conversation.id"
@@ -73,6 +74,7 @@
7374
GEN_AI_CALLER_AGENT_ID_KEY = "microsoft.a365.caller.agent.id"
7475
GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY = "microsoft.a365.caller.agent.blueprint.id"
7576
GEN_AI_CALLER_AGENT_PLATFORM_ID_KEY = "microsoft.a365.caller.agent.platform.id"
77+
GEN_AI_CALLER_AGENT_VERSION_KEY = "microsoft.a365.caller.agent.version"
7678

7779
# Agent-specific dimensions
7880
AGENT_ID_KEY = "gen_ai.agent.id"

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

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
GEN_AI_CONVERSATION_ID_KEY,
1313
GEN_AI_TOOL_ARGS_KEY,
1414
GEN_AI_TOOL_CALL_ID_KEY,
15+
GEN_AI_TOOL_CALL_RESULT_KEY,
1516
GEN_AI_TOOL_DESCRIPTION_KEY,
1617
GEN_AI_TOOL_NAME_KEY,
1718
GEN_AI_TOOL_TYPE_KEY,
@@ -21,12 +22,12 @@
2122
USER_ID_KEY,
2223
USER_NAME_KEY,
2324
)
25+
from .utils import safe_json_dumps, validate_and_normalize_ip
2426
from .models.user_details import UserDetails
2527
from .opentelemetry_scope import OpenTelemetryScope
2628
from .request import Request
2729
from .span_details import SpanDetails
2830
from .tool_call_details import ToolCallDetails
29-
from .utils import validate_and_normalize_ip
3031

3132

3233
class ExecuteToolScope(OpenTelemetryScope):
@@ -77,25 +78,26 @@ def __init__(
7778
user_details: Optional human user details
7879
span_details: Optional span configuration (parent context, timing, kind)
7980
"""
80-
kind = SpanKind.INTERNAL
81-
parent_context = None
82-
start_time = None
83-
end_time = None
84-
if span_details is not None:
85-
if span_details.span_kind is not None:
86-
kind = span_details.span_kind
87-
parent_context = span_details.parent_context
88-
start_time = span_details.start_time
89-
end_time = span_details.end_time
81+
# spanKind defaults to INTERNAL; allow override via span_details
82+
resolved_span_details = (
83+
SpanDetails(
84+
span_kind=span_details.span_kind
85+
if span_details and span_details.span_kind
86+
else SpanKind.INTERNAL,
87+
parent_context=span_details.parent_context if span_details else None,
88+
start_time=span_details.start_time if span_details else None,
89+
end_time=span_details.end_time if span_details else None,
90+
span_links=span_details.span_links if span_details else None,
91+
)
92+
if span_details
93+
else SpanDetails(span_kind=SpanKind.INTERNAL)
94+
)
9095

9196
super().__init__(
92-
kind=kind,
9397
operation_name=EXECUTE_TOOL_OPERATION_NAME,
9498
activity_name=f"{EXECUTE_TOOL_OPERATION_NAME} {details.tool_name}",
9599
agent_details=agent_details,
96-
parent_context=parent_context,
97-
start_time=start_time,
98-
end_time=end_time,
100+
span_details=resolved_span_details,
99101
)
100102

101103
# Extract details
@@ -107,7 +109,9 @@ def __init__(
107109
endpoint = details.endpoint
108110

109111
self.set_tag_maybe(GEN_AI_TOOL_NAME_KEY, tool_name)
110-
self.set_tag_maybe(GEN_AI_TOOL_ARGS_KEY, arguments)
112+
if arguments is not None:
113+
serialized = safe_json_dumps(arguments) if isinstance(arguments, dict) else arguments
114+
self.set_tag_maybe(GEN_AI_TOOL_ARGS_KEY, serialized)
111115
self.set_tag_maybe(GEN_AI_TOOL_TYPE_KEY, tool_type)
112116
self.set_tag_maybe(GEN_AI_TOOL_CALL_ID_KEY, tool_call_id)
113117
self.set_tag_maybe(GEN_AI_TOOL_DESCRIPTION_KEY, description)
@@ -133,13 +137,15 @@ def __init__(
133137
validate_and_normalize_ip(user_details.user_client_ip),
134138
)
135139

136-
def record_response(self, response: str) -> None:
137-
"""Records response information for telemetry tracking.
140+
def record_response(self, result: dict[str, object] | str) -> None:
141+
"""Record the tool call result for telemetry tracking.
138142
139-
Note: This method is intentionally a no-op as GEN_AI_EVENT_CONTENT was removed.
140-
The method is kept for interface compatibility.
143+
Per OTEL spec, the result is expected to be an object. If a string
144+
is provided, it is recorded as-is (JSON string fallback). If a dict
145+
is provided, it is serialized to JSON.
141146
142147
Args:
143-
response: The response to record
148+
result: Tool call result as a structured dict or JSON string
144149
"""
145-
pass
150+
serialized = safe_json_dumps(result) if isinstance(result, dict) else result
151+
self.set_tag_maybe(GEN_AI_TOOL_CALL_RESULT_KEY, serialized)

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

Lines changed: 0 additions & 14 deletions
This file was deleted.

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

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,7 @@
44
from dataclasses import dataclass
55

66
from .inference_operation_type import InferenceOperationType
7-
8-
9-
@dataclass
10-
class ServiceEndpoint:
11-
"""Represents a service endpoint with hostname and optional port."""
12-
13-
hostname: str
14-
"""The hostname of the service endpoint."""
15-
16-
port: int | None = None
17-
"""The port of the service endpoint."""
7+
from .models.service_endpoint import ServiceEndpoint
188

199

2010
@dataclass

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

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
from typing import List
55

6+
from opentelemetry.trace import SpanKind
7+
68
from .agent_details import AgentDetails
79
from .constants import (
810
CHANNEL_LINK_KEY,
@@ -25,6 +27,12 @@
2527
GEN_AI_CALLER_CLIENT_IP_KEY,
2628
)
2729
from .inference_call_details import InferenceCallDetails
30+
from .message_utils import (
31+
normalize_input_messages,
32+
normalize_output_messages,
33+
serialize_messages,
34+
)
35+
from .models.messages import InputMessagesParam, OutputMessagesParam
2836
from .models.user_details import UserDetails
2937
from .opentelemetry_scope import OpenTelemetryScope
3038
from .request import Request
@@ -74,26 +82,28 @@ def __init__(
7482
user_details: Optional human user details
7583
span_details: Optional span configuration (parent context, timing)
7684
"""
77-
parent_context = None
78-
start_time = None
79-
end_time = None
80-
if span_details is not None:
81-
parent_context = span_details.parent_context
82-
start_time = span_details.start_time
83-
end_time = span_details.end_time
85+
# spanKind for InferenceScope is always CLIENT
86+
resolved_span_details = (
87+
SpanDetails(
88+
span_kind=SpanKind.CLIENT,
89+
parent_context=span_details.parent_context if span_details else None,
90+
start_time=span_details.start_time if span_details else None,
91+
end_time=span_details.end_time if span_details else None,
92+
span_links=span_details.span_links if span_details else None,
93+
)
94+
if span_details
95+
else SpanDetails(span_kind=SpanKind.CLIENT)
96+
)
8497

8598
super().__init__(
86-
kind="Client",
8799
operation_name=details.operationName.value,
88100
activity_name=f"{details.operationName.value} {details.model}",
89101
agent_details=agent_details,
90-
parent_context=parent_context,
91-
start_time=start_time,
92-
end_time=end_time,
102+
span_details=resolved_span_details,
93103
)
94104

95-
if request.content:
96-
self.set_tag_maybe(GEN_AI_INPUT_MESSAGES_KEY, request.content)
105+
if request.content is not None:
106+
self.record_input_messages(request.content)
97107
self.set_tag_maybe(GEN_AI_CONVERSATION_ID_KEY, request.conversation_id)
98108

99109
self.set_tag_maybe(GEN_AI_OPERATION_NAME_KEY, details.operationName.value)
@@ -134,21 +144,29 @@ def __init__(
134144
validate_and_normalize_ip(user_details.user_client_ip),
135145
)
136146

137-
def record_input_messages(self, messages: List[str]) -> None:
147+
def record_input_messages(self, messages: InputMessagesParam) -> None:
138148
"""Records the input messages for telemetry tracking.
139149
150+
Accepts plain strings (auto-wrapped as OTEL ChatMessage with role ``user``)
151+
or a versioned ``InputMessages`` wrapper.
152+
140153
Args:
141-
messages: List of input messages
154+
messages: List of input message strings or an InputMessages wrapper
142155
"""
143-
self.set_tag_maybe(GEN_AI_INPUT_MESSAGES_KEY, safe_json_dumps(messages))
156+
wrapper = normalize_input_messages(messages)
157+
self.set_tag_maybe(GEN_AI_INPUT_MESSAGES_KEY, serialize_messages(wrapper))
144158

145-
def record_output_messages(self, messages: List[str]) -> None:
159+
def record_output_messages(self, messages: OutputMessagesParam) -> None:
146160
"""Records the output messages for telemetry tracking.
147161
162+
Accepts plain strings (auto-wrapped as OTEL OutputMessage with role ``assistant``)
163+
or a versioned ``OutputMessages`` wrapper.
164+
148165
Args:
149-
messages: List of output messages
166+
messages: List of output message strings or an OutputMessages wrapper
150167
"""
151-
self.set_tag_maybe(GEN_AI_OUTPUT_MESSAGES_KEY, safe_json_dumps(messages))
168+
wrapper = normalize_output_messages(messages)
169+
self.set_tag_maybe(GEN_AI_OUTPUT_MESSAGES_KEY, serialize_messages(wrapper))
152170

153171
def record_input_tokens(self, input_tokens: int) -> None:
154172
"""Records the number of input tokens for telemetry tracking.

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
# Data class for invoke agent scope details.
55

66
from dataclasses import dataclass
7-
from urllib.parse import ParseResult
7+
8+
from .models.service_endpoint import ServiceEndpoint
89

910

1011
@dataclass
1112
class InvokeAgentScopeDetails:
1213
"""Scope-level configuration for agent invocation tracing."""
1314

14-
endpoint: ParseResult | None = None
15+
endpoint: ServiceEndpoint | None = None

0 commit comments

Comments
 (0)