Skip to content

Conversation

@RZuchniak
Copy link
Contributor

@RZuchniak RZuchniak commented Dec 2, 2025

Changes

  • Modified save_artifact_with_metadata() to emit artifact_creation_progress signals with function_call_id field linking artifacts to their creating tool calls
  • LLM generated artifacts: Backend emits artifact creation signal before the notify_artifact_save tool call is sent over.
  • Frontend handler distinguishes normal tool call signals from the LLM generated creation signals by ID prefix and queues LLM generated artifacts until tool result arrives
  • Added workflow diagram logic to create artifact nodes linked to parent tools via function_call_id lookup
  • Created new GenericArtifactNode React component for artifact visualization
  • Removed redundant _notify_artifact_save handler in ChatProvider (handled by new artifact_creation_progress handler)

Considerations

  • Current implementation assumes that a tool call can only create 1 artifact. Frontend can be updated to support multiple
  • LLM generated artifacts no longer rely on the _notify_artifact_save tool call for completion status and metadata
image image

@github-actions
Copy link

github-actions bot commented Dec 2, 2025

WhiteSource Policy Violation Summary

✅︎ No Blocking Whitesource Policy Violations found in solaceai/solace-agent-mesh-ui-pr-607!

@RZuchniak RZuchniak requested a review from Copilot December 2, 2025 16:48
Copilot finished reviewing on behalf of RZuchniak December 2, 2025 16:52
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements workflow diagram visualization for created artifacts by establishing a link between artifacts and their creating tool calls through the function_call_id field. The implementation handles both regular tool-created artifacts and LLM-generated artifacts (fenced blocks) with different timing patterns.

Key changes:

  • Backend emits artifact_creation_progress signals with function_call_id linking artifacts to tool calls
  • Frontend distinguishes between synthetic tool calls (LLM-generated) and regular tool calls, queuing the former until tool results arrive
  • New GenericArtifactNode component displays artifacts in the workflow diagram, connected to parent tool nodes

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/solace_agent_mesh/common/data_parts.py Added function_call_id field to ArtifactCreationProgressData for linking artifacts to tools
src/solace_agent_mesh/agent/utils/artifact_helpers.py Modified save_artifact_with_metadata() to emit artifact creation signals with function_call_id and added suppress_visualization_signal parameter
src/solace_agent_mesh/agent/tools/builtin_artifact_tools.py Suppressed visualization signals in fenced blocks since callbacks handle them
src/solace_agent_mesh/agent/adk/callbacks.py Updated to emit artifact signals with function_call_id after generating the synthetic tool call ID
tests/unit/agent/utils/test_artifact_workflow_signals.py Added comprehensive unit tests for signal emission, suppression flag, and missing context handling
client/webui/frontend/src/lib/providers/ChatProvider.tsx Removed redundant _notify_artifact_save handler as completion is now handled by new signal
client/webui/frontend/src/lib/providers/TaskProvider.tsx Code formatting improvements
client/webui/frontend/src/lib/components/activities/taskVisualizerProcessor.ts Added logic to queue LLM-generated artifacts and create visualization steps for both artifact types
client/webui/frontend/src/lib/components/activities/VisualizerStepCard.tsx Enhanced artifact notification display with "View File" button
client/webui/frontend/src/lib/components/activities/FlowChartPanel.tsx Registered ArtifactNode component and added click handling for artifact nodes
client/webui/frontend/src/lib/components/activities/FlowChart/taskToFlowData.ts Implemented handleArtifactNotification() to create artifact nodes linked to parent tools
client/webui/frontend/src/lib/components/activities/FlowChart/customNodes/GenericToolNode.tsx Added right-side output handle for artifact connections
client/webui/frontend/src/lib/components/activities/FlowChart/customNodes/GenericArtifactNode.tsx Created new artifact node component with purple styling
src/solace_agent_mesh/gateway/http_sse/routers/visualization.py Removed trailing comma formatting
src/solace_agent_mesh/common/sam_events/event_service.py Code formatting cleanup
Comments suppressed due to low confidence (1)

