Skip to content

Commit e3efbb0

Browse files
CopilotnikhilNava
andcommitted
feat: Replace parent_id string with Context object and add context propagation
- Changed all scope classes to accept `parent_context: Context` instead of `parent_id: str` - Added `inject_trace_context()` method to OpenTelemetryScope for header propagation - Added `get_context()` method to get the scope's Context for passing to child scopes - Added `extract_trace_context()` utility function using OTel's standard `extract()` - Removed custom W3C trace context parsing in favor of OTel's propagation API - Updated OutputLoggingMiddleware to use extract_trace_context() - Updated all tests to use extract_trace_context() and parent_context Co-authored-by: nikhilNava <211831449+nikhilNava@users.noreply.github.com>
1 parent e60c505 commit e3efbb0

13 files changed

Lines changed: 445 additions & 159 deletions

File tree

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
from .tool_call_details import ToolCallDetails
3434
from .tool_type import ToolType
3535
from .trace_processor.span_processor import SpanProcessor
36+
from .utils import extract_trace_context
3637

3738
__all__ = [
3839
# Main SDK functions
@@ -71,6 +72,8 @@
7172
"ExecutionType",
7273
"InferenceOperationType",
7374
"ToolType",
75+
# Utility functions
76+
"extract_trace_context",
7477
# Constants
7578
# all constants from constants.py are exported via *
7679
]

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

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

44
from datetime import datetime
55

6+
from opentelemetry.context import Context
67
from opentelemetry.trace import SpanKind
78

