mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 01:56:46 +00:00
feat(HTTP Request Tool Node): Use DynamicStructuredTool with models supporting it (no-changelog) (#10246)
This commit is contained in:
@@ -38,7 +38,7 @@ export async function openAiFunctionsAgentExecute(
|
||||
const memory = (await this.getInputConnectionData(NodeConnectionType.AiMemory, 0)) as
|
||||
| BaseChatMemory
|
||||
| undefined;
|
||||
const tools = await getConnectedTools(this, nodeVersion >= 1.5);
|
||||
const tools = await getConnectedTools(this, nodeVersion >= 1.5, false);
|
||||
const outputParsers = await getOptionalOutputParsers(this);
|
||||
const options = this.getNodeParameter('options', 0, {}) as {
|
||||
systemMessage?: string;
|
||||
|
||||
@@ -90,7 +90,7 @@ export async function toolsAgentExecute(this: IExecuteFunctions): Promise<INodeE
|
||||
| BaseChatMemory
|
||||
| undefined;
|
||||
|
||||
const tools = (await getConnectedTools(this, true)) as Array<DynamicStructuredTool | Tool>;
|
||||
const tools = (await getConnectedTools(this, true, false)) as Array<DynamicStructuredTool | Tool>;
|
||||
const outputParser = (await getOptionalOutputParsers(this))?.[0];
|
||||
let structuredOutputParserTool: DynamicStructuredTool | undefined;
|
||||
|
||||
|
||||
@@ -313,7 +313,7 @@ export class OpenAiAssistant implements INodeType {
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const nodeVersion = this.getNode().typeVersion;
|
||||
const tools = await getConnectedTools(this, nodeVersion > 1);
|
||||
const tools = await getConnectedTools(this, nodeVersion > 1, false);
|
||||
const credentials = await this.getCredentials('openAiApi');
|
||||
|
||||
const items = this.getInputData();
|
||||
|
||||
@@ -12,6 +12,7 @@ import { NodeConnectionType, NodeOperationError, tryToParseAlphanumericString }
|
||||
import { DynamicTool } from '@langchain/core/tools';
|
||||
import { getConnectionHintNoticeField } from '../../../utils/sharedFields';
|
||||
|
||||
import { N8nTool } from '../../../utils/N8nTool';
|
||||
import {
|
||||
configureHttpRequestFunction,
|
||||
configureResponseOptimizer,
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
prepareToolDescription,
|
||||
configureToolFunction,
|
||||
updateParametersAndOptions,
|
||||
makeToolInputSchema,
|
||||
} from './utils';
|
||||
|
||||
import {
|
||||
@@ -38,7 +40,7 @@ export class ToolHttpRequest implements INodeType {
|
||||
name: 'toolHttpRequest',
|
||||
icon: { light: 'file:httprequest.svg', dark: 'file:httprequest.dark.svg' },
|
||||
group: ['output'],
|
||||
version: 1,
|
||||
version: [1, 1.1],
|
||||
description: 'Makes an HTTP request and returns the response data',
|
||||
subtitle: '={{ $parameter.toolDescription }}',
|
||||
defaults: {
|
||||
@@ -394,9 +396,24 @@ export class ToolHttpRequest implements INodeType {
|
||||
optimizeResponse,
|
||||
);
|
||||
|
||||
const description = prepareToolDescription(toolDescription, toolParameters);
|
||||
let tool: DynamicTool | N8nTool;
|
||||
|
||||
const tool = new DynamicTool({ name, description, func });
|
||||
// If the node version is 1.1 or higher, we use the N8nTool wrapper:
|
||||
// it allows to use tool as a DynamicStructuredTool and have a fallback to DynamicTool
|
||||
if (this.getNode().typeVersion >= 1.1) {
|
||||
const schema = makeToolInputSchema(toolParameters);
|
||||
|
||||
tool = new N8nTool(this, {
|
||||
name,
|
||||
description: toolDescription,
|
||||
func,
|
||||
schema,
|
||||
});
|
||||
} else {
|
||||
// Keep the old behavior for nodes with version 1.0
|
||||
const description = prepareToolDescription(toolDescription, toolParameters);
|
||||
tool = new DynamicTool({ name, description, func });
|
||||
}
|
||||
|
||||
return {
|
||||
response: tool,
|
||||
|
||||
@@ -27,6 +27,8 @@ import type {
|
||||
SendIn,
|
||||
ToolParameter,
|
||||
} from './interfaces';
|
||||
import type { DynamicZodObject } from '../../../types/zod.types';
|
||||
import { z } from 'zod';
|
||||
|
||||
const genericCredentialRequest = async (ctx: IExecuteFunctions, itemIndex: number) => {
|
||||
const genericType = ctx.getNodeParameter('genericAuthType', itemIndex) as string;
|
||||
@@ -566,7 +568,7 @@ export const configureToolFunction = (
|
||||
httpRequest: (options: IHttpRequestOptions) => Promise<any>,
|
||||
optimizeResponse: (response: string) => string,
|
||||
) => {
|
||||
return async (query: string): Promise<string> => {
|
||||
return async (query: string | IDataObject): Promise<string> => {
|
||||
const { index } = ctx.addInputData(NodeConnectionType.AiTool, [[{ json: { query } }]]);
|
||||
|
||||
let response: string = '';
|
||||
@@ -581,18 +583,22 @@ export const configureToolFunction = (
|
||||
if (query) {
|
||||
let dataFromModel;
|
||||
|
||||
try {
|
||||
dataFromModel = jsonParse<IDataObject>(query);
|
||||
} catch (error) {
|
||||
if (toolParameters.length === 1) {
|
||||
dataFromModel = { [toolParameters[0].name]: query };
|
||||
} else {
|
||||
throw new NodeOperationError(
|
||||
ctx.getNode(),
|
||||
`Input is not a valid JSON: ${error.message}`,
|
||||
{ itemIndex },
|
||||
);
|
||||
if (typeof query === 'string') {
|
||||
try {
|
||||
dataFromModel = jsonParse<IDataObject>(query);
|
||||
} catch (error) {
|
||||
if (toolParameters.length === 1) {
|
||||
dataFromModel = { [toolParameters[0].name]: query };
|
||||
} else {
|
||||
throw new NodeOperationError(
|
||||
ctx.getNode(),
|
||||
`Input is not a valid JSON: ${error.message}`,
|
||||
{ itemIndex },
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dataFromModel = query;
|
||||
}
|
||||
|
||||
for (const parameter of toolParameters) {
|
||||
@@ -727,6 +733,8 @@ export const configureToolFunction = (
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
const errorMessage = 'Input provided by model is not valid';
|
||||
|
||||
if (error instanceof NodeOperationError) {
|
||||
@@ -765,3 +773,38 @@ export const configureToolFunction = (
|
||||
return response;
|
||||
};
|
||||
};
|
||||
|
||||
function makeParameterZodSchema(parameter: ToolParameter) {
|
||||
let schema: z.ZodTypeAny;
|
||||
|
||||
if (parameter.type === 'string') {
|
||||
schema = z.string();
|
||||
} else if (parameter.type === 'number') {
|
||||
schema = z.number();
|
||||
} else if (parameter.type === 'boolean') {
|
||||
schema = z.boolean();
|
||||
} else if (parameter.type === 'json') {
|
||||
schema = z.record(z.any());
|
||||
} else {
|
||||
schema = z.string();
|
||||
}
|
||||
|
||||
if (!parameter.required) {
|
||||
schema = schema.optional();
|
||||
}
|
||||
|
||||
if (parameter.description) {
|
||||
schema = schema.describe(parameter.description);
|
||||
}
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
||||
export function makeToolInputSchema(parameters: ToolParameter[]): DynamicZodObject {
|
||||
const schemaEntries = parameters.map((parameter) => [
|
||||
parameter.name,
|
||||
makeParameterZodSchema(parameter),
|
||||
]);
|
||||
|
||||
return z.object(Object.fromEntries(schemaEntries));
|
||||
}
|
||||
|
||||
@@ -493,7 +493,7 @@ export class ToolWorkflow implements INodeType {
|
||||
if (useSchema) {
|
||||
try {
|
||||
// We initialize these even though one of them will always be empty
|
||||
// it makes it easer to navigate the ternary operator
|
||||
// it makes it easier to navigate the ternary operator
|
||||
const jsonExample = this.getNodeParameter('jsonSchemaExample', itemIndex, '') as string;
|
||||
const inputSchema = this.getNodeParameter('inputSchema', itemIndex, '') as string;
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@ export async function execute(this: IExecuteFunctions, i: number): Promise<INode
|
||||
|
||||
const agent = new OpenAIAssistantRunnable({ assistantId, client, asAgent: true });
|
||||
|
||||
const tools = await getConnectedTools(this, nodeVersion > 1);
|
||||
const tools = await getConnectedTools(this, nodeVersion > 1, false);
|
||||
let assistantTools;
|
||||
|
||||
if (tools.length) {
|
||||
|
||||
@@ -219,7 +219,7 @@ export async function execute(this: IExecuteFunctions, i: number): Promise<INode
|
||||
|
||||
if (hideTools !== 'hide') {
|
||||
const enforceUniqueNames = nodeVersion > 1;
|
||||
externalTools = await getConnectedTools(this, enforceUniqueNames);
|
||||
externalTools = await getConnectedTools(this, enforceUniqueNames, false);
|
||||
}
|
||||
|
||||
if (externalTools.length) {
|
||||
|
||||
Reference in New Issue
Block a user