Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,21 +60,18 @@ def set_request_id_to_context_var(self, request):
request_context.set(ctx)

def set_run_context_to_context_var(self, run_context):
agent_id, agent_name = "", ""
agent_obj = run_context.get_agent_id_object()
if agent_obj:
agent_name = getattr(agent_obj, "name", "")
agent_version = getattr(agent_obj, "version", "")
agent_id = f"{agent_name}:{agent_version}"
agent_name, agent_id = get_agent_name(run_context)

res = {
"azure.ai.agentserver.response_id": run_context.response_id or "",
"azure.ai.agentserver.conversation_id": run_context.conversation_id or "",
"azure.ai.agentserver.streaming": str(run_context.stream or False),
"gen_ai.operation.name": "invoke_agent",
"gen_ai.agent.id": agent_id,
"gen_ai.agent.name": agent_name,
"gen_ai.agent.name": agent_name or "",
"gen_ai.provider.name": "AzureAI Hosted Agents",
"gen_ai.response.id": run_context.response_id or "",
"gen_ai.conversation.id": run_context.conversation_id or "",
}
ctx = request_context.get() or {}
ctx.update(res)
Expand All @@ -87,10 +84,12 @@ async def runs_endpoint(request):
# Set up tracing context and span
context = request.state.agent_run_context
ctx = request_context.get()
agent_name, _ = get_agent_name(context)
span_name = f"invoke_agent {agent_name}" if agent_name else "invoke_agent"
with self.tracer.start_as_current_span(
name=f"HostedAgents-{context.response_id}",
name=span_name,
attributes=ctx,
kind=trace.SpanKind.SERVER,
kind=trace.SpanKind.CLIENT,
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The span kind has been changed from SERVER to CLIENT, but this endpoint is receiving HTTP requests, which indicates it should use SpanKind.SERVER according to OpenTelemetry semantic conventions. SpanKind.CLIENT is typically used for outbound calls the service makes, not for handling incoming requests. This is a server endpoint (runs_endpoint) handling incoming POST requests to /runs or /responses. Unless this represents the client-side invocation of an agent (not the HTTP server receiving the request), this should remain SpanKind.SERVER.

Suggested change
kind=trace.SpanKind.CLIENT,
kind=trace.SpanKind.SERVER,

Copilot uses AI. Check for mistakes.
):
try:
logger.info("Start processing CreateResponse request:")
Expand Down Expand Up @@ -304,6 +303,30 @@ def setup_otlp_exporter(self, endpoint, provider):
logger.info(f"Tracing setup with OTLP exporter: {endpoint}")


def get_agent_name(agent_run_context: AgentRunContext) -> tuple:
"""
Extract the agent name and agent id from an AgentRunContext.

:param agent_run_context: The AgentRunContext instance to extract from.
:type agent_run_context: AgentRunContext
:return: Tuple of (agent_name, agent_id). Agent id is formatted as "name:version".
Comment on lines +308 to +312
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring uses the term "Agent id" in the description, but the parameter name is "agent_run_context". For consistency with other docstrings in the codebase and clarity, consider rephrasing to "Extract the agent name and agent ID from an AgentRunContext." (capitalizing "ID" to match the parameter description).

Suggested change
Extract the agent name and agent id from an AgentRunContext.
:param agent_run_context: The AgentRunContext instance to extract from.
:type agent_run_context: AgentRunContext
:return: Tuple of (agent_name, agent_id). Agent id is formatted as "name:version".
Extract the agent name and agent ID from an AgentRunContext.
:param agent_run_context: The AgentRunContext instance to extract from.
:type agent_run_context: AgentRunContext
:return: Tuple of (agent_name, agent_id). Agent ID is formatted as "name:version".

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring states "Agent id is formatted as 'name:version'" but should be "Agent ID is formatted as 'name:version'" for consistency with standard capitalization of "ID" in technical documentation.

Suggested change
:return: Tuple of (agent_name, agent_id). Agent id is formatted as "name:version".
:return: Tuple of (agent_name, agent_id). Agent ID is formatted as "name:version".

Copilot uses AI. Check for mistakes.
Returns (None, "") if agent information is not available.
:rtype: tuple
"""
agent_name = None
agent_id = ""

if agent_run_context:
agent_obj = agent_run_context.get_agent_id_object()
if agent_obj:
agent_name = getattr(agent_obj, "name", None)
agent_version = getattr(agent_obj, "version", "")
if agent_name:
agent_id = f"{agent_name}:{agent_version}"

return agent_name, agent_id


