Skip to content

Commit 0d85fc6

Browse files
authored
tracing: add operation to create agent and tokens to stream (#44037)
* tracing: adding operation name to create agent and token count to streaming responses * updating assets.json * fixed change log and removed commented out code per review comments
1 parent 696d9d1 commit 0d85fc6

File tree

9 files changed

+44
-22
lines changed

9 files changed

+44
-22
lines changed

sdk/ai/azure-ai-projects/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
### Breaking changes
88

99
* `get_openai_client()` method on the asynchronous AIProjectClient is no longer an "async" method.
10-
* tracing: tool call output event content format updated to be in line with other events
10+
* Tracing: tool call output event content format updated to be in line with other events
1111

1212
### Bugs Fixed
13+
* Tracing: operation name attribute added to create agent span, token usage added to streaming response generation span
1314

1415
### Sample updates
1516
* Added `finetuning` samples for operations create, retrieve, list, list_events, list_checkpoints, cancel, pause and resume. Also, these samples includes various finetuning techniques like Supervised (SFT), Reinforcement (RFT) and Direct performance optimization (DPO).

sdk/ai/azure-ai-projects/assets.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"AssetsRepo": "Azure/azure-sdk-assets",
33
"AssetsRepoPrefixPath": "python",
44
"TagPrefix": "python/ai/azure-ai-projects",
5-
"Tag": "python/ai/azure-ai-projects_7e1b7f222f"
5+
"Tag": "python/ai/azure-ai-projects_8ddbfaaa38"
66
}

sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_ai_project_instrumentor.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
GEN_AI_EVENT_CONTENT,
3030
GEN_AI_MESSAGE_ID,
3131
GEN_AI_MESSAGE_STATUS,
32+
GEN_AI_OPERATION_NAME,
3233
GEN_AI_SYSTEM,
3334
GEN_AI_SYSTEM_MESSAGE,
3435
GEN_AI_THREAD_ID,
@@ -504,6 +505,7 @@ def start_create_agent_span( # pylint: disable=too-many-locals
504505
gen_ai_system=AZ_AI_AGENT_SYSTEM,
505506
)
506507
if span and span.span_instance.is_recording:
508+
span.add_attribute(GEN_AI_OPERATION_NAME, OperationName.CREATE_AGENT.value)
507509
if name:
508510
span.add_attribute(GEN_AI_AGENT_NAME, name)
509511
if description:

sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_responses_instrumentor.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1965,6 +1965,18 @@ def process_chunk(self, chunk):
19651965
self.response_id = chunk.response.id
19661966
if not self.response_model:
19671967
self.response_model = getattr(chunk.response, "model", None)
1968+
# Extract usage from the completed response
1969+
if hasattr(chunk.response, "usage"):
1970+
response_usage = chunk.response.usage
1971+
if hasattr(response_usage, "input_tokens") and response_usage.input_tokens:
1972+
self.input_tokens = response_usage.input_tokens
1973+
if hasattr(response_usage, "output_tokens") and response_usage.output_tokens:
1974+
self.output_tokens = response_usage.output_tokens
1975+
# Also handle standard token field names for compatibility
1976+
if hasattr(response_usage, "prompt_tokens") and response_usage.prompt_tokens:
1977+
self.input_tokens = response_usage.prompt_tokens
1978+
if hasattr(response_usage, "completion_tokens") and response_usage.completion_tokens:
1979+
self.output_tokens = response_usage.completion_tokens
19681980

19691981
# Only append TEXT content from delta events (not function call arguments or other deltas)
19701982
# Text deltas can come as:
@@ -2041,6 +2053,7 @@ def cleanup(self):
20412053
self.instrumentor._set_span_attribute_safe(
20422054
self.span, "gen_ai.response.model", self.response_model
20432055
)
2056+
20442057
if self.service_tier:
20452058
self.instrumentor._set_span_attribute_safe(
20462059
self.span, "gen_ai.openai.response.service_tier", self.service_tier
@@ -2049,11 +2062,11 @@ def cleanup(self):
20492062
# Set token usage span attributes
20502063
if self.input_tokens > 0:
20512064
self.instrumentor._set_span_attribute_safe(
2052-
self.span, "gen_ai.usage.prompt_tokens", self.input_tokens
2065+
self.span, "gen_ai.usage.input_tokens", self.input_tokens
20532066
)
20542067
if self.output_tokens > 0:
20552068
self.instrumentor._set_span_attribute_safe(
2056-
self.span, "gen_ai.usage.completion_tokens", self.output_tokens
2069+
self.span, "gen_ai.usage.output_tokens", self.output_tokens
20572070
)
20582071

20592072
# Record metrics using accumulated data
@@ -2373,6 +2386,18 @@ def process_chunk(self, chunk):
23732386
self.response_id = chunk.response.id
23742387
if not self.response_model:
23752388
self.response_model = getattr(chunk.response, "model", None)
2389+
# Extract usage from the completed response
2390+
if hasattr(chunk.response, "usage"):
2391+
response_usage = chunk.response.usage
2392+
if hasattr(response_usage, "input_tokens") and response_usage.input_tokens:
2393+
self.input_tokens = response_usage.input_tokens
2394+
if hasattr(response_usage, "output_tokens") and response_usage.output_tokens:
2395+
self.output_tokens = response_usage.output_tokens
2396+
# Also handle standard token field names for compatibility
2397+
if hasattr(response_usage, "prompt_tokens") and response_usage.prompt_tokens:
2398+
self.input_tokens = response_usage.prompt_tokens
2399+
if hasattr(response_usage, "completion_tokens") and response_usage.completion_tokens:
2400+
self.output_tokens = response_usage.completion_tokens
23762401

23772402
# Only append TEXT content from delta events (not function call arguments or other deltas)
23782403
# Text deltas can come as:
@@ -2449,6 +2474,7 @@ def cleanup(self):
24492474
self.instrumentor._set_span_attribute_safe(
24502475
self.span, "gen_ai.response.model", self.response_model
24512476
)
2477+
24522478
if self.service_tier:
24532479
self.instrumentor._set_span_attribute_safe(
24542480
self.span, "gen_ai.openai.response.service_tier", self.service_tier
@@ -2457,11 +2483,11 @@ def cleanup(self):
24572483
# Set token usage span attributes
24582484
if self.input_tokens > 0:
24592485
self.instrumentor._set_span_attribute_safe(
2460-
self.span, "gen_ai.usage.prompt_tokens", self.input_tokens
2486+
self.span, "gen_ai.usage.input_tokens", self.input_tokens
24612487
)
24622488
if self.output_tokens > 0:
24632489
self.instrumentor._set_span_attribute_safe(
2464-
self.span, "gen_ai.usage.completion_tokens", self.output_tokens
2490+
self.span, "gen_ai.usage.output_tokens", self.output_tokens
24652491
)
24662492

24672493
# Record metrics using accumulated data

sdk/ai/azure-ai-projects/tests/agents/telemetry/gen_ai_trace_verifier.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ def check_span_attributes(self, span, attributes):
1818
attribute_dict = dict(attributes)
1919
attribute_dict["az.namespace"] = "Microsoft.CognitiveServices"
2020

21+
# First, check that all expected attributes are present in the span
22+
for expected_attribute_name in attribute_dict.keys():
23+
if expected_attribute_name not in span.attributes:
24+
raise AssertionError(
25+
f"Expected attribute '{expected_attribute_name}' not found in span. "
26+
f"Span has: {list(span.attributes.keys())}"
27+
)
28+
29+
# Then, check that all attributes in the span are expected and have correct values
2130
for attribute_name in span.attributes.keys():
2231
# Check if the attribute name exists in the input attributes
2332
if attribute_name not in attribute_dict:

sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,6 @@ def test_agent_creation_with_tracing_content_recording_disabled(self, **kwargs):
338338
events_match = GenAiTraceVerifier().check_span_events(span, expected_events)
339339
assert events_match == True
340340

341-
@pytest.mark.skip(reason="recordings to be added")
342341
@pytest.mark.usefixtures("instrument_with_content")
343342
@servicePreparer()
344343
@recorded_by_proxy
@@ -409,7 +408,6 @@ def test_workflow_agent_creation_with_tracing_content_recording_enabled(self, **
409408
assert "workflow" in event_content["content"][0]
410409
assert "kind: workflow" in event_content["content"][0]["workflow"]
411410

412-
@pytest.mark.skip(reason="recordings to be added")
413411
@pytest.mark.usefixtures("instrument_without_content")
414412
@servicePreparer()
415413
@recorded_by_proxy

sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor_async.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,6 @@ async def test_agent_creation_with_tracing_content_recording_disabled(self, **kw
216216
events_match = GenAiTraceVerifier().check_span_events(span, expected_events)
217217
assert events_match == True
218218

219-
@pytest.mark.skip(reason="recordings to be added")
220219
@pytest.mark.usefixtures("instrument_with_content")
221220
@servicePreparer()
222221
@recorded_by_proxy_async
@@ -287,7 +286,6 @@ async def test_workflow_agent_creation_with_tracing_content_recording_enabled(se
287286
assert "workflow" in event_content["content"][0]
288287
assert "kind: workflow" in event_content["content"][0]["workflow"]
289288

290-
@pytest.mark.skip(reason="recordings to be added")
291289
@pytest.mark.usefixtures("instrument_without_content")
292290
@servicePreparer()
293291
@recorded_by_proxy_async

sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -756,7 +756,6 @@ def test_sync_function_tool_with_content_recording_non_streaming(self, **kwargs)
756756
expected_attributes_1 = [
757757
("az.namespace", "Microsoft.CognitiveServices"),
758758
("gen_ai.operation.name", "responses"),
759-
("gen_ai.request.model", deployment_name),
760759
("gen_ai.request.assistant_name", agent.name),
761760
("gen_ai.provider.name", "azure.openai"),
762761
("server.address", ""),
@@ -796,7 +795,6 @@ def test_sync_function_tool_with_content_recording_non_streaming(self, **kwargs)
796795
expected_attributes_2 = [
797796
("az.namespace", "Microsoft.CognitiveServices"),
798797
("gen_ai.operation.name", "responses"),
799-
("gen_ai.request.model", deployment_name),
800798
("gen_ai.request.assistant_name", agent.name),
801799
("gen_ai.provider.name", "azure.openai"),
802800
("server.address", ""),
@@ -952,7 +950,6 @@ def test_sync_function_tool_with_content_recording_streaming(self, **kwargs):
952950
expected_attributes_1 = [
953951
("az.namespace", "Microsoft.CognitiveServices"),
954952
("gen_ai.operation.name", "responses"),
955-
("gen_ai.request.model", deployment_name),
956953
("gen_ai.request.assistant_name", agent.name),
957954
("gen_ai.provider.name", "azure.openai"),
958955
("server.address", ""),
@@ -992,7 +989,6 @@ def test_sync_function_tool_with_content_recording_streaming(self, **kwargs):
992989
expected_attributes_2 = [
993990
("az.namespace", "Microsoft.CognitiveServices"),
994991
("gen_ai.operation.name", "responses"),
995-
("gen_ai.request.model", deployment_name),
996992
("gen_ai.request.assistant_name", agent.name),
997993
("gen_ai.provider.name", "azure.openai"),
998994
("server.address", ""),
@@ -1124,7 +1120,6 @@ def test_sync_function_tool_without_content_recording_non_streaming(self, **kwar
11241120
expected_attributes_1 = [
11251121
("az.namespace", "Microsoft.CognitiveServices"),
11261122
("gen_ai.operation.name", "responses"),
1127-
("gen_ai.request.model", deployment_name),
11281123
("gen_ai.request.assistant_name", agent.name),
11291124
("gen_ai.provider.name", "azure.openai"),
11301125
("server.address", ""),
@@ -1164,7 +1159,6 @@ def test_sync_function_tool_without_content_recording_non_streaming(self, **kwar
11641159
expected_attributes_2 = [
11651160
("az.namespace", "Microsoft.CognitiveServices"),
11661161
("gen_ai.operation.name", "responses"),
1167-
("gen_ai.request.model", deployment_name),
11681162
("gen_ai.request.assistant_name", agent.name),
11691163
("gen_ai.provider.name", "azure.openai"),
11701164
("server.address", ""),
@@ -1314,7 +1308,6 @@ def test_sync_function_tool_without_content_recording_streaming(self, **kwargs):
13141308
expected_attributes_1 = [
13151309
("az.namespace", "Microsoft.CognitiveServices"),
13161310
("gen_ai.operation.name", "responses"),
1317-
("gen_ai.request.model", deployment_name),
13181311
("gen_ai.request.assistant_name", agent.name),
13191312
("gen_ai.provider.name", "azure.openai"),
13201313
("server.address", ""),
@@ -1354,7 +1347,6 @@ def test_sync_function_tool_without_content_recording_streaming(self, **kwargs):
13541347
expected_attributes_2 = [
13551348
("az.namespace", "Microsoft.CognitiveServices"),
13561349
("gen_ai.operation.name", "responses"),
1357-
("gen_ai.request.model", deployment_name),
13581350
("gen_ai.request.assistant_name", agent.name),
13591351
("gen_ai.provider.name", "azure.openai"),
13601352
("server.address", ""),

sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_async.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,6 @@ async def test_async_function_tool_with_content_recording_streaming(self, **kwar
412412
expected_attributes_1 = [
413413
("az.namespace", "Microsoft.CognitiveServices"),
414414
("gen_ai.operation.name", "responses"),
415-
("gen_ai.request.model", deployment_name),
416415
("gen_ai.request.assistant_name", agent.name),
417416
("gen_ai.provider.name", "azure.openai"),
418417
("server.address", ""),
@@ -452,7 +451,6 @@ async def test_async_function_tool_with_content_recording_streaming(self, **kwar
452451
expected_attributes_2 = [
453452
("az.namespace", "Microsoft.CognitiveServices"),
454453
("gen_ai.operation.name", "responses"),
455-
("gen_ai.request.model", deployment_name),
456454
("gen_ai.request.assistant_name", agent.name),
457455
("gen_ai.provider.name", "azure.openai"),
458456
("server.address", ""),
@@ -604,7 +602,6 @@ async def test_async_function_tool_without_content_recording_streaming(self, **k
604602
expected_attributes_1 = [
605603
("az.namespace", "Microsoft.CognitiveServices"),
606604
("gen_ai.operation.name", "responses"),
607-
("gen_ai.request.model", deployment_name),
608605
("gen_ai.request.assistant_name", agent.name),
609606
("gen_ai.provider.name", "azure.openai"),
610607
("server.address", ""),
@@ -644,7 +641,6 @@ async def test_async_function_tool_without_content_recording_streaming(self, **k
644641
expected_attributes_2 = [
645642
("az.namespace", "Microsoft.CognitiveServices"),
646643
("gen_ai.operation.name", "responses"),
647-
("gen_ai.request.model", deployment_name),
648644
("gen_ai.request.assistant_name", agent.name),
649645
("gen_ai.provider.name", "azure.openai"),
650646
("server.address", ""),

0 commit comments

Comments
 (0)