89
from .agent_details import AgentDetails
@@ -33,7 +34,7 @@ def start(
3334
agent_details: AgentDetails,
3435
tenant_details: TenantDetails,
3536
request: Request | None = None,
36-
parent_id: str | None = None,
37+
parent_context: Context | None = None,
3738
start_time: datetime | None = None,
3839
end_time: datetime | None = None,
3940
span_kind: SpanKind | None = None,
@@ -45,8 +46,9 @@ def start(
4546
agent_details: The details of the agent making the call
4647
tenant_details: The details of the tenant
4748
request: Optional request details for additional context
48-
parent_id: Optional parent Activity ID used to link this span to an upstream
49-
operation
49+
parent_context: Optional OpenTelemetry Context used to link this span to an
50+
upstream operation. Use ``extract_trace_context()`` to convert a
51+
Context from HTTP headers containing W3C traceparent.
5052
start_time: Optional explicit start time as a datetime object. Useful when
5153
recording a tool call after execution has already completed.
5254
end_time: Optional explicit end time as a datetime object. When provided,
@@ -63,7 +65,7 @@ def start(
6365
agent_details,
6466
tenant_details,
6567
request,
66-
parent_id,
68+
parent_context,
6769
start_time,
6870
end_time,
6971
span_kind,
@@ -75,7 +77,7 @@ def __init__(
7577
agent_details: AgentDetails,
7678
tenant_details: TenantDetails,
7779
request: Request | None = None,
78-
parent_id: str | None = None,
80+
parent_context: Context | None = None,
7981
start_time: datetime | None = None,
8082
end_time: datetime | None = None,
8183
span_kind: SpanKind | None = None,
@@ -87,8 +89,9 @@ def __init__(
8789
agent_details: The details of the agent making the call
8890
tenant_details: The details of the tenant
8991
request: Optional request details for additional context
90-
parent_id: Optional parent Activity ID used to link this span to an upstream
91-
operation
92+
parent_context: Optional OpenTelemetry Context used to link this span to an
93+
upstream operation. Use ``extract_trace_context()`` to convert a
94+
Context from HTTP headers containing W3C traceparent.
9295
start_time: Optional explicit start time as a datetime object. Useful when
9396
recording a tool call after execution has already completed.
9497
end_time: Optional explicit end time as a datetime object. When provided,
@@ -103,7 +106,7 @@ def __init__(
103106
activity_name=f"{EXECUTE_TOOL_OPERATION_NAME} {details.tool_name}",
104107
agent_details=agent_details,
105108
tenant_details=tenant_details,
106-
parent_id=parent_id,
109+
parent_context=parent_context,
107110
start_time=start_time,
108111
end_time=end_time,
109112
)

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

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from datetime import datetime
55
from typing import List
66

7+
from opentelemetry.context import Context
8+
79
from .agent_details import AgentDetails
810
from .constants import (
911
CHANNEL_LINK_KEY,
@@ -36,7 +38,7 @@ def start(
3638
agent_details: AgentDetails,
3739
tenant_details: TenantDetails,
3840
request: Request | None = None,
39-
parent_id: str | None = None,
41+
parent_context: Context | None = None,
4042
start_time: datetime | None = None,
4143
end_time: datetime | None = None,
4244
) -> "InferenceScope":
@@ -47,16 +49,17 @@ def start(
4749
agent_details: The details of the agent making the call
4850
tenant_details: The details of the tenant
4951
request: Optional request details for additional context
50-
parent_id: Optional parent Activity ID used to link this span to an upstream
51-
operation
52+
parent_context: Optional OpenTelemetry Context used to link this span to an
53+
upstream operation. Use ``extract_trace_context()`` to convert a
54+
Context from HTTP headers containing W3C traceparent.
5255
start_time: Optional explicit start time as a datetime object.
5356
end_time: Optional explicit end time as a datetime object.
5457
5558
Returns:
5659
A new InferenceScope instance
5760
"""
5861
return InferenceScope(
59-
details, agent_details, tenant_details, request, parent_id, start_time, end_time
62+
details, agent_details, tenant_details, request, parent_context, start_time, end_time
6063
)
6164

6265
def __init__(
@@ -65,7 +68,7 @@ def __init__(
6568
agent_details: AgentDetails,
6669
tenant_details: TenantDetails,
6770
request: Request | None = None,
68-
parent_id: str | None = None,
71+
parent_context: Context | None = None,
6972
start_time: datetime | None = None,
7073
end_time: datetime | None = None,
7174
):
@@ -76,8 +79,9 @@ def __init__(
7679
agent_details: The details of the agent making the call
7780
tenant_details: The details of the tenant
7881
request: Optional request details for additional context
79-
parent_id: Optional parent Activity ID used to link this span to an upstream
80-
operation
82+
parent_context: Optional OpenTelemetry Context used to link this span to an
83+
upstream operation. Use ``extract_trace_context()`` to convert a
84+
Context from HTTP headers containing W3C traceparent.
8185
start_time: Optional explicit start time as a datetime object.
8286
end_time: Optional explicit end time as a datetime object.
8387
"""
@@ -88,7 +92,7 @@ def __init__(
8892
activity_name=f"{details.operationName.value} {details.model}",
8993
agent_details=agent_details,
9094
tenant_details=tenant_details,
91-
parent_id=parent_id,
95+
parent_context=parent_context,
9296
start_time=start_time,
9397
end_time=end_time,
9498
)

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import logging
77
from datetime import datetime
88

9+
from opentelemetry.context import Context
910
from opentelemetry.trace import SpanKind
1011

1112
from .agent_details import AgentDetails
@@ -50,6 +51,7 @@ def start(
5051
request: Request | None = None,
5152
caller_agent_details: AgentDetails | None = None,
5253
caller_details: CallerDetails | None = None,
54+
parent_context: Context | None = None,
5355
start_time: datetime | None = None,
5456
end_time: datetime | None = None,
5557
span_kind: SpanKind | None = None,
@@ -63,6 +65,9 @@ def start(
6365
request: Optional request details for additional context
6466
caller_agent_details: Optional details of the caller agent
6567
caller_details: Optional details of the non-agentic caller
68+
parent_context: Optional OpenTelemetry Context used to link this span to an
69+
upstream operation. Use ``extract_trace_context()`` to convert a
70+
Context from HTTP headers containing W3C traceparent.
6671
start_time: Optional explicit start time as a datetime object.
6772
end_time: Optional explicit end time as a datetime object.
6873
span_kind: Optional span kind override. Defaults to ``SpanKind.CLIENT``.
@@ -77,6 +82,7 @@ def start(
7782
request,
7883
caller_agent_details,
7984
caller_details,
85+
parent_context,
8086
start_time,
8187
end_time,
8288
span_kind,
@@ -89,6 +95,7 @@ def __init__(
8995
request: Request | None = None,
9096
caller_agent_details: AgentDetails | None = None,
9197
caller_details: CallerDetails | None = None,
98+
parent_context: Context | None = None,
9299
start_time: datetime | None = None,
93100
end_time: datetime | None = None,
94101
span_kind: SpanKind | None = None,
@@ -101,6 +108,9 @@ def __init__(
101108
request: Optional request details for additional context
102109
caller_agent_details: Optional details of the caller agent
103110
caller_details: Optional details of the non-agentic caller
111+
parent_context: Optional OpenTelemetry Context used to link this span to an
112+
upstream operation. Use ``extract_trace_context()`` to convert a
113+
Context from HTTP headers containing W3C traceparent.
104114
start_time: Optional explicit start time as a datetime object.
105115
end_time: Optional explicit end time as a datetime object.
106116
span_kind: Optional span kind override. Defaults to ``SpanKind.CLIENT``.
@@ -118,6 +128,7 @@ def __init__(
118128
activity_name=activity_name,
119129
agent_details=invoke_agent_details.details,
120130
tenant_details=tenant_details,
131+
parent_context=parent_context,
121132
start_time=start_time,
122133
end_time=end_time,
123134
)

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

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
from typing import TYPE_CHECKING, Any
1111

1212
from opentelemetry import context, trace
13+
from opentelemetry.context import Context
14+
from opentelemetry.propagate import inject
1315
from opentelemetry.trace import (
1416
Span,
1517
SpanKind,
@@ -43,7 +45,7 @@
4345
TELEMETRY_SDK_VERSION_KEY,
4446
TENANT_ID_KEY,
4547
)
46-
from .utils import get_sdk_version, parse_parent_id_to_context
48+
from .utils import get_sdk_version
4749

4850
if TYPE_CHECKING:
4951
from .agent_details import AgentDetails
@@ -97,7 +99,7 @@ def __init__(
9799
activity_name: str,
98100
agent_details: "AgentDetails | None" = None,
99101
tenant_details: "TenantDetails | None" = None,
100-
parent_id: str | None = None,
102+
parent_context: Context | None = None,
101103
start_time: datetime | None = None,
102104
end_time: datetime | None = None,
103105
):
@@ -111,8 +113,9 @@ def __init__(
111113
activity_name: The name of the activity for display purposes
112114
agent_details: Optional agent details
113115
tenant_details: Optional tenant details
114-
parent_id: Optional parent Activity ID used to link this span to an upstream
115-
operation
116+
parent_context: Optional OpenTelemetry Context used to link this span to an
117+
upstream operation. Use ``extract_trace_context()`` to extract a
118+
Context from HTTP headers containing W3C traceparent.
116119
start_time: Optional explicit start time as a datetime object.
117120
Useful when recording an operation after it has already completed.
118121
end_time: Optional explicit end time as a datetime object.
@@ -146,9 +149,8 @@ def __init__(
146149
activity_kind = SpanKind.CONSUMER
147150

148151
# Get context for parent relationship
149-
# If parent_id is provided, parse it and use it as the parent context
152+
# If parent_context is provided, use it directly
150153
# Otherwise, use the current context
151-
parent_context = parse_parent_id_to_context(parent_id)
152154
span_context = parent_context if parent_context else context.get_current()
153155

154156
# Convert custom start time to OTel-compatible format (nanoseconds since epoch)
@@ -286,6 +288,52 @@ def _end(self) -> None:
286288
else:
287289
self._span.end()
288290

291+
def get_context(self) -> Context | None:
292+
"""Get the OpenTelemetry context for this scope's span.
293+
294+
This method returns a Context object containing this scope's span,
295+
which can be used to propagate trace context to child operations
296+
or downstream services.
297+
298+
Returns:
299+
A Context containing this scope's span, or None if telemetry
300+
is disabled or no span exists.
301+
"""
302+
if self._span and self._is_telemetry_enabled():
303+
return set_span_in_context(self._span)
304+
return None
305+
306+
def inject_trace_context(self) -> dict[str, str]:
307+
"""Inject trace context headers for distributed tracing propagation.
308+
309+
This method returns a dictionary of headers containing the trace
310+
context (traceparent and tracestate) that can be used to propagate
311+
the current span's context to downstream services via HTTP headers
312+
or other transport mechanisms.
313+
314+
The headers follow the W3C Trace Context specification and include:
315+
- ``traceparent``: Contains version, trace-id, parent-id, and trace-flags
316+
- ``tracestate``: Contains vendor-specific trace information (if any)
317+
318+
Example usage::
319+
320+
>>> scope = OpenTelemetryScope(...)
321+
>>> headers = scope.inject_trace_context()
322+
>>> # Add headers to outgoing HTTP request
323+
>>> requests.get("https://downstream-service/api", headers=headers)
324+
325+
Returns:
326+
A dictionary containing W3C trace context headers. Returns an
327+
empty dictionary if telemetry is disabled or no span exists.
328+
"""
329+
headers: dict[str, str] = {}
330+
if self._span and self._is_telemetry_enabled():
331+
# Create a context with the current span
332+
ctx = set_span_in_context(self._span)
333+
# Use the global propagator to inject trace context into headers
334+
inject(headers, context=ctx)
335+
return headers
336+
289337
def __enter__(self):
290338
"""Enter the context manager and make span active."""
291339
if self._span and self._is_telemetry_enabled():

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

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

44
from datetime import datetime
55

6+
from opentelemetry.context import Context
7+
68
from ..agent_details import AgentDetails
79
from ..constants import GEN_AI_OUTPUT_MESSAGES_KEY
810
from ..models.response import Response
@@ -23,7 +25,7 @@ def start(
2325
agent_details: AgentDetails,
2426
tenant_details: TenantDetails,
2527
response: Response,
26-
parent_id: str | None = None,
28+
parent_context: Context | None = None,
2729
start_time: datetime | None = None,
2830
end_time: datetime | None = None,
2931
) -> "OutputScope":
@@ -33,22 +35,25 @@ def start(
3335
agent_details: The details of the agent
3436
tenant_details: The details of the tenant
3537
response: The response details from the agent
36-
parent_id: Optional parent Activity ID used to link this span to an upstream
37-
operation
38+
parent_context: Optional OpenTelemetry Context used to link this span to an
39+
upstream operation. Use ``extract_trace_context()`` to convert a
40+
Context from HTTP headers containing W3C traceparent.
3841
start_time: Optional explicit start time as a datetime object.
3942
end_time: Optional explicit end time as a datetime object.
4043
4144
Returns:
4245
A new OutputScope instance
4346
"""
44-
return OutputScope(agent_details, tenant_details, response, parent_id, start_time, end_time)
47+
return OutputScope(
48+
agent_details, tenant_details, response, parent_context, start_time, end_time
49+
)
4550

4651
def __init__(
4752
self,
4853
agent_details: AgentDetails,
4954
tenant_details: TenantDetails,
5055
response: Response,
51-
parent_id: str | None = None,
56+
parent_context: Context | None = None,
5257
start_time: datetime | None = None,
5358
end_time: datetime | None = None,
5459
):
@@ -58,8 +63,9 @@ def __init__(
5863
agent_details: The details of the agent
5964
tenant_details: The details of the tenant
6065
response: The response details from the agent
61-
parent_id: Optional parent Activity ID used to link this span to an upstream
62-
operation
66+
parent_context: Optional OpenTelemetry Context used to link this span to an
67+
upstream operation. Use ``extract_trace_context()`` to convert a
68+
Context from HTTP headers containing W3C traceparent.
6369
start_time: Optional explicit start time as a datetime object.
6470
end_time: Optional explicit end time as a datetime object.
6571
"""
@@ -69,7 +75,7 @@ def __init__(
6975
activity_name=(f"{OUTPUT_OPERATION_NAME} {agent_details.agent_id}"),
7076
agent_details=agent_details,
7177
tenant_details=tenant_details,
72-
parent_id=parent_id,
78+
parent_context=parent_context,
7379
start_time=start_time,
7480
end_time=end_time,
7581
)

0 commit comments

Comments
 (0)