diff --git a/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/ConversationalAgent/execute.ts b/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/ConversationalAgent/execute.ts index fd14107627de4..b204f74eddcec 100644 --- a/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/ConversationalAgent/execute.ts +++ b/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/ConversationalAgent/execute.ts @@ -13,6 +13,7 @@ import { getConnectedTools, } from '../../../../../utils/helpers'; import { getTracingConfig } from '../../../../../utils/tracing'; +import { throwIfToolSchema } from '../../../../../utils/schemaParsing'; export async function conversationalAgentExecute( this: IExecuteFunctions, @@ -111,6 +112,8 @@ export async function conversationalAgentExecute( returnData.push({ json: response }); } catch (error) { + throwIfToolSchema(this, error); + if (this.continueOnFail()) { returnData.push({ json: { error: error.message }, pairedItem: { item: itemIndex } }); continue; diff --git a/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/PlanAndExecuteAgent/execute.ts b/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/PlanAndExecuteAgent/execute.ts index 3957f867cd2ce..e8f25481d2054 100644 --- a/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/PlanAndExecuteAgent/execute.ts +++ b/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/PlanAndExecuteAgent/execute.ts @@ -16,6 +16,7 @@ import { getPromptInputByType, } from '../../../../../utils/helpers'; import { getTracingConfig } from '../../../../../utils/tracing'; +import { throwIfToolSchema } from '../../../../../utils/schemaParsing'; export async function planAndExecuteAgentExecute( this: IExecuteFunctions, @@ -91,6 +92,7 @@ export async function planAndExecuteAgentExecute( returnData.push({ json: response }); } catch (error) { + throwIfToolSchema(this, error); if (this.continueOnFail()) { returnData.push({ json: { error: error.message }, pairedItem: { item: itemIndex } }); continue; diff --git a/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/ReActAgent/execute.ts b/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/ReActAgent/execute.ts index a2a6392a5ff13..c83b7e99978af 100644 --- a/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/ReActAgent/execute.ts +++ b/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/ReActAgent/execute.ts @@ -18,6 +18,7 @@ import { isChatInstance, } from '../../../../../utils/helpers'; import { getTracingConfig } from '../../../../../utils/tracing'; +import { throwIfToolSchema } from '../../../../../utils/schemaParsing'; export async function reActAgentAgentExecute( this: IExecuteFunctions, @@ -112,6 +113,7 @@ export async function reActAgentAgentExecute( returnData.push({ json: response }); } catch (error) { + throwIfToolSchema(this, error); if (this.continueOnFail()) { returnData.push({ json: { error: error.message }, pairedItem: { item: itemIndex } }); continue; diff --git a/packages/@n8n/nodes-langchain/nodes/code/Code.node.ts b/packages/@n8n/nodes-langchain/nodes/code/Code.node.ts index be15860b27970..4100d12348032 100644 --- a/packages/@n8n/nodes-langchain/nodes/code/Code.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/code/Code.node.ts @@ -92,6 +92,8 @@ function getSandbox( // eslint-disable-next-line @typescript-eslint/unbound-method context.executeWorkflow = this.executeWorkflow; // eslint-disable-next-line @typescript-eslint/unbound-method + context.getWorkflowDataProxy = this.getWorkflowDataProxy; + // eslint-disable-next-line @typescript-eslint/unbound-method context.logger = this.logger; if (options?.addItems) { diff --git a/packages/@n8n/nodes-langchain/nodes/output_parser/OutputParserStructured/OutputParserStructured.node.ts b/packages/@n8n/nodes-langchain/nodes/output_parser/OutputParserStructured/OutputParserStructured.node.ts index e41cd75fcb1cc..354ba8fbb0387 100644 --- a/packages/@n8n/nodes-langchain/nodes/output_parser/OutputParserStructured/OutputParserStructured.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/output_parser/OutputParserStructured/OutputParserStructured.node.ts @@ -13,11 +13,15 @@ import type { JSONSchema7 } from 'json-schema'; import { StructuredOutputParser } from 'langchain/output_parsers'; import { OutputParserException } from '@langchain/core/output_parsers'; import get from 'lodash/get'; -import { getSandboxContext } from 'n8n-nodes-base/dist/nodes/Code/Sandbox'; -import { JavaScriptSandbox } from 'n8n-nodes-base/dist/nodes/Code/JavaScriptSandbox'; -import { makeResolverFromLegacyOptions } from '@n8n/vm2'; +import type { JavaScriptSandbox } from 'n8n-nodes-base/dist/nodes/Code/JavaScriptSandbox'; import { getConnectionHintNoticeField } from '../../../utils/sharedFields'; import { logWrapper } from '../../../utils/logWrapper'; +import { generateSchema, getSandboxWithZod } from '../../../utils/schemaParsing'; +import { + inputSchemaField, + jsonSchemaExampleField, + schemaTypeField, +} from '../../../utils/descriptions'; const STRUCTURED_OUTPUT_KEY = '__structured__output'; const STRUCTURED_OUTPUT_OBJECT_KEY = '__structured__output__object'; @@ -87,8 +91,8 @@ export class OutputParserStructured implements INodeType { name: 'outputParserStructured', icon: 'fa:code', group: ['transform'], - version: [1, 1.1], - defaultVersion: 1.1, + version: [1, 1.1, 1.2], + defaultVersion: 1.2, description: 'Return data in a defined JSON format', defaults: { name: 'Structured Output Parser', @@ -115,6 +119,33 @@ export class OutputParserStructured implements INodeType { outputNames: ['Output Parser'], properties: [ getConnectionHintNoticeField([NodeConnectionType.AiChain, NodeConnectionType.AiAgent]), + { ...schemaTypeField, displayOptions: { show: { '@version': [{ _cnd: { gte: 1.2 } }] } } }, + { + ...jsonSchemaExampleField, + default: `{ + "state": "California", + "cities": ["Los Angeles", "San Francisco", "San Diego"] +}`, + }, + { + ...inputSchemaField, + displayName: 'JSON Schema', + description: 'JSON Schema to structure and validate the output against', + default: `{ + "type": "object", + "properties": { + "state": { + "type": "string" + }, + "cities": { + "type": "array", + "items": { + "type": "string" + } + } + } +}`, + }, { displayName: 'JSON Schema', name: 'jsonSchema', @@ -138,6 +169,11 @@ export class OutputParserStructured implements INodeType { rows: 10, }, required: true, + displayOptions: { + show: { + '@version': [{ _cnd: { lte: 1.1 } }], + }, + }, }, { displayName: @@ -145,72 +181,36 @@ export class OutputParserStructured implements INodeType { name: 'notice', type: 'notice', default: '', + displayOptions: { + hide: { + schemaType: ['fromJson'], + }, + }, }, ], }; async supplyData(this: IExecuteFunctions, itemIndex: number): Promise { - const schema = this.getNodeParameter('jsonSchema', itemIndex) as string; + const schemaType = this.getNodeParameter('schemaType', itemIndex, '') as 'fromJson' | 'manual'; + // We initialize these even though one of them will always be empty + // it makes it easer to navigate the ternary operator + const jsonExample = this.getNodeParameter('jsonSchemaExample', itemIndex, '') as string; + let inputSchema: string; - let itemSchema: JSONSchema7; - try { - itemSchema = jsonParse(schema); - - // If the type is not defined, we assume it's an object - if (itemSchema.type === undefined) { - itemSchema = { - type: 'object', - properties: itemSchema.properties ?? (itemSchema as { [key: string]: JSONSchema7 }), - }; - } - } catch (error) { - throw new NodeOperationError(this.getNode(), 'Error during parsing of JSON Schema.'); + if (this.getNode().typeVersion <= 1.1) { + inputSchema = this.getNodeParameter('jsonSchema', itemIndex, '') as string; + } else { + inputSchema = this.getNodeParameter('inputSchema', itemIndex, '') as string; } - const vmResolver = makeResolverFromLegacyOptions({ - external: { - modules: ['json-schema-to-zod', 'zod'], - transitive: false, - }, - resolve(moduleName, parentDirname) { - if (moduleName === 'json-schema-to-zod') { - return require.resolve( - '@n8n/n8n-nodes-langchain/node_modules/json-schema-to-zod/dist/cjs/jsonSchemaToZod.js', - { - paths: [parentDirname], - }, - ); - } - if (moduleName === 'zod') { - return require.resolve('@n8n/n8n-nodes-langchain/node_modules/zod.cjs', { - paths: [parentDirname], - }); - } - return; - }, - builtin: [], - }); - const context = getSandboxContext.call(this, itemIndex); - // Make sure to remove the description from root schema - const { description, ...restOfSchema } = itemSchema; - const sandboxedSchema = new JavaScriptSandbox( - context, - ` - const { z } = require('zod'); - const { parseSchema } = require('json-schema-to-zod'); - const zodSchema = parseSchema(${JSON.stringify(restOfSchema)}); - const itemSchema = new Function('z', 'return (' + zodSchema + ')')(z) - return itemSchema - `, - itemIndex, - this.helpers, - { resolver: vmResolver }, - ); + const jsonSchema = + schemaType === 'fromJson' ? generateSchema(jsonExample) : jsonParse(inputSchema); + const zodSchemaSandbox = getSandboxWithZod(this, jsonSchema, 0); const nodeVersion = this.getNode().typeVersion; try { const parser = await N8nStructuredOutputParser.fromZedJsonSchema( - sandboxedSchema, + zodSchemaSandbox, nodeVersion, ); return { diff --git a/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/ToolWorkflow.node.ts b/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/ToolWorkflow.node.ts index 5130cf7d2924d..3b06c841864da 100644 --- a/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/ToolWorkflow.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/ToolWorkflow.node.ts @@ -9,16 +9,23 @@ import type { ExecutionError, IDataObject, } from 'n8n-workflow'; -import { NodeConnectionType, NodeOperationError } from 'n8n-workflow'; +import { NodeConnectionType, NodeOperationError, jsonParse } from 'n8n-workflow'; import type { SetField, SetNodeOptions } from 'n8n-nodes-base/dist/nodes/Set/v2/helpers/interfaces'; import * as manual from 'n8n-nodes-base/dist/nodes/Set/v2/manual.mode'; -import { DynamicTool } from '@langchain/core/tools'; +import { DynamicStructuredTool, DynamicTool } from '@langchain/core/tools'; import get from 'lodash/get'; import isObject from 'lodash/isObject'; import type { CallbackManagerForToolRun } from '@langchain/core/callbacks/manager'; +import type { JSONSchema7 } from 'json-schema'; import { getConnectionHintNoticeField } from '../../../utils/sharedFields'; - +import type { DynamicZodObject } from '../../../types/zod.types'; +import { generateSchema, getSandboxWithZod } from '../../../utils/schemaParsing'; +import { + jsonSchemaExampleField, + schemaTypeField, + inputSchemaField, +} from '../../../utils/descriptions'; export class ToolWorkflow implements INodeType { description: INodeTypeDescription = { displayName: 'Custom n8n Workflow Tool', @@ -314,6 +321,21 @@ export class ToolWorkflow implements INodeType { }, ], }, + // ---------------------------------- + // Output Parsing + // ---------------------------------- + { + displayName: 'Specify Input Schema', + name: 'specifyInputSchema', + type: 'boolean', + description: + 'Whether to specify the schema for the function. This would require the LLM to provide the input in the correct format and would validate it against the schema.', + noDataExpression: true, + default: false, + }, + { ...schemaTypeField, displayOptions: { show: { specifyInputSchema: [true] } } }, + jsonSchemaExampleField, + inputSchemaField, ], }; @@ -321,8 +343,11 @@ export class ToolWorkflow implements INodeType { const name = this.getNodeParameter('name', itemIndex) as string; const description = this.getNodeParameter('description', itemIndex) as string; + const useSchema = this.getNodeParameter('specifyInputSchema', itemIndex) as boolean; + let tool: DynamicTool | DynamicStructuredTool | undefined = undefined; + const runFunction = async ( - query: string, + query: string | IDataObject, runManager?: CallbackManagerForToolRun, ): Promise => { const source = this.getNodeParameter('source', itemIndex) as string; @@ -416,50 +441,86 @@ export class ToolWorkflow implements INodeType { return response; }; - return { - response: new DynamicTool({ - name, - description, + const toolHandler = async ( + query: string | IDataObject, + runManager?: CallbackManagerForToolRun, + ): Promise => { + const { index } = this.addInputData(NodeConnectionType.AiTool, [[{ json: { query } }]]); - func: async (query: string, runManager?: CallbackManagerForToolRun): Promise => { - const { index } = this.addInputData(NodeConnectionType.AiTool, [[{ json: { query } }]]); + let response: string = ''; + let executionError: ExecutionError | undefined; + try { + response = await runFunction(query, runManager); + } catch (error) { + // TODO: Do some more testing. Issues here should actually fail the workflow + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + executionError = error; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + response = `There was an error: "${error.message}"`; + } - let response: string = ''; - let executionError: ExecutionError | undefined; - try { - response = await runFunction(query, runManager); - } catch (error) { - // TODO: Do some more testing. Issues here should actually fail the workflow - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - executionError = error; - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - response = `There was an error: "${error.message}"`; - } + if (typeof response === 'number') { + response = (response as number).toString(); + } - if (typeof response === 'number') { - response = (response as number).toString(); - } + if (isObject(response)) { + response = JSON.stringify(response, null, 2); + } - if (isObject(response)) { - response = JSON.stringify(response, null, 2); - } + if (typeof response !== 'string') { + // TODO: Do some more testing. Issues here should actually fail the workflow + executionError = new NodeOperationError(this.getNode(), 'Wrong output type returned', { + description: `The response property should be a string, but it is an ${typeof response}`, + }); + response = `There was an error: "${executionError.message}"`; + } - if (typeof response !== 'string') { - // TODO: Do some more testing. Issues here should actually fail the workflow - executionError = new NodeOperationError(this.getNode(), 'Wrong output type returned', { - description: `The response property should be a string, but it is an ${typeof response}`, - }); - response = `There was an error: "${executionError.message}"`; - } + if (executionError) { + void this.addOutputData(NodeConnectionType.AiTool, index, executionError); + } else { + void this.addOutputData(NodeConnectionType.AiTool, index, [[{ json: { response } }]]); + } + return response; + }; - if (executionError) { - void this.addOutputData(NodeConnectionType.AiTool, index, executionError); - } else { - void this.addOutputData(NodeConnectionType.AiTool, index, [[{ json: { response } }]]); - } - return response; - }, - }), + const functionBase = { + name, + description, + func: toolHandler, + }; + + if (useSchema) { + try { + // We initialize these even though one of them will always be empty + // it makes it easer to navigate the ternary operator + const jsonExample = this.getNodeParameter('jsonSchemaExample', itemIndex, '') as string; + const inputSchema = this.getNodeParameter('inputSchema', itemIndex, '') as string; + + const schemaType = this.getNodeParameter('schemaType', itemIndex) as 'fromJson' | 'manual'; + const jsonSchema = + schemaType === 'fromJson' + ? generateSchema(jsonExample) + : jsonParse(inputSchema); + + const zodSchemaSandbox = getSandboxWithZod(this, jsonSchema, 0); + const zodSchema = (await zodSchemaSandbox.runCode()) as DynamicZodObject; + + tool = new DynamicStructuredTool({ + schema: zodSchema, + ...functionBase, + }); + } catch (error) { + throw new NodeOperationError( + this.getNode(), + 'Error during parsing of JSON Schema. \n ' + error, + ); + } + } else { + tool = new DynamicTool(functionBase); + } + + return { + response: tool, }; } } diff --git a/packages/@n8n/nodes-langchain/package.json b/packages/@n8n/nodes-langchain/package.json index 23854907f7534..d327b63fa8075 100644 --- a/packages/@n8n/nodes-langchain/package.json +++ b/packages/@n8n/nodes-langchain/package.json @@ -157,6 +157,7 @@ "d3-dsv": "2.0.0", "epub2": "3.0.2", "form-data": "4.0.0", + "generate-schema": "^2.6.0", "html-to-text": "9.0.5", "jest-mock-extended": "^3.0.4", "json-schema-to-zod": "2.0.14", diff --git a/packages/@n8n/nodes-langchain/tsconfig.build.json b/packages/@n8n/nodes-langchain/tsconfig.build.json index d7b07412f61eb..a3b8ff9a405a2 100644 --- a/packages/@n8n/nodes-langchain/tsconfig.build.json +++ b/packages/@n8n/nodes-langchain/tsconfig.build.json @@ -11,7 +11,8 @@ "credentials/**/*.ts", "nodes/**/*.ts", "nodes/**/*.json", - "credentials/translations/**/*.json" + "credentials/translations/**/*.json", + "types/*.ts" ], "exclude": ["nodes/**/*.test.ts", "test/**"] } diff --git a/packages/@n8n/nodes-langchain/tsconfig.json b/packages/@n8n/nodes-langchain/tsconfig.json index 8377c89500383..734160344cb9d 100644 --- a/packages/@n8n/nodes-langchain/tsconfig.json +++ b/packages/@n8n/nodes-langchain/tsconfig.json @@ -20,5 +20,5 @@ "skipLibCheck": true, "outDir": "./dist/" }, - "include": ["credentials/**/*", "nodes/**/*", "utils/**/*.ts", "nodes/**/*.json"] + "include": ["credentials/**/*", "nodes/**/*", "utils/**/*.ts", "nodes/**/*.json", "types/*.ts"] } diff --git a/packages/@n8n/nodes-langchain/types/generate-schema.d.ts b/packages/@n8n/nodes-langchain/types/generate-schema.d.ts new file mode 100644 index 0000000000000..90e0e15b05cac --- /dev/null +++ b/packages/@n8n/nodes-langchain/types/generate-schema.d.ts @@ -0,0 +1,27 @@ +declare module 'generate-schema' { + export interface SchemaObject { + $schema: string; + title?: string; + type: string; + properties?: { + [key: string]: SchemaObject | SchemaArray | SchemaProperty; + }; + required?: string[]; + items?: SchemaObject | SchemaArray; + } + + export interface SchemaArray { + type: string; + items?: SchemaObject | SchemaArray | SchemaProperty; + oneOf?: Array; + required?: string[]; + } + + export interface SchemaProperty { + type: string | string[]; + format?: string; + } + + export function json(title: string, schema: SchemaObject): SchemaObject; + export function json(schema: SchemaObject): SchemaObject; +} diff --git a/packages/@n8n/nodes-langchain/types/zod.types.ts b/packages/@n8n/nodes-langchain/types/zod.types.ts new file mode 100644 index 0000000000000..933bd1e33d34b --- /dev/null +++ b/packages/@n8n/nodes-langchain/types/zod.types.ts @@ -0,0 +1,4 @@ +import type { z } from 'zod'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type DynamicZodObject = z.ZodObject; diff --git a/packages/@n8n/nodes-langchain/utils/descriptions.ts b/packages/@n8n/nodes-langchain/utils/descriptions.ts index 19ef99213fb6e..b779df1be4a12 100644 --- a/packages/@n8n/nodes-langchain/utils/descriptions.ts +++ b/packages/@n8n/nodes-langchain/utils/descriptions.ts @@ -1,5 +1,70 @@ import type { INodeProperties } from 'n8n-workflow'; +export const schemaTypeField: INodeProperties = { + displayName: 'Schema Type', + name: 'schemaType', + type: 'options', + noDataExpression: true, + options: [ + { + name: 'Generate From JSON Example', + value: 'fromJson', + description: 'Generate a schema from an example JSON object', + }, + { + name: 'Define Below', + value: 'manual', + description: 'Define the JSON schema manually', + }, + ], + default: 'fromJson', + description: 'How to specify the schema for the function', +}; + +export const jsonSchemaExampleField: INodeProperties = { + displayName: 'JSON Example', + name: 'jsonSchemaExample', + type: 'json', + default: `{ + "some_input": "some_value" +}`, + noDataExpression: true, + typeOptions: { + rows: 10, + }, + displayOptions: { + show: { + schemaType: ['fromJson'], + }, + }, + description: 'Example JSON object to use to generate the schema', +}; + +export const inputSchemaField: INodeProperties = { + displayName: 'Input Schema', + name: 'inputSchema', + type: 'json', + default: `{ +"type": "object", +"properties": { + "some_input": { + "type": "string", + "description": "Some input to the function" + } + } +}`, + noDataExpression: true, + typeOptions: { + rows: 10, + }, + displayOptions: { + show: { + schemaType: ['manual'], + }, + }, + description: 'Schema to use for the function', +}; + export const promptTypeOptions: INodeProperties = { displayName: 'Prompt', name: 'promptType', diff --git a/packages/@n8n/nodes-langchain/utils/schemaParsing.ts b/packages/@n8n/nodes-langchain/utils/schemaParsing.ts new file mode 100644 index 0000000000000..8d5f61153dace --- /dev/null +++ b/packages/@n8n/nodes-langchain/utils/schemaParsing.ts @@ -0,0 +1,81 @@ +import { makeResolverFromLegacyOptions } from '@n8n/vm2'; +import { json as generateJsonSchema } from 'generate-schema'; +import type { SchemaObject } from 'generate-schema'; +import type { JSONSchema7 } from 'json-schema'; +import { JavaScriptSandbox } from 'n8n-nodes-base/dist/nodes/Code/JavaScriptSandbox'; +import { getSandboxContext } from 'n8n-nodes-base/dist/nodes/Code/Sandbox'; +import type { IExecuteFunctions } from 'n8n-workflow'; +import { NodeOperationError, jsonParse } from 'n8n-workflow'; + +const vmResolver = makeResolverFromLegacyOptions({ + external: { + modules: ['json-schema-to-zod', 'zod'], + transitive: false, + }, + resolve(moduleName, parentDirname) { + if (moduleName === 'json-schema-to-zod') { + return require.resolve( + '@n8n/n8n-nodes-langchain/node_modules/json-schema-to-zod/dist/cjs/jsonSchemaToZod.js', + { + paths: [parentDirname], + }, + ); + } + if (moduleName === 'zod') { + return require.resolve('@n8n/n8n-nodes-langchain/node_modules/zod.cjs', { + paths: [parentDirname], + }); + } + return; + }, + builtin: [], +}); + +export function getSandboxWithZod(ctx: IExecuteFunctions, schema: JSONSchema7, itemIndex: number) { + const context = getSandboxContext.call(ctx, itemIndex); + let itemSchema: JSONSchema7 = schema; + try { + // If the root type is not defined, we assume it's an object + if (itemSchema.type === undefined) { + itemSchema = { + type: 'object', + properties: itemSchema.properties ?? (itemSchema as { [key: string]: JSONSchema7 }), + }; + } + } catch (error) { + throw new NodeOperationError(ctx.getNode(), 'Error during parsing of JSON Schema.'); + } + + // Make sure to remove the description from root schema + const { description, ...restOfSchema } = itemSchema; + const sandboxedSchema = new JavaScriptSandbox( + context, + ` + const { z } = require('zod'); + const { parseSchema } = require('json-schema-to-zod'); + const zodSchema = parseSchema(${JSON.stringify(restOfSchema)}); + const itemSchema = new Function('z', 'return (' + zodSchema + ')')(z) + return itemSchema + `, + itemIndex, + ctx.helpers, + { resolver: vmResolver }, + ); + return sandboxedSchema; +} + +export function generateSchema(schemaString: string): JSONSchema7 { + const parsedSchema = jsonParse(schemaString); + + return generateJsonSchema(parsedSchema) as JSONSchema7; +} + +export function throwIfToolSchema(ctx: IExecuteFunctions, error: Error) { + if (error?.message?.includes('tool input did not match expected schema')) { + throw new NodeOperationError( + ctx.getNode(), + `${error.message}. + This is most likely because some of your tools are configured to require a specific schema. This is not supported by Conversational Agent. Remove the schema from the tool configuration or use Tools agent instead.`, + ); + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc17b5847acb1..34499a5fada3d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -315,6 +315,9 @@ importers: form-data: specifier: 4.0.0 version: 4.0.0 + generate-schema: + specifier: ^2.6.0 + version: 2.6.0 html-to-text: specifier: 9.0.5 version: 9.0.5 @@ -15175,6 +15178,14 @@ packages: is-property: 1.0.2 dev: false + /generate-schema@2.6.0: + resolution: {integrity: sha512-EUBKfJNzT8f91xUk5X5gKtnbdejZeE065UAJ3BCzE8VEbvwKI9Pm5jaWmqVeK1MYc1g5weAVFDTSJzN7ymtTqA==} + hasBin: true + dependencies: + commander: 2.20.3 + type-of-is: 3.5.1 + dev: false + /generic-pool@3.9.0: resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} engines: {node: '>= 4'} @@ -23560,6 +23571,11 @@ packages: media-typer: 0.3.0 mime-types: 2.1.35 + /type-of-is@3.5.1: + resolution: {integrity: sha512-SOnx8xygcAh8lvDU2exnK2bomASfNjzB3Qz71s2tw9QnX8fkAo7aC+D0H7FV0HjRKj94CKV2Hi71kVkkO6nOxg==} + engines: {node: '>=0.10.5'} + dev: false + /type@1.2.0: resolution: {integrity: sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==} dev: false