Skip to content

[Task]32767979: Support AI Foundry by Handling GEN_AI_SYSTEM Attributes #41705

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

### Other Changes

- Support AI Foundry by Handling GEN_AI_SYSTEM Attributes with [Spec](https://github.com/aep-health-and-standards/Telemetry-Collection-Spec/blob/main/ApplicationInsights/genai_semconv_mapping.md) ([#41705](https://github.com/Azure/azure-sdk-for-python/pull/41705))

## 1.0.0b39 (2025-06-25)

### Bugs Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@
_SAMPLE_RATE_KEY,
]

_GEN_AI_ATTRIBUTE_PREFIX = "GenAI | {}"


class AzureMonitorTraceExporter(BaseExporter, SpanExporter):
"""Azure Monitor Trace exporter for OpenTelemetry."""
Expand Down Expand Up @@ -348,7 +350,7 @@ def _convert_span_to_envelope(span: ReadableSpan) -> TelemetryItem:
# data
if url:
data.data = url
target, path = trace_utils._get_target_and_path_for_http_dependency(
target, path = trace_utils._get_target_and_path_for_http_dependency_with_gen_ai(
span.attributes,
url,
)
Expand Down Expand Up @@ -406,10 +408,12 @@ def _convert_span_to_envelope(span: ReadableSpan) -> TelemetryItem:
target, # type: ignore
span.attributes,
)
elif gen_ai_attributes.GEN_AI_SYSTEM in span.attributes: # GenAI
data.type = span.attributes[gen_ai_attributes.GEN_AI_SYSTEM]
else:
data.type = "N/A"
# gen_ai take precedence over other mappings (ex. HTTP) even if their attributes are also present on the span.
# overrides any previously set type.
if gen_ai_attributes.GEN_AI_SYSTEM in span.attributes: # GenAI
data.type = _GEN_AI_ATTRIBUTE_PREFIX.format(span.attributes[gen_ai_attributes.GEN_AI_SYSTEM])
elif span.kind is SpanKind.PRODUCER: # Messaging
# Currently only eventhub and servicebus are supported that produce PRODUCER spans
if _AZURE_SDK_NAMESPACE_NAME in span.attributes:
Expand All @@ -426,7 +430,9 @@ def _convert_span_to_envelope(span: ReadableSpan) -> TelemetryItem:
)
else: # SpanKind.INTERNAL
data.type = "InProc"
if _AZURE_SDK_NAMESPACE_NAME in span.attributes:
if gen_ai_attributes.GEN_AI_SYSTEM in span.attributes: # GenAI
data.type = _GEN_AI_ATTRIBUTE_PREFIX.format(span.attributes[gen_ai_attributes.GEN_AI_SYSTEM])
elif _AZURE_SDK_NAMESPACE_NAME in span.attributes:
data.type += " | {}".format(span.attributes[_AZURE_SDK_NAMESPACE_NAME])
# Apply truncation
# See https://github.com/MohanGsk/ApplicationInsights-Home/tree/master/EndpointSpecs/Schemas/Bond
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
user_agent_attributes,
)
from opentelemetry.semconv.trace import DbSystemValues, SpanAttributes
from opentelemetry.semconv._incubating.attributes import gen_ai_attributes
from opentelemetry.util.types import Attributes


Expand Down Expand Up @@ -207,6 +208,23 @@ def _get_target_and_path_for_http_dependency(
target = _get_target_for_dependency_from_peer(attributes)
return (target, path)

@no_type_check
def _get_target_and_path_for_http_dependency_with_gen_ai(
attributes: Attributes,
url: Optional[str] = "", # Usually populated by _get_url_for_http_dependency()
) -> Tuple[Optional[str], str]:
target = ""
path = "/"
# set target using standard rules
target, path = _get_target_and_path_for_http_dependency(attributes, url)

# If no fields are available to set target using standard rules, set Dependency Target to gen_ai.system if present
if not target and attributes and gen_ai_attributes.GEN_AI_SYSTEM in attributes:
gen_ai_system = attributes[gen_ai_attributes.GEN_AI_SYSTEM]
if gen_ai_system:
target = gen_ai_system
return (target, path)


@no_type_check
def _get_target_for_db_dependency(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,15 @@ def test_span_to_envelope_client_http(self):
envelope = exporter._span_to_envelope(span)
self.assertEqual(envelope.data.base_data.target, "www.example.com")

# Set target with gen_ai if no fields are available to set target using standard rules
span._attributes = {
"http.request.method": "GET",
"gen_ai.system": "az.ai.inference"
}
envelope = exporter._span_to_envelope(span)
self.assertEqual(envelope.data.base_data.target, "az.ai.inference")
self.assertEqual(envelope.data.base_data.name, "GET /")

# url
# spell-checker:ignore ddds
span._attributes = {
Expand Down Expand Up @@ -760,8 +769,57 @@ def test_span_to_envelope_client_gen_ai(self):
self.assertEqual(envelope.data.base_data.result_code, "0")

self.assertEqual(envelope.data.base_type, "RemoteDependencyData")
self.assertEqual(envelope.data.base_data.type, "az.ai.inference")
self.assertEqual(envelope.data.base_data.type, "GenAI | az.ai.inference")
self.assertEqual(len(envelope.data.base_data.properties), 1)

def test_span_to_envelope_client_internal_gen_ai_type(self):
exporter = self._exporter
start_time = 1575494316027613500
end_time = start_time + 1001000000

span = trace._Span(
name="test",
context=SpanContext(
trace_id=36873507687745823477771305566750195431,
span_id=12030755672171557337,
is_remote=False,
),
attributes={
"gen_ai.system": "az.ai.inference",
},
kind=SpanKind.INTERNAL,
)
span.start(start_time=start_time)
span.end(end_time=end_time)
span._status = Status(status_code=StatusCode.UNSET)
envelope = exporter._span_to_envelope(span)

self.assertEqual(envelope.data.base_data.type, "GenAI | az.ai.inference")

def test_span_to_envelope_client_mutiple_types_with_gen_ai(self):
exporter = self._exporter
start_time = 1575494316027613500
end_time = start_time + 1001000000

span = trace._Span(
name="test",
context=SpanContext(
trace_id=36873507687745823477771305566750195431,
span_id=12030755672171557337,
is_remote=False,
),
attributes={
"gen_ai.system": "az.ai.inference",
"az.namespace": "Microsoft.EventHub",
},
kind=SpanKind.INTERNAL,
)
span.start(start_time=start_time)
span.end(end_time=end_time)
span._status = Status(status_code=StatusCode.UNSET)
envelope = exporter._span_to_envelope(span)

self.assertEqual(envelope.data.base_data.type, "GenAI | az.ai.inference")

def test_span_to_envelope_client_azure(self):
exporter = self._exporter
Expand Down
Loading