fix(Call n8n Sub-Workflow Tool Node): Return structured data from Workflow Tool when called by engine (#20869)

This commit is contained in:
Danny Martini 2025-10-16 16:21:55 +02:00 committed by GitHub
parent 5b5cef8e00
commit 44d1835797
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 38 additions and 3 deletions

View File

@ -137,6 +137,35 @@ describe('WorkflowTool::WorkflowToolService', () => {
});
});
it('returns un-stringified data if manualLogging is false (meaning it was called from the engine)', async () => {
const TEST_RESPONSE = { msg: 'test response' };
const mockExecuteWorkflowResponse: ExecuteWorkflowData = {
data: [[{ json: TEST_RESPONSE }]],
executionId: 'test-execution',
};
jest.spyOn(context, 'executeWorkflow').mockResolvedValueOnce(mockExecuteWorkflowResponse);
jest.spyOn(context, 'getNodeParameter').mockReturnValue('database');
jest.spyOn(context, 'getWorkflowDataProxy').mockReturnValue({
$execution: { id: 'exec-id' },
$workflow: { id: 'workflow-id' },
} as unknown as IWorkflowDataProxyData);
const tool = await service.createTool({
ctx: context,
name: 'Test Tool',
description: 'Test Description',
itemIndex: 0,
manualLogging: false,
});
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const result = await tool.func('test query');
expect(result).toEqual([{ json: TEST_RESPONSE }]);
});
it('should handle errors during tool execution', async () => {
const toolParams = {
ctx: context,

View File

@ -160,6 +160,8 @@ export class WorkflowToolService {
};
}
// If manualLogging is enabled we've been called by the AgentExecutor
// and have to return a stringified response.
if (manualLogging) {
void context.addOutputData(
NodeConnectionTypes.AiTool,
@ -167,9 +169,13 @@ export class WorkflowToolService {
[responseData],
metadata,
);
return processedResponse;
}
return processedResponse;
// If manualLogging is false we've been called by the engine and need
// the structured response.
return responseData;
} catch (error) {
// Check if error is due to cancellation
if (abortSignal?.aborted) {
@ -240,7 +246,7 @@ export class WorkflowToolService {
items: INodeExecutionData[],
workflowProxy: IWorkflowDataProxyData,
runManager?: CallbackManagerForToolRun,
): Promise<{ response: string | IDataObject | INodeExecutionData[]; subExecutionId: string }> {
): Promise<{ response: IDataObject | INodeExecutionData[]; subExecutionId: string }> {
let receivedData: ExecuteWorkflowData;
try {
receivedData = await context.executeWorkflow(workflowInfo, items, runManager?.getChild(), {
@ -280,7 +286,7 @@ export class WorkflowToolService {
query: string | IDataObject,
itemIndex: number,
runManager?: CallbackManagerForToolRun,
): Promise<string | IDataObject | INodeExecutionData[]> {
): Promise<IDataObject | INodeExecutionData[]> {
const source = context.getNodeParameter('source', itemIndex) as string;
const workflowProxy = context.getWorkflowDataProxy(0);