feat(OpenAI Node): Allow to specify thread ID for Assistant -> Message operation (#11080)

This commit is contained in:
oleg 2024-10-07 09:58:28 +02:00 committed by GitHub
parent 7e8955b322
commit 6a2f9e7295
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 84 additions and 26 deletions

View file

@ -1,30 +1,27 @@
import type { BaseMessage } from '@langchain/core/messages';
import { AgentExecutor } from 'langchain/agents';
import { OpenAIAssistantRunnable } from 'langchain/experimental/openai_assistant';
import type { OpenAIToolType } from 'langchain/dist/experimental/openai_assistant/schema';
import { OpenAI as OpenAIClient } from 'openai';
import {
ApplicationError,
NodeConnectionType,
NodeOperationError,
updateDisplayOptions,
} from 'n8n-workflow';
import { OpenAIAssistantRunnable } from 'langchain/experimental/openai_assistant';
import type { BufferWindowMemory } from 'langchain/memory';
import omit from 'lodash/omit';
import type {
IDataObject,
IExecuteFunctions,
INodeExecutionData,
INodeProperties,
} from 'n8n-workflow';
import type { BufferWindowMemory } from 'langchain/memory';
import omit from 'lodash/omit';
import type { BaseMessage } from '@langchain/core/messages';
import { formatToOpenAIAssistantTool } from '../../helpers/utils';
import { assistantRLC } from '../descriptions';
import {
ApplicationError,
NodeConnectionType,
NodeOperationError,
updateDisplayOptions,
} from 'n8n-workflow';
import { OpenAI as OpenAIClient } from 'openai';
import { getConnectedTools } from '../../../../../utils/helpers';
import { getTracingConfig } from '../../../../../utils/tracing';
import { formatToOpenAIAssistantTool } from '../../helpers/utils';
import { assistantRLC } from '../descriptions';
const properties: INodeProperties[] = [
assistantRLC,
@ -63,6 +60,46 @@ const properties: INodeProperties[] = [
},
},
},
{
displayName: 'Memory',
name: 'memory',
type: 'options',
options: [
{
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
name: 'Use memory connector',
value: 'connector',
description: 'Connect one of the supported memory nodes',
},
{
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
name: 'Use thread ID',
value: 'threadId',
description: 'Specify the ID of the thread to continue',
},
],
displayOptions: {
show: {
'@version': [{ _cnd: { gte: 1.6 } }],
},
},
default: 'connector',
},
{
displayName: 'Thread ID',
name: 'threadId',
type: 'string',
default: '',
placeholder: '',
description: 'The ID of the thread to continue, a new thread will be created if not specified',
hint: 'If the thread ID is empty or undefined a new thread will be created and included in the response',
displayOptions: {
show: {
'@version': [{ _cnd: { gte: 1.6 } }],
memory: ['threadId'],
},
},
},
{
displayName: 'Connect your own custom n8n tools to this node on the canvas',
name: 'noticeTools',
@ -201,9 +238,19 @@ export async function execute(this: IExecuteFunctions, i: number): Promise<INode
tools: tools ?? [],
});
const memory = (await this.getInputConnectionData(NodeConnectionType.AiMemory, 0)) as
const useMemoryConnector =
nodeVersion >= 1.6 && this.getNodeParameter('memory', i) === 'connector';
const memory =
useMemoryConnector || nodeVersion < 1.6
? ((await this.getInputConnectionData(NodeConnectionType.AiMemory, 0)) as
| BufferWindowMemory
| undefined;
| undefined)
: undefined;
const threadId =
nodeVersion >= 1.6 && !useMemoryConnector
? (this.getNodeParameter('threadId', i) as string)
: undefined;
const chainValues: IDataObject = {
content: input,
@ -231,6 +278,8 @@ export async function execute(this: IExecuteFunctions, i: number): Promise<INode
chainValues.threadId = thread.id;
}
} else if (threadId) {
chainValues.threadId = threadId;
}
let filteredResponse: IDataObject = {};
@ -257,7 +306,8 @@ export async function execute(this: IExecuteFunctions, i: number): Promise<INode
tools: assistantTools,
});
}
filteredResponse = omit(response, ['signal', 'timeout']) as IDataObject;
// Remove configuration properties and runId added by Langchain that are not relevant to the user
filteredResponse = omit(response, ['signal', 'timeout', 'content', 'runId']) as IDataObject;
} catch (error) {
if (!(error instanceof ApplicationError)) {
throw new NodeOperationError(this.getNode(), error.message, { itemIndex: i });

View file

@ -1,5 +1,5 @@
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
import type { INodeTypeDescription } from 'n8n-workflow';
import type { INodeInputConfiguration, INodeTypeDescription } from 'n8n-workflow';
import { NodeConnectionType } from 'n8n-workflow';
import * as assistant from './assistant';
@ -42,13 +42,21 @@ const prettifyOperation = (resource: string, operation: string) => {
return `${capitalize(operation)} ${capitalize(resource)}`;
};
const configureNodeInputs = (resource: string, operation: string, hideTools: string) => {
const configureNodeInputs = (
resource: string,
operation: string,
hideTools: string,
memory: string | undefined,
) => {
if (resource === 'assistant' && operation === 'message') {
return [
const inputs: INodeInputConfiguration[] = [
{ type: NodeConnectionType.Main },
{ type: NodeConnectionType.AiMemory, displayName: 'Memory', maxConnections: 1 },
{ type: NodeConnectionType.AiTool, displayName: 'Tools' },
];
if (memory !== 'threadId') {
inputs.push({ type: NodeConnectionType.AiMemory, displayName: 'Memory', maxConnections: 1 });
}
return inputs;
}
if (resource === 'text' && operation === 'message') {
if (hideTools === 'hide') {
@ -69,7 +77,7 @@ export const versionDescription: INodeTypeDescription = {
name: 'openAi',
icon: { light: 'file:openAi.svg', dark: 'file:openAi.dark.svg' },
group: ['transform'],
version: [1, 1.1, 1.2, 1.3, 1.4, 1.5],
version: [1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6],
subtitle: `={{(${prettifyOperation})($parameter.resource, $parameter.operation)}}`,
description: 'Message an assistant or GPT, analyze images, generate audio, etc.',
defaults: {
@ -89,7 +97,7 @@ export const versionDescription: INodeTypeDescription = {
],
},
},
inputs: `={{(${configureNodeInputs})($parameter.resource, $parameter.operation, $parameter.hideTools)}}`,
inputs: `={{(${configureNodeInputs})($parameter.resource, $parameter.operation, $parameter.hideTools, $parameter.memory ?? undefined)}}`,
outputs: [NodeConnectionType.Main],
credentials: [
{