diff --git a/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/execute_tool_scope.py b/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/execute_tool_scope.py index 656f7ef..f502cee 100644 --- a/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/execute_tool_scope.py +++ b/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/execute_tool_scope.py @@ -30,6 +30,7 @@ def start( agent_details: AgentDetails, tenant_details: TenantDetails, request: Request | None = None, + parent_id: str | None = None, ) -> "ExecuteToolScope": """Creates and starts a new scope for tool execution tracing. @@ -38,11 +39,13 @@ def start( agent_details: The details of the agent making the call tenant_details: The details of the tenant request: Optional request details for additional context + parent_id: Optional parent Activity ID used to link this span to an upstream + operation Returns: A new ExecuteToolScope instance """ - return ExecuteToolScope(details, agent_details, tenant_details, request) + return ExecuteToolScope(details, agent_details, tenant_details, request, parent_id) def __init__( self, @@ -50,6 +53,7 @@ def __init__( agent_details: AgentDetails, tenant_details: TenantDetails, request: Request | None = None, + parent_id: str | None = None, ): """Initialize the tool execution scope. @@ -58,6 +62,8 @@ def __init__( agent_details: The details of the agent making the call tenant_details: The details of the tenant request: Optional request details for additional context + parent_id: Optional parent Activity ID used to link this span to an upstream + operation """ super().__init__( kind="Internal", @@ -65,6 +71,7 @@ def __init__( activity_name=f"{EXECUTE_TOOL_OPERATION_NAME} {details.tool_name}", agent_details=agent_details, tenant_details=tenant_details, + parent_id=parent_id, ) # Extract details using deconstruction-like approach diff --git a/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/inference_scope.py b/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/inference_scope.py index f3e11bd..79294ea 100644 --- a/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/inference_scope.py +++ b/libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/inference_scope.py @@ -34,6 +34,7 @@ def start( agent_details: AgentDetails, tenant_details: TenantDetails, request: Request | None = None, + parent_id: str | None = None, ) -> "InferenceScope": """Creates and starts a new scope for inference tracing. @@ -42,11 +43,13 @@ def start( agent_details: The details of the agent making the call tenant_details: The details of the tenant request: Optional request details for additional context + parent_id: Optional parent Activity ID used to link this span to an upstream + operation Returns: A new InferenceScope instance """ - return InferenceScope(details, agent_details, tenant_details, request) + return InferenceScope(details, agent_details, tenant_details, request, parent_id) def __init__( self, @@ -54,6 +57,7 @@ def __init__( agent_details: AgentDetails, tenant_details: TenantDetails, request: Request | None = None, + parent_id: str | None = None, ): """Initialize the inference scope. @@ -62,6 +66,8 @@ def __init__( agent_details: The details of the agent making the call tenant_details: The details of the tenant request: Optional request details for additional context + parent_id: Optional parent Activity ID used to link this span to an upstream + operation """ super().__init__( @@ -70,6 +76,7 @@ def __init__( activity_name=f"{details.operationName.value} {details.model}", agent_details=agent_details, tenant_details=tenant_details, + parent_id=parent_id, ) if request: diff --git a/tests/observability/core/test_execute_tool_scope.py b/tests/observability/core/test_execute_tool_scope.py index 187ee26..82f073d 100644 --- a/tests/observability/core/test_execute_tool_scope.py +++ b/tests/observability/core/test_execute_tool_scope.py @@ -131,6 +131,32 @@ def test_request_metadata_set_on_span(self): request.source_metadata.description, ) + def test_execute_tool_scope_with_parent_id(self): + """Test ExecuteToolScope uses parent_id to link span to parent context.""" + parent_trace_id = "1234567890abcdef1234567890abcdef" + parent_span_id = "abcdefabcdef1234" + parent_id = f"00-{parent_trace_id}-{parent_span_id}-01" + + with ExecuteToolScope.start( + self.tool_details, self.agent_details, self.tenant_details, parent_id=parent_id + ): + pass + + finished_spans = self.span_exporter.get_finished_spans() + self.assertTrue(finished_spans, "Expected at least one span to be created") + + span = finished_spans[-1] + + # Verify span inherits parent's trace_id + span_trace_id = f"{span.context.trace_id:032x}" + self.assertEqual(span_trace_id, parent_trace_id) + + # Verify span's parent_span_id matches + self.assertIsNotNone(span.parent, "Expected span to have a parent") + self.assertTrue(hasattr(span.parent, "span_id"), "Expected parent to have span_id") + span_parent_id = f"{span.parent.span_id:016x}" + self.assertEqual(span_parent_id, parent_span_id) + if __name__ == "__main__": # Run pytest only on the current file diff --git a/tests/observability/core/test_inference_scope.py b/tests/observability/core/test_inference_scope.py index 01eef83..adf935b 100644 --- a/tests/observability/core/test_inference_scope.py +++ b/tests/observability/core/test_inference_scope.py @@ -340,6 +340,38 @@ def test_record_thought_process(self): # Should not raise an exception self.assertTrue(hasattr(scope, "record_thought_process")) + def test_inference_scope_with_parent_id(self): + """Test InferenceScope uses parent_id to link span to parent context.""" + details = InferenceCallDetails( + operationName=InferenceOperationType.CHAT, + model="gpt-4", + providerName="openai", + ) + + parent_trace_id = "1234567890abcdef1234567890abcdef" + parent_span_id = "abcdefabcdef1234" + parent_id = f"00-{parent_trace_id}-{parent_span_id}-01" + + with InferenceScope.start( + details, self.agent_details, self.tenant_details, parent_id=parent_id + ): + pass + + finished_spans = self.span_exporter.get_finished_spans() + self.assertTrue(finished_spans, "Expected at least one span to be created") + + span = finished_spans[-1] + + # Verify span inherits parent's trace_id + span_trace_id = f"{span.context.trace_id:032x}" + self.assertEqual(span_trace_id, parent_trace_id) + + # Verify span's parent_span_id matches + self.assertIsNotNone(span.parent, "Expected span to have a parent") + self.assertTrue(hasattr(span.parent, "span_id"), "Expected parent to have span_id") + span_parent_id = f"{span.parent.span_id:016x}" + self.assertEqual(span_parent_id, parent_span_id) + if __name__ == "__main__": # Run pytest only on the current file