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 @@ -333,6 +335,13 @@ def _convert_span_to_envelope(span: ReadableSpan) -> TelemetryItem:
envelope.data = MonitorBase(base_data=data, base_type="RemoteDependencyData")
target = trace_utils._get_target_for_dependency_from_peer(span.attributes)
if span.kind is SpanKind.CLIENT:
gen_ai_attributes_val = ""
# gen_ai take precedence over other mappings (ex. HTTP) even if their attributes are also present on the span.
# following mappings will override the type
if gen_ai_attributes.GEN_AI_SYSTEM in span.attributes: # GenAI
gen_ai_attributes_val = span.attributes[gen_ai_attributes.GEN_AI_SYSTEM]
if gen_ai_attributes_val:
data.type = _GEN_AI_ATTRIBUTE_PREFIX.format(gen_ai_attributes_val)
if _AZURE_SDK_NAMESPACE_NAME in span.attributes: # Azure specific resources
Copy link
Member

Choose a reason for hiding this comment

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

This should be an elif:.

# Currently only eventhub and servicebus are supported
# https://github.com/Azure/azure-sdk-for-python/issues/9256
Expand Down Expand Up @@ -406,10 +415,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"

# If no fields are available to set target using standard rules, set Dependency Target to gen_ai.system if present
if not target and gen_ai_attributes_val:
target = gen_ai_attributes_val
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 +437,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 @@ -438,6 +438,24 @@ def test_span_to_envelope_client_http(self):
envelope = exporter._span_to_envelope(span)
self.assertEqual(envelope.data.base_data.target, "www.example.com")

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 /")

span._attributes = {
"http.request.method": "GET",
"server.address": "www.example.com",
"server.port": 80,
"url.scheme": "http",
"gen_ai.system": "az.ai.inference"
}
envelope = exporter._span_to_envelope(span)
self.assertEqual(envelope.data.base_data.target, "www.example.com")

# url
# spell-checker:ignore ddds
span._attributes = {
Expand Down Expand Up @@ -760,8 +778,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, "N/A")
Copy link
Member Author

Choose a reason for hiding this comment

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

@lzchen once gen_ai mappings is set at the top, this test case will be returning "N/A" instead of gen_ai. Just to confirm, it is what we are expecting.

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