fix(OpenAI Node): Don't include function calls when conversation id is used (#21047)

This commit is contained in:
yehorkardash 2025-10-22 16:39:08 +03:00 committed by GitHub
parent c62b6a0419
commit 2fff38827a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 108 additions and 10 deletions

View File

@ -522,6 +522,108 @@ describe('OpenAI Response Operation', () => {
expect(mockTool.invoke).toHaveBeenCalledWith('test input');
expect(mockApiRequest).toHaveBeenCalledTimes(2);
expect(mockApiRequest).toHaveBeenNthCalledWith(2, 'POST', '/responses', {
body: {
model: 'gpt-4o',
input: [
{
type: 'reasoning',
content: 'I need to use the test tool to get information',
},
{
type: 'function_call',
call_id: 'call_123',
name: 'test_tool',
arguments: JSON.stringify({ input: 'test input' }),
},
{
call_id: 'call_123',
output: 'Tool response',
type: 'function_call_output',
},
],
tools: [{ name: 'test_tool', type: 'function', parameters: {}, strict: false }],
},
});
expect(result).toEqual([
{
json: finalResponse,
pairedItem: { item: 0 },
},
]);
});
it('should not include function_call or reasoning items in the request if there is a conversation', async () => {
const mockTool = {
name: 'test_tool',
invoke: jest.fn().mockResolvedValue('Tool response'),
schema: {
typeName: 'ZodObject',
_def: { typeName: 'ZodObject', shape: () => ({}) },
parse: jest.fn(),
safeParse: jest.fn(),
},
call: jest.fn(),
description: 'Test tool',
returnDirect: false,
} as any;
const initialResponse = {
id: 'resp_123',
status: 'completed',
output: [
{
type: 'reasoning',
content: 'I need to use the test tool to get information',
},
{
type: 'function_call',
call_id: 'call_123',
name: 'test_tool',
arguments: JSON.stringify({ input: 'test input' }),
},
],
};
const finalResponse = {
id: 'resp_123',
status: 'completed',
output: [
{
type: 'message',
role: 'assistant',
content: [{ type: 'output_text', text: 'Final response' }],
},
],
};
mockGetConnectedTools.mockResolvedValue([mockTool]);
mockCreateRequest.mockResolvedValue({
model: 'gpt-4o',
input: [],
tools: [{ name: 'test_tool', type: 'function', parameters: {}, strict: false }],
conversation: 'conv_123',
});
mockApiRequest.mockResolvedValueOnce(initialResponse).mockResolvedValueOnce(finalResponse);
const result = await execute.call(mockExecuteFunctions, 0);
expect(mockTool.invoke).toHaveBeenCalledWith('test input');
expect(mockApiRequest).toHaveBeenCalledTimes(2);
expect(mockApiRequest).toHaveBeenNthCalledWith(2, 'POST', '/responses', {
body: {
model: 'gpt-4o',
input: [
{
call_id: 'call_123',
output: 'Tool response',
type: 'function_call_output',
},
],
tools: [{ name: 'test_tool', type: 'function', parameters: {}, strict: false }],
conversation: 'conv_123',
},
});
expect(result).toEqual([
{
json: finalResponse,

View File

@ -706,7 +706,6 @@ export async function execute(this: IExecuteFunctions, i: number): Promise<INode
const hasFunctionCall = () => toolCalls.some((item) => item.type === 'function_call');
const answeredToolCalls = new Set<string>();
let currentIteration = 1;
// make sure there's actually a function call to answer
while (toolCalls.length && hasFunctionCall()) {
@ -714,14 +713,13 @@ export async function execute(this: IExecuteFunctions, i: number): Promise<INode
break;
}
// if there's conversation, we don't need to include function_call or reasoning items in the request
// if we include them, OpenAI will throw "Duplicate item with id" error
if (!body.conversation) {
body.input.push.apply(body.input, toolCalls);
}
for (const item of toolCalls) {
if (item.type === 'function_call' && answeredToolCalls.has(item.call_id)) {
continue;
}
// include function_call or reasoning items in the request
body.input.push(item);
if (item.type === 'function_call') {
const functionName = item.name;
const functionArgs = item.arguments;
@ -745,8 +743,6 @@ export async function execute(this: IExecuteFunctions, i: number): Promise<INode
call_id: callId,
output: functionResponse,
});
answeredToolCalls.add(callId);
}
}