From 6f37824223fb9312ee186ff5c089b999b4928792 Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Mon, 14 Jul 2025 12:50:34 -0700 Subject: [PATCH 1/4] Agents SDK telemetry improvements: add az.namespace and schema url --- .../agents/telemetry/_ai_agents_instrumentor.py | 2 ++ .../azure/ai/agents/telemetry/_utils.py | 8 ++++++-- .../tests/gen_ai_trace_verifier.py | 3 +++ .../tests/test_ai_agents_instrumentor_async.py | 1 + .../monitor/opentelemetry/exporter/_constants.py | 3 +++ .../exporter/export/trace/_exporter.py | 16 +++++++++++----- 6 files changed, 26 insertions(+), 7 deletions(-) diff --git a/sdk/ai/azure-ai-agents/azure/ai/agents/telemetry/_ai_agents_instrumentor.py b/sdk/ai/azure-ai-agents/azure/ai/agents/telemetry/_ai_agents_instrumentor.py index b7aa476b4ecd..0ea222efc7f9 100644 --- a/sdk/ai/azure-ai-agents/azure/ai/agents/telemetry/_ai_agents_instrumentor.py +++ b/sdk/ai/azure-ai-agents/azure/ai/agents/telemetry/_ai_agents_instrumentor.py @@ -47,6 +47,8 @@ from azure.core.tracing import AbstractSpan from ._utils import ( AZ_AI_AGENT_SYSTEM, + AZ_NAMESPACE, + AZ_NAMESPACE_VALUE, ERROR_TYPE, GEN_AI_AGENT_DESCRIPTION, GEN_AI_AGENT_ID, diff --git a/sdk/ai/azure-ai-agents/azure/ai/agents/telemetry/_utils.py b/sdk/ai/azure-ai-agents/azure/ai/agents/telemetry/_utils.py index 10c6f252a3e5..c4bacc200257 100644 --- a/sdk/ai/azure-ai-agents/azure/ai/agents/telemetry/_utils.py +++ b/sdk/ai/azure-ai-agents/azure/ai/agents/telemetry/_utils.py @@ -38,6 +38,8 @@ GEN_AI_SYSTEM = "gen_ai.system" SERVER_ADDRESS = "server.address" AZ_AI_AGENT_SYSTEM = "az.ai.agents" +AZ_NAMESPACE = "az.namespace" +AZ_NAMESPACE_VALUE = "Microsoft.CognitiveServices" GEN_AI_TOOL_NAME = "gen_ai.tool.name" GEN_AI_TOOL_CALL_ID = "gen_ai.tool.call.id" GEN_AI_REQUEST_RESPONSE_FORMAT = "gen_ai.request.response_format" @@ -50,7 +52,7 @@ GEN_AI_RUN_STEP_STATUS = "gen_ai.run_step.status" ERROR_TYPE = "error.type" ERROR_MESSAGE = "error.message" - +GEN_AI_SEMCONV_SCHEMA_VERSION = "1.35.0" class OperationName(Enum): CREATE_AGENT = "create_agent" @@ -114,9 +116,11 @@ def start_span( if _span_impl_type is None: return None - span = _span_impl_type(name=span_name or operation_name.value, kind=kind) + + span = _span_impl_type(name=span_name or operation_name.value, kind=kind, schema_version=GEN_AI_SEMCONV_SCHEMA_VERSION) if span and span.span_instance.is_recording: + span.add_attribute(AZ_NAMESPACE, AZ_NAMESPACE_VALUE) if gen_ai_system: span.add_attribute(GEN_AI_SYSTEM, AZ_AI_AGENT_SYSTEM) diff --git a/sdk/ai/azure-ai-agents/tests/gen_ai_trace_verifier.py b/sdk/ai/azure-ai-agents/tests/gen_ai_trace_verifier.py index 94ffdf1fd832..be5107bb9296 100644 --- a/sdk/ai/azure-ai-agents/tests/gen_ai_trace_verifier.py +++ b/sdk/ai/azure-ai-agents/tests/gen_ai_trace_verifier.py @@ -12,8 +12,11 @@ class GenAiTraceVerifier: def check_span_attributes(self, span, attributes): + assert "https://opentelemetry.io/schemas/1.35.0" == span.instrumentation_scope.schema_url + # Convert the list of tuples to a dictionary for easier lookup attribute_dict = dict(attributes) + attribute_dict["az.namespace"] = "Microsoft.CognitiveServices" for attribute_name in span.attributes.keys(): # Check if the attribute name exists in the input attributes diff --git a/sdk/ai/azure-ai-agents/tests/test_ai_agents_instrumentor_async.py b/sdk/ai/azure-ai-agents/tests/test_ai_agents_instrumentor_async.py index 832b1598d8e8..471d257ff618 100644 --- a/sdk/ai/azure-ai-agents/tests/test_ai_agents_instrumentor_async.py +++ b/sdk/ai/azure-ai-agents/tests/test_ai_agents_instrumentor_async.py @@ -146,6 +146,7 @@ async def test_agent_chat_with_tracing_content_recording_enabled(self, **kwargs) spans = self.exporter.get_spans_by_name("create_agent my-agent") assert len(spans) == 1 span = spans[0] + assert span.instrumentation_scope.schema_url == "https://opentelemetry.io/schemas/1.35.0" expected_attributes = [ ("gen_ai.system", "az.ai.agents"), ("gen_ai.operation.name", "create_agent"), diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_constants.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_constants.py index 20b22e91e710..70f4303beba1 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_constants.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_constants.py @@ -194,6 +194,8 @@ class _RP_Names(Enum): # Special constant for azure-sdk opentelemetry instrumentation _AZURE_SDK_OPENTELEMETRY_NAME = "azure-sdk-opentelemetry" _AZURE_SDK_NAMESPACE_NAME = "az.namespace" +# TODO: do we need -opentelemetry suffix? +_AZURE_AI_SDK_NAME = "azure-ai" _BASE = 2 @@ -253,6 +255,7 @@ class _RP_Names(Enum): "openai_v2", "vertexai", # Instrumentations below this line have not been added to statsbeat report yet + _AZURE_AI_SDK_NAME ] _INSTRUMENTATIONS_BIT_MAP = {_INSTRUMENTATIONS_LIST[i]: _BASE**i for i in range(len(_INSTRUMENTATIONS_LIST))} diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_exporter.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_exporter.py index 00335525d370..494f1bcc8d23 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_exporter.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_exporter.py @@ -23,6 +23,7 @@ _APPLICATIONINSIGHTS_OPENTELEMETRY_RESOURCE_METRIC_DISABLED, _AZURE_SDK_NAMESPACE_NAME, _AZURE_SDK_OPENTELEMETRY_NAME, + _AZURE_AI_SDK_NAME, _INSTRUMENTATION_SUPPORTING_METRICS_LIST, _SAMPLE_RATE_KEY, _METRIC_ENVELOPE_NAME, @@ -525,13 +526,18 @@ def _convert_span_events_to_envelopes(span: ReadableSpan) -> Sequence[TelemetryI def _check_instrumentation_span(span: ReadableSpan) -> None: - # Special use-case for spans generated from azure-sdk services - # Identified by having az.namespace as a span attribute - if span.attributes and _AZURE_SDK_NAMESPACE_NAME in span.attributes: - _utils.add_instrumentation(_AZURE_SDK_OPENTELEMETRY_NAME) - return if span.instrumentation_scope is None: return + + # Special use-case for spans generated from azure-sdk services + # `azure-` or `azure.` is a prefix + if span.instrumentation_scope.name.startswith("azure"): + # spec-case for Azure AI SDKs - identified by `az.namespace` attribute + if (span.attributes.get(_AZURE_SDK_NAMESPACE_NAME or None) == "Microsoft.CognitiveServices"): + _utils.add_instrumentation(_AZURE_AI_SDK_NAME) + else: + _utils.add_instrumentation(_AZURE_SDK_OPENTELEMETRY_NAME) + return # All instrumentation scope names from OpenTelemetry instrumentations have # `opentelemetry.instrumentation.` as a prefix if span.instrumentation_scope.name.startswith("opentelemetry.instrumentation."): From 8ec4d15c5b88eb66ed16804ff5707b304d4c27fb Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Mon, 14 Jul 2025 16:37:08 -0700 Subject: [PATCH 2/4] undo monitor changes --- .../monitor/opentelemetry/exporter/_constants.py | 3 --- .../exporter/export/trace/_exporter.py | 16 +++++----------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_constants.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_constants.py index 70f4303beba1..20b22e91e710 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_constants.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_constants.py @@ -194,8 +194,6 @@ class _RP_Names(Enum): # Special constant for azure-sdk opentelemetry instrumentation _AZURE_SDK_OPENTELEMETRY_NAME = "azure-sdk-opentelemetry" _AZURE_SDK_NAMESPACE_NAME = "az.namespace" -# TODO: do we need -opentelemetry suffix? -_AZURE_AI_SDK_NAME = "azure-ai" _BASE = 2 @@ -255,7 +253,6 @@ class _RP_Names(Enum): "openai_v2", "vertexai", # Instrumentations below this line have not been added to statsbeat report yet - _AZURE_AI_SDK_NAME ] _INSTRUMENTATIONS_BIT_MAP = {_INSTRUMENTATIONS_LIST[i]: _BASE**i for i in range(len(_INSTRUMENTATIONS_LIST))} diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_exporter.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_exporter.py index 494f1bcc8d23..00335525d370 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_exporter.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_exporter.py @@ -23,7 +23,6 @@ _APPLICATIONINSIGHTS_OPENTELEMETRY_RESOURCE_METRIC_DISABLED, _AZURE_SDK_NAMESPACE_NAME, _AZURE_SDK_OPENTELEMETRY_NAME, - _AZURE_AI_SDK_NAME, _INSTRUMENTATION_SUPPORTING_METRICS_LIST, _SAMPLE_RATE_KEY, _METRIC_ENVELOPE_NAME, @@ -526,17 +525,12 @@ def _convert_span_events_to_envelopes(span: ReadableSpan) -> Sequence[TelemetryI def _check_instrumentation_span(span: ReadableSpan) -> None: - if span.instrumentation_scope is None: - return - # Special use-case for spans generated from azure-sdk services - # `azure-` or `azure.` is a prefix - if span.instrumentation_scope.name.startswith("azure"): - # spec-case for Azure AI SDKs - identified by `az.namespace` attribute - if (span.attributes.get(_AZURE_SDK_NAMESPACE_NAME or None) == "Microsoft.CognitiveServices"): - _utils.add_instrumentation(_AZURE_AI_SDK_NAME) - else: - _utils.add_instrumentation(_AZURE_SDK_OPENTELEMETRY_NAME) + # Identified by having az.namespace as a span attribute + if span.attributes and _AZURE_SDK_NAMESPACE_NAME in span.attributes: + _utils.add_instrumentation(_AZURE_SDK_OPENTELEMETRY_NAME) + return + if span.instrumentation_scope is None: return # All instrumentation scope names from OpenTelemetry instrumentations have # `opentelemetry.instrumentation.` as a prefix From 285c4e47c67f7a8a12f26d89f137240862b721d7 Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Mon, 14 Jul 2025 16:51:35 -0700 Subject: [PATCH 3/4] update to 1.34.0 --- sdk/ai/azure-ai-agents/azure/ai/agents/telemetry/_utils.py | 2 +- sdk/ai/azure-ai-agents/tests/gen_ai_trace_verifier.py | 2 +- .../azure-ai-agents/tests/test_ai_agents_instrumentor_async.py | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/sdk/ai/azure-ai-agents/azure/ai/agents/telemetry/_utils.py b/sdk/ai/azure-ai-agents/azure/ai/agents/telemetry/_utils.py index c4bacc200257..5d40622ff970 100644 --- a/sdk/ai/azure-ai-agents/azure/ai/agents/telemetry/_utils.py +++ b/sdk/ai/azure-ai-agents/azure/ai/agents/telemetry/_utils.py @@ -52,7 +52,7 @@ GEN_AI_RUN_STEP_STATUS = "gen_ai.run_step.status" ERROR_TYPE = "error.type" ERROR_MESSAGE = "error.message" -GEN_AI_SEMCONV_SCHEMA_VERSION = "1.35.0" +GEN_AI_SEMCONV_SCHEMA_VERSION = "1.34.0" class OperationName(Enum): CREATE_AGENT = "create_agent" diff --git a/sdk/ai/azure-ai-agents/tests/gen_ai_trace_verifier.py b/sdk/ai/azure-ai-agents/tests/gen_ai_trace_verifier.py index be5107bb9296..805ba951cdb6 100644 --- a/sdk/ai/azure-ai-agents/tests/gen_ai_trace_verifier.py +++ b/sdk/ai/azure-ai-agents/tests/gen_ai_trace_verifier.py @@ -12,7 +12,7 @@ class GenAiTraceVerifier: def check_span_attributes(self, span, attributes): - assert "https://opentelemetry.io/schemas/1.35.0" == span.instrumentation_scope.schema_url + assert "https://opentelemetry.io/schemas/1.34.0" == span.instrumentation_scope.schema_url # Convert the list of tuples to a dictionary for easier lookup attribute_dict = dict(attributes) diff --git a/sdk/ai/azure-ai-agents/tests/test_ai_agents_instrumentor_async.py b/sdk/ai/azure-ai-agents/tests/test_ai_agents_instrumentor_async.py index 471d257ff618..832b1598d8e8 100644 --- a/sdk/ai/azure-ai-agents/tests/test_ai_agents_instrumentor_async.py +++ b/sdk/ai/azure-ai-agents/tests/test_ai_agents_instrumentor_async.py @@ -146,7 +146,6 @@ async def test_agent_chat_with_tracing_content_recording_enabled(self, **kwargs) spans = self.exporter.get_spans_by_name("create_agent my-agent") assert len(spans) == 1 span = spans[0] - assert span.instrumentation_scope.schema_url == "https://opentelemetry.io/schemas/1.35.0" expected_attributes = [ ("gen_ai.system", "az.ai.agents"), ("gen_ai.operation.name", "create_agent"), From 31c3d030dd281137778e088a82b41eed76e7a5dc Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Mon, 14 Jul 2025 23:05:25 -0700 Subject: [PATCH 4/4] lint --- .../azure/ai/agents/telemetry/_ai_agents_instrumentor.py | 2 -- sdk/ai/azure-ai-agents/azure/ai/agents/telemetry/_utils.py | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/sdk/ai/azure-ai-agents/azure/ai/agents/telemetry/_ai_agents_instrumentor.py b/sdk/ai/azure-ai-agents/azure/ai/agents/telemetry/_ai_agents_instrumentor.py index 0ea222efc7f9..b7aa476b4ecd 100644 --- a/sdk/ai/azure-ai-agents/azure/ai/agents/telemetry/_ai_agents_instrumentor.py +++ b/sdk/ai/azure-ai-agents/azure/ai/agents/telemetry/_ai_agents_instrumentor.py @@ -47,8 +47,6 @@ from azure.core.tracing import AbstractSpan from ._utils import ( AZ_AI_AGENT_SYSTEM, - AZ_NAMESPACE, - AZ_NAMESPACE_VALUE, ERROR_TYPE, GEN_AI_AGENT_DESCRIPTION, GEN_AI_AGENT_ID, diff --git a/sdk/ai/azure-ai-agents/azure/ai/agents/telemetry/_utils.py b/sdk/ai/azure-ai-agents/azure/ai/agents/telemetry/_utils.py index 5d40622ff970..b24d07345d72 100644 --- a/sdk/ai/azure-ai-agents/azure/ai/agents/telemetry/_utils.py +++ b/sdk/ai/azure-ai-agents/azure/ai/agents/telemetry/_utils.py @@ -52,7 +52,7 @@ GEN_AI_RUN_STEP_STATUS = "gen_ai.run_step.status" ERROR_TYPE = "error.type" ERROR_MESSAGE = "error.message" -GEN_AI_SEMCONV_SCHEMA_VERSION = "1.34.0" +GEN_AI_SEMANTIC_CONVENTIONS_SCHEMA_VERSION = "1.34.0" class OperationName(Enum): CREATE_AGENT = "create_agent" @@ -117,7 +117,7 @@ def start_span( return None - span = _span_impl_type(name=span_name or operation_name.value, kind=kind, schema_version=GEN_AI_SEMCONV_SCHEMA_VERSION) + span = _span_impl_type(name=span_name or operation_name.value, kind=kind, schema_version=GEN_AI_SEMANTIC_CONVENTIONS_SCHEMA_VERSION) if span and span.span_instance.is_recording: span.add_attribute(AZ_NAMESPACE, AZ_NAMESPACE_VALUE)