n8n/packages/@n8n/nodes-langchain/nodes/agents/OpenAiAssistant/OpenAiAssistant.node.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

398 lines
9.8 KiB
TypeScript
Raw Normal View History

import { AgentExecutor } from 'langchain/agents';
import { OpenAI as OpenAIClient } from 'openai';
import { OpenAIAssistantRunnable } from 'langchain/experimental/openai_assistant';
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
import type {
IExecuteFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import type { OpenAIToolType } from 'langchain/dist/experimental/openai_assistant/schema';
import { getConnectedTools } from '../../../utils/helpers';
import { getTracingConfig } from '../../../utils/tracing';
import { formatToOpenAIAssistantTool } from './utils';
export class OpenAiAssistant implements INodeType {
description: INodeTypeDescription = {
displayName: 'OpenAI Assistant',
name: 'openAiAssistant',
2024-02-15 00:15:58 -08:00
hidden: true,
icon: 'fa:robot',
group: ['transform'],
version: [1, 1.1],
description: 'Utilizes Assistant API from Open AI.',
subtitle: 'Open AI Assistant',
defaults: {
name: 'OpenAI Assistant',
color: '#404040',
},
codex: {
alias: ['LangChain'],
categories: ['AI'],
subcategories: {
AI: ['Agents'],
},
resources: {
primaryDocumentation: [
{
url: 'https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.openaiassistant/',
},
],
},
},
inputs: [
{ type: NodeConnectionType.Main },
{ type: NodeConnectionType.AiTool, displayName: 'Tools' },
],
outputs: [NodeConnectionType.Main],
credentials: [
{
name: 'openAiApi',
required: true,
},
],
requestDefaults: {
ignoreHttpStatusErrors: true,
baseURL:
'={{ $parameter.options?.baseURL?.split("/").slice(0,-1).join("/") || "https://api.openai.com" }}',
},
properties: [
{
displayName: 'Operation',
name: 'mode',
type: 'options',
noDataExpression: true,
default: 'existing',
options: [
{
feat: AI nodes usability fixes + Summarization Chain V2 (#7949) Fixes: - Refactor connection snapping when dragging and enable it also for non-main connection types - Fix propagation of errors from sub-nodes - Fix chat scrolling when sending/receiving messages - Prevent empty chat messages - Fix sub-node selected styles - Fix output names text overflow Usability improvements: - Auto-add manual chat trigger for agents & chain nodes - Various labels and description updates - Make the output parser input optional for Basic LLM Chain - Summarization Chain V2 with a simplified document loader & text chunking mode #### How to test the change: Example workflow showcasing different operation mode of the new summarization chain: [Summarization_V2.json](https://github.com/n8n-io/n8n/files/13599901/Summarization_V2.json) ## Issues fixed Include links to Github issue or Community forum post or **Linear ticket**: > Important in order to close automatically and provide context to reviewers - https://www.notion.so/n8n/David-Langchain-Posthog-notes-7a9294938420403095f4508f1a21d31d - https://linear.app/n8n/issue/N8N-7070/ux-fixes-batch - https://linear.app/n8n/issue/N8N-7071/ai-sub-node-bugs ## Review / Merge checklist - [x] PR title and summary are descriptive. **Remember, the title automatically goes into the changelog. Use `(no-changelog)` otherwise.** ([conventions](https://github.com/n8n-io/n8n/blob/master/.github/pull_request_title_conventions.md)) - [x] [Docs updated](https://github.com/n8n-io/n8n-docs) or follow-up ticket created. - [ ] Tests included. > A bug is not considered fixed, unless a test is added to prevent it from happening again. A feature is not complete without tests. > > *(internal)* You can use Slack commands to trigger [e2e tests](https://www.notion.so/n8n/How-to-use-Test-Instances-d65f49dfc51f441ea44367fb6f67eb0a?pvs=4#a39f9e5ba64a48b58a71d81c837e8227) or [deploy test instance](https://www.notion.so/n8n/How-to-use-Test-Instances-d65f49dfc51f441ea44367fb6f67eb0a?pvs=4#f6a177d32bde4b57ae2da0b8e454bfce) or [deploy early access version on Cloud](https://www.notion.so/n8n/Cloudbot-3dbe779836004972b7057bc989526998?pvs=4#fef2d36ab02247e1a0f65a74f6fb534e). --------- Signed-off-by: Oleg Ivaniv <me@olegivaniv.com> Co-authored-by: Elias Meire <elias@meire.dev>
2023-12-08 04:42:32 -08:00
name: 'Use New Assistant',
value: 'new',
},
{
name: 'Use Existing Assistant',
value: 'existing',
},
],
},
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
'/mode': ['new'],
},
},
},
{
displayName: 'Instructions',
name: 'instructions',
type: 'string',
description: 'How the Assistant and model should behave or respond',
default: '',
typeOptions: {
rows: 5,
},
displayOptions: {
show: {
'/mode': ['new'],
},
},
},
{
displayName: 'Model',
name: 'model',
type: 'options',
description:
'The model which will be used to power the assistant. <a href="https://beta.openai.com/docs/models/overview">Learn more</a>. The Retrieval tool requires gpt-3.5-turbo-1106 and gpt-4-1106-preview models.',
required: true,
displayOptions: {
show: {
'/mode': ['new'],
},
},
typeOptions: {
loadOptions: {
routing: {
request: {
method: 'GET',
url: '={{ $parameter.options?.baseURL?.split("/").slice(-1).pop() || "v1" }}/models',
},
output: {
postReceive: [
{
type: 'rootProperty',
properties: {
property: 'data',
},
},
{
type: 'filter',
properties: {
pass: "={{ $responseItem.id.startsWith('gpt-') && !$responseItem.id.includes('instruct') }}",
},
},
{
type: 'setKeyValue',
properties: {
name: '={{$responseItem.id}}',
value: '={{$responseItem.id}}',
},
},
{
type: 'sort',
properties: {
key: 'name',
},
},
],
},
},
},
},
routing: {
send: {
type: 'body',
property: 'model',
},
},
default: 'gpt-3.5-turbo-1106',
},
{
displayName: 'Assistant',
name: 'assistantId',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
'/mode': ['existing'],
},
},
description:
'The assistant to use. <a href="https://beta.openai.com/docs/assistants/overview">Learn more</a>.',
typeOptions: {
loadOptions: {
routing: {
request: {
method: 'GET',
headers: {
'OpenAI-Beta': 'assistants=v1',
},
url: '={{ $parameter.options?.baseURL?.split("/").slice(-1).pop() || "v1" }}/assistants',
},
output: {
postReceive: [
{
type: 'rootProperty',
properties: {
property: 'data',
},
},
{
type: 'setKeyValue',
properties: {
name: '={{$responseItem.name}}',
value: '={{$responseItem.id}}',
// eslint-disable-next-line n8n-local-rules/no-interpolation-in-regular-string
description: '={{$responseItem.model}}',
},
},
{
type: 'sort',
properties: {
key: 'name',
},
},
],
},
},
},
},
routing: {
send: {
type: 'body',
property: 'assistant',
},
},
required: true,
default: '',
},
{
displayName: 'Text',
name: 'text',
type: 'string',
required: true,
default: '={{ $json.chat_input }}',
displayOptions: {
show: {
'@version': [1],
},
},
},
{
displayName: 'Text',
name: 'text',
type: 'string',
required: true,
default: '={{ $json.chatInput }}',
displayOptions: {
show: {
'@version': [1.1],
},
},
},
{
displayName: 'OpenAI Tools',
name: 'nativeTools',
type: 'multiOptions',
default: [],
options: [
{
name: 'Code Interpreter',
value: 'code_interpreter',
},
{
feat: AI nodes usability fixes + Summarization Chain V2 (#7949) Fixes: - Refactor connection snapping when dragging and enable it also for non-main connection types - Fix propagation of errors from sub-nodes - Fix chat scrolling when sending/receiving messages - Prevent empty chat messages - Fix sub-node selected styles - Fix output names text overflow Usability improvements: - Auto-add manual chat trigger for agents & chain nodes - Various labels and description updates - Make the output parser input optional for Basic LLM Chain - Summarization Chain V2 with a simplified document loader & text chunking mode #### How to test the change: Example workflow showcasing different operation mode of the new summarization chain: [Summarization_V2.json](https://github.com/n8n-io/n8n/files/13599901/Summarization_V2.json) ## Issues fixed Include links to Github issue or Community forum post or **Linear ticket**: > Important in order to close automatically and provide context to reviewers - https://www.notion.so/n8n/David-Langchain-Posthog-notes-7a9294938420403095f4508f1a21d31d - https://linear.app/n8n/issue/N8N-7070/ux-fixes-batch - https://linear.app/n8n/issue/N8N-7071/ai-sub-node-bugs ## Review / Merge checklist - [x] PR title and summary are descriptive. **Remember, the title automatically goes into the changelog. Use `(no-changelog)` otherwise.** ([conventions](https://github.com/n8n-io/n8n/blob/master/.github/pull_request_title_conventions.md)) - [x] [Docs updated](https://github.com/n8n-io/n8n-docs) or follow-up ticket created. - [ ] Tests included. > A bug is not considered fixed, unless a test is added to prevent it from happening again. A feature is not complete without tests. > > *(internal)* You can use Slack commands to trigger [e2e tests](https://www.notion.so/n8n/How-to-use-Test-Instances-d65f49dfc51f441ea44367fb6f67eb0a?pvs=4#a39f9e5ba64a48b58a71d81c837e8227) or [deploy test instance](https://www.notion.so/n8n/How-to-use-Test-Instances-d65f49dfc51f441ea44367fb6f67eb0a?pvs=4#f6a177d32bde4b57ae2da0b8e454bfce) or [deploy early access version on Cloud](https://www.notion.so/n8n/Cloudbot-3dbe779836004972b7057bc989526998?pvs=4#fef2d36ab02247e1a0f65a74f6fb534e). --------- Signed-off-by: Oleg Ivaniv <me@olegivaniv.com> Co-authored-by: Elias Meire <elias@meire.dev>
2023-12-08 04:42:32 -08:00
name: 'Knowledge Retrieval',
value: 'retrieval',
},
],
},
feat: AI nodes usability fixes + Summarization Chain V2 (#7949) Fixes: - Refactor connection snapping when dragging and enable it also for non-main connection types - Fix propagation of errors from sub-nodes - Fix chat scrolling when sending/receiving messages - Prevent empty chat messages - Fix sub-node selected styles - Fix output names text overflow Usability improvements: - Auto-add manual chat trigger for agents & chain nodes - Various labels and description updates - Make the output parser input optional for Basic LLM Chain - Summarization Chain V2 with a simplified document loader & text chunking mode #### How to test the change: Example workflow showcasing different operation mode of the new summarization chain: [Summarization_V2.json](https://github.com/n8n-io/n8n/files/13599901/Summarization_V2.json) ## Issues fixed Include links to Github issue or Community forum post or **Linear ticket**: > Important in order to close automatically and provide context to reviewers - https://www.notion.so/n8n/David-Langchain-Posthog-notes-7a9294938420403095f4508f1a21d31d - https://linear.app/n8n/issue/N8N-7070/ux-fixes-batch - https://linear.app/n8n/issue/N8N-7071/ai-sub-node-bugs ## Review / Merge checklist - [x] PR title and summary are descriptive. **Remember, the title automatically goes into the changelog. Use `(no-changelog)` otherwise.** ([conventions](https://github.com/n8n-io/n8n/blob/master/.github/pull_request_title_conventions.md)) - [x] [Docs updated](https://github.com/n8n-io/n8n-docs) or follow-up ticket created. - [ ] Tests included. > A bug is not considered fixed, unless a test is added to prevent it from happening again. A feature is not complete without tests. > > *(internal)* You can use Slack commands to trigger [e2e tests](https://www.notion.so/n8n/How-to-use-Test-Instances-d65f49dfc51f441ea44367fb6f67eb0a?pvs=4#a39f9e5ba64a48b58a71d81c837e8227) or [deploy test instance](https://www.notion.so/n8n/How-to-use-Test-Instances-d65f49dfc51f441ea44367fb6f67eb0a?pvs=4#f6a177d32bde4b57ae2da0b8e454bfce) or [deploy early access version on Cloud](https://www.notion.so/n8n/Cloudbot-3dbe779836004972b7057bc989526998?pvs=4#fef2d36ab02247e1a0f65a74f6fb534e). --------- Signed-off-by: Oleg Ivaniv <me@olegivaniv.com> Co-authored-by: Elias Meire <elias@meire.dev>
2023-12-08 04:42:32 -08:00
{
displayName: 'Connect your own custom tools to this node on the canvas',
name: 'noticeTools',
type: 'notice',
default: '',
},
{
displayName:
'Upload files for retrieval using the <a href="https://platform.openai.com/playground" target="_blank">OpenAI website<a/>',
name: 'noticeTools',
type: 'notice',
typeOptions: {
noticeTheme: 'info',
},
displayOptions: { show: { '/nativeTools': ['retrieval'] } },
default: '',
},
{
displayName: 'Options',
name: 'options',
placeholder: 'Add Option',
description: 'Additional options to add',
type: 'collection',
default: {},
options: [
{
displayName: 'Base URL',
name: 'baseURL',
default: 'https://api.openai.com/v1',
description: 'Override the default base URL for the API',
type: 'string',
},
{
displayName: 'Max Retries',
name: 'maxRetries',
default: 2,
description: 'Maximum number of retries to attempt',
type: 'number',
},
{
displayName: 'Timeout',
name: 'timeout',
default: 10000,
description: 'Maximum amount of time a request is allowed to take in milliseconds',
type: 'number',
},
],
},
],
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const nodeVersion = this.getNode().typeVersion;
const tools = await getConnectedTools(this, nodeVersion > 1);
const credentials = await this.getCredentials('openAiApi');
const items = this.getInputData();
const returnData: INodeExecutionData[] = [];
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
try {
const input = this.getNodeParameter('text', itemIndex) as string;
const assistantId = this.getNodeParameter('assistantId', itemIndex, '') as string;
const nativeTools = this.getNodeParameter('nativeTools', itemIndex, []) as Array<
'code_interpreter' | 'retrieval'
>;
const options = this.getNodeParameter('options', itemIndex, {}) as {
baseURL?: string;
maxRetries: number;
timeout: number;
};
if (input === undefined) {
throw new NodeOperationError(this.getNode(), 'The text parameter is empty.');
}
const client = new OpenAIClient({
apiKey: credentials.apiKey as string,
maxRetries: options.maxRetries ?? 2,
timeout: options.timeout ?? 10000,
baseURL: options.baseURL,
});
let agent;
const nativeToolsParsed: OpenAIToolType = nativeTools.map((tool) => ({ type: tool }));
const transformedConnectedTools = tools?.map(formatToOpenAIAssistantTool) ?? [];
const newTools = [...transformedConnectedTools, ...nativeToolsParsed];
// Existing agent, update tools with currently assigned
if (assistantId) {
agent = new OpenAIAssistantRunnable({ assistantId, client, asAgent: true });
await client.beta.assistants.update(assistantId, {
tools: newTools,
});
} else {
const name = this.getNodeParameter('name', itemIndex, '') as string;
const instructions = this.getNodeParameter('instructions', itemIndex, '') as string;
const model = this.getNodeParameter('model', itemIndex, 'gpt-3.5-turbo-1106') as string;
agent = await OpenAIAssistantRunnable.createAssistant({
model,
client,
instructions,
name,
tools: newTools,
asAgent: true,
});
}
const agentExecutor = AgentExecutor.fromAgentAndTools({
agent,
tools,
});
const response = await agentExecutor.withConfig(getTracingConfig(this)).invoke({
content: input,
signal: this.getExecutionCancelSignal(),
timeout: options.timeout ?? 10000,
});
returnData.push({ json: response });
} catch (error) {
if (this.continueOnFail()) {
returnData.push({ json: { error: error.message }, pairedItem: { item: itemIndex } });
continue;
}
throw error;
}
}
return await this.prepareOutputData(returnData);
}
}