Decouple http node from workflows (#13272)
- Added a generic HTTP request tool, allowing agents and workflows to make HTTP requests to external APIs with configurable method, headers, and body. - Decoupled HTTP request workflow nodes from workflow-specific types and factories, introducing a generic tool interface. - Updated agent system prompts to include explicit guidance for the HTTP request tool, including when and how to use it, and how to communicate limitations. ### Demo https://github.com/user-attachments/assets/129bc445-a277-4a19-95ab-09f890f8f051
This commit is contained in:
@ -7,6 +7,7 @@ import { AIBillingService } from 'src/engine/core-modules/ai/services/ai-billing
|
||||
import { AiModelRegistryService } from 'src/engine/core-modules/ai/services/ai-model-registry.service';
|
||||
import { AiService } from 'src/engine/core-modules/ai/services/ai.service';
|
||||
import { McpService } from 'src/engine/core-modules/ai/services/mcp.service';
|
||||
import { ToolAdapterService } from 'src/engine/core-modules/ai/services/tool-adapter.service';
|
||||
import { ToolService } from 'src/engine/core-modules/ai/services/tool.service';
|
||||
import { TokenModule } from 'src/engine/core-modules/auth/token/token.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
@ -32,6 +33,7 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/
|
||||
AiService,
|
||||
AiModelRegistryService,
|
||||
ToolService,
|
||||
ToolAdapterService,
|
||||
AIBillingService,
|
||||
McpService,
|
||||
],
|
||||
@ -40,6 +42,7 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/
|
||||
AiModelRegistryService,
|
||||
AIBillingService,
|
||||
ToolService,
|
||||
ToolAdapterService,
|
||||
McpService,
|
||||
],
|
||||
})
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { ToolSet } from 'ai';
|
||||
|
||||
import { TOOLS } from 'src/engine/core-modules/tool/constants/tools.const';
|
||||
|
||||
@Injectable()
|
||||
export class ToolAdapterService {
|
||||
getCoreTools(): ToolSet {
|
||||
const tools = Array.from(TOOLS.entries()).reduce<ToolSet>(
|
||||
(acc, [toolType, tool]) => {
|
||||
acc[toolType.toLowerCase()] = {
|
||||
description: tool.description,
|
||||
parameters: tool.parameters,
|
||||
execute: async (parameters) => {
|
||||
const response = await tool.execute(parameters.input);
|
||||
|
||||
return response;
|
||||
},
|
||||
};
|
||||
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
return tools;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
import { ToolType } from 'src/engine/core-modules/tool/enums/tool-type.enum';
|
||||
import { HttpTool } from 'src/engine/core-modules/tool/tools/http-tool/http-tool';
|
||||
import { Tool } from 'src/engine/core-modules/tool/types/tool.type';
|
||||
|
||||
export const TOOLS: Map<ToolType, Tool> = new Map([
|
||||
[ToolType.HTTP_REQUEST, new HttpTool()],
|
||||
]);
|
||||
@ -0,0 +1,3 @@
|
||||
export enum ToolType {
|
||||
HTTP_REQUEST = 'HTTP_REQUEST',
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const HttpRequestInputZodSchema = z.object({
|
||||
url: z.string().describe('The URL to make the request to'),
|
||||
method: z
|
||||
.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE'])
|
||||
.describe('The HTTP method to use'),
|
||||
headers: z
|
||||
.record(z.string())
|
||||
.optional()
|
||||
.describe('HTTP headers to include in the request'),
|
||||
body: z
|
||||
.any()
|
||||
.optional()
|
||||
.describe('Request body for POST, PUT, PATCH requests'),
|
||||
});
|
||||
|
||||
export const HttpToolParametersZodSchema = z.object({
|
||||
toolDescription: z
|
||||
.string()
|
||||
.describe(
|
||||
"A clear, human-readable status message describing the HTTP request being made. This will be shown to the user while the tool is being called, so phrase it as a present-tense status update (e.g., 'Making a GET request to ...'). Explain what endpoint you are calling and with what parameters in natural language.",
|
||||
),
|
||||
input: HttpRequestInputZodSchema,
|
||||
});
|
||||
@ -0,0 +1,46 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import axios, { AxiosRequestConfig } from 'axios';
|
||||
|
||||
import { HttpToolParametersZodSchema } from 'src/engine/core-modules/tool/tools/http-tool/http-tool.schema';
|
||||
import { HttpRequestInput } from 'src/engine/core-modules/tool/tools/http-tool/types/http-request-input.type';
|
||||
import { ToolInput } from 'src/engine/core-modules/tool/types/tool-input.type';
|
||||
import { ToolOutput } from 'src/engine/core-modules/tool/types/tool-output.type';
|
||||
import { Tool } from 'src/engine/core-modules/tool/types/tool.type';
|
||||
|
||||
@Injectable()
|
||||
export class HttpTool implements Tool {
|
||||
description =
|
||||
'Make an HTTP request to any URL with configurable method, headers, and body.';
|
||||
parameters = HttpToolParametersZodSchema;
|
||||
|
||||
async execute(parameters: ToolInput): Promise<ToolOutput> {
|
||||
const { url, method, headers, body } = parameters as HttpRequestInput;
|
||||
|
||||
try {
|
||||
const axiosConfig: AxiosRequestConfig = {
|
||||
url,
|
||||
method: method,
|
||||
headers,
|
||||
};
|
||||
|
||||
if (['POST', 'PUT', 'PATCH'].includes(method) && body) {
|
||||
axiosConfig.data = body;
|
||||
}
|
||||
|
||||
const response = await axios(axiosConfig);
|
||||
|
||||
return { result: response.data };
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
return {
|
||||
error: error.response?.data || error.message || 'HTTP request failed',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
error: error instanceof Error ? error.message : 'HTTP request failed',
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { HttpRequestInputZodSchema } from 'src/engine/core-modules/tool/tools/http-tool/http-tool.schema';
|
||||
|
||||
export type HttpRequestInput = z.infer<typeof HttpRequestInputZodSchema>;
|
||||
@ -0,0 +1,6 @@
|
||||
export type ToolContext = {
|
||||
workspaceId: string;
|
||||
userId?: string;
|
||||
roleId?: string;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export type ToolInput = Record<string, unknown>;
|
||||
@ -0,0 +1,4 @@
|
||||
export type ToolOutput = {
|
||||
result?: unknown;
|
||||
error?: string;
|
||||
};
|
||||
@ -0,0 +1,11 @@
|
||||
import { JSONSchema7 } from 'json-schema';
|
||||
import { ZodType } from 'zod';
|
||||
|
||||
import { ToolInput } from 'src/engine/core-modules/tool/types/tool-input.type';
|
||||
import { ToolOutput } from 'src/engine/core-modules/tool/types/tool-output.type';
|
||||
|
||||
export type Tool = {
|
||||
description: string;
|
||||
parameters: JSONSchema7 | ZodType;
|
||||
execute(input: ToolInput): Promise<ToolOutput>;
|
||||
};
|
||||
Reference in New Issue
Block a user