Skip to content

Commit

Permalink
feat(OpenAI Node): Overhaul (#8335)
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-radency authored Feb 15, 2024
1 parent 2b9391a commit 941278d
Show file tree
Hide file tree
Showing 49 changed files with 3,542 additions and 20 deletions.
2 changes: 2 additions & 0 deletions packages/@n8n/nodes-langchain/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/** @type {import('jest').Config} */
module.exports = require('../../../jest.config');
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export class OpenAiAssistant implements INodeType {
description: INodeTypeDescription = {
displayName: 'OpenAI Assistant',
name: 'openAiAssistant',
hidden: true,
icon: 'fa:robot',
group: ['transform'],
version: 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ export class ToolCode implements INodeType {
outputNames: ['Tool'],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiAgent]),
{
displayName:
'See an example of a conversational agent with custom tool written in JavaScript <a href="/templates/1963" target="_blank">here</a>.',
name: 'noticeTemplateExample',
type: 'notice',
default: '',
},
{
displayName: 'Name',
name: 'name',
Expand Down Expand Up @@ -95,11 +102,12 @@ export class ToolCode implements INodeType {
editor: 'codeNodeEditor',
editorLanguage: 'javaScript',
},
default: '',
default:
'// Example: convert the incoming query to uppercase and return it\nreturn query.toUpperCase()',
// TODO: Add proper text here later
hint: 'You can access the input the tool receives via the input property "query". The returned value should be a single string.',
description:
'JavaScript code to execute.<br><br>Tip: You can use luxon vars like <code>$today</code> for dates and <code>$jmespath</code> for querying JSON structures. <a href="https://docs.n8n.io/nodes/n8n-nodes-base.function">Learn more</a>.',
// eslint-disable-next-line n8n-nodes-base/node-param-description-missing-final-period
description: 'E.g. Converts any text to uppercase',
noDataExpression: true,
},
{
Expand All @@ -115,11 +123,12 @@ export class ToolCode implements INodeType {
editor: 'codeNodeEditor',
editorLanguage: 'python',
},
default: '',
default:
'# Example: convert the incoming query to uppercase and return it\nreturn query.upper()',
// TODO: Add proper text here later
hint: 'You can access the input the tool receives via the input property "query". The returned value should be a single string.',
description:
'Python code to execute.<br><br>Tip: You can use built-in methods and variables like <code>_today</code> for dates and <code>_jmespath</code> for querying JSON structures. <a href="https://docs.n8n.io/code/builtin/">Learn more</a>.',
// eslint-disable-next-line n8n-nodes-base/node-param-description-missing-final-period
description: 'E.g. Converts any text to uppercase',
noDataExpression: true,
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ export class ToolWorkflow implements INodeType {
outputNames: ['Tool'],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiAgent]),
{
displayName:
'See an example of a workflow to suggest meeting slots using AI <a href="/templates/1953" target="_blank">here</a>.',
name: 'noticeTemplateExample',
type: 'notice',
default: '',
},
{
displayName: 'Name',
name: 'name',
Expand Down
17 changes: 17 additions & 0 deletions packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/OpenAi.node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { IExecuteFunctions, INodeType } from 'n8n-workflow';
import { router } from './actions/router';
import { versionDescription } from './actions/versionDescription';
import { listSearch, loadOptions } from './methods';

export class OpenAi implements INodeType {
description = versionDescription;

methods = {
listSearch,
loadOptions,
};

async execute(this: IExecuteFunctions) {
return await router.call(this);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
import type {
INodeProperties,
IExecuteFunctions,
INodeExecutionData,
IDataObject,
} from 'n8n-workflow';
import { NodeOperationError, updateDisplayOptions } from 'n8n-workflow';
import { apiRequest } from '../../transport';
import { modelRLC } from '../descriptions';

const properties: INodeProperties[] = [
modelRLC,
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
description: 'The name of the assistant. The maximum length is 256 characters.',
placeholder: 'e.g. My Assistant',
required: true,
},
{
displayName: 'Description',
name: 'description',
type: 'string',
default: '',
description: 'The description of the assistant. The maximum length is 512 characters.',
placeholder: 'e.g. My personal assistant',
},
{
displayName: 'Instructions',
name: 'instructions',
type: 'string',
description:
'The system instructions that the assistant uses. The maximum length is 32768 characters.',
default: '',
typeOptions: {
rows: 2,
},
},
{
displayName: 'Code Interpreter',
name: 'codeInterpreter',
type: 'boolean',
default: false,
description:
'Whether to enable the code interpreter that allows the assistants to write and run Python code in a sandboxed execution environment, find more <a href="https://platform.openai.com/docs/assistants/tools/code-interpreter" target="_blank">here</a>',
},
{
displayName: 'Knowledge Retrieval',
name: 'knowledgeRetrieval',
type: 'boolean',
default: false,
description:
'Whether to augments the assistant with knowledge from outside its model, such as proprietary product information or documents, find more <a href="https://platform.openai.com/docs/assistants/tools/knowledge-retrieval" target="_blank">here</a>',
},
//we want to display Files selector only when codeInterpreter true or knowledgeRetrieval true or both
{
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-multi-options
displayName: 'Files',
name: 'file_ids',
type: 'multiOptions',
// eslint-disable-next-line n8n-nodes-base/node-param-description-wrong-for-dynamic-multi-options
description:
'The files to be used by the assistant, there can be a maximum of 20 files attached to the assistant',
typeOptions: {
loadOptionsMethod: 'getFiles',
},
default: [],
hint: "Add more files by using the 'Upload a File' operation",
displayOptions: {
show: {
codeInterpreter: [true],
},
hide: {
knowledgeRetrieval: [true],
},
},
},
{
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-multi-options
displayName: 'Files',
name: 'file_ids',
type: 'multiOptions',
// eslint-disable-next-line n8n-nodes-base/node-param-description-wrong-for-dynamic-multi-options
description:
'The files to be used by the assistant, there can be a maximum of 20 files attached to the assistant',
typeOptions: {
loadOptionsMethod: 'getFiles',
},
default: [],
hint: "Add more files by using the 'Upload a File' operation",
displayOptions: {
show: {
knowledgeRetrieval: [true],
},
hide: {
codeInterpreter: [true],
},
},
},
{
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-multi-options
displayName: 'Files',
name: 'file_ids',
type: 'multiOptions',
// eslint-disable-next-line n8n-nodes-base/node-param-description-wrong-for-dynamic-multi-options
description:
'The files to be used by the assistant, there can be a maximum of 20 files attached to the assistant',
typeOptions: {
loadOptionsMethod: 'getFiles',
},
default: [],
hint: "Add more files by using the 'Upload a File' operation",
displayOptions: {
show: {
knowledgeRetrieval: [true],
codeInterpreter: [true],
},
},
},
{
displayName: "Add custom n8n tools when using the 'Message Assistant' operation",
name: 'noticeTools',
type: 'notice',
default: '',
},
{
displayName: 'Options',
name: 'options',
placeholder: 'Add Option',
type: 'collection',
default: {},
options: [
{
displayName: 'Fail if Assistant Already Exists',
name: 'failIfExists',
type: 'boolean',
default: false,
description:
'Whether to fail an operation if the assistant with the same name already exists',
},
],
},
];

const displayOptions = {
show: {
operation: ['create'],
resource: ['assistant'],
},
};

export const description = updateDisplayOptions(displayOptions, properties);

export async function execute(this: IExecuteFunctions, i: number): Promise<INodeExecutionData[]> {
const model = this.getNodeParameter('modelId', i, '', { extractValue: true }) as string;
const name = this.getNodeParameter('name', i) as string;
const assistantDescription = this.getNodeParameter('description', i) as string;
const instructions = this.getNodeParameter('instructions', i) as string;
const codeInterpreter = this.getNodeParameter('codeInterpreter', i) as boolean;
const knowledgeRetrieval = this.getNodeParameter('knowledgeRetrieval', i) as boolean;
const file_ids = this.getNodeParameter('file_ids', i, []) as string[];
const options = this.getNodeParameter('options', i, {});

if (options.failIfExists) {
const assistants: string[] = [];

let has_more = true;
let after: string | undefined;

do {
const response = await apiRequest.call(this, 'GET', '/assistants', {

Check warning on line 173 in packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/create.operation.ts

View workflow job for this annotation

GitHub Actions / Lint changes (18.x)

Unsafe assignment of an `any` value

Check warning on line 173 in packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/create.operation.ts

View workflow job for this annotation

GitHub Actions / Lint changes (20.x)

Unsafe assignment of an `any` value
headers: {
'OpenAI-Beta': 'assistants=v1',
},
qs: {
limit: 100,
after,
},
});

for (const assistant of response.data || []) {

Check warning on line 183 in packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/create.operation.ts

View workflow job for this annotation

GitHub Actions / Lint changes (18.x)

Unsafe member access .data on an `any` value

Check warning on line 183 in packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/create.operation.ts

View workflow job for this annotation

GitHub Actions / Lint changes (20.x)

Unsafe member access .data on an `any` value
assistants.push(assistant.name);

Check warning on line 184 in packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/create.operation.ts

View workflow job for this annotation

GitHub Actions / Lint changes (18.x)

Unsafe argument of type `any` assigned to a parameter of type `string`

Check warning on line 184 in packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/create.operation.ts

View workflow job for this annotation

GitHub Actions / Lint changes (18.x)

Unsafe member access .name on an `any` value

Check warning on line 184 in packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/create.operation.ts

View workflow job for this annotation

GitHub Actions / Lint changes (20.x)

Unsafe argument of type `any` assigned to a parameter of type `string`

Check warning on line 184 in packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/create.operation.ts

View workflow job for this annotation

GitHub Actions / Lint changes (20.x)

Unsafe member access .name on an `any` value
}

has_more = response.has_more;

Check warning on line 187 in packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/create.operation.ts

View workflow job for this annotation

GitHub Actions / Lint changes (18.x)

Unsafe assignment of an `any` value

Check warning on line 187 in packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/create.operation.ts

View workflow job for this annotation

GitHub Actions / Lint changes (18.x)

Unsafe member access .has_more on an `any` value

Check warning on line 187 in packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/create.operation.ts

View workflow job for this annotation

GitHub Actions / Lint changes (20.x)

Unsafe assignment of an `any` value

Check warning on line 187 in packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/create.operation.ts

View workflow job for this annotation

GitHub Actions / Lint changes (20.x)

Unsafe member access .has_more on an `any` value

if (has_more) {
after = response.last_id as string;

Check warning on line 190 in packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/create.operation.ts

View workflow job for this annotation

GitHub Actions / Lint changes (18.x)

Unsafe member access .last_id on an `any` value

Check warning on line 190 in packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/create.operation.ts

View workflow job for this annotation

GitHub Actions / Lint changes (20.x)

Unsafe member access .last_id on an `any` value
} else {
break;
}
} while (has_more);

if (assistants.includes(name)) {
throw new NodeOperationError(
this.getNode(),
`An assistant with the same name '${name}' already exists`,
{ itemIndex: i },
);
}
}

if (file_ids.length > 20) {
throw new NodeOperationError(
this.getNode(),
'The maximum number of files that can be attached to the assistant is 20',
{ itemIndex: i },
);
}

const body: IDataObject = {
model,
name,
description: assistantDescription,
instructions,
file_ids,
};

const tools = [];

if (codeInterpreter) {
tools.push({
type: 'code_interpreter',
});
}

if (knowledgeRetrieval) {
tools.push({
type: 'retrieval',
});
}

if (tools.length) {
body.tools = tools;
}

const response = await apiRequest.call(this, 'POST', '/assistants', {

Check warning on line 239 in packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/create.operation.ts

View workflow job for this annotation

GitHub Actions / Lint changes (18.x)

Unsafe assignment of an `any` value

Check warning on line 239 in packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/create.operation.ts

View workflow job for this annotation

GitHub Actions / Lint changes (20.x)

Unsafe assignment of an `any` value
body,
headers: {
'OpenAI-Beta': 'assistants=v1',
},
});

return [
{
json: response,

Check warning on line 248 in packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/create.operation.ts

View workflow job for this annotation

GitHub Actions / Lint changes (18.x)

Unsafe assignment of an `any` value

Check warning on line 248 in packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/create.operation.ts

View workflow job for this annotation

GitHub Actions / Lint changes (20.x)

Unsafe assignment of an `any` value
pairedItem: { item: i },
},
];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { updateDisplayOptions } from 'n8n-workflow';
import { apiRequest } from '../../transport';
import { assistantRLC } from '../descriptions';

const properties: INodeProperties[] = [assistantRLC];

const displayOptions = {
show: {
operation: ['deleteAssistant'],
resource: ['assistant'],
},
};

export const description = updateDisplayOptions(displayOptions, properties);

export async function execute(this: IExecuteFunctions, i: number): Promise<INodeExecutionData[]> {
const assistantId = this.getNodeParameter('assistantId', i, '', { extractValue: true }) as string;

const response = await apiRequest.call(this, 'DELETE', `/assistants/${assistantId}`, {
headers: {
'OpenAI-Beta': 'assistants=v1',
},
});

return [
{
json: response,
pairedItem: { item: i },
},
];
}
Loading

0 comments on commit 941278d

Please sign in to comment.