microsoft-agents-a365-observability-core 0.2.1.dev45__tar.gz → 0.2.1.dev46__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 (52) hide show
  1. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/PKG-INFO +1 -1
  2. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/__init__.py +10 -8
  3. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/agent_details.py +3 -6
  4. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/constants.py +3 -1
  5. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/execute_tool_scope.py +48 -50
  6. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/inference_scope.py +41 -35
  7. microsoft_agents_a365_observability_core-0.2.1.dev46/microsoft_agents_a365/observability/core/invoke_agent_details.py +14 -0
  8. microsoft_agents_a365_observability_core-0.2.1.dev46/microsoft_agents_a365/observability/core/invoke_agent_scope.py +187 -0
  9. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/middleware/baggage_builder.py +15 -15
  10. microsoft_agents_a365_observability_core-0.2.1.dev46/microsoft_agents_a365/observability/core/models/caller_details.py +24 -0
  11. microsoft_agents_a365_observability_core-0.2.1.dev46/microsoft_agents_a365/observability/core/models/user_details.py +22 -0
  12. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/opentelemetry_scope.py +4 -12
  13. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/request.py +2 -3
  14. microsoft_agents_a365_observability_core-0.2.1.dev46/microsoft_agents_a365/observability/core/span_details.py +25 -0
  15. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/spans_scopes/output_scope.py +49 -35
  16. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/trace_processor/util.py +0 -1
  17. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365_observability_core.egg-info/PKG-INFO +1 -1
  18. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365_observability_core.egg-info/SOURCES.txt +2 -0
  19. microsoft_agents_a365_observability_core-0.2.1.dev45/microsoft_agents_a365/observability/core/invoke_agent_details.py +0 -18
  20. microsoft_agents_a365_observability_core-0.2.1.dev45/microsoft_agents_a365/observability/core/invoke_agent_scope.py +0 -208
  21. microsoft_agents_a365_observability_core-0.2.1.dev45/microsoft_agents_a365/observability/core/models/caller_details.py +0 -22
  22. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/README.md +0 -0
  23. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/channel.py +0 -0
  24. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/config.py +0 -0
  25. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/execution_type.py +0 -0
  26. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/exporters/__init__.py +0 -0
  27. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/exporters/agent365_exporter.py +0 -0
  28. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/exporters/agent365_exporter_options.py +0 -0
  29. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/exporters/enriched_span.py +0 -0
  30. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/exporters/enriching_span_processor.py +0 -0
  31. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/exporters/spectra_exporter_options.py +0 -0
  32. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/exporters/utils.py +0 -0
  33. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/inference_call_details.py +0 -0
  34. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/inference_operation_type.py +0 -0
  35. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/middleware/__init__.py +0 -0
  36. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/models/__init__.py +0 -0
  37. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/models/agent_type.py +0 -0
  38. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/models/operation_source.py +0 -0
  39. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/models/response.py +0 -0
  40. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/spans_scopes/__init__.py +0 -0
  41. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/tenant_details.py +0 -0
  42. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/tool_call_details.py +0 -0
  43. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/tool_type.py +0 -0
  44. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/trace_processor/__init__.py +0 -0
  45. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/trace_processor/span_processor.py +0 -0
  46. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365/observability/core/utils.py +0 -0
  47. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365_observability_core.egg-info/dependency_links.txt +0 -0
  48. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365_observability_core.egg-info/requires.txt +0 -0
  49. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/microsoft_agents_a365_observability_core.egg-info/top_level.txt +0 -0
  50. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/pyproject.toml +0 -0
  51. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/setup.cfg +0 -0
  52. {microsoft_agents_a365_observability_core-0.2.1.dev45 → microsoft_agents_a365_observability_core-0.2.1.dev46}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: microsoft-agents-a365-observability-core
3
- Version: 0.2.1.dev45
3
+ Version: 0.2.1.dev46
4
4
  Summary: Telemetry, tracing, and monitoring components for AI agents
5
5
  Author-email: Microsoft <support@microsoft.com>
6
6
  License: MIT
