mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
feat(OpenAI Node): Add Max Tools Iteration parameter and prevent tool calling after execution is aborted (#10735)
This commit is contained in:
parent
99ba710642
commit
5c47a5f691
|
@ -6,13 +6,13 @@ import type {
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { jsonParse, updateDisplayOptions } from 'n8n-workflow';
|
import { jsonParse, updateDisplayOptions } from 'n8n-workflow';
|
||||||
import type { Tool } from '@langchain/core/tools';
|
import type { Tool } from '@langchain/core/tools';
|
||||||
|
import _omit from 'lodash/omit';
|
||||||
import { apiRequest } from '../../transport';
|
import { apiRequest } from '../../transport';
|
||||||
import type { ChatCompletion } from '../../helpers/interfaces';
|
import type { ChatCompletion } from '../../helpers/interfaces';
|
||||||
import { formatToOpenAIAssistantTool } from '../../helpers/utils';
|
import { formatToOpenAIAssistantTool } from '../../helpers/utils';
|
||||||
import { modelRLC } from '../descriptions';
|
import { modelRLC } from '../descriptions';
|
||||||
import { getConnectedTools } from '../../../../../utils/helpers';
|
import { getConnectedTools } from '../../../../../utils/helpers';
|
||||||
import { MODELS_NOT_SUPPORT_FUNCTION_CALLS } from '../../helpers/constants';
|
import { MODELS_NOT_SUPPORT_FUNCTION_CALLS } from '../../helpers/constants';
|
||||||
|
|
||||||
const properties: INodeProperties[] = [
|
const properties: INodeProperties[] = [
|
||||||
modelRLC('modelSearch'),
|
modelRLC('modelSearch'),
|
||||||
{
|
{
|
||||||
|
@ -170,6 +170,19 @@ const properties: INodeProperties[] = [
|
||||||
'An alternative to sampling with temperature, controls diversity via nucleus sampling: 0.5 means half of all likelihood-weighted options are considered. We generally recommend altering this or temperature but not both.',
|
'An alternative to sampling with temperature, controls diversity via nucleus sampling: 0.5 means half of all likelihood-weighted options are considered. We generally recommend altering this or temperature but not both.',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Max Tool Calls Iterations',
|
||||||
|
name: 'maxToolsIterations',
|
||||||
|
type: 'number',
|
||||||
|
default: 15,
|
||||||
|
description:
|
||||||
|
'The maximum number of tool iteration cycles the LLM will run before stopping. A single iteration can contain multiple tool calls. Set to 0 for no limit.',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
'@version': [{ _cnd: { gte: 1.5 } }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -189,6 +202,10 @@ export async function execute(this: IExecuteFunctions, i: number): Promise<INode
|
||||||
let messages = this.getNodeParameter('messages.values', i, []) as IDataObject[];
|
let messages = this.getNodeParameter('messages.values', i, []) as IDataObject[];
|
||||||
const options = this.getNodeParameter('options', i, {});
|
const options = this.getNodeParameter('options', i, {});
|
||||||
const jsonOutput = this.getNodeParameter('jsonOutput', i, false) as boolean;
|
const jsonOutput = this.getNodeParameter('jsonOutput', i, false) as boolean;
|
||||||
|
const maxToolsIterations =
|
||||||
|
nodeVersion >= 1.5 ? (this.getNodeParameter('options.maxToolsIterations', i, 15) as number) : 0;
|
||||||
|
|
||||||
|
const abortSignal = this.getExecutionCancelSignal();
|
||||||
|
|
||||||
if (options.maxTokens !== undefined) {
|
if (options.maxTokens !== undefined) {
|
||||||
options.max_tokens = options.maxTokens;
|
options.max_tokens = options.maxTokens;
|
||||||
|
@ -231,7 +248,7 @@ export async function execute(this: IExecuteFunctions, i: number): Promise<INode
|
||||||
messages,
|
messages,
|
||||||
tools,
|
tools,
|
||||||
response_format,
|
response_format,
|
||||||
...options,
|
..._omit(options, ['maxToolsIterations']),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = (await apiRequest.call(this, 'POST', '/chat/completions', {
|
let response = (await apiRequest.call(this, 'POST', '/chat/completions', {
|
||||||
|
@ -240,9 +257,17 @@ export async function execute(this: IExecuteFunctions, i: number): Promise<INode
|
||||||
|
|
||||||
if (!response) return [];
|
if (!response) return [];
|
||||||
|
|
||||||
|
let currentIteration = 1;
|
||||||
let toolCalls = response?.choices[0]?.message?.tool_calls;
|
let toolCalls = response?.choices[0]?.message?.tool_calls;
|
||||||
|
|
||||||
while (toolCalls?.length) {
|
while (toolCalls?.length) {
|
||||||
|
// Break the loop if the max iterations is reached or the execution is canceled
|
||||||
|
if (
|
||||||
|
abortSignal?.aborted ||
|
||||||
|
(maxToolsIterations > 0 && currentIteration >= maxToolsIterations)
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
messages.push(response.choices[0].message);
|
messages.push(response.choices[0].message);
|
||||||
|
|
||||||
for (const toolCall of toolCalls) {
|
for (const toolCall of toolCalls) {
|
||||||
|
@ -274,6 +299,7 @@ export async function execute(this: IExecuteFunctions, i: number): Promise<INode
|
||||||
})) as ChatCompletion;
|
})) as ChatCompletion;
|
||||||
|
|
||||||
toolCalls = response.choices[0].message.tool_calls;
|
toolCalls = response.choices[0].message.tool_calls;
|
||||||
|
currentIteration += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response_format) {
|
if (response_format) {
|
||||||
|
|
|
@ -69,7 +69,7 @@ export const versionDescription: INodeTypeDescription = {
|
||||||
name: 'openAi',
|
name: 'openAi',
|
||||||
icon: { light: 'file:openAi.svg', dark: 'file:openAi.dark.svg' },
|
icon: { light: 'file:openAi.svg', dark: 'file:openAi.dark.svg' },
|
||||||
group: ['transform'],
|
group: ['transform'],
|
||||||
version: [1, 1.1, 1.2, 1.3, 1.4],
|
version: [1, 1.1, 1.2, 1.3, 1.4, 1.5],
|
||||||
subtitle: `={{(${prettifyOperation})($parameter.resource, $parameter.operation)}}`,
|
subtitle: `={{(${prettifyOperation})($parameter.resource, $parameter.operation)}}`,
|
||||||
description: 'Message an assistant or GPT, analyze images, generate audio, etc.',
|
description: 'Message an assistant or GPT, analyze images, generate audio, etc.',
|
||||||
defaults: {
|
defaults: {
|
||||||
|
|
|
@ -11,6 +11,9 @@ import * as transport from '../transport';
|
||||||
const createExecuteFunctionsMock = (parameters: IDataObject) => {
|
const createExecuteFunctionsMock = (parameters: IDataObject) => {
|
||||||
const nodeParameters = parameters;
|
const nodeParameters = parameters;
|
||||||
return {
|
return {
|
||||||
|
getExecutionCancelSignal() {
|
||||||
|
return new AbortController().signal;
|
||||||
|
},
|
||||||
getNodeParameter(parameter: string) {
|
getNodeParameter(parameter: string) {
|
||||||
return get(nodeParameters, parameter);
|
return get(nodeParameters, parameter);
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue