microsoft-agents-a365-observability-core 0.1.0.dev30__tar.gz → 0.2.1.dev0__tar.gz

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 (43) hide show
  1. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/PKG-INFO +5 -3
  2. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/README.md +2 -2
  3. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365/observability/core/__init__.py +2 -1
  4. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365/observability/core/agent_details.py +7 -4
  5. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365/observability/core/config.py +71 -26
  6. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365/observability/core/constants.py +5 -2
  7. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365/observability/core/execute_tool_scope.py +15 -1
  8. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365/observability/core/execution_type.py +2 -1
  9. microsoft_agents_a365_observability_core-0.2.1.dev0/microsoft_agents_a365/observability/core/exporters/__init__.py +8 -0
  10. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365/observability/core/exporters/agent365_exporter.py +55 -7
  11. microsoft_agents_a365_observability_core-0.2.1.dev0/microsoft_agents_a365/observability/core/exporters/agent365_exporter_options.py +39 -0
  12. microsoft_agents_a365_observability_core-0.2.1.dev0/microsoft_agents_a365/observability/core/exporters/utils.py +204 -0
  13. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365/observability/core/inference_call_details.py +2 -1
  14. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365/observability/core/inference_operation_type.py +2 -1
  15. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365/observability/core/inference_scope.py +9 -0
  16. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365/observability/core/invoke_agent_details.py +2 -1
  17. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365/observability/core/invoke_agent_scope.py +12 -3
  18. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365/observability/core/middleware/__init__.py +2 -1
  19. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365/observability/core/middleware/baggage_builder.py +52 -28
  20. microsoft_agents_a365_observability_core-0.2.1.dev0/microsoft_agents_a365/observability/core/models/operation_source.py +21 -0
  21. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365/observability/core/opentelemetry_scope.py +2 -1
  22. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365/observability/core/request.py +2 -2
  23. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365/observability/core/source_metadata.py +2 -1
  24. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365/observability/core/tenant_details.py +2 -1
  25. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365/observability/core/tool_call_details.py +2 -1
  26. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365/observability/core/tool_type.py +2 -1
  27. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365/observability/core/trace_processor/__init__.py +2 -1
  28. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365/observability/core/trace_processor/span_processor.py +13 -3
  29. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365/observability/core/trace_processor/util.py +10 -5
  30. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365/observability/core/utils.py +53 -4
  31. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365_observability_core.egg-info/PKG-INFO +5 -3
  32. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365_observability_core.egg-info/SOURCES.txt +6 -1
  33. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365_observability_core.egg-info/top_level.txt +1 -0
  34. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/pyproject.toml +4 -0
  35. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/setup.py +1 -1
  36. microsoft_agents_a365_observability_core-0.1.0.dev30/microsoft_agents_a365/observability/core/exporters/utils.py +0 -72
  37. microsoft_agents_a365_observability_core-0.1.0.dev30/microsoft_agents_a365/observability/core/middleware/turn_context_baggage.py +0 -193
  38. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365/observability/core/models/__init__.py +0 -0
  39. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365/observability/core/models/agent_type.py +0 -0
  40. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365/observability/core/models/caller_details.py +0 -0
  41. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365_observability_core.egg-info/dependency_links.txt +0 -0
  42. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/microsoft_agents_a365_observability_core.egg-info/requires.txt +0 -0
  43. {microsoft_agents_a365_observability_core-0.1.0.dev30 → microsoft_agents_a365_observability_core-0.2.1.dev0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: microsoft-agents-a365-observability-core
3
- Version: 0.1.0.dev30
3
+ Version: 0.2.1.dev0
4
4
  Summary: Telemetry, tracing, and monitoring components for AI agents
5
5
  Author-email: Microsoft <support@microsoft.com>
6
6
  License: MIT
@@ -20,6 +20,7 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
20
  Classifier: Topic :: System :: Monitoring
21
21
  Requires-Python: >=3.11
22
22
  Description-Content-Type: text/markdown
23
+ License-File: LICENSE
23
24
  Requires-Dist: opentelemetry-api>=1.36.0
24
25
  Requires-Dist: opentelemetry-sdk>=1.36.0
25
26
  Requires-Dist: opentelemetry-exporter-otlp>=1.36.0
@@ -41,6 +42,7 @@ Requires-Dist: mypy>=1.0.0; extra == "dev"
41
42
  Provides-Extra: test
42
43
  Requires-Dist: pytest>=7.0.0; extra == "test"
43
44
  Requires-Dist: pytest-asyncio>=0.21.0; extra == "test"
45
+ Dynamic: license-file
44
46
 
45
47
  # microsoft-agents-a365-observability-core
46
48
 
@@ -64,7 +66,7 @@ For usage examples and detailed documentation, see the [Observability documentat
64
66
  For issues, questions, or feedback:
65
67
 
66
68
  - File issues in the [GitHub Issues](https://github.com/microsoft/Agent365-python/issues) section
67
- - See the [main documentation](../../../README.md) for more information
69
+ - See the [main documentation](../../README.md) for more information
68
70
 
69
71
  ## Trademarks
70
72
 
@@ -74,5 +76,5 @@ For issues, questions, or feedback:
74
76
 
75
77
  Copyright (c) Microsoft Corporation. All rights reserved.
76
78
 
77
- Licensed under the MIT License - see the [LICENSE](../../../LICENSE.md) file for details.
79
+ Licensed under the MIT License - see the [LICENSE](../../LICENSE.md) file for details.
78
80
 
@@ -20,7 +20,7 @@ For usage examples and detailed documentation, see the [Observability documentat
20
20
  For issues, questions, or feedback:
21
21
 
22
22
  - File issues in the [GitHub Issues](https://github.com/microsoft/Agent365-python/issues) section
23
- - See the [main documentation](../../../README.md) for more information
23
+ - See the [main documentation](../../README.md) for more information
24
24
 
25
25
  ## Trademarks
26
26
 
@@ -30,5 +30,5 @@ For issues, questions, or feedback:
30
30
 
31
31
  Copyright (c) Microsoft Corporation. All rights reserved.
32
32
 
33
- Licensed under the MIT License - see the [LICENSE](../../../LICENSE.md) file for details.
33
+ Licensed under the MIT License - see the [LICENSE](../../LICENSE.md) file for details.
34
34
 
@@ -1,4 +1,5 @@
1
- # Copyright (c) Microsoft. All rights reserved.
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
2
3
 
3
4
  # Microsoft Agent 365 Python SDK for OpenTelemetry tracing.
4
5
 
@@ -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."""
@@ -1,4 +1,5 @@
1
- # Copyright (c) Microsoft. All rights reserved.
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
2
3
 
3
4
  import logging
4
5
  import threading
@@ -10,7 +11,8 @@ from opentelemetry.sdk.resources import SERVICE_NAME, SERVICE_NAMESPACE, Resourc
10
11
  from opentelemetry.sdk.trace import TracerProvider
11
12
  from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter
12
13
 
13
- from .exporters.agent365_exporter import Agent365Exporter
14
+ from .exporters.agent365_exporter import _Agent365Exporter
15
+ from .exporters.agent365_exporter_options import Agent365ExporterOptions
14
16
  from .exporters.utils import is_agent365_exporter_enabled
15
17
  from .trace_processor.span_processor import SpanProcessor
16
18
 
@@ -51,6 +53,8 @@ class TelemetryManager:
51
53
  logger_name: str = DEFAULT_LOGGER_NAME,
52
54
  token_resolver: Callable[[str, str], str | None] | None = None,
53
55
  cluster_category: str = "prod",
56
+ exporter_options: Optional[Agent365ExporterOptions] = None,
57
+ suppress_invoke_agent_input: bool = False,
54
58
  **kwargs: Any,
55
59
  ) -> bool:
56
60
  """
@@ -59,8 +63,13 @@ class TelemetryManager:
59
63
  :param service_name: The name of the service.
60
64
  :param service_namespace: The namespace of the service.
61
65
  :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").
66
+ :param token_resolver: (Deprecated) Callable that returns an auth token for a given agent + tenant.
67
+ Use exporter_options instead.
68
+ :param cluster_category: (Deprecated) Environment / cluster category (e.g. "prod").
69
+ Use exporter_options instead.
70
+ :param exporter_options: Agent365ExporterOptions instance for configuring the exporter.
71
+ 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.
72
+ :param suppress_invoke_agent_input: If True, suppress input messages for spans that are children of InvokeAgent spans.
64
73
  :return: True if configuration succeeded, False otherwise.
65
74
  """
66
75
  try:
@@ -71,6 +80,8 @@ class TelemetryManager:
71
80
  logger_name,
72
81
  token_resolver,
73
82
  cluster_category,
83
+ exporter_options,
84
+ suppress_invoke_agent_input,
74
85
  **kwargs,
75
86
  )
76
87
  except Exception as e:
@@ -84,10 +95,19 @@ class TelemetryManager:
84
95
  logger_name: str,
85
96
  token_resolver: Callable[[str, str], str | None] | None = None,
86
97
  cluster_category: str = "prod",
98
+ exporter_options: Optional[Agent365ExporterOptions] = None,
99
+ suppress_invoke_agent_input: bool = False,
87
100
  **kwargs: Any,
88
101
  ) -> bool:
89
102
  """Internal configuration method - not thread-safe, must be called with lock."""
90
103
 
104
+ # Check if a365 observability is already configured
105
+ if self._tracer_provider is not None:
106
+ self._logger.warning(
107
+ "a365 observability already configured. Ignoring repeated configure() call."
108
+ )
109
+ return True
110
+
91
111
  # Create resource with service information
92
112
  resource = Resource.create(
93
113
  {
@@ -96,30 +116,47 @@ class TelemetryManager:
96
116
  }
97
117
  )
98
118
 
99
- # Get existing tracer provider or create new one
100
- try:
101
- tracer_provider = trace.get_tracer_provider()
102
- # Check if it's already configured
103
- if hasattr(tracer_provider, "resource") and tracer_provider.resource:
104
- # Already configured, just add our span processor
105
- agent_processor = SpanProcessor()
106
- tracer_provider.add_span_processor(agent_processor)
107
- self._tracer_provider = tracer_provider
108
- self._span_processors["agent"] = agent_processor
109
- return True
110
- except Exception:
111
- pass
112
-
113
- # Configure tracer provider
114
- tracer_provider = TracerProvider(resource=resource)
115
- trace.set_tracer_provider(tracer_provider)
119
+ # Check if there's an existing TracerProvider (from app's OTEL setup)
120
+ tracer_provider = trace.get_tracer_provider()
121
+
122
+ # Determine if we should use existing provider or create new one
123
+ # Check if it's a real TracerProvider with a resource (not a proxy/no-op)
124
+ if getattr(tracer_provider, "resource", None):
125
+ # Use existing provider from application's OTEL setup
126
+ self._logger.info(
127
+ "Detected existing TracerProvider with resource. "
128
+ "Adding a365 observability processors to it."
129
+ )
130
+ else:
131
+ # Create new TracerProvider with our resource
132
+ self._logger.info("Creating new TracerProvider for a365 observability.")
133
+ tracer_provider = TracerProvider(resource=resource)
134
+ trace.set_tracer_provider(tracer_provider)
135
+
136
+ # Store reference
116
137
  self._tracer_provider = tracer_provider
117
138
 
118
- if is_agent365_exporter_enabled() and token_resolver is not None:
119
- exporter = Agent365Exporter(
120
- token_resolver=token_resolver,
139
+ # Use exporter_options if provided, otherwise create default options with legacy parameters
140
+ if exporter_options is None:
141
+ exporter_options = Agent365ExporterOptions(
121
142
  cluster_category=cluster_category,
122
- **kwargs,
143
+ token_resolver=token_resolver,
144
+ )
145
+
146
+ # Extract configuration for BatchSpanProcessor
147
+ batch_processor_kwargs = {
148
+ "max_queue_size": exporter_options.max_queue_size,
149
+ "schedule_delay_millis": exporter_options.scheduled_delay_ms,
150
+ "export_timeout_millis": exporter_options.exporter_timeout_ms,
151
+ "max_export_batch_size": exporter_options.max_export_batch_size,
152
+ }
153
+
154
+ if is_agent365_exporter_enabled() and exporter_options.token_resolver is not None:
155
+ exporter = _Agent365Exporter(
156
+ token_resolver=exporter_options.token_resolver,
157
+ cluster_category=exporter_options.cluster_category,
158
+ use_s2s_endpoint=exporter_options.use_s2s_endpoint,
159
+ suppress_invoke_agent_input=suppress_invoke_agent_input,
123
160
  )
124
161
  else:
125
162
  exporter = ConsoleSpanExporter()
@@ -130,7 +167,7 @@ class TelemetryManager:
130
167
  # Add span processors
131
168
 
132
169
  # Create BatchSpanProcessor with optimized settings
133
- batch_processor = BatchSpanProcessor(exporter)
170
+ batch_processor = BatchSpanProcessor(exporter, **batch_processor_kwargs)
134
171
  agent_processor = SpanProcessor()
135
172
 
136
173
  tracer_provider.add_span_processor(batch_processor)
@@ -197,6 +234,7 @@ def configure(
197
234
  logger_name: str = DEFAULT_LOGGER_NAME,
198
235
  token_resolver: Callable[[str, str], str | None] | None = None,
199
236
  cluster_category: str = "prod",
237
+ exporter_options: Optional[Agent365ExporterOptions] = None,
200
238
  **kwargs: Any,
201
239
  ) -> bool:
202
240
  """
@@ -205,6 +243,12 @@ def configure(
205
243
  :param service_name: The name of the service.
206
244
  :param service_namespace: The namespace of the service.
207
245
  :param logger_name: The name of the logger to collect telemetry from.
246
+ :param token_resolver: (Deprecated) Callable that returns an auth token for a given agent + tenant.
247
+ Use exporter_options instead.
248
+ :param cluster_category: (Deprecated) Environment / cluster category (e.g. "prod").
249
+ Use exporter_options instead.
250
+ :param exporter_options: Agent365ExporterOptions instance for configuring the exporter.
251
+ 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
252
  :return: True if configuration succeeded, False otherwise.
209
253
  """
210
254
  return _telemetry_manager.configure(
@@ -213,6 +257,7 @@ def configure(
213
257
  logger_name,
214
258
  token_resolver,
215
259
  cluster_category,
260
+ exporter_options,
216
261
  **kwargs,
217
262
  )
218
263
 
@@ -1,4 +1,5 @@
1
- # Copyright (c) Microsoft. All rights reserved.
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
2
3
 
3
4
  # Constants for SDK OpenTelemetry implementation.
4
5
 
@@ -68,6 +69,7 @@ GEN_AI_CALLER_TENANT_ID_KEY = "gen_ai.caller.tenantid"
68
69
  GEN_AI_CALLER_ID_KEY = "gen_ai.caller.id"
69
70
  GEN_AI_CALLER_NAME_KEY = "gen_ai.caller.name"
70
71
  GEN_AI_CALLER_UPN_KEY = "gen_ai.caller.upn"
72
+ GEN_AI_CALLER_CLIENT_IP_KEY = "gen_ai.caller.client.ip"
71
73
 
72
74
  # Agent to Agent caller agent dimensions
73
75
  GEN_AI_CALLER_AGENT_USER_ID_KEY = "gen_ai.caller.agent.userid"
@@ -77,6 +79,7 @@ GEN_AI_CALLER_AGENT_NAME_KEY = "gen_ai.caller.agent.name"
77
79
  GEN_AI_CALLER_AGENT_ID_KEY = "gen_ai.caller.agent.id"
78
80
  GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY = "gen_ai.caller.agent.applicationid"
79
81
  GEN_AI_CALLER_AGENT_TYPE_KEY = "gen_ai.caller.agent.type"
82
+ GEN_AI_CALLER_AGENT_USER_CLIENT_IP = "gen_ai.caller.agent.user.client.ip"
80
83
 
81
84
  # Agent-specific dimensions
82
85
  AGENT_ID_KEY = "gen_ai.agent.id"
@@ -92,13 +95,13 @@ GEN_AI_AGENT_UPN_KEY = "gen_ai.agent.upn"
92
95
  GEN_AI_AGENT_BLUEPRINT_ID_KEY = "gen_ai.agent.applicationid"
93
96
  CORRELATION_ID_KEY = "correlation.id"
94
97
  HIRING_MANAGER_ID_KEY = "hiring.manager.id"
98
+ SESSION_DESCRIPTION_KEY = "session.description"
95
99
 
96
100
  # Execution context dimensions
97
101
  GEN_AI_EXECUTION_TYPE_KEY = "gen_ai.execution.type"
98
102
  GEN_AI_EXECUTION_PAYLOAD_KEY = "gen_ai.execution.payload"
99
103
 
100
104
  # Source metadata dimensions
101
- GEN_AI_EXECUTION_SOURCE_ID_KEY = "gen_ai.execution.sourceMetadata.id"
102
105
  GEN_AI_EXECUTION_SOURCE_NAME_KEY = "gen_ai.channel.name"
103
106
  GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY = "gen_ai.channel.link"
104
107
 
@@ -5,6 +5,8 @@ from .agent_details import AgentDetails
5
5
  from .constants import (
6
6
  EXECUTE_TOOL_OPERATION_NAME,
7
7
  GEN_AI_EVENT_CONTENT,
8
+ GEN_AI_EXECUTION_SOURCE_DESCRIPTION_KEY,
9
+ GEN_AI_EXECUTION_SOURCE_NAME_KEY,
8
10
  GEN_AI_TOOL_ARGS_KEY,
9
11
  GEN_AI_TOOL_CALL_ID_KEY,
10
12
  GEN_AI_TOOL_DESCRIPTION_KEY,
@@ -14,6 +16,7 @@ from .constants import (
14
16
  SERVER_PORT_KEY,
15
17
  )
16
18
  from .opentelemetry_scope import OpenTelemetryScope
19
+ from .request import Request
17
20
  from .tenant_details import TenantDetails
18
21
  from .tool_call_details import ToolCallDetails
19
22
 
@@ -26,6 +29,7 @@ class ExecuteToolScope(OpenTelemetryScope):
26
29
  details: ToolCallDetails,
27
30
  agent_details: AgentDetails,
28
31
  tenant_details: TenantDetails,
32
+ request: Request | None = None,
29
33
  ) -> "ExecuteToolScope":
30
34
  """Creates and starts a new scope for tool execution tracing.
31
35
 
@@ -33,17 +37,19 @@ class ExecuteToolScope(OpenTelemetryScope):
33
37
  details: The details of the tool call
34
38
  agent_details: The details of the agent making the call
35
39
  tenant_details: The details of the tenant
40
+ request: Optional request details for additional context
36
41
 
37
42
  Returns:
38
43
  A new ExecuteToolScope instance
39
44
  """
40
- return ExecuteToolScope(details, agent_details, tenant_details)
45
+ return ExecuteToolScope(details, agent_details, tenant_details, request)
41
46
 
42
47
  def __init__(
43
48
  self,
44
49
  details: ToolCallDetails,
45
50
  agent_details: AgentDetails,
46
51
  tenant_details: TenantDetails,
52
+ request: Request | None = None,
47
53
  ):
48
54
  """Initialize the tool execution scope.
49
55
 
@@ -51,6 +57,7 @@ class ExecuteToolScope(OpenTelemetryScope):
51
57
  details: The details of the tool call
52
58
  agent_details: The details of the agent making the call
53
59
  tenant_details: The details of the tenant
60
+ request: Optional request details for additional context
54
61
  """
55
62
  super().__init__(
56
63
  kind="Internal",
@@ -79,6 +86,13 @@ class ExecuteToolScope(OpenTelemetryScope):
79
86
  if endpoint.port and endpoint.port != 443:
80
87
  self.set_tag_maybe(SERVER_PORT_KEY, endpoint.port)
81
88
 
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
+
82
96
  def record_response(self, response: str) -> None:
83
97
  """Records response information for telemetry tracking.
84
98
 
@@ -1,4 +1,5 @@
1
- # Copyright (c) Microsoft. All rights reserved.
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
2
3
 
3
4
  # Execution type enum.
4
5
 
@@ -0,0 +1,8 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+
4
+ from .agent365_exporter_options import Agent365ExporterOptions
5
+
6
+ # Agent365Exporter is not exported intentionally.
7
+ # It should only be used internally by the observability core module.
8
+ __all__ = ["Agent365ExporterOptions"]
@@ -1,4 +1,5 @@
1
- # Copyright (c) Microsoft. All rights reserved.
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
2
3
 
3
4
  # pip install opentelemetry-sdk opentelemetry-api requests
4
5
 
@@ -9,7 +10,8 @@ import logging
9
10
  import threading
10
11
  import time
11
12
  from collections.abc import Callable, Sequence
12
- from typing import Any
13
+ from typing import Any, final
14
+ from urllib.parse import urlparse
13
15
 
14
16
  import requests
15
17
  from microsoft_agents_a365.runtime.power_platform_api_discovery import PowerPlatformApiDiscovery
@@ -17,12 +19,19 @@ from opentelemetry.sdk.trace import ReadableSpan
17
19
  from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
18
20
  from opentelemetry.trace import StatusCode
19
21
 
22
+ from ..constants import (
23
+ GEN_AI_INPUT_MESSAGES_KEY,
24
+ GEN_AI_OPERATION_NAME_KEY,
25
+ INVOKE_AGENT_OPERATION_NAME,
26
+ )
20
27
  from .utils import (
28
+ get_validated_domain_override,
21
29
  hex_span_id,
22
30
  hex_trace_id,
23
31
  kind_name,
24
32
  partition_by_identity,
25
33
  status_name,
34
+ truncate_span,
26
35
  )
27
36
 
28
37
  # ---- Exporter ---------------------------------------------------------------
@@ -35,7 +44,8 @@ DEFAULT_MAX_RETRIES = 3
35
44
  logger = logging.getLogger(__name__)
36
45
 
37
46
 
38
- class Agent365Exporter(SpanExporter):
47
+ @final
48
+ class _Agent365Exporter(SpanExporter):
39
49
  """
40
50
  Agent 365 span exporter for Agent 365:
41
51
  * Partitions spans by (tenantId, agentId)
@@ -49,6 +59,7 @@ class Agent365Exporter(SpanExporter):
49
59
  token_resolver: Callable[[str, str], str | None],
50
60
  cluster_category: str = "prod",
51
61
  use_s2s_endpoint: bool = False,
62
+ suppress_invoke_agent_input: bool = False,
52
63
  ):
53
64
  if token_resolver is None:
54
65
  raise ValueError("token_resolver must be provided.")
@@ -58,6 +69,9 @@ class Agent365Exporter(SpanExporter):
58
69
  self._token_resolver = token_resolver
59
70
  self._cluster_category = cluster_category
60
71
  self._use_s2s_endpoint = use_s2s_endpoint
72
+ self._suppress_invoke_agent_input = suppress_invoke_agent_input
73
+ # Read domain override once at initialization
74
+ self._domain_override = get_validated_domain_override()
61
75
 
62
76
  # ------------- SpanExporter API -----------------
63
77
 
@@ -84,14 +98,29 @@ class Agent365Exporter(SpanExporter):
84
98
  body = json.dumps(payload, separators=(",", ":"), ensure_ascii=False)
85
99
 
86
100
  # Resolve endpoint + token
87
- discovery = PowerPlatformApiDiscovery(self._cluster_category)
88
- endpoint = discovery.get_tenant_island_cluster_endpoint(tenant_id)
101
+ if self._domain_override:
102
+ endpoint = self._domain_override
103
+ else:
104
+ discovery = PowerPlatformApiDiscovery(self._cluster_category)
105
+ endpoint = discovery.get_tenant_island_cluster_endpoint(tenant_id)
106
+
89
107
  endpoint_path = (
90
108
  f"/maven/agent365/service/agents/{agent_id}/traces"
91
109
  if self._use_s2s_endpoint
92
110
  else f"/maven/agent365/agents/{agent_id}/traces"
93
111
  )
94
- url = f"https://{endpoint}{endpoint_path}?api-version=1"
112
+
113
+ # Construct URL - if endpoint has a scheme (http:// or https://), use it as-is
114
+ # Otherwise, prepend https://
115
+ # Note: Check for "://" to distinguish between real protocols and domain:port format
116
+ # (urlparse treats "example.com:8080" as having scheme="example.com")
117
+ parsed = urlparse(endpoint)
118
+ if parsed.scheme and "://" in endpoint:
119
+ # Endpoint is a full URL, append path
120
+ url = f"{endpoint}{endpoint_path}?api-version=1"
121
+ else:
122
+ # Endpoint is just a domain (possibly with port), prepend https://
123
+ url = f"https://{endpoint}{endpoint_path}?api-version=1"
95
124
 
96
125
  # Debug: Log endpoint being used
97
126
  logger.info(
@@ -140,6 +169,8 @@ class Agent365Exporter(SpanExporter):
140
169
  def force_flush(self, timeout_millis: int = 30000) -> bool:
141
170
  return True
142
171
 
172
+ # ------------- Helper methods -------------------
173
+
143
174
  # ------------- HTTP helper ----------------------
144
175
 
145
176
  @staticmethod
@@ -256,6 +287,20 @@ class Agent365Exporter(SpanExporter):
256
287
 
257
288
  # attributes
258
289
  attrs = dict(sp.attributes or {})
290
+
291
+ # Suppress input messages if configured and current span is an InvokeAgent span
292
+ if self._suppress_invoke_agent_input:
293
+ # Check if current span is an InvokeAgent span by:
294
+ # 1. Span name starts with "invoke_agent"
295
+ # 2. Has attribute gen_ai.operation.name set to INVOKE_AGENT_OPERATION_NAME
296
+ operation_name = attrs.get(GEN_AI_OPERATION_NAME_KEY)
297
+ if (
298
+ sp.name.startswith(INVOKE_AGENT_OPERATION_NAME)
299
+ and operation_name == INVOKE_AGENT_OPERATION_NAME
300
+ ):
301
+ # Remove input messages attribute
302
+ attrs.pop(GEN_AI_INPUT_MESSAGES_KEY, None)
303
+
259
304
  # events
260
305
  events = []
261
306
  for ev in sp.events:
@@ -295,7 +340,7 @@ class Agent365Exporter(SpanExporter):
295
340
  start_ns = sp.start_time
296
341
  end_ns = sp.end_time
297
342
 
298
- return {
343
+ span_dict = {
299
344
  "traceId": hex_trace_id(ctx.trace_id),
300
345
  "spanId": hex_span_id(ctx.span_id),
301
346
  "parentSpanId": parent_span_id,
@@ -308,3 +353,6 @@ class Agent365Exporter(SpanExporter):
308
353
  "links": links,
309
354
  "status": status,
310
355
  }
356
+
357
+ # Apply truncation if needed
358
+ 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