microsoft-agents-a365-observability-core 0.1.0__py3-none-any.whl → 0.2.0.dev5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (18) hide show
  1. microsoft_agents_a365/observability/core/agent_details.py +7 -4
  2. microsoft_agents_a365/observability/core/config.py +38 -7
  3. microsoft_agents_a365/observability/core/constants.py +3 -1
  4. microsoft_agents_a365/observability/core/exporters/agent365_exporter.py +5 -1
  5. microsoft_agents_a365/observability/core/exporters/agent365_exporter_options.py +39 -0
  6. microsoft_agents_a365/observability/core/exporters/utils.py +77 -0
  7. microsoft_agents_a365/observability/core/invoke_agent_scope.py +12 -3
  8. microsoft_agents_a365/observability/core/middleware/baggage_builder.py +40 -11
  9. microsoft_agents_a365/observability/core/middleware/turn_context_baggage.py +31 -32
  10. microsoft_agents_a365/observability/core/models/operation_source.py +21 -0
  11. microsoft_agents_a365/observability/core/request.py +0 -1
  12. microsoft_agents_a365/observability/core/trace_processor/span_processor.py +10 -1
  13. microsoft_agents_a365/observability/core/trace_processor/util.py +7 -4
  14. microsoft_agents_a365/observability/core/utils.py +53 -4
  15. {microsoft_agents_a365_observability_core-0.1.0.dist-info → microsoft_agents_a365_observability_core-0.2.0.dev5.dist-info}/METADATA +3 -3
  16. {microsoft_agents_a365_observability_core-0.1.0.dist-info → microsoft_agents_a365_observability_core-0.2.0.dev5.dist-info}/RECORD +18 -16
  17. {microsoft_agents_a365_observability_core-0.1.0.dist-info → microsoft_agents_a365_observability_core-0.2.0.dev5.dist-info}/WHEEL +0 -0
  18. {microsoft_agents_a365_observability_core-0.1.0.dist-info → microsoft_agents_a365_observability_core-0.2.0.dev5.dist-info}/top_level.txt +0 -0
@@ -21,22 +21,25 @@ class AgentDetails:
21
21
  """A description of the AI agent's purpose or capabilities."""
22
22
 
23
23
  agent_auid: Optional[str] = None
24
- """Optional Agent User ID for the agent."""
24
+ """Agentic User ID for the agent."""
25
25
 
26
26
  agent_upn: Optional[str] = None
27
- """Optional User Principal Name (UPN) for the agent."""
27
+ """User Principal Name (UPN) for the agentic user."""
28
28
 
29
29
  agent_blueprint_id: Optional[str] = None
30
- """Optional Blueprint/Application ID for the agent."""
30
+ """Blueprint/Application ID for the agent."""
31
31
 
32
32
  agent_type: Optional[AgentType] = None
33
33
  """The agent type."""
34
34
 
35
35
  tenant_id: Optional[str] = None
36
- """Optional Tenant ID for the agent."""
36
+ """Tenant ID for the agent."""
37
37
 
38
38
  conversation_id: Optional[str] = None
39
39
  """Optional conversation ID for compatibility."""
40
40
 
41
41
  icon_uri: Optional[str] = None
42
42
  """Optional icon URI for the agent."""
43
+
44
+ agent_client_ip: Optional[str] = None
45
+ """Client IP address of the agent user."""
@@ -11,6 +11,7 @@ from opentelemetry.sdk.trace import TracerProvider
11
11
  from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter
12
12
 
13
13
  from .exporters.agent365_exporter import Agent365Exporter
14
+ from .exporters.agent365_exporter_options import Agent365ExporterOptions
14
15
  from .exporters.utils import is_agent365_exporter_enabled
15
16
  from .trace_processor.span_processor import SpanProcessor
16
17
 
@@ -51,6 +52,7 @@ class TelemetryManager:
51
52
  logger_name: str = DEFAULT_LOGGER_NAME,
52
53
  token_resolver: Callable[[str, str], str | None] | None = None,
53
54
  cluster_category: str = "prod",
55
+ exporter_options: Optional[Agent365ExporterOptions] = None,
54
56
  **kwargs: Any,
55
57
  ) -> bool:
56
58
  """
@@ -59,8 +61,12 @@ class TelemetryManager:
59
61
  :param service_name: The name of the service.
60
62
  :param service_namespace: The namespace of the service.
61
63
  :param logger_name: The name of the logger to collect telemetry from.
62
- :param token_resolver: Callable that returns an auth token for a given agent + tenant.
63
- :param cluster_category: Environment / cluster category (e.g., "preprod", "prod").
64
+ :param token_resolver: (Deprecated) Callable that returns an auth token for a given agent + tenant.
65
+ Use exporter_options instead.
66
+ :param cluster_category: (Deprecated) Environment / cluster category (e.g. "prod").
67
+ Use exporter_options instead.
68
+ :param exporter_options: Agent365ExporterOptions instance for configuring the exporter.
69
+ If provided, exporter_options takes precedence. If exporter_options is None, the token_resolver and cluster_category parameters are used as fallback/legacy support to construct a default Agent365ExporterOptions instance.
64
70
  :return: True if configuration succeeded, False otherwise.
65
71
  """
66
72
  try:
@@ -71,6 +77,7 @@ class TelemetryManager:
71
77
  logger_name,
72
78
  token_resolver,
73
79
  cluster_category,
80
+ exporter_options,
74
81
  **kwargs,
75
82
  )
76
83
  except Exception as e:
@@ -84,6 +91,7 @@ class TelemetryManager:
84
91
  logger_name: str,
85
92
  token_resolver: Callable[[str, str], str | None] | None = None,
86
93
  cluster_category: str = "prod",
94
+ exporter_options: Optional[Agent365ExporterOptions] = None,
87
95
  **kwargs: Any,
88
96
  ) -> bool:
89
97
  """Internal configuration method - not thread-safe, must be called with lock."""
@@ -115,11 +123,26 @@ class TelemetryManager:
115
123
  trace.set_tracer_provider(tracer_provider)
116
124
  self._tracer_provider = tracer_provider
117
125
 
118
- if is_agent365_exporter_enabled() and token_resolver is not None:
119
- exporter = Agent365Exporter(
120
- token_resolver=token_resolver,
126
+ # Use exporter_options if provided, otherwise create default options with legacy parameters
127
+ if exporter_options is None:
128
+ exporter_options = Agent365ExporterOptions(
121
129
  cluster_category=cluster_category,
122
- **kwargs,
130
+ token_resolver=token_resolver,
131
+ )
132
+
133
+ # Extract configuration for BatchSpanProcessor
134
+ batch_processor_kwargs = {
135
+ "max_queue_size": exporter_options.max_queue_size,
136
+ "schedule_delay_millis": exporter_options.scheduled_delay_ms,
137
+ "export_timeout_millis": exporter_options.exporter_timeout_ms,
138
+ "max_export_batch_size": exporter_options.max_export_batch_size,
139
+ }
140
+
141
+ if is_agent365_exporter_enabled() and exporter_options.token_resolver is not None:
142
+ exporter = Agent365Exporter(
143
+ token_resolver=exporter_options.token_resolver,
144
+ cluster_category=exporter_options.cluster_category,
145
+ use_s2s_endpoint=exporter_options.use_s2s_endpoint,
123
146
  )
124
147
  else:
125
148
  exporter = ConsoleSpanExporter()
@@ -130,7 +153,7 @@ class TelemetryManager:
130
153
  # Add span processors
131
154
 
132
155
  # Create BatchSpanProcessor with optimized settings
133
- batch_processor = BatchSpanProcessor(exporter)
156
+ batch_processor = BatchSpanProcessor(exporter, **batch_processor_kwargs)
134
157
  agent_processor = SpanProcessor()
135
158
 
136
159
  tracer_provider.add_span_processor(batch_processor)
@@ -197,6 +220,7 @@ def configure(
197
220
  logger_name: str = DEFAULT_LOGGER_NAME,
198
221
  token_resolver: Callable[[str, str], str | None] | None = None,
199
222
  cluster_category: str = "prod",
223
+ exporter_options: Optional[Agent365ExporterOptions] = None,
200
224
  **kwargs: Any,
201
225
  ) -> bool:
202
226
  """
@@ -205,6 +229,12 @@ def configure(
205
229
  :param service_name: The name of the service.
206
230
  :param service_namespace: The namespace of the service.
207
231
  :param logger_name: The name of the logger to collect telemetry from.
232
+ :param token_resolver: (Deprecated) Callable that returns an auth token for a given agent + tenant.
233
+ Use exporter_options instead.
234
+ :param cluster_category: (Deprecated) Environment / cluster category (e.g. "prod").
235
+ Use exporter_options instead.
236
+ :param exporter_options: Agent365ExporterOptions instance for configuring the exporter.
237
+ If provided, exporter_options takes precedence. If exporter_options is None, the token_resolver and cluster_category parameters are used as fallback/legacy support to construct a default Agent365ExporterOptions instance.
208
238
  :return: True if configuration succeeded, False otherwise.
209
239
  """
210
240
  return _telemetry_manager.configure(
@@ -213,6 +243,7 @@ def configure(
213
243
  logger_name,
214
244
  token_resolver,
215
245
  cluster_category,
246
+ exporter_options,
216
247
  **kwargs,
217
248
  )
218
249
 
@@ -68,6 +68,7 @@ GEN_AI_CALLER_TENANT_ID_KEY = "gen_ai.caller.tenantid"
68
68
  GEN_AI_CALLER_ID_KEY = "gen_ai.caller.id"
69
69
  GEN_AI_CALLER_NAME_KEY = "gen_ai.caller.name"
70
70
  GEN_AI_CALLER_UPN_KEY = "gen_ai.caller.upn"
71
+ GEN_AI_CALLER_CLIENT_IP_KEY = "gen_ai.caller.client.ip"
71
72
 
72
73
  # Agent to Agent caller agent dimensions
73
74
  GEN_AI_CALLER_AGENT_USER_ID_KEY = "gen_ai.caller.agent.userid"
@@ -77,6 +78,7 @@ GEN_AI_CALLER_AGENT_NAME_KEY = "gen_ai.caller.agent.name"
77
78
  GEN_AI_CALLER_AGENT_ID_KEY = "gen_ai.caller.agent.id"
78
79
  GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY = "gen_ai.caller.agent.applicationid"
79
80
  GEN_AI_CALLER_AGENT_TYPE_KEY = "gen_ai.caller.agent.type"
81
+ GEN_AI_CALLER_AGENT_USER_CLIENT_IP = "gen_ai.caller.agent.user.client.ip"
80
82
 
81
83
  # Agent-specific dimensions
82
84
  AGENT_ID_KEY = "gen_ai.agent.id"
@@ -92,13 +94,13 @@ GEN_AI_AGENT_UPN_KEY = "gen_ai.agent.upn"
92
94
  GEN_AI_AGENT_BLUEPRINT_ID_KEY = "gen_ai.agent.applicationid"
93
95
  CORRELATION_ID_KEY = "correlation.id"
94
96
  HIRING_MANAGER_ID_KEY = "hiring.manager.id"
97
+ SESSION_DESCRIPTION_KEY = "session.description"
95
98
 
96
99
  # Execution context dimensions
97
100
  GEN_AI_EXECUTION_TYPE_KEY = "gen_ai.execution.type"
98
101
  GEN_AI_EXECUTION_PAYLOAD_KEY = "gen_ai.execution.payload"
99
102
 
100
103
  # Source metadata dimensions
101
- GEN_AI_EXECUTION_SOURCE_ID_KEY = "gen_ai.execution.sourceMetadata.id"
102
104
  GEN_AI_EXECUTION_SOURCE_NAME_KEY = "gen_ai.channel.name"
103
105
  GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY = "gen_ai.channel.link"
104
106
 
@@ -23,6 +23,7 @@ from .utils import (
23
23
  kind_name,
24
24
  partition_by_identity,
25
25
  status_name,
26
+ truncate_span,
26
27
  )
27
28
 
28
29
  # ---- Exporter ---------------------------------------------------------------
@@ -295,7 +296,7 @@ class Agent365Exporter(SpanExporter):
295
296
  start_ns = sp.start_time
296
297
  end_ns = sp.end_time
297
298
 
298
- return {
299
+ span_dict = {
299
300
  "traceId": hex_trace_id(ctx.trace_id),
300
301
  "spanId": hex_span_id(ctx.span_id),
301
302
  "parentSpanId": parent_span_id,
@@ -308,3 +309,6 @@ class Agent365Exporter(SpanExporter):
308
309
  "links": links,
309
310
  "status": status,
310
311
  }
312
+
313
+ # Apply truncation if needed
314
+ return truncate_span(span_dict)
@@ -0,0 +1,39 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+
4
+ from typing import Awaitable, Callable, Optional
5
+
6
+
7
+ class Agent365ExporterOptions:
8
+ """
9
+ Configuration for Agent365Exporter.
10
+ Only cluster_category and token_resolver are required for core operation.
11
+ """
12
+
13
+ def __init__(
14
+ self,
15
+ cluster_category: str = "prod",
16
+ token_resolver: Optional[Callable[[str, str], Awaitable[Optional[str]]]] = None,
17
+ use_s2s_endpoint: bool = False,
18
+ max_queue_size: int = 2048,
19
+ scheduled_delay_ms: int = 5000,
20
+ exporter_timeout_ms: int = 30000,
21
+ max_export_batch_size: int = 512,
22
+ ):
23
+ """
24
+ Args:
25
+ cluster_category: Cluster region argument. Defaults to 'prod'.
26
+ token_resolver: Async callable that resolves the auth token (REQUIRED).
27
+ use_s2s_endpoint: Use the S2S endpoint instead of standard endpoint.
28
+ max_queue_size: Maximum queue size for the batch processor. Default is 2048.
29
+ scheduled_delay_ms: Delay between export batches (ms). Default is 5000.
30
+ exporter_timeout_ms: Timeout for the export operation (ms). Default is 30000.
31
+ max_export_batch_size: Maximum batch size for export operations. Default is 512.
32
+ """
33
+ self.cluster_category = cluster_category
34
+ self.token_resolver = token_resolver
35
+ self.use_s2s_endpoint = use_s2s_endpoint
36
+ self.max_queue_size = max_queue_size
37
+ self.scheduled_delay_ms = scheduled_delay_ms
38
+ self.exporter_timeout_ms = exporter_timeout_ms
39
+ self.max_export_batch_size = max_export_batch_size
@@ -1,5 +1,7 @@
1
1
  # Copyright (c) Microsoft. All rights reserved.
2
2
 
3
+ import json
4
+ import logging
3
5
  import os
4
6
  from collections.abc import Sequence
5
7
  from typing import Any
@@ -13,6 +15,11 @@ from ..constants import (
13
15
  TENANT_ID_KEY,
14
16
  )
15
17
 
18
+ logger = logging.getLogger(__name__)
19
+
20
+ # Maximum allowed span size in bytes (250KB)
21
+ MAX_SPAN_SIZE_BYTES = 250 * 1024
22
+
16
23
 
17
24
  def hex_trace_id(value: int) -> str:
18
25
  # 128-bit -> 32 hex chars
@@ -46,6 +53,76 @@ def status_name(code: StatusCode) -> str:
46
53
  return str(code)
47
54
 
48
55
 
56
+ def truncate_span(span_dict: dict[str, Any]) -> dict[str, Any]:
57
+ """
58
+ Truncate span attributes if the serialized span exceeds MAX_SPAN_SIZE_BYTES.
59
+
60
+ Args:
61
+ span_dict: The span dictionary to potentially truncate
62
+
63
+ Returns:
64
+ The potentially truncated span dictionary
65
+ """
66
+ try:
67
+ # Serialize the span to check its size
68
+ serialized = json.dumps(span_dict, separators=(",", ":"))
69
+ current_size = len(serialized.encode("utf-8"))
70
+
71
+ if current_size <= MAX_SPAN_SIZE_BYTES:
72
+ return span_dict
73
+
74
+ logger.warning(
75
+ f"Span size ({current_size} bytes) exceeds limit ({MAX_SPAN_SIZE_BYTES} bytes). "
76
+ "Truncating large payload attributes."
77
+ )
78
+
79
+ # Create a deep copy to modify (shallow copy would still reference original attributes)
80
+ truncated_span = span_dict.copy()
81
+ if "attributes" in truncated_span:
82
+ truncated_span["attributes"] = truncated_span["attributes"].copy()
83
+ attributes = truncated_span.get("attributes", {})
84
+
85
+ # Track what was truncated for logging
86
+ truncated_keys = []
87
+
88
+ # Sort attributes by size (largest first) and truncate until size is acceptable
89
+ if attributes:
90
+ # Calculate size of each attribute value when serialized
91
+ attr_sizes = []
92
+ for key, value in attributes.items():
93
+ try:
94
+ value_size = len(json.dumps(value, separators=(",", ":")).encode("utf-8"))
95
+ attr_sizes.append((key, value_size))
96
+ except Exception:
97
+ # If we can't serialize the value, assume it's small
98
+ attr_sizes.append((key, 0))
99
+
100
+ # Sort by size (descending - largest first)
101
+ attr_sizes.sort(key=lambda x: x[1], reverse=True)
102
+
103
+ # Truncate largest attributes first until size is acceptable
104
+ for key, _ in attr_sizes:
105
+ if key in attributes:
106
+ attributes[key] = "TRUNCATED"
107
+ truncated_keys.append(key)
108
+
109
+ # Check size after truncation
110
+ serialized = json.dumps(truncated_span, separators=(",", ":"))
111
+ current_size = len(serialized.encode("utf-8"))
112
+
113
+ if current_size <= MAX_SPAN_SIZE_BYTES:
114
+ break
115
+
116
+ if truncated_keys:
117
+ logger.info(f"Truncated attributes: {', '.join(truncated_keys)}")
118
+
119
+ return truncated_span
120
+
121
+ except Exception as e:
122
+ logger.error(f"Error during span truncation: {e}")
123
+ return span_dict
124
+
125
+
49
126
  def partition_by_identity(
50
127
  spans: Sequence[ReadableSpan],
51
128
  ) -> dict[tuple[str, str], list[ReadableSpan]]:
@@ -3,6 +3,8 @@
3
3
 
4
4
  # Invoke agent scope for tracing agent invocation.
5
5
 
6
+ import logging
7
+
6
8
  from .agent_details import AgentDetails
7
9
  from .constants import (
8
10
  GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY,
@@ -10,6 +12,7 @@ from .constants import (
10
12
  GEN_AI_CALLER_AGENT_NAME_KEY,
11
13
  GEN_AI_CALLER_AGENT_TENANT_ID_KEY,
12
14
  GEN_AI_CALLER_AGENT_UPN_KEY,
15
+ GEN_AI_CALLER_AGENT_USER_CLIENT_IP,
13
16
  GEN_AI_CALLER_AGENT_USER_ID_KEY,
14
17
  GEN_AI_CALLER_ID_KEY,
15
18
  GEN_AI_CALLER_NAME_KEY,
@@ -17,7 +20,6 @@ from .constants import (
17
20
  GEN_AI_CALLER_UPN_KEY,
18
21
  GEN_AI_CALLER_USER_ID_KEY,
19
22
  GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY,
20
- GEN_AI_EXECUTION_SOURCE_ID_KEY,
21
23
  GEN_AI_EXECUTION_SOURCE_NAME_KEY,
22
24
  GEN_AI_EXECUTION_TYPE_KEY,
23
25
  GEN_AI_INPUT_MESSAGES_KEY,
@@ -32,7 +34,9 @@ from .models.caller_details import CallerDetails
32
34
  from .opentelemetry_scope import OpenTelemetryScope
33
35
  from .request import Request
34
36
  from .tenant_details import TenantDetails
35
- from .utils import safe_json_dumps
37
+ from .utils import safe_json_dumps, validate_and_normalize_ip
38
+
39
+ logger = logging.getLogger(__name__)
36
40
 
37
41
 
38
42
  class InvokeAgentScope(OpenTelemetryScope):
@@ -111,7 +115,6 @@ class InvokeAgentScope(OpenTelemetryScope):
111
115
  # Set request metadata if provided
112
116
  if request:
113
117
  if request.source_metadata:
114
- self.set_tag_maybe(GEN_AI_EXECUTION_SOURCE_ID_KEY, request.source_metadata.id)
115
118
  self.set_tag_maybe(GEN_AI_EXECUTION_SOURCE_NAME_KEY, request.source_metadata.name)
116
119
  self.set_tag_maybe(
117
120
  GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, request.source_metadata.description
@@ -121,6 +124,7 @@ class InvokeAgentScope(OpenTelemetryScope):
121
124
  GEN_AI_EXECUTION_TYPE_KEY,
122
125
  request.execution_type.value if request.execution_type else None,
123
126
  )
127
+ self.set_tag_maybe(GEN_AI_INPUT_MESSAGES_KEY, safe_json_dumps([request.content]))
124
128
 
125
129
  # Set caller details tags
126
130
  if caller_details:
@@ -140,6 +144,11 @@ class InvokeAgentScope(OpenTelemetryScope):
140
144
  self.set_tag_maybe(GEN_AI_CALLER_AGENT_USER_ID_KEY, caller_agent_details.agent_auid)
141
145
  self.set_tag_maybe(GEN_AI_CALLER_AGENT_UPN_KEY, caller_agent_details.agent_upn)
142
146
  self.set_tag_maybe(GEN_AI_CALLER_AGENT_TENANT_ID_KEY, caller_agent_details.tenant_id)
147
+ # Validate and set caller agent client IP
148
+ self.set_tag_maybe(
149
+ GEN_AI_CALLER_AGENT_USER_CLIENT_IP,
150
+ validate_and_normalize_ip(caller_agent_details.agent_client_ip),
151
+ )
143
152
 
144
153
  def record_response(self, response: str) -> None:
145
154
  """Record response information for telemetry tracking.
@@ -2,6 +2,7 @@
2
2
 
3
3
  # Per request baggage builder for OpenTelemetry context propagation.
4
4
 
5
+ import logging
5
6
  from typing import Any
6
7
 
7
8
  from opentelemetry import baggage, context
@@ -14,20 +15,26 @@ from ..constants import (
14
15
  GEN_AI_AGENT_ID_KEY,
15
16
  GEN_AI_AGENT_NAME_KEY,
16
17
  GEN_AI_AGENT_UPN_KEY,
18
+ GEN_AI_CALLER_CLIENT_IP_KEY,
17
19
  GEN_AI_CALLER_ID_KEY,
18
20
  GEN_AI_CALLER_NAME_KEY,
19
21
  GEN_AI_CALLER_UPN_KEY,
20
22
  GEN_AI_CONVERSATION_ID_KEY,
21
23
  GEN_AI_CONVERSATION_ITEM_LINK_KEY,
22
24
  GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY,
23
- GEN_AI_EXECUTION_SOURCE_ID_KEY,
24
25
  GEN_AI_EXECUTION_SOURCE_NAME_KEY,
25
26
  HIRING_MANAGER_ID_KEY,
26
27
  OPERATION_SOURCE_KEY,
28
+ SESSION_DESCRIPTION_KEY,
29
+ SESSION_ID_KEY,
27
30
  TENANT_ID_KEY,
28
31
  )
32
+ from ..models.operation_source import OperationSource
33
+ from ..utils import deprecated, validate_and_normalize_ip
29
34
  from .turn_context_baggage import from_turn_context
30
35
 
36
+ logger = logging.getLogger(__name__)
37
+
31
38
 
32
39
  class BaggageBuilder:
33
40
  """Per request baggage builder.
@@ -50,16 +57,18 @@ class BaggageBuilder:
50
57
  """Initialize the baggage builder."""
51
58
  self._pairs: dict[str, str] = {}
52
59
 
53
- def operation_source(self, value: str | None) -> "BaggageBuilder":
60
+ def operation_source(self, value: OperationSource | None) -> "BaggageBuilder":
54
61
  """Set the operation source baggage value.
55
62
 
56
63
  Args:
57
- value: The operation source value
64
+ value: The operation source enum value
58
65
 
59
66
  Returns:
60
67
  Self for method chaining
61
68
  """
62
- self._set(OPERATION_SOURCE_KEY, value)
69
+ # Convert enum to string value for baggage storage
70
+ str_value = value.value if value is not None else None
71
+ self._set(OPERATION_SOURCE_KEY, str_value)
63
72
  return self
64
73
 
65
74
  def tenant_id(self, value: str | None) -> "BaggageBuilder":
@@ -178,6 +187,11 @@ class BaggageBuilder:
178
187
  self._set(GEN_AI_CALLER_UPN_KEY, value)
179
188
  return self
180
189
 
190
+ def caller_client_ip(self, value: str | None) -> "BaggageBuilder":
191
+ """Set the caller client IP baggage value."""
192
+ self._set(GEN_AI_CALLER_CLIENT_IP_KEY, validate_and_normalize_ip(value))
193
+ return self
194
+
181
195
  def conversation_id(self, value: str | None) -> "BaggageBuilder":
182
196
  """Set the conversation ID baggage value."""
183
197
  self._set(GEN_AI_CONVERSATION_ID_KEY, value)
@@ -188,18 +202,33 @@ class BaggageBuilder:
188
202
  self._set(GEN_AI_CONVERSATION_ITEM_LINK_KEY, value)
189
203
  return self
190
204
 
191
- def source_metadata_id(self, value: str | None) -> "BaggageBuilder":
192
- """Set the execution source metadata ID (e.g., channel ID)."""
193
- self._set(GEN_AI_EXECUTION_SOURCE_ID_KEY, value)
194
- return self
195
-
205
+ @deprecated("Use channel_name() instead")
196
206
  def source_metadata_name(self, value: str | None) -> "BaggageBuilder":
197
207
  """Set the execution source metadata name (e.g., channel name)."""
198
- self._set(GEN_AI_EXECUTION_SOURCE_NAME_KEY, value)
199
- return self
208
+ return self.channel_name(value)
200
209
 
210
+ @deprecated("Use channel_links() instead")
201
211
  def source_metadata_description(self, value: str | None) -> "BaggageBuilder":
202
212
  """Set the execution source metadata description (e.g., channel description)."""
213
+ return self.channel_links(value)
214
+
215
+ def session_id(self, value: str | None) -> "BaggageBuilder":
216
+ """Set the session ID baggage value."""
217
+ self._set(SESSION_ID_KEY, value)
218
+ return self
219
+
220
+ def session_description(self, value: str | None) -> "BaggageBuilder":
221
+ """Set the session description baggage value."""
222
+ self._set(SESSION_DESCRIPTION_KEY, value)
223
+ return self
224
+
225
+ def channel_name(self, value: str | None) -> "BaggageBuilder":
226
+ """Sets the channel name baggage value (e.g., 'Teams', 'msteams')."""
227
+ self._set(GEN_AI_EXECUTION_SOURCE_NAME_KEY, value)
228
+ return self
229
+
230
+ def channel_links(self, value: str | None) -> "BaggageBuilder":
231
+ """Sets the channel link baggage value. (e.g., channel links or description)."""
203
232
  self._set(GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, value)
204
233
  return self
205
234
 
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
- from typing import Any, Iterable, Iterator, Mapping
4
+ from typing import Any, Iterator, Mapping
5
5
 
6
6
  from ..constants import (
7
7
  GEN_AI_AGENT_AUID_KEY,
@@ -17,7 +17,6 @@ from ..constants import (
17
17
  GEN_AI_CONVERSATION_ID_KEY,
18
18
  GEN_AI_CONVERSATION_ITEM_LINK_KEY,
19
19
  GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY,
20
- GEN_AI_EXECUTION_SOURCE_ID_KEY,
21
20
  GEN_AI_EXECUTION_SOURCE_NAME_KEY,
22
21
  GEN_AI_EXECUTION_TYPE_KEY,
23
22
  TENANT_ID_KEY,
@@ -26,9 +25,6 @@ from ..execution_type import ExecutionType
26
25
 
27
26
  AGENT_ROLE = "agenticUser"
28
27
  CHANNEL_ID_AGENTS = "agents"
29
- ENTITY_TYPE_WPX_COMMENT = "wpxcomment"
30
- ENTITY_TYPE_EMAIL_NOTIFICATION = "emailNotification"
31
- WPX_CONVERSATION_ID_FORMAT = "{document_id}_{parent_comment_id}"
32
28
 
33
29
 
34
30
  def _safe_get(obj: Any, *names: str) -> Any:
@@ -135,37 +131,40 @@ def _iter_tenant_id_pair(activity: Any) -> Iterator[tuple[str, Any]]:
135
131
 
136
132
 
137
133
  def _iter_source_metadata_pairs(activity: Any) -> Iterator[tuple[str, Any]]:
134
+ """
135
+ Generate source metadata pairs from activity, handling both string and ChannelId object cases.
136
+
137
+ :param activity: The activity object (Activity instance or dict)
138
+ :return: Iterator of (key, value) tuples for source metadata
139
+ """
140
+ # Handle channel_id (can be string or ChannelId object)
138
141
  channel_id = _safe_get(activity, "channel_id")
139
- yield GEN_AI_EXECUTION_SOURCE_ID_KEY, channel_id
140
- yield GEN_AI_EXECUTION_SOURCE_NAME_KEY, channel_id
141
- yield GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, _safe_get(activity, "type", "Type")
142
+
143
+ # Extract channel name from either string or ChannelId object
144
+ channel_name = None
145
+ sub_channel = None
146
+
147
+ if channel_id is not None:
148
+ if isinstance(channel_id, str):
149
+ # Direct string value
150
+ channel_name = channel_id
151
+ elif hasattr(channel_id, "channel"):
152
+ # ChannelId object
153
+ channel_name = channel_id.channel
154
+ sub_channel = getattr(channel_id, "sub_channel", None)
155
+ elif isinstance(channel_id, dict):
156
+ # Serialized ChannelId as dict
157
+ channel_name = channel_id.get("channel")
158
+ sub_channel = channel_id.get("sub_channel")
159
+
160
+ # Yield channel name as source name
161
+ yield GEN_AI_EXECUTION_SOURCE_NAME_KEY, channel_name
162
+ yield GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, sub_channel
142
163
 
143
164
 
144
165
  def _iter_conversation_pairs(activity: Any) -> Iterator[tuple[str, Any]]:
145
- channel_id = _safe_get(activity, "channel_id")
146
- entities = _safe_get(activity, "entities") or []
147
- conversation_id = None
148
-
149
- if channel_id == CHANNEL_ID_AGENTS and isinstance(entities, Iterable):
150
- # search entities for wpxcomment or emailNotification
151
- for e in entities:
152
- etype = _safe_get(e, "type", "Type")
153
- if etype == ENTITY_TYPE_WPX_COMMENT:
154
- document_id = _safe_get(e, "documentId", "document_id")
155
- parent_comment_id = _safe_get(e, "parentCommentId", "parent_comment_id")
156
- if document_id and parent_comment_id:
157
- conversation_id = WPX_CONVERSATION_ID_FORMAT.format(
158
- document_id=document_id,
159
- parent_comment_id=parent_comment_id,
160
- )
161
- break
162
- elif etype == ENTITY_TYPE_EMAIL_NOTIFICATION:
163
- conversation_id = _safe_get(e, "conversationId", "conversation_id")
164
- if conversation_id:
165
- break
166
- if not conversation_id:
167
- conv = _safe_get(activity, "conversation")
168
- conversation_id = _safe_get(conv, "id", "Id")
166
+ conv = _safe_get(activity, "conversation")
167
+ conversation_id = _safe_get(conv, "id")
169
168
 
170
169
  item_link = _safe_get(activity, "service_url")
171
170
 
@@ -0,0 +1,21 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+
4
+ """Operation source enumeration for Agent365 SDK."""
5
+
6
+ from enum import Enum
7
+
8
+
9
+ class OperationSource(Enum):
10
+ """
11
+ Enumeration representing the source of an operation.
12
+ """
13
+
14
+ SDK = "SDK"
15
+ """Operation executed by SDK."""
16
+
17
+ GATEWAY = "Gateway"
18
+ """Operation executed by Gateway."""
19
+
20
+ MCP_SERVER = "MCPServer"
21
+ """Operation executed by MCP Server."""
@@ -16,4 +16,3 @@ class Request:
16
16
  execution_type: ExecutionType
17
17
  session_id: str | None = None
18
18
  source_metadata: SourceMetadata | None = None
19
- payload: str | None = None
@@ -16,7 +16,8 @@ For every new span:
16
16
  from opentelemetry import baggage, context
17
17
  from opentelemetry.sdk.trace import SpanProcessor as BaseSpanProcessor
18
18
 
19
- from ..constants import GEN_AI_OPERATION_NAME_KEY, INVOKE_AGENT_OPERATION_NAME
19
+ from ..constants import GEN_AI_OPERATION_NAME_KEY, INVOKE_AGENT_OPERATION_NAME, OPERATION_SOURCE_KEY
20
+ from ..models.operation_source import OperationSource
20
21
  from .util import COMMON_ATTRIBUTES, INVOKE_AGENT_ATTRIBUTES
21
22
 
22
23
 
@@ -41,6 +42,14 @@ class SpanProcessor(BaseSpanProcessor):
41
42
  except Exception:
42
43
  baggage_map = {}
43
44
 
45
+ # Set operation source - coalesce baggage value with SDK default
46
+ if OPERATION_SOURCE_KEY not in existing:
47
+ operation_source = baggage_map.get(OPERATION_SOURCE_KEY) or OperationSource.SDK.value
48
+ try:
49
+ span.set_attribute(OPERATION_SOURCE_KEY, operation_source)
50
+ except Exception:
51
+ pass
52
+
44
53
  operation_name = existing.get(GEN_AI_OPERATION_NAME_KEY)
45
54
  is_invoke_agent = False
46
55
  if operation_name == INVOKE_AGENT_OPERATION_NAME:
@@ -19,6 +19,13 @@ COMMON_ATTRIBUTES = [
19
19
  consts.GEN_AI_AGENT_BLUEPRINT_ID_KEY, # gen_ai.agent.applicationid
20
20
  consts.GEN_AI_AGENT_AUID_KEY,
21
21
  consts.GEN_AI_AGENT_TYPE_KEY,
22
+ consts.OPERATION_SOURCE_KEY, # operation.source
23
+ consts.SESSION_ID_KEY,
24
+ consts.SESSION_DESCRIPTION_KEY,
25
+ consts.HIRING_MANAGER_ID_KEY,
26
+ # Execution context
27
+ consts.GEN_AI_EXECUTION_SOURCE_NAME_KEY, # gen_ai.channel.name
28
+ consts.GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, # gen_ai.channel.link
22
29
  ]
23
30
 
24
31
  # Invoke Agent–specific attributes
@@ -36,9 +43,5 @@ INVOKE_AGENT_ATTRIBUTES = [
36
43
  consts.GEN_AI_CALLER_AGENT_UPN_KEY, # gen_ai.caller.agent.upn
37
44
  consts.GEN_AI_CALLER_AGENT_TENANT_ID_KEY, # gen_ai.caller.agent.tenantid
38
45
  consts.GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY, # gen_ai.caller.agent.applicationid
39
- # Execution context
40
46
  consts.GEN_AI_EXECUTION_TYPE_KEY, # gen_ai.execution.type
41
- consts.GEN_AI_EXECUTION_SOURCE_ID_KEY, # gen_ai.execution.sourceMetadata.id
42
- consts.GEN_AI_EXECUTION_SOURCE_NAME_KEY, # gen_ai.execution.sourceMetadata.name
43
- consts.GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY, # gen_ai.execution.sourceMetadata.description
44
47
  ]
@@ -1,15 +1,22 @@
1
- # Copyright (c) Microsoft. All rights reserved.
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
2
3
 
3
4
  import datetime
5
+ import functools
4
6
  import json
5
7
  import logging
6
8
  import traceback
9
+ import warnings
7
10
  from collections.abc import Callable, Hashable, Iterable, Iterator, Mapping
8
11
  from enum import Enum
12
+ from ipaddress import AddressValueError, ip_address
9
13
  from threading import RLock
10
14
  from typing import Any, Generic, TypeVar, cast
11
15
 
12
- from opentelemetry.semconv.trace import SpanAttributes as OTELSpanAttributes
16
+ from opentelemetry.semconv.attributes.exception_attributes import (
17
+ EXCEPTION_MESSAGE,
18
+ EXCEPTION_STACKTRACE,
19
+ )
13
20
  from opentelemetry.trace import Span
14
21
  from opentelemetry.util.types import AttributeValue
15
22
  from wrapt import ObjectProxy
@@ -69,10 +76,10 @@ def record_exception(span: Span, error: BaseException) -> None:
69
76
  exception_message = repr(error)
70
77
  attributes: dict[str, AttributeValue] = {
71
78
  ERROR_TYPE_KEY: exception_type,
72
- OTELSpanAttributes.EXCEPTION_MESSAGE: exception_message,
79
+ EXCEPTION_MESSAGE: exception_message,
73
80
  }
74
81
  try:
75
- attributes[OTELSpanAttributes.EXCEPTION_STACKTRACE] = traceback.format_exc()
82
+ attributes[EXCEPTION_STACKTRACE] = traceback.format_exc()
76
83
  except Exception:
77
84
  logger.exception("Failed to record exception stacktrace.")
78
85
  span.add_event(name="exception", attributes=attributes)
@@ -149,3 +156,45 @@ def extract_model_name(span_name: str) -> str | None:
149
156
  return model_name.strip()
150
157
 
151
158
  return None
159
+
160
+
161
+ def deprecated(reason: str):
162
+ """Decorator to mark functions as deprecated."""
163
+
164
+ def decorator(func):
165
+ @functools.wraps(func)
166
+ def wrapper(*args, **kwargs):
167
+ warnings.warn(
168
+ f"{func.__name__}() is deprecated. {reason}",
169
+ category=DeprecationWarning,
170
+ stacklevel=2,
171
+ )
172
+ return func(*args, **kwargs)
173
+
174
+ return wrapper
175
+
176
+ return decorator
177
+
178
+
179
+ def validate_and_normalize_ip(ip_string: str | None) -> str | None:
180
+ """Validate and normalize an IP address string.
181
+
182
+ Args:
183
+ ip_string: The IP address string to validate (IPv4 or IPv6)
184
+
185
+ Returns:
186
+ The normalized IP address string if valid, None if invalid or None input
187
+
188
+ Logs:
189
+ Error message if the IP address is invalid
190
+ """
191
+ if ip_string is None:
192
+ return None
193
+
194
+ try:
195
+ # Validate and normalize IP address
196
+ ip_obj = ip_address(ip_string)
197
+ return str(ip_obj)
198
+ except (ValueError, AddressValueError):
199
+ logger.error(f"Invalid IP address: '{ip_string}'")
200
+ return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: microsoft-agents-a365-observability-core
3
- Version: 0.1.0
3
+ Version: 0.2.0.dev5
4
4
  Summary: Telemetry, tracing, and monitoring components for AI agents
5
5
  Author-email: Microsoft <support@microsoft.com>
6
6
  License: MIT
@@ -64,7 +64,7 @@ For usage examples and detailed documentation, see the [Observability documentat
64
64
  For issues, questions, or feedback:
65
65
 
66
66
  - File issues in the [GitHub Issues](https://github.com/microsoft/Agent365-python/issues) section
67
- - See the [main documentation](../../../README.md) for more information
67
+ - See the [main documentation](../../README.md) for more information
68
68
 
69
69
  ## Trademarks
70
70
 
@@ -74,5 +74,5 @@ For issues, questions, or feedback:
74
74
 
75
75
  Copyright (c) Microsoft Corporation. All rights reserved.
76
76
 
77
- Licensed under the MIT License - see the [LICENSE](../../../LICENSE.md) file for details.
77
+ Licensed under the MIT License - see the [LICENSE](../../LICENSE.md) file for details.
78
78
 
@@ -1,33 +1,35 @@
1
1
  microsoft_agents_a365/observability/core/__init__.py,sha256=1-Jmokc2cQv7p_RB7W22ZEdqQ0Doq8yDENx_Yews2p8,1737
2
- microsoft_agents_a365/observability/core/agent_details.py,sha256=U0xgrtrnNXrI8yjmBk7teXuMCvIIk0zWEOti_RvJP1o,1147
3
- microsoft_agents_a365/observability/core/config.py,sha256=-sCfRUQNSk2XHnh-Ez1DSVrXS2vHr_2YD-lZ8byyFjk,8828
4
- microsoft_agents_a365/observability/core/constants.py,sha256=K__ga_vnsqrOIVzPtW4gWeu8xLJnAxJdkmbMzYYwPXY,4614
2
+ microsoft_agents_a365/observability/core/agent_details.py,sha256=7x4tqcocig1hz4T6eCRZy7fSF2gOCpM0pvE6Q3Txww0,1210
3
+ microsoft_agents_a365/observability/core/config.py,sha256=BK1Yg2ykt5yYUBmA-z_wKy2H4k8UB6InF4C5Xh8ZM-g,10996
4
+ microsoft_agents_a365/observability/core/constants.py,sha256=sypi1CXrTX5lX-19qQTq6VL6CZ691w5-gFVh5W6OpbY,4722
5
5
  microsoft_agents_a365/observability/core/execute_tool_scope.py,sha256=RcibM_Q-lEhSOpAoRioDao-HiC72W741-SCbcPDWqyI,2989
6
6
  microsoft_agents_a365/observability/core/execution_type.py,sha256=Qsf-7_7DzZnRQZ5NHYU31ggfxAjPwneNnda1hOodmG0,303
7
7
  microsoft_agents_a365/observability/core/inference_call_details.py,sha256=V1-KQPNq0V36J91ptX96UNRcOW6cy-AV5eIlwbzsrSg,483
8
8
  microsoft_agents_a365/observability/core/inference_operation_type.py,sha256=ZLdDdYC0V_0Sff_aTSaiIRVB4a9wEduS7EXpYlme6_M,273
9
9
  microsoft_agents_a365/observability/core/inference_scope.py,sha256=87r367uQ4qVp8NpR0na-uQScRR5ID1FlP6c3cmIxvMc,4973
10
10
  microsoft_agents_a365/observability/core/invoke_agent_details.py,sha256=PEwkJq-nUz8sxLsbz44hIydGHiO2tHj-sVBkMpZXC1A,389
11
- microsoft_agents_a365/observability/core/invoke_agent_scope.py,sha256=NJSeKeNmvutn_MZARIGSg80n7HxTW3BazqyXw-9LFlY,6600
11
+ microsoft_agents_a365/observability/core/invoke_agent_scope.py,sha256=A2syvvT9lUE1x7M1Ns7DuXY0J4STMiOBe7kGmPpyLmQ,6917
12
12
  microsoft_agents_a365/observability/core/opentelemetry_scope.py,sha256=I8WQoQkCygC7jZRrP2aHsJScGqQKDVktLlRGxApfiZo,9734
13
- microsoft_agents_a365/observability/core/request.py,sha256=fcqsmxPl0BMITwbvy5lK5DkDm5YjIXKIwlJTclcdMCs,430
13
+ microsoft_agents_a365/observability/core/request.py,sha256=x9kRlUbbaK0btsJ8Ym-Ctp5jgsiy54b3eMPst7Olirk,399
14
14
  microsoft_agents_a365/observability/core/source_metadata.py,sha256=ijMIiJGvfepDnBqSuZbGsjvrTivz3f_Ub1azyZsCeMA,321
15
15
  microsoft_agents_a365/observability/core/tenant_details.py,sha256=eC5S9Zq5sJrpGFWJLKBXVlj-OipysPSoLKSl-8I5YY0,218
16
16
  microsoft_agents_a365/observability/core/tool_call_details.py,sha256=sPDW5yvey9_itpKxoo76pwoncOZyhONt9fy3cDdSYyY,455
17
17
  microsoft_agents_a365/observability/core/tool_type.py,sha256=O9snmopwdhBjK5eqwyQU3aSdLZzX88c1UMi7oW7lFHg,271
18
- microsoft_agents_a365/observability/core/utils.py,sha256=PkoB6J65HhRbiFU2uz_WMn5XHLiRbtvPWzkMWe5Vkos,4850
19
- microsoft_agents_a365/observability/core/exporters/agent365_exporter.py,sha256=j_u0fykKK7BY0sg1PsEFE4ABoU4HPwEJMjeZ_KZ1fhk,11386
20
- microsoft_agents_a365/observability/core/exporters/utils.py,sha256=6G09FlUmtpq57415QQal22AMJQi21IZqBE8_4No_Qfg,1877
18
+ microsoft_agents_a365/observability/core/utils.py,sha256=fnZBCEsD-1FlxpJ5hcbwovrEK4baeSKOyKJADSD3n3Q,6063
19
+ microsoft_agents_a365/observability/core/exporters/agent365_exporter.py,sha256=lhicYBnuoDL9RrvGOADEefKV8JCoJd9ChcFn5XS8YjM,11488
20
+ microsoft_agents_a365/observability/core/exporters/agent365_exporter_options.py,sha256=f-p11ZQfxtDIiK-kVPDfebBgT83onxdE29IQryYoAPk,1648
21
+ microsoft_agents_a365/observability/core/exporters/utils.py,sha256=VHhZOELtZtsNEFYcCA-rb7JNX9_aDcJ9XC6OhVIBjl8,4646
21
22
  microsoft_agents_a365/observability/core/middleware/__init__.py,sha256=v_vLyv0BQOW9pAvuUeL6Q0ZQ3Ji4MdYYcFDfP7CqvMk,177
22
- microsoft_agents_a365/observability/core/middleware/baggage_builder.py,sha256=RCdaOuXO6T_5zFP1K_xukTuibee2CIIQmYuvvFjXtNw,9538
23
- microsoft_agents_a365/observability/core/middleware/turn_context_baggage.py,sha256=6IuS8SLbM-W-G5FgHncBurXi9fF-hRaFHjI78Bh3qsI,6698
23
+ microsoft_agents_a365/observability/core/middleware/baggage_builder.py,sha256=F1HcLKEA4-h1tjfp0AgXMqIsEn58waP6nvyChvo-cAI,10752
24
+ microsoft_agents_a365/observability/core/middleware/turn_context_baggage.py,sha256=teDtlB38zKKdTynYaV9egI3G8wShgCw4KSPxzukSrmQ,6326
24
25
  microsoft_agents_a365/observability/core/models/__init__.py,sha256=aCOr6sEsQpv9z4cJgWFA4qOs4xJqclqYYnxOVcxiK2Q,75
25
26
  microsoft_agents_a365/observability/core/models/agent_type.py,sha256=ZazwwMAQRrYzzN3Ytz69F6thV4R6nemA6JrK-70fyt0,560
26
27
  microsoft_agents_a365/observability/core/models/caller_details.py,sha256=8oaRKeGNteZq_RAQShhfUBs0iO-Sr9yjfk13Mv-xSjA,674
28
+ microsoft_agents_a365/observability/core/models/operation_source.py,sha256=HJp-SNR-lNSPIgoLlF6DMMlijtu4ZGhnqSyu58lIgnk,448
27
29
  microsoft_agents_a365/observability/core/trace_processor/__init__.py,sha256=GML9CA7ssJPngxpGFPdVlp-0NmhgS4Ke9IGW5vm5Gys,194
28
- microsoft_agents_a365/observability/core/trace_processor/span_processor.py,sha256=9Sou0m1huEncNcGdU-ThE8b9uPwHosZNSPdldOUdlkU,2513
29
- microsoft_agents_a365/observability/core/trace_processor/util.py,sha256=-jM4ei60f5HKEDAVvBlZiTmhMhq8hVvvt4SwGgEnPuI,2239
30
- microsoft_agents_a365_observability_core-0.1.0.dist-info/METADATA,sha256=MBThT80LmnRbrnUkb90Wyvau2hXUlTu2KilAapZTd9I,3872
31
- microsoft_agents_a365_observability_core-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
32
- microsoft_agents_a365_observability_core-0.1.0.dist-info/top_level.txt,sha256=G3c2_4sy5_EM_BWO67SbK2tKj4G8XFn-QXRbh8g9Lgk,22
33
- microsoft_agents_a365_observability_core-0.1.0.dist-info/RECORD,,
30
+ microsoft_agents_a365/observability/core/trace_processor/span_processor.py,sha256=4gEvm8gcf7_A-Fty-InBc2wJG80WCvn8Sh7GKBuFYzU,2953
31
+ microsoft_agents_a365/observability/core/trace_processor/util.py,sha256=xoBZ7qbucZGYJCKgKylaUrl0tP34bcd34fgmh8sXZ_E,2270
32
+ microsoft_agents_a365_observability_core-0.2.0.dev5.dist-info/METADATA,sha256=OGU05eHdSN_0kiUd9CM2xJqE6iI9XOZqBe5mMcNln3k,3871
33
+ microsoft_agents_a365_observability_core-0.2.0.dev5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
+ microsoft_agents_a365_observability_core-0.2.0.dev5.dist-info/top_level.txt,sha256=G3c2_4sy5_EM_BWO67SbK2tKj4G8XFn-QXRbh8g9Lgk,22
35
+ microsoft_agents_a365_observability_core-0.2.0.dev5.dist-info/RECORD,,