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
2 changes: 1 addition & 1 deletion sdk/ai/azure-ai-projects/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "python",
"TagPrefix": "python/ai/azure-ai-projects",
"Tag": "python/ai/azure-ai-projects_875c4f833f"
"Tag": "python/ai/azure-ai-projects_7959346120"
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
AGENT_TYPE_PROMPT,
AGENT_TYPE_WORKFLOW,
AGENT_TYPE_HOSTED,
AGENT_TYPE_EXTERNAL,
AGENT_TYPE_UNKNOWN,
GEN_AI_AGENT_TYPE,
ERROR_MESSAGE,
Expand Down Expand Up @@ -901,9 +902,20 @@ def _create_agent_span_from_parameters(
hosted_cpu = definition.get("cpu")
hosted_memory = definition.get("memory")
hosted_image = definition.get("image")
# Also check container_configuration for image
if not hosted_image:
container_config = definition.get("container_configuration")
if container_config:
if hasattr(container_config, "image"):
hosted_image = getattr(container_config, "image", None)
elif isinstance(container_config, dict):
hosted_image = container_config.get("image")
hosted_protocol = None
hosted_protocol_version = None
# Check both old and new field names for protocol versions
container_protocol_versions = definition.get("container_protocol_versions")
if not container_protocol_versions:
container_protocol_versions = definition.get("protocol_versions")
if container_protocol_versions and len(container_protocol_versions) > 0:
# Extract protocol and version from first entry
protocol_record = container_protocol_versions[0]
Expand All @@ -920,10 +932,21 @@ def _create_agent_span_from_parameters(
# Determine agent type from definition
# Check for hosted agent first (most specific - has container/image configuration)
agent_type = None
# Extract kind discriminator for external agents
kind = definition.get("kind")
if isinstance(kind, str):
kind = kind.lower()
elif hasattr(kind, "value"):
kind = str(kind.value).lower()
else:
kind = None

if hosted_image or hosted_cpu or hosted_memory:
agent_type = AGENT_TYPE_HOSTED
elif workflow_yaml:
agent_type = AGENT_TYPE_WORKFLOW
elif kind == "external" or definition.get("otel_agent_id") is not None:
agent_type = AGENT_TYPE_EXTERNAL
Comment on lines +948 to +949
elif instructions or model:
# Prompt agent - identified by having instructions and/or a model.
# Note: An agent with only a model (no instructions) is treated as a prompt agent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
AGENT_TYPE_PROMPT = "prompt"
AGENT_TYPE_WORKFLOW = "workflow"
AGENT_TYPE_HOSTED = "hosted"
AGENT_TYPE_EXTERNAL = "external"
AGENT_TYPE_UNKNOWN = "unknown"

# Span name prefixes for responses API operations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
)
from azure.ai.projects.telemetry import AIProjectInstrumentor, _utils
from azure.core.settings import settings
from azure.ai.projects.models import PromptAgentDefinition, PromptAgentDefinitionTextOptions
from azure.ai.projects.models import PromptAgentDefinition, PromptAgentDefinitionTextOptions, ExternalAgentDefinition
from azure.ai.projects.telemetry._utils import (
GEN_AI_AGENT_ID,
GEN_AI_AGENT_NAME,
Expand All @@ -34,6 +34,7 @@
AGENTS_PROVIDER,
AGENT_TYPE_PROMPT,
AGENT_TYPE_WORKFLOW,
AGENT_TYPE_EXTERNAL,
_set_use_message_events,
)

Expand Down Expand Up @@ -1097,3 +1098,63 @@ def test_agent_with_structured_output_without_instructions_content_recording_dis
self._test_agent_with_structured_output_without_instructions_impl(
use_events=False, content_recording_enabled=False, **kwargs
)

# ---- External agent creation tests ----

def _test_external_agent_creation_impl(self, content_recording_enabled: bool, **kwargs):
"""Implementation for external agent creation test.

:param content_recording_enabled: Whether content recording is enabled.
:type content_recording_enabled: bool
"""
self.cleanup()
os.environ.update({CONTENT_TRACING_ENV_VARIABLE: "True" if content_recording_enabled else "False"})
self.setup_telemetry()
assert content_recording_enabled == AIProjectInstrumentor().is_content_recording_enabled()
assert True == AIProjectInstrumentor().is_instrumented()

operation_group = "tracing" if content_recording_enabled else "agents"
with self.create_client(operation_group=operation_group, allow_preview=True, **kwargs) as project_client:

agent = project_client.agents.create_version(
agent_name="my-external-agent",
definition=ExternalAgentDefinition(otel_agent_id="external-service-agent-001"),
)
version = agent.version

project_client.agents.delete_version(agent_name=agent.name, agent_version=agent.version)
print("Deleted external agent")

# ------------------------- Validate "create_agent" span ---------------------------------
self.exporter.force_flush()
spans = self.exporter.get_spans_by_name("create_agent my-external-agent")
assert len(spans) == 1
span = spans[0]
expected_attributes = [
(GEN_AI_PROVIDER_NAME, AGENTS_PROVIDER),
(GEN_AI_OPERATION_NAME, "create_agent"),
(SERVER_ADDRESS, ""),
(GEN_AI_AGENT_NAME, "my-external-agent"),
(GEN_AI_AGENT_ID, "my-external-agent:" + str(version)),
(GEN_AI_AGENT_VERSION, str(version)),
(GEN_AI_AGENT_TYPE, AGENT_TYPE_EXTERNAL),
]
attributes_match = GenAiTraceVerifier().check_span_attributes(span, expected_attributes)
assert attributes_match == True

# External agents have no instructions, no model, no tools — so no events or content attributes
assert len(span.events) == 0

@pytest.mark.usefixtures("instrument_with_content")
@servicePreparer()
@recorded_by_proxy
def test_external_agent_creation_with_tracing_content_recording_enabled(self, **kwargs):
"""Test external agent creation with content recording enabled."""
self._test_external_agent_creation_impl(content_recording_enabled=True, **kwargs)

@pytest.mark.usefixtures("instrument_without_content")
@servicePreparer()
@recorded_by_proxy
def test_external_agent_creation_with_tracing_content_recording_disabled(self, **kwargs):
"""Test external agent creation with content recording disabled."""
self._test_external_agent_creation_impl(content_recording_enabled=False, **kwargs)
Loading