Skip to content

Commit 72cb66a

Browse files
authored
[Task]32767979: Support AI Foundry by Handling GEN_AI_SYSTEM Attributes (#41705)
* add azure monitor exporter trace ai field * update * update * update * update * update * udpate * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update
1 parent 48a23de commit 72cb66a

File tree

3 files changed

+91
-3
lines changed

3 files changed

+91
-3
lines changed

sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
([#41971](https://github.com/Azure/azure-sdk-for-python/pull/41971))
1515

1616

17+
- 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))
18+
1719
## 1.0.0b39 (2025-06-25)
1820

1921
### Bugs Fixed

sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_exporter.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@
9797
_SAMPLE_RATE_KEY,
9898
]
9999

100+
_GEN_AI_ATTRIBUTE_PREFIX = "GenAI | {}"
101+
100102

101103
class AzureMonitorTraceExporter(BaseExporter, SpanExporter):
102104
"""Azure Monitor Trace exporter for OpenTelemetry."""
@@ -334,6 +336,9 @@ def _convert_span_to_envelope(span: ReadableSpan) -> TelemetryItem:
334336
envelope.data = MonitorBase(base_data=data, base_type="RemoteDependencyData")
335337
target = trace_utils._get_target_for_dependency_from_peer(span.attributes)
336338
if span.kind is SpanKind.CLIENT:
339+
gen_ai_attributes_val = ""
340+
if gen_ai_attributes.GEN_AI_SYSTEM in span.attributes: # GenAI
341+
gen_ai_attributes_val = span.attributes[gen_ai_attributes.GEN_AI_SYSTEM]
337342
if _AZURE_SDK_NAMESPACE_NAME in span.attributes: # Azure specific resources
338343
# Currently only eventhub and servicebus are supported
339344
# https://github.com/Azure/azure-sdk-for-python/issues/9256
@@ -408,9 +413,18 @@ def _convert_span_to_envelope(span: ReadableSpan) -> TelemetryItem:
408413
span.attributes,
409414
)
410415
elif gen_ai_attributes.GEN_AI_SYSTEM in span.attributes: # GenAI
411-
data.type = span.attributes[gen_ai_attributes.GEN_AI_SYSTEM]
416+
data.type = _GEN_AI_ATTRIBUTE_PREFIX.format(gen_ai_attributes_val)
412417
else:
413418
data.type = "N/A"
419+
# gen_ai take precedence over other mappings (ex. HTTP)
420+
# even if their attributes are also present on the span.
421+
# following mappings will override the type
422+
if gen_ai_attributes_val:
423+
data.type = _GEN_AI_ATTRIBUTE_PREFIX.format(gen_ai_attributes_val)
424+
# If no fields are available to set target using standard rules,
425+
# set Dependency Target to gen_ai.system if present
426+
if not target and not data.target and gen_ai_attributes_val:
427+
target = gen_ai_attributes_val
414428
elif span.kind is SpanKind.PRODUCER: # Messaging
415429
# Currently only eventhub and servicebus are supported that produce PRODUCER spans
416430
if _AZURE_SDK_NAMESPACE_NAME in span.attributes:
@@ -427,7 +441,9 @@ def _convert_span_to_envelope(span: ReadableSpan) -> TelemetryItem:
427441
)
428442
else: # SpanKind.INTERNAL
429443
data.type = "InProc"
430-
if _AZURE_SDK_NAMESPACE_NAME in span.attributes:
444+
if gen_ai_attributes.GEN_AI_SYSTEM in span.attributes: # GenAI
445+
data.type = _GEN_AI_ATTRIBUTE_PREFIX.format(span.attributes[gen_ai_attributes.GEN_AI_SYSTEM])
446+
elif _AZURE_SDK_NAMESPACE_NAME in span.attributes:
431447
data.type += " | {}".format(span.attributes[_AZURE_SDK_NAMESPACE_NAME])
432448
# Apply truncation
433449
# See https://github.com/MohanGsk/ApplicationInsights-Home/tree/master/EndpointSpecs/Schemas/Bond

sdk/monitor/azure-monitor-opentelemetry-exporter/tests/trace/test_trace.py

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,24 @@ def test_span_to_envelope_client_http(self):
451451
envelope = exporter._span_to_envelope(span)
452452
self.assertEqual(envelope.data.base_data.target, "www.example.com")
453453

454+
span._attributes = {
455+
"http.request.method": "GET",
456+
"gen_ai.system": "az.ai.inference"
457+
}
458+
envelope = exporter._span_to_envelope(span)
459+
self.assertEqual(envelope.data.base_data.target, "az.ai.inference")
460+
self.assertEqual(envelope.data.base_data.name, "GET /")
461+
462+
span._attributes = {
463+
"http.request.method": "GET",
464+
"server.address": "www.example.com",
465+
"server.port": 80,
466+
"url.scheme": "http",
467+
"gen_ai.system": "az.ai.inference"
468+
}
469+
envelope = exporter._span_to_envelope(span)
470+
self.assertEqual(envelope.data.base_data.target, "www.example.com")
471+
454472
# url
455473
# spell-checker:ignore ddds
456474
span._attributes = {
@@ -773,8 +791,60 @@ def test_span_to_envelope_client_gen_ai(self):
773791
self.assertEqual(envelope.data.base_data.result_code, "0")
774792

775793
self.assertEqual(envelope.data.base_type, "RemoteDependencyData")
776-
self.assertEqual(envelope.data.base_data.type, "az.ai.inference")
794+
self.assertEqual(envelope.data.base_data.type, "GenAI | az.ai.inference")
795+
self.assertEqual(envelope.data.base_data.target, "az.ai.inference")
777796
self.assertEqual(len(envelope.data.base_data.properties), 1)
797+
798+
def test_span_to_envelope_client_internal_gen_ai_type(self):
799+
exporter = self._exporter
800+
start_time = 1575494316027613500
801+
end_time = start_time + 1001000000
802+
803+
span = trace._Span(
804+
name="test",
805+
context=SpanContext(
806+
trace_id=36873507687745823477771305566750195431,
807+
span_id=12030755672171557337,
808+
is_remote=False,
809+
),
810+
attributes={
811+
"gen_ai.system": "az.ai.inference",
812+
},
813+
kind=SpanKind.INTERNAL,
814+
)
815+
span.start(start_time=start_time)
816+
span.end(end_time=end_time)
817+
span._status = Status(status_code=StatusCode.UNSET)
818+
envelope = exporter._span_to_envelope(span)
819+
self.assertEqual(envelope.data.base_data.type, "GenAI | az.ai.inference")
820+
821+
def test_span_to_envelope_client_multiple_types_with_gen_ai(self):
822+
exporter = self._exporter
823+
start_time = 1575494316027613500
824+
end_time = start_time + 1001000000
825+
826+
span = trace._Span(
827+
name="test",
828+
context=SpanContext(
829+
trace_id=36873507687745823477771305566750195431,
830+
span_id=12030755672171557337,
831+
is_remote=False,
832+
),
833+
attributes={
834+
"gen_ai.system": "az.ai.inference",
835+
"az.namespace": "Microsoft.EventHub",
836+
"peer.address": "test_address",
837+
"message_bus.destination": "test_destination",
838+
},
839+
kind=SpanKind.CLIENT,
840+
)
841+
span.start(start_time=start_time)
842+
span.end(end_time=end_time)
843+
span._status = Status(status_code=StatusCode.UNSET)
844+
envelope = exporter._span_to_envelope(span)
845+
846+
self.assertEqual(envelope.data.base_data.type, "GenAI | az.ai.inference")
847+
self.assertEqual(envelope.data.base_data.target, "test_address/test_destination")
778848

779849
def test_span_to_envelope_client_azure(self):
780850
exporter = self._exporter

0 commit comments

Comments
 (0)