@@ -11,7 +11,6 @@ from .config import (
11
11
  is_configured,
12
12
  )
13
13
  from .execute_tool_scope import ExecuteToolScope
14
- from .execution_type import ExecutionType
15
14
  from .exporters.agent365_exporter_options import Agent365ExporterOptions
16
15
  from .exporters.enriched_span import EnrichedReadableSpan
17
16
  from .exporters.enriching_span_processor import (
@@ -23,13 +22,16 @@ from .exporters.spectra_exporter_options import SpectraExporterOptions
23
22
  from .inference_call_details import InferenceCallDetails, ServiceEndpoint
24
23
  from .inference_operation_type import InferenceOperationType
25
24
  from .inference_scope import InferenceScope
26
- from .invoke_agent_details import InvokeAgentDetails
25
+ from .invoke_agent_details import InvokeAgentScopeDetails
27
26
  from .invoke_agent_scope import InvokeAgentScope
28
27
  from .middleware.baggage_builder import BaggageBuilder
28
+ from .models.caller_details import CallerDetails
29
+ from .models.user_details import UserDetails
29
30
  from .opentelemetry_scope import OpenTelemetryScope
30
31
  from .request import Request
31
32
  from .channel import Channel
32
- from .tenant_details import TenantDetails
33
+ from .span_details import SpanDetails
34
+ from .spans_scopes.output_scope import OutputScope
33
35
  from .tool_call_details import ToolCallDetails
34
36
  from .tool_type import ToolType
35
37
  from .trace_processor.span_processor import SpanProcessor
@@ -57,26 +59,26 @@ __all__ = [
57
59
  "ExecuteToolScope",
58
60
  "InvokeAgentScope",
59
61
  "InferenceScope",
62
+ "OutputScope",
60
63
  # Middleware
61
64
  "BaggageBuilder",
62
65
  # Data classes
63
- "InvokeAgentDetails",
66
+ "InvokeAgentScopeDetails",
64
67
  "AgentDetails",
65
- "TenantDetails",
68
+ "CallerDetails",
69
+ "UserDetails",
66
70
  "ToolCallDetails",
67
71
  "Channel",
68
72
  "Request",
73
+ "SpanDetails",
69
74
  "InferenceCallDetails",
70
75
  "ServiceEndpoint",
71
76
  # Enums
72
- "ExecutionType",
73
77
  "InferenceOperationType",
74
78
  "ToolType",
75
79
  # Utility functions
76
80
  "extract_context_from_headers",
77
81
  "get_traceparent",
78
- # Constants
79
- # all constants from constants.py are exported via *
80
82
  ]
81
83
 
82
84
  # This is a namespace package
@@ -18,11 +18,11 @@ class AgentDetails:
18
18
  agent_description: Optional[str] = None
19
19
  """A description of the AI agent's purpose or capabilities."""
20
20
 
21
- agent_auid: Optional[str] = None
21
+ agentic_user_id: Optional[str] = None
22
22
  """Agentic User ID for the agent."""
23
23
 
24
- agent_upn: Optional[str] = None
25
- """User Principal Name (UPN) for the agentic user."""
24
+ agentic_user_email: Optional[str] = None
25
+ """Email address for the agentic user."""
26
26
 
27
27
  agent_blueprint_id: Optional[str] = None
28
28
  """Blueprint/Application ID for the agent."""
@@ -33,9 +33,6 @@ class AgentDetails:
33
33
  tenant_id: Optional[str] = None
34
34
  """Tenant ID for the agent."""
35
35
 
36
- conversation_id: Optional[str] = None
37
- """Optional conversation ID for compatibility."""
38
-
39
36
  icon_uri: Optional[str] = None
40
37
  """Optional icon URI for the agent."""
41
38
 
@@ -87,8 +87,10 @@ GEN_AI_AGENT_AUID_KEY = "microsoft.agent.user.id"
87
87
  GEN_AI_AGENT_EMAIL_KEY = "microsoft.agent.user.email"
88
88
  GEN_AI_AGENT_BLUEPRINT_ID_KEY = "microsoft.a365.agent.blueprint.id"
89
89
 
90
+ # Error type constants
91
+ ERROR_TYPE_CANCELLED = "TaskCanceledException"
92
+
90
93
  # Execution context dimensions
91
- GEN_AI_EXECUTION_TYPE_KEY = "gen_ai.execution.type"
92
94
  GEN_AI_EXECUTION_PAYLOAD_KEY = "gen_ai.execution.payload"
93
95
 
94
96
  # Channel dimensions
@@ -1,9 +1,6 @@
1
1
  # Copyright (c) Microsoft Corporation.
2
2
  # Licensed under the MIT License.
3
3
 
4
- from datetime import datetime
5
-
6
- from opentelemetry.context import Context
7
4
  from opentelemetry.trace import SpanKind
8
5
 
9
6
  from .agent_details import AgentDetails
@@ -11,6 +8,8 @@ from .constants import (
11
8
  CHANNEL_LINK_KEY,
12
9
  CHANNEL_NAME_KEY,
13
10
  EXECUTE_TOOL_OPERATION_NAME,
11
+ GEN_AI_CALLER_CLIENT_IP_KEY,
12
+ GEN_AI_CONVERSATION_ID_KEY,
14
13
  GEN_AI_TOOL_ARGS_KEY,
15
14
  GEN_AI_TOOL_CALL_ID_KEY,
16
15
  GEN_AI_TOOL_DESCRIPTION_KEY,
@@ -18,11 +17,16 @@ from .constants import (
18
17
  GEN_AI_TOOL_TYPE_KEY,
19
18
  SERVER_ADDRESS_KEY,
20
19
  SERVER_PORT_KEY,
20
+ USER_EMAIL_KEY,
21
+ USER_ID_KEY,
22
+ USER_NAME_KEY,
21
23
  )
24
+ from .models.user_details import UserDetails
22
25
  from .opentelemetry_scope import OpenTelemetryScope
23
26
  from .request import Request
24
- from .tenant_details import TenantDetails
27
+ from .span_details import SpanDetails
25
28
  from .tool_call_details import ToolCallDetails
29
+ from .utils import validate_and_normalize_ip
26
30
 
27
31
 
28
32
  class ExecuteToolScope(OpenTelemetryScope):
@@ -30,88 +34,71 @@ class ExecuteToolScope(OpenTelemetryScope):
30
34
 
31
35
  @staticmethod
32
36
  def start(
37
+ request: Request,
33
38
  details: ToolCallDetails,
34
39
  agent_details: AgentDetails,
35
- tenant_details: TenantDetails,
36
- request: Request | None = None,
37
- parent_context: Context | None = None,
38
- start_time: datetime | None = None,
39
- end_time: datetime | None = None,
40
- span_kind: SpanKind | None = None,
40
+ user_details: UserDetails | None = None,
41
+ span_details: SpanDetails | None = None,
41
42
  ) -> "ExecuteToolScope":
42
43
  """Creates and starts a new scope for tool execution tracing.
43
44
 
44
45
  Args:
46
+ request: Request details for the tool execution
45
47
  details: The details of the tool call
46
48
  agent_details: The details of the agent making the call
47
- tenant_details: The details of the tenant
48
- request: Optional request details for additional context
49
- parent_context: Optional OpenTelemetry Context used to link this span to an
50
- upstream operation. Use ``extract_context_from_headers()`` to convert a
51
- Context from HTTP headers containing W3C traceparent.
52
- start_time: Optional explicit start time as a datetime object. Useful when
53
- recording a tool call after execution has already completed.
54
- end_time: Optional explicit end time as a datetime object. When provided,
55
- the span will use this timestamp when disposed instead of the
56
- current wall-clock time.
57
- span_kind: Optional span kind override. Defaults to ``SpanKind.INTERNAL``.
58
- Use ``SpanKind.CLIENT`` when the tool calls an external service.
49
+ user_details: Optional human user details
50
+ span_details: Optional span configuration (parent context, timing, kind)
59
51
 
60
52
  Returns:
61
53
  A new ExecuteToolScope instance
62
54
  """
63
55
  return ExecuteToolScope(
56
+ request,
64
57
  details,
65
58
  agent_details,
66
- tenant_details,
67
- request,
68
- parent_context,
69
- start_time,
70
- end_time,
71
- span_kind,
59
+ user_details,
60
+ span_details,
72
61
  )
73
62
 
74
63
  def __init__(
75
64
  self,
65
+ request: Request,
76
66
  details: ToolCallDetails,
77
67
  agent_details: AgentDetails,
78
- tenant_details: TenantDetails,
79
- request: Request | None = None,
80
- parent_context: Context | None = None,
81
- start_time: datetime | None = None,
82
- end_time: datetime | None = None,
83
- span_kind: SpanKind | None = None,
68
+ user_details: UserDetails | None = None,
69
+ span_details: SpanDetails | None = None,
84
70
  ):
85
71
  """Initialize the tool execution scope.
86
72
 
87
73
  Args:
74
+ request: Request details for the tool execution
88
75
  details: The details of the tool call
89
76
  agent_details: The details of the agent making the call
90
- tenant_details: The details of the tenant
91
- request: Optional request details for additional context
92
- parent_context: Optional OpenTelemetry Context used to link this span to an
93
- upstream operation. Use ``extract_context_from_headers()`` to convert a
94
- Context from HTTP headers containing W3C traceparent.
95
- start_time: Optional explicit start time as a datetime object. Useful when
96
- recording a tool call after execution has already completed.
97
- end_time: Optional explicit end time as a datetime object. When provided,
98
- the span will use this timestamp when disposed instead of the
99
- current wall-clock time.
100
- span_kind: Optional span kind override. Defaults to ``SpanKind.INTERNAL``.
101
- Use ``SpanKind.CLIENT`` when the tool calls an external service.
77
+ user_details: Optional human user details
78
+ span_details: Optional span configuration (parent context, timing, kind)
102
79
  """
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
90
+
103
91
  super().__init__(
104
- kind=span_kind if span_kind is not None else SpanKind.INTERNAL,
92
+ kind=kind,
105
93
  operation_name=EXECUTE_TOOL_OPERATION_NAME,
106
94
  activity_name=f"{EXECUTE_TOOL_OPERATION_NAME} {details.tool_name}",
107
95
  agent_details=agent_details,
108
- tenant_details=tenant_details,
109
96
  parent_context=parent_context,
110
97
  start_time=start_time,
111
98
  end_time=end_time,
112
99
  )
113
100
 
114
- # Extract details using deconstruction-like approach
101
+ # Extract details
115
102
  tool_name = details.tool_name
116
103
  arguments = details.arguments
117
104
  tool_call_id = details.tool_call_id
@@ -124,6 +111,7 @@ class ExecuteToolScope(OpenTelemetryScope):
124
111
  self.set_tag_maybe(GEN_AI_TOOL_TYPE_KEY, tool_type)
125
112
  self.set_tag_maybe(GEN_AI_TOOL_CALL_ID_KEY, tool_call_id)
126
113
  self.set_tag_maybe(GEN_AI_TOOL_DESCRIPTION_KEY, description)
114
+ self.set_tag_maybe(GEN_AI_CONVERSATION_ID_KEY, request.conversation_id)
127
115
 
128
116
  if endpoint:
129
117
  self.set_tag_maybe(SERVER_ADDRESS_KEY, endpoint.hostname)
@@ -131,10 +119,20 @@ class ExecuteToolScope(OpenTelemetryScope):
131
119
  self.set_tag_maybe(SERVER_PORT_KEY, endpoint.port)
132
120
 
133
121
  # Set request metadata if provided
134
- if request and request.channel:
122
+ if request.channel:
135
123
  self.set_tag_maybe(CHANNEL_NAME_KEY, request.channel.name)
136
124
  self.set_tag_maybe(CHANNEL_LINK_KEY, request.channel.link)
137
125
 
126
+ # Set user details if provided
127
+ if user_details:
128
+ self.set_tag_maybe(USER_ID_KEY, user_details.user_id)
129
+ self.set_tag_maybe(USER_EMAIL_KEY, user_details.user_email)
130
+ self.set_tag_maybe(USER_NAME_KEY, user_details.user_name)
131
+ self.set_tag_maybe(
132
+ GEN_AI_CALLER_CLIENT_IP_KEY,
133
+ validate_and_normalize_ip(user_details.user_client_ip),
134
+ )
135
+
138
136
  def record_response(self, response: str) -> None:
139
137
  """Records response information for telemetry tracking.
140
138
 
@@ -1,16 +1,14 @@
1
1
  # Copyright (c) Microsoft Corporation.
2
2
  # Licensed under the MIT License.
3
3
 
4
- from datetime import datetime
5
4
  from typing import List
6
5
 
7
- from opentelemetry.context import Context
8
-
9
6
  from .agent_details import AgentDetails
10
7
  from .constants import (
11
8
  CHANNEL_LINK_KEY,
12
9
  CHANNEL_NAME_KEY,
13
10
  GEN_AI_AGENT_THOUGHT_PROCESS_KEY,
11
+ GEN_AI_CONVERSATION_ID_KEY,
14
12
  GEN_AI_INPUT_MESSAGES_KEY,
15
13
  GEN_AI_OPERATION_NAME_KEY,
16
14
  GEN_AI_OUTPUT_MESSAGES_KEY,
@@ -21,12 +19,17 @@ from .constants import (
21
19
  GEN_AI_USAGE_OUTPUT_TOKENS_KEY,
22
20
  SERVER_ADDRESS_KEY,
23
21
  SERVER_PORT_KEY,
22
+ USER_EMAIL_KEY,
23
+ USER_ID_KEY,
24
+ USER_NAME_KEY,
25
+ GEN_AI_CALLER_CLIENT_IP_KEY,
24
26
  )
25
27
  from .inference_call_details import InferenceCallDetails
28
+ from .models.user_details import UserDetails
26
29
  from .opentelemetry_scope import OpenTelemetryScope
27
30
  from .request import Request
28
- from .tenant_details import TenantDetails
29
- from .utils import safe_json_dumps
31
+ from .span_details import SpanDetails
32
+ from .utils import safe_json_dumps, validate_and_normalize_ip
30
33
 
31
34
 
32
35
  class InferenceScope(OpenTelemetryScope):
@@ -34,71 +37,64 @@ class InferenceScope(OpenTelemetryScope):
34
37
 
35
38
  @staticmethod
36
39
  def start(
40
+ request: Request,
37
41
  details: InferenceCallDetails,
38
42
  agent_details: AgentDetails,
39
- tenant_details: TenantDetails,
40
- request: Request | None = None,
41
- parent_context: Context | None = None,
42
- start_time: datetime | None = None,
43
- end_time: datetime | None = None,
43
+ user_details: UserDetails | None = None,
44
+ span_details: SpanDetails | None = None,
44
45
  ) -> "InferenceScope":
45
46
  """Creates and starts a new scope for inference tracing.
46
47
 
47
48
  Args:
49
+ request: Request details for the inference
48
50
  details: The details of the inference call
49
51
  agent_details: The details of the agent making the call
50
- tenant_details: The details of the tenant
51
- request: Optional request details for additional context
52
- parent_context: Optional OpenTelemetry Context used to link this span to an
53
- upstream operation. Use ``extract_context_from_headers()`` to convert a
54
- Context from HTTP headers containing W3C traceparent.
55
- start_time: Optional explicit start time as a datetime object.
56
- end_time: Optional explicit end time as a datetime object.
52
+ user_details: Optional human user details
53
+ span_details: Optional span configuration (parent context, timing)
57
54
 
58
55
  Returns:
59
56
  A new InferenceScope instance
60
57
  """
61
- return InferenceScope(
62
- details, agent_details, tenant_details, request, parent_context, start_time, end_time
63
- )
58
+ return InferenceScope(request, details, agent_details, user_details, span_details)
64
59
 
65
60
  def __init__(
66
61
  self,
62
+ request: Request,
67
63
  details: InferenceCallDetails,
68
64
  agent_details: AgentDetails,
69
- tenant_details: TenantDetails,
70
- request: Request | None = None,
71
- parent_context: Context | None = None,
72
- start_time: datetime | None = None,
73
- end_time: datetime | None = None,
65
+ user_details: UserDetails | None = None,
66
+ span_details: SpanDetails | None = None,
74
67
  ):
75
68
  """Initialize the inference scope.
76
69
 
77
70
  Args:
71
+ request: Request details for the inference
78
72
  details: The details of the inference call
79
73
  agent_details: The details of the agent making the call
80
- tenant_details: The details of the tenant
81
- request: Optional request details for additional context
82
- parent_context: Optional OpenTelemetry Context used to link this span to an
83
- upstream operation. Use ``extract_context_from_headers()`` to convert a
84
- Context from HTTP headers containing W3C traceparent.
85
- start_time: Optional explicit start time as a datetime object.
86
- end_time: Optional explicit end time as a datetime object.
74
+ user_details: Optional human user details
75
+ span_details: Optional span configuration (parent context, timing)
87
76
  """
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
88
84
 
89
85
  super().__init__(
90
86
  kind="Client",
91
87
  operation_name=details.operationName.value,
92
88
  activity_name=f"{details.operationName.value} {details.model}",
93
89
  agent_details=agent_details,
94
- tenant_details=tenant_details,
95
90
  parent_context=parent_context,
96
91
  start_time=start_time,
97
92
  end_time=end_time,
98
93
  )
99
94
 
100
- if request:
95
+ if request.content:
101
96
  self.set_tag_maybe(GEN_AI_INPUT_MESSAGES_KEY, request.content)
97
+ self.set_tag_maybe(GEN_AI_CONVERSATION_ID_KEY, request.conversation_id)
102
98
 
103
99
  self.set_tag_maybe(GEN_AI_OPERATION_NAME_KEY, details.operationName.value)
104
100
  self.set_tag_maybe(GEN_AI_REQUEST_MODEL_KEY, details.model)
@@ -124,10 +120,20 @@ class InferenceScope(OpenTelemetryScope):
124
120
  self.set_tag_maybe(SERVER_PORT_KEY, str(details.endpoint.port))
125
121
 
126
122
  # Set request metadata if provided
127
- if request and request.channel:
123
+ if request.channel:
128
124
  self.set_tag_maybe(CHANNEL_NAME_KEY, request.channel.name)
129
125
  self.set_tag_maybe(CHANNEL_LINK_KEY, request.channel.link)
130
126
 
127
+ # Set user details if provided
128
+ if user_details:
129
+ self.set_tag_maybe(USER_ID_KEY, user_details.user_id)
130
+ self.set_tag_maybe(USER_EMAIL_KEY, user_details.user_email)
131
+ self.set_tag_maybe(USER_NAME_KEY, user_details.user_name)
132
+ self.set_tag_maybe(
133
+ GEN_AI_CALLER_CLIENT_IP_KEY,
134
+ validate_and_normalize_ip(user_details.user_client_ip),
135
+ )
136
+
131
137
  def record_input_messages(self, messages: List[str]) -> None:
132
138
  """Records the input messages for telemetry tracking.
133
139
 
@@ -0,0 +1,14 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+
4
+ # Data class for invoke agent scope details.
5
+
6
+ from dataclasses import dataclass
7
+ from urllib.parse import ParseResult
8
+
9
+
10
+ @dataclass
11
+ class InvokeAgentScopeDetails:
12
+ """Scope-level configuration for agent invocation tracing."""
13
+
14
+ endpoint: ParseResult | None = None
@@ -0,0 +1,187 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+
4
+ # Invoke agent scope for tracing agent invocation.
5
+
6
+ import logging
7
+
8
+ from opentelemetry.trace import SpanKind
9
+
10
+ from .agent_details import AgentDetails
11
+ from .constants import (
12
+ CHANNEL_LINK_KEY,
13
+ CHANNEL_NAME_KEY,
14
+ GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY,
15
+ GEN_AI_CALLER_AGENT_EMAIL_KEY,
16
+ GEN_AI_CALLER_AGENT_ID_KEY,
17
+ GEN_AI_CALLER_AGENT_NAME_KEY,
18
+ GEN_AI_CALLER_AGENT_PLATFORM_ID_KEY,
19
+ GEN_AI_CALLER_AGENT_USER_ID_KEY,
20
+ GEN_AI_CALLER_CLIENT_IP_KEY,
21
+ GEN_AI_CONVERSATION_ID_KEY,
22
+ GEN_AI_INPUT_MESSAGES_KEY,
23
+ GEN_AI_OUTPUT_MESSAGES_KEY,
24
+ INVOKE_AGENT_OPERATION_NAME,
25
+ SERVER_ADDRESS_KEY,
26
+ SERVER_PORT_KEY,
27
+ SESSION_ID_KEY,
28
+ USER_EMAIL_KEY,
29
+ USER_ID_KEY,
30
+ USER_NAME_KEY,
31
+ )
32
+ from .invoke_agent_details import InvokeAgentScopeDetails
33
+ from .models.caller_details import CallerDetails
34
+ from .opentelemetry_scope import OpenTelemetryScope
35
+ from .request import Request
36
+ from .span_details import SpanDetails
37
+ from .utils import safe_json_dumps, validate_and_normalize_ip
38
+
39
+ logger = logging.getLogger(__name__)
40
+
41
+
42
+ class InvokeAgentScope(OpenTelemetryScope):
43
+ """Provides OpenTelemetry tracing scope for AI agent invocation operations."""
44
+
45
+ @staticmethod
46
+ def start(
47
+ request: Request,
48
+ scope_details: InvokeAgentScopeDetails,
49
+ agent_details: AgentDetails,
50
+ caller_details: CallerDetails | None = None,
51
+ span_details: SpanDetails | None = None,
52
+ ) -> "InvokeAgentScope":
53
+ """Create and start a new scope for agent invocation tracing.
54
+
55
+ Args:
56
+ request: Request details for the invocation
57
+ scope_details: Scope-level configuration (endpoint)
58
+ agent_details: The details of the agent being invoked
59
+ caller_details: Optional composite caller details (human user and/or
60
+ calling agent for A2A scenarios)
61
+ span_details: Optional span configuration (parent context, timing, kind)
62
+
63
+ Returns:
64
+ A new InvokeAgentScope instance
65
+ """
66
+ return InvokeAgentScope(
67
+ request,
68
+ scope_details,
69
+ agent_details,
70
+ caller_details,
71
+ span_details,
72
+ )
73
+
74
+ def __init__(
75
+ self,
76
+ request: Request,
77
+ scope_details: InvokeAgentScopeDetails,
78
+ agent_details: AgentDetails,
79
+ caller_details: CallerDetails | None = None,
80
+ span_details: SpanDetails | None = None,
81
+ ):
82
+ """Initialize the agent invocation scope.
83
+
84
+ Args:
85
+ request: Request details for the invocation
86
+ scope_details: Scope-level configuration (endpoint)
87
+ agent_details: The details of the agent being invoked
88
+ caller_details: Optional composite caller details (human user and/or
89
+ calling agent for A2A scenarios)
90
+ span_details: Optional span configuration (parent context, timing, kind)
91
+ """
92
+ activity_name = INVOKE_AGENT_OPERATION_NAME
93
+ if agent_details.agent_name:
94
+ activity_name = f"{INVOKE_AGENT_OPERATION_NAME} {agent_details.agent_name}"
95
+
96
+ kind = SpanKind.CLIENT
97
+ parent_context = None
98
+ start_time = None
99
+ end_time = None
100
+ if span_details is not None:
101
+ if span_details.span_kind is not None:
102
+ kind = span_details.span_kind
103
+ parent_context = span_details.parent_context
104
+ start_time = span_details.start_time
105
+ end_time = span_details.end_time
106
+
107
+ super().__init__(
108
+ kind=kind,
109
+ operation_name=INVOKE_AGENT_OPERATION_NAME,
110
+ activity_name=activity_name,
111
+ agent_details=agent_details,
112
+ parent_context=parent_context,
113
+ start_time=start_time,
114
+ end_time=end_time,
115
+ )
116
+
117
+ self.set_tag_maybe(SESSION_ID_KEY, request.session_id)
118
+ self.set_tag_maybe(GEN_AI_CONVERSATION_ID_KEY, request.conversation_id)
119
+
120
+ endpoint = scope_details.endpoint
121
+ if endpoint:
122
+ self.set_tag_maybe(SERVER_ADDRESS_KEY, endpoint.hostname)
123
+ if endpoint.port and endpoint.port != 443:
124
+ self.set_tag_maybe(SERVER_PORT_KEY, endpoint.port)
125
+
126
+ # Set request metadata
127
+ if request.channel:
128
+ self.set_tag_maybe(CHANNEL_NAME_KEY, request.channel.name)
129
+ self.set_tag_maybe(CHANNEL_LINK_KEY, request.channel.link)
130
+ if request.content:
131
+ self.set_tag_maybe(GEN_AI_INPUT_MESSAGES_KEY, safe_json_dumps([request.content]))
132
+
133
+ # Set caller details tags
134
+ if caller_details:
135
+ user_details = caller_details.user_details
136
+ if user_details:
137
+ self.set_tag_maybe(USER_ID_KEY, user_details.user_id)
138
+ self.set_tag_maybe(USER_EMAIL_KEY, user_details.user_email)
139
+ self.set_tag_maybe(USER_NAME_KEY, user_details.user_name)
140
+ self.set_tag_maybe(
141
+ GEN_AI_CALLER_CLIENT_IP_KEY,
142
+ validate_and_normalize_ip(user_details.user_client_ip),
143
+ )
144
+
145
+ # Set caller agent details tags
146
+ caller_agent_details = caller_details.caller_agent_details
147
+ if caller_agent_details:
148
+ self.set_tag_maybe(GEN_AI_CALLER_AGENT_NAME_KEY, caller_agent_details.agent_name)
149
+ self.set_tag_maybe(GEN_AI_CALLER_AGENT_ID_KEY, caller_agent_details.agent_id)
150
+ self.set_tag_maybe(
151
+ GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY,
152
+ caller_agent_details.agent_blueprint_id,
153
+ )
154
+ self.set_tag_maybe(
155
+ GEN_AI_CALLER_AGENT_USER_ID_KEY, caller_agent_details.agentic_user_id
156
+ )
157
+ self.set_tag_maybe(
158
+ GEN_AI_CALLER_AGENT_EMAIL_KEY, caller_agent_details.agentic_user_email
159
+ )
160
+ self.set_tag_maybe(
161
+ GEN_AI_CALLER_AGENT_PLATFORM_ID_KEY,
162
+ caller_agent_details.agent_platform_id,
163
+ )
164
+
165
+ def record_response(self, response: str) -> None:
166
+ """Record response information for telemetry tracking.
167
+
168
+ Args:
169
+ response: The response string to record
170
+ """
171
+ self.record_output_messages([response])
172
+
173
+ def record_input_messages(self, messages: list[str]) -> None:
174
+ """Record the input messages for telemetry tracking.
175
+
176
+ Args:
177
+ messages: List of input messages to record
178
+ """
179
+ self.set_tag_maybe(GEN_AI_INPUT_MESSAGES_KEY, safe_json_dumps(messages))
180
+
181
+ def record_output_messages(self, messages: list[str]) -> None:
182
+ """Record the output messages for telemetry tracking.
183
+
184
+ Args:
185
+ messages: List of output messages to record
186
+ """
187
+ self.set_tag_maybe(GEN_AI_OUTPUT_MESSAGES_KEY, safe_json_dumps(messages))