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:
Abdul Rahman
2025-07-18 20:17:19 +05:30
committed by GitHub
parent 45655a39b0
commit dd24fbe4ee
20 changed files with 259 additions and 113 deletions

View File

@ -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,
],
})

View File

@ -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;
}
}

View File

@ -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()],
]);

View File

@ -0,0 +1,3 @@
export enum ToolType {
HTTP_REQUEST = 'HTTP_REQUEST',
}

View File

@ -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,
});

View File

@ -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',
};
}
}
}

View File

@ -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>;

View File

@ -0,0 +1,6 @@
export type ToolContext = {
workspaceId: string;
userId?: string;
roleId?: string;
[key: string]: unknown;
};

View File

@ -0,0 +1 @@
export type ToolInput = Record<string, unknown>;

View File

@ -0,0 +1,4 @@
export type ToolOutput = {
result?: unknown;
error?: string;
};

View File

@ -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>;
};