-
Notifications
You must be signed in to change notification settings - Fork 63
feat(DATAGO-117907): Show created artifacts in the workflow diagram #607
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
base: main
Are you sure you want to change the base?
Conversation
WhiteSource Policy Violation Summary✅︎ No Blocking Whitesource Policy Violations found in solaceai/solace-agent-mesh-ui-pr-607! |
There was a problem hiding this 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_progresssignals withfunction_call_idlinking artifacts to tool calls - Frontend distinguishes between synthetic tool calls (LLM-generated) and regular tool calls, queuing the former until tool results arrive
- New
GenericArtifactNodecomponent 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) {
| # 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 |
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
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 linkingThis makes it clearer why we're deferring the signal emission (to ensure we have the function_call_id).
| # 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. |
| 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 |
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
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;
}
}| 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; | |
| } |
| type: "artifactNode", | ||
| position: { x: artifactX, y: artifactY }, | ||
| data: { | ||
| label: "Artifact", |
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
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.
| label: "Artifact", | |
| label: artifactName, |
|
|
||
| // Update maxY and maxContentXRelative to ensure group accommodates the artifact | ||
| const artifactBottom = artifactY + NODE_HEIGHT; | ||
| const artifactRight = artifactX + NODE_WIDTH / 2; |
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
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;| const artifactRight = artifactX + NODE_WIDTH / 2; | |
| const artifactRight = artifactX + NODE_WIDTH; |
| 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." |
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
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.
| 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." |
| """ | ||
|
|
||
| import pytest | ||
| from unittest.mock import Mock, AsyncMock, patch, call |
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
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.
| from unittest.mock import Mock, AsyncMock, patch, call | |
| from unittest.mock import Mock, AsyncMock, patch |
efunneko
left a comment
There was a problem hiding this 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 |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
Changes
save_artifact_with_metadata()to emitartifact_creation_progresssignals withfunction_call_idfield linking artifacts to their creating tool callsnotify_artifact_savetool call is sent over.function_call_idlookupGenericArtifactNodeReact component for artifact visualization_notify_artifact_savehandler inChatProvider(handled by newartifact_creation_progresshandler)Considerations
_notify_artifact_savetool call for completion status and metadata