From 75ba58f0b7ed0b101a90f31f6defdcafce28ce11 Mon Sep 17 00:00:00 2001 From: Valya Bullions Date: Wed, 21 Jun 2023 13:52:05 +0100 Subject: [PATCH 001/372] add new expression evaluator and setting to toggle it --- packages/cli/src/ExpressionEvalator.ts | 6 + packages/cli/src/Server.ts | 3 + packages/cli/src/commands/BaseCommand.ts | 2 + packages/cli/src/config/schema.ts | 9 + .../editor-ui/src/mixins/workflowHelpers.ts | 9 +- packages/workflow/src/Expression.ts | 38 +- .../workflow/src/ExpressionEvaluatorProxy.ts | 54 +++ packages/workflow/src/Interfaces.ts | 5 + packages/workflow/src/index.ts | 1 + packages/workflow/test/Expression.test.ts | 370 +++++++++--------- 10 files changed, 302 insertions(+), 195 deletions(-) create mode 100644 packages/cli/src/ExpressionEvalator.ts create mode 100644 packages/workflow/src/ExpressionEvaluatorProxy.ts diff --git a/packages/cli/src/ExpressionEvalator.ts b/packages/cli/src/ExpressionEvalator.ts new file mode 100644 index 0000000000000..4f1f5149a2d7b --- /dev/null +++ b/packages/cli/src/ExpressionEvalator.ts @@ -0,0 +1,6 @@ +import config from '@/config'; +import { ExpressionEvaluatorProxy } from 'n8n-workflow'; + +export const initExpressionEvaluator = () => { + ExpressionEvaluatorProxy.setEvaluator(config.getEnv('expression.evaluator')); +}; diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 545c6449b20f2..0401dccc38ba1 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -322,6 +322,9 @@ export class Server extends AbstractServer { variables: { limit: 0, }, + expressions: { + evaluator: config.get('expression.evaluator') as 'tmpl' | 'tournament', + }, }; } diff --git a/packages/cli/src/commands/BaseCommand.ts b/packages/cli/src/commands/BaseCommand.ts index 9b190542d5a87..40988d9abc34c 100644 --- a/packages/cli/src/commands/BaseCommand.ts +++ b/packages/cli/src/commands/BaseCommand.ts @@ -20,6 +20,7 @@ import type { IExternalHooksClass } from '@/Interfaces'; import { InternalHooks } from '@/InternalHooks'; import { PostHogClient } from '@/posthog'; import { License } from '@/License'; +import { initExpressionEvaluator } from '@/ExpressionEvalator'; export const UM_FIX_INSTRUCTION = 'Please fix the database by running ./packages/cli/bin/n8n user-management:reset'; @@ -41,6 +42,7 @@ export abstract class BaseCommand extends Command { async init(): Promise { await initErrorHandling(); + initExpressionEvaluator(); process.once('SIGTERM', async () => this.stopProcess()); process.once('SIGINT', async () => this.stopProcess()); diff --git a/packages/cli/src/config/schema.ts b/packages/cli/src/config/schema.ts index c25db2b255f65..1bc50c9d36226 100644 --- a/packages/cli/src/config/schema.ts +++ b/packages/cli/src/config/schema.ts @@ -1185,4 +1185,13 @@ export const schema = { }, }, }, + + expression: { + evaluator: { + doc: 'Expression evaluator to use', + format: ['tmpl', 'tournament'] as const, + default: 'tmpl', + env: 'N8N_EXPRESSION_EVALUATOR', + }, + }, }; diff --git a/packages/editor-ui/src/mixins/workflowHelpers.ts b/packages/editor-ui/src/mixins/workflowHelpers.ts index 93fbefec7895c..53960b87b3412 100644 --- a/packages/editor-ui/src/mixins/workflowHelpers.ts +++ b/packages/editor-ui/src/mixins/workflowHelpers.ts @@ -9,7 +9,7 @@ import { MODAL_CONFIRM, } from '@/constants'; -import type { +import { IConnections, IDataObject, INode, @@ -27,6 +27,7 @@ import type { IExecuteData, INodeConnection, IWebhookDescription, + ExpressionEvaluatorProxy, } from 'n8n-workflow'; import { NodeHelpers } from 'n8n-workflow'; @@ -61,7 +62,7 @@ import { useUsersStore } from '@/stores/users.store'; import type { IPermissions } from '@/permissions'; import { getWorkflowPermissions } from '@/permissions'; import type { ICredentialsResponse } from '@/Interface'; -import { useEnvironmentsStore } from '@/stores'; +import { useEnvironmentsStore, useSettingsStore } from '@/stores'; export function resolveParameter( parameter: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[], @@ -157,6 +158,10 @@ export function resolveParameter( } const _executeData = executeData(parentNode, activeNode!.name, inputName, runIndexCurrent); + ExpressionEvaluatorProxy.setEvaluator( + useSettingsStore().settings.expressions?.evaluator ?? 'tmpl', + ); + return workflow.expression.getParameterValue( parameter, runExecutionData, diff --git a/packages/workflow/src/Expression.ts b/packages/workflow/src/Expression.ts index 2d6aa4e14fb26..a18d12b5d00b5 100644 --- a/packages/workflow/src/Expression.ts +++ b/packages/workflow/src/Expression.ts @@ -1,5 +1,5 @@ -import * as tmpl from '@n8n_io/riot-tmpl'; import { DateTime, Duration, Interval } from 'luxon'; +import type * as tmpl from '@n8n_io/riot-tmpl'; import type { IExecuteData, @@ -22,12 +22,29 @@ import type { Workflow } from './Workflow'; import { extend, extendOptional } from './Extensions'; import { extendedFunctions } from './Extensions/ExtendedFunctions'; import { extendSyntax } from './Extensions/ExpressionExtension'; - -// Set it to use double curly brackets instead of single ones -tmpl.brackets.set('{{ }}'); - -// Make sure that error get forwarded -tmpl.tmpl.errorHandler = (error: Error) => { +import { + checkEvaluatorDifferences, + evaluateExpression, + setErrorHandler, +} from './ExpressionEvaluatorProxy'; + +// // Set it to use double curly brackets instead of single ones +// tmpl.brackets.set('{{ }}'); + +// // Make sure that error get forwarded +// tmpl.tmpl.errorHandler = (error: Error) => { +// if (error instanceof ExpressionError) { +// if (error.context.failExecution) { +// throw error; +// } + +// if (typeof process === 'undefined' && error.clientOnly) { +// throw error; +// } +// } +// }; + +setErrorHandler((error: Error) => { if (error instanceof ExpressionError) { if (error.context.failExecution) { throw error; @@ -37,7 +54,7 @@ tmpl.tmpl.errorHandler = (error: Error) => { throw error; } } -}; +}); // eslint-disable-next-line @typescript-eslint/naming-convention const AsyncFunction = (async () => {}).constructor as FunctionConstructor; @@ -59,7 +76,7 @@ export class Expression { } static resolveWithoutWorkflow(expression: string) { - return tmpl.tmpl(expression, {}); + return evaluateExpression(expression, {}); } /** @@ -330,7 +347,8 @@ export class Expression { [Function, AsyncFunction].forEach(({ prototype }) => Object.defineProperty(prototype, 'constructor', { value: fnConstructors.mock }), ); - return tmpl.tmpl(expression, data); + checkEvaluatorDifferences(expression); + return evaluateExpression(expression, data); } catch (error) { if (error instanceof ExpressionError) { // Ignore all errors except if they are ExpressionErrors and they are supposed diff --git a/packages/workflow/src/ExpressionEvaluatorProxy.ts b/packages/workflow/src/ExpressionEvaluatorProxy.ts new file mode 100644 index 0000000000000..30ca62190ac0c --- /dev/null +++ b/packages/workflow/src/ExpressionEvaluatorProxy.ts @@ -0,0 +1,54 @@ +import * as tmpl from '@n8n_io/riot-tmpl'; +import type { TmplDifference } from '@n8n/tournament'; +import { Tournament } from '@n8n/tournament'; +import type { ExpressionEvaluatorType } from './Interfaces'; + +type Evaluator = (expr: string, data: unknown) => tmpl.ReturnValue; +type ErrorHandler = (error: Error) => void; +type DifferenceHandler = (expr: string) => void; + +// Set it to use double curly brackets instead of single ones +tmpl.brackets.set('{{ }}'); + +let errorHandler: ErrorHandler = () => {}; +let differenceHandler: DifferenceHandler = () => {}; +const differenceChecker = (diff: TmplDifference) => { + if (diff.same) { + return; + } + if (diff.has?.function || diff.has?.templateString) { + return; + } + differenceHandler(diff.expression as string); +}; +const tournamentEvaluator = new Tournament(errorHandler, undefined, differenceChecker); +let evaluator: Evaluator = tmpl.tmpl; + +export const setErrorHandler = (handler: ErrorHandler) => { + errorHandler = handler; + tmpl.tmpl.errorHandler = handler; + tournamentEvaluator.errorHandler = handler; +}; + +export const setEvaluator = (evalType: ExpressionEvaluatorType) => { + console.log('setEvaluator', evalType); + if (evalType === 'tmpl') { + evaluator = tmpl.tmpl; + } else if (evalType === 'tournament') { + evaluator = tournamentEvaluator.execute.bind(tournamentEvaluator); + } +}; + +export const setDiffReporter = (reporter: (expr: string) => void) => { + differenceHandler = reporter; +}; + +export const checkEvaluatorDifferences = (expr: string) => { + tournamentEvaluator.tmplDiff(expr); +}; + +export const getEvaluator = () => { + return evaluator; +}; + +export const evaluateExpression: Evaluator = (expr, data) => evaluator(expr, data); diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 790b704bd2102..216568622c760 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -2039,6 +2039,8 @@ export interface IPublicApiSettings { export type ILogLevel = 'info' | 'debug' | 'warn' | 'error' | 'verbose' | 'silent'; +export type ExpressionEvaluatorType = 'tmpl' | 'tournament'; + export interface IN8nUISettings { endpointWebhook: string; endpointWebhookTest: string; @@ -2121,4 +2123,7 @@ export interface IN8nUISettings { variables: { limit: number; }; + expressions: { + evaluator: ExpressionEvaluatorType; + }; } diff --git a/packages/workflow/src/index.ts b/packages/workflow/src/index.ts index 6ab146926cf0c..1b59f34a67e27 100644 --- a/packages/workflow/src/index.ts +++ b/packages/workflow/src/index.ts @@ -1,5 +1,6 @@ import * as LoggerProxy from './LoggerProxy'; export * as ErrorReporterProxy from './ErrorReporterProxy'; +export * as ExpressionEvaluatorProxy from './ExpressionEvaluatorProxy'; import * as NodeHelpers from './NodeHelpers'; import * as ObservableObject from './ObservableObject'; import * as TelemetryHelpers from './TelemetryHelpers'; diff --git a/packages/workflow/test/Expression.test.ts b/packages/workflow/test/Expression.test.ts index 5229361440a11..ac1ab5bd156fe 100644 --- a/packages/workflow/test/Expression.test.ts +++ b/packages/workflow/test/Expression.test.ts @@ -10,208 +10,212 @@ import type { ExpressionTestEvaluation, ExpressionTestTransform } from './Expres import { baseFixtures } from './ExpressionFixtures/base'; import type { INodeExecutionData } from '@/Interfaces'; import { extendSyntax } from '@/Extensions/ExpressionExtension'; +import { setEvaluator } from '@/ExpressionEvaluatorProxy'; + +for (const evaluator of ['tmpl', 'tournament'] as const) { + setEvaluator(evaluator); + describe(`Expression (with ${evaluator})`, () => { + describe('getParameterValue()', () => { + const nodeTypes = Helpers.NodeTypes(); + const workflow = new Workflow({ + nodes: [ + { + name: 'node', + typeVersion: 1, + type: 'test.set', + id: 'uuid-1234', + position: [0, 0], + parameters: {}, + }, + ], + connections: {}, + active: false, + nodeTypes, + }); + const expression = new Expression(workflow); -describe('Expression', () => { - describe('getParameterValue()', () => { - const nodeTypes = Helpers.NodeTypes(); - const workflow = new Workflow({ - nodes: [ - { - name: 'node', - typeVersion: 1, - type: 'test.set', - id: 'uuid-1234', - position: [0, 0], - parameters: {}, - }, - ], - connections: {}, - active: false, - nodeTypes, - }); - const expression = new Expression(workflow); - - const evaluate = (value: string) => - expression.getParameterValue(value, null, 0, 0, 'node', [], 'manual', '', {}); + const evaluate = (value: string) => + expression.getParameterValue(value, null, 0, 0, 'node', [], 'manual', '', {}); - it('should not be able to use global built-ins from denylist', () => { - expect(evaluate('={{document}}')).toEqual({}); - expect(evaluate('={{window}}')).toEqual({}); + it('should not be able to use global built-ins from denylist', () => { + expect(evaluate('={{document}}')).toEqual({}); + expect(evaluate('={{window}}')).toEqual({}); - expect(evaluate('={{Window}}')).toEqual({}); - expect(evaluate('={{globalThis}}')).toEqual({}); - expect(evaluate('={{self}}')).toEqual({}); + expect(evaluate('={{Window}}')).toEqual({}); + expect(evaluate('={{globalThis}}')).toEqual({}); + expect(evaluate('={{self}}')).toEqual({}); - expect(evaluate('={{alert}}')).toEqual({}); - expect(evaluate('={{prompt}}')).toEqual({}); - expect(evaluate('={{confirm}}')).toEqual({}); + expect(evaluate('={{alert}}')).toEqual({}); + expect(evaluate('={{prompt}}')).toEqual({}); + expect(evaluate('={{confirm}}')).toEqual({}); - expect(evaluate('={{eval}}')).toEqual({}); - expect(evaluate('={{uneval}}')).toEqual({}); - expect(evaluate('={{setTimeout}}')).toEqual({}); - expect(evaluate('={{setInterval}}')).toEqual({}); - expect(evaluate('={{Function}}')).toEqual({}); + expect(evaluate('={{eval}}')).toEqual({}); + expect(evaluate('={{uneval}}')).toEqual({}); + expect(evaluate('={{setTimeout}}')).toEqual({}); + expect(evaluate('={{setInterval}}')).toEqual({}); + expect(evaluate('={{Function}}')).toEqual({}); - expect(evaluate('={{fetch}}')).toEqual({}); - expect(evaluate('={{XMLHttpRequest}}')).toEqual({}); + expect(evaluate('={{fetch}}')).toEqual({}); + expect(evaluate('={{XMLHttpRequest}}')).toEqual({}); - expect(evaluate('={{Promise}}')).toEqual({}); - expect(evaluate('={{Generator}}')).toEqual({}); - expect(evaluate('={{GeneratorFunction}}')).toEqual({}); - expect(evaluate('={{AsyncFunction}}')).toEqual({}); - expect(evaluate('={{AsyncGenerator}}')).toEqual({}); - expect(evaluate('={{AsyncGeneratorFunction}}')).toEqual({}); + expect(evaluate('={{Promise}}')).toEqual({}); + expect(evaluate('={{Generator}}')).toEqual({}); + expect(evaluate('={{GeneratorFunction}}')).toEqual({}); + expect(evaluate('={{AsyncFunction}}')).toEqual({}); + expect(evaluate('={{AsyncGenerator}}')).toEqual({}); + expect(evaluate('={{AsyncGeneratorFunction}}')).toEqual({}); - expect(evaluate('={{WebAssembly}}')).toEqual({}); + expect(evaluate('={{WebAssembly}}')).toEqual({}); - expect(evaluate('={{Reflect}}')).toEqual({}); - expect(evaluate('={{Proxy}}')).toEqual({}); + expect(evaluate('={{Reflect}}')).toEqual({}); + expect(evaluate('={{Proxy}}')).toEqual({}); - expect(evaluate('={{constructor}}')).toEqual({}); + expect(evaluate('={{constructor}}')).toEqual({}); - expect(evaluate('={{escape}}')).toEqual({}); - expect(evaluate('={{unescape}}')).toEqual({}); - }); + expect(evaluate('={{escape}}')).toEqual({}); + expect(evaluate('={{unescape}}')).toEqual({}); + }); - it('should be able to use global built-ins from allowlist', () => { - expect(evaluate('={{new Date()}}')).toBeInstanceOf(Date); - expect(evaluate('={{DateTime.now().toLocaleString()}}')).toEqual( - DateTime.now().toLocaleString(), - ); - expect(evaluate('={{Interval.after(new Date(), 100)}}')).toEqual( - Interval.after(new Date(), 100), - ); - expect(evaluate('={{Duration.fromMillis(100)}}')).toEqual(Duration.fromMillis(100)); - - expect(evaluate('={{new Object()}}')).toEqual(new Object()); - - expect(evaluate('={{new Array()}}')).toEqual([]); - expect(evaluate('={{new Int8Array()}}')).toEqual(new Int8Array()); - expect(evaluate('={{new Uint8Array()}}')).toEqual(new Uint8Array()); - expect(evaluate('={{new Uint8ClampedArray()}}')).toEqual(new Uint8ClampedArray()); - expect(evaluate('={{new Int16Array()}}')).toEqual(new Int16Array()); - expect(evaluate('={{new Uint16Array()}}')).toEqual(new Uint16Array()); - expect(evaluate('={{new Int32Array()}}')).toEqual(new Int32Array()); - expect(evaluate('={{new Uint32Array()}}')).toEqual(new Uint32Array()); - expect(evaluate('={{new Float32Array()}}')).toEqual(new Float32Array()); - expect(evaluate('={{new Float64Array()}}')).toEqual(new Float64Array()); - expect(evaluate('={{new BigInt64Array()}}')).toEqual(new BigInt64Array()); - expect(evaluate('={{new BigUint64Array()}}')).toEqual(new BigUint64Array()); - - expect(evaluate('={{new Map()}}')).toEqual(new Map()); - expect(evaluate('={{new WeakMap()}}')).toEqual(new WeakMap()); - expect(evaluate('={{new Set()}}')).toEqual(new Set()); - expect(evaluate('={{new WeakSet()}}')).toEqual(new WeakSet()); - - expect(evaluate('={{new Error()}}')).toEqual(new Error()); - expect(evaluate('={{new TypeError()}}')).toEqual(new TypeError()); - expect(evaluate('={{new SyntaxError()}}')).toEqual(new SyntaxError()); - expect(evaluate('={{new EvalError()}}')).toEqual(new EvalError()); - expect(evaluate('={{new RangeError()}}')).toEqual(new RangeError()); - expect(evaluate('={{new ReferenceError()}}')).toEqual(new ReferenceError()); - expect(evaluate('={{new URIError()}}')).toEqual(new URIError()); - - expect(evaluate('={{Intl}}')).toEqual(Intl); - - expect(evaluate('={{new String()}}')).toEqual(new String()); - expect(evaluate("={{new RegExp('')}}")).toEqual(new RegExp('')); - - expect(evaluate('={{Math}}')).toEqual(Math); - expect(evaluate('={{new Number()}}')).toEqual(new Number()); - expect(evaluate("={{BigInt('1')}}")).toEqual(BigInt('1')); - expect(evaluate('={{Infinity}}')).toEqual(Infinity); - expect(evaluate('={{NaN}}')).toEqual(NaN); - expect(evaluate('={{isFinite(1)}}')).toEqual(isFinite(1)); - expect(evaluate('={{isNaN(1)}}')).toEqual(isNaN(1)); - expect(evaluate("={{parseFloat('1')}}")).toEqual(parseFloat('1')); - expect(evaluate("={{parseInt('1', 10)}}")).toEqual(parseInt('1', 10)); - - expect(evaluate('={{JSON.stringify({})}}')).toEqual(JSON.stringify({})); - expect(evaluate('={{new ArrayBuffer(10)}}')).toEqual(new ArrayBuffer(10)); - expect(evaluate('={{new SharedArrayBuffer(10)}}')).toEqual(new SharedArrayBuffer(10)); - expect(evaluate('={{Atomics}}')).toEqual(Atomics); - expect(evaluate('={{new DataView(new ArrayBuffer(1))}}')).toEqual( - new DataView(new ArrayBuffer(1)), - ); - - expect(evaluate("={{encodeURI('https://google.com')}}")).toEqual( - encodeURI('https://google.com'), - ); - expect(evaluate("={{encodeURIComponent('https://google.com')}}")).toEqual( - encodeURIComponent('https://google.com'), - ); - expect(evaluate("={{decodeURI('https://google.com')}}")).toEqual( - decodeURI('https://google.com'), - ); - expect(evaluate("={{decodeURIComponent('https://google.com')}}")).toEqual( - decodeURIComponent('https://google.com'), - ); - - expect(evaluate('={{Boolean(1)}}')).toEqual(Boolean(1)); - expect(evaluate('={{Symbol(1).toString()}}')).toEqual(Symbol(1).toString()); - }); + it('should be able to use global built-ins from allowlist', () => { + expect(evaluate('={{new Date()}}')).toBeInstanceOf(Date); + expect(evaluate('={{DateTime.now().toLocaleString()}}')).toEqual( + DateTime.now().toLocaleString(), + ); + expect(evaluate('={{Interval.after(new Date(), 100)}}')).toEqual( + Interval.after(new Date(), 100), + ); + expect(evaluate('={{Duration.fromMillis(100)}}')).toEqual(Duration.fromMillis(100)); + + expect(evaluate('={{new Object()}}')).toEqual(new Object()); + + expect(evaluate('={{new Array()}}')).toEqual([]); + expect(evaluate('={{new Int8Array()}}')).toEqual(new Int8Array()); + expect(evaluate('={{new Uint8Array()}}')).toEqual(new Uint8Array()); + expect(evaluate('={{new Uint8ClampedArray()}}')).toEqual(new Uint8ClampedArray()); + expect(evaluate('={{new Int16Array()}}')).toEqual(new Int16Array()); + expect(evaluate('={{new Uint16Array()}}')).toEqual(new Uint16Array()); + expect(evaluate('={{new Int32Array()}}')).toEqual(new Int32Array()); + expect(evaluate('={{new Uint32Array()}}')).toEqual(new Uint32Array()); + expect(evaluate('={{new Float32Array()}}')).toEqual(new Float32Array()); + expect(evaluate('={{new Float64Array()}}')).toEqual(new Float64Array()); + expect(evaluate('={{new BigInt64Array()}}')).toEqual(new BigInt64Array()); + expect(evaluate('={{new BigUint64Array()}}')).toEqual(new BigUint64Array()); + + expect(evaluate('={{new Map()}}')).toEqual(new Map()); + expect(evaluate('={{new WeakMap()}}')).toEqual(new WeakMap()); + expect(evaluate('={{new Set()}}')).toEqual(new Set()); + expect(evaluate('={{new WeakSet()}}')).toEqual(new WeakSet()); + + expect(evaluate('={{new Error()}}')).toEqual(new Error()); + expect(evaluate('={{new TypeError()}}')).toEqual(new TypeError()); + expect(evaluate('={{new SyntaxError()}}')).toEqual(new SyntaxError()); + expect(evaluate('={{new EvalError()}}')).toEqual(new EvalError()); + expect(evaluate('={{new RangeError()}}')).toEqual(new RangeError()); + expect(evaluate('={{new ReferenceError()}}')).toEqual(new ReferenceError()); + expect(evaluate('={{new URIError()}}')).toEqual(new URIError()); + + expect(evaluate('={{Intl}}')).toEqual(Intl); + + expect(evaluate('={{new String()}}')).toEqual(new String()); + expect(evaluate("={{new RegExp('')}}")).toEqual(new RegExp('')); + + expect(evaluate('={{Math}}')).toEqual(Math); + expect(evaluate('={{new Number()}}')).toEqual(new Number()); + expect(evaluate("={{BigInt('1')}}")).toEqual(BigInt('1')); + expect(evaluate('={{Infinity}}')).toEqual(Infinity); + expect(evaluate('={{NaN}}')).toEqual(NaN); + expect(evaluate('={{isFinite(1)}}')).toEqual(isFinite(1)); + expect(evaluate('={{isNaN(1)}}')).toEqual(isNaN(1)); + expect(evaluate("={{parseFloat('1')}}")).toEqual(parseFloat('1')); + expect(evaluate("={{parseInt('1', 10)}}")).toEqual(parseInt('1', 10)); + + expect(evaluate('={{JSON.stringify({})}}')).toEqual(JSON.stringify({})); + expect(evaluate('={{new ArrayBuffer(10)}}')).toEqual(new ArrayBuffer(10)); + expect(evaluate('={{new SharedArrayBuffer(10)}}')).toEqual(new SharedArrayBuffer(10)); + expect(evaluate('={{Atomics}}')).toEqual(Atomics); + expect(evaluate('={{new DataView(new ArrayBuffer(1))}}')).toEqual( + new DataView(new ArrayBuffer(1)), + ); + + expect(evaluate("={{encodeURI('https://google.com')}}")).toEqual( + encodeURI('https://google.com'), + ); + expect(evaluate("={{encodeURIComponent('https://google.com')}}")).toEqual( + encodeURIComponent('https://google.com'), + ); + expect(evaluate("={{decodeURI('https://google.com')}}")).toEqual( + decodeURI('https://google.com'), + ); + expect(evaluate("={{decodeURIComponent('https://google.com')}}")).toEqual( + decodeURIComponent('https://google.com'), + ); + + expect(evaluate('={{Boolean(1)}}')).toEqual(Boolean(1)); + expect(evaluate('={{Symbol(1).toString()}}')).toEqual(Symbol(1).toString()); + }); - it('should not able to do arbitrary code execution', () => { - const testFn = jest.fn(); - Object.assign(global, { testFn }); - evaluate("={{ Date['constructor']('testFn()')()}}"); - expect(testFn).not.toHaveBeenCalled(); + it('should not able to do arbitrary code execution', () => { + const testFn = jest.fn(); + Object.assign(global, { testFn }); + evaluate("={{ Date['constructor']('testFn()')()}}"); + expect(testFn).not.toHaveBeenCalled(); + }); }); - }); - describe('Test all expression value fixtures', () => { - const nodeTypes = Helpers.NodeTypes(); - const workflow = new Workflow({ - nodes: [ - { - name: 'node', - typeVersion: 1, - type: 'test.set', - id: 'uuid-1234', - position: [0, 0], - parameters: {}, - }, - ], - connections: {}, - active: false, - nodeTypes, - }); + describe('Test all expression value fixtures', () => { + const nodeTypes = Helpers.NodeTypes(); + const workflow = new Workflow({ + nodes: [ + { + name: 'node', + typeVersion: 1, + type: 'test.set', + id: 'uuid-1234', + position: [0, 0], + parameters: {}, + }, + ], + connections: {}, + active: false, + nodeTypes, + }); - const expression = new Expression(workflow); + const expression = new Expression(workflow); - const evaluate = (value: string, data: INodeExecutionData[]) => { - return expression.getParameterValue(value, null, 0, 0, 'node', data, 'manual', '', {}); - }; + const evaluate = (value: string, data: INodeExecutionData[]) => { + return expression.getParameterValue(value, null, 0, 0, 'node', data, 'manual', '', {}); + }; - for (const t of baseFixtures) { - if (!t.tests.some((test) => test.type === 'evaluation')) { - continue; - } - test(t.expression, () => { - for (const test of t.tests.filter( - (test) => test.type === 'evaluation', - ) as ExpressionTestEvaluation[]) { - expect(evaluate(t.expression, test.input.map((d) => ({ json: d })) as any)).toStrictEqual( - test.output, - ); + for (const t of baseFixtures) { + if (!t.tests.some((test) => test.type === 'evaluation')) { + continue; } - }); - } - }); - - describe('Test all expression transform fixtures', () => { - for (const t of baseFixtures) { - if (!t.tests.some((test) => test.type === 'transform')) { - continue; + test(t.expression, () => { + for (const test of t.tests.filter( + (test) => test.type === 'evaluation', + ) as ExpressionTestEvaluation[]) { + expect( + evaluate(t.expression, test.input.map((d) => ({ json: d })) as any), + ).toStrictEqual(test.output); + } + }); } - test(t.expression, () => { - for (const test of t.tests.filter( - (test) => test.type === 'transform', - ) as ExpressionTestTransform[]) { - const expr = t.expression; - expect(extendSyntax(expr, test.forceTransform)).toEqual(test.result ?? expr); + }); + + describe('Test all expression transform fixtures', () => { + for (const t of baseFixtures) { + if (!t.tests.some((test) => test.type === 'transform')) { + continue; } - }); - } + test(t.expression, () => { + for (const test of t.tests.filter( + (test) => test.type === 'transform', + ) as ExpressionTestTransform[]) { + const expr = t.expression; + expect(extendSyntax(expr, test.forceTransform)).toEqual(test.result ?? expr); + } + }); + } + }); }); -}); +} From 5d322a00842b1a44d69dab44fe0de8eaba3ffbd6 Mon Sep 17 00:00:00 2001 From: Valya Bullions Date: Thu, 20 Jul 2023 16:26:25 +0100 Subject: [PATCH 002/372] feat: add expression difference reporter --- packages/cli/src/ExpressionEvalator.ts | 10 +- packages/cli/src/config/schema.ts | 6 ++ .../workflow/src/ExpressionEvaluatorProxy.ts | 100 ++++++++++++++++-- 3 files changed, 108 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/ExpressionEvalator.ts b/packages/cli/src/ExpressionEvalator.ts index 4f1f5149a2d7b..1b53f845dc1c5 100644 --- a/packages/cli/src/ExpressionEvalator.ts +++ b/packages/cli/src/ExpressionEvalator.ts @@ -1,6 +1,14 @@ import config from '@/config'; -import { ExpressionEvaluatorProxy } from 'n8n-workflow'; +import { ErrorReporterProxy, ExpressionEvaluatorProxy } from 'n8n-workflow'; export const initExpressionEvaluator = () => { ExpressionEvaluatorProxy.setEvaluator(config.getEnv('expression.evaluator')); + ExpressionEvaluatorProxy.setDifferEnabled(config.getEnv('expression.reportDifference')); + ExpressionEvaluatorProxy.setDiffReporter((expr) => { + ErrorReporterProxy.warn('Expression difference', { + extra: { + expression: expr, + }, + }); + }); }; diff --git a/packages/cli/src/config/schema.ts b/packages/cli/src/config/schema.ts index c324cbe54db27..74473a07eb9c3 100644 --- a/packages/cli/src/config/schema.ts +++ b/packages/cli/src/config/schema.ts @@ -1103,5 +1103,11 @@ export const schema = { default: 'tmpl', env: 'N8N_EXPRESSION_EVALUATOR', }, + reportDifference: { + doc: 'Expression evaluator to use', + format: Boolean, + default: false, + env: 'N8N_EXPRESSION_REPORT_DIFFERENCE', + }, }, }; diff --git a/packages/workflow/src/ExpressionEvaluatorProxy.ts b/packages/workflow/src/ExpressionEvaluatorProxy.ts index 30ca62190ac0c..fa49f703709cd 100644 --- a/packages/workflow/src/ExpressionEvaluatorProxy.ts +++ b/packages/workflow/src/ExpressionEvaluatorProxy.ts @@ -1,7 +1,8 @@ import * as tmpl from '@n8n_io/riot-tmpl'; -import type { TmplDifference } from '@n8n/tournament'; +import type { ReturnValue, TmplDifference } from '@n8n/tournament'; import { Tournament } from '@n8n/tournament'; import type { ExpressionEvaluatorType } from './Interfaces'; +import * as ErrorReporterProxy from './ErrorReporterProxy'; type Evaluator = (expr: string, data: unknown) => tmpl.ReturnValue; type ErrorHandler = (error: Error) => void; @@ -19,10 +20,16 @@ const differenceChecker = (diff: TmplDifference) => { if (diff.has?.function || diff.has?.templateString) { return; } - differenceHandler(diff.expression as string); + if (diff.expression === 'UNPARSEABLE') { + differenceHandler(diff.expression); + } else { + differenceHandler(diff.expression.value); + } }; -const tournamentEvaluator = new Tournament(errorHandler, undefined, differenceChecker); +const tournamentEvaluator = new Tournament(errorHandler, undefined); let evaluator: Evaluator = tmpl.tmpl; +let currentEvaluatorType: ExpressionEvaluatorType = 'tmpl'; +let diffExpressions = false; export const setErrorHandler = (handler: ErrorHandler) => { errorHandler = handler; @@ -31,7 +38,7 @@ export const setErrorHandler = (handler: ErrorHandler) => { }; export const setEvaluator = (evalType: ExpressionEvaluatorType) => { - console.log('setEvaluator', evalType); + currentEvaluatorType = evalType; if (evalType === 'tmpl') { evaluator = tmpl.tmpl; } else if (evalType === 'tournament') { @@ -43,12 +50,91 @@ export const setDiffReporter = (reporter: (expr: string) => void) => { differenceHandler = reporter; }; -export const checkEvaluatorDifferences = (expr: string) => { - tournamentEvaluator.tmplDiff(expr); +export const setDifferEnabled = (enabled: boolean) => { + diffExpressions = enabled; +}; + +const diffCache: Record = {}; + +export const checkEvaluatorDifferences = (expr: string): TmplDifference | null => { + if (expr in diffCache) { + return diffCache[expr]; + } + let diff: TmplDifference | null; + try { + diff = tournamentEvaluator.tmplDiff(expr); + } catch (e) { + // We don't include the expression for privacy reasons + differenceHandler('ERROR'); + diff = null; + } + + if (diff?.same === false) { + differenceChecker(diff); + } + + diffCache[expr] = diff; + return diff; }; export const getEvaluator = () => { return evaluator; }; -export const evaluateExpression: Evaluator = (expr, data) => evaluator(expr, data); +export const evaluateExpression: Evaluator = (expr, data) => { + if (!diffExpressions) { + return evaluator(expr, data); + } + const diff = checkEvaluatorDifferences(expr); + + // We already know that they're different so don't bother + // evaluating with both evaluators + if (!diff?.same) { + return evaluator(expr, data); + } + + let tmplValue: tmpl.ReturnValue; + let tournValue: ReturnValue; + let wasTmplError = false; + let tmplError: unknown; + let wasTournError = false; + let tournError: unknown; + + try { + tmplValue = tmpl.tmpl(expr, data); + } catch (error) { + tmplError = error; + wasTmplError = true; + } + + try { + tournValue = tournamentEvaluator.execute(expr, data); + } catch (error) { + tournError = error; + wasTournError = true; + } + + if ( + wasTmplError !== wasTournError || + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + JSON.stringify(tmplValue!) !== JSON.stringify(tournValue!) + ) { + if (diff.expression) { + differenceHandler(diff.expression.value); + } + } + + if (currentEvaluatorType === 'tmpl') { + if (wasTmplError) { + throw tmplError; + } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return tmplValue!; + } + + if (wasTournError) { + throw tournError; + } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return tournValue!; +}; From 48545d488c276ab88b700604a9682d298178ebef Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 11 Aug 2023 15:14:08 +0200 Subject: [PATCH 003/372] feat(core): Add support for multiple connection types --- packages/editor-ui/src/mixins/nodeBase.ts | 103 ++++++++++----- packages/editor-ui/src/utils/nodeViewUtils.ts | 122 ++++++++++++------ packages/editor-ui/src/views/NodeView.vue | 39 ++++-- packages/workflow/src/Interfaces.ts | 13 +- 4 files changed, 193 insertions(+), 84 deletions(-) diff --git a/packages/editor-ui/src/mixins/nodeBase.ts b/packages/editor-ui/src/mixins/nodeBase.ts index 2f43f6c46326f..63c9bb73ab7c4 100644 --- a/packages/editor-ui/src/mixins/nodeBase.ts +++ b/packages/editor-ui/src/mixins/nodeBase.ts @@ -6,7 +6,7 @@ import type { INodeUi } from '@/Interface'; import { deviceSupportHelpers } from '@/mixins/deviceSupportHelpers'; import { NO_OP_NODE_TYPE } from '@/constants'; -import type { INodeTypeDescription } from 'n8n-workflow'; +import type { ConnectionTypes, INodeTypeDescription } from 'n8n-workflow'; import { useUIStore } from '@/stores/ui.store'; import { useWorkflowsStore } from '@/stores/workflows.store'; import { useNodeTypesStore } from '@/stores/nodeTypes.store'; @@ -72,7 +72,6 @@ export const nodeBase = defineComponent({ }, __addInputEndpoints(node: INodeUi, nodeTypeData: INodeTypeDescription) { // Add Inputs - let index; const indexData: { [key: string]: number; } = {}; @@ -84,14 +83,16 @@ export const nodeBase = defineComponent({ } else { indexData[inputName] = 0; } - index = indexData[inputName]; + const typeIndex = indexData[inputName]; + + const inputsOfSameType = nodeTypeData.inputs.filter((input) => input === inputName); // Get the position of the anchor depending on how many it has const anchorPosition = - NodeViewUtils.ANCHOR_POSITIONS.input[nodeTypeData.inputs.length][index]; + NodeViewUtils.ANCHOR_POSITIONS[inputName].input[inputsOfSameType.length][typeIndex]; const newEndpointData: EndpointOptions = { - uuid: NodeViewUtils.getInputEndpointUUID(this.nodeId, index), + uuid: NodeViewUtils.getInputEndpointUUID(this.nodeId, i), anchor: anchorPosition, maxConnections: -1, endpoint: 'Rectangle', @@ -102,22 +103,25 @@ export const nodeBase = defineComponent({ parameters: { nodeId: this.nodeId, type: inputName, - index, + index: i, }, enabled: !this.isReadOnly, // enabled in default case to allow dragging cssClass: 'rect-input-endpoint', dragAllowedWhenFull: true, hoverClass: 'dropHover', + ...this.__getConnectionStyle('input', inputName, nodeTypeData), }; const endpoint = this.instance?.addEndpoint( this.$refs[this.data.name] as Element, newEndpointData, ); - this.__addEndpointTestingData(endpoint, 'input', index); + this.__addEndpointTestingData(endpoint, 'input', i); if (nodeTypeData.inputNames) { // Apply input names if they got set - endpoint.addOverlay(NodeViewUtils.getInputNameOverlay(nodeTypeData.inputNames[index])); + endpoint.addOverlay( + NodeViewUtils.getInputNameOverlay(nodeTypeData.inputNames[i], inputName), + ); } if (!Array.isArray(endpoint)) { endpoint.__meta = { @@ -144,61 +148,63 @@ export const nodeBase = defineComponent({ } }, __addOutputEndpoints(node: INodeUi, nodeTypeData: INodeTypeDescription) { - let index; const indexData: { [key: string]: number; } = {}; - nodeTypeData.outputs.forEach((inputName: string, i: number) => { + // TODO: Does not save connections perfectly. If there are two types + // they do not both start with index 0 in the workflow JSON + // TODO: There are still a lot of references of "main" in NodesView and + // other locations. So assume there will be more problems + + nodeTypeData.outputs.forEach((outputName: string, i: number) => { // Increment the index for outputs with current name - if (indexData.hasOwnProperty(inputName)) { - indexData[inputName]++; + if (indexData.hasOwnProperty(outputName)) { + indexData[outputName]++; } else { - indexData[inputName] = 0; + indexData[outputName] = 0; } - index = indexData[inputName]; + const typeIndex = indexData[outputName]; + + const outputsOfSameType = nodeTypeData.outputs.filter((output) => output === outputName); // Get the position of the anchor depending on how many it has const anchorPosition = - NodeViewUtils.ANCHOR_POSITIONS.output[nodeTypeData.outputs.length][index]; + NodeViewUtils.ANCHOR_POSITIONS[outputName].output[outputsOfSameType.length][typeIndex]; const newEndpointData: EndpointOptions = { - uuid: NodeViewUtils.getOutputEndpointUUID(this.nodeId, index), + uuid: NodeViewUtils.getOutputEndpointUUID(this.nodeId, i), anchor: anchorPosition, maxConnections: -1, endpoint: { type: 'Dot', options: { - radius: nodeTypeData && nodeTypeData.outputs.length > 2 ? 7 : 9, + radius: nodeTypeData && outputsOfSameType.length > 2 ? 7 : 9, }, }, - paintStyle: NodeViewUtils.getOutputEndpointStyle( - nodeTypeData, - '--color-foreground-xdark', - ), hoverPaintStyle: NodeViewUtils.getOutputEndpointStyle(nodeTypeData, '--color-primary'), source: true, target: false, enabled: !this.isReadOnly, parameters: { nodeId: this.nodeId, - type: inputName, - index, + type: outputName, + index: i, }, hoverClass: 'dot-output-endpoint-hover', connectionsDirected: true, - cssClass: 'dot-output-endpoint', dragAllowedWhenFull: false, + ...this.__getConnectionStyle('output', outputName, nodeTypeData), }; const endpoint = this.instance.addEndpoint( this.$refs[this.data.name] as Element, newEndpointData, ); - this.__addEndpointTestingData(endpoint, 'output', index); + this.__addEndpointTestingData(endpoint, 'output', i); if (nodeTypeData.outputNames) { // Apply output names if they got set - const overlaySpec = NodeViewUtils.getOutputNameOverlay(nodeTypeData.outputNames[index]); + const overlaySpec = NodeViewUtils.getOutputNameOverlay(nodeTypeData.outputNames[i]); endpoint.addOverlay(overlaySpec); } @@ -211,9 +217,9 @@ export const nodeBase = defineComponent({ }; } - if (!this.isReadOnly) { + if (!this.isReadOnly && outputName === 'main') { const plusEndpointData: EndpointOptions = { - uuid: NodeViewUtils.getOutputEndpointUUID(this.nodeId, index), + uuid: NodeViewUtils.getOutputEndpointUUID(this.nodeId, i), anchor: anchorPosition, maxConnections: -1, endpoint: { @@ -237,8 +243,8 @@ export const nodeBase = defineComponent({ }, parameters: { nodeId: this.nodeId, - type: inputName, - index, + type: outputName, + index: i, }, cssClass: 'plus-draggable-endpoint', dragAllowedWhenFull: false, @@ -247,7 +253,7 @@ export const nodeBase = defineComponent({ this.$refs[this.data.name] as Element, plusEndpointData, ); - this.__addEndpointTestingData(plusEndpoint, 'plus', index); + this.__addEndpointTestingData(plusEndpoint, 'plus', i); if (!Array.isArray(plusEndpoint)) { plusEndpoint.__meta = { @@ -267,6 +273,41 @@ export const nodeBase = defineComponent({ this.__addInputEndpoints(node, nodeTypeData); this.__addOutputEndpoints(node, nodeTypeData); }, + __getConnectionStyle( + type: 'input' | 'output', + connectionType: ConnectionTypes, + nodeTypeData: INodeTypeDescription, + ): EndpointOptions { + const connectionTypes: { + [key: string]: EndpointOptions; + } = { + main: { + paintStyle: (type === 'input' + ? NodeViewUtils.getInputEndpointStyle + : NodeViewUtils.getOutputEndpointStyle)(nodeTypeData, '--color-foreground-xdark'), + cssClass: `dot-${type}-endpoint`, + }, + test: { + paintStyle: (type === 'input' + ? NodeViewUtils.getInputEndpointStyle + : NodeViewUtils.getOutputEndpointStyle)(nodeTypeData, '--color-danger'), + cssClass: `dot-${type}-endpoint`, + }, + }; + + if (!connectionTypes.hasOwnProperty(connectionType)) { + return {}; + } + + if (connectionType === 'test') { + const width = connectionTypes[connectionType].paintStyle!.width!; + connectionTypes[connectionType].paintStyle!.width = + connectionTypes[connectionType].paintStyle!.height; + connectionTypes[connectionType].paintStyle!.height = width; + } + + return connectionTypes[connectionType]; + }, touchEnd(e: MouseEvent) { if (this.isTouchDevice) { if (this.uiStore.isActionActive('dragActive')) { diff --git a/packages/editor-ui/src/utils/nodeViewUtils.ts b/packages/editor-ui/src/utils/nodeViewUtils.ts index a0936cb453c3d..a0c8dcad8a26e 100644 --- a/packages/editor-ui/src/utils/nodeViewUtils.ts +++ b/packages/editor-ui/src/utils/nodeViewUtils.ts @@ -7,6 +7,7 @@ import type { Endpoint, Connection } from '@jsplumb/core'; import { N8nConnector } from '@/plugins/connectors/N8nCustomConnector'; import { closestNumberDivisibleBy } from '@/utils'; import type { + ConnectionTypes, IConnection, INode, ITaskData, @@ -130,45 +131,88 @@ export const CONNECTOR_ARROW_OVERLAYS: OverlaySpec[] = [ ]; export const ANCHOR_POSITIONS: { - [key: string]: { - [key: number]: ArrayAnchorSpec[]; + [key: ConnectionTypes]: { + // type: input | output + [key: string]: { + [key: number]: ArrayAnchorSpec[]; + }; }; } = { - input: { - 1: [[0.01, 0.5, -1, 0]], - 2: [ - [0.01, 0.3, -1, 0], - [0.01, 0.7, -1, 0], - ], - 3: [ - [0.01, 0.25, -1, 0], - [0.01, 0.5, -1, 0], - [0.01, 0.75, -1, 0], - ], - 4: [ - [0.01, 0.2, -1, 0], - [0.01, 0.4, -1, 0], - [0.01, 0.6, -1, 0], - [0.01, 0.8, -1, 0], - ], + main: { + input: { + 1: [[0.01, 0.5, -1, 0]], + 2: [ + [0.01, 0.3, -1, 0], + [0.01, 0.7, -1, 0], + ], + 3: [ + [0.01, 0.25, -1, 0], + [0.01, 0.5, -1, 0], + [0.01, 0.75, -1, 0], + ], + 4: [ + [0.01, 0.2, -1, 0], + [0.01, 0.4, -1, 0], + [0.01, 0.6, -1, 0], + [0.01, 0.8, -1, 0], + ], + }, + output: { + 1: [[0.99, 0.5, 1, 0]], + 2: [ + [0.99, 0.3, 1, 0], + [0.99, 0.7, 1, 0], + ], + 3: [ + [0.99, 0.25, 1, 0], + [0.99, 0.5, 1, 0], + [0.99, 0.75, 1, 0], + ], + 4: [ + [0.99, 0.2, 1, 0], + [0.99, 0.4, 1, 0], + [0.99, 0.6, 1, 0], + [0.99, 0.8, 1, 0], + ], + }, }, - output: { - 1: [[0.99, 0.5, 1, 0]], - 2: [ - [0.99, 0.3, 1, 0], - [0.99, 0.7, 1, 0], - ], - 3: [ - [0.99, 0.25, 1, 0], - [0.99, 0.5, 1, 0], - [0.99, 0.75, 1, 0], - ], - 4: [ - [0.99, 0.2, 1, 0], - [0.99, 0.4, 1, 0], - [0.99, 0.6, 1, 0], - [0.99, 0.8, 1, 0], - ], + test: { + input: { + 1: [[0.5, 0.01, 0, -1]], + 2: [ + [0.3, 0.01, 0, -1], + [0.7, 0, 0, -1], + ], + 3: [ + [0.25, 0.01, 0, -1], + [0.5, 0.01, 0, -1], + [0.75, 0.01, 0, -1], + ], + 4: [ + [0.2, 0.01, 0, -1], + [0.4, 0.01, 0, -1], + [0.6, 0.01, 0, -1], + [0.8, 0.01, 0, -1], + ], + }, + output: { + 1: [[0.5, 0.99, 0, 1]], + 2: [ + [0.3, 0.99, 0, 1], + [0.7, 0.99, 0, 1], + ], + 3: [ + [0.25, 0.99, 0, 1], + [0.5, 0.99, 0, 1], + [0.75, 0.99, 0, 1], + ], + 4: [ + [0.2, 0.99, 0, 1], + [0.4, 0.99, 0, 1], + [0.6, 0.99, 0, 1], + [0.8, 0.99, 0, 1], + ], + }, }, }; @@ -183,7 +227,7 @@ export const getInputEndpointStyle = ( lineWidth: 0, }); -export const getInputNameOverlay = (labelText: string): OverlaySpec => ({ +export const getInputNameOverlay = (labelText: string, type: ConnectionTypes): OverlaySpec => ({ type: 'Custom', options: { id: OVERLAY_INPUT_NAME_LABEL, @@ -191,7 +235,9 @@ export const getInputNameOverlay = (labelText: string): OverlaySpec => ({ create: (component: Endpoint) => { const label = document.createElement('div'); label.innerHTML = labelText; - label.classList.add('node-input-endpoint-label'); + label.classList.add( + type === 'main' ? 'node-input-endpoint-label' : 'node-output-endpoint-label', + ); return label; }, }, diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index 497faabc42e6f..d8717b05d4f6e 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -230,6 +230,7 @@ import Sticky from '@/components/Sticky.vue'; import CanvasAddButton from './CanvasAddButton.vue'; import { v4 as uuid } from 'uuid'; import type { + ConnectionTypes, IConnection, IConnections, IDataObject, @@ -2006,10 +2007,11 @@ export default defineComponent({ sourceNodeOutputIndex: number, targetNodeName: string, targetNodeOuputIndex: number, + type: ConnectionTypes, ): IConnection | undefined { const nodeConnections = ( this.workflowsStore.outgoingConnectionsByNodeName(sourceNodeName) as INodeConnections - ).main; + )[type]; if (nodeConnections) { const connections: IConnection[] | null = nodeConnections[sourceNodeOutputIndex]; @@ -2028,6 +2030,7 @@ export default defineComponent({ sourceNodeOutputIndex: number, targetNodeName: string, targetNodeOuputIndex: number, + type: ConnectionTypes, ) { this.uiStore.stateIsDirty = true; @@ -2037,6 +2040,7 @@ export default defineComponent({ sourceNodeOutputIndex, targetNodeName, targetNodeOuputIndex, + type, ) ) { return; @@ -2045,12 +2049,12 @@ export default defineComponent({ const connectionData = [ { node: sourceNodeName, - type: 'main', + type, index: sourceNodeOutputIndex, }, { node: targetNodeName, - type: 'main', + type, index: targetNodeOuputIndex, }, ] as [IConnection, IConnection]; @@ -2096,11 +2100,11 @@ export default defineComponent({ const targetNodeName = lastSelectedConnection.__meta.targetNodeName; const targetOutputIndex = lastSelectedConnection.__meta.targetOutputIndex; - this.connectTwoNodes(newNodeData.name, 0, targetNodeName, targetOutputIndex); + this.connectTwoNodes(newNodeData.name, 0, targetNodeName, targetOutputIndex, 'main'); } // Connect active node to the newly created one - this.connectTwoNodes(lastSelectedNode.name, outputIndex, newNodeData.name, 0); + this.connectTwoNodes(lastSelectedNode.name, outputIndex, newNodeData.name, 0, 'main'); } this.historyStore.stopRecordingUndo(); }, @@ -2144,7 +2148,13 @@ export default defineComponent({ const sourceNodeName = sourceNode.name; const outputIndex = connection.parameters.index; - this.connectTwoNodes(sourceNodeName, outputIndex, this.pullConnActiveNodeName, 0); + this.connectTwoNodes( + sourceNodeName, + outputIndex, + this.pullConnActiveNodeName, + 0, + 'main', + ); this.pullConnActiveNodeName = null; this.dropPrevented = true; } @@ -2165,12 +2175,22 @@ export default defineComponent({ const sourceInfo = info.connection.endpoints[0].parameters; const targetInfo = info.dropEndpoint.parameters; + if (sourceInfo.type !== targetInfo.type) { + return false; + } + const sourceNodeName = this.workflowsStore.getNodeById(sourceInfo.nodeId)?.name || ''; const targetNodeName = this.workflowsStore.getNodeById(targetInfo.nodeId)?.name || ''; // check for duplicates if ( - this.getConnection(sourceNodeName, sourceInfo.index, targetNodeName, targetInfo.index) + this.getConnection( + sourceNodeName, + sourceInfo.index, + targetNodeName, + targetInfo.index, + sourceInfo.type, + ) ) { this.dropPrevented = true; this.pullConnActiveNodeName = null; @@ -2385,7 +2405,7 @@ export default defineComponent({ if (connectionInfo) { this.historyStore.pushCommandToUndo(new RemoveConnectionCommand(connectionInfo)); } - this.connectTwoNodes(sourceNodeName, outputIndex, this.pullConnActiveNodeName, 0); + this.connectTwoNodes(sourceNodeName, outputIndex, this.pullConnActiveNodeName, 0, 'main'); this.pullConnActiveNodeName = null; await this.$nextTick(); this.historyStore.stopRecordingUndo(); @@ -3058,6 +3078,7 @@ export default defineComponent({ sourceNodeOutputIndex, targetNodeName, targetNodeOuputIndex, + 'main', ); if (waitForNewConnection) { @@ -3796,7 +3817,7 @@ export default defineComponent({ previouslyAddedNode.position[1], ]; await this.$nextTick(); - this.connectTwoNodes(previouslyAddedNode.name, 0, lastAddedNode.name, 0); + this.connectTwoNodes(previouslyAddedNode.name, 0, lastAddedNode.name, 0, 'main'); actionWatcher(); }); diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index c5cec473a257d..5be4c284e1dbf 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -1204,10 +1204,9 @@ export interface INodePropertyValueExtractorRegex extends INodePropertyValueExtr } export interface INodePropertyValueExtractorFunction { - ( - this: IExecuteSingleFunctions, - value: string | NodeParameterValue, - ): Promise | (string | NodeParameterValue); + (this: IExecuteSingleFunctions, value: string | NodeParameterValue): + | Promise + | (string | NodeParameterValue); } export type INodePropertyValueExtractor = INodePropertyValueExtractorRegex; @@ -1463,15 +1462,17 @@ export interface IPostReceiveSort extends IPostReceiveBase { }; } +export type ConnectionTypes = 'main' | 'test'; + export interface INodeTypeDescription extends INodeTypeBaseDescription { version: number | number[]; defaults: INodeParameters; eventTriggerDescription?: string; activationMessage?: string; - inputs: string[]; + inputs: ConnectionTypes[]; requiredInputs?: string | number[] | number; // Ony available with executionOrder => "v1" inputNames?: string[]; - outputs: string[]; + outputs: ConnectionTypes[]; outputNames?: string[]; properties: INodeProperties[]; credentials?: INodeCredentialDescription[]; From 8fc1a94463fea80b61d380b95cc280b4b4b441c9 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 11 Aug 2023 15:55:41 +0200 Subject: [PATCH 004/372] :zap: Make it possible to read data from connected node --- packages/core/src/NodeExecuteFunctions.ts | 43 ++++++++++++++ .../nodes-base/nodes/AiTest/AiTest.node.ts | 39 +++++++++++++ .../nodes/AiTest/AiTestConfig.node.ts | 56 +++++++++++++++++++ packages/nodes-base/package.json | 2 + packages/workflow/src/Interfaces.ts | 5 ++ 5 files changed, 145 insertions(+) create mode 100644 packages/nodes-base/nodes/AiTest/AiTest.node.ts create mode 100644 packages/nodes-base/nodes/AiTest/AiTestConfig.node.ts diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index b68c5a06d6154..29a8ce82d460b 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -12,6 +12,7 @@ /* eslint-disable @typescript-eslint/no-shadow */ import type { + ConnectionTypes, GenericValue, IAdditionalCredentialOptions, IAllExecuteFunctions, @@ -2572,6 +2573,48 @@ export function getExecuteFunctions( getContext(type: string): IContextObject { return NodeHelpers.getContext(runExecutionData, type, node); }, + getInputConnectionData( + itemIndex: number, + inputIndex?: number, + inputName?: ConnectionTypes, + ): IDataObject { + const parentNodes = workflow.getParentNodes(node.name, inputName, 1); + + if (parentNodes.length === 0) { + throw new Error('Could not get input connection data'); + } + + const dataProxy = new WorkflowDataProxy( + workflow, + runExecutionData, + runIndex, + itemIndex, + node.name, + connectionInputData, + {}, + mode, + additionalData.timezone, + getAdditionalKeys(additionalData, mode, runExecutionData), + executeData, + ); + const proxy = dataProxy.getDataProxy(); + + const data = proxy.$node[parentNodes[0]].parameter; + + // Resolve parameters on node within the context of the current node + return workflow.expression.getParameterValue( + data, + runExecutionData, + runIndex, + itemIndex, + node.name, + connectionInputData, + mode, + additionalData.timezone, + getAdditionalKeys(additionalData, mode, runExecutionData), + executeData, + ) as IDataObject; + }, getInputData: (inputIndex = 0, inputName = 'main') => { if (!inputData.hasOwnProperty(inputName)) { // Return empty array because else it would throw error when nothing is connected to input diff --git a/packages/nodes-base/nodes/AiTest/AiTest.node.ts b/packages/nodes-base/nodes/AiTest/AiTest.node.ts new file mode 100644 index 0000000000000..e5cb1534b130d --- /dev/null +++ b/packages/nodes-base/nodes/AiTest/AiTest.node.ts @@ -0,0 +1,39 @@ +import type { + IExecuteFunctions, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +export class AiTest implements INodeType { + description: INodeTypeDescription = { + displayName: 'AiTest', + name: 'aiTest', + icon: 'fa:map-signs', + group: ['transform'], + version: 1, + description: 'AI Test node', + defaults: { + name: 'AiTest', + color: '#408000', + }, + // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node + inputs: ['main', 'test'], + inputNames: ['', 'test'], + outputs: ['main'], + properties: [], + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + + const returnData: INodeExecutionData[] = []; + + for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { + const data = this.getInputConnectionData(itemIndex, 0, 'test'); + returnData.push({ json: data }); + } + + return this.prepareOutputData(returnData); + } +} diff --git a/packages/nodes-base/nodes/AiTest/AiTestConfig.node.ts b/packages/nodes-base/nodes/AiTest/AiTestConfig.node.ts new file mode 100644 index 0000000000000..12a24deb10859 --- /dev/null +++ b/packages/nodes-base/nodes/AiTest/AiTestConfig.node.ts @@ -0,0 +1,56 @@ +import type { + IExecuteFunctions, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +export class AiTestConfig implements INodeType { + description: INodeTypeDescription = { + displayName: 'AiTestConfig', + name: 'aiTestConfig', + icon: 'fa:map-signs', + group: ['transform'], + version: 1, + description: 'AI Test node', + defaults: { + name: 'AiTest Config', + color: '#400080', + }, + // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node + inputs: [], + // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong + outputs: ['test'], + outputNames: ['Test'], + properties: [ + { + displayName: 'Model', + name: 'model', + type: 'options', + options: [ + { + name: 'Model 1', + value: 'model1', + }, + { + name: 'Model 2', + value: 'model2', + }, + ], + default: 'model1', + }, + { + displayName: 'Text', + name: 'text', + type: 'string', + default: '', + }, + ], + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + + return this.prepareOutputData(items); + } +} diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index dd6d99f13cc12..d23047a0552f2 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -388,6 +388,8 @@ "dist/nodes/AgileCrm/AgileCrm.node.js", "dist/nodes/Airtable/Airtable.node.js", "dist/nodes/Airtable/AirtableTrigger.node.js", + "dist/nodes/AiTest/AiTest.node.js", + "dist/nodes/AiTest/AiTestConfig.node.js", "dist/nodes/Amqp/Amqp.node.js", "dist/nodes/Amqp/AmqpTrigger.node.js", "dist/nodes/ApiTemplateIo/ApiTemplateIo.node.js", diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 5be4c284e1dbf..5792953bb993d 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -761,6 +761,11 @@ export type IExecuteFunctions = ExecuteFunctions.GetNodeParameterFn & workflowInfo: IExecuteWorkflowInfo, inputData?: INodeExecutionData[], ): Promise; + getInputConnectionData( + itemIndex: number, + inputIndex?: number, + inputName?: ConnectionTypes, + ): IDataObject; getInputData(inputIndex?: number, inputName?: string): INodeExecutionData[]; prepareOutputData( outputData: INodeExecutionData[], From 0032e9e0f82285cc6dbf35ed6f08ed24a7707308 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 11 Aug 2023 16:15:20 +0200 Subject: [PATCH 005/372] :zap: Support multiple connected nodes --- packages/core/src/NodeExecuteFunctions.ts | 55 +++++++++++------------ 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index 29a8ce82d460b..09b17e17844cc 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -2575,6 +2575,7 @@ export function getExecuteFunctions( }, getInputConnectionData( itemIndex: number, + // TODO: Not implemented yet, and maybe also not needed inputIndex?: number, inputName?: ConnectionTypes, ): IDataObject { @@ -2584,36 +2585,30 @@ export function getExecuteFunctions( throw new Error('Could not get input connection data'); } - const dataProxy = new WorkflowDataProxy( - workflow, - runExecutionData, - runIndex, - itemIndex, - node.name, - connectionInputData, - {}, - mode, - additionalData.timezone, - getAdditionalKeys(additionalData, mode, runExecutionData), - executeData, - ); - const proxy = dataProxy.getDataProxy(); - - const data = proxy.$node[parentNodes[0]].parameter; - - // Resolve parameters on node within the context of the current node - return workflow.expression.getParameterValue( - data, - runExecutionData, - runIndex, - itemIndex, - node.name, - connectionInputData, - mode, - additionalData.timezone, - getAdditionalKeys(additionalData, mode, runExecutionData), - executeData, - ) as IDataObject; + return parentNodes.map((nodeName) => { + const connectedNode = workflow.getNode(nodeName); + + // Resolve parameters on node within the context of the current node + const parameters = workflow.expression.getParameterValue( + connectedNode!.parameters, + runExecutionData, + runIndex, + itemIndex, + node.name, + connectionInputData, + mode, + additionalData.timezone, + getAdditionalKeys(additionalData, mode, runExecutionData), + executeData, + ) as IDataObject; + + return { + node: { + ...node, + parameters, + }, + }; + }); }, getInputData: (inputIndex = 0, inputName = 'main') => { if (!inputData.hasOwnProperty(inputName)) { From 8e7c604d0f0d96b39375d158be54ccc9ce88e5ac Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 11 Aug 2023 16:24:16 +0200 Subject: [PATCH 006/372] :zap: Fix lint issues --- packages/core/src/NodeExecuteFunctions.ts | 8 +++----- packages/workflow/src/Interfaces.ts | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index 09b17e17844cc..e87d51e43199a 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -2578,7 +2578,7 @@ export function getExecuteFunctions( // TODO: Not implemented yet, and maybe also not needed inputIndex?: number, inputName?: ConnectionTypes, - ): IDataObject { + ): IDataObject[] { const parentNodes = workflow.getParentNodes(node.name, inputName, 1); if (parentNodes.length === 0) { @@ -2603,10 +2603,8 @@ export function getExecuteFunctions( ) as IDataObject; return { - node: { - ...node, - parameters, - }, + ...node, + parameters, }; }); }, diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 5792953bb993d..81f60f5cac501 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -765,7 +765,7 @@ export type IExecuteFunctions = ExecuteFunctions.GetNodeParameterFn & itemIndex: number, inputIndex?: number, inputName?: ConnectionTypes, - ): IDataObject; + ): IDataObject[]; getInputData(inputIndex?: number, inputName?: string): INodeExecutionData[]; prepareOutputData( outputData: INodeExecutionData[], From f545e411123dc7a2ac1338a5f61bb178334aa055 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 11 Aug 2023 16:25:22 +0200 Subject: [PATCH 007/372] :zap: Fix lint issues in the node --- packages/nodes-base/nodes/AiTest/AiTest.node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/AiTest/AiTest.node.ts b/packages/nodes-base/nodes/AiTest/AiTest.node.ts index e5cb1534b130d..3d734e908c2ed 100644 --- a/packages/nodes-base/nodes/AiTest/AiTest.node.ts +++ b/packages/nodes-base/nodes/AiTest/AiTest.node.ts @@ -31,7 +31,7 @@ export class AiTest implements INodeType { for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { const data = this.getInputConnectionData(itemIndex, 0, 'test'); - returnData.push({ json: data }); + returnData.push({ json: { data } }); } return this.prepareOutputData(returnData); From aad1b0aabf7c848f0cb383e8279e697d4d1c38a0 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 11 Aug 2023 18:18:44 +0200 Subject: [PATCH 008/372] :zap: Fix bug that it returns the wrong node data --- packages/core/src/NodeExecuteFunctions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index e87d51e43199a..b91971e0543b5 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -2603,7 +2603,7 @@ export function getExecuteFunctions( ) as IDataObject; return { - ...node, + ...connectedNode, parameters, }; }); From 7b1e6a407bb135cc45c5ab8d07ab9842200cdac7 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 11 Aug 2023 19:10:08 +0200 Subject: [PATCH 009/372] :zap: Give also access to credentials of connected nodes --- packages/core/src/NodeExecuteFunctions.ts | 33 ++++++++++++++++++++--- packages/workflow/src/Interfaces.ts | 2 +- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index b91971e0543b5..a45b998b1b069 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -2573,19 +2573,19 @@ export function getExecuteFunctions( getContext(type: string): IContextObject { return NodeHelpers.getContext(runExecutionData, type, node); }, - getInputConnectionData( + async getInputConnectionData( itemIndex: number, // TODO: Not implemented yet, and maybe also not needed inputIndex?: number, inputName?: ConnectionTypes, - ): IDataObject[] { + ): Promise { const parentNodes = workflow.getParentNodes(node.name, inputName, 1); if (parentNodes.length === 0) { throw new Error('Could not get input connection data'); } - return parentNodes.map((nodeName) => { + const constParentNodes = parentNodes.map(async (nodeName) => { const connectedNode = workflow.getNode(nodeName); // Resolve parameters on node within the context of the current node @@ -2602,11 +2602,38 @@ export function getExecuteFunctions( executeData, ) as IDataObject; + const credentials: { + [key: string]: ICredentialDataDecryptedObject; + } = {}; + + if (!connectedNode?.credentials) { + return { + ...connectedNode, + parameters, + }; + } + for (const key of Object.keys(connectedNode?.credentials)) { + credentials[key] = await getCredentials( + workflow, + connectedNode, + key, + additionalData, + mode, + runExecutionData, + runIndex, + connectionInputData, + itemIndex, + ); + } + return { ...connectedNode, + credentials, parameters, }; }); + + return Promise.all(constParentNodes); }, getInputData: (inputIndex = 0, inputName = 'main') => { if (!inputData.hasOwnProperty(inputName)) { diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 81f60f5cac501..a24fb83b0d388 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -765,7 +765,7 @@ export type IExecuteFunctions = ExecuteFunctions.GetNodeParameterFn & itemIndex: number, inputIndex?: number, inputName?: ConnectionTypes, - ): IDataObject[]; + ): Promise; getInputData(inputIndex?: number, inputName?: string): INodeExecutionData[]; prepareOutputData( outputData: INodeExecutionData[], From d3076bf8426c8714821d959953e70b466e77ef33 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 11 Aug 2023 22:29:03 +0200 Subject: [PATCH 010/372] :zap: Add first basic LangChain nodes --- packages/core/src/NodeExecuteFunctions.ts | 11 +- .../nodes/AiTest/AiTestConfig.node.ts | 6 + .../nodes/LangChain/LangChain.node.ts | 115 +++++ .../LangChain/LangChainToolCalculator.node.ts | 39 ++ .../LangChain/LangChainToolSerpApi.node.ts | 46 ++ .../LangChain/LangChainToolWikipedia.node.ts | 40 ++ .../nodes-base/nodes/LangChain/google.svg | 1 + .../nodes-base/nodes/LangChain/wikipedia.svg | 41 ++ packages/nodes-base/package.json | 6 + packages/workflow/src/Interfaces.ts | 2 +- pnpm-lock.yaml | 453 +++++++++++++++++- 11 files changed, 739 insertions(+), 21 deletions(-) create mode 100644 packages/nodes-base/nodes/LangChain/LangChain.node.ts create mode 100644 packages/nodes-base/nodes/LangChain/LangChainToolCalculator.node.ts create mode 100644 packages/nodes-base/nodes/LangChain/LangChainToolSerpApi.node.ts create mode 100644 packages/nodes-base/nodes/LangChain/LangChainToolWikipedia.node.ts create mode 100644 packages/nodes-base/nodes/LangChain/google.svg create mode 100644 packages/nodes-base/nodes/LangChain/wikipedia.svg diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index a45b998b1b069..56c864caba867 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -62,6 +62,7 @@ import type { BinaryMetadata, FileSystemHelperFunctions, INodeType, + INodeParameters, } from 'n8n-workflow'; import { createDeferredPromise, @@ -2578,7 +2579,7 @@ export function getExecuteFunctions( // TODO: Not implemented yet, and maybe also not needed inputIndex?: number, inputName?: ConnectionTypes, - ): Promise { + ): Promise { const parentNodes = workflow.getParentNodes(node.name, inputName, 1); if (parentNodes.length === 0) { @@ -2586,11 +2587,11 @@ export function getExecuteFunctions( } const constParentNodes = parentNodes.map(async (nodeName) => { - const connectedNode = workflow.getNode(nodeName); + const connectedNode = workflow.getNode(nodeName) as INode; // Resolve parameters on node within the context of the current node const parameters = workflow.expression.getParameterValue( - connectedNode!.parameters, + connectedNode.parameters, runExecutionData, runIndex, itemIndex, @@ -2600,7 +2601,7 @@ export function getExecuteFunctions( additionalData.timezone, getAdditionalKeys(additionalData, mode, runExecutionData), executeData, - ) as IDataObject; + ) as INodeParameters; const credentials: { [key: string]: ICredentialDataDecryptedObject; @@ -2630,7 +2631,7 @@ export function getExecuteFunctions( ...connectedNode, credentials, parameters, - }; + } as unknown as INode; }); return Promise.all(constParentNodes); diff --git a/packages/nodes-base/nodes/AiTest/AiTestConfig.node.ts b/packages/nodes-base/nodes/AiTest/AiTestConfig.node.ts index 12a24deb10859..ebcf8f192f246 100644 --- a/packages/nodes-base/nodes/AiTest/AiTestConfig.node.ts +++ b/packages/nodes-base/nodes/AiTest/AiTestConfig.node.ts @@ -45,6 +45,12 @@ export class AiTestConfig implements INodeType { type: 'string', default: '', }, + { + displayName: 'Enabled', + name: 'enabled', + type: 'boolean', + default: true, + }, ], }; diff --git a/packages/nodes-base/nodes/LangChain/LangChain.node.ts b/packages/nodes-base/nodes/LangChain/LangChain.node.ts new file mode 100644 index 0000000000000..768b6fdc0d6fd --- /dev/null +++ b/packages/nodes-base/nodes/LangChain/LangChain.node.ts @@ -0,0 +1,115 @@ +/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ +import type { + IExecuteFunctions, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; +import { NodeOperationError } from 'n8n-workflow'; + +import get from 'lodash/get'; + +import { initializeAgentExecutorWithOptions } from 'langchain/agents'; +import { ChatOpenAI } from 'langchain/chat_models/openai'; +import type { StructuredTool } from 'langchain/tools'; +import { SerpAPI, WikipediaQueryRun } from 'langchain/tools'; +import { Calculator } from 'langchain/tools/calculator'; + +export class LangChain implements INodeType { + description: INodeTypeDescription = { + displayName: 'LangChain', + name: 'langChain', + icon: 'fa:link', + group: ['transform'], + version: 1, + description: 'LangChain', + defaults: { + name: 'LangChain', + color: '#404040', + }, + // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node + inputs: ['main', 'test'], + inputNames: ['', 'Tools'], + outputs: ['main'], + credentials: [ + { + name: 'openAiApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Text', + name: 'text', + type: 'string', + default: '', + }, + ], + }; + + async execute(this: IExecuteFunctions): Promise { + // const llm = new OpenAI(); + // const result = await llm.predict( + // 'What would be a good company name for a company that makes colorful socks?', + // ); + // const credentials = await this.getCredentials('openAiApi'); + // const model = new OpenAI({ + // // openAIApiKey: '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', + // openAIApiKey: credentials.apiKey as string, + // }); + // const memory = new BufferMemory(); + // const chain = new ConversationChain({ llm: model, memory }); + // // const text = this.getNodeParameter('text', 0) as string; + // const asdf = await chain.call({ input: 'hello my name is Jan' }); + // console.log('asdf', asdf); + // const response = await chain.call({ input: 'What did I say my name was?' }); + // return this.prepareOutputData([ + // { json: { history: memory.chatHistory.getMessages(), response } }, + // ]); + // // TODO: Add support for memory and tools in first step + // console.log('result', result); + + const tools: StructuredTool[] = []; + + const toolNodes = await this.getInputConnectionData(0, 0, 'test'); + + toolNodes.forEach((toolNode) => { + if (!toolNode.parameters.enabled) { + return; + } + if (toolNode.type === 'n8n-nodes-base.langChainToolCalculator') { + tools.push(new Calculator()); + } else if (toolNode.type === 'n8n-nodes-base.langChainToolSerpApi') { + const apiKey = get(toolNode, 'credentials.serpApi.apiKey'); + if (!apiKey) { + throw new NodeOperationError(this.getNode(), 'SerpAPI API key missing'); + } + tools.push(new SerpAPI(apiKey as string)); + } else if (toolNode.type === 'n8n-nodes-base.langChainToolWikipedia') { + tools.push(new WikipediaQueryRun()); + } + }); + + const credentials = await this.getCredentials('openAiApi'); + + const chat = new ChatOpenAI({ + openAIApiKey: credentials.apiKey as string, + modelName: 'gpt-3.5-turbo', + temperature: 0, + }); + + const executor = await initializeAgentExecutorWithOptions(tools, chat, { + agentType: 'openai-functions', + // verbose: true, + }); + + const items = this.getInputData(); + const returnData: INodeExecutionData[] = []; + for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { + const text = this.getNodeParameter('text', itemIndex) as string; + const response = await executor.run(text); + returnData.push({ json: { response } }); + } + return this.prepareOutputData(returnData); + } +} diff --git a/packages/nodes-base/nodes/LangChain/LangChainToolCalculator.node.ts b/packages/nodes-base/nodes/LangChain/LangChainToolCalculator.node.ts new file mode 100644 index 0000000000000..9d6c3e90466af --- /dev/null +++ b/packages/nodes-base/nodes/LangChain/LangChainToolCalculator.node.ts @@ -0,0 +1,39 @@ +/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ +import type { + IExecuteFunctions, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +export class LangChainToolCalculator implements INodeType { + description: INodeTypeDescription = { + displayName: 'LangChain - Calculator', + name: 'langChainToolCalculator', + icon: 'fa:calculator', + group: ['transform'], + version: 1, + description: 'Calculator', + defaults: { + name: 'LangChain - Calculator', + color: '#400080', + }, + // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node + inputs: [], + // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong + outputs: ['test'], + outputNames: ['Tool'], + properties: [ + { + displayName: 'Enabled', + name: 'enabled', + type: 'boolean', + default: true, + }, + ], + }; + + async execute(this: IExecuteFunctions): Promise { + return []; + } +} diff --git a/packages/nodes-base/nodes/LangChain/LangChainToolSerpApi.node.ts b/packages/nodes-base/nodes/LangChain/LangChainToolSerpApi.node.ts new file mode 100644 index 0000000000000..af05ab2e1314b --- /dev/null +++ b/packages/nodes-base/nodes/LangChain/LangChainToolSerpApi.node.ts @@ -0,0 +1,46 @@ +/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ +import type { + IExecuteFunctions, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +export class LangChainToolSerpApi implements INodeType { + description: INodeTypeDescription = { + displayName: 'LangChain - SerpAPI', + name: 'langChainToolSerpApi', + icon: 'file:google.svg', + group: ['transform'], + version: 1, + description: 'Search in Google', + defaults: { + name: 'LangChain - SerpAPI', + // eslint-disable-next-line n8n-nodes-base/node-class-description-non-core-color-present + color: '#400080', + }, + // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node + inputs: [], + // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong + outputs: ['test'], + outputNames: ['Tool'], + credentials: [ + { + name: 'serpApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Enabled', + name: 'enabled', + type: 'boolean', + default: true, + }, + ], + }; + + async execute(this: IExecuteFunctions): Promise { + return []; + } +} diff --git a/packages/nodes-base/nodes/LangChain/LangChainToolWikipedia.node.ts b/packages/nodes-base/nodes/LangChain/LangChainToolWikipedia.node.ts new file mode 100644 index 0000000000000..c8c134a0cfb0b --- /dev/null +++ b/packages/nodes-base/nodes/LangChain/LangChainToolWikipedia.node.ts @@ -0,0 +1,40 @@ +/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ +import type { + IExecuteFunctions, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +export class LangChainToolWikipedia implements INodeType { + description: INodeTypeDescription = { + displayName: 'LangChain - Wikipedia', + name: 'langChainToolWikipedia', + icon: 'file:wikipedia.svg', + group: ['transform'], + version: 1, + description: 'Search in Wikipedia', + defaults: { + name: 'LangChain - Wikipedia', + // eslint-disable-next-line n8n-nodes-base/node-class-description-non-core-color-present + color: '#400080', + }, + // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node + inputs: [], + // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong + outputs: ['test'], + outputNames: ['Tool'], + properties: [ + { + displayName: 'Enabled', + name: 'enabled', + type: 'boolean', + default: true, + }, + ], + }; + + async execute(this: IExecuteFunctions): Promise { + return []; + } +} diff --git a/packages/nodes-base/nodes/LangChain/google.svg b/packages/nodes-base/nodes/LangChain/google.svg new file mode 100644 index 0000000000000..4cf163bfe2478 --- /dev/null +++ b/packages/nodes-base/nodes/LangChain/google.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/nodes-base/nodes/LangChain/wikipedia.svg b/packages/nodes-base/nodes/LangChain/wikipedia.svg new file mode 100644 index 0000000000000..bfa60b37e6bfa --- /dev/null +++ b/packages/nodes-base/nodes/LangChain/wikipedia.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index d23047a0552f2..d31f4421b4bc5 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -297,6 +297,7 @@ "dist/credentials/SentryIoApi.credentials.js", "dist/credentials/SentryIoOAuth2Api.credentials.js", "dist/credentials/SentryIoServerApi.credentials.js", + "dist/credentials/SerpApi.credentials.js", "dist/credentials/ServiceNowOAuth2Api.credentials.js", "dist/credentials/ServiceNowBasicApi.credentials.js", "dist/credentials/Sftp.credentials.js", @@ -568,6 +569,10 @@ "dist/nodes/Kitemaker/Kitemaker.node.js", "dist/nodes/KoBoToolbox/KoBoToolbox.node.js", "dist/nodes/KoBoToolbox/KoBoToolboxTrigger.node.js", + "dist/nodes/LangChain/LangChain.node.js", + "dist/nodes/LangChain/LangChainToolCalculator.node.js", + "dist/nodes/LangChain/LangChainToolSerpApi.node.js", + "dist/nodes/LangChain/LangChainToolWikipedia.node.js", "dist/nodes/Ldap/Ldap.node.js", "dist/nodes/Lemlist/Lemlist.node.js", "dist/nodes/Lemlist/LemlistTrigger.node.js", @@ -832,6 +837,7 @@ "js-nacl": "^1.4.0", "jsonwebtoken": "^9.0.0", "kafkajs": "^1.14.0", + "langchain": "^0.0.126", "ldapts": "^4.2.6", "lodash": "^4.17.21", "lossless-json": "^1.0.4", diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index a24fb83b0d388..041dcc6b9590c 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -765,7 +765,7 @@ export type IExecuteFunctions = ExecuteFunctions.GetNodeParameterFn & itemIndex: number, inputIndex?: number, inputName?: ConnectionTypes, - ): Promise; + ): Promise; getInputData(inputIndex?: number, inputName?: string): INodeExecutionData[]; prepareOutputData( outputData: INodeExecutionData[], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 35dbd66a4d741..c51691efe4739 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -135,7 +135,7 @@ importers: dependencies: axios: specifier: ^0.21.1 - version: 0.21.4 + version: 0.21.4(debug@4.3.2) packages/@n8n_io/eslint-config: devDependencies: @@ -216,7 +216,7 @@ importers: version: 7.28.1 axios: specifier: ^0.21.1 - version: 0.21.4 + version: 0.21.4(debug@4.3.2) basic-auth: specifier: ^2.0.1 version: 2.0.1 @@ -574,7 +574,7 @@ importers: version: link:../@n8n/client-oauth2 axios: specifier: ^0.21.1 - version: 0.21.4 + version: 0.21.4(debug@4.3.2) concat-stream: specifier: ^2.0.0 version: 2.0.0 @@ -834,7 +834,7 @@ importers: version: 10.2.0(vue@3.3.4) axios: specifier: ^0.21.1 - version: 0.21.4 + version: 0.21.4(debug@4.3.2) codemirror-lang-html-n8n: specifier: ^1.0.0 version: 1.0.0 @@ -1059,6 +1059,9 @@ importers: kafkajs: specifier: ^1.14.0 version: 1.16.0 + langchain: + specifier: ^0.0.126 + version: 0.0.126(cheerio@1.0.0-rc.6)(mongodb@4.10.0)(mysql2@2.3.3)(pg@8.8.0)(redis@3.1.2) ldapts: specifier: ^4.2.6 version: 4.2.6 @@ -1365,6 +1368,22 @@ packages: '@jridgewell/trace-mapping': 0.3.17 dev: true + /@anthropic-ai/sdk@0.5.10: + resolution: {integrity: sha512-P8xrIuTUO/6wDzcjQRUROXp4WSqtngbXaE4GpEu0PhEmnq/1Q8vbF1s0o7W07EV3j8zzRoyJxAKovUJtNXH7ew==} + dependencies: + '@types/node': 18.16.16 + '@types/node-fetch': 2.6.4 + abort-controller: 3.0.0 + agentkeepalive: 4.2.1 + digest-fetch: 1.3.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.6.8 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + /@apidevtools/json-schema-ref-parser@8.0.0: resolution: {integrity: sha512-n4YBtwQhdpLto1BaUCyAeflizmIbaloGShsPyRtFf5qdFJxfssj+GgLavczgKJFa3Bq+3St2CKcpRJdjtB4EBw==} dependencies: @@ -4454,7 +4473,7 @@ packages: dependencies: '@segment/loosely-validate-event': 2.0.0 auto-changelog: 1.16.4 - axios: 0.21.4 + axios: 0.21.4(debug@4.3.2) axios-retry: 3.3.1 bull: 3.29.3 lodash.clonedeep: 4.5.0 @@ -6490,6 +6509,10 @@ packages: form-data: 2.5.1 dev: true + /@types/retry@0.12.0: + resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + dev: false + /@types/rfc2047@2.0.1: resolution: {integrity: sha512-slgtykv+XXME7EperkdqfdBBUGcs28ru+a21BK0zOQY4IoxE7tEqvIcvAFAz5DJVxyOmoAUXo30Oxpm3KS+TBQ==} dev: true @@ -6645,6 +6668,10 @@ packages: resolution: {integrity: sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q==} dev: false + /@types/uuid@9.0.2: + resolution: {integrity: sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ==} + dev: false + /@types/validator@13.7.10: resolution: {integrity: sha512-t1yxFAR2n0+VO6hd/FJ9F2uezAZVWHLmpmlJzm1eX03+H7+HsuTAp7L8QJs+2pQCfWkP1+EXsGK9Z9v7o/qPVQ==} dev: false @@ -7573,7 +7600,6 @@ packages: transitivePeerDependencies: - supports-color dev: false - optional: true /aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} @@ -7669,7 +7695,6 @@ packages: /ansi-styles@5.2.0: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} - dev: true /ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} @@ -8145,18 +8170,27 @@ packages: is-retry-allowed: 2.2.0 dev: false - /axios@0.21.4: + /axios@0.21.4(debug@4.3.2): resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} dependencies: - follow-redirects: 1.15.2(debug@4.3.4) + follow-redirects: 1.15.2(debug@4.3.2) transitivePeerDependencies: - debug dev: false - /axios@0.21.4(debug@4.3.2): - resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} + /axios@0.26.1: + resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==} + dependencies: + follow-redirects: 1.15.2(debug@4.3.2) + transitivePeerDependencies: + - debug + dev: false + + /axios@0.27.2: + resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==} dependencies: follow-redirects: 1.15.2(debug@4.3.2) + form-data: 4.0.0 transitivePeerDependencies: - debug dev: false @@ -8177,6 +8211,7 @@ packages: form-data: 4.0.0 transitivePeerDependencies: - debug + dev: true /babel-core@7.0.0-bridge.0(@babel/core@7.22.9): resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==} @@ -8319,6 +8354,10 @@ packages: /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + /base-64@0.1.0: + resolution: {integrity: sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==} + dev: false + /base-64@1.0.0: resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==} dev: false @@ -8386,6 +8425,10 @@ packages: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} + /binary-search@1.3.6: + resolution: {integrity: sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==} + dev: false + /binascii@0.0.2: resolution: {integrity: sha512-rA2CrUl1+6yKrn+XgLs8Hdy18OER1UW146nM+ixzhQXDY+Bd3ySkyIJGwF2a4I45JwbvF1mDL/nWkqBwpOcdBA==} dev: false @@ -9230,7 +9273,6 @@ packages: /commander@10.0.1: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} - dev: true /commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -9851,7 +9893,6 @@ packages: /decamelize@1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} - dev: true /decimal.js@10.4.3: resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} @@ -10027,7 +10068,6 @@ packages: resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} engines: {node: '>= 0.6'} dev: false - optional: true /depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} @@ -10090,6 +10130,13 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dev: true + /digest-fetch@1.3.0: + resolution: {integrity: sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==} + dependencies: + base-64: 0.1.0 + md5: 2.3.0 + dev: false + /digest-header@0.0.1: resolution: {integrity: sha512-Qi0KOZgRnkQJuvMWbs1ZRRajEnbsMU8xlJI4rHIbPC+skHQ30heO5cIHpUFT4jAvAe+zPtdavLSAxASqoyZ3cg==} engines: {node: '>= 0.10.0'} @@ -11138,6 +11185,10 @@ packages: resolution: {integrity: sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==} dev: true + /eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + dev: false + /events@1.1.1: resolution: {integrity: sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==} engines: {node: '>=0.4.x'} @@ -11251,6 +11302,10 @@ packages: jest-util: 29.6.2 dev: true + /expr-eval@2.0.2: + resolution: {integrity: sha512-4EMSHGOPSwAfBiibw3ndnP0AvjDWLsMvGOvWEZ2F96IGk0bIVdjQisOHxReSkE13mHcfbuCiXw+G4y0zv6N8Eg==} + dev: false + /express-async-errors@3.1.1(express@4.18.2): resolution: {integrity: sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng==} peerDependencies: @@ -11704,6 +11759,11 @@ packages: rimraf: 3.0.2 dev: true + /flat@5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + dev: false + /flatted@3.2.7: resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} @@ -11757,6 +11817,7 @@ packages: optional: true dependencies: debug: 4.3.4(supports-color@8.1.1) + dev: true /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -11787,6 +11848,10 @@ packages: resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} dev: true + /form-data-encoder@1.7.2: + resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} + dev: false + /form-data@2.3.3: resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} engines: {node: '>= 0.12'} @@ -11821,6 +11886,14 @@ packages: combined-stream: 1.0.8 mime-types: 2.1.35 + /formdata-node@4.4.1: + resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} + engines: {node: '>= 12.20'} + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 + dev: false + /formidable@2.1.2: resolution: {integrity: sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==} dependencies: @@ -13035,6 +13108,10 @@ packages: kind-of: 6.0.3 dev: true + /is-any-array@2.0.1: + resolution: {integrity: sha512-UtilS7hLRu++wb/WBAw9bNuP1Eg04Ivn1vERJck8zJthEvXCBEBpGR/33u/xLKWEQf95803oalHrVDptcAvFdQ==} + dev: false + /is-arguments@1.1.1: resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} engines: {node: '>= 0.4'} @@ -14146,6 +14223,12 @@ packages: resolution: {integrity: sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==} dev: true + /js-tiktoken@1.0.7: + resolution: {integrity: sha512-biba8u/clw7iesNEWLOLwrNGoBP2lA+hTaBLs/D45pJdUPFXyxD6nhcDVtADChghv4GgyAiMKYMiRx7x6h7Biw==} + dependencies: + base64-js: 1.5.1 + dev: false + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} dev: true @@ -14380,6 +14463,11 @@ packages: underscore: 1.12.1 dev: false + /jsonpointer@5.0.1: + resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} + engines: {node: '>=0.10.0'} + dev: false + /jsonschema@1.4.1: resolution: {integrity: sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==} dev: false @@ -14506,6 +14594,244 @@ packages: resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} dev: false + /langchain@0.0.126(cheerio@1.0.0-rc.6)(mongodb@4.10.0)(mysql2@2.3.3)(pg@8.8.0)(redis@3.1.2): + resolution: {integrity: sha512-Z57WjAwLN+ZbJ9D4h2oTN7iDt1Aa67mcXhKYVCQFRu4w8PIPH5k0VBvmrzKyRj2iFIWdUaqVsHch/jz5+1mGnA==} + engines: {node: '>=18'} + peerDependencies: + '@aws-sdk/client-dynamodb': ^3.310.0 + '@aws-sdk/client-kendra': ^3.352.0 + '@aws-sdk/client-lambda': ^3.310.0 + '@aws-sdk/client-s3': ^3.310.0 + '@aws-sdk/client-sagemaker-runtime': ^3.310.0 + '@aws-sdk/client-sfn': ^3.310.0 + '@azure/storage-blob': ^12.15.0 + '@clickhouse/client': ^0.0.14 + '@elastic/elasticsearch': ^8.4.0 + '@getmetal/metal-sdk': '*' + '@getzep/zep-js': ^0.4.1 + '@gomomento/sdk': ^1.23.0 + '@google-ai/generativelanguage': ^0.2.1 + '@google-cloud/storage': ^6.10.1 + '@huggingface/inference': ^1.5.1 + '@notionhq/client': ^2.2.5 + '@opensearch-project/opensearch': '*' + '@pinecone-database/pinecone': '*' + '@planetscale/database': ^1.8.0 + '@qdrant/js-client-rest': ^1.2.0 + '@supabase/postgrest-js': ^1.1.1 + '@supabase/supabase-js': ^2.10.0 + '@tensorflow-models/universal-sentence-encoder': '*' + '@tensorflow/tfjs-converter': '*' + '@tensorflow/tfjs-core': '*' + '@tigrisdata/vector': ^1.1.0 + '@upstash/redis': ^1.20.6 + '@xata.io/client': ^0.25.1 + '@zilliz/milvus2-sdk-node': '>=2.2.7' + apify-client: ^2.7.1 + axios: '*' + cheerio: ^1.0.0-rc.12 + chromadb: ^1.5.3 + cohere-ai: ^5.0.2 + d3-dsv: ^2.0.0 + epub2: ^3.0.1 + faiss-node: ^0.2.1 + firebase-admin: ^11.9.0 + google-auth-library: ^8.9.0 + hnswlib-node: ^1.4.2 + html-to-text: ^9.0.5 + ignore: ^5.2.0 + ioredis: ^5.3.2 + mammoth: '*' + mongodb: ^5.2.0 + mysql2: ^3.3.3 + notion-to-md: ^3.1.0 + pdf-parse: 1.1.1 + peggy: ^3.0.2 + pg: ^8.11.0 + pg-copy-streams: ^6.0.5 + pickleparser: ^0.1.0 + playwright: ^1.32.1 + puppeteer: ^19.7.2 + redis: ^4.6.4 + replicate: ^0.12.3 + sonix-speech-recognition: ^2.1.1 + srt-parser-2: ^1.2.2 + typeorm: ^0.3.12 + typesense: ^1.5.3 + usearch: ^1.1.1 + vectordb: ^0.1.4 + weaviate-ts-client: ^1.4.0 + peerDependenciesMeta: + '@aws-sdk/client-dynamodb': + optional: true + '@aws-sdk/client-kendra': + optional: true + '@aws-sdk/client-lambda': + optional: true + '@aws-sdk/client-s3': + optional: true + '@aws-sdk/client-sagemaker-runtime': + optional: true + '@aws-sdk/client-sfn': + optional: true + '@azure/storage-blob': + optional: true + '@clickhouse/client': + optional: true + '@elastic/elasticsearch': + optional: true + '@getmetal/metal-sdk': + optional: true + '@getzep/zep-js': + optional: true + '@gomomento/sdk': + optional: true + '@google-ai/generativelanguage': + optional: true + '@google-cloud/storage': + optional: true + '@huggingface/inference': + optional: true + '@notionhq/client': + optional: true + '@opensearch-project/opensearch': + optional: true + '@pinecone-database/pinecone': + optional: true + '@planetscale/database': + optional: true + '@qdrant/js-client-rest': + optional: true + '@supabase/postgrest-js': + optional: true + '@supabase/supabase-js': + optional: true + '@tensorflow-models/universal-sentence-encoder': + optional: true + '@tensorflow/tfjs-converter': + optional: true + '@tensorflow/tfjs-core': + optional: true + '@tigrisdata/vector': + optional: true + '@upstash/redis': + optional: true + '@xata.io/client': + optional: true + '@zilliz/milvus2-sdk-node': + optional: true + apify-client: + optional: true + axios: + optional: true + cheerio: + optional: true + chromadb: + optional: true + cohere-ai: + optional: true + d3-dsv: + optional: true + epub2: + optional: true + faiss-node: + optional: true + firebase-admin: + optional: true + google-auth-library: + optional: true + hnswlib-node: + optional: true + html-to-text: + optional: true + ignore: + optional: true + ioredis: + optional: true + mammoth: + optional: true + mongodb: + optional: true + mysql2: + optional: true + notion-to-md: + optional: true + pdf-parse: + optional: true + peggy: + optional: true + pg: + optional: true + pg-copy-streams: + optional: true + pickleparser: + optional: true + playwright: + optional: true + puppeteer: + optional: true + redis: + optional: true + replicate: + optional: true + sonix-speech-recognition: + optional: true + srt-parser-2: + optional: true + typeorm: + optional: true + typesense: + optional: true + usearch: + optional: true + vectordb: + optional: true + weaviate-ts-client: + optional: true + dependencies: + '@anthropic-ai/sdk': 0.5.10 + ansi-styles: 5.2.0 + binary-extensions: 2.2.0 + camelcase: 6.3.0 + cheerio: 1.0.0-rc.6 + decamelize: 1.2.0 + expr-eval: 2.0.2 + flat: 5.0.2 + js-tiktoken: 1.0.7 + js-yaml: 4.1.0 + jsonpointer: 5.0.1 + langsmith: 0.0.20 + ml-distance: 4.0.1 + mongodb: 4.10.0 + mysql2: 2.3.3 + object-hash: 3.0.0 + openai: 3.3.0 + openapi-types: 12.1.3 + p-queue: 6.6.2 + p-retry: 4.6.2 + pg: 8.8.0 + redis: 3.1.2 + uuid: 9.0.0 + yaml: 2.3.1 + zod: 3.21.4 + zod-to-json-schema: 3.21.4(zod@3.21.4) + transitivePeerDependencies: + - debug + - encoding + - supports-color + dev: false + + /langsmith@0.0.20: + resolution: {integrity: sha512-5VCBELL3YECxm0UA8TlX0K9B7Ecme9L1jd1Lly2TSPVCgABEcNDGAXrXKJ+o72hwWlf6XL5sQejzQCXspXRdVw==} + hasBin: true + dependencies: + '@types/uuid': 9.0.2 + commander: 10.0.1 + p-queue: 6.6.2 + p-retry: 4.6.2 + uuid: 9.0.0 + dev: false + /last-run@1.1.1: resolution: {integrity: sha512-U/VxvpX4N/rFvPzr3qG5EtLKEnNI0emvIQB3/ecEwv+8GHaUKbIB8vxv1Oai5FAF0d0r7LXHhLLe5K/yChm5GQ==} engines: {node: '>= 0.10'} @@ -15498,6 +15824,37 @@ packages: hasBin: true dev: false + /ml-array-mean@1.1.6: + resolution: {integrity: sha512-MIdf7Zc8HznwIisyiJGRH9tRigg3Yf4FldW8DxKxpCCv/g5CafTw0RRu51nojVEOXuCQC7DRVVu5c7XXO/5joQ==} + dependencies: + ml-array-sum: 1.1.6 + dev: false + + /ml-array-sum@1.1.6: + resolution: {integrity: sha512-29mAh2GwH7ZmiRnup4UyibQZB9+ZLyMShvt4cH4eTK+cL2oEMIZFnSyB3SS8MlsTh6q/w/yh48KmqLxmovN4Dw==} + dependencies: + is-any-array: 2.0.1 + dev: false + + /ml-distance-euclidean@2.0.0: + resolution: {integrity: sha512-yC9/2o8QF0A3m/0IXqCTXCzz2pNEzvmcE/9HFKOZGnTjatvBbsn4lWYJkxENkA4Ug2fnYl7PXQxnPi21sgMy/Q==} + dev: false + + /ml-distance@4.0.1: + resolution: {integrity: sha512-feZ5ziXs01zhyFUUUeZV5hwc0f5JW0Sh0ckU1koZe/wdVkJdGxcP06KNQuF0WBTj8FttQUzcvQcpcrOp/XrlEw==} + dependencies: + ml-array-mean: 1.1.6 + ml-distance-euclidean: 2.0.0 + ml-tree-similarity: 1.0.0 + dev: false + + /ml-tree-similarity@1.0.0: + resolution: {integrity: sha512-XJUyYqjSuUQkNQHMscr6tcjldsOoAekxADTplt40QKfwW6nd++1wHWV9AArl0Zvw/TIHgNaZZNvr8QGvE8wLRg==} + dependencies: + binary-search: 1.3.6 + num-sort: 2.1.0 + dev: false + /mlly@1.4.0: resolution: {integrity: sha512-ua8PAThnTwpprIaU47EPeZ/bPUVp2QYBbWMphUQpVdBI3Lgqzm5KZQ45Agm3YJedHXaIHl6pBGabaLSUPPSptg==} dependencies: @@ -15815,6 +16172,11 @@ packages: minimatch: 3.1.2 dev: true + /node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + dev: false + /node-fetch-native@1.0.1: resolution: {integrity: sha512-VzW+TAk2wE4X9maiKMlT+GsPU4OMmR1U9CrHSmd3DFLn2IcZ9VJ6M6BBugGfYUnPCLSYxXdZy17M0BEJyhUTwg==} dev: true @@ -16055,6 +16417,11 @@ packages: resolution: {integrity: sha512-dK0Ss9C34R/vV0FfYJXuqDAqHlaW9fvWVufq9MmGF2umCuDbd5GRfRD9fpi/LiM0l4ZXf8IBB+RYmZExqCrf0w==} dev: false + /num-sort@2.1.0: + resolution: {integrity: sha512-1MQz1Ed8z2yckoBeSfkQHHO9K1yDRxxtotKSJ9yvcTUUxSvfvzEq5GwBrjjHEpMlq/k5gvXdmJ1SbYxWtpNoVg==} + engines: {node: '>=8'} + dev: false + /number-allocator@1.0.14: resolution: {integrity: sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==} dependencies: @@ -16106,6 +16473,11 @@ packages: kind-of: 3.2.2 dev: true + /object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + dev: false + /object-inspect@1.12.3: resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} @@ -16268,6 +16640,15 @@ packages: is-docker: 2.2.1 is-wsl: 2.2.0 + /openai@3.3.0: + resolution: {integrity: sha512-uqxI/Au+aPRnsaQRe8CojU0eCR7I0mBiKjD3sNMzY6DaC1ZVrc85u98mtJW6voDug8fgGN+DIZmTDxTthxb7dQ==} + dependencies: + axios: 0.26.1 + form-data: 4.0.0 + transitivePeerDependencies: + - debug + dev: false + /openapi-types@1.3.5: resolution: {integrity: sha512-11oi4zYorsgvg5yBarZplAqbpev5HkuVNPlZaPTknPDzAynq+lnJdXAmruGWP0s+dNYZS7bjM+xrTpJw7184Fg==} dev: true @@ -16276,6 +16657,10 @@ packages: resolution: {integrity: sha512-Y8xOCT2eiKGYDzMW9R4x5cmfc3vGaaI4EL2pwhDmodWw1HlK18YcZ4uJxc7Rdp7/gGzAygzH9SXr6GKYIXbRcQ==} dev: false + /openapi-types@12.1.3: + resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} + dev: false + /openurl@1.1.1: resolution: {integrity: sha512-d/gTkTb1i1GKz5k3XE3XFV/PxQ1k45zDqGP2OA7YhgsaLoqm6qRvARAZOFer1fcXritWlGBRCu/UgeS4HAnXAA==} dev: false @@ -16428,6 +16813,22 @@ packages: dependencies: aggregate-error: 3.1.0 + /p-queue@6.6.2: + resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} + engines: {node: '>=8'} + dependencies: + eventemitter3: 4.0.7 + p-timeout: 3.2.0 + dev: false + + /p-retry@4.6.2: + resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} + engines: {node: '>=8'} + dependencies: + '@types/retry': 0.12.0 + retry: 0.13.1 + dev: false + /p-timeout@3.2.0: resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} engines: {node: '>=8'} @@ -17116,7 +17517,7 @@ packages: resolution: {integrity: sha512-aXYe/D+28kF63W8Cz53t09ypEORz+ULeDCahdAqhVrRm2scbOXFbtnn0GGhvMpYe45grepLKuwui9KxrZ2ZuMw==} engines: {node: '>=14.17.0'} dependencies: - axios: 0.27.2(debug@4.3.4) + axios: 0.27.2 transitivePeerDependencies: - debug dev: false @@ -21067,6 +21468,11 @@ packages: engines: {node: '>= 8'} dev: false + /web-streams-polyfill@4.0.0-beta.3: + resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} + engines: {node: '>= 14'} + dev: false + /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -21475,6 +21881,11 @@ packages: engines: {node: '>= 6'} dev: true + /yaml@2.3.1: + resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==} + engines: {node: '>= 14'} + dev: false + /yamljs@0.3.0: resolution: {integrity: sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==} hasBin: true @@ -21648,6 +22059,18 @@ packages: commander: 2.20.3 dev: true + /zod-to-json-schema@3.21.4(zod@3.21.4): + resolution: {integrity: sha512-fjUZh4nQ1s6HMccgIeE0VP4QG/YRGPmyjO9sAh890aQKPEk3nqbfUXhMFaC+Dr5KvYBm8BCyvfpZf2jY9aGSsw==} + peerDependencies: + zod: ^3.21.4 + dependencies: + zod: 3.21.4 + dev: false + + /zod@3.21.4: + resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==} + dev: false + '@cdn.sheetjs.com/xlsx-0.19.3/xlsx-0.19.3.tgz': resolution: {tarball: https://cdn.sheetjs.com/xlsx-0.19.3/xlsx-0.19.3.tgz} name: xlsx From 33a73cc8489467946320ace06c3100c21bf5ac5e Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 11 Aug 2023 22:42:33 +0200 Subject: [PATCH 011/372] :zap: Rename output and remove test code --- packages/editor-ui/src/mixins/nodeBase.ts | 4 +-- packages/editor-ui/src/utils/nodeViewUtils.ts | 2 +- .../nodes/AiTest/AiTestConfig.node.ts | 2 +- .../nodes/LangChain/LangChain.node.ts | 25 ++----------------- .../LangChain/LangChainToolCalculator.node.ts | 2 +- .../LangChain/LangChainToolSerpApi.node.ts | 2 +- .../LangChain/LangChainToolWikipedia.node.ts | 2 +- packages/workflow/src/Interfaces.ts | 2 +- 8 files changed, 10 insertions(+), 31 deletions(-) diff --git a/packages/editor-ui/src/mixins/nodeBase.ts b/packages/editor-ui/src/mixins/nodeBase.ts index 63c9bb73ab7c4..1b389b297351e 100644 --- a/packages/editor-ui/src/mixins/nodeBase.ts +++ b/packages/editor-ui/src/mixins/nodeBase.ts @@ -287,7 +287,7 @@ export const nodeBase = defineComponent({ : NodeViewUtils.getOutputEndpointStyle)(nodeTypeData, '--color-foreground-xdark'), cssClass: `dot-${type}-endpoint`, }, - test: { + tool: { paintStyle: (type === 'input' ? NodeViewUtils.getInputEndpointStyle : NodeViewUtils.getOutputEndpointStyle)(nodeTypeData, '--color-danger'), @@ -299,7 +299,7 @@ export const nodeBase = defineComponent({ return {}; } - if (connectionType === 'test') { + if (connectionType === 'tool') { const width = connectionTypes[connectionType].paintStyle!.width!; connectionTypes[connectionType].paintStyle!.width = connectionTypes[connectionType].paintStyle!.height; diff --git a/packages/editor-ui/src/utils/nodeViewUtils.ts b/packages/editor-ui/src/utils/nodeViewUtils.ts index a0c8dcad8a26e..09227835da76e 100644 --- a/packages/editor-ui/src/utils/nodeViewUtils.ts +++ b/packages/editor-ui/src/utils/nodeViewUtils.ts @@ -176,7 +176,7 @@ export const ANCHOR_POSITIONS: { ], }, }, - test: { + tool: { input: { 1: [[0.5, 0.01, 0, -1]], 2: [ diff --git a/packages/nodes-base/nodes/AiTest/AiTestConfig.node.ts b/packages/nodes-base/nodes/AiTest/AiTestConfig.node.ts index ebcf8f192f246..0fd4865de2041 100644 --- a/packages/nodes-base/nodes/AiTest/AiTestConfig.node.ts +++ b/packages/nodes-base/nodes/AiTest/AiTestConfig.node.ts @@ -20,7 +20,7 @@ export class AiTestConfig implements INodeType { // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node inputs: [], // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong - outputs: ['test'], + outputs: ['tool'], outputNames: ['Test'], properties: [ { diff --git a/packages/nodes-base/nodes/LangChain/LangChain.node.ts b/packages/nodes-base/nodes/LangChain/LangChain.node.ts index 768b6fdc0d6fd..00b213dbfd838 100644 --- a/packages/nodes-base/nodes/LangChain/LangChain.node.ts +++ b/packages/nodes-base/nodes/LangChain/LangChain.node.ts @@ -28,7 +28,7 @@ export class LangChain implements INodeType { color: '#404040', }, // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node - inputs: ['main', 'test'], + inputs: ['main', 'tool'], inputNames: ['', 'Tools'], outputs: ['main'], credentials: [ @@ -48,30 +48,9 @@ export class LangChain implements INodeType { }; async execute(this: IExecuteFunctions): Promise { - // const llm = new OpenAI(); - // const result = await llm.predict( - // 'What would be a good company name for a company that makes colorful socks?', - // ); - // const credentials = await this.getCredentials('openAiApi'); - // const model = new OpenAI({ - // // openAIApiKey: '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', - // openAIApiKey: credentials.apiKey as string, - // }); - // const memory = new BufferMemory(); - // const chain = new ConversationChain({ llm: model, memory }); - // // const text = this.getNodeParameter('text', 0) as string; - // const asdf = await chain.call({ input: 'hello my name is Jan' }); - // console.log('asdf', asdf); - // const response = await chain.call({ input: 'What did I say my name was?' }); - // return this.prepareOutputData([ - // { json: { history: memory.chatHistory.getMessages(), response } }, - // ]); - // // TODO: Add support for memory and tools in first step - // console.log('result', result); - const tools: StructuredTool[] = []; - const toolNodes = await this.getInputConnectionData(0, 0, 'test'); + const toolNodes = await this.getInputConnectionData(0, 0, 'tool'); toolNodes.forEach((toolNode) => { if (!toolNode.parameters.enabled) { diff --git a/packages/nodes-base/nodes/LangChain/LangChainToolCalculator.node.ts b/packages/nodes-base/nodes/LangChain/LangChainToolCalculator.node.ts index 9d6c3e90466af..1d8a34e9c3a2d 100644 --- a/packages/nodes-base/nodes/LangChain/LangChainToolCalculator.node.ts +++ b/packages/nodes-base/nodes/LangChain/LangChainToolCalculator.node.ts @@ -21,7 +21,7 @@ export class LangChainToolCalculator implements INodeType { // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node inputs: [], // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong - outputs: ['test'], + outputs: ['tool'], outputNames: ['Tool'], properties: [ { diff --git a/packages/nodes-base/nodes/LangChain/LangChainToolSerpApi.node.ts b/packages/nodes-base/nodes/LangChain/LangChainToolSerpApi.node.ts index af05ab2e1314b..ba0291d897365 100644 --- a/packages/nodes-base/nodes/LangChain/LangChainToolSerpApi.node.ts +++ b/packages/nodes-base/nodes/LangChain/LangChainToolSerpApi.node.ts @@ -22,7 +22,7 @@ export class LangChainToolSerpApi implements INodeType { // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node inputs: [], // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong - outputs: ['test'], + outputs: ['tool'], outputNames: ['Tool'], credentials: [ { diff --git a/packages/nodes-base/nodes/LangChain/LangChainToolWikipedia.node.ts b/packages/nodes-base/nodes/LangChain/LangChainToolWikipedia.node.ts index c8c134a0cfb0b..a36feb274485a 100644 --- a/packages/nodes-base/nodes/LangChain/LangChainToolWikipedia.node.ts +++ b/packages/nodes-base/nodes/LangChain/LangChainToolWikipedia.node.ts @@ -22,7 +22,7 @@ export class LangChainToolWikipedia implements INodeType { // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node inputs: [], // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong - outputs: ['test'], + outputs: ['tool'], outputNames: ['Tool'], properties: [ { diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 041dcc6b9590c..30f2c5d00a330 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -1467,7 +1467,7 @@ export interface IPostReceiveSort extends IPostReceiveBase { }; } -export type ConnectionTypes = 'main' | 'test'; +export type ConnectionTypes = 'main' | 'tool'; export interface INodeTypeDescription extends INodeTypeBaseDescription { version: number | number[]; From 5e7973dd9cf0d4102e01e4b6a3dd9bf08de04586 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 12 Aug 2023 08:26:37 +0200 Subject: [PATCH 012/372] :zap: Make sure that only nodes from other inputs than main can get queried --- packages/core/src/NodeExecuteFunctions.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index 56c864caba867..d5b8c29c7d992 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -2580,6 +2580,12 @@ export function getExecuteFunctions( inputIndex?: number, inputName?: ConnectionTypes, ): Promise { + if (inputName === 'main') { + throw new Error( + 'It is not allowed to query data from nodes connected through the main input!', + ); + } + const parentNodes = workflow.getParentNodes(node.name, inputName, 1); if (parentNodes.length === 0) { From e04a80cb6780b9d5a9548c4c6c484f4a2eeb84d7 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 12 Aug 2023 08:47:28 +0200 Subject: [PATCH 013/372] :zap: Add support for more connection types --- packages/editor-ui/src/mixins/nodeBase.ts | 44 ++++++++++++------- packages/editor-ui/src/utils/nodeViewUtils.ts | 4 +- .../nodes/AiTest/AiTestConfig.node.ts | 6 +-- packages/workflow/src/Interfaces.ts | 2 +- 4 files changed, 35 insertions(+), 21 deletions(-) diff --git a/packages/editor-ui/src/mixins/nodeBase.ts b/packages/editor-ui/src/mixins/nodeBase.ts index 1b389b297351e..b64955669590e 100644 --- a/packages/editor-ui/src/mixins/nodeBase.ts +++ b/packages/editor-ui/src/mixins/nodeBase.ts @@ -76,20 +76,24 @@ export const nodeBase = defineComponent({ [key: string]: number; } = {}; - nodeTypeData.inputs.forEach((inputName: string, i: number) => { + nodeTypeData.inputs.forEach((inputName, i) => { + const locactionIntputName = inputName === 'main' ? 'main' : 'other'; + // Increment the index for inputs with current name - if (indexData.hasOwnProperty(inputName)) { - indexData[inputName]++; + if (indexData.hasOwnProperty(locactionIntputName)) { + indexData[locactionIntputName]++; } else { - indexData[inputName] = 0; + indexData[locactionIntputName] = 0; } - const typeIndex = indexData[inputName]; + const typeIndex = indexData[locactionIntputName]; - const inputsOfSameType = nodeTypeData.inputs.filter((input) => input === inputName); + const inputsOfSameType = nodeTypeData.inputs.filter((input) => input !== 'main'); // Get the position of the anchor depending on how many it has const anchorPosition = - NodeViewUtils.ANCHOR_POSITIONS[inputName].input[inputsOfSameType.length][typeIndex]; + NodeViewUtils.ANCHOR_POSITIONS[locactionIntputName].input[inputsOfSameType.length][ + typeIndex + ]; const newEndpointData: EndpointOptions = { uuid: NodeViewUtils.getInputEndpointUUID(this.nodeId, i), @@ -157,20 +161,24 @@ export const nodeBase = defineComponent({ // TODO: There are still a lot of references of "main" in NodesView and // other locations. So assume there will be more problems - nodeTypeData.outputs.forEach((outputName: string, i: number) => { + nodeTypeData.outputs.forEach((outputName, i) => { + const locactionOutputName = outputName === 'main' ? 'main' : 'other'; + // Increment the index for outputs with current name - if (indexData.hasOwnProperty(outputName)) { - indexData[outputName]++; + if (indexData.hasOwnProperty(locactionOutputName)) { + indexData[locactionOutputName]++; } else { - indexData[outputName] = 0; + indexData[locactionOutputName] = 0; } - const typeIndex = indexData[outputName]; + const typeIndex = indexData[locactionOutputName]; - const outputsOfSameType = nodeTypeData.outputs.filter((output) => output === outputName); + const outputsOfSameType = nodeTypeData.outputs.filter((output) => output !== 'main'); // Get the position of the anchor depending on how many it has const anchorPosition = - NodeViewUtils.ANCHOR_POSITIONS[outputName].output[outputsOfSameType.length][typeIndex]; + NodeViewUtils.ANCHOR_POSITIONS[locactionOutputName].output[outputsOfSameType.length][ + typeIndex + ]; const newEndpointData: EndpointOptions = { uuid: NodeViewUtils.getOutputEndpointUUID(this.nodeId, i), @@ -287,6 +295,12 @@ export const nodeBase = defineComponent({ : NodeViewUtils.getOutputEndpointStyle)(nodeTypeData, '--color-foreground-xdark'), cssClass: `dot-${type}-endpoint`, }, + memory: { + paintStyle: (type === 'input' + ? NodeViewUtils.getInputEndpointStyle + : NodeViewUtils.getOutputEndpointStyle)(nodeTypeData, '--color-danger'), + cssClass: `dot-${type}-endpoint`, + }, tool: { paintStyle: (type === 'input' ? NodeViewUtils.getInputEndpointStyle @@ -299,7 +313,7 @@ export const nodeBase = defineComponent({ return {}; } - if (connectionType === 'tool') { + if (connectionType !== 'main') { const width = connectionTypes[connectionType].paintStyle!.width!; connectionTypes[connectionType].paintStyle!.width = connectionTypes[connectionType].paintStyle!.height; diff --git a/packages/editor-ui/src/utils/nodeViewUtils.ts b/packages/editor-ui/src/utils/nodeViewUtils.ts index 09227835da76e..e8a3b51503f32 100644 --- a/packages/editor-ui/src/utils/nodeViewUtils.ts +++ b/packages/editor-ui/src/utils/nodeViewUtils.ts @@ -131,7 +131,7 @@ export const CONNECTOR_ARROW_OVERLAYS: OverlaySpec[] = [ ]; export const ANCHOR_POSITIONS: { - [key: ConnectionTypes]: { + [key: string]: { // type: input | output [key: string]: { [key: number]: ArrayAnchorSpec[]; @@ -176,7 +176,7 @@ export const ANCHOR_POSITIONS: { ], }, }, - tool: { + other: { input: { 1: [[0.5, 0.01, 0, -1]], 2: [ diff --git a/packages/nodes-base/nodes/AiTest/AiTestConfig.node.ts b/packages/nodes-base/nodes/AiTest/AiTestConfig.node.ts index 0fd4865de2041..2375090d811c6 100644 --- a/packages/nodes-base/nodes/AiTest/AiTestConfig.node.ts +++ b/packages/nodes-base/nodes/AiTest/AiTestConfig.node.ts @@ -18,10 +18,10 @@ export class AiTestConfig implements INodeType { color: '#400080', }, // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node - inputs: [], + inputs: ['main', 'tool', 'memory'], // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong - outputs: ['tool'], - outputNames: ['Test'], + outputs: ['main', 'tool', 'memory'], + outputNames: ['Test', 'Tool', 'Memory'], properties: [ { displayName: 'Model', diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 30f2c5d00a330..4059a4404e715 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -1467,7 +1467,7 @@ export interface IPostReceiveSort extends IPostReceiveBase { }; } -export type ConnectionTypes = 'main' | 'tool'; +export type ConnectionTypes = 'main' | 'memory' | 'tool'; export interface INodeTypeDescription extends INodeTypeBaseDescription { version: number | number[]; From 061eb27c22dd0836a7fbd6082f6f1309ea976bc7 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 12 Aug 2023 09:14:13 +0200 Subject: [PATCH 014/372] :zap: Fix bug with connections --- packages/editor-ui/src/mixins/nodeBase.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/editor-ui/src/mixins/nodeBase.ts b/packages/editor-ui/src/mixins/nodeBase.ts index b64955669590e..24ccd2e83db40 100644 --- a/packages/editor-ui/src/mixins/nodeBase.ts +++ b/packages/editor-ui/src/mixins/nodeBase.ts @@ -87,7 +87,9 @@ export const nodeBase = defineComponent({ } const typeIndex = indexData[locactionIntputName]; - const inputsOfSameType = nodeTypeData.inputs.filter((input) => input !== 'main'); + const inputsOfSameType = nodeTypeData.inputs.filter((input) => + inputName === 'main' ? input === 'main' : input !== 'main', + ); // Get the position of the anchor depending on how many it has const anchorPosition = @@ -172,7 +174,9 @@ export const nodeBase = defineComponent({ } const typeIndex = indexData[locactionOutputName]; - const outputsOfSameType = nodeTypeData.outputs.filter((output) => output !== 'main'); + const outputsOfSameType = nodeTypeData.outputs.filter((output) => + outputName === 'main' ? output === 'main' : output !== 'main', + ); // Get the position of the anchor depending on how many it has const anchorPosition = From 63c197aa0112764eba091c3a958931ceaef6b82c Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 12 Aug 2023 13:39:07 +0200 Subject: [PATCH 015/372] :zap: Add incomplete Motorhead support --- packages/core/src/NodeExecuteFunctions.ts | 2 +- packages/editor-ui/src/mixins/nodeBase.ts | 2 +- .../credentials/MotorheadApi.credentials.ts | 54 ++++++++++++++++++ .../credentials/SerpApi.credentials.ts | 41 +++++++++++++ .../nodes/LangChain/LangChain.node.ts | 53 ++++++++++++----- .../LangChainMemoryMotorhead.node.ts | 57 +++++++++++++++++++ packages/nodes-base/package.json | 2 + pnpm-lock.yaml | 29 +++++----- 8 files changed, 207 insertions(+), 33 deletions(-) create mode 100644 packages/nodes-base/credentials/MotorheadApi.credentials.ts create mode 100644 packages/nodes-base/credentials/SerpApi.credentials.ts create mode 100644 packages/nodes-base/nodes/LangChain/LangChainMemoryMotorhead.node.ts diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index d5b8c29c7d992..b315c2b7c099d 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -2589,7 +2589,7 @@ export function getExecuteFunctions( const parentNodes = workflow.getParentNodes(node.name, inputName, 1); if (parentNodes.length === 0) { - throw new Error('Could not get input connection data'); + return []; } const constParentNodes = parentNodes.map(async (nodeName) => { diff --git a/packages/editor-ui/src/mixins/nodeBase.ts b/packages/editor-ui/src/mixins/nodeBase.ts index 24ccd2e83db40..2732eb6597714 100644 --- a/packages/editor-ui/src/mixins/nodeBase.ts +++ b/packages/editor-ui/src/mixins/nodeBase.ts @@ -302,7 +302,7 @@ export const nodeBase = defineComponent({ memory: { paintStyle: (type === 'input' ? NodeViewUtils.getInputEndpointStyle - : NodeViewUtils.getOutputEndpointStyle)(nodeTypeData, '--color-danger'), + : NodeViewUtils.getOutputEndpointStyle)(nodeTypeData, '--color-secondary'), cssClass: `dot-${type}-endpoint`, }, tool: { diff --git a/packages/nodes-base/credentials/MotorheadApi.credentials.ts b/packages/nodes-base/credentials/MotorheadApi.credentials.ts new file mode 100644 index 0000000000000..f9cecc49fe325 --- /dev/null +++ b/packages/nodes-base/credentials/MotorheadApi.credentials.ts @@ -0,0 +1,54 @@ +import type { + IAuthenticateGeneric, + ICredentialTestRequest, + ICredentialType, + INodeProperties, +} from 'n8n-workflow'; + +export class MotorheadApi implements ICredentialType { + name = 'motorheadApi'; + + displayName = 'MotorheadApi'; + + documentationUrl = 'motorheadApi'; + + properties: INodeProperties[] = [ + { + displayName: 'Host', + name: 'host', + required: true, + type: 'string', + default: '', + }, + { + displayName: 'API Key', + name: 'apiKey', + type: 'string', + typeOptions: { password: true }, + required: true, + default: '', + }, + { + displayName: 'Client ID', + name: 'clientId', + type: 'string', + default: '', + }, + ]; + + authenticate: IAuthenticateGeneric = { + type: 'generic', + properties: { + headers: { + 'x-metal-client-id': '={{$credentials.clientId}}', + 'x-metal-api-key': '={{$credentials.apiKey}}', + }, + }, + }; + + test: ICredentialTestRequest = { + request: { + baseURL: '={{$credentials.host}}', + }, + }; +} diff --git a/packages/nodes-base/credentials/SerpApi.credentials.ts b/packages/nodes-base/credentials/SerpApi.credentials.ts new file mode 100644 index 0000000000000..fc649655d6909 --- /dev/null +++ b/packages/nodes-base/credentials/SerpApi.credentials.ts @@ -0,0 +1,41 @@ +import type { + IAuthenticateGeneric, + ICredentialTestRequest, + ICredentialType, + INodeProperties, +} from 'n8n-workflow'; + +export class SerpApi implements ICredentialType { + name = 'serpApi'; + + displayName = 'SerpAPI'; + + documentationUrl = 'serpAi'; + + properties: INodeProperties[] = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string', + typeOptions: { password: true }, + required: true, + default: '', + }, + ]; + + authenticate: IAuthenticateGeneric = { + type: 'generic', + properties: { + qs: { + api_key: '={{$credentials.apiKey}}', + }, + }, + }; + + test: ICredentialTestRequest = { + request: { + baseURL: 'https://serpapi.com', + url: '/account.json ', + }, + }; +} diff --git a/packages/nodes-base/nodes/LangChain/LangChain.node.ts b/packages/nodes-base/nodes/LangChain/LangChain.node.ts index 00b213dbfd838..95d9bfa18fb6b 100644 --- a/packages/nodes-base/nodes/LangChain/LangChain.node.ts +++ b/packages/nodes-base/nodes/LangChain/LangChain.node.ts @@ -9,11 +9,13 @@ import { NodeOperationError } from 'n8n-workflow'; import get from 'lodash/get'; -import { initializeAgentExecutorWithOptions } from 'langchain/agents'; import { ChatOpenAI } from 'langchain/chat_models/openai'; import type { StructuredTool } from 'langchain/tools'; import { SerpAPI, WikipediaQueryRun } from 'langchain/tools'; import { Calculator } from 'langchain/tools/calculator'; +import type { BaseChatMemory } from 'langchain/memory'; +import { MotorheadMemory } from 'langchain/memory'; +import { ConversationChain } from 'langchain/chains'; export class LangChain implements INodeType { description: INodeTypeDescription = { @@ -28,8 +30,8 @@ export class LangChain implements INodeType { color: '#404040', }, // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node - inputs: ['main', 'tool'], - inputNames: ['', 'Tools'], + inputs: ['main', 'tool', 'memory'], + inputNames: ['', 'Tools', 'Memory'], outputs: ['main'], credentials: [ { @@ -49,44 +51,65 @@ export class LangChain implements INodeType { async execute(this: IExecuteFunctions): Promise { const tools: StructuredTool[] = []; + let memory: BaseChatMemory | undefined; + + const memoryNodes = await this.getInputConnectionData(0, 0, 'memory'); + memoryNodes.forEach((connectedNode) => { + if (connectedNode.type === 'n8n-nodes-base.langChainMemoryMotorhead') { + const url = get(connectedNode, 'credentials.motorheadApi.host', '') as string; + const clientId = get(connectedNode, 'credentials.motorheadApi.clientId'); + const apiKey = get(connectedNode, 'credentials.motorheadApi.apiKey'); + + const memoryKey = get(connectedNode, 'parameters.memoryKey', '') as string; + const sessionId = get(connectedNode, 'parameters.sessionId', '') as string; + + // TODO: Does not work yet + memory = new MotorheadMemory({ + memoryKey, + sessionId, + url, + clientId, + apiKey, + }); + } + }); const toolNodes = await this.getInputConnectionData(0, 0, 'tool'); - toolNodes.forEach((toolNode) => { - if (!toolNode.parameters.enabled) { + // TODO: Should later find way to move that logic to the nodes again + // but at the same time keep maxium flexibility + toolNodes.forEach((connectedNode) => { + if (!connectedNode.parameters.enabled) { return; } - if (toolNode.type === 'n8n-nodes-base.langChainToolCalculator') { + if (connectedNode.type === 'n8n-nodes-base.langChainToolCalculator') { tools.push(new Calculator()); - } else if (toolNode.type === 'n8n-nodes-base.langChainToolSerpApi') { - const apiKey = get(toolNode, 'credentials.serpApi.apiKey'); + } else if (connectedNode.type === 'n8n-nodes-base.langChainToolSerpApi') { + const apiKey = get(connectedNode, 'credentials.serpApi.apiKey'); if (!apiKey) { throw new NodeOperationError(this.getNode(), 'SerpAPI API key missing'); } tools.push(new SerpAPI(apiKey as string)); - } else if (toolNode.type === 'n8n-nodes-base.langChainToolWikipedia') { + } else if (connectedNode.type === 'n8n-nodes-base.langChainToolWikipedia') { tools.push(new WikipediaQueryRun()); } }); const credentials = await this.getCredentials('openAiApi'); - const chat = new ChatOpenAI({ + const model = new ChatOpenAI({ openAIApiKey: credentials.apiKey as string, modelName: 'gpt-3.5-turbo', temperature: 0, }); - const executor = await initializeAgentExecutorWithOptions(tools, chat, { - agentType: 'openai-functions', - // verbose: true, - }); + const chain = new ConversationChain({ llm: model, memory }); const items = this.getInputData(); const returnData: INodeExecutionData[] = []; for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { const text = this.getNodeParameter('text', itemIndex) as string; - const response = await executor.run(text); + const response = await chain.call({ input: text }); returnData.push({ json: { response } }); } return this.prepareOutputData(returnData); diff --git a/packages/nodes-base/nodes/LangChain/LangChainMemoryMotorhead.node.ts b/packages/nodes-base/nodes/LangChain/LangChainMemoryMotorhead.node.ts new file mode 100644 index 0000000000000..55ff34a1ca925 --- /dev/null +++ b/packages/nodes-base/nodes/LangChain/LangChainMemoryMotorhead.node.ts @@ -0,0 +1,57 @@ +/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ +import type { + IExecuteFunctions, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +export class LangChainMemoryMotorhead implements INodeType { + description: INodeTypeDescription = { + displayName: 'LangChain - Motorhead', + name: 'langChainMemoryMotorhead', + icon: 'fa:file-export', + group: ['transform'], + version: 1, + description: 'Motorhead Memory', + defaults: { + name: 'LangChain - Motorhead', + color: '#303030', + }, + // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node + inputs: [], + // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong + outputs: ['memory'], + outputNames: ['Memory'], + credentials: [ + { + name: 'motorheadApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Memory Key', + name: 'memoryKey', + type: 'string', + default: '', + }, + { + displayName: 'Session ID', + name: 'sessionId', + type: 'string', + default: '', + }, + { + displayName: 'Enabled', + name: 'enabled', + type: 'boolean', + default: true, + }, + ], + }; + + async execute(this: IExecuteFunctions): Promise { + return []; + } +} diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index d31f4421b4bc5..d6e165e09f1fd 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -230,6 +230,7 @@ "dist/credentials/MondayComOAuth2Api.credentials.js", "dist/credentials/MongoDb.credentials.js", "dist/credentials/MonicaCrmApi.credentials.js", + "dist/credentials/MotorheadApi.credentials.js", "dist/credentials/Mqtt.credentials.js", "dist/credentials/Msg91Api.credentials.js", "dist/credentials/MySql.credentials.js", @@ -570,6 +571,7 @@ "dist/nodes/KoBoToolbox/KoBoToolbox.node.js", "dist/nodes/KoBoToolbox/KoBoToolboxTrigger.node.js", "dist/nodes/LangChain/LangChain.node.js", + "dist/nodes/LangChain/LangChainMemoryMotorhead.node.js", "dist/nodes/LangChain/LangChainToolCalculator.node.js", "dist/nodes/LangChain/LangChainToolSerpApi.node.js", "dist/nodes/LangChain/LangChainToolWikipedia.node.js", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c51691efe4739..7be8a071380ce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -135,7 +135,7 @@ importers: dependencies: axios: specifier: ^0.21.1 - version: 0.21.4(debug@4.3.2) + version: 0.21.4 packages/@n8n_io/eslint-config: devDependencies: @@ -216,7 +216,7 @@ importers: version: 7.28.1 axios: specifier: ^0.21.1 - version: 0.21.4(debug@4.3.2) + version: 0.21.4 basic-auth: specifier: ^2.0.1 version: 2.0.1 @@ -574,7 +574,7 @@ importers: version: link:../@n8n/client-oauth2 axios: specifier: ^0.21.1 - version: 0.21.4(debug@4.3.2) + version: 0.21.4 concat-stream: specifier: ^2.0.0 version: 2.0.0 @@ -834,7 +834,7 @@ importers: version: 10.2.0(vue@3.3.4) axios: specifier: ^0.21.1 - version: 0.21.4(debug@4.3.2) + version: 0.21.4 codemirror-lang-html-n8n: specifier: ^1.0.0 version: 1.0.0 @@ -4473,7 +4473,7 @@ packages: dependencies: '@segment/loosely-validate-event': 2.0.0 auto-changelog: 1.16.4 - axios: 0.21.4(debug@4.3.2) + axios: 0.21.4 axios-retry: 3.3.1 bull: 3.29.3 lodash.clonedeep: 4.5.0 @@ -8170,27 +8170,26 @@ packages: is-retry-allowed: 2.2.0 dev: false - /axios@0.21.4(debug@4.3.2): + /axios@0.21.4: resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} dependencies: - follow-redirects: 1.15.2(debug@4.3.2) + follow-redirects: 1.15.2(debug@4.3.4) transitivePeerDependencies: - debug dev: false - /axios@0.26.1: - resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==} + /axios@0.21.4(debug@4.3.2): + resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} dependencies: follow-redirects: 1.15.2(debug@4.3.2) transitivePeerDependencies: - debug dev: false - /axios@0.27.2: - resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==} + /axios@0.26.1: + resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==} dependencies: - follow-redirects: 1.15.2(debug@4.3.2) - form-data: 4.0.0 + follow-redirects: 1.15.2(debug@4.3.4) transitivePeerDependencies: - debug dev: false @@ -8211,7 +8210,6 @@ packages: form-data: 4.0.0 transitivePeerDependencies: - debug - dev: true /babel-core@7.0.0-bridge.0(@babel/core@7.22.9): resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==} @@ -11817,7 +11815,6 @@ packages: optional: true dependencies: debug: 4.3.4(supports-color@8.1.1) - dev: true /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -17517,7 +17514,7 @@ packages: resolution: {integrity: sha512-aXYe/D+28kF63W8Cz53t09ypEORz+ULeDCahdAqhVrRm2scbOXFbtnn0GGhvMpYe45grepLKuwui9KxrZ2ZuMw==} engines: {node: '>=14.17.0'} dependencies: - axios: 0.27.2 + axios: 0.27.2(debug@4.3.4) transitivePeerDependencies: - debug dev: false From 1802557d63bddb87d68c09f15d32041cd7c83f47 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 12 Aug 2023 14:04:16 +0200 Subject: [PATCH 016/372] :zap: Split out the Language Model --- packages/editor-ui/src/mixins/nodeBase.ts | 6 ++ .../nodes/LangChain/LangChain.node.ts | 50 ++++++++---- .../nodes/LangChain/LangChainLMOpenAi.node.ts | 77 +++++++++++++++++++ .../nodes-base/nodes/LangChain/openAi.svg | 7 ++ packages/nodes-base/package.json | 1 + packages/workflow/src/Interfaces.ts | 2 +- 6 files changed, 126 insertions(+), 17 deletions(-) create mode 100644 packages/nodes-base/nodes/LangChain/LangChainLMOpenAi.node.ts create mode 100644 packages/nodes-base/nodes/LangChain/openAi.svg diff --git a/packages/editor-ui/src/mixins/nodeBase.ts b/packages/editor-ui/src/mixins/nodeBase.ts index 2732eb6597714..f53855cf88904 100644 --- a/packages/editor-ui/src/mixins/nodeBase.ts +++ b/packages/editor-ui/src/mixins/nodeBase.ts @@ -293,6 +293,12 @@ export const nodeBase = defineComponent({ const connectionTypes: { [key: string]: EndpointOptions; } = { + languageModel: { + paintStyle: (type === 'input' + ? NodeViewUtils.getInputEndpointStyle + : NodeViewUtils.getOutputEndpointStyle)(nodeTypeData, '--color-primary'), + cssClass: `dot-${type}-endpoint`, + }, main: { paintStyle: (type === 'input' ? NodeViewUtils.getInputEndpointStyle diff --git a/packages/nodes-base/nodes/LangChain/LangChain.node.ts b/packages/nodes-base/nodes/LangChain/LangChain.node.ts index 95d9bfa18fb6b..cc499018ca12e 100644 --- a/packages/nodes-base/nodes/LangChain/LangChain.node.ts +++ b/packages/nodes-base/nodes/LangChain/LangChain.node.ts @@ -16,6 +16,7 @@ import { Calculator } from 'langchain/tools/calculator'; import type { BaseChatMemory } from 'langchain/memory'; import { MotorheadMemory } from 'langchain/memory'; import { ConversationChain } from 'langchain/chains'; +import { BaseChatModel } from 'langchain/dist/chat_models/base'; export class LangChain implements INodeType { description: INodeTypeDescription = { @@ -30,15 +31,10 @@ export class LangChain implements INodeType { color: '#404040', }, // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node - inputs: ['main', 'tool', 'memory'], - inputNames: ['', 'Tools', 'Memory'], + inputs: ['main', 'tool', 'memory', 'languageModel'], + inputNames: ['', 'Tools', 'Memory', 'Language Model'], outputs: ['main'], - credentials: [ - { - name: 'openAiApi', - required: true, - }, - ], + credentials: [], properties: [ { displayName: 'Text', @@ -52,9 +48,38 @@ export class LangChain implements INodeType { async execute(this: IExecuteFunctions): Promise { const tools: StructuredTool[] = []; let memory: BaseChatMemory | undefined; + let model: BaseChatModel | undefined; + + const languageModelNodes = await this.getInputConnectionData(0, 0, 'languageModel'); + languageModelNodes.forEach((connectedNode) => { + if (!connectedNode.parameters.enabled) { + return; + } + + if (connectedNode.type === 'n8n-nodes-base.langChainLMOpenAi') { + const apiKey = get(connectedNode, 'credentials.openAiApi.apiKey', ''); + + const modelName = get(connectedNode, 'parameters.model', '') as string; + const temperature = get(connectedNode, 'parameters.temperature', 0) as number; + + model = new ChatOpenAI({ + openAIApiKey: apiKey as string, + modelName, + temperature, + }); + } + }); + + if (!model) { + throw new NodeOperationError(this.getNode(), 'No language model defined'); + } const memoryNodes = await this.getInputConnectionData(0, 0, 'memory'); memoryNodes.forEach((connectedNode) => { + if (!connectedNode.parameters.enabled) { + return; + } + if (connectedNode.type === 'n8n-nodes-base.langChainMemoryMotorhead') { const url = get(connectedNode, 'credentials.motorheadApi.host', '') as string; const clientId = get(connectedNode, 'credentials.motorheadApi.clientId'); @@ -82,6 +107,7 @@ export class LangChain implements INodeType { if (!connectedNode.parameters.enabled) { return; } + if (connectedNode.type === 'n8n-nodes-base.langChainToolCalculator') { tools.push(new Calculator()); } else if (connectedNode.type === 'n8n-nodes-base.langChainToolSerpApi') { @@ -95,14 +121,6 @@ export class LangChain implements INodeType { } }); - const credentials = await this.getCredentials('openAiApi'); - - const model = new ChatOpenAI({ - openAIApiKey: credentials.apiKey as string, - modelName: 'gpt-3.5-turbo', - temperature: 0, - }); - const chain = new ConversationChain({ llm: model, memory }); const items = this.getInputData(); diff --git a/packages/nodes-base/nodes/LangChain/LangChainLMOpenAi.node.ts b/packages/nodes-base/nodes/LangChain/LangChainLMOpenAi.node.ts new file mode 100644 index 0000000000000..711baa04e3dc5 --- /dev/null +++ b/packages/nodes-base/nodes/LangChain/LangChainLMOpenAi.node.ts @@ -0,0 +1,77 @@ +/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ +import type { + IExecuteFunctions, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +export class LangChainLMOpenAi implements INodeType { + description: INodeTypeDescription = { + displayName: 'LangChain - OpenAI', + // eslint-disable-next-line n8n-nodes-base/node-class-description-name-miscased + name: 'langChainLMOpenAi', + icon: 'file:openAi.svg', + group: ['transform'], + version: 1, + description: 'Language Model OpenAI', + defaults: { + name: 'LangChain - OpenAI', + }, + // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node + inputs: [], + // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong + outputs: ['languageModel'], + outputNames: ['Language Model'], + credentials: [ + { + name: 'openAiApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Model', + name: 'model', + type: 'options', + noDataExpression: true, + options: [ + { + name: 'GTP 3.5 Turbo', + value: 'gpt-3.5-turbo', + }, + { + name: 'DaVinci-003', + value: 'text-davinci-003', + }, + ], + default: 'gpt-3.5-turbo', + }, + { + displayName: 'Sampling Temperature', + name: 'temperature', + default: 0.7, + typeOptions: { maxValue: 1, minValue: 0, numberPrecision: 1 }, + description: + 'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive.', + type: 'number', + routing: { + send: { + type: 'body', + property: 'temperature', + }, + }, + }, + { + displayName: 'Enabled', + name: 'enabled', + type: 'boolean', + default: true, + }, + ], + }; + + async execute(this: IExecuteFunctions): Promise { + return []; + } +} diff --git a/packages/nodes-base/nodes/LangChain/openAi.svg b/packages/nodes-base/nodes/LangChain/openAi.svg new file mode 100644 index 0000000000000..d9445723fffb7 --- /dev/null +++ b/packages/nodes-base/nodes/LangChain/openAi.svg @@ -0,0 +1,7 @@ + + + OpenAI + + + + diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index d6e165e09f1fd..3252d037d6fa5 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -571,6 +571,7 @@ "dist/nodes/KoBoToolbox/KoBoToolbox.node.js", "dist/nodes/KoBoToolbox/KoBoToolboxTrigger.node.js", "dist/nodes/LangChain/LangChain.node.js", + "dist/nodes/LangChain/LangChainLMOpenAi.node.js", "dist/nodes/LangChain/LangChainMemoryMotorhead.node.js", "dist/nodes/LangChain/LangChainToolCalculator.node.js", "dist/nodes/LangChain/LangChainToolSerpApi.node.js", diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 4059a4404e715..f0be141b5de5a 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -1467,7 +1467,7 @@ export interface IPostReceiveSort extends IPostReceiveBase { }; } -export type ConnectionTypes = 'main' | 'memory' | 'tool'; +export type ConnectionTypes = 'languageModel' | 'main' | 'memory' | 'tool'; export interface INodeTypeDescription extends INodeTypeBaseDescription { version: number | number[]; From c6dbe58d8d55b93275280769ce428c36adfb208d Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 12 Aug 2023 20:35:06 +0200 Subject: [PATCH 017/372] :zap: Add HuggingFace --- .../credentials/HuggingFaceApi.credentials.ts | 41 +++++++++++ .../nodes/LangChain/LangChain.node.ts | 38 ++++++++-- ...angChainLMOpenHuggingFaceInference.node.ts | 73 +++++++++++++++++++ .../nodes/LangChain/huggingface.svg | 8 ++ packages/nodes-base/package.json | 5 +- 5 files changed, 157 insertions(+), 8 deletions(-) create mode 100644 packages/nodes-base/credentials/HuggingFaceApi.credentials.ts create mode 100644 packages/nodes-base/nodes/LangChain/LangChainLMOpenHuggingFaceInference.node.ts create mode 100644 packages/nodes-base/nodes/LangChain/huggingface.svg diff --git a/packages/nodes-base/credentials/HuggingFaceApi.credentials.ts b/packages/nodes-base/credentials/HuggingFaceApi.credentials.ts new file mode 100644 index 0000000000000..ed9a8f30f549a --- /dev/null +++ b/packages/nodes-base/credentials/HuggingFaceApi.credentials.ts @@ -0,0 +1,41 @@ +import type { + IAuthenticateGeneric, + ICredentialTestRequest, + ICredentialType, + INodeProperties, +} from 'n8n-workflow'; + +export class HuggingFaceApi implements ICredentialType { + name = 'huggingFaceApi'; + + displayName = 'HuggingFaceApi'; + + documentationUrl = 'huggingFaceApi'; + + properties: INodeProperties[] = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string', + typeOptions: { password: true }, + required: true, + default: '', + }, + ]; + + authenticate: IAuthenticateGeneric = { + type: 'generic', + properties: { + headers: { + Authorization: '=Bearer {{$credentials.apiKey}}', + }, + }, + }; + + test: ICredentialTestRequest = { + request: { + baseURL: 'https://api-inference.huggingface.co', + url: '/models/gpt2', + }, + }; +} diff --git a/packages/nodes-base/nodes/LangChain/LangChain.node.ts b/packages/nodes-base/nodes/LangChain/LangChain.node.ts index cc499018ca12e..25a83ced55264 100644 --- a/packages/nodes-base/nodes/LangChain/LangChain.node.ts +++ b/packages/nodes-base/nodes/LangChain/LangChain.node.ts @@ -10,13 +10,15 @@ import { NodeOperationError } from 'n8n-workflow'; import get from 'lodash/get'; import { ChatOpenAI } from 'langchain/chat_models/openai'; -import type { StructuredTool } from 'langchain/tools'; +import type { Tool } from 'langchain/tools'; import { SerpAPI, WikipediaQueryRun } from 'langchain/tools'; import { Calculator } from 'langchain/tools/calculator'; import type { BaseChatMemory } from 'langchain/memory'; import { MotorheadMemory } from 'langchain/memory'; -import { ConversationChain } from 'langchain/chains'; -import { BaseChatModel } from 'langchain/dist/chat_models/base'; +import type { InitializeAgentExecutorOptions } from 'langchain/agents'; +import { initializeAgentExecutorWithOptions } from 'langchain/agents'; +import { HuggingFaceInference } from 'langchain/llms/hf'; +import type { BaseLanguageModel } from 'langchain/dist/base_language'; export class LangChain implements INodeType { description: INodeTypeDescription = { @@ -46,9 +48,9 @@ export class LangChain implements INodeType { }; async execute(this: IExecuteFunctions): Promise { - const tools: StructuredTool[] = []; + const tools: Tool[] = []; let memory: BaseChatMemory | undefined; - let model: BaseChatModel | undefined; + let model: BaseLanguageModel | undefined; const languageModelNodes = await this.getInputConnectionData(0, 0, 'languageModel'); languageModelNodes.forEach((connectedNode) => { @@ -67,6 +69,18 @@ export class LangChain implements INodeType { modelName, temperature, }); + } else if (connectedNode.type === 'n8n-nodes-base.langChainLMOpenHuggingFaceInference') { + const apiKey = get(connectedNode, 'credentials.huggingFaceApi.apiKey', ''); + + const modelName = get(connectedNode, 'parameters.model', '') as string; + const temperature = get(connectedNode, 'parameters.temperature', 0) as number; + + model = new HuggingFaceInference({ + model: modelName, + apiKey, + temperature, + maxTokens: 100, + }); } }); @@ -121,13 +135,23 @@ export class LangChain implements INodeType { } }); - const chain = new ConversationChain({ llm: model, memory }); + const options: InitializeAgentExecutorOptions = { + agentType: 'chat-conversational-react-description', + verbose: true, + memory, + }; + + const executor = await initializeAgentExecutorWithOptions(tools, model, options); const items = this.getInputData(); const returnData: INodeExecutionData[] = []; for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { const text = this.getNodeParameter('text', itemIndex) as string; - const response = await chain.call({ input: text }); + + const response = await executor.call({ + input: text, + }); + returnData.push({ json: { response } }); } return this.prepareOutputData(returnData); diff --git a/packages/nodes-base/nodes/LangChain/LangChainLMOpenHuggingFaceInference.node.ts b/packages/nodes-base/nodes/LangChain/LangChainLMOpenHuggingFaceInference.node.ts new file mode 100644 index 0000000000000..f7520d6e7c513 --- /dev/null +++ b/packages/nodes-base/nodes/LangChain/LangChainLMOpenHuggingFaceInference.node.ts @@ -0,0 +1,73 @@ +/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ +import type { + IExecuteFunctions, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +export class LangChainLMOpenHuggingFaceInference implements INodeType { + description: INodeTypeDescription = { + displayName: 'LangChain - HuggingFaceInference', + // eslint-disable-next-line n8n-nodes-base/node-class-description-name-miscased + name: 'langChainLMOpenHuggingFaceInference', + icon: 'file:huggingface.svg', + group: ['transform'], + version: 1, + description: 'Language Model HuggingFaceInference', + defaults: { + name: 'LangChain - HuggingFaceInference', + }, + // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node + inputs: [], + // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong + outputs: ['languageModel'], + outputNames: ['Language Model'], + credentials: [ + { + name: 'huggingFaceApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Model', + name: 'model', + type: 'options', + noDataExpression: true, + options: [ + { + name: 'GTP 2', + value: 'gpt2', + }, + ], + default: 'gpt2', + }, + { + displayName: 'Sampling Temperature', + name: 'temperature', + default: 0.7, + typeOptions: { maxValue: 1, minValue: 0, numberPrecision: 1 }, + description: + 'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive.', + type: 'number', + routing: { + send: { + type: 'body', + property: 'temperature', + }, + }, + }, + { + displayName: 'Enabled', + name: 'enabled', + type: 'boolean', + default: true, + }, + ], + }; + + async execute(this: IExecuteFunctions): Promise { + return []; + } +} diff --git a/packages/nodes-base/nodes/LangChain/huggingface.svg b/packages/nodes-base/nodes/LangChain/huggingface.svg new file mode 100644 index 0000000000000..ab959d165fa5b --- /dev/null +++ b/packages/nodes-base/nodes/LangChain/huggingface.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 3252d037d6fa5..187f8bc42b545 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -168,6 +168,7 @@ "dist/credentials/HubspotAppToken.credentials.js", "dist/credentials/HubspotDeveloperApi.credentials.js", "dist/credentials/HubspotOAuth2Api.credentials.js", + "dist/credentials/HuggingFaceApi.credentials.js", "dist/credentials/HumanticAiApi.credentials.js", "dist/credentials/HunterApi.credentials.js", "dist/credentials/HybridAnalysisApi.credentials.js", @@ -572,6 +573,7 @@ "dist/nodes/KoBoToolbox/KoBoToolboxTrigger.node.js", "dist/nodes/LangChain/LangChain.node.js", "dist/nodes/LangChain/LangChainLMOpenAi.node.js", + "dist/nodes/LangChain/LangChainLMOpenHuggingFaceInference.node.js", "dist/nodes/LangChain/LangChainMemoryMotorhead.node.js", "dist/nodes/LangChain/LangChainToolCalculator.node.js", "dist/nodes/LangChain/LangChainToolSerpApi.node.js", @@ -818,6 +820,7 @@ "n8n-core": "workspace:*" }, "dependencies": { + "@huggingface/inference": "1", "@kafkajs/confluent-schema-registry": "1.0.6", "amqplib": "^0.10.3", "aws4": "^1.8.0", @@ -880,4 +883,4 @@ "xlsx": "https://cdn.sheetjs.com/xlsx-0.19.3/xlsx-0.19.3.tgz", "xml2js": "^0.5.0" } -} \ No newline at end of file +} From 81a5c1fe0ba71392a3cf56a469bd858d40d5e3db Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 13 Aug 2023 19:59:24 +0200 Subject: [PATCH 018/372] :zap: Finally move logic to nodes --- packages/core/src/NodeExecuteFunctions.ts | 114 ++++++++++-------- .../nodes/LangChain/LangChain.node.ts | 114 +++++------------- .../nodes/LangChain/LangChainLMOpenAi.node.ts | 34 +++--- ...angChainLMOpenHuggingFaceInference.node.ts | 36 +++--- .../LangChainMemoryMotorhead.node.ts | 36 +++--- .../LangChain/LangChainToolCalculator.node.ts | 23 ++-- .../LangChain/LangChainToolSerpApi.node.ts | 26 ++-- .../LangChain/LangChainToolWikipedia.node.ts | 23 ++-- packages/workflow/src/Interfaces.ts | 8 +- pnpm-lock.yaml | 42 ++++--- 10 files changed, 214 insertions(+), 242 deletions(-) diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index b315c2b7c099d..ffcc234ca66a0 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -62,7 +62,7 @@ import type { BinaryMetadata, FileSystemHelperFunctions, INodeType, - INodeParameters, + SupplyData, } from 'n8n-workflow'; import { createDeferredPromise, @@ -2579,66 +2579,76 @@ export function getExecuteFunctions( // TODO: Not implemented yet, and maybe also not needed inputIndex?: number, inputName?: ConnectionTypes, - ): Promise { - if (inputName === 'main') { - throw new Error( - 'It is not allowed to query data from nodes connected through the main input!', - ); - } - + ): Promise { const parentNodes = workflow.getParentNodes(node.name, inputName, 1); - if (parentNodes.length === 0) { return []; } - const constParentNodes = parentNodes.map(async (nodeName) => { - const connectedNode = workflow.getNode(nodeName) as INode; - - // Resolve parameters on node within the context of the current node - const parameters = workflow.expression.getParameterValue( - connectedNode.parameters, - runExecutionData, - runIndex, - itemIndex, - node.name, - connectionInputData, - mode, - additionalData.timezone, - getAdditionalKeys(additionalData, mode, runExecutionData), - executeData, - ) as INodeParameters; + console.log('new..'); + const constParentNodes = parentNodes + .map((nodeName) => { + return workflow.getNode(nodeName) as INode; + }) + .filter((connectedNode) => connectedNode.disabled !== true) + .map(async (connectedNode) => { + const nodeType = workflow.nodeTypes.getByNameAndVersion( + connectedNode.type, + connectedNode.typeVersion, + ); - const credentials: { - [key: string]: ICredentialDataDecryptedObject; - } = {}; + if (!nodeType.supplyData) { + throw new Error( + `The node "${connectedNode.name}" does not have a "supplyData method defined!"`, + ); + } + + const context = Object.assign({}, this); + + // @ts-ignore + context.getNodeParameter = ( + parameterName: string, + itemIndex: number, + fallbackValue?: any, + options?: IGetNodeParameterOptions, + ) => { + return getNodeParameter( + workflow, + runExecutionData, + runIndex, + connectionInputData, + connectedNode, + parameterName, + itemIndex, + mode, + additionalData.timezone, + getAdditionalKeys(additionalData, mode, runExecutionData), + executeData, + fallbackValue, + options, + ); + }; - if (!connectedNode?.credentials) { - return { - ...connectedNode, - parameters, + // TODO: Check what else should be overwritten + context.getCredentials = async (key: string) => { + console.log('getCredentials - overwritten:', key); + console.log('connectedNode.name:', connectedNode.name); + + return getCredentials( + workflow, + connectedNode, + key, + additionalData, + mode, + runExecutionData, + runIndex, + connectionInputData, + itemIndex, + ); }; - } - for (const key of Object.keys(connectedNode?.credentials)) { - credentials[key] = await getCredentials( - workflow, - connectedNode, - key, - additionalData, - mode, - runExecutionData, - runIndex, - connectionInputData, - itemIndex, - ); - } - return { - ...connectedNode, - credentials, - parameters, - } as unknown as INode; - }); + return nodeType.supplyData.call(context); + }); return Promise.all(constParentNodes); }, diff --git a/packages/nodes-base/nodes/LangChain/LangChain.node.ts b/packages/nodes-base/nodes/LangChain/LangChain.node.ts index 25a83ced55264..a60eea5dabb1b 100644 --- a/packages/nodes-base/nodes/LangChain/LangChain.node.ts +++ b/packages/nodes-base/nodes/LangChain/LangChain.node.ts @@ -7,17 +7,10 @@ import type { } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow'; -import get from 'lodash/get'; - -import { ChatOpenAI } from 'langchain/chat_models/openai'; import type { Tool } from 'langchain/tools'; -import { SerpAPI, WikipediaQueryRun } from 'langchain/tools'; -import { Calculator } from 'langchain/tools/calculator'; import type { BaseChatMemory } from 'langchain/memory'; -import { MotorheadMemory } from 'langchain/memory'; import type { InitializeAgentExecutorOptions } from 'langchain/agents'; import { initializeAgentExecutorWithOptions } from 'langchain/agents'; -import { HuggingFaceInference } from 'langchain/llms/hf'; import type { BaseLanguageModel } from 'langchain/dist/base_language'; export class LangChain implements INodeType { @@ -48,91 +41,44 @@ export class LangChain implements INodeType { }; async execute(this: IExecuteFunctions): Promise { - const tools: Tool[] = []; let memory: BaseChatMemory | undefined; - let model: BaseLanguageModel | undefined; const languageModelNodes = await this.getInputConnectionData(0, 0, 'languageModel'); - languageModelNodes.forEach((connectedNode) => { - if (!connectedNode.parameters.enabled) { - return; - } - - if (connectedNode.type === 'n8n-nodes-base.langChainLMOpenAi') { - const apiKey = get(connectedNode, 'credentials.openAiApi.apiKey', ''); - - const modelName = get(connectedNode, 'parameters.model', '') as string; - const temperature = get(connectedNode, 'parameters.temperature', 0) as number; - - model = new ChatOpenAI({ - openAIApiKey: apiKey as string, - modelName, - temperature, - }); - } else if (connectedNode.type === 'n8n-nodes-base.langChainLMOpenHuggingFaceInference') { - const apiKey = get(connectedNode, 'credentials.huggingFaceApi.apiKey', ''); - - const modelName = get(connectedNode, 'parameters.model', '') as string; - const temperature = get(connectedNode, 'parameters.temperature', 0) as number; - - model = new HuggingFaceInference({ - model: modelName, - apiKey, - temperature, - maxTokens: 100, - }); - } - }); - - if (!model) { - throw new NodeOperationError(this.getNode(), 'No language model defined'); + if (languageModelNodes.length === 0) { + throw new NodeOperationError( + this.getNode(), + 'At least one Language Model has to be connected!', + ); + } else if (languageModelNodes.length > 1) { + throw new NodeOperationError( + this.getNode(), + 'Only one Language Model is allowed to be connected!', + ); + } + const model = languageModelNodes[0].response as BaseLanguageModel; + + if (languageModelNodes.length === 0) { + throw new NodeOperationError( + this.getNode(), + 'At least one Language Model has to be connected!', + ); + } else if (languageModelNodes.length > 1) { + throw new NodeOperationError( + this.getNode(), + 'Only one Language Model is allowed to be connected!', + ); } const memoryNodes = await this.getInputConnectionData(0, 0, 'memory'); - memoryNodes.forEach((connectedNode) => { - if (!connectedNode.parameters.enabled) { - return; - } - - if (connectedNode.type === 'n8n-nodes-base.langChainMemoryMotorhead') { - const url = get(connectedNode, 'credentials.motorheadApi.host', '') as string; - const clientId = get(connectedNode, 'credentials.motorheadApi.clientId'); - const apiKey = get(connectedNode, 'credentials.motorheadApi.apiKey'); - - const memoryKey = get(connectedNode, 'parameters.memoryKey', '') as string; - const sessionId = get(connectedNode, 'parameters.sessionId', '') as string; - - // TODO: Does not work yet - memory = new MotorheadMemory({ - memoryKey, - sessionId, - url, - clientId, - apiKey, - }); - } - }); + if (memoryNodes.length === 1) { + memory = memoryNodes[0].response as BaseChatMemory; + } else if (languageModelNodes.length > 1) { + throw new NodeOperationError(this.getNode(), 'Only one Memory is allowed to be connected!'); + } const toolNodes = await this.getInputConnectionData(0, 0, 'tool'); - - // TODO: Should later find way to move that logic to the nodes again - // but at the same time keep maxium flexibility - toolNodes.forEach((connectedNode) => { - if (!connectedNode.parameters.enabled) { - return; - } - - if (connectedNode.type === 'n8n-nodes-base.langChainToolCalculator') { - tools.push(new Calculator()); - } else if (connectedNode.type === 'n8n-nodes-base.langChainToolSerpApi') { - const apiKey = get(connectedNode, 'credentials.serpApi.apiKey'); - if (!apiKey) { - throw new NodeOperationError(this.getNode(), 'SerpAPI API key missing'); - } - tools.push(new SerpAPI(apiKey as string)); - } else if (connectedNode.type === 'n8n-nodes-base.langChainToolWikipedia') { - tools.push(new WikipediaQueryRun()); - } + const tools = toolNodes.map((connectedNode) => { + return connectedNode.response as Tool; }); const options: InitializeAgentExecutorOptions = { diff --git a/packages/nodes-base/nodes/LangChain/LangChainLMOpenAi.node.ts b/packages/nodes-base/nodes/LangChain/LangChainLMOpenAi.node.ts index 711baa04e3dc5..708bcc6e2ad84 100644 --- a/packages/nodes-base/nodes/LangChain/LangChainLMOpenAi.node.ts +++ b/packages/nodes-base/nodes/LangChain/LangChainLMOpenAi.node.ts @@ -1,11 +1,7 @@ /* eslint-disable n8n-nodes-base/node-dirname-against-convention */ -import type { - IExecuteFunctions, - INodeExecutionData, - INodeType, - INodeTypeDescription, -} from 'n8n-workflow'; +import type { IExecuteFunctions, INodeType, INodeTypeDescription, SupplyData } from 'n8n-workflow'; +import { ChatOpenAI } from 'langchain/chat_models/openai'; export class LangChainLMOpenAi implements INodeType { description: INodeTypeDescription = { displayName: 'LangChain - OpenAI', @@ -62,16 +58,26 @@ export class LangChainLMOpenAi implements INodeType { }, }, }, - { - displayName: 'Enabled', - name: 'enabled', - type: 'boolean', - default: true, - }, ], }; - async execute(this: IExecuteFunctions): Promise { - return []; + async supplyData(this: IExecuteFunctions): Promise { + const credentials = await this.getCredentials('openAiApi'); + + const itemIndex = 0; + + // TODO: Should it get executed once per item or not? + const modelName = this.getNodeParameter('model', itemIndex) as string; + const temperature = this.getNodeParameter('temperature', itemIndex) as number; + + const model = new ChatOpenAI({ + openAIApiKey: credentials.apiKey as string, + modelName, + temperature, + }); + + return { + response: model, + }; } } diff --git a/packages/nodes-base/nodes/LangChain/LangChainLMOpenHuggingFaceInference.node.ts b/packages/nodes-base/nodes/LangChain/LangChainLMOpenHuggingFaceInference.node.ts index f7520d6e7c513..385d098a9e195 100644 --- a/packages/nodes-base/nodes/LangChain/LangChainLMOpenHuggingFaceInference.node.ts +++ b/packages/nodes-base/nodes/LangChain/LangChainLMOpenHuggingFaceInference.node.ts @@ -1,10 +1,7 @@ /* eslint-disable n8n-nodes-base/node-dirname-against-convention */ -import type { - IExecuteFunctions, - INodeExecutionData, - INodeType, - INodeTypeDescription, -} from 'n8n-workflow'; +import type { IExecuteFunctions, INodeType, INodeTypeDescription, SupplyData } from 'n8n-workflow'; + +import { HuggingFaceInference } from 'langchain/llms/hf'; export class LangChainLMOpenHuggingFaceInference implements INodeType { description: INodeTypeDescription = { @@ -58,16 +55,27 @@ export class LangChainLMOpenHuggingFaceInference implements INodeType { }, }, }, - { - displayName: 'Enabled', - name: 'enabled', - type: 'boolean', - default: true, - }, ], }; - async execute(this: IExecuteFunctions): Promise { - return []; + async supplyData(this: IExecuteFunctions): Promise { + const credentials = await this.getCredentials('huggingFaceApi'); + + const itemIndex = 0; + + // TODO: Should it get executed once per item or not? + const modelName = this.getNodeParameter('model', itemIndex) as string; + const temperature = this.getNodeParameter('temperature', itemIndex) as number; + + const model = new HuggingFaceInference({ + model: modelName, + apiKey: credentials.apiKey as string, + temperature, + maxTokens: 100, + }); + + return { + response: model, + }; } } diff --git a/packages/nodes-base/nodes/LangChain/LangChainMemoryMotorhead.node.ts b/packages/nodes-base/nodes/LangChain/LangChainMemoryMotorhead.node.ts index 55ff34a1ca925..01de2cba032bf 100644 --- a/packages/nodes-base/nodes/LangChain/LangChainMemoryMotorhead.node.ts +++ b/packages/nodes-base/nodes/LangChain/LangChainMemoryMotorhead.node.ts @@ -1,10 +1,7 @@ /* eslint-disable n8n-nodes-base/node-dirname-against-convention */ -import type { - IExecuteFunctions, - INodeExecutionData, - INodeType, - INodeTypeDescription, -} from 'n8n-workflow'; +import type { IExecuteFunctions, INodeType, INodeTypeDescription, SupplyData } from 'n8n-workflow'; + +import { MotorheadMemory } from 'langchain/memory'; export class LangChainMemoryMotorhead implements INodeType { description: INodeTypeDescription = { @@ -42,16 +39,27 @@ export class LangChainMemoryMotorhead implements INodeType { type: 'string', default: '', }, - { - displayName: 'Enabled', - name: 'enabled', - type: 'boolean', - default: true, - }, ], }; - async execute(this: IExecuteFunctions): Promise { - return []; + async supplyData(this: IExecuteFunctions): Promise { + const credentials = await this.getCredentials('motorheadApi'); + + const itemIndex = 0; + + // TODO: Should it get executed once per item or not? + const memoryKey = this.getNodeParameter('memoryKey', itemIndex) as string; + const sessionId = this.getNodeParameter('sessionId', itemIndex) as string; + + return { + // TODO: Does not work yet + response: new MotorheadMemory({ + memoryKey, + sessionId, + url: credentials.host as string, + clientId: credentials.clientId as string, + apiKey: credentials.apiKey as string, + }), + }; } } diff --git a/packages/nodes-base/nodes/LangChain/LangChainToolCalculator.node.ts b/packages/nodes-base/nodes/LangChain/LangChainToolCalculator.node.ts index 1d8a34e9c3a2d..6a03748624d05 100644 --- a/packages/nodes-base/nodes/LangChain/LangChainToolCalculator.node.ts +++ b/packages/nodes-base/nodes/LangChain/LangChainToolCalculator.node.ts @@ -1,10 +1,6 @@ /* eslint-disable n8n-nodes-base/node-dirname-against-convention */ -import type { - IExecuteFunctions, - INodeExecutionData, - INodeType, - INodeTypeDescription, -} from 'n8n-workflow'; +import type { IExecuteFunctions, INodeType, INodeTypeDescription, SupplyData } from 'n8n-workflow'; +import { Calculator } from 'langchain/tools/calculator'; export class LangChainToolCalculator implements INodeType { description: INodeTypeDescription = { @@ -23,17 +19,12 @@ export class LangChainToolCalculator implements INodeType { // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong outputs: ['tool'], outputNames: ['Tool'], - properties: [ - { - displayName: 'Enabled', - name: 'enabled', - type: 'boolean', - default: true, - }, - ], + properties: [], }; - async execute(this: IExecuteFunctions): Promise { - return []; + async supplyData(this: IExecuteFunctions): Promise { + return { + response: new Calculator(), + }; } } diff --git a/packages/nodes-base/nodes/LangChain/LangChainToolSerpApi.node.ts b/packages/nodes-base/nodes/LangChain/LangChainToolSerpApi.node.ts index ba0291d897365..37b271f13b51c 100644 --- a/packages/nodes-base/nodes/LangChain/LangChainToolSerpApi.node.ts +++ b/packages/nodes-base/nodes/LangChain/LangChainToolSerpApi.node.ts @@ -1,10 +1,7 @@ /* eslint-disable n8n-nodes-base/node-dirname-against-convention */ -import type { - IExecuteFunctions, - INodeExecutionData, - INodeType, - INodeTypeDescription, -} from 'n8n-workflow'; +import type { IExecuteFunctions, INodeType, INodeTypeDescription, SupplyData } from 'n8n-workflow'; + +import { SerpAPI } from 'langchain/tools'; export class LangChainToolSerpApi implements INodeType { description: INodeTypeDescription = { @@ -30,17 +27,14 @@ export class LangChainToolSerpApi implements INodeType { required: true, }, ], - properties: [ - { - displayName: 'Enabled', - name: 'enabled', - type: 'boolean', - default: true, - }, - ], + properties: [], }; - async execute(this: IExecuteFunctions): Promise { - return []; + async supplyData(this: IExecuteFunctions): Promise { + const credentials = await this.getCredentials('serpApi'); + + return { + response: new SerpAPI(credentials.apiKey as string), + }; } } diff --git a/packages/nodes-base/nodes/LangChain/LangChainToolWikipedia.node.ts b/packages/nodes-base/nodes/LangChain/LangChainToolWikipedia.node.ts index a36feb274485a..3224a915c55ef 100644 --- a/packages/nodes-base/nodes/LangChain/LangChainToolWikipedia.node.ts +++ b/packages/nodes-base/nodes/LangChain/LangChainToolWikipedia.node.ts @@ -1,10 +1,6 @@ /* eslint-disable n8n-nodes-base/node-dirname-against-convention */ -import type { - IExecuteFunctions, - INodeExecutionData, - INodeType, - INodeTypeDescription, -} from 'n8n-workflow'; +import type { IExecuteFunctions, INodeType, INodeTypeDescription, SupplyData } from 'n8n-workflow'; +import { WikipediaQueryRun } from 'langchain/tools'; export class LangChainToolWikipedia implements INodeType { description: INodeTypeDescription = { @@ -24,17 +20,12 @@ export class LangChainToolWikipedia implements INodeType { // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong outputs: ['tool'], outputNames: ['Tool'], - properties: [ - { - displayName: 'Enabled', - name: 'enabled', - type: 'boolean', - default: true, - }, - ], + properties: [], }; - async execute(this: IExecuteFunctions): Promise { - return []; + async supplyData(this: IExecuteFunctions): Promise { + return { + response: new WikipediaQueryRun(), + }; } } diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index f0be141b5de5a..203be4493d38f 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -765,7 +765,7 @@ export type IExecuteFunctions = ExecuteFunctions.GetNodeParameterFn & itemIndex: number, inputIndex?: number, inputName?: ConnectionTypes, - ): Promise; + ): Promise; getInputData(inputIndex?: number, inputName?: string): INodeExecutionData[]; prepareOutputData( outputData: INodeExecutionData[], @@ -1257,8 +1257,14 @@ export namespace MultiPartFormData { >; } +export interface SupplyData { + metadata?: IDataObject; + response: any; +} + export interface INodeType { description: INodeTypeDescription; + supplyData?(this: IExecuteFunctions): Promise; execute?( this: IExecuteFunctions, ): Promise; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7be8a071380ce..1e7377319a5be 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -135,7 +135,7 @@ importers: dependencies: axios: specifier: ^0.21.1 - version: 0.21.4 + version: 0.21.4(debug@4.3.2) packages/@n8n_io/eslint-config: devDependencies: @@ -216,7 +216,7 @@ importers: version: 7.28.1 axios: specifier: ^0.21.1 - version: 0.21.4 + version: 0.21.4(debug@4.3.2) basic-auth: specifier: ^2.0.1 version: 2.0.1 @@ -574,7 +574,7 @@ importers: version: link:../@n8n/client-oauth2 axios: specifier: ^0.21.1 - version: 0.21.4 + version: 0.21.4(debug@4.3.2) concat-stream: specifier: ^2.0.0 version: 2.0.0 @@ -834,7 +834,7 @@ importers: version: 10.2.0(vue@3.3.4) axios: specifier: ^0.21.1 - version: 0.21.4 + version: 0.21.4(debug@4.3.2) codemirror-lang-html-n8n: specifier: ^1.0.0 version: 1.0.0 @@ -993,6 +993,9 @@ importers: packages/nodes-base: dependencies: + '@huggingface/inference': + specifier: '1' + version: 1.8.0 '@kafkajs/confluent-schema-registry': specifier: 1.0.6 version: 1.0.6 @@ -1061,7 +1064,7 @@ importers: version: 1.16.0 langchain: specifier: ^0.0.126 - version: 0.0.126(cheerio@1.0.0-rc.6)(mongodb@4.10.0)(mysql2@2.3.3)(pg@8.8.0)(redis@3.1.2) + version: 0.0.126(@huggingface/inference@1.8.0)(cheerio@1.0.0-rc.6)(mongodb@4.10.0)(mysql2@2.3.3)(pg@8.8.0)(redis@3.1.2) ldapts: specifier: ^4.2.6 version: 4.2.6 @@ -3564,6 +3567,11 @@ packages: '@hapi/hoek': 9.3.0 dev: true + /@huggingface/inference@1.8.0: + resolution: {integrity: sha512-Dkh7PiyMf6TINRocQsdceiR5LcqJiUHgWjaBMRpCUOCbs+GZA122VH9q+wodoSptj6rIQf7wIwtDsof+/gd0WA==} + engines: {node: '>=18'} + dev: false + /@humanwhocodes/config-array@0.11.10: resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==} engines: {node: '>=10.10.0'} @@ -4473,7 +4481,7 @@ packages: dependencies: '@segment/loosely-validate-event': 2.0.0 auto-changelog: 1.16.4 - axios: 0.21.4 + axios: 0.21.4(debug@4.3.2) axios-retry: 3.3.1 bull: 3.29.3 lodash.clonedeep: 4.5.0 @@ -8170,26 +8178,27 @@ packages: is-retry-allowed: 2.2.0 dev: false - /axios@0.21.4: + /axios@0.21.4(debug@4.3.2): resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} dependencies: - follow-redirects: 1.15.2(debug@4.3.4) + follow-redirects: 1.15.2(debug@4.3.2) transitivePeerDependencies: - debug dev: false - /axios@0.21.4(debug@4.3.2): - resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} + /axios@0.26.1: + resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==} dependencies: follow-redirects: 1.15.2(debug@4.3.2) transitivePeerDependencies: - debug dev: false - /axios@0.26.1: - resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==} + /axios@0.27.2: + resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==} dependencies: - follow-redirects: 1.15.2(debug@4.3.4) + follow-redirects: 1.15.2(debug@4.3.2) + form-data: 4.0.0 transitivePeerDependencies: - debug dev: false @@ -8210,6 +8219,7 @@ packages: form-data: 4.0.0 transitivePeerDependencies: - debug + dev: true /babel-core@7.0.0-bridge.0(@babel/core@7.22.9): resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==} @@ -11815,6 +11825,7 @@ packages: optional: true dependencies: debug: 4.3.4(supports-color@8.1.1) + dev: true /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -14591,7 +14602,7 @@ packages: resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} dev: false - /langchain@0.0.126(cheerio@1.0.0-rc.6)(mongodb@4.10.0)(mysql2@2.3.3)(pg@8.8.0)(redis@3.1.2): + /langchain@0.0.126(@huggingface/inference@1.8.0)(cheerio@1.0.0-rc.6)(mongodb@4.10.0)(mysql2@2.3.3)(pg@8.8.0)(redis@3.1.2): resolution: {integrity: sha512-Z57WjAwLN+ZbJ9D4h2oTN7iDt1Aa67mcXhKYVCQFRu4w8PIPH5k0VBvmrzKyRj2iFIWdUaqVsHch/jz5+1mGnA==} engines: {node: '>=18'} peerDependencies: @@ -14787,6 +14798,7 @@ packages: optional: true dependencies: '@anthropic-ai/sdk': 0.5.10 + '@huggingface/inference': 1.8.0 ansi-styles: 5.2.0 binary-extensions: 2.2.0 camelcase: 6.3.0 @@ -17514,7 +17526,7 @@ packages: resolution: {integrity: sha512-aXYe/D+28kF63W8Cz53t09ypEORz+ULeDCahdAqhVrRm2scbOXFbtnn0GGhvMpYe45grepLKuwui9KxrZ2ZuMw==} engines: {node: '>=14.17.0'} dependencies: - axios: 0.27.2(debug@4.3.4) + axios: 0.27.2 transitivePeerDependencies: - debug dev: false From 7034fad6da9fb0f769532cfbaa325cc9bbd7e254 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 13 Aug 2023 22:27:11 +0200 Subject: [PATCH 019/372] :zap: Remove not needed code --- .../LangChain/LangChainLMOpenHuggingFaceInference.node.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/nodes-base/nodes/LangChain/LangChainLMOpenHuggingFaceInference.node.ts b/packages/nodes-base/nodes/LangChain/LangChainLMOpenHuggingFaceInference.node.ts index 385d098a9e195..761682cbf1e1d 100644 --- a/packages/nodes-base/nodes/LangChain/LangChainLMOpenHuggingFaceInference.node.ts +++ b/packages/nodes-base/nodes/LangChain/LangChainLMOpenHuggingFaceInference.node.ts @@ -48,12 +48,6 @@ export class LangChainLMOpenHuggingFaceInference implements INodeType { description: 'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive.', type: 'number', - routing: { - send: { - type: 'body', - property: 'temperature', - }, - }, }, ], }; From 5629f8523e4464d6c69438044a16a3f67b7035a4 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 13 Aug 2023 22:28:26 +0200 Subject: [PATCH 020/372] :zap: Add Cohere support (WIP) --- .../credentials/CohereApi.credentials.ts | 45 +++++++++ .../nodes/LangChain/LangChainLMCohere.node.ts | 57 ++++++++++++ .../nodes-base/nodes/LangChain/cohere.svg | 93 +++++++++++++++++++ packages/nodes-base/package.json | 3 + pnpm-lock.yaml | 12 ++- 5 files changed, 208 insertions(+), 2 deletions(-) create mode 100644 packages/nodes-base/credentials/CohereApi.credentials.ts create mode 100644 packages/nodes-base/nodes/LangChain/LangChainLMCohere.node.ts create mode 100644 packages/nodes-base/nodes/LangChain/cohere.svg diff --git a/packages/nodes-base/credentials/CohereApi.credentials.ts b/packages/nodes-base/credentials/CohereApi.credentials.ts new file mode 100644 index 0000000000000..7fab31306275f --- /dev/null +++ b/packages/nodes-base/credentials/CohereApi.credentials.ts @@ -0,0 +1,45 @@ +import type { + IAuthenticateGeneric, + ICredentialTestRequest, + ICredentialType, + INodeProperties, +} from 'n8n-workflow'; + +export class CohereApi implements ICredentialType { + name = 'cohereApi'; + + displayName = 'CohereApi'; + + documentationUrl = 'cohereApi'; + + properties: INodeProperties[] = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string', + typeOptions: { password: true }, + required: true, + default: '', + }, + ]; + + authenticate: IAuthenticateGeneric = { + type: 'generic', + properties: { + headers: { + Authorization: '=Bearer {{$credentials.apiKey}}', + }, + }, + }; + + test: ICredentialTestRequest = { + request: { + baseURL: 'https://api.cohere.ai', + url: '/v1/detect-language', + method: 'POST', + body: { + texts: ['hello'], + }, + }, + }; +} diff --git a/packages/nodes-base/nodes/LangChain/LangChainLMCohere.node.ts b/packages/nodes-base/nodes/LangChain/LangChainLMCohere.node.ts new file mode 100644 index 0000000000000..4be61f91082d0 --- /dev/null +++ b/packages/nodes-base/nodes/LangChain/LangChainLMCohere.node.ts @@ -0,0 +1,57 @@ +/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ +import type { IExecuteFunctions, INodeType, INodeTypeDescription, SupplyData } from 'n8n-workflow'; + +import { Cohere } from 'langchain/llms/cohere'; + +export class LangChainLMCohere implements INodeType { + description: INodeTypeDescription = { + displayName: 'LangChain - Cohere', + // eslint-disable-next-line n8n-nodes-base/node-class-description-name-miscased + name: 'langChainLMCohere', + icon: 'file:cohere.svg', + group: ['transform'], + version: 1, + description: 'Language Model Cohere', + defaults: { + name: 'LangChain - Cohere', + }, + // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node + inputs: [], + // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong + outputs: ['languageModel'], + outputNames: ['Language Model'], + credentials: [ + { + name: 'cohereApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Maximum Tokens', + name: 'maxTokens', + default: 20, + typeOptions: { minValue: 0 }, + description: 'Maximum amount of tokens to use for the completion', + type: 'number', + }, + ], + }; + + async supplyData(this: IExecuteFunctions): Promise { + const credentials = await this.getCredentials('cohereApi'); + + const itemIndex = 0; + + const maxTokens = this.getNodeParameter('maxTokens', itemIndex) as number; + + const model = new Cohere({ + maxTokens, + apiKey: credentials.apiKey as string, + }); + + return { + response: model, + }; + } +} diff --git a/packages/nodes-base/nodes/LangChain/cohere.svg b/packages/nodes-base/nodes/LangChain/cohere.svg new file mode 100644 index 0000000000000..abe70db29b052 --- /dev/null +++ b/packages/nodes-base/nodes/LangChain/cohere.svg @@ -0,0 +1,93 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 187f8bc42b545..f6c3f579ce48e 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -78,6 +78,7 @@ "dist/credentials/ClockifyApi.credentials.js", "dist/credentials/CockpitApi.credentials.js", "dist/credentials/CodaApi.credentials.js", + "dist/credentials/CohereApi.credentials.js", "dist/credentials/ContentfulApi.credentials.js", "dist/credentials/ConvertKitApi.credentials.js", "dist/credentials/CopperApi.credentials.js", @@ -573,6 +574,7 @@ "dist/nodes/KoBoToolbox/KoBoToolboxTrigger.node.js", "dist/nodes/LangChain/LangChain.node.js", "dist/nodes/LangChain/LangChainLMOpenAi.node.js", + "dist/nodes/LangChain/LangChainLMCohere.node.js", "dist/nodes/LangChain/LangChainLMOpenHuggingFaceInference.node.js", "dist/nodes/LangChain/LangChainMemoryMotorhead.node.js", "dist/nodes/LangChain/LangChainToolCalculator.node.js", @@ -828,6 +830,7 @@ "change-case": "^4.1.1", "cheerio": "1.0.0-rc.6", "chokidar": "3.5.2", + "cohere-ai": "^6.2.2", "cron": "~1.7.2", "currency-codes": "^2.1.0", "eventsource": "^2.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1e7377319a5be..362d25a3da5c2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1017,6 +1017,9 @@ importers: chokidar: specifier: 3.5.2 version: 3.5.2 + cohere-ai: + specifier: ^6.2.2 + version: 6.2.2 cron: specifier: ~1.7.2 version: 1.7.2 @@ -1064,7 +1067,7 @@ importers: version: 1.16.0 langchain: specifier: ^0.0.126 - version: 0.0.126(@huggingface/inference@1.8.0)(cheerio@1.0.0-rc.6)(mongodb@4.10.0)(mysql2@2.3.3)(pg@8.8.0)(redis@3.1.2) + version: 0.0.126(@huggingface/inference@1.8.0)(cheerio@1.0.0-rc.6)(cohere-ai@6.2.2)(mongodb@4.10.0)(mysql2@2.3.3)(pg@8.8.0)(redis@3.1.2) ldapts: specifier: ^4.2.6 version: 4.2.6 @@ -9200,6 +9203,10 @@ packages: - '@lezer/common' dev: false + /cohere-ai@6.2.2: + resolution: {integrity: sha512-+Tq+4e8N/YWKJqFpWaULsfbZR/GOvGh8WWYFKR1bpipu8bCok3VcbTPnBmIToQiIqOgFpGk3HsA4b0guVyL3vg==} + dev: false + /collect-v8-coverage@1.0.1: resolution: {integrity: sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==} dev: true @@ -14602,7 +14609,7 @@ packages: resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} dev: false - /langchain@0.0.126(@huggingface/inference@1.8.0)(cheerio@1.0.0-rc.6)(mongodb@4.10.0)(mysql2@2.3.3)(pg@8.8.0)(redis@3.1.2): + /langchain@0.0.126(@huggingface/inference@1.8.0)(cheerio@1.0.0-rc.6)(cohere-ai@6.2.2)(mongodb@4.10.0)(mysql2@2.3.3)(pg@8.8.0)(redis@3.1.2): resolution: {integrity: sha512-Z57WjAwLN+ZbJ9D4h2oTN7iDt1Aa67mcXhKYVCQFRu4w8PIPH5k0VBvmrzKyRj2iFIWdUaqVsHch/jz5+1mGnA==} engines: {node: '>=18'} peerDependencies: @@ -14803,6 +14810,7 @@ packages: binary-extensions: 2.2.0 camelcase: 6.3.0 cheerio: 1.0.0-rc.6 + cohere-ai: 6.2.2 decamelize: 1.2.0 expr-eval: 2.0.2 flat: 5.0.2 From 48dc854955553fdb41860188674c84f4937a4460 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 14 Aug 2023 10:53:05 +0200 Subject: [PATCH 021/372] :zap: Move nodes to own package --- packages/cli/package.json | 1 + packages/cli/src/LoadNodesAndCredentials.ts | 1 + .../credentials/CohereApi.credentials.ts | 45 ----- .../credentials/HuggingFaceApi.credentials.ts | 41 ----- .../credentials/MotorheadApi.credentials.ts | 54 ------ .../credentials/SerpApi.credentials.ts | 41 ----- .../nodes/LangChain/LangChain.node.ts | 105 ----------- .../nodes/LangChain/LangChainLMCohere.node.ts | 57 ------ .../nodes/LangChain/LangChainLMOpenAi.node.ts | 83 --------- ...angChainLMOpenHuggingFaceInference.node.ts | 75 -------- .../LangChainMemoryMotorhead.node.ts | 65 ------- .../LangChain/LangChainToolCalculator.node.ts | 30 ---- .../LangChain/LangChainToolSerpApi.node.ts | 40 ----- .../LangChain/LangChainToolWikipedia.node.ts | 31 ---- .../nodes-base/nodes/LangChain/cohere.svg | 93 ---------- .../nodes-base/nodes/LangChain/google.svg | 1 - .../nodes/LangChain/huggingface.svg | 8 - .../nodes-base/nodes/LangChain/openAi.svg | 7 - .../nodes-base/nodes/LangChain/wikipedia.svg | 41 ----- packages/nodes-base/package.json | 15 -- pnpm-lock.yaml | 170 +++++++++++++----- 21 files changed, 127 insertions(+), 877 deletions(-) delete mode 100644 packages/nodes-base/credentials/CohereApi.credentials.ts delete mode 100644 packages/nodes-base/credentials/HuggingFaceApi.credentials.ts delete mode 100644 packages/nodes-base/credentials/MotorheadApi.credentials.ts delete mode 100644 packages/nodes-base/credentials/SerpApi.credentials.ts delete mode 100644 packages/nodes-base/nodes/LangChain/LangChain.node.ts delete mode 100644 packages/nodes-base/nodes/LangChain/LangChainLMCohere.node.ts delete mode 100644 packages/nodes-base/nodes/LangChain/LangChainLMOpenAi.node.ts delete mode 100644 packages/nodes-base/nodes/LangChain/LangChainLMOpenHuggingFaceInference.node.ts delete mode 100644 packages/nodes-base/nodes/LangChain/LangChainMemoryMotorhead.node.ts delete mode 100644 packages/nodes-base/nodes/LangChain/LangChainToolCalculator.node.ts delete mode 100644 packages/nodes-base/nodes/LangChain/LangChainToolSerpApi.node.ts delete mode 100644 packages/nodes-base/nodes/LangChain/LangChainToolWikipedia.node.ts delete mode 100644 packages/nodes-base/nodes/LangChain/cohere.svg delete mode 100644 packages/nodes-base/nodes/LangChain/google.svg delete mode 100644 packages/nodes-base/nodes/LangChain/huggingface.svg delete mode 100644 packages/nodes-base/nodes/LangChain/openAi.svg delete mode 100644 packages/nodes-base/nodes/LangChain/wikipedia.svg diff --git a/packages/cli/package.json b/packages/cli/package.json index 9d070131db4f2..4cf2ecf04d3c9 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -153,6 +153,7 @@ "n8n-core": "workspace:*", "n8n-editor-ui": "workspace:*", "n8n-nodes-base": "workspace:*", + "n8n-nodes-langchain": "workspace:*", "n8n-workflow": "workspace:*", "nanoid": "^3.3.6", "nodemailer": "^6.7.1", diff --git a/packages/cli/src/LoadNodesAndCredentials.ts b/packages/cli/src/LoadNodesAndCredentials.ts index 0494b7e7b831b..7ccfc2558149c 100644 --- a/packages/cli/src/LoadNodesAndCredentials.ts +++ b/packages/cli/src/LoadNodesAndCredentials.ts @@ -77,6 +77,7 @@ export class LoadNodesAndCredentials implements INodesAndCredentials { for (const nodeModulesDir of basePathsToScan) { await this.loadNodesFromNodeModules(nodeModulesDir, 'n8n-nodes-base'); + await this.loadNodesFromNodeModules(nodeModulesDir, 'n8n-nodes-langchain'); } // Load nodes from any other `n8n-nodes-*` packages in the download directory diff --git a/packages/nodes-base/credentials/CohereApi.credentials.ts b/packages/nodes-base/credentials/CohereApi.credentials.ts deleted file mode 100644 index 7fab31306275f..0000000000000 --- a/packages/nodes-base/credentials/CohereApi.credentials.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { - IAuthenticateGeneric, - ICredentialTestRequest, - ICredentialType, - INodeProperties, -} from 'n8n-workflow'; - -export class CohereApi implements ICredentialType { - name = 'cohereApi'; - - displayName = 'CohereApi'; - - documentationUrl = 'cohereApi'; - - properties: INodeProperties[] = [ - { - displayName: 'API Key', - name: 'apiKey', - type: 'string', - typeOptions: { password: true }, - required: true, - default: '', - }, - ]; - - authenticate: IAuthenticateGeneric = { - type: 'generic', - properties: { - headers: { - Authorization: '=Bearer {{$credentials.apiKey}}', - }, - }, - }; - - test: ICredentialTestRequest = { - request: { - baseURL: 'https://api.cohere.ai', - url: '/v1/detect-language', - method: 'POST', - body: { - texts: ['hello'], - }, - }, - }; -} diff --git a/packages/nodes-base/credentials/HuggingFaceApi.credentials.ts b/packages/nodes-base/credentials/HuggingFaceApi.credentials.ts deleted file mode 100644 index ed9a8f30f549a..0000000000000 --- a/packages/nodes-base/credentials/HuggingFaceApi.credentials.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { - IAuthenticateGeneric, - ICredentialTestRequest, - ICredentialType, - INodeProperties, -} from 'n8n-workflow'; - -export class HuggingFaceApi implements ICredentialType { - name = 'huggingFaceApi'; - - displayName = 'HuggingFaceApi'; - - documentationUrl = 'huggingFaceApi'; - - properties: INodeProperties[] = [ - { - displayName: 'API Key', - name: 'apiKey', - type: 'string', - typeOptions: { password: true }, - required: true, - default: '', - }, - ]; - - authenticate: IAuthenticateGeneric = { - type: 'generic', - properties: { - headers: { - Authorization: '=Bearer {{$credentials.apiKey}}', - }, - }, - }; - - test: ICredentialTestRequest = { - request: { - baseURL: 'https://api-inference.huggingface.co', - url: '/models/gpt2', - }, - }; -} diff --git a/packages/nodes-base/credentials/MotorheadApi.credentials.ts b/packages/nodes-base/credentials/MotorheadApi.credentials.ts deleted file mode 100644 index f9cecc49fe325..0000000000000 --- a/packages/nodes-base/credentials/MotorheadApi.credentials.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { - IAuthenticateGeneric, - ICredentialTestRequest, - ICredentialType, - INodeProperties, -} from 'n8n-workflow'; - -export class MotorheadApi implements ICredentialType { - name = 'motorheadApi'; - - displayName = 'MotorheadApi'; - - documentationUrl = 'motorheadApi'; - - properties: INodeProperties[] = [ - { - displayName: 'Host', - name: 'host', - required: true, - type: 'string', - default: '', - }, - { - displayName: 'API Key', - name: 'apiKey', - type: 'string', - typeOptions: { password: true }, - required: true, - default: '', - }, - { - displayName: 'Client ID', - name: 'clientId', - type: 'string', - default: '', - }, - ]; - - authenticate: IAuthenticateGeneric = { - type: 'generic', - properties: { - headers: { - 'x-metal-client-id': '={{$credentials.clientId}}', - 'x-metal-api-key': '={{$credentials.apiKey}}', - }, - }, - }; - - test: ICredentialTestRequest = { - request: { - baseURL: '={{$credentials.host}}', - }, - }; -} diff --git a/packages/nodes-base/credentials/SerpApi.credentials.ts b/packages/nodes-base/credentials/SerpApi.credentials.ts deleted file mode 100644 index fc649655d6909..0000000000000 --- a/packages/nodes-base/credentials/SerpApi.credentials.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { - IAuthenticateGeneric, - ICredentialTestRequest, - ICredentialType, - INodeProperties, -} from 'n8n-workflow'; - -export class SerpApi implements ICredentialType { - name = 'serpApi'; - - displayName = 'SerpAPI'; - - documentationUrl = 'serpAi'; - - properties: INodeProperties[] = [ - { - displayName: 'API Key', - name: 'apiKey', - type: 'string', - typeOptions: { password: true }, - required: true, - default: '', - }, - ]; - - authenticate: IAuthenticateGeneric = { - type: 'generic', - properties: { - qs: { - api_key: '={{$credentials.apiKey}}', - }, - }, - }; - - test: ICredentialTestRequest = { - request: { - baseURL: 'https://serpapi.com', - url: '/account.json ', - }, - }; -} diff --git a/packages/nodes-base/nodes/LangChain/LangChain.node.ts b/packages/nodes-base/nodes/LangChain/LangChain.node.ts deleted file mode 100644 index a60eea5dabb1b..0000000000000 --- a/packages/nodes-base/nodes/LangChain/LangChain.node.ts +++ /dev/null @@ -1,105 +0,0 @@ -/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ -import type { - IExecuteFunctions, - INodeExecutionData, - INodeType, - INodeTypeDescription, -} from 'n8n-workflow'; -import { NodeOperationError } from 'n8n-workflow'; - -import type { Tool } from 'langchain/tools'; -import type { BaseChatMemory } from 'langchain/memory'; -import type { InitializeAgentExecutorOptions } from 'langchain/agents'; -import { initializeAgentExecutorWithOptions } from 'langchain/agents'; -import type { BaseLanguageModel } from 'langchain/dist/base_language'; - -export class LangChain implements INodeType { - description: INodeTypeDescription = { - displayName: 'LangChain', - name: 'langChain', - icon: 'fa:link', - group: ['transform'], - version: 1, - description: 'LangChain', - defaults: { - name: 'LangChain', - color: '#404040', - }, - // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node - inputs: ['main', 'tool', 'memory', 'languageModel'], - inputNames: ['', 'Tools', 'Memory', 'Language Model'], - outputs: ['main'], - credentials: [], - properties: [ - { - displayName: 'Text', - name: 'text', - type: 'string', - default: '', - }, - ], - }; - - async execute(this: IExecuteFunctions): Promise { - let memory: BaseChatMemory | undefined; - - const languageModelNodes = await this.getInputConnectionData(0, 0, 'languageModel'); - if (languageModelNodes.length === 0) { - throw new NodeOperationError( - this.getNode(), - 'At least one Language Model has to be connected!', - ); - } else if (languageModelNodes.length > 1) { - throw new NodeOperationError( - this.getNode(), - 'Only one Language Model is allowed to be connected!', - ); - } - const model = languageModelNodes[0].response as BaseLanguageModel; - - if (languageModelNodes.length === 0) { - throw new NodeOperationError( - this.getNode(), - 'At least one Language Model has to be connected!', - ); - } else if (languageModelNodes.length > 1) { - throw new NodeOperationError( - this.getNode(), - 'Only one Language Model is allowed to be connected!', - ); - } - - const memoryNodes = await this.getInputConnectionData(0, 0, 'memory'); - if (memoryNodes.length === 1) { - memory = memoryNodes[0].response as BaseChatMemory; - } else if (languageModelNodes.length > 1) { - throw new NodeOperationError(this.getNode(), 'Only one Memory is allowed to be connected!'); - } - - const toolNodes = await this.getInputConnectionData(0, 0, 'tool'); - const tools = toolNodes.map((connectedNode) => { - return connectedNode.response as Tool; - }); - - const options: InitializeAgentExecutorOptions = { - agentType: 'chat-conversational-react-description', - verbose: true, - memory, - }; - - const executor = await initializeAgentExecutorWithOptions(tools, model, options); - - const items = this.getInputData(); - const returnData: INodeExecutionData[] = []; - for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { - const text = this.getNodeParameter('text', itemIndex) as string; - - const response = await executor.call({ - input: text, - }); - - returnData.push({ json: { response } }); - } - return this.prepareOutputData(returnData); - } -} diff --git a/packages/nodes-base/nodes/LangChain/LangChainLMCohere.node.ts b/packages/nodes-base/nodes/LangChain/LangChainLMCohere.node.ts deleted file mode 100644 index 4be61f91082d0..0000000000000 --- a/packages/nodes-base/nodes/LangChain/LangChainLMCohere.node.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ -import type { IExecuteFunctions, INodeType, INodeTypeDescription, SupplyData } from 'n8n-workflow'; - -import { Cohere } from 'langchain/llms/cohere'; - -export class LangChainLMCohere implements INodeType { - description: INodeTypeDescription = { - displayName: 'LangChain - Cohere', - // eslint-disable-next-line n8n-nodes-base/node-class-description-name-miscased - name: 'langChainLMCohere', - icon: 'file:cohere.svg', - group: ['transform'], - version: 1, - description: 'Language Model Cohere', - defaults: { - name: 'LangChain - Cohere', - }, - // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node - inputs: [], - // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong - outputs: ['languageModel'], - outputNames: ['Language Model'], - credentials: [ - { - name: 'cohereApi', - required: true, - }, - ], - properties: [ - { - displayName: 'Maximum Tokens', - name: 'maxTokens', - default: 20, - typeOptions: { minValue: 0 }, - description: 'Maximum amount of tokens to use for the completion', - type: 'number', - }, - ], - }; - - async supplyData(this: IExecuteFunctions): Promise { - const credentials = await this.getCredentials('cohereApi'); - - const itemIndex = 0; - - const maxTokens = this.getNodeParameter('maxTokens', itemIndex) as number; - - const model = new Cohere({ - maxTokens, - apiKey: credentials.apiKey as string, - }); - - return { - response: model, - }; - } -} diff --git a/packages/nodes-base/nodes/LangChain/LangChainLMOpenAi.node.ts b/packages/nodes-base/nodes/LangChain/LangChainLMOpenAi.node.ts deleted file mode 100644 index 708bcc6e2ad84..0000000000000 --- a/packages/nodes-base/nodes/LangChain/LangChainLMOpenAi.node.ts +++ /dev/null @@ -1,83 +0,0 @@ -/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ -import type { IExecuteFunctions, INodeType, INodeTypeDescription, SupplyData } from 'n8n-workflow'; - -import { ChatOpenAI } from 'langchain/chat_models/openai'; -export class LangChainLMOpenAi implements INodeType { - description: INodeTypeDescription = { - displayName: 'LangChain - OpenAI', - // eslint-disable-next-line n8n-nodes-base/node-class-description-name-miscased - name: 'langChainLMOpenAi', - icon: 'file:openAi.svg', - group: ['transform'], - version: 1, - description: 'Language Model OpenAI', - defaults: { - name: 'LangChain - OpenAI', - }, - // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node - inputs: [], - // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong - outputs: ['languageModel'], - outputNames: ['Language Model'], - credentials: [ - { - name: 'openAiApi', - required: true, - }, - ], - properties: [ - { - displayName: 'Model', - name: 'model', - type: 'options', - noDataExpression: true, - options: [ - { - name: 'GTP 3.5 Turbo', - value: 'gpt-3.5-turbo', - }, - { - name: 'DaVinci-003', - value: 'text-davinci-003', - }, - ], - default: 'gpt-3.5-turbo', - }, - { - displayName: 'Sampling Temperature', - name: 'temperature', - default: 0.7, - typeOptions: { maxValue: 1, minValue: 0, numberPrecision: 1 }, - description: - 'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive.', - type: 'number', - routing: { - send: { - type: 'body', - property: 'temperature', - }, - }, - }, - ], - }; - - async supplyData(this: IExecuteFunctions): Promise { - const credentials = await this.getCredentials('openAiApi'); - - const itemIndex = 0; - - // TODO: Should it get executed once per item or not? - const modelName = this.getNodeParameter('model', itemIndex) as string; - const temperature = this.getNodeParameter('temperature', itemIndex) as number; - - const model = new ChatOpenAI({ - openAIApiKey: credentials.apiKey as string, - modelName, - temperature, - }); - - return { - response: model, - }; - } -} diff --git a/packages/nodes-base/nodes/LangChain/LangChainLMOpenHuggingFaceInference.node.ts b/packages/nodes-base/nodes/LangChain/LangChainLMOpenHuggingFaceInference.node.ts deleted file mode 100644 index 761682cbf1e1d..0000000000000 --- a/packages/nodes-base/nodes/LangChain/LangChainLMOpenHuggingFaceInference.node.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ -import type { IExecuteFunctions, INodeType, INodeTypeDescription, SupplyData } from 'n8n-workflow'; - -import { HuggingFaceInference } from 'langchain/llms/hf'; - -export class LangChainLMOpenHuggingFaceInference implements INodeType { - description: INodeTypeDescription = { - displayName: 'LangChain - HuggingFaceInference', - // eslint-disable-next-line n8n-nodes-base/node-class-description-name-miscased - name: 'langChainLMOpenHuggingFaceInference', - icon: 'file:huggingface.svg', - group: ['transform'], - version: 1, - description: 'Language Model HuggingFaceInference', - defaults: { - name: 'LangChain - HuggingFaceInference', - }, - // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node - inputs: [], - // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong - outputs: ['languageModel'], - outputNames: ['Language Model'], - credentials: [ - { - name: 'huggingFaceApi', - required: true, - }, - ], - properties: [ - { - displayName: 'Model', - name: 'model', - type: 'options', - noDataExpression: true, - options: [ - { - name: 'GTP 2', - value: 'gpt2', - }, - ], - default: 'gpt2', - }, - { - displayName: 'Sampling Temperature', - name: 'temperature', - default: 0.7, - typeOptions: { maxValue: 1, minValue: 0, numberPrecision: 1 }, - description: - 'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive.', - type: 'number', - }, - ], - }; - - async supplyData(this: IExecuteFunctions): Promise { - const credentials = await this.getCredentials('huggingFaceApi'); - - const itemIndex = 0; - - // TODO: Should it get executed once per item or not? - const modelName = this.getNodeParameter('model', itemIndex) as string; - const temperature = this.getNodeParameter('temperature', itemIndex) as number; - - const model = new HuggingFaceInference({ - model: modelName, - apiKey: credentials.apiKey as string, - temperature, - maxTokens: 100, - }); - - return { - response: model, - }; - } -} diff --git a/packages/nodes-base/nodes/LangChain/LangChainMemoryMotorhead.node.ts b/packages/nodes-base/nodes/LangChain/LangChainMemoryMotorhead.node.ts deleted file mode 100644 index 01de2cba032bf..0000000000000 --- a/packages/nodes-base/nodes/LangChain/LangChainMemoryMotorhead.node.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ -import type { IExecuteFunctions, INodeType, INodeTypeDescription, SupplyData } from 'n8n-workflow'; - -import { MotorheadMemory } from 'langchain/memory'; - -export class LangChainMemoryMotorhead implements INodeType { - description: INodeTypeDescription = { - displayName: 'LangChain - Motorhead', - name: 'langChainMemoryMotorhead', - icon: 'fa:file-export', - group: ['transform'], - version: 1, - description: 'Motorhead Memory', - defaults: { - name: 'LangChain - Motorhead', - color: '#303030', - }, - // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node - inputs: [], - // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong - outputs: ['memory'], - outputNames: ['Memory'], - credentials: [ - { - name: 'motorheadApi', - required: true, - }, - ], - properties: [ - { - displayName: 'Memory Key', - name: 'memoryKey', - type: 'string', - default: '', - }, - { - displayName: 'Session ID', - name: 'sessionId', - type: 'string', - default: '', - }, - ], - }; - - async supplyData(this: IExecuteFunctions): Promise { - const credentials = await this.getCredentials('motorheadApi'); - - const itemIndex = 0; - - // TODO: Should it get executed once per item or not? - const memoryKey = this.getNodeParameter('memoryKey', itemIndex) as string; - const sessionId = this.getNodeParameter('sessionId', itemIndex) as string; - - return { - // TODO: Does not work yet - response: new MotorheadMemory({ - memoryKey, - sessionId, - url: credentials.host as string, - clientId: credentials.clientId as string, - apiKey: credentials.apiKey as string, - }), - }; - } -} diff --git a/packages/nodes-base/nodes/LangChain/LangChainToolCalculator.node.ts b/packages/nodes-base/nodes/LangChain/LangChainToolCalculator.node.ts deleted file mode 100644 index 6a03748624d05..0000000000000 --- a/packages/nodes-base/nodes/LangChain/LangChainToolCalculator.node.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ -import type { IExecuteFunctions, INodeType, INodeTypeDescription, SupplyData } from 'n8n-workflow'; -import { Calculator } from 'langchain/tools/calculator'; - -export class LangChainToolCalculator implements INodeType { - description: INodeTypeDescription = { - displayName: 'LangChain - Calculator', - name: 'langChainToolCalculator', - icon: 'fa:calculator', - group: ['transform'], - version: 1, - description: 'Calculator', - defaults: { - name: 'LangChain - Calculator', - color: '#400080', - }, - // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node - inputs: [], - // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong - outputs: ['tool'], - outputNames: ['Tool'], - properties: [], - }; - - async supplyData(this: IExecuteFunctions): Promise { - return { - response: new Calculator(), - }; - } -} diff --git a/packages/nodes-base/nodes/LangChain/LangChainToolSerpApi.node.ts b/packages/nodes-base/nodes/LangChain/LangChainToolSerpApi.node.ts deleted file mode 100644 index 37b271f13b51c..0000000000000 --- a/packages/nodes-base/nodes/LangChain/LangChainToolSerpApi.node.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ -import type { IExecuteFunctions, INodeType, INodeTypeDescription, SupplyData } from 'n8n-workflow'; - -import { SerpAPI } from 'langchain/tools'; - -export class LangChainToolSerpApi implements INodeType { - description: INodeTypeDescription = { - displayName: 'LangChain - SerpAPI', - name: 'langChainToolSerpApi', - icon: 'file:google.svg', - group: ['transform'], - version: 1, - description: 'Search in Google', - defaults: { - name: 'LangChain - SerpAPI', - // eslint-disable-next-line n8n-nodes-base/node-class-description-non-core-color-present - color: '#400080', - }, - // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node - inputs: [], - // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong - outputs: ['tool'], - outputNames: ['Tool'], - credentials: [ - { - name: 'serpApi', - required: true, - }, - ], - properties: [], - }; - - async supplyData(this: IExecuteFunctions): Promise { - const credentials = await this.getCredentials('serpApi'); - - return { - response: new SerpAPI(credentials.apiKey as string), - }; - } -} diff --git a/packages/nodes-base/nodes/LangChain/LangChainToolWikipedia.node.ts b/packages/nodes-base/nodes/LangChain/LangChainToolWikipedia.node.ts deleted file mode 100644 index 3224a915c55ef..0000000000000 --- a/packages/nodes-base/nodes/LangChain/LangChainToolWikipedia.node.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ -import type { IExecuteFunctions, INodeType, INodeTypeDescription, SupplyData } from 'n8n-workflow'; -import { WikipediaQueryRun } from 'langchain/tools'; - -export class LangChainToolWikipedia implements INodeType { - description: INodeTypeDescription = { - displayName: 'LangChain - Wikipedia', - name: 'langChainToolWikipedia', - icon: 'file:wikipedia.svg', - group: ['transform'], - version: 1, - description: 'Search in Wikipedia', - defaults: { - name: 'LangChain - Wikipedia', - // eslint-disable-next-line n8n-nodes-base/node-class-description-non-core-color-present - color: '#400080', - }, - // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node - inputs: [], - // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong - outputs: ['tool'], - outputNames: ['Tool'], - properties: [], - }; - - async supplyData(this: IExecuteFunctions): Promise { - return { - response: new WikipediaQueryRun(), - }; - } -} diff --git a/packages/nodes-base/nodes/LangChain/cohere.svg b/packages/nodes-base/nodes/LangChain/cohere.svg deleted file mode 100644 index abe70db29b052..0000000000000 --- a/packages/nodes-base/nodes/LangChain/cohere.svg +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - diff --git a/packages/nodes-base/nodes/LangChain/google.svg b/packages/nodes-base/nodes/LangChain/google.svg deleted file mode 100644 index 4cf163bfe2478..0000000000000 --- a/packages/nodes-base/nodes/LangChain/google.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/nodes-base/nodes/LangChain/huggingface.svg b/packages/nodes-base/nodes/LangChain/huggingface.svg deleted file mode 100644 index ab959d165fa5b..0000000000000 --- a/packages/nodes-base/nodes/LangChain/huggingface.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/nodes-base/nodes/LangChain/openAi.svg b/packages/nodes-base/nodes/LangChain/openAi.svg deleted file mode 100644 index d9445723fffb7..0000000000000 --- a/packages/nodes-base/nodes/LangChain/openAi.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - OpenAI - - - - diff --git a/packages/nodes-base/nodes/LangChain/wikipedia.svg b/packages/nodes-base/nodes/LangChain/wikipedia.svg deleted file mode 100644 index bfa60b37e6bfa..0000000000000 --- a/packages/nodes-base/nodes/LangChain/wikipedia.svg +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index f6c3f579ce48e..9cbb6b346f897 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -78,7 +78,6 @@ "dist/credentials/ClockifyApi.credentials.js", "dist/credentials/CockpitApi.credentials.js", "dist/credentials/CodaApi.credentials.js", - "dist/credentials/CohereApi.credentials.js", "dist/credentials/ContentfulApi.credentials.js", "dist/credentials/ConvertKitApi.credentials.js", "dist/credentials/CopperApi.credentials.js", @@ -169,7 +168,6 @@ "dist/credentials/HubspotAppToken.credentials.js", "dist/credentials/HubspotDeveloperApi.credentials.js", "dist/credentials/HubspotOAuth2Api.credentials.js", - "dist/credentials/HuggingFaceApi.credentials.js", "dist/credentials/HumanticAiApi.credentials.js", "dist/credentials/HunterApi.credentials.js", "dist/credentials/HybridAnalysisApi.credentials.js", @@ -232,7 +230,6 @@ "dist/credentials/MondayComOAuth2Api.credentials.js", "dist/credentials/MongoDb.credentials.js", "dist/credentials/MonicaCrmApi.credentials.js", - "dist/credentials/MotorheadApi.credentials.js", "dist/credentials/Mqtt.credentials.js", "dist/credentials/Msg91Api.credentials.js", "dist/credentials/MySql.credentials.js", @@ -300,7 +297,6 @@ "dist/credentials/SentryIoApi.credentials.js", "dist/credentials/SentryIoOAuth2Api.credentials.js", "dist/credentials/SentryIoServerApi.credentials.js", - "dist/credentials/SerpApi.credentials.js", "dist/credentials/ServiceNowOAuth2Api.credentials.js", "dist/credentials/ServiceNowBasicApi.credentials.js", "dist/credentials/Sftp.credentials.js", @@ -572,14 +568,6 @@ "dist/nodes/Kitemaker/Kitemaker.node.js", "dist/nodes/KoBoToolbox/KoBoToolbox.node.js", "dist/nodes/KoBoToolbox/KoBoToolboxTrigger.node.js", - "dist/nodes/LangChain/LangChain.node.js", - "dist/nodes/LangChain/LangChainLMOpenAi.node.js", - "dist/nodes/LangChain/LangChainLMCohere.node.js", - "dist/nodes/LangChain/LangChainLMOpenHuggingFaceInference.node.js", - "dist/nodes/LangChain/LangChainMemoryMotorhead.node.js", - "dist/nodes/LangChain/LangChainToolCalculator.node.js", - "dist/nodes/LangChain/LangChainToolSerpApi.node.js", - "dist/nodes/LangChain/LangChainToolWikipedia.node.js", "dist/nodes/Ldap/Ldap.node.js", "dist/nodes/Lemlist/Lemlist.node.js", "dist/nodes/Lemlist/LemlistTrigger.node.js", @@ -822,7 +810,6 @@ "n8n-core": "workspace:*" }, "dependencies": { - "@huggingface/inference": "1", "@kafkajs/confluent-schema-registry": "1.0.6", "amqplib": "^0.10.3", "aws4": "^1.8.0", @@ -830,7 +817,6 @@ "change-case": "^4.1.1", "cheerio": "1.0.0-rc.6", "chokidar": "3.5.2", - "cohere-ai": "^6.2.2", "cron": "~1.7.2", "currency-codes": "^2.1.0", "eventsource": "^2.0.2", @@ -846,7 +832,6 @@ "js-nacl": "^1.4.0", "jsonwebtoken": "^9.0.0", "kafkajs": "^1.14.0", - "langchain": "^0.0.126", "ldapts": "^4.2.6", "lodash": "^4.17.21", "lossless-json": "^1.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 362d25a3da5c2..0746a576ba825 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -135,7 +135,7 @@ importers: dependencies: axios: specifier: ^0.21.1 - version: 0.21.4(debug@4.3.2) + version: 0.21.4 packages/@n8n_io/eslint-config: devDependencies: @@ -216,7 +216,7 @@ importers: version: 7.28.1 axios: specifier: ^0.21.1 - version: 0.21.4(debug@4.3.2) + version: 0.21.4 basic-auth: specifier: ^2.0.1 version: 2.0.1 @@ -346,6 +346,9 @@ importers: n8n-nodes-base: specifier: workspace:* version: link:../nodes-base + n8n-nodes-langchain: + specifier: workspace:* + version: link:../nodes-langchain n8n-workflow: specifier: workspace:* version: link:../workflow @@ -574,7 +577,7 @@ importers: version: link:../@n8n/client-oauth2 axios: specifier: ^0.21.1 - version: 0.21.4(debug@4.3.2) + version: 0.21.4 concat-stream: specifier: ^2.0.0 version: 2.0.0 @@ -834,7 +837,7 @@ importers: version: 10.2.0(vue@3.3.4) axios: specifier: ^0.21.1 - version: 0.21.4(debug@4.3.2) + version: 0.21.4 codemirror-lang-html-n8n: specifier: ^1.0.0 version: 1.0.0 @@ -993,9 +996,6 @@ importers: packages/nodes-base: dependencies: - '@huggingface/inference': - specifier: '1' - version: 1.8.0 '@kafkajs/confluent-schema-registry': specifier: 1.0.6 version: 1.0.6 @@ -1017,9 +1017,6 @@ importers: chokidar: specifier: 3.5.2 version: 3.5.2 - cohere-ai: - specifier: ^6.2.2 - version: 6.2.2 cron: specifier: ~1.7.2 version: 1.7.2 @@ -1065,9 +1062,6 @@ importers: kafkajs: specifier: ^1.14.0 version: 1.16.0 - langchain: - specifier: ^0.0.126 - version: 0.0.126(@huggingface/inference@1.8.0)(cheerio@1.0.0-rc.6)(cohere-ai@6.2.2)(mongodb@4.10.0)(mysql2@2.3.3)(pg@8.8.0)(redis@3.1.2) ldapts: specifier: ^4.2.6 version: 4.2.6 @@ -1277,6 +1271,46 @@ importers: specifier: workspace:* version: link:../core + packages/nodes-langchain: + dependencies: + '@huggingface/inference': + specifier: '1' + version: 1.8.0 + cohere-ai: + specifier: ^6.2.2 + version: 6.2.2 + langchain: + specifier: ^0.0.126 + version: 0.0.126(@huggingface/inference@1.8.0)(cohere-ai@6.2.2) + devDependencies: + '@types/express': + specifier: ^4.17.6 + version: 4.17.14 + '@types/request-promise-native': + specifier: ~1.0.15 + version: 1.0.18 + '@typescript-eslint/parser': + specifier: ~5.45 + version: 5.45.0(eslint@8.45.0)(typescript@5.1.6) + eslint-plugin-n8n-nodes-base: + specifier: ^1.11.0 + version: 1.16.0(eslint@8.45.0)(typescript@5.1.6) + gulp: + specifier: ^4.0.2 + version: 4.0.2 + n8n-core: + specifier: '*' + version: link:../core + n8n-workflow: + specifier: '*' + version: link:../workflow + prettier: + specifier: ^2.7.1 + version: 2.8.8 + typescript: + specifier: ^5.1.6 + version: 5.1.6 + packages/workflow: dependencies: '@n8n_io/riot-tmpl': @@ -4484,7 +4518,7 @@ packages: dependencies: '@segment/loosely-validate-event': 2.0.0 auto-changelog: 1.16.4 - axios: 0.21.4(debug@4.3.2) + axios: 0.21.4 axios-retry: 3.3.1 bull: 3.29.3 lodash.clonedeep: 4.5.0 @@ -6805,6 +6839,26 @@ packages: - supports-color dev: true + /@typescript-eslint/parser@5.45.0(eslint@8.45.0)(typescript@5.1.6): + resolution: {integrity: sha512-brvs/WSM4fKUmF5Ot/gEve6qYiCMjm6w4HkHPfS6ZNmxTS0m0iNN4yOChImaCkqc1hRwFGqUyanMXuGal6oyyQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 5.45.0 + '@typescript-eslint/types': 5.45.0 + '@typescript-eslint/typescript-estree': 5.45.0(typescript@5.1.6) + debug: 4.3.4(supports-color@8.1.1) + eslint: 8.45.0 + typescript: 5.1.6 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/parser@5.59.5(eslint@8.45.0)(typescript@5.1.6): resolution: {integrity: sha512-NJXQC4MRnF9N9yWqQE2/KLRSOLvrrlZb48NGVfBa+RuPMN6B7ZcK5jZOvhuygv4D64fRKnZI4L4p8+M+rfeQuw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -6846,6 +6900,14 @@ packages: - supports-color dev: true + /@typescript-eslint/scope-manager@5.45.0: + resolution: {integrity: sha512-noDMjr87Arp/PuVrtvN3dXiJstQR1+XlQ4R1EvzG+NMgXi8CuMCXpb8JqNtFHKceVSQ985BZhfRdowJzbv4yKw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.45.0 + '@typescript-eslint/visitor-keys': 5.45.0 + dev: true + /@typescript-eslint/scope-manager@5.59.5: resolution: {integrity: sha512-jVecWwnkX6ZgutF+DovbBJirZcAxgxC0EOHYt/niMROf8p4PwxxG32Qdhj/iIQQIuOflLjNkxoXyArkcIP7C3A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -6910,6 +6972,11 @@ packages: - supports-color dev: true + /@typescript-eslint/types@5.45.0: + resolution: {integrity: sha512-QQij+u/vgskA66azc9dCmx+rev79PzX8uDHpsqSjEFtfF2gBUTRCpvYMh2gw2ghkJabNkPlSUCimsyBEQZd1DA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + /@typescript-eslint/types@5.59.5: resolution: {integrity: sha512-xkfRPHbqSH4Ggx4eHRIO/eGL8XL4Ysb4woL8c87YuAo8Md7AUjyWKa9YMwTL519SyDPrfEgKdewjkxNCVeJW7w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -6925,6 +6992,27 @@ packages: engines: {node: ^16.0.0 || >=18.0.0} dev: true + /@typescript-eslint/typescript-estree@5.45.0(typescript@5.1.6): + resolution: {integrity: sha512-maRhLGSzqUpFcZgXxg1qc/+H0bT36lHK4APhp0AEUVrpSwXiRAomm/JGjSG+kNUio5kAa3uekCYu/47cnGn5EQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 5.45.0 + '@typescript-eslint/visitor-keys': 5.45.0 + debug: 4.3.4(supports-color@8.1.1) + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.5.4 + tsutils: 3.21.0(typescript@5.1.6) + typescript: 5.1.6 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/typescript-estree@5.59.5(typescript@5.1.6): resolution: {integrity: sha512-+XXdLN2CZLZcD/mO7mQtJMvCkzRfmODbeSKuMY/yXbGkzvA9rJyDY5qDYNoiz2kP/dmyAxXquL2BvLQLJFPQIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -7046,6 +7134,14 @@ packages: - typescript dev: true + /@typescript-eslint/visitor-keys@5.45.0: + resolution: {integrity: sha512-jc6Eccbn2RtQPr1s7th6jJWQHBHI6GBVQkCHoJFQ5UreaKm59Vxw+ynQUPPY2u2Amquc+7tmEoC2G52ApsGNNg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.45.0 + eslint-visitor-keys: 3.4.1 + dev: true + /@typescript-eslint/visitor-keys@5.59.5: resolution: {integrity: sha512-qL+Oz+dbeBRTeyJTIy0eniD3uvqU7x+y1QceBismZ41hd4aBSRh8UAw4pZP0+XzLuPZmx4raNMq/I+59W2lXKA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -7853,7 +7949,7 @@ packages: engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.1.4 + define-properties: 1.2.0 es-abstract: 1.21.1 get-intrinsic: 1.1.3 is-string: 1.0.7 @@ -7910,7 +8006,7 @@ packages: engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.1.4 + define-properties: 1.2.0 es-abstract: 1.21.1 es-shim-unscopables: 1.0.0 get-intrinsic: 1.1.3 @@ -7921,7 +8017,7 @@ packages: engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.1.4 + define-properties: 1.2.0 es-abstract: 1.21.1 es-shim-unscopables: 1.0.0 dev: true @@ -7931,7 +8027,7 @@ packages: engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.1.4 + define-properties: 1.2.0 es-abstract: 1.21.1 es-shim-unscopables: 1.0.0 dev: true @@ -8181,27 +8277,26 @@ packages: is-retry-allowed: 2.2.0 dev: false - /axios@0.21.4(debug@4.3.2): + /axios@0.21.4: resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} dependencies: - follow-redirects: 1.15.2(debug@4.3.2) + follow-redirects: 1.15.2(debug@4.3.4) transitivePeerDependencies: - debug dev: false - /axios@0.26.1: - resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==} + /axios@0.21.4(debug@4.3.2): + resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} dependencies: follow-redirects: 1.15.2(debug@4.3.2) transitivePeerDependencies: - debug dev: false - /axios@0.27.2: - resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==} + /axios@0.26.1: + resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==} dependencies: - follow-redirects: 1.15.2(debug@4.3.2) - form-data: 4.0.0 + follow-redirects: 1.15.2(debug@4.3.4) transitivePeerDependencies: - debug dev: false @@ -8222,7 +8317,6 @@ packages: form-data: 4.0.0 transitivePeerDependencies: - debug - dev: true /babel-core@7.0.0-bridge.0(@babel/core@7.22.9): resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==} @@ -9998,14 +10092,6 @@ packages: resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} engines: {node: '>=8'} - /define-properties@1.1.4: - resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==} - engines: {node: '>= 0.4'} - dependencies: - has-property-descriptors: 1.0.0 - object-keys: 1.1.1 - dev: true - /define-properties@1.2.0: resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} engines: {node: '>= 0.4'} @@ -11832,7 +11918,6 @@ packages: optional: true dependencies: debug: 4.3.4(supports-color@8.1.1) - dev: true /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -14609,7 +14694,7 @@ packages: resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} dev: false - /langchain@0.0.126(@huggingface/inference@1.8.0)(cheerio@1.0.0-rc.6)(cohere-ai@6.2.2)(mongodb@4.10.0)(mysql2@2.3.3)(pg@8.8.0)(redis@3.1.2): + /langchain@0.0.126(@huggingface/inference@1.8.0)(cohere-ai@6.2.2): resolution: {integrity: sha512-Z57WjAwLN+ZbJ9D4h2oTN7iDt1Aa67mcXhKYVCQFRu4w8PIPH5k0VBvmrzKyRj2iFIWdUaqVsHch/jz5+1mGnA==} engines: {node: '>=18'} peerDependencies: @@ -14809,7 +14894,6 @@ packages: ansi-styles: 5.2.0 binary-extensions: 2.2.0 camelcase: 6.3.0 - cheerio: 1.0.0-rc.6 cohere-ai: 6.2.2 decamelize: 1.2.0 expr-eval: 2.0.2 @@ -14819,15 +14903,11 @@ packages: jsonpointer: 5.0.1 langsmith: 0.0.20 ml-distance: 4.0.1 - mongodb: 4.10.0 - mysql2: 2.3.3 object-hash: 3.0.0 openai: 3.3.0 openapi-types: 12.1.3 p-queue: 6.6.2 p-retry: 4.6.2 - pg: 8.8.0 - redis: 3.1.2 uuid: 9.0.0 yaml: 2.3.1 zod: 3.21.4 @@ -16553,7 +16633,7 @@ packages: engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.1.4 + define-properties: 1.2.0 es-abstract: 1.21.1 dev: true @@ -16604,7 +16684,7 @@ packages: engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.1.4 + define-properties: 1.2.0 es-abstract: 1.21.1 dev: true @@ -17534,7 +17614,7 @@ packages: resolution: {integrity: sha512-aXYe/D+28kF63W8Cz53t09ypEORz+ULeDCahdAqhVrRm2scbOXFbtnn0GGhvMpYe45grepLKuwui9KxrZ2ZuMw==} engines: {node: '>=14.17.0'} dependencies: - axios: 0.27.2 + axios: 0.27.2(debug@4.3.4) transitivePeerDependencies: - debug dev: false From a295a5e4b566439d88822770732a426e0c2b0cde Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 14 Aug 2023 11:38:08 +0200 Subject: [PATCH 022/372] :zap: Proper move and make the nodes load --- packages/cli/package.json | 2 +- packages/cli/src/LoadNodesAndCredentials.ts | 2 +- packages/nodes-langchain/.editorconfig | 20 +++ packages/nodes-langchain/.eslintrc.js | 155 ++++++++++++++++++ packages/nodes-langchain/.gitignore | 8 + packages/nodes-langchain/.npmignore | 2 + packages/nodes-langchain/.prettierrc.js | 51 ++++++ .../nodes-langchain/.vscode/extensions.json | 7 + packages/nodes-langchain/LICENSE.md | 85 ++++++++++ packages/nodes-langchain/README.md | 11 ++ .../credentials/CohereApi.credentials.ts | 45 +++++ .../credentials/HuggingFaceApi.credentials.ts | 41 +++++ .../credentials/MotorheadApi.credentials.ts | 54 ++++++ .../credentials/SerpApi.credentials.ts | 41 +++++ packages/nodes-langchain/gulpfile.js | 16 ++ packages/nodes-langchain/index.js | 0 .../nodes/LangChain/LangChain.node.ts | 104 ++++++++++++ .../LangChainLMCohere.node.ts | 57 +++++++ .../nodes/LangChainLMCohere/cohere.svg | 93 +++++++++++ .../LangChainLMOpenAi.node.ts | 83 ++++++++++ .../nodes/LangChainLMOpenAi/openAi.svg | 7 + ...angChainLMOpenHuggingFaceInference.node.ts | 75 +++++++++ .../huggingface.svg | 8 + .../LangChainMemoryMotorhead.node.ts | 65 ++++++++ .../LangChainToolCalculator.node.ts | 30 ++++ .../LangChainToolSerpApi.node.ts | 40 +++++ .../nodes/LangChainToolSerpApi/google.svg | 1 + .../LangChainToolWikipedia.node.ts | 31 ++++ .../LangChainToolWikipedia/wikipedia.svg | 41 +++++ packages/nodes-langchain/package.json | 65 ++++++++ packages/nodes-langchain/tsconfig.json | 30 ++++ packages/nodes-langchain/tslint.json | 127 ++++++++++++++ pnpm-lock.yaml | 6 +- 33 files changed, 1398 insertions(+), 5 deletions(-) create mode 100644 packages/nodes-langchain/.editorconfig create mode 100644 packages/nodes-langchain/.eslintrc.js create mode 100644 packages/nodes-langchain/.gitignore create mode 100644 packages/nodes-langchain/.npmignore create mode 100644 packages/nodes-langchain/.prettierrc.js create mode 100644 packages/nodes-langchain/.vscode/extensions.json create mode 100644 packages/nodes-langchain/LICENSE.md create mode 100644 packages/nodes-langchain/README.md create mode 100644 packages/nodes-langchain/credentials/CohereApi.credentials.ts create mode 100644 packages/nodes-langchain/credentials/HuggingFaceApi.credentials.ts create mode 100644 packages/nodes-langchain/credentials/MotorheadApi.credentials.ts create mode 100644 packages/nodes-langchain/credentials/SerpApi.credentials.ts create mode 100644 packages/nodes-langchain/gulpfile.js create mode 100644 packages/nodes-langchain/index.js create mode 100644 packages/nodes-langchain/nodes/LangChain/LangChain.node.ts create mode 100644 packages/nodes-langchain/nodes/LangChainLMCohere/LangChainLMCohere.node.ts create mode 100644 packages/nodes-langchain/nodes/LangChainLMCohere/cohere.svg create mode 100644 packages/nodes-langchain/nodes/LangChainLMOpenAi/LangChainLMOpenAi.node.ts create mode 100644 packages/nodes-langchain/nodes/LangChainLMOpenAi/openAi.svg create mode 100644 packages/nodes-langchain/nodes/LangChainLMOpenHuggingFaceInference/LangChainLMOpenHuggingFaceInference.node.ts create mode 100644 packages/nodes-langchain/nodes/LangChainLMOpenHuggingFaceInference/huggingface.svg create mode 100644 packages/nodes-langchain/nodes/LangChainMemoryMotorhead/LangChainMemoryMotorhead.node.ts create mode 100644 packages/nodes-langchain/nodes/LangChainToolCalculator/LangChainToolCalculator.node.ts create mode 100644 packages/nodes-langchain/nodes/LangChainToolSerpApi/LangChainToolSerpApi.node.ts create mode 100644 packages/nodes-langchain/nodes/LangChainToolSerpApi/google.svg create mode 100644 packages/nodes-langchain/nodes/LangChainToolWikipedia/LangChainToolWikipedia.node.ts create mode 100644 packages/nodes-langchain/nodes/LangChainToolWikipedia/wikipedia.svg create mode 100644 packages/nodes-langchain/package.json create mode 100644 packages/nodes-langchain/tsconfig.json create mode 100644 packages/nodes-langchain/tslint.json diff --git a/packages/cli/package.json b/packages/cli/package.json index 4cf2ecf04d3c9..f6aa46eb200e6 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -153,7 +153,7 @@ "n8n-core": "workspace:*", "n8n-editor-ui": "workspace:*", "n8n-nodes-base": "workspace:*", - "n8n-nodes-langchain": "workspace:*", + "@n8n/nodes-langchain": "workspace:*", "n8n-workflow": "workspace:*", "nanoid": "^3.3.6", "nodemailer": "^6.7.1", diff --git a/packages/cli/src/LoadNodesAndCredentials.ts b/packages/cli/src/LoadNodesAndCredentials.ts index 7ccfc2558149c..b1e0ad2bc4e08 100644 --- a/packages/cli/src/LoadNodesAndCredentials.ts +++ b/packages/cli/src/LoadNodesAndCredentials.ts @@ -77,7 +77,7 @@ export class LoadNodesAndCredentials implements INodesAndCredentials { for (const nodeModulesDir of basePathsToScan) { await this.loadNodesFromNodeModules(nodeModulesDir, 'n8n-nodes-base'); - await this.loadNodesFromNodeModules(nodeModulesDir, 'n8n-nodes-langchain'); + await this.loadNodesFromNodeModules(nodeModulesDir, '@n8n/nodes-langchain'); } // Load nodes from any other `n8n-nodes-*` packages in the download directory diff --git a/packages/nodes-langchain/.editorconfig b/packages/nodes-langchain/.editorconfig new file mode 100644 index 0000000000000..cff7816e85b4d --- /dev/null +++ b/packages/nodes-langchain/.editorconfig @@ -0,0 +1,20 @@ +root = true + +[*] +charset = utf-8 +indent_style = tab +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[package.json] +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false + +[*.yml] +indent_style = space +indent_size = 2 diff --git a/packages/nodes-langchain/.eslintrc.js b/packages/nodes-langchain/.eslintrc.js new file mode 100644 index 0000000000000..5229bbe9644d1 --- /dev/null +++ b/packages/nodes-langchain/.eslintrc.js @@ -0,0 +1,155 @@ +const sharedOptions = require('@n8n_io/eslint-config/shared'); + +/** + * @type {import('@types/eslint').ESLint.ConfigData} + */ +module.exports = { + extends: ['@n8n_io/eslint-config/node'], + + ...sharedOptions(__dirname), + + ignorePatterns: ['index.js'], + + rules: { + // TODO: remove all the following rules + eqeqeq: 'warn', + 'id-denylist': 'warn', + 'import/extensions': 'warn', + 'import/order': 'warn', + 'prefer-spread': 'warn', + 'import/no-extraneous-dependencies': 'warn', + + '@typescript-eslint/naming-convention': ['error', { selector: 'memberLike', format: null }], + '@typescript-eslint/no-explicit-any': 'warn', //812 warnings, better to fix in separate PR + '@typescript-eslint/no-non-null-assertion': 'warn', //665 errors, better to fix in separate PR + '@typescript-eslint/no-unsafe-assignment': 'warn', //7084 problems, better to fix in separate PR + '@typescript-eslint/no-unsafe-call': 'warn', //541 errors, better to fix in separate PR + '@typescript-eslint/no-unsafe-member-access': 'warn', //4591 errors, better to fix in separate PR + '@typescript-eslint/no-unsafe-return': 'warn', //438 errors, better to fix in separate PR + '@typescript-eslint/no-unused-expressions': ['error', { allowTernary: true }], + '@typescript-eslint/restrict-template-expressions': 'warn', //1152 errors, better to fix in separate PR + '@typescript-eslint/unbound-method': 'warn', + '@typescript-eslint/ban-ts-comment': ['warn', { 'ts-ignore': true }], + '@typescript-eslint/prefer-nullish-coalescing': 'warn', + '@typescript-eslint/no-base-to-string': 'warn', + '@typescript-eslint/no-redundant-type-constituents': 'warn', + '@typescript-eslint/no-unused-vars': 'warn', + '@typescript-eslint/no-unsafe-argument': 'warn', + '@typescript-eslint/prefer-optional-chain': 'warn', + '@typescript-eslint/restrict-plus-operands': 'warn', + }, + + overrides: [ + { + files: ['./credentials/*.ts'], + plugins: ['eslint-plugin-n8n-nodes-base'], + rules: { + 'n8n-nodes-base/cred-class-field-authenticate-type-assertion': 'error', + 'n8n-nodes-base/cred-class-field-display-name-missing-oauth2': 'error', + 'n8n-nodes-base/cred-class-field-display-name-miscased': 'error', + 'n8n-nodes-base/cred-class-field-documentation-url-missing': 'error', + 'n8n-nodes-base/cred-class-field-name-missing-oauth2': 'error', + 'n8n-nodes-base/cred-class-field-name-unsuffixed': 'error', + 'n8n-nodes-base/cred-class-field-name-uppercase-first-char': 'error', + 'n8n-nodes-base/cred-class-field-properties-assertion': 'error', + 'n8n-nodes-base/cred-class-field-type-options-password-missing': 'error', + 'n8n-nodes-base/cred-class-name-missing-oauth2-suffix': 'error', + 'n8n-nodes-base/cred-class-name-unsuffixed': 'error', + 'n8n-nodes-base/cred-filename-against-convention': 'error', + }, + }, + { + files: ['./nodes/**/*.ts'], + plugins: ['eslint-plugin-n8n-nodes-base'], + rules: { + 'n8n-nodes-base/node-class-description-credentials-name-unsuffixed': 'error', + 'n8n-nodes-base/node-class-description-display-name-unsuffixed-trigger-node': 'error', + 'n8n-nodes-base/node-class-description-empty-string': 'error', + 'n8n-nodes-base/node-class-description-icon-not-svg': 'error', + 'n8n-nodes-base/node-class-description-inputs-wrong-regular-node': 'error', + 'n8n-nodes-base/node-class-description-inputs-wrong-trigger-node': 'error', + 'n8n-nodes-base/node-class-description-missing-subtitle': 'error', + 'n8n-nodes-base/node-class-description-non-core-color-present': 'error', + 'n8n-nodes-base/node-class-description-name-miscased': 'error', + 'n8n-nodes-base/node-class-description-name-unsuffixed-trigger-node': 'error', + 'n8n-nodes-base/node-class-description-outputs-wrong': 'error', + 'n8n-nodes-base/node-dirname-against-convention': 'error', + 'n8n-nodes-base/node-execute-block-double-assertion-for-items': 'error', + 'n8n-nodes-base/node-execute-block-wrong-error-thrown': 'error', + 'n8n-nodes-base/node-filename-against-convention': 'error', + 'n8n-nodes-base/node-param-array-type-assertion': 'error', + 'n8n-nodes-base/node-param-collection-type-unsorted-items': 'error', + 'n8n-nodes-base/node-param-color-type-unused': 'error', + 'n8n-nodes-base/node-param-default-missing': 'error', + 'n8n-nodes-base/node-param-default-wrong-for-boolean': 'error', + 'n8n-nodes-base/node-param-default-wrong-for-collection': 'error', + 'n8n-nodes-base/node-param-default-wrong-for-fixed-collection': 'error', + 'n8n-nodes-base/node-param-default-wrong-for-fixed-collection': 'error', + 'n8n-nodes-base/node-param-default-wrong-for-multi-options': 'error', + 'n8n-nodes-base/node-param-default-wrong-for-number': 'error', + 'n8n-nodes-base/node-param-default-wrong-for-simplify': 'error', + 'n8n-nodes-base/node-param-default-wrong-for-string': 'error', + 'n8n-nodes-base/node-param-description-boolean-without-whether': 'error', + 'n8n-nodes-base/node-param-description-comma-separated-hyphen': 'error', + 'n8n-nodes-base/node-param-description-empty-string': 'error', + 'n8n-nodes-base/node-param-description-excess-final-period': 'error', + 'n8n-nodes-base/node-param-description-excess-inner-whitespace': 'error', + 'n8n-nodes-base/node-param-description-identical-to-display-name': 'error', + 'n8n-nodes-base/node-param-description-line-break-html-tag': 'error', + 'n8n-nodes-base/node-param-description-lowercase-first-char': 'error', + 'n8n-nodes-base/node-param-description-miscased-id': 'error', + 'n8n-nodes-base/node-param-description-miscased-json': 'error', + 'n8n-nodes-base/node-param-description-miscased-url': 'error', + 'n8n-nodes-base/node-param-description-missing-final-period': 'error', + 'n8n-nodes-base/node-param-description-missing-for-ignore-ssl-issues': 'error', + 'n8n-nodes-base/node-param-description-missing-for-return-all': 'error', + 'n8n-nodes-base/node-param-description-missing-for-simplify': 'error', + 'n8n-nodes-base/node-param-description-missing-from-dynamic-multi-options': 'error', + 'n8n-nodes-base/node-param-description-missing-from-dynamic-options': 'error', + 'n8n-nodes-base/node-param-description-missing-from-limit': 'error', + 'n8n-nodes-base/node-param-description-unencoded-angle-brackets': 'error', + 'n8n-nodes-base/node-param-description-unneeded-backticks': 'error', + 'n8n-nodes-base/node-param-description-untrimmed': 'error', + 'n8n-nodes-base/node-param-description-url-missing-protocol': 'error', + 'n8n-nodes-base/node-param-description-weak': 'error', + 'n8n-nodes-base/node-param-description-wrong-for-dynamic-multi-options': 'error', + 'n8n-nodes-base/node-param-description-wrong-for-dynamic-options': 'error', + 'n8n-nodes-base/node-param-description-wrong-for-ignore-ssl-issues': 'error', + 'n8n-nodes-base/node-param-description-wrong-for-limit': 'error', + 'n8n-nodes-base/node-param-description-wrong-for-return-all': 'error', + 'n8n-nodes-base/node-param-description-wrong-for-simplify': 'error', + 'n8n-nodes-base/node-param-description-wrong-for-upsert': 'error', + 'n8n-nodes-base/node-param-display-name-excess-inner-whitespace': 'error', + 'n8n-nodes-base/node-param-display-name-miscased-id': 'error', + 'n8n-nodes-base/node-param-display-name-miscased': 'error', + 'n8n-nodes-base/node-param-display-name-not-first-position': 'error', + 'n8n-nodes-base/node-param-display-name-untrimmed': 'error', + 'n8n-nodes-base/node-param-display-name-wrong-for-dynamic-multi-options': 'error', + 'n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options': 'error', + 'n8n-nodes-base/node-param-display-name-wrong-for-simplify': 'error', + 'n8n-nodes-base/node-param-display-name-wrong-for-update-fields': 'error', + 'n8n-nodes-base/node-param-min-value-wrong-for-limit': 'error', + 'n8n-nodes-base/node-param-multi-options-type-unsorted-items': 'error', + 'n8n-nodes-base/node-param-name-untrimmed': 'error', + 'n8n-nodes-base/node-param-operation-option-action-wrong-for-get-many': 'error', + 'n8n-nodes-base/node-param-operation-option-description-wrong-for-get-many': 'error', + 'n8n-nodes-base/node-param-operation-option-without-action': 'error', + 'n8n-nodes-base/node-param-operation-without-no-data-expression': 'error', + 'n8n-nodes-base/node-param-option-description-identical-to-name': 'error', + 'n8n-nodes-base/node-param-option-name-containing-star': 'error', + 'n8n-nodes-base/node-param-option-name-duplicate': 'error', + 'n8n-nodes-base/node-param-option-name-wrong-for-get-many': 'error', + 'n8n-nodes-base/node-param-option-name-wrong-for-upsert': 'error', + 'n8n-nodes-base/node-param-option-value-duplicate': 'error', + 'n8n-nodes-base/node-param-options-type-unsorted-items': 'error', + 'n8n-nodes-base/node-param-placeholder-miscased-id': 'error', + 'n8n-nodes-base/node-param-placeholder-missing-email': 'error', + 'n8n-nodes-base/node-param-required-false': 'error', + 'n8n-nodes-base/node-param-resource-with-plural-option': 'error', + 'n8n-nodes-base/node-param-resource-without-no-data-expression': 'error', + 'n8n-nodes-base/node-param-type-options-missing-from-limit': 'error', + 'n8n-nodes-base/node-param-type-options-password-missing': 'error', + }, + }, + ], +}; diff --git a/packages/nodes-langchain/.gitignore b/packages/nodes-langchain/.gitignore new file mode 100644 index 0000000000000..7e729633290f4 --- /dev/null +++ b/packages/nodes-langchain/.gitignore @@ -0,0 +1,8 @@ +node_modules +.DS_Store +.tmp +tmp +dist +npm-debug.log* +yarn.lock +.vscode/launch.json diff --git a/packages/nodes-langchain/.npmignore b/packages/nodes-langchain/.npmignore new file mode 100644 index 0000000000000..a7ac611a60a9a --- /dev/null +++ b/packages/nodes-langchain/.npmignore @@ -0,0 +1,2 @@ +.DS_Store +*.tsbuildinfo diff --git a/packages/nodes-langchain/.prettierrc.js b/packages/nodes-langchain/.prettierrc.js new file mode 100644 index 0000000000000..ebf28d8091492 --- /dev/null +++ b/packages/nodes-langchain/.prettierrc.js @@ -0,0 +1,51 @@ +module.exports = { + /** + * https://prettier.io/docs/en/options.html#semicolons + */ + semi: true, + + /** + * https://prettier.io/docs/en/options.html#trailing-commas + */ + trailingComma: 'all', + + /** + * https://prettier.io/docs/en/options.html#bracket-spacing + */ + bracketSpacing: true, + + /** + * https://prettier.io/docs/en/options.html#tabs + */ + useTabs: true, + + /** + * https://prettier.io/docs/en/options.html#tab-width + */ + tabWidth: 2, + + /** + * https://prettier.io/docs/en/options.html#arrow-function-parentheses + */ + arrowParens: 'always', + + /** + * https://prettier.io/docs/en/options.html#quotes + */ + singleQuote: true, + + /** + * https://prettier.io/docs/en/options.html#quote-props + */ + quoteProps: 'as-needed', + + /** + * https://prettier.io/docs/en/options.html#end-of-line + */ + endOfLine: 'lf', + + /** + * https://prettier.io/docs/en/options.html#print-width + */ + printWidth: 100, +}; diff --git a/packages/nodes-langchain/.vscode/extensions.json b/packages/nodes-langchain/.vscode/extensions.json new file mode 100644 index 0000000000000..306a6af39a1b7 --- /dev/null +++ b/packages/nodes-langchain/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "dbaeumer.vscode-eslint", + "EditorConfig.EditorConfig", + "esbenp.prettier-vscode", + ] +} diff --git a/packages/nodes-langchain/LICENSE.md b/packages/nodes-langchain/LICENSE.md new file mode 100644 index 0000000000000..c1d74239754fd --- /dev/null +++ b/packages/nodes-langchain/LICENSE.md @@ -0,0 +1,85 @@ +# License + +Portions of this software are licensed as follows: + +- Content of branches other than the main branch (i.e. "master") are not licensed. +- All source code files that contain ".ee." in their filename are licensed under the + "n8n Enterprise License" defined in "LICENSE_EE.md". +- All third party components incorporated into the n8n Software are licensed under the original license + provided by the owner of the applicable component. +- Content outside of the above mentioned files or restrictions is available under the "Sustainable Use + License" as defined below. + +## Sustainable Use License + +Version 1.0 + +### Acceptance + +By using the software, you agree to all of the terms and conditions below. + +### Copyright License + +The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license +to use, copy, distribute, make available, and prepare derivative works of the software, in each case subject +to the limitations below. + +### Limitations + +You may use or modify the software only for your own internal business purposes or for non-commercial or +personal use. You may distribute the software or provide it to others only if you do so free of charge for +non-commercial purposes. You may not alter, remove, or obscure any licensing, copyright, or other notices of +the licensor in the software. Any use of the licensor’s trademarks is subject to applicable law. + +### Patents + +The licensor grants you a license, under any patent claims the licensor can license, or becomes able to +license, to make, have made, use, sell, offer for sale, import and have imported the software, in each case +subject to the limitations and conditions in this license. This license does not cover any patent claims that +you cause to be infringed by modifications or additions to the software. If you or your company make any +written claim that the software infringes or contributes to infringement of any patent, your patent license +for the software granted under these terms ends immediately. If your company makes such a claim, your patent +license ends immediately for work on behalf of your company. + +### Notices + +You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these +terms. If you modify the software, you must include in any modified copies of the software a prominent notice +stating that you have modified the software. + +### No Other Rights + +These terms do not imply any licenses other than those expressly granted in these terms. + +### Termination + +If you use the software in violation of these terms, such use is not licensed, and your license will +automatically terminate. If the licensor provides you with a notice of your violation, and you cease all +violation of this license no later than 30 days after you receive that notice, your license will be reinstated +retroactively. However, if you violate these terms after such reinstatement, any additional violation of these +terms will cause your license to terminate automatically and permanently. + +### No Liability + +As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will +not be liable to you for any damages arising out of these terms or the use or nature of the software, under +any kind of legal claim. + +### Definitions + +The “licensor” is the entity offering these terms. + +The “software” is the software the licensor makes available under these terms, including any portion of it. + +“You” refers to the individual or entity agreeing to these terms. + +“Your company” is any legal entity, sole proprietorship, or other kind of organization that you work for, plus +all organizations that have control over, are under the control of, or are under common control with that +organization. Control means ownership of substantially all the assets of an entity, or the power to direct its +management and policies by vote, contract, or otherwise. Control can be direct or indirect. + +“Your license” is the license granted to you for the software under these terms. + +“Use” means anything you do with the software requiring your license. + +“Trademark” means trademarks, service marks, and similar rights. diff --git a/packages/nodes-langchain/README.md b/packages/nodes-langchain/README.md new file mode 100644 index 0000000000000..2b76e49cc2b57 --- /dev/null +++ b/packages/nodes-langchain/README.md @@ -0,0 +1,11 @@ +![Banner image](https://user-images.githubusercontent.com/10284570/173569848-c624317f-42b1-45a6-ab09-f0ea3c247648.png) + +# n8n-nodes-langchain + +This repo contains nodes to use n8n in combination with [LangChain](https://langchain.com/). + +## License + +n8n is [fair-code](http://faircode.io) distributed under the [**Sustainable Use License**](https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE.md). + +Additional information about the license can be found in the [docs](https://docs.n8n.io/reference/license/). diff --git a/packages/nodes-langchain/credentials/CohereApi.credentials.ts b/packages/nodes-langchain/credentials/CohereApi.credentials.ts new file mode 100644 index 0000000000000..7fab31306275f --- /dev/null +++ b/packages/nodes-langchain/credentials/CohereApi.credentials.ts @@ -0,0 +1,45 @@ +import type { + IAuthenticateGeneric, + ICredentialTestRequest, + ICredentialType, + INodeProperties, +} from 'n8n-workflow'; + +export class CohereApi implements ICredentialType { + name = 'cohereApi'; + + displayName = 'CohereApi'; + + documentationUrl = 'cohereApi'; + + properties: INodeProperties[] = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string', + typeOptions: { password: true }, + required: true, + default: '', + }, + ]; + + authenticate: IAuthenticateGeneric = { + type: 'generic', + properties: { + headers: { + Authorization: '=Bearer {{$credentials.apiKey}}', + }, + }, + }; + + test: ICredentialTestRequest = { + request: { + baseURL: 'https://api.cohere.ai', + url: '/v1/detect-language', + method: 'POST', + body: { + texts: ['hello'], + }, + }, + }; +} diff --git a/packages/nodes-langchain/credentials/HuggingFaceApi.credentials.ts b/packages/nodes-langchain/credentials/HuggingFaceApi.credentials.ts new file mode 100644 index 0000000000000..ed9a8f30f549a --- /dev/null +++ b/packages/nodes-langchain/credentials/HuggingFaceApi.credentials.ts @@ -0,0 +1,41 @@ +import type { + IAuthenticateGeneric, + ICredentialTestRequest, + ICredentialType, + INodeProperties, +} from 'n8n-workflow'; + +export class HuggingFaceApi implements ICredentialType { + name = 'huggingFaceApi'; + + displayName = 'HuggingFaceApi'; + + documentationUrl = 'huggingFaceApi'; + + properties: INodeProperties[] = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string', + typeOptions: { password: true }, + required: true, + default: '', + }, + ]; + + authenticate: IAuthenticateGeneric = { + type: 'generic', + properties: { + headers: { + Authorization: '=Bearer {{$credentials.apiKey}}', + }, + }, + }; + + test: ICredentialTestRequest = { + request: { + baseURL: 'https://api-inference.huggingface.co', + url: '/models/gpt2', + }, + }; +} diff --git a/packages/nodes-langchain/credentials/MotorheadApi.credentials.ts b/packages/nodes-langchain/credentials/MotorheadApi.credentials.ts new file mode 100644 index 0000000000000..f9cecc49fe325 --- /dev/null +++ b/packages/nodes-langchain/credentials/MotorheadApi.credentials.ts @@ -0,0 +1,54 @@ +import type { + IAuthenticateGeneric, + ICredentialTestRequest, + ICredentialType, + INodeProperties, +} from 'n8n-workflow'; + +export class MotorheadApi implements ICredentialType { + name = 'motorheadApi'; + + displayName = 'MotorheadApi'; + + documentationUrl = 'motorheadApi'; + + properties: INodeProperties[] = [ + { + displayName: 'Host', + name: 'host', + required: true, + type: 'string', + default: '', + }, + { + displayName: 'API Key', + name: 'apiKey', + type: 'string', + typeOptions: { password: true }, + required: true, + default: '', + }, + { + displayName: 'Client ID', + name: 'clientId', + type: 'string', + default: '', + }, + ]; + + authenticate: IAuthenticateGeneric = { + type: 'generic', + properties: { + headers: { + 'x-metal-client-id': '={{$credentials.clientId}}', + 'x-metal-api-key': '={{$credentials.apiKey}}', + }, + }, + }; + + test: ICredentialTestRequest = { + request: { + baseURL: '={{$credentials.host}}', + }, + }; +} diff --git a/packages/nodes-langchain/credentials/SerpApi.credentials.ts b/packages/nodes-langchain/credentials/SerpApi.credentials.ts new file mode 100644 index 0000000000000..fc649655d6909 --- /dev/null +++ b/packages/nodes-langchain/credentials/SerpApi.credentials.ts @@ -0,0 +1,41 @@ +import type { + IAuthenticateGeneric, + ICredentialTestRequest, + ICredentialType, + INodeProperties, +} from 'n8n-workflow'; + +export class SerpApi implements ICredentialType { + name = 'serpApi'; + + displayName = 'SerpAPI'; + + documentationUrl = 'serpAi'; + + properties: INodeProperties[] = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string', + typeOptions: { password: true }, + required: true, + default: '', + }, + ]; + + authenticate: IAuthenticateGeneric = { + type: 'generic', + properties: { + qs: { + api_key: '={{$credentials.apiKey}}', + }, + }, + }; + + test: ICredentialTestRequest = { + request: { + baseURL: 'https://serpapi.com', + url: '/account.json ', + }, + }; +} diff --git a/packages/nodes-langchain/gulpfile.js b/packages/nodes-langchain/gulpfile.js new file mode 100644 index 0000000000000..831c707540ee2 --- /dev/null +++ b/packages/nodes-langchain/gulpfile.js @@ -0,0 +1,16 @@ +const path = require('path'); +const { task, src, dest } = require('gulp'); + +task('build:icons', copyIcons); + +function copyIcons() { + const nodeSource = path.resolve('nodes', '**', '*.{png,svg}'); + const nodeDestination = path.resolve('dist', 'nodes'); + + src(nodeSource).pipe(dest(nodeDestination)); + + const credSource = path.resolve('credentials', '**', '*.{png,svg}'); + const credDestination = path.resolve('dist', 'credentials'); + + return src(credSource).pipe(dest(credDestination)); +} diff --git a/packages/nodes-langchain/index.js b/packages/nodes-langchain/index.js new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/nodes-langchain/nodes/LangChain/LangChain.node.ts b/packages/nodes-langchain/nodes/LangChain/LangChain.node.ts new file mode 100644 index 0000000000000..f14fbefd6f570 --- /dev/null +++ b/packages/nodes-langchain/nodes/LangChain/LangChain.node.ts @@ -0,0 +1,104 @@ +import type { + IExecuteFunctions, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; +import { NodeOperationError } from 'n8n-workflow'; + +import type { Tool } from 'langchain/tools'; +import type { BaseChatMemory } from 'langchain/memory'; +import type { InitializeAgentExecutorOptions } from 'langchain/agents'; +import { initializeAgentExecutorWithOptions } from 'langchain/agents'; +import type { BaseLanguageModel } from 'langchain/dist/base_language'; + +export class LangChain implements INodeType { + description: INodeTypeDescription = { + displayName: 'LangChain', + name: 'langChain', + icon: 'fa:link', + group: ['transform'], + version: 1, + description: 'LangChain', + defaults: { + name: 'LangChain', + color: '#404040', + }, + // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node + inputs: ['main', 'tool', 'memory', 'languageModel'], + inputNames: ['', 'Tools', 'Memory', 'Language Model'], + outputs: ['main'], + credentials: [], + properties: [ + { + displayName: 'Text', + name: 'text', + type: 'string', + default: '', + }, + ], + }; + + async execute(this: IExecuteFunctions): Promise { + let memory: BaseChatMemory | undefined; + + const languageModelNodes = await this.getInputConnectionData(0, 0, 'languageModel'); + if (languageModelNodes.length === 0) { + throw new NodeOperationError( + this.getNode(), + 'At least one Language Model has to be connected!', + ); + } else if (languageModelNodes.length > 1) { + throw new NodeOperationError( + this.getNode(), + 'Only one Language Model is allowed to be connected!', + ); + } + const model = languageModelNodes[0].response as BaseLanguageModel; + + if (languageModelNodes.length === 0) { + throw new NodeOperationError( + this.getNode(), + 'At least one Language Model has to be connected!', + ); + } else if (languageModelNodes.length > 1) { + throw new NodeOperationError( + this.getNode(), + 'Only one Language Model is allowed to be connected!', + ); + } + + const memoryNodes = await this.getInputConnectionData(0, 0, 'memory'); + if (memoryNodes.length === 1) { + memory = memoryNodes[0].response as BaseChatMemory; + } else if (languageModelNodes.length > 1) { + throw new NodeOperationError(this.getNode(), 'Only one Memory is allowed to be connected!'); + } + + const toolNodes = await this.getInputConnectionData(0, 0, 'tool'); + const tools = toolNodes.map((connectedNode) => { + return connectedNode.response as Tool; + }); + + const options: InitializeAgentExecutorOptions = { + agentType: 'chat-conversational-react-description', + verbose: true, + memory, + }; + + const executor = await initializeAgentExecutorWithOptions(tools, model, options); + + const items = this.getInputData(); + const returnData: INodeExecutionData[] = []; + for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { + const text = this.getNodeParameter('text', itemIndex) as string; + + const response = await executor.call({ + input: text, + }); + + returnData.push({ json: { response } }); + } + return this.prepareOutputData(returnData); + } +} diff --git a/packages/nodes-langchain/nodes/LangChainLMCohere/LangChainLMCohere.node.ts b/packages/nodes-langchain/nodes/LangChainLMCohere/LangChainLMCohere.node.ts new file mode 100644 index 0000000000000..4be61f91082d0 --- /dev/null +++ b/packages/nodes-langchain/nodes/LangChainLMCohere/LangChainLMCohere.node.ts @@ -0,0 +1,57 @@ +/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ +import type { IExecuteFunctions, INodeType, INodeTypeDescription, SupplyData } from 'n8n-workflow'; + +import { Cohere } from 'langchain/llms/cohere'; + +export class LangChainLMCohere implements INodeType { + description: INodeTypeDescription = { + displayName: 'LangChain - Cohere', + // eslint-disable-next-line n8n-nodes-base/node-class-description-name-miscased + name: 'langChainLMCohere', + icon: 'file:cohere.svg', + group: ['transform'], + version: 1, + description: 'Language Model Cohere', + defaults: { + name: 'LangChain - Cohere', + }, + // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node + inputs: [], + // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong + outputs: ['languageModel'], + outputNames: ['Language Model'], + credentials: [ + { + name: 'cohereApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Maximum Tokens', + name: 'maxTokens', + default: 20, + typeOptions: { minValue: 0 }, + description: 'Maximum amount of tokens to use for the completion', + type: 'number', + }, + ], + }; + + async supplyData(this: IExecuteFunctions): Promise { + const credentials = await this.getCredentials('cohereApi'); + + const itemIndex = 0; + + const maxTokens = this.getNodeParameter('maxTokens', itemIndex) as number; + + const model = new Cohere({ + maxTokens, + apiKey: credentials.apiKey as string, + }); + + return { + response: model, + }; + } +} diff --git a/packages/nodes-langchain/nodes/LangChainLMCohere/cohere.svg b/packages/nodes-langchain/nodes/LangChainLMCohere/cohere.svg new file mode 100644 index 0000000000000..abe70db29b052 --- /dev/null +++ b/packages/nodes-langchain/nodes/LangChainLMCohere/cohere.svg @@ -0,0 +1,93 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/packages/nodes-langchain/nodes/LangChainLMOpenAi/LangChainLMOpenAi.node.ts b/packages/nodes-langchain/nodes/LangChainLMOpenAi/LangChainLMOpenAi.node.ts new file mode 100644 index 0000000000000..d1a7f77aa9e66 --- /dev/null +++ b/packages/nodes-langchain/nodes/LangChainLMOpenAi/LangChainLMOpenAi.node.ts @@ -0,0 +1,83 @@ +/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ +import type { IExecuteFunctions, INodeType, INodeTypeDescription, SupplyData } from 'n8n-workflow'; + +import { ChatOpenAI } from 'langchain/chat_models/openai'; +export class LangChainLMOpenAi implements INodeType { + description: INodeTypeDescription = { + displayName: 'LangChain - OpenAI', + // eslint-disable-next-line n8n-nodes-base/node-class-description-name-miscased + name: 'langChainLMOpenAi', + icon: 'file:openAi.svg', + group: ['transform'], + version: 1, + description: 'Language Model OpenAI', + defaults: { + name: 'LangChain - OpenAI', + }, + // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node + inputs: [], + // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong + outputs: ['languageModel'], + outputNames: ['Language Model'], + credentials: [ + { + name: 'openAiApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Model45', + name: 'model', + type: 'options', + noDataExpression: true, + options: [ + { + name: 'GTP 3.5 Turbo', + value: 'gpt-3.5-turbo', + }, + { + name: 'DaVinci-003', + value: 'text-davinci-003', + }, + ], + default: 'gpt-3.5-turbo', + }, + { + displayName: 'Sampling Temperature', + name: 'temperature', + default: 0.7, + typeOptions: { maxValue: 1, minValue: 0, numberPrecision: 1 }, + description: + 'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive.', + type: 'number', + routing: { + send: { + type: 'body', + property: 'temperature', + }, + }, + }, + ], + }; + + async supplyData(this: IExecuteFunctions): Promise { + const credentials = await this.getCredentials('openAiApi'); + + const itemIndex = 0; + + // TODO: Should it get executed once per item or not? + const modelName = this.getNodeParameter('model', itemIndex) as string; + const temperature = this.getNodeParameter('temperature', itemIndex) as number; + + const model = new ChatOpenAI({ + openAIApiKey: credentials.apiKey as string, + modelName, + temperature, + }); + + return { + response: model, + }; + } +} diff --git a/packages/nodes-langchain/nodes/LangChainLMOpenAi/openAi.svg b/packages/nodes-langchain/nodes/LangChainLMOpenAi/openAi.svg new file mode 100644 index 0000000000000..d9445723fffb7 --- /dev/null +++ b/packages/nodes-langchain/nodes/LangChainLMOpenAi/openAi.svg @@ -0,0 +1,7 @@ + + + OpenAI + + + + diff --git a/packages/nodes-langchain/nodes/LangChainLMOpenHuggingFaceInference/LangChainLMOpenHuggingFaceInference.node.ts b/packages/nodes-langchain/nodes/LangChainLMOpenHuggingFaceInference/LangChainLMOpenHuggingFaceInference.node.ts new file mode 100644 index 0000000000000..761682cbf1e1d --- /dev/null +++ b/packages/nodes-langchain/nodes/LangChainLMOpenHuggingFaceInference/LangChainLMOpenHuggingFaceInference.node.ts @@ -0,0 +1,75 @@ +/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ +import type { IExecuteFunctions, INodeType, INodeTypeDescription, SupplyData } from 'n8n-workflow'; + +import { HuggingFaceInference } from 'langchain/llms/hf'; + +export class LangChainLMOpenHuggingFaceInference implements INodeType { + description: INodeTypeDescription = { + displayName: 'LangChain - HuggingFaceInference', + // eslint-disable-next-line n8n-nodes-base/node-class-description-name-miscased + name: 'langChainLMOpenHuggingFaceInference', + icon: 'file:huggingface.svg', + group: ['transform'], + version: 1, + description: 'Language Model HuggingFaceInference', + defaults: { + name: 'LangChain - HuggingFaceInference', + }, + // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node + inputs: [], + // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong + outputs: ['languageModel'], + outputNames: ['Language Model'], + credentials: [ + { + name: 'huggingFaceApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Model', + name: 'model', + type: 'options', + noDataExpression: true, + options: [ + { + name: 'GTP 2', + value: 'gpt2', + }, + ], + default: 'gpt2', + }, + { + displayName: 'Sampling Temperature', + name: 'temperature', + default: 0.7, + typeOptions: { maxValue: 1, minValue: 0, numberPrecision: 1 }, + description: + 'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive.', + type: 'number', + }, + ], + }; + + async supplyData(this: IExecuteFunctions): Promise { + const credentials = await this.getCredentials('huggingFaceApi'); + + const itemIndex = 0; + + // TODO: Should it get executed once per item or not? + const modelName = this.getNodeParameter('model', itemIndex) as string; + const temperature = this.getNodeParameter('temperature', itemIndex) as number; + + const model = new HuggingFaceInference({ + model: modelName, + apiKey: credentials.apiKey as string, + temperature, + maxTokens: 100, + }); + + return { + response: model, + }; + } +} diff --git a/packages/nodes-langchain/nodes/LangChainLMOpenHuggingFaceInference/huggingface.svg b/packages/nodes-langchain/nodes/LangChainLMOpenHuggingFaceInference/huggingface.svg new file mode 100644 index 0000000000000..ab959d165fa5b --- /dev/null +++ b/packages/nodes-langchain/nodes/LangChainLMOpenHuggingFaceInference/huggingface.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/packages/nodes-langchain/nodes/LangChainMemoryMotorhead/LangChainMemoryMotorhead.node.ts b/packages/nodes-langchain/nodes/LangChainMemoryMotorhead/LangChainMemoryMotorhead.node.ts new file mode 100644 index 0000000000000..01de2cba032bf --- /dev/null +++ b/packages/nodes-langchain/nodes/LangChainMemoryMotorhead/LangChainMemoryMotorhead.node.ts @@ -0,0 +1,65 @@ +/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ +import type { IExecuteFunctions, INodeType, INodeTypeDescription, SupplyData } from 'n8n-workflow'; + +import { MotorheadMemory } from 'langchain/memory'; + +export class LangChainMemoryMotorhead implements INodeType { + description: INodeTypeDescription = { + displayName: 'LangChain - Motorhead', + name: 'langChainMemoryMotorhead', + icon: 'fa:file-export', + group: ['transform'], + version: 1, + description: 'Motorhead Memory', + defaults: { + name: 'LangChain - Motorhead', + color: '#303030', + }, + // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node + inputs: [], + // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong + outputs: ['memory'], + outputNames: ['Memory'], + credentials: [ + { + name: 'motorheadApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Memory Key', + name: 'memoryKey', + type: 'string', + default: '', + }, + { + displayName: 'Session ID', + name: 'sessionId', + type: 'string', + default: '', + }, + ], + }; + + async supplyData(this: IExecuteFunctions): Promise { + const credentials = await this.getCredentials('motorheadApi'); + + const itemIndex = 0; + + // TODO: Should it get executed once per item or not? + const memoryKey = this.getNodeParameter('memoryKey', itemIndex) as string; + const sessionId = this.getNodeParameter('sessionId', itemIndex) as string; + + return { + // TODO: Does not work yet + response: new MotorheadMemory({ + memoryKey, + sessionId, + url: credentials.host as string, + clientId: credentials.clientId as string, + apiKey: credentials.apiKey as string, + }), + }; + } +} diff --git a/packages/nodes-langchain/nodes/LangChainToolCalculator/LangChainToolCalculator.node.ts b/packages/nodes-langchain/nodes/LangChainToolCalculator/LangChainToolCalculator.node.ts new file mode 100644 index 0000000000000..6a03748624d05 --- /dev/null +++ b/packages/nodes-langchain/nodes/LangChainToolCalculator/LangChainToolCalculator.node.ts @@ -0,0 +1,30 @@ +/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ +import type { IExecuteFunctions, INodeType, INodeTypeDescription, SupplyData } from 'n8n-workflow'; +import { Calculator } from 'langchain/tools/calculator'; + +export class LangChainToolCalculator implements INodeType { + description: INodeTypeDescription = { + displayName: 'LangChain - Calculator', + name: 'langChainToolCalculator', + icon: 'fa:calculator', + group: ['transform'], + version: 1, + description: 'Calculator', + defaults: { + name: 'LangChain - Calculator', + color: '#400080', + }, + // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node + inputs: [], + // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong + outputs: ['tool'], + outputNames: ['Tool'], + properties: [], + }; + + async supplyData(this: IExecuteFunctions): Promise { + return { + response: new Calculator(), + }; + } +} diff --git a/packages/nodes-langchain/nodes/LangChainToolSerpApi/LangChainToolSerpApi.node.ts b/packages/nodes-langchain/nodes/LangChainToolSerpApi/LangChainToolSerpApi.node.ts new file mode 100644 index 0000000000000..37b271f13b51c --- /dev/null +++ b/packages/nodes-langchain/nodes/LangChainToolSerpApi/LangChainToolSerpApi.node.ts @@ -0,0 +1,40 @@ +/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ +import type { IExecuteFunctions, INodeType, INodeTypeDescription, SupplyData } from 'n8n-workflow'; + +import { SerpAPI } from 'langchain/tools'; + +export class LangChainToolSerpApi implements INodeType { + description: INodeTypeDescription = { + displayName: 'LangChain - SerpAPI', + name: 'langChainToolSerpApi', + icon: 'file:google.svg', + group: ['transform'], + version: 1, + description: 'Search in Google', + defaults: { + name: 'LangChain - SerpAPI', + // eslint-disable-next-line n8n-nodes-base/node-class-description-non-core-color-present + color: '#400080', + }, + // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node + inputs: [], + // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong + outputs: ['tool'], + outputNames: ['Tool'], + credentials: [ + { + name: 'serpApi', + required: true, + }, + ], + properties: [], + }; + + async supplyData(this: IExecuteFunctions): Promise { + const credentials = await this.getCredentials('serpApi'); + + return { + response: new SerpAPI(credentials.apiKey as string), + }; + } +} diff --git a/packages/nodes-langchain/nodes/LangChainToolSerpApi/google.svg b/packages/nodes-langchain/nodes/LangChainToolSerpApi/google.svg new file mode 100644 index 0000000000000..4cf163bfe2478 --- /dev/null +++ b/packages/nodes-langchain/nodes/LangChainToolSerpApi/google.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/nodes-langchain/nodes/LangChainToolWikipedia/LangChainToolWikipedia.node.ts b/packages/nodes-langchain/nodes/LangChainToolWikipedia/LangChainToolWikipedia.node.ts new file mode 100644 index 0000000000000..3224a915c55ef --- /dev/null +++ b/packages/nodes-langchain/nodes/LangChainToolWikipedia/LangChainToolWikipedia.node.ts @@ -0,0 +1,31 @@ +/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ +import type { IExecuteFunctions, INodeType, INodeTypeDescription, SupplyData } from 'n8n-workflow'; +import { WikipediaQueryRun } from 'langchain/tools'; + +export class LangChainToolWikipedia implements INodeType { + description: INodeTypeDescription = { + displayName: 'LangChain - Wikipedia', + name: 'langChainToolWikipedia', + icon: 'file:wikipedia.svg', + group: ['transform'], + version: 1, + description: 'Search in Wikipedia', + defaults: { + name: 'LangChain - Wikipedia', + // eslint-disable-next-line n8n-nodes-base/node-class-description-non-core-color-present + color: '#400080', + }, + // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node + inputs: [], + // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong + outputs: ['tool'], + outputNames: ['Tool'], + properties: [], + }; + + async supplyData(this: IExecuteFunctions): Promise { + return { + response: new WikipediaQueryRun(), + }; + } +} diff --git a/packages/nodes-langchain/nodes/LangChainToolWikipedia/wikipedia.svg b/packages/nodes-langchain/nodes/LangChainToolWikipedia/wikipedia.svg new file mode 100644 index 0000000000000..bfa60b37e6bfa --- /dev/null +++ b/packages/nodes-langchain/nodes/LangChainToolWikipedia/wikipedia.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/nodes-langchain/package.json b/packages/nodes-langchain/package.json new file mode 100644 index 0000000000000..ddc1e74c9f66b --- /dev/null +++ b/packages/nodes-langchain/package.json @@ -0,0 +1,65 @@ +{ + "name": "@n8n/nodes-langchain", + "version": "0.1.0", + "description": "", + "keywords": [ + "n8n-community-node-package" + ], + "license": "MIT", + "homepage": "", + "author": { + "name": "", + "email": "" + }, + "repository": { + "type": "git", + "url": "https://github.com/n8n-io/n8n-nodes-langchain.git" + }, + "main": "index.js", + "scripts": { + "build": "tsc && gulp build:icons", + "dev": "tsc --watch", + "format": "prettier nodes credentials --write", + "lint": "eslint nodes credentials package.json", + "lintfix": "eslint nodes credentials package.json --fix", + "prepublishOnly": "npm run build && npm run lint -c .eslintrc.prepublish.js nodes credentials package.json" + }, + "files": [ + "dist" + ], + "n8n": { + "n8nNodesApiVersion": 1, + "credentials": [ + "dist/credentials/CohereApi.credentials.js", + "dist/credentials/HuggingFaceApi.credentials.js", + "dist/credentials/MotorheadApi.credentials.js", + "dist/credentials/SerpApi.credentials.js" + ], + "nodes": [ + "dist/nodes/LangChain/LangChain.node.js", + "dist/nodes/LangChainLMOpenAi/LangChainLMOpenAi.node.js", + "dist/nodes/LangChainLMCohere/LangChainLMCohere.node.js", + "dist/nodes/LangChainLMOpenHuggingFaceInference/LangChainLMOpenHuggingFaceInference.node.js", + "dist/nodes/LangChainMemoryMotorhead/LangChainMemoryMotorhead.node.js", + "dist/nodes/LangChainToolCalculator/LangChainToolCalculator.node.js", + "dist/nodes/LangChainToolSerpApi/LangChainToolSerpApi.node.js", + "dist/nodes/LangChainToolWikipedia/LangChainToolWikipedia.node.js" + ] + }, + "devDependencies": { + "@types/express": "^4.17.6", + "@types/request-promise-native": "~1.0.15", + "@typescript-eslint/parser": "~5.45", + "eslint-plugin-n8n-nodes-base": "^1.11.0", + "gulp": "^4.0.2", + "n8n-core": "*", + "n8n-workflow": "*", + "prettier": "^2.7.1", + "typescript": "~4.8.4" + }, + "dependencies": { + "@huggingface/inference": "1", + "cohere-ai": "^6.2.2", + "langchain": "^0.0.126" + } +} diff --git a/packages/nodes-langchain/tsconfig.json b/packages/nodes-langchain/tsconfig.json new file mode 100644 index 0000000000000..7469d24ec9dd7 --- /dev/null +++ b/packages/nodes-langchain/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "strict": true, + "module": "commonjs", + "moduleResolution": "node", + "target": "es2019", + "lib": ["es2019", "es2020", "es2022.error"], + "removeComments": true, + "useUnknownInCatchVariables": false, + "forceConsistentCasingInFileNames": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noUnusedLocals": true, + "strictNullChecks": true, + "preserveConstEnums": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "incremental": true, + "declaration": true, + "sourceMap": true, + "skipLibCheck": true, + "outDir": "./dist/", + }, + "include": [ + "credentials/**/*", + "nodes/**/*", + "nodes/**/*.json", + "package.json", + ], +} diff --git a/packages/nodes-langchain/tslint.json b/packages/nodes-langchain/tslint.json new file mode 100644 index 0000000000000..f03bbfee37151 --- /dev/null +++ b/packages/nodes-langchain/tslint.json @@ -0,0 +1,127 @@ +{ + "linterOptions": { + "exclude": [ + "node_modules/**/*" + ] + }, + "defaultSeverity": "error", + "jsRules": {}, + "rules": { + "array-type": [ + true, + "array-simple" + ], + "arrow-return-shorthand": true, + "ban": [ + true, + { + "name": "Array", + "message": "tsstyle#array-constructor" + } + ], + "ban-types": [ + true, + [ + "Object", + "Use {} instead." + ], + [ + "String", + "Use 'string' instead." + ], + [ + "Number", + "Use 'number' instead." + ], + [ + "Boolean", + "Use 'boolean' instead." + ] + ], + "class-name": true, + "curly": [ + true, + "ignore-same-line" + ], + "forin": true, + "jsdoc-format": true, + "label-position": true, + "indent": [ + true, + "tabs", + 2 + ], + "member-access": [ + true, + "no-public" + ], + "new-parens": true, + "no-angle-bracket-type-assertion": true, + "no-any": true, + "no-arg": true, + "no-conditional-assignment": true, + "no-construct": true, + "no-debugger": true, + "no-default-export": true, + "no-duplicate-variable": true, + "no-inferrable-types": true, + "ordered-imports": [ + true, + { + "import-sources-order": "any", + "named-imports-order": "case-insensitive" + } + ], + "no-namespace": [ + true, + "allow-declarations" + ], + "no-reference": true, + "no-string-throw": true, + "no-unused-expression": true, + "no-var-keyword": true, + "object-literal-shorthand": true, + "only-arrow-functions": [ + true, + "allow-declarations", + "allow-named-functions" + ], + "prefer-const": true, + "radix": true, + "semicolon": [ + true, + "always", + "ignore-bound-class-methods" + ], + "switch-default": true, + "trailing-comma": [ + true, + { + "multiline": { + "objects": "always", + "arrays": "always", + "functions": "always", + "typeLiterals": "ignore" + }, + "esSpecCompliant": true + } + ], + "triple-equals": [ + true, + "allow-null-check" + ], + "use-isnan": true, + "quotes": [ + "error", + "single" + ], + "variable-name": [ + true, + "check-format", + "ban-keywords", + "allow-leading-underscore", + "allow-trailing-underscore" + ] + }, + "rulesDirectory": [] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0746a576ba825..0ddb67d5f508a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -193,6 +193,9 @@ importers: '@n8n/client-oauth2': specifier: workspace:* version: link:../@n8n/client-oauth2 + '@n8n/nodes-langchain': + specifier: workspace:* + version: link:../nodes-langchain '@n8n_io/license-sdk': specifier: ~2.4.0 version: 2.4.0 @@ -346,9 +349,6 @@ importers: n8n-nodes-base: specifier: workspace:* version: link:../nodes-base - n8n-nodes-langchain: - specifier: workspace:* - version: link:../nodes-langchain n8n-workflow: specifier: workspace:* version: link:../workflow From 06a8b6fb67acf92ae13367a9be1a9e1e8df8dc19 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 14 Aug 2023 12:28:07 +0200 Subject: [PATCH 023/372] :zap: Improve design --- packages/editor-ui/src/components/Node.vue | 14 +- packages/editor-ui/src/mixins/nodeBase.ts | 35 ++-- .../editor-ui/src/stores/nodeTypes.store.ts | 8 + packages/editor-ui/src/utils/nodeViewUtils.ts | 154 ++++++++---------- 4 files changed, 108 insertions(+), 103 deletions(-) diff --git a/packages/editor-ui/src/components/Node.vue b/packages/editor-ui/src/components/Node.vue index 08ca458cc0972..393ee7d4c935d 100644 --- a/packages/editor-ui/src/components/Node.vue +++ b/packages/editor-ui/src/components/Node.vue @@ -1,6 +1,10 @@ + + + + @@ -137,6 +141,7 @@ import { VALUE_SURVEY_MODAL_KEY, VERSIONS_MODAL_KEY, WORKFLOW_ACTIVE_MODAL_KEY, + WORKFLOW_LM_CHAT_MODAL_KEY, WORKFLOW_SETTINGS_MODAL_KEY, WORKFLOW_SHARE_MODAL_KEY, IMPORT_CURL_MODAL_KEY, @@ -160,6 +165,7 @@ import PersonalizationModal from './PersonalizationModal.vue'; import TagsManager from './TagsManager/TagsManager.vue'; import UpdatesPanel from './UpdatesPanel.vue'; import ValueSurvey from './ValueSurvey.vue'; +import WorkflowLMChat from './WorkflowLMChat.vue'; import WorkflowSettings from './WorkflowSettings.vue'; import DeleteUserModal from './DeleteUserModal.vue'; import ActivationModal from './ActivationModal.vue'; @@ -189,6 +195,7 @@ export default defineComponent({ TagsManager, UpdatesPanel, ValueSurvey, + WorkflowLMChat, WorkflowSettings, WorkflowShareModal, ImportCurlModal, @@ -211,6 +218,7 @@ export default defineComponent({ INVITE_USER_MODAL_KEY, TAGS_MANAGER_MODAL_KEY, VERSIONS_MODAL_KEY, + WORKFLOW_LM_CHAT_MODAL_KEY, WORKFLOW_SETTINGS_MODAL_KEY, WORKFLOW_SHARE_MODAL_KEY, VALUE_SURVEY_MODAL_KEY, diff --git a/packages/editor-ui/src/components/NodeExecuteButton.vue b/packages/editor-ui/src/components/NodeExecuteButton.vue index bc462189f947d..ef639dc7722fd 100644 --- a/packages/editor-ui/src/components/NodeExecuteButton.vue +++ b/packages/editor-ui/src/components/NodeExecuteButton.vue @@ -224,7 +224,7 @@ export default defineComponent({ this.$telemetry.track('User clicked execute node button', telemetryPayload); await this.$externalHooks().run('nodeExecuteButton.onClick', telemetryPayload); - await this.runWorkflow(this.nodeName, 'RunData.ExecuteNodeButton'); + await this.runWorkflow({ destinationNode: this.nodeName, source: 'RunData.ExecuteNodeButton' }); this.$emit('execute'); } } diff --git a/packages/editor-ui/src/components/WorkflowLMChat.vue b/packages/editor-ui/src/components/WorkflowLMChat.vue new file mode 100644 index 0000000000000..6467f851a349b --- /dev/null +++ b/packages/editor-ui/src/components/WorkflowLMChat.vue @@ -0,0 +1,248 @@ + + + + + diff --git a/packages/editor-ui/src/constants.ts b/packages/editor-ui/src/constants.ts index ec659f2fa7391..84c370cbc8927 100644 --- a/packages/editor-ui/src/constants.ts +++ b/packages/editor-ui/src/constants.ts @@ -35,6 +35,7 @@ export const DUPLICATE_MODAL_KEY = 'duplicate'; export const TAGS_MANAGER_MODAL_KEY = 'tagsManager'; export const VERSIONS_MODAL_KEY = 'versions'; export const WORKFLOW_SETTINGS_MODAL_KEY = 'settings'; +export const WORKFLOW_LM_CHAT_MODAL_KEY = 'lmChat'; export const WORKFLOW_SHARE_MODAL_KEY = 'workflowShare'; export const PERSONALIZATION_MODAL_KEY = 'personalization'; export const CONTACT_PROMPT_MODAL_KEY = 'contactPrompt'; @@ -154,6 +155,8 @@ export const NODES_USING_CODE_NODE_EDITOR = [ '@n8n/nodes-langchain.dynamicTool', ]; +export const NODE_TRIGGER_CHAT_BUTTON = '@n8n/nodes-langchain.manualChatTrigger'; + export const PIN_DATA_NODE_TYPES_DENYLIST = [SPLIT_IN_BATCHES_NODE_TYPE]; // Node creator diff --git a/packages/editor-ui/src/mixins/workflowRun.ts b/packages/editor-ui/src/mixins/workflowRun.ts index 170f2ed0c5f98..12e6becc4a1e8 100644 --- a/packages/editor-ui/src/mixins/workflowRun.ts +++ b/packages/editor-ui/src/mixins/workflowRun.ts @@ -2,7 +2,7 @@ import { defineComponent } from 'vue'; import { mapStores } from 'pinia'; import type { IExecutionPushResponse, IExecutionResponse, IStartRunData } from '@/Interface'; -import type { IRunData, IRunExecutionData, IWorkflowBase } from 'n8n-workflow'; +import type { IRunData, IRunExecutionData, ITaskData, IWorkflowBase } from 'n8n-workflow'; import { NodeHelpers, TelemetryHelpers } from 'n8n-workflow'; import { externalHooks } from '@/mixins/externalHooks'; @@ -57,9 +57,11 @@ export const workflowRun = defineComponent({ return response; }, - async runWorkflow( - nodeName?: string, - source?: string, + + async runWorkflow(options: + { destinationNode: string, source?: string } | + { triggerNode: string, nodeData: ITaskData, source?: string } | + { source?: string } ): Promise { const workflow = this.getCurrentWorkflow(); @@ -76,7 +78,7 @@ export const workflowRun = defineComponent({ const issuesExist = this.workflowsStore.nodesIssuesExist; if (issuesExist === true) { // If issues exist get all of the issues of all nodes - const workflowIssues = this.checkReadyForExecution(workflow, nodeName); + const workflowIssues = this.checkReadyForExecution(workflow, options.destinationNode); if (workflowIssues !== null) { const errorMessages = []; let nodeIssues: string[]; @@ -115,13 +117,13 @@ export const workflowRun = defineComponent({ duration: 0, }); this.titleSet(workflow.name as string, 'ERROR'); - void this.$externalHooks().run('workflowRun.runError', { errorMessages, nodeName }); + void this.$externalHooks().run('workflowRun.runError', { errorMessages, nodeName: options.destinationNode }); await this.getWorkflowDataToSave().then((workflowData) => { this.$telemetry.track('Workflow execution preflight failed', { workflow_id: workflow.id, workflow_name: workflow.name, - execution_type: nodeName ? 'node' : 'workflow', + execution_type: (options.destinationNode || options.triggerNode) ? 'node' : 'workflow', node_graph_string: JSON.stringify( TelemetryHelpers.generateNodesGraph( workflowData as IWorkflowBase, @@ -138,8 +140,8 @@ export const workflowRun = defineComponent({ // Get the direct parents of the node let directParentNodes: string[] = []; - if (nodeName !== undefined) { - directParentNodes = workflow.getParentNodes(nodeName, 'main', 1); + if (options.destinationNode !== undefined) { + directParentNodes = workflow.getParentNodes(options.destinationNode, 'main', 1); } const runData = this.workflowsStore.getWorkflowRunData; @@ -181,8 +183,16 @@ export const workflowRun = defineComponent({ } } - if (startNodes.length === 0 && nodeName !== undefined) { - startNodes.push(nodeName); + let executedNode: string | undefined; + if (startNodes.length === 0 && 'destinationNode' in options && options.destinationNode !== undefined) { + executedNode = options.destinationNode; + startNodes.push(options.destinationNode); + } else if ('triggerNode' in options && 'nodeData' in options) { + startNodes.push.apply(startNodes, workflow.getChildNodes(options.triggerNode, 'main', 1)); + newRunData = { + [options.triggerNode]: [options.nodeData], + }; + executedNode = options.triggerNode; } const isNewWorkflow = this.workflowsStore.isNewWorkflow; @@ -199,8 +209,8 @@ export const workflowRun = defineComponent({ pinData: workflowData.pinData, startNodes, }; - if (nodeName) { - startRunData.destinationNode = nodeName; + if ('destinationNode' in options) { + startRunData.destinationNode = options.destinationNode; } // Init the execution data to represent the start of the execution @@ -213,7 +223,7 @@ export const workflowRun = defineComponent({ startedAt: new Date(), stoppedAt: undefined, workflowId: workflow.id, - executedNode: nodeName, + executedNode, data: { resultData: { runData: newRunData || {}, @@ -236,7 +246,7 @@ export const workflowRun = defineComponent({ const runWorkflowApiResponse = await this.runWorkflowApi(startRunData); - await this.$externalHooks().run('workflowRun.runWorkflow', { nodeName, source }); + await this.$externalHooks().run('workflowRun.runWorkflow', { nodeName: options.destinationNode, source: options.source }); return runWorkflowApiResponse; } catch (error) { diff --git a/packages/editor-ui/src/stores/ui.store.ts b/packages/editor-ui/src/stores/ui.store.ts index f3fe6c6b4a85b..d42d4f213c736 100644 --- a/packages/editor-ui/src/stores/ui.store.ts +++ b/packages/editor-ui/src/stores/ui.store.ts @@ -26,6 +26,7 @@ import { VERSIONS_MODAL_KEY, VIEWS, WORKFLOW_ACTIVE_MODAL_KEY, + WORKFLOW_LM_CHAT_MODAL_KEY, WORKFLOW_SETTINGS_MODAL_KEY, WORKFLOW_SHARE_MODAL_KEY, SOURCE_CONTROL_PUSH_MODAL_KEY, @@ -100,6 +101,9 @@ export const useUIStore = defineStore(STORES.UI, { [VERSIONS_MODAL_KEY]: { open: false, }, + [WORKFLOW_LM_CHAT_MODAL_KEY]: { + open: false, + }, [WORKFLOW_SETTINGS_MODAL_KEY]: { open: false, }, diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index f804ccf38c7cc..ba0e22294b6fb 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -121,6 +121,17 @@ /> + + 0; }, + containsChatNodes(): boolean { + return !!this.nodes.find((node)=> node.type === NODE_TRIGGER_CHAT_BUTTON); + }, isExecutionDisabled(): boolean { return !this.containsTrigger || this.allTriggersDisabled; }, @@ -682,7 +698,15 @@ export default defineComponent({ }; this.$telemetry.track('User clicked execute node button', telemetryPayload); void this.$externalHooks().run('nodeView.onRunNode', telemetryPayload); - void this.runWorkflow(nodeName, source); + void this.runWorkflow({destinationNode: nodeName, source}); + }, + async onOpenChat() { + const telemetryPayload = { + workflow_id: this.workflowsStore.workflowId, + }; + this.$telemetry.track('User clicked chat open button', telemetryPayload); + void this.$externalHooks().run('nodeView.onOpenChat', telemetryPayload); + this.uiStore.openModal(WORKFLOW_LM_CHAT_MODAL_KEY); }, async onRunWorkflow() { void this.getWorkflowDataToSave().then((workflowData) => { @@ -697,7 +721,7 @@ export default defineComponent({ void this.$externalHooks().run('nodeView.onRunWorkflow', telemetryPayload); }); - await this.runWorkflow(); + await this.runWorkflow({}); }, onRunContainerClick() { if (this.containsTrigger && !this.allTriggersDisabled) return; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9c08fc85e5fa9..e3598f25dc55d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.0' +lockfileVersion: '6.1' settings: autoInstallPeers: true @@ -155,6 +155,9 @@ importers: langchain: specifier: ^0.0.126 version: 0.0.126(@huggingface/inference@1.8.0)(cohere-ai@6.2.2)(ignore@5.2.4) + lodash: + specifier: ^4.17.21 + version: 4.17.21 n8n-nodes-base: specifier: workspace:* version: link:../../nodes-base @@ -181,8 +184,8 @@ importers: specifier: '*' version: link:../../workflow prettier: - specifier: ^2.7.1 - version: 2.8.8 + specifier: ^3.0.0 + version: 3.0.0 typescript: specifier: ^5.1.6 version: 5.1.6 @@ -8148,7 +8151,7 @@ packages: dependencies: '@xata.io/client': 0.25.2(typescript@5.1.6) case: 1.6.3 - prettier: 2.8.8 + prettier: 3.0.0 ts-morph: 19.0.0 typescript: 5.1.6 zod: 3.21.4 From 97d5906aae3320211e6bbaa6f49af8cf87f55257 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 21 Aug 2023 18:31:24 +0200 Subject: [PATCH 070/372] :zap: Do not start workflow from Chat Trigger Node, only via Chat --- packages/editor-ui/src/constants.ts | 5 +++-- packages/editor-ui/src/views/NodeView.vue | 4 ++-- packages/workflow/src/Workflow.ts | 5 +++++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/editor-ui/src/constants.ts b/packages/editor-ui/src/constants.ts index 84c370cbc8927..da0d4ab5976a4 100644 --- a/packages/editor-ui/src/constants.ts +++ b/packages/editor-ui/src/constants.ts @@ -82,6 +82,8 @@ export const CUSTOM_NODES_DOCS_URL = `https://${DOCS_DOMAIN}/integrations/creati export const EXPRESSIONS_DOCS_URL = `https://${DOCS_DOMAIN}/code-examples/expressions/`; export const N8N_PRICING_PAGE_URL = 'https://n8n.io/pricing'; +export const NODE_TRIGGER_CHAT_BUTTON = '@n8n/nodes-langchain.manualChatTrigger'; + // node types export const BAMBOO_HR_NODE_TYPE = 'n8n-nodes-base.bambooHr'; export const CALENDLY_TRIGGER_NODE_TYPE = 'n8n-nodes-base.calendlyTrigger'; @@ -148,6 +150,7 @@ export const NON_ACTIVATABLE_TRIGGER_NODE_TYPES = [ ERROR_TRIGGER_NODE_TYPE, MANUAL_TRIGGER_NODE_TYPE, EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE, + NODE_TRIGGER_CHAT_BUTTON, ]; export const NODES_USING_CODE_NODE_EDITOR = [ @@ -155,8 +158,6 @@ export const NODES_USING_CODE_NODE_EDITOR = [ '@n8n/nodes-langchain.dynamicTool', ]; -export const NODE_TRIGGER_CHAT_BUTTON = '@n8n/nodes-langchain.manualChatTrigger'; - export const PIN_DATA_NODE_TYPES_DENYLIST = [SPLIT_IN_BATCHES_NODE_TYPE]; // Node creator diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index ba0e22294b6fb..fed91ee57c0cf 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -606,14 +606,14 @@ export default defineComponent({ }, triggerNodes(): INodeUi[] { return this.nodes.filter( - (node) => node.type === START_NODE_TYPE || this.nodeTypesStore.isTriggerNode(node.type), + (node) => node.type === START_NODE_TYPE || this.nodeTypesStore.isTriggerNode(node.type) && node.type !== NODE_TRIGGER_CHAT_BUTTON, ); }, containsTrigger(): boolean { return this.triggerNodes.length > 0; }, containsChatNodes(): boolean { - return !!this.nodes.find((node)=> node.type === NODE_TRIGGER_CHAT_BUTTON); + return !!this.nodes.find((node)=> node.type === NODE_TRIGGER_CHAT_BUTTON && node.disabled !== true); }, isExecutionDisabled(): boolean { return !this.containsTrigger || this.allTriggersDisabled; diff --git a/packages/workflow/src/Workflow.ts b/packages/workflow/src/Workflow.ts index 267780536cb1f..0c76f440282ae 100644 --- a/packages/workflow/src/Workflow.ts +++ b/packages/workflow/src/Workflow.ts @@ -911,6 +911,11 @@ export class Workflow { nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion); + // TODO: Identify later differently + if (nodeType.description.name === '@n8n/nodes-langchain.manualChatTrigger') { + continue; + } + if (nodeType && (nodeType.trigger !== undefined || nodeType.poll !== undefined)) { if (node.disabled === true) { continue; From d8bad25a6a9271effe79f576b8ae09e8d714d0cc Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 22 Aug 2023 10:22:49 +0200 Subject: [PATCH 071/372] :zap: Change nodes from vertical to horizontal --- packages/editor-ui/src/components/Node.vue | 11 +---- .../src/components/WorkflowLMChat.vue | 3 ++ packages/editor-ui/src/mixins/nodeBase.ts | 30 ++++-------- packages/editor-ui/src/utils/nodeViewUtils.ts | 49 ++++++++++--------- 4 files changed, 41 insertions(+), 52 deletions(-) diff --git a/packages/editor-ui/src/components/Node.vue b/packages/editor-ui/src/components/Node.vue index 94d2bca0ac6f3..afde120f19ab2 100644 --- a/packages/editor-ui/src/components/Node.vue +++ b/packages/editor-ui/src/components/Node.vue @@ -690,14 +690,7 @@ export default defineComponent({ } &--configurable { - height: 250px; - .node-description { - top: 250px; - } - - .node-executing-info { - top: 75px !important; - } + width: 250px; } .node-default { @@ -888,7 +881,7 @@ export default defineComponent({ width: 108px !important; } .node-wrapper--configurable & { - height: 266px; + width: 266px !important; } } diff --git a/packages/editor-ui/src/components/WorkflowLMChat.vue b/packages/editor-ui/src/components/WorkflowLMChat.vue index 6467f851a349b..3ab17d771331d 100644 --- a/packages/editor-ui/src/components/WorkflowLMChat.vue +++ b/packages/editor-ui/src/components/WorkflowLMChat.vue @@ -207,8 +207,11 @@ export default defineComponent({ diff --git a/packages/editor-ui/src/components/RunDataAiContent.vue b/packages/editor-ui/src/components/RunDataAiContent.vue new file mode 100644 index 0000000000000..1ec18959a87c9 --- /dev/null +++ b/packages/editor-ui/src/components/RunDataAiContent.vue @@ -0,0 +1,205 @@ + + + + + + + diff --git a/packages/editor-ui/src/mixins/nodeBase.ts b/packages/editor-ui/src/mixins/nodeBase.ts index 4e562d156ae39..08ad50a7e0dcf 100644 --- a/packages/editor-ui/src/mixins/nodeBase.ts +++ b/packages/editor-ui/src/mixins/nodeBase.ts @@ -17,6 +17,7 @@ import { useHistoryStore } from '@/stores/history.store'; import { useCanvasStore } from '@/stores/canvas.store'; import type { EndpointSpec } from '@jsplumb/common'; import { EndpointType } from '@/Interface'; +import { CONNECTOR_COLOR } from '@/utils/nodeViewUtils'; const createAddInputEndpointSpec = (color?: string): EndpointSpec => ({ type: 'N8nAddInput', @@ -371,19 +372,19 @@ export const nodeBase = defineComponent({ languageModel: { paintStyle: NodeViewUtils.getOutputEndpointStyle( nodeTypeData, - '--color-primary', + CONNECTOR_COLOR['languageModel'], connectionType, ), hoverPaintStyle: NodeViewUtils.getOutputEndpointStyle( nodeTypeData, - '--color-primary', + CONNECTOR_COLOR['languageModel'], connectionType, ), }, main: { paintStyle: NodeViewUtils.getOutputEndpointStyle( nodeTypeData, - '--color-foreground-xdark', + CONNECTOR_COLOR['main'], connectionType, ), cssClass: `dot-${type}-endpoint`, @@ -391,36 +392,36 @@ export const nodeBase = defineComponent({ memory: { paintStyle: NodeViewUtils.getOutputEndpointStyle( nodeTypeData, - '--color-secondary', + CONNECTOR_COLOR['memory'], connectionType, ), hoverPaintStyle: NodeViewUtils.getOutputEndpointStyle( nodeTypeData, - '--color-secondary', + CONNECTOR_COLOR['memory'], connectionType, ), }, tool: { paintStyle: NodeViewUtils.getOutputEndpointStyle( nodeTypeData, - '--color-danger', + CONNECTOR_COLOR['tool'], connectionType, ), hoverPaintStyle: NodeViewUtils.getOutputEndpointStyle( nodeTypeData, - '--color-danger', + CONNECTOR_COLOR['tool'], connectionType, ), }, vectorRetriever: { paintStyle: NodeViewUtils.getOutputEndpointStyle( nodeTypeData, - '--color-avatar-accent-2', + CONNECTOR_COLOR['vectorRetriever'], connectionType, ), hoverPaintStyle: NodeViewUtils.getOutputEndpointStyle( nodeTypeData, - '--color-avatar-accent-2', + CONNECTOR_COLOR['vectorRetriever'], connectionType, ), cssClass: `dot-${type}-endpoint`, @@ -428,12 +429,12 @@ export const nodeBase = defineComponent({ embedding: { paintStyle: NodeViewUtils.getOutputEndpointStyle( nodeTypeData, - '--color-json-default', + CONNECTOR_COLOR['embedding'], connectionType, ), hoverPaintStyle: NodeViewUtils.getOutputEndpointStyle( nodeTypeData, - '--color-json-default', + CONNECTOR_COLOR['embedding'], connectionType, ), cssClass: `dot-${type}-endpoint`, @@ -441,12 +442,12 @@ export const nodeBase = defineComponent({ document: { paintStyle: NodeViewUtils.getOutputEndpointStyle( nodeTypeData, - '--color-success-light', + CONNECTOR_COLOR['document'], connectionType, ), hoverPaintStyle: NodeViewUtils.getOutputEndpointStyle( nodeTypeData, - '--color-success-light', + CONNECTOR_COLOR['document'], connectionType, ), cssClass: `dot-${type}-endpoint`, @@ -454,12 +455,12 @@ export const nodeBase = defineComponent({ textSplitter: { paintStyle: NodeViewUtils.getOutputEndpointStyle( nodeTypeData, - '--color-secondary-tint-2', + CONNECTOR_COLOR['textSplitter'], connectionType, ), hoverPaintStyle: NodeViewUtils.getOutputEndpointStyle( nodeTypeData, - '--color-secondary-tint-2', + CONNECTOR_COLOR['textSplitter'], connectionType, ), cssClass: `dot-${type}-endpoint`, diff --git a/packages/editor-ui/src/utils/nodeViewUtils.ts b/packages/editor-ui/src/utils/nodeViewUtils.ts index c435309d666ac..4bbdd9dae7e27 100644 --- a/packages/editor-ui/src/utils/nodeViewUtils.ts +++ b/packages/editor-ui/src/utils/nodeViewUtils.ts @@ -114,6 +114,21 @@ export const CONNECTOR_PAINT_STYLE_DATA: PaintStyle = { dashstyle: '2 2', }; +export const CONNECTOR_COLOR: { + [K in ConnectionTypes]: string; +} = { + chain: '--color-primary', + document: '--color-success-light', + embedding: '--color-json-default', + languageModel: '--color-primary', + main: '--color-foreground-xdark', + memory: '--color-secondary', + textSplitter: '--color-secondary-tint-2', + tool: '--color-danger', + vectorRetriever: '--color-avatar-accent-2', + vectorStore: '--color-avatar-accent-1', +}; + export const CONNECTOR_ARROW_OVERLAYS: OverlaySpec[] = [ { type: 'Arrow', diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 2f7341e293803..106525b1e0ced 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -1480,7 +1480,18 @@ export interface IPostReceiveSort extends IPostReceiveBase { }; } -export type ConnectionTypes = 'languageModel' | 'main' | 'memory' | 'tool' | 'textSplitter' | 'document' | 'vectorRetriever' | 'vectorStore' | 'embedding' | 'chain' +export type ConnectionTypes = + | 'chain' + | 'document' + | 'embedding' + | 'languageModel' + | 'main' + | 'memory' + | 'tool' + | 'textSplitter' + | 'vectorRetriever' + | 'vectorStore'; + export interface INodeTypeDescription extends INodeTypeBaseDescription { version: number | number[]; @@ -1671,6 +1682,10 @@ export interface IRunExecutionData { executionData?: { contextData: IExecuteContextData; nodeExecutionStack: IExecuteData[]; + metadata: { + // node-name: metadata by runIndex + [key: string]: ITaskMetadata[]; + }; waitingExecution: IWaitingForExecution; waitingExecutionSource: IWaitingForExecutionSource | null; }; @@ -1682,6 +1697,15 @@ export interface IRunData { [key: string]: ITaskData[]; } +export interface ITaskAIRunMetadata { + node: string; + runIndex: number; +} + +export interface ITaskMetadata { + aiRun?: ITaskAIRunMetadata[]; +} + // The data that gets returned when a node runs export interface ITaskData { startTime: number; @@ -1691,6 +1715,7 @@ export interface ITaskData { inputOverride?: ITaskDataConnections; error?: ExecutionError; source: Array; // Is an array as nodes have multiple inputs + metadata?: ITaskMetadata; } export interface ISourceData { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e37ed8e0def3c..cacd14428d562 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -972,6 +972,9 @@ importers: vue-json-pretty: specifier: 2.2.4 version: 2.2.4(vue@3.3.4) + vue-markdown-render: + specifier: ^2.0.1 + version: 2.0.1 vue-router: specifier: ^4.2.2 version: 4.2.2(vue@3.3.4) @@ -1009,6 +1012,9 @@ importers: '@types/luxon': specifier: ^3.2.0 version: 3.2.0 + '@types/markdown-it': + specifier: ^12.2.3 + version: 12.2.3 '@types/uuid': specifier: ^8.3.2 version: 8.3.4 @@ -8936,7 +8942,7 @@ packages: /axios@0.21.4: resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} dependencies: - follow-redirects: 1.15.2(debug@4.3.4) + follow-redirects: 1.15.2(debug@3.2.7) transitivePeerDependencies: - debug dev: false @@ -8952,7 +8958,7 @@ packages: /axios@0.26.1: resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==} dependencies: - follow-redirects: 1.15.2(debug@4.3.4) + follow-redirects: 1.15.2(debug@3.2.7) transitivePeerDependencies: - debug dev: false @@ -8973,6 +8979,7 @@ packages: form-data: 4.0.0 transitivePeerDependencies: - debug + dev: true /babel-core@7.0.0-bridge.0(@babel/core@7.22.9): resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==} @@ -11289,6 +11296,10 @@ packages: resolution: {integrity: sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==} dev: false + /entities@2.1.0: + resolution: {integrity: sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==} + dev: false + /entities@2.2.0: resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} dev: false @@ -12737,6 +12748,7 @@ packages: optional: true dependencies: debug: 4.3.4(supports-color@8.1.1) + dev: true /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -15983,6 +15995,12 @@ packages: /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + /linkify-it@3.0.3: + resolution: {integrity: sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==} + dependencies: + uc.micro: 1.0.6 + dev: false + /linkify-it@4.0.0: resolution: {integrity: sha512-QAxkXyzT/TXgwGyY4rTgC95Ex6/lZ5/lYTV9nug6eJt93BCBQGOE47D/g2+/m5J1MrVLr2ot97OXkBZ9bBpR4A==} dependencies: @@ -16499,6 +16517,17 @@ packages: resolution: {integrity: sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==} dev: false + /markdown-it@12.3.2: + resolution: {integrity: sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==} + hasBin: true + dependencies: + argparse: 2.0.1 + entities: 2.1.0 + linkify-it: 3.0.3 + mdurl: 1.0.1 + uc.micro: 1.0.6 + dev: false + /markdown-it@13.0.1: resolution: {integrity: sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==} hasBin: true @@ -18739,7 +18768,7 @@ packages: resolution: {integrity: sha512-aXYe/D+28kF63W8Cz53t09ypEORz+ULeDCahdAqhVrRm2scbOXFbtnn0GGhvMpYe45grepLKuwui9KxrZ2ZuMw==} engines: {node: '>=14.17.0'} dependencies: - axios: 0.27.2(debug@4.3.4) + axios: 0.27.2(debug@3.2.7) transitivePeerDependencies: - debug dev: false @@ -22758,6 +22787,13 @@ packages: vue: 3.3.4 dev: false + /vue-markdown-render@2.0.1: + resolution: {integrity: sha512-/UBCu0OrZ9zzEDtiZVwlV/CQ+CgcwViServGis3TRXSVc6+6lJxcaOcD43vRoQzYfPa9r9WDt0Q7GyupOmpEWA==} + dependencies: + markdown-it: 12.3.2 + vue: 3.3.4 + dev: false + /vue-router@4.2.2(vue@3.3.4): resolution: {integrity: sha512-cChBPPmAflgBGmy3tBsjeoe3f3VOSG6naKyY5pjtrqLGbNEXdzCigFUHgBvp9e3ysAtFtEx7OLqcSDh/1Cq2TQ==} peerDependencies: From cb45f565665647c3527bc3967319adacdf36bb9a Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 27 Aug 2023 13:32:43 +0200 Subject: [PATCH 090/372] :bug: Display AI tab only for output pane --- packages/editor-ui/src/components/RunData.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor-ui/src/components/RunData.vue b/packages/editor-ui/src/components/RunData.vue index 8324d961e723e..6186e45ef1e1c 100644 --- a/packages/editor-ui/src/components/RunData.vue +++ b/packages/editor-ui/src/components/RunData.vue @@ -721,7 +721,7 @@ export default defineComponent({ defaults.unshift({ label: 'HTML', value: 'html' }); } - if (this.activeNode) { + if (this.isPaneTypeOutput && this.activeNode) { const resultData = this.workflowsStore.getWorkflowResultDataByNodeName( this.activeNode.name, ); From 2b32b2fc774178f554c5012d584b17299b34c137 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 27 Aug 2023 19:05:44 +0200 Subject: [PATCH 091/372] :zap: Improve display of AI data --- packages/editor-ui/src/components/RunData.vue | 12 ++- .../editor-ui/src/components/RunDataAi.vue | 4 +- .../src/components/RunDataAiContent.vue | 74 +++++++++++++++++-- 3 files changed, 80 insertions(+), 10 deletions(-) diff --git a/packages/editor-ui/src/components/RunData.vue b/packages/editor-ui/src/components/RunData.vue index 6186e45ef1e1c..51cd6c75fb236 100644 --- a/packages/editor-ui/src/components/RunData.vue +++ b/packages/editor-ui/src/components/RunData.vue @@ -181,7 +181,11 @@
pageSize && - !isSchemaView + !isSchemaView && + !isAiView " v-show="!editMode.enabled" > @@ -683,6 +688,9 @@ export default defineComponent({ } return null; }, + isAiView(): boolean { + return this.displayMode === 'ai'; + }, isSchemaView(): boolean { return this.displayMode === 'schema'; }, diff --git a/packages/editor-ui/src/components/RunDataAi.vue b/packages/editor-ui/src/components/RunDataAi.vue index 80d5e2ebcefb4..3c1003212a356 100644 --- a/packages/editor-ui/src/components/RunDataAi.vue +++ b/packages/editor-ui/src/components/RunDataAi.vue @@ -1,7 +1,7 @@ diff --git a/packages/editor-ui/src/components/RunDataAiContent.vue b/packages/editor-ui/src/components/RunDataAiContent.vue index 1ec18959a87c9..db6b92a16cd0a 100644 --- a/packages/editor-ui/src/components/RunDataAiContent.vue +++ b/packages/editor-ui/src/components/RunDataAiContent.vue @@ -1,12 +1,26 @@