client/webui/frontend/src/lib/components/activities/FlowChart/taskToFlowData.ts:485

  • This use of variable 'sourceToolNode' always evaluates to true.
    if (sourceToolNode) {

Comment on lines +346 to +348
# Don't send completion signal here - it will be sent later
# when we inject the _notify_artifact_save tool call
# This ensures the signal and tool call arrive together with matching IDs
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

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

[nitpick] The comment at lines 346-348 could be more precise about the timing relationship. Consider clarifying the sequence:

# Don't send completion signal here - it will be sent later
# when we inject the _notify_artifact_save tool call and have
# generated the function_call_id for proper workflow linking

This makes it clearer why we're deferring the signal emission (to ensure we have the function_call_id).

Suggested change
# Don't send completion signal here - it will be sent later
# when we inject the _notify_artifact_save tool call
# This ensures the signal and tool call arrive together with matching IDs
# Don't send completion signal here it will be sent later,
# after we inject the _notify_artifact_save tool call and have
# generated the function_call_id for proper workflow linking.

Copilot uses AI. Check for mistakes.
Comment on lines 485 to 501
if (sourceToolNode) {
const ARTIFACT_X_OFFSET = 300; // Horizontal distance from tool to artifact

if (subflow) {
const toolNode = nodes.find(n => n.id === sourceToolNode.id);
if (toolNode) {
artifactX = toolNode.position.x + ARTIFACT_X_OFFSET;
artifactY = toolNode.position.y;
} else {
artifactX = (sourceToolNode.xPosition ?? LANE_X_POSITIONS.TOOLS) + ARTIFACT_X_OFFSET;
artifactY = sourceToolNode.yPosition ?? manager.nextAvailableGlobalY;
}
} else {
artifactX = (sourceToolNode.xPosition ?? LANE_X_POSITIONS.TOOLS) + ARTIFACT_X_OFFSET;
artifactY = sourceToolNode.yPosition ?? manager.nextAvailableGlobalY;
}
} else return; // Cannot create artifact node without a source tool
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

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

The logic at line 485-501 has redundant checks. The code checks if (sourceToolNode) at line 485, performs calculations, then checks else return at line 501, which is unreachable. This is because the same condition was already checked at line 473-476 with an early return if sourceToolNode is falsy.

Consider removing the redundant checks to simplify the code:

if (sourceToolNode) {
    const ARTIFACT_X_OFFSET = 300; // Horizontal distance from tool to artifact

    if (subflow) {
        const toolNode = nodes.find(n => n.id === sourceToolNode.id);
        if (toolNode) {
            artifactX = toolNode.position.x + ARTIFACT_X_OFFSET;
            artifactY = toolNode.position.y;
        } else {
            artifactX = (sourceToolNode.xPosition ?? LANE_X_POSITIONS.TOOLS) + ARTIFACT_X_OFFSET;
            artifactY = sourceToolNode.yPosition ?? manager.nextAvailableGlobalY;
        }
    } else {
        artifactX = (sourceToolNode.xPosition ?? LANE_X_POSITIONS.TOOLS) + ARTIFACT_X_OFFSET;
        artifactY = sourceToolNode.yPosition ?? manager.nextAvailableGlobalY;
    }
}
Suggested change
if (sourceToolNode) {
const ARTIFACT_X_OFFSET = 300; // Horizontal distance from tool to artifact
if (subflow) {
const toolNode = nodes.find(n => n.id === sourceToolNode.id);
if (toolNode) {
artifactX = toolNode.position.x + ARTIFACT_X_OFFSET;
artifactY = toolNode.position.y;
} else {
artifactX = (sourceToolNode.xPosition ?? LANE_X_POSITIONS.TOOLS) + ARTIFACT_X_OFFSET;
artifactY = sourceToolNode.yPosition ?? manager.nextAvailableGlobalY;
}
} else {
artifactX = (sourceToolNode.xPosition ?? LANE_X_POSITIONS.TOOLS) + ARTIFACT_X_OFFSET;
artifactY = sourceToolNode.yPosition ?? manager.nextAvailableGlobalY;
}
} else return; // Cannot create artifact node without a source tool
const ARTIFACT_X_OFFSET = 300; // Horizontal distance from tool to artifact
if (subflow) {
const toolNode = nodes.find(n => n.id === sourceToolNode.id);
if (toolNode) {
artifactX = toolNode.position.x + ARTIFACT_X_OFFSET;
artifactY = toolNode.position.y;
} else {
artifactX = (sourceToolNode.xPosition ?? LANE_X_POSITIONS.TOOLS) + ARTIFACT_X_OFFSET;
artifactY = sourceToolNode.yPosition ?? manager.nextAvailableGlobalY;
}
} else {
artifactX = (sourceToolNode.xPosition ?? LANE_X_POSITIONS.TOOLS) + ARTIFACT_X_OFFSET;
artifactY = sourceToolNode.yPosition ?? manager.nextAvailableGlobalY;
}

Copilot uses AI. Check for mistakes.
type: "artifactNode",
position: { x: artifactX, y: artifactY },
data: {
label: "Artifact",
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

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

The artifact node creation uses a generic "Artifact" label instead of including the actual artifact name. This makes it difficult to distinguish between multiple artifacts in the workflow diagram.

Consider updating the label to include the artifact name:

data: {
    label: artifactName,
    visualizerStepId: step.id,
},

This would make the diagram more informative and easier to understand.

Suggested change
label: "Artifact",
label: artifactName,

Copilot uses AI. Check for mistakes.

// Update maxY and maxContentXRelative to ensure group accommodates the artifact
const artifactBottom = artifactY + NODE_HEIGHT;
const artifactRight = artifactX + NODE_WIDTH / 2;
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

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

The calculation artifactRight = artifactX + NODE_WIDTH / 2 appears incorrect. When calculating the rightmost extent of the artifact node for group sizing, it should be artifactX + NODE_WIDTH (the full width), not half the width. Using half the width will result in insufficient space allocation for the artifact node within the group.

Consider changing line 529 to:

const artifactRight = artifactX + NODE_WIDTH;
Suggested change
const artifactRight = artifactX + NODE_WIDTH / 2;
const artifactRight = artifactX + NODE_WIDTH;

Copilot uses AI. Check for mistakes.
description="The version number of the artifact being created or updated.",
)
function_call_id: Optional[str] = Field(
None, description="The function call ID if artifact was created by a tool."
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

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

[nitpick] The description for the new function_call_id field could be more specific about its purpose in the workflow visualization. Consider updating the description to clarify when and why this field is populated:

function_call_id: Optional[str] = Field(
    None, 
    description="The function call ID linking this artifact to the tool call that created it, used for workflow diagram visualization."
)

This makes it clearer that the field serves a specific visualization purpose.

Suggested change
None, description="The function call ID if artifact was created by a tool."
None,
description="The function call ID linking this artifact to the tool call that created it, used for workflow diagram visualization. Populated only when the artifact is created by a tool call."

Copilot uses AI. Check for mistakes.
"""

import pytest
from unittest.mock import Mock, AsyncMock, patch, call
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

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

Import of 'call' is not used.

Suggested change
from unittest.mock import Mock, AsyncMock, patch, call
from unittest.mock import Mock, AsyncMock, patch

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

@efunneko efunneko left a comment

Choose a reason for hiding this comment

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

There are a couple of changes that are needed. You can ask for more detail from me if you need it

await _publish_data_part_status_update(
host_component, a2a_context, progress_data
)
# Don't send completion signal here - it will be sent later
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't really like that we are overloading this event for all artifact creation events. There is a protocol to how they are created with a start ... updates ... end which we would be violating. My preference would be to have another event that would be just to indicate that an artifact has been save and we restore this code to what it was

)

# Publish as status update with signal
await _publish_data_part_status_update(
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't like having this in a utility file - especially calling a "_" function (I realize we do it in callbacks.py and that should be fixed). We should define a function (probably in sac/component.py) that is called from here to notify_artifact_creation(). You should pass in an "ArtifactInfo" with all the information about the artifact and pass in an optional 'function_call_id". Then the publishing stuff can be where it belongs and this code is reduced a lot

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should I update callbacks.py to use a separate function instead of _publish_data_part_status_update() when notifying about artifact completion, or consider it out of scope for these changes?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants