mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-21 00:02:23 +00:00
fix(ai-agent): don't mark repeated tool calls as failed in flow graph (#10075)
* fix(ai-agent): don't mark repeated tool calls as failed in flow graph Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(ai-agent): cover reporter's mixed repeated-tool-call scenario Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -160,7 +160,12 @@
|
||||
data: {
|
||||
tool: tool.name,
|
||||
type: tool.type,
|
||||
nameError: getToolNameError(tool.name, tool.type, siblingNames),
|
||||
// agentActions are runtime tool calls: the same tool called multiple times
|
||||
// yields duplicate names, which is expected and must not read as a Failure.
|
||||
// Only validate names in the editor, where they define the static tool set.
|
||||
nameError: agentActions
|
||||
? undefined
|
||||
: getToolNameError(tool.name, tool.type, siblingNames),
|
||||
eventHandlers,
|
||||
moduleId: tool.id,
|
||||
insertable,
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
|
||||
// Mock the component wrapper so importing the .svelte module doesn't pull in the
|
||||
// full render-time dependency graph.
|
||||
vi.mock('./NodeWrapper.svelte', () => ({ default: {} }))
|
||||
|
||||
import { computeAIToolNodes } from './AIToolNode.svelte'
|
||||
|
||||
const eventHandlers = {} as any
|
||||
|
||||
function aiAgentNode(id: string, tools: any[]): any {
|
||||
return {
|
||||
id,
|
||||
type: 'module',
|
||||
position: { x: 0, y: 0 },
|
||||
data: { module: { id, value: { type: 'aiagent', tools } } }
|
||||
}
|
||||
}
|
||||
|
||||
describe('computeAIToolNodes', () => {
|
||||
it('does not flag duplicate names when the same tool is called multiple times at runtime', () => {
|
||||
// One statically-defined tool that the agent called twice. The runtime
|
||||
// agent_actions therefore carry the same function_name twice — this is
|
||||
// expected and must not surface as a `nameError` (which renders as Failure).
|
||||
const node = aiAgentNode('agent', [
|
||||
{ id: 'tool_a', summary: 'my_tool', value: { tool_type: 'flowmodule', type: 'script' } }
|
||||
])
|
||||
const flowModuleStates = {
|
||||
agent: {
|
||||
type: 'Success',
|
||||
agent_actions: [
|
||||
{ type: 'tool_call', function_name: 'my_tool', module_id: 'tool_a', job_id: 'j1' },
|
||||
{ type: 'tool_call', function_name: 'my_tool', module_id: 'tool_a', job_id: 'j2' }
|
||||
]
|
||||
}
|
||||
} as any
|
||||
|
||||
const { toolNodes } = computeAIToolNodes([node], eventHandlers, false, flowModuleStates)
|
||||
|
||||
expect(toolNodes.length).toBe(2)
|
||||
for (const n of toolNodes) {
|
||||
expect((n.data as any).nameError).toBeUndefined()
|
||||
}
|
||||
})
|
||||
|
||||
it('does not flag any node in a mixed run where one tool repeats (reporter scenario)', () => {
|
||||
// Repo-intel run: query_stored called 3x plus two single calls, all succeeded.
|
||||
// Before the fix the three query_stored nodes rendered red (Failure) purely
|
||||
// from the duplicate-name check, while the unique tools stayed green.
|
||||
const node = aiAgentNode('chat', [
|
||||
{ id: 'q', summary: 'query_stored', value: { tool_type: 'flowmodule', type: 'script' } },
|
||||
{ id: 'h', summary: 'hybrid_search', value: { tool_type: 'flowmodule', type: 'script' } },
|
||||
{
|
||||
id: 't',
|
||||
summary: 'trace_outbound_calls',
|
||||
value: { tool_type: 'flowmodule', type: 'script' }
|
||||
}
|
||||
])
|
||||
const call = (name: string, job: string) => ({
|
||||
type: 'tool_call',
|
||||
function_name: name,
|
||||
module_id: name[0],
|
||||
job_id: job
|
||||
})
|
||||
const flowModuleStates = {
|
||||
chat: {
|
||||
type: 'Success',
|
||||
agent_actions: [
|
||||
call('query_stored', 'j1'),
|
||||
call('query_stored', 'j2'),
|
||||
call('query_stored', 'j3'),
|
||||
call('hybrid_search', 'j4'),
|
||||
call('trace_outbound_calls', 'j5')
|
||||
]
|
||||
}
|
||||
} as any
|
||||
|
||||
const { toolNodes } = computeAIToolNodes([node], eventHandlers, false, flowModuleStates)
|
||||
|
||||
expect(toolNodes.length).toBe(5)
|
||||
for (const n of toolNodes) {
|
||||
expect((n.data as any).nameError).toBeUndefined()
|
||||
}
|
||||
})
|
||||
|
||||
it('still flags genuinely duplicate tool names in the editor (static tool set)', () => {
|
||||
const node = aiAgentNode('agent2', [
|
||||
{ id: 't1', summary: 'dup', value: { tool_type: 'flowmodule', type: 'script' } },
|
||||
{ id: 't2', summary: 'dup', value: { tool_type: 'flowmodule', type: 'script' } }
|
||||
])
|
||||
|
||||
const { toolNodes } = computeAIToolNodes([node], eventHandlers, true, undefined)
|
||||
|
||||
const toolCallNodes = toolNodes.filter((n) => n.type === 'aiTool')
|
||||
expect(toolCallNodes.length).toBe(2)
|
||||
for (const n of toolCallNodes) {
|
||||
expect((n.data as any).nameError).toBe('Duplicate tool name')
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user