Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sdk/ai/azure-ai-projects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
* Rename class `MicrosoftFabricAgentTool` to `MicrosoftFabricPreviewTool`.
* Rename class `SharepointAgentTool` to `SharepointPreviewTool`.
* Rename class `ItemParam` to `InputItem`.
* Tracing: workflow actions in conversation item listings are now emitted as "gen_ai.conversation.item" events (with role="workflow") instead of "gen_ai.workflow.action" events in the list_conversation_items span.

## 2.0.0b3 (2026-01-06)

Expand Down
2 changes: 1 addition & 1 deletion sdk/ai/azure-ai-projects/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "python",
"TagPrefix": "python/ai/azure-ai-projects",
"Tag": "python/ai/azure-ai-projects_6f9985fe6d"
"Tag": "python/ai/azure-ai-projects_7cddb7d06f"
}
Original file line number Diff line number Diff line change
Expand Up @@ -3998,9 +3998,7 @@ def _add_conversation_item_event( # pylint: disable=too-many-branches,too-many-
# Wrap in parts array for semantic convention compliance
parts: List[Dict[str, Any]] = [{"type": "workflow_action", "content": workflow_details}]
event_body = [{"role": role, "parts": parts}]

# Use generic event name for workflow actions
event_name = GEN_AI_WORKFLOW_ACTION_EVENT
event_name = GEN_AI_CONVERSATION_ITEM_EVENT

elif item_type == "message":
# Regular message - use content format for consistency
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3989,30 +3989,34 @@ def test_workflow_agent_non_streaming_with_content_recording(self, **kwargs):
assert "content" in part
assert "status" in part["content"]

# Verify conversation items listing span also has workflow actions
# Verify conversation items listing span
list_spans = self.exporter.get_spans_by_name("list_conversation_items")
assert len(list_spans) >= 1
list_span = list_spans[0]

# Check for workflow action events in list items span
list_workflow_events = [e for e in list_span.events if e.name == "gen_ai.workflow.action"]
assert len(list_workflow_events) > 0
# Check for conversation item events in list items span
list_item_events = [e for e in list_span.events if e.name == "gen_ai.conversation.item"]
assert len(list_item_events) > 0

# Verify workflow event content structure in list items
for event in list_workflow_events:
# Verify conversation item event content structure - check for workflow items
found_workflow_item = False
for event in list_item_events:
content_str = event.attributes.get("gen_ai.event.content", "[]")
content = json.loads(content_str)
assert isinstance(content, list)
assert len(content) == 1
assert content[0]["role"] == "workflow"
assert "parts" in content[0]
assert len(content[0]["parts"]) == 1
part = content[0]["parts"][0]
assert part["type"] == "workflow_action"
assert "content" in part
assert "status" in part["content"]
# With content recording ON, action_id should be present
assert "action_id" in part["content"]
for item in content:
if item.get("role") == "workflow":
found_workflow_item = True
assert "parts" in item
assert len(item["parts"]) >= 1
part = item["parts"][0]
assert part["type"] == "workflow_action"
assert "content" in part
assert "status" in part["content"]
# With content recording ON, action_id and previous_action_id should be present
assert "action_id" in part["content"], "action_id should be present when content recording is enabled"
assert "previous_action_id" in part["content"], "previous_action_id should be present when content recording is enabled"
assert found_workflow_item, "Should have found workflow items in conversation items"

@pytest.mark.usefixtures("instrument_without_content")
@servicePreparer()
Expand Down Expand Up @@ -4095,31 +4099,34 @@ def test_workflow_agent_non_streaming_without_content_recording(self, **kwargs):
assert "action_id" not in part["content"]
assert "previous_action_id" not in part["content"]

# Verify conversation items listing span also has workflow actions
# Verify conversation items listing span
list_spans = self.exporter.get_spans_by_name("list_conversation_items")
assert len(list_spans) >= 1
list_span = list_spans[0]

# Check for workflow action events in list items span
list_workflow_events = [e for e in list_span.events if e.name == "gen_ai.workflow.action"]
assert len(list_workflow_events) > 0
# Check for conversation item events in list items span
list_item_events = [e for e in list_span.events if e.name == "gen_ai.conversation.item"]
assert len(list_item_events) > 0

# Verify workflow event content structure in list items (content recording OFF)
for event in list_workflow_events:
# Verify conversation item event content structure (content recording OFF)
found_workflow_item = False
for event in list_item_events:
content_str = event.attributes.get("gen_ai.event.content", "[]")
content = json.loads(content_str)
assert isinstance(content, list)
assert len(content) == 1
assert content[0]["role"] == "workflow"
assert "parts" in content[0]
assert len(content[0]["parts"]) == 1
part = content[0]["parts"][0]
assert part["type"] == "workflow_action"
assert "content" in part
assert "status" in part["content"]
# action_id and previous_action_id should NOT be present when content recording is off
assert "action_id" not in part["content"]
assert "previous_action_id" not in part["content"]
for item in content:
if item.get("role") == "workflow":
found_workflow_item = True
assert "parts" in item
assert len(item["parts"]) >= 1
part = item["parts"][0]
assert part["type"] == "workflow_action"
assert "content" in part
assert "status" in part["content"]
# action_id and previous_action_id should NOT be present when content recording is off
assert "action_id" not in part["content"], "action_id should not be present when content recording is disabled"
assert "previous_action_id" not in part["content"], "previous_action_id should not be present when content recording is disabled"
assert found_workflow_item, "Should have found workflow items in conversation items"

@pytest.mark.usefixtures("instrument_with_content")
@servicePreparer()
Expand Down Expand Up @@ -4294,30 +4301,34 @@ def test_workflow_agent_streaming_with_content_recording(self, **kwargs):
assert "content" in part
assert "status" in part["content"]

# Verify conversation items listing span also has workflow actions
# Verify conversation items listing span
list_spans = self.exporter.get_spans_by_name("list_conversation_items")
assert len(list_spans) >= 1
list_span = list_spans[0]

# Check for workflow action events in list items span
list_workflow_events = [e for e in list_span.events if e.name == "gen_ai.workflow.action"]
assert len(list_workflow_events) > 0
# Check for conversation item events in list items span
list_item_events = [e for e in list_span.events if e.name == "gen_ai.conversation.item"]
assert len(list_item_events) > 0

# Verify workflow event content structure in list items
for event in list_workflow_events:
# Verify conversation item event content structure - check for workflow items
found_workflow_item = False
for event in list_item_events:
content_str = event.attributes.get("gen_ai.event.content", "[]")
content = json.loads(content_str)
assert isinstance(content, list)
assert len(content) == 1
assert content[0]["role"] == "workflow"
assert "parts" in content[0]
assert len(content[0]["parts"]) == 1
part = content[0]["parts"][0]
assert part["type"] == "workflow_action"
assert "content" in part
assert "status" in part["content"]
# With content recording ON, action_id should be present
assert "action_id" in part["content"]
for item in content:
if item.get("role") == "workflow":
found_workflow_item = True
assert "parts" in item
assert len(item["parts"]) >= 1
part = item["parts"][0]
assert part["type"] == "workflow_action"
assert "content" in part
assert "status" in part["content"]
# With content recording ON, action_id and previous_action_id should be present
assert "action_id" in part["content"], "action_id should be present when content recording is enabled"
assert "previous_action_id" in part["content"], "previous_action_id should be present when content recording is enabled"
assert found_workflow_item, "Should have found workflow items in conversation items"

@pytest.mark.usefixtures("instrument_without_content")
@servicePreparer()
Expand Down Expand Up @@ -4405,31 +4416,34 @@ def test_workflow_agent_streaming_without_content_recording(self, **kwargs):
assert "action_id" not in part["content"]
assert "previous_action_id" not in part["content"]

# Verify conversation items listing span also has workflow actions
# Verify conversation items listing span
list_spans = self.exporter.get_spans_by_name("list_conversation_items")
assert len(list_spans) >= 1
list_span = list_spans[0]

# Check for workflow action events in list items span
list_workflow_events = [e for e in list_span.events if e.name == "gen_ai.workflow.action"]
assert len(list_workflow_events) > 0
# Check for conversation item events in list items span
list_item_events = [e for e in list_span.events if e.name == "gen_ai.conversation.item"]
assert len(list_item_events) > 0

# Verify workflow event content structure in list items (content recording OFF)
for event in list_workflow_events:
# Verify conversation item event content structure (content recording OFF)
found_workflow_item = False
for event in list_item_events:
content_str = event.attributes.get("gen_ai.event.content", "[]")
content = json.loads(content_str)
assert isinstance(content, list)
assert len(content) == 1
assert content[0]["role"] == "workflow"
assert "parts" in content[0]
assert len(content[0]["parts"]) == 1
part = content[0]["parts"][0]
assert part["type"] == "workflow_action"
assert "content" in part
assert "status" in part["content"]
# action_id and previous_action_id should NOT be present when content recording is off
assert "action_id" not in part["content"]
assert "previous_action_id" not in part["content"]
for item in content:
if item.get("role") == "workflow":
found_workflow_item = True
assert "parts" in item
assert len(item["parts"]) >= 1
part = item["parts"][0]
assert part["type"] == "workflow_action"
assert "content" in part
assert "status" in part["content"]
# action_id and previous_action_id should NOT be present when content recording is off
assert "action_id" not in part["content"], "action_id should not be present when content recording is disabled"
assert "previous_action_id" not in part["content"], "previous_action_id should not be present when content recording is disabled"
assert found_workflow_item, "Should have found workflow items in conversation items"

@pytest.mark.usefixtures("instrument_with_content")
@servicePreparer()
Expand Down
Loading
Loading