def _event_to_sse_chunk(event: ResponseStreamEvent) -> str:
event_data = json.dumps(event.as_dict())
if event.type:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#!/usr/bin/env python3
# ---------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# ---------------------------------------------------------
"""Unit tests for get_agent_name function."""

import sys
from pathlib import Path
from unittest.mock import MagicMock

import pytest

# Add the project root to the path
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))

from azure.ai.agentserver.core.server.base import get_agent_name
from azure.ai.agentserver.core.server.common.agent_run_context import AgentRunContext


class TestGetAgentName:
"""Test suite for get_agent_name function."""

def test_get_agent_name_with_valid_agent_object(self):
"""Test get_agent_name with valid agent object."""
# Create a mock agent object
mock_agent = MagicMock()
mock_agent.name = "TestAgent"
mock_agent.version = "1.0.0"

# Create a mock AgentRunContext
mock_context = MagicMock(spec=AgentRunContext)
mock_context.get_agent_id_object.return_value = mock_agent

# Call get_agent_name
agent_name, agent_id = get_agent_name(mock_context)

# Assert results
assert agent_name == "TestAgent"
assert agent_id == "TestAgent:1.0.0"

def test_get_agent_name_with_no_version(self):
"""Test get_agent_name when agent has no version."""
# Create a mock agent object with no version
mock_agent = MagicMock()
mock_agent.name = "TestAgent"
mock_agent.version = ""

# Create a mock AgentRunContext
mock_context = MagicMock(spec=AgentRunContext)
mock_context.get_agent_id_object.return_value = mock_agent

# Call get_agent_name
agent_name, agent_id = get_agent_name(mock_context)

# Assert results
assert agent_name == "TestAgent"
assert agent_id == "TestAgent:"

def test_get_agent_name_with_none_agent_object(self):
"""Test get_agent_name when agent object is None."""
# Create a mock AgentRunContext that returns None
mock_context = MagicMock(spec=AgentRunContext)
mock_context.get_agent_id_object.return_value = None

# Call get_agent_name
agent_name, agent_id = get_agent_name(mock_context)

# Assert results
assert agent_name is None
assert agent_id == ""

def test_get_agent_name_with_none_context(self):
"""Test get_agent_name with None context."""
# Call get_agent_name with None
agent_name, agent_id = get_agent_name(None)

# Assert results
assert agent_name is None
assert agent_id == ""

def test_get_agent_name_with_missing_name_attribute(self):
"""Test get_agent_name when agent object has no name attribute."""
# Create a mock agent object without name
mock_agent = MagicMock()
mock_agent.name = None
mock_agent.version = "1.0.0"

# Create a mock AgentRunContext
mock_context = MagicMock(spec=AgentRunContext)
mock_context.get_agent_id_object.return_value = mock_agent

# Call get_agent_name
agent_name, agent_id = get_agent_name(mock_context)

# Assert results - agent_id should not be set if name is None
assert agent_name is None
assert agent_id == ""

def test_get_agent_name_with_empty_name(self):
"""Test get_agent_name when agent name is empty string."""
# Create a mock agent object with empty name
mock_agent = MagicMock()
mock_agent.name = ""
mock_agent.version = "1.0.0"

# Create a mock AgentRunContext
mock_context = MagicMock(spec=AgentRunContext)
mock_context.get_agent_id_object.return_value = mock_agent

# Call get_agent_name
agent_name, agent_id = get_agent_name(mock_context)

# Assert results - agent_id should not be set if name is empty
assert agent_name == ""
assert agent_id == ""

def test_get_agent_name_return_type(self):
"""Test that get_agent_name returns a tuple."""
# Create a mock agent object
mock_agent = MagicMock()
mock_agent.name = "TestAgent"
mock_agent.version = "2.0.0"

# Create a mock AgentRunContext
mock_context = MagicMock(spec=AgentRunContext)
mock_context.get_agent_id_object.return_value = mock_agent

# Call get_agent_name
result = get_agent_name(mock_context)

# Assert result is a tuple of length 2
assert isinstance(result, tuple)
assert len(result) == 2

def test_get_agent_name_with_special_characters(self):
"""Test get_agent_name with special characters in agent name and version."""
# Create a mock agent object with special characters
mock_agent = MagicMock()
mock_agent.name = "Test-Agent_v2"
mock_agent.version = "2.0.0-beta.1"

# Create a mock AgentRunContext
mock_context = MagicMock(spec=AgentRunContext)
mock_context.get_agent_id_object.return_value = mock_agent

# Call get_agent_name
agent_name, agent_id = get_agent_name(mock_context)

# Assert results
assert agent_name == "Test-Agent_v2"
assert agent_id == "Test-Agent_v2:2.0.0-beta.1"
Loading
Loading