mirror of
https://github.com/n8n-io/n8n.git
synced 2026-06-04 18:49:20 +02:00
fix(OpenAI Node): Don't include function calls when conversation id is used (#21047)
This commit is contained in:
parent
c62b6a0419
commit
2fff38827a
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user