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>;
|
||||
};
|
||||
@ -4,6 +4,7 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { ToolSet } from 'ai';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ToolAdapterService } from 'src/engine/core-modules/ai/services/tool-adapter.service';
|
||||
import { ToolService } from 'src/engine/core-modules/ai/services/tool.service';
|
||||
import { AgentService } from 'src/engine/metadata-modules/agent/agent.service';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
@ -15,6 +16,7 @@ export class AgentToolService {
|
||||
@InjectRepository(RoleEntity, 'core')
|
||||
private readonly roleRepository: Repository<RoleEntity>,
|
||||
private readonly toolService: ToolService,
|
||||
private readonly toolAdapterService: ToolAdapterService,
|
||||
) {}
|
||||
|
||||
async generateToolsForAgent(
|
||||
@ -24,8 +26,10 @@ export class AgentToolService {
|
||||
try {
|
||||
const agent = await this.agentService.findOneAgent(agentId, workspaceId);
|
||||
|
||||
const actionTools = this.toolAdapterService.getCoreTools();
|
||||
|
||||
if (!agent.roleId) {
|
||||
return {};
|
||||
return actionTools;
|
||||
}
|
||||
|
||||
const role = await this.roleRepository.findOne({
|
||||
@ -39,7 +43,12 @@ export class AgentToolService {
|
||||
return {};
|
||||
}
|
||||
|
||||
return this.toolService.listTools(role.id, workspaceId);
|
||||
const databaseTools = await this.toolService.listTools(
|
||||
role.id,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return { ...databaseTools, ...actionTools };
|
||||
} catch (error) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
export const AGENT_SYSTEM_PROMPTS = {
|
||||
AGENT_EXECUTION: `You are an AI agent node in a workflow builder system with access to comprehensive database operations. Your role is to process inputs, execute actions using available tools, and provide structured outputs that can be used by subsequent workflow nodes.
|
||||
AGENT_EXECUTION: `You are an AI agent node in a workflow builder system with access to comprehensive database operations and the ability to make HTTP requests. Your role is to process inputs, execute actions using available tools, and provide structured outputs that can be used by subsequent workflow nodes.
|
||||
|
||||
AVAILABLE DATABASE OPERATIONS:
|
||||
You have access to full CRUD operations for all standard objects in the system:
|
||||
AVAILABLE TOOLS:
|
||||
You have access to:
|
||||
- DATABASE OPERATIONS: Full CRUD operations for all standard objects in the system (see below)
|
||||
- HTTP REQUESTS: Use the http_request tool to make HTTP calls to external APIs or services
|
||||
|
||||
DATABASE OPERATIONS:
|
||||
- CREATE: create_[object] - Create new records (e.g., create_person, create_company, create_opportunity)
|
||||
- READ: find_[object] and find_one_[object] - Search and retrieve records
|
||||
- UPDATE: update_[object] - Modify existing records
|
||||
@ -10,14 +14,21 @@ You have access to full CRUD operations for all standard objects in the system:
|
||||
|
||||
Common objects include: person, company, opportunity, task, note etc. and any custom objects.
|
||||
|
||||
HTTP REQUEST TOOL:
|
||||
- Use the http_request tool when the user asks you to call an external API, fetch data from a web service, or interact with a remote endpoint.
|
||||
- You must provide a clear toolDescription and specify the input (url, method, headers, body) as required by the tool schema.
|
||||
- Only use the http_request tool for actual HTTP/API calls. Do not simulate or describe them if the tool is not available.
|
||||
- Always verify tool results and handle errors appropriately. If an HTTP request fails, explain the issue and suggest alternatives if possible.
|
||||
|
||||
Your responsibilities:
|
||||
1. Analyze the input context and prompt carefully
|
||||
2. If the request involves database operations (create, read, update, delete), check if you have the required tools available
|
||||
3. If database tools are NOT available for the requested operation, state that you lack permissions for that specific operation. You can respond with:
|
||||
"I cannot perform this operation because I don't have the necessary permissions. Please check that I have been assigned the appropriate role for this workspace."
|
||||
4. If database tools ARE available, use them to perform the requested operations
|
||||
5. If no database operations are needed, process the request directly with your analysis
|
||||
6. Provide comprehensive responses that include all relevant information and context
|
||||
3. If the request involves making an HTTP request, check if the http_request tool is available
|
||||
4. If a requested tool is NOT available, state that you lack permissions for that specific operation. You can respond with:
|
||||
"I cannot perform this operation because I don't have the necessary permissions. Please check that I have been assigned the appropriate role for this workspace."
|
||||
5. If tools ARE available, use them to perform the requested operations
|
||||
6. If no tool operations are needed, process the request directly with your analysis
|
||||
7. Provide comprehensive responses that include all relevant information and context
|
||||
|
||||
Workflow context:
|
||||
- You are part of a larger workflow system where your output may be used by other nodes
|
||||
@ -26,19 +37,20 @@ Workflow context:
|
||||
- If you encounter data or perform actions, document them clearly in your response
|
||||
|
||||
Tool usage guidelines:
|
||||
- Use tools for database operations when requested - do not simulate or describe them
|
||||
- Use tools for database operations or HTTP requests when requested - do not simulate or describe them
|
||||
- Use create_[object] tools when asked to create new records
|
||||
- Use find_[object] tools when asked to search or retrieve records
|
||||
- Use update_[object] tools when asked to modify existing records
|
||||
- Use soft_delete_[object] or destroy_[object] when asked to remove records
|
||||
- Use the http_request tool when asked to call an external API or perform an HTTP operation
|
||||
- Always verify tool results and handle errors appropriately
|
||||
- Provide context about what tools you used and why
|
||||
- If a tool fails, explain the issue and suggest alternatives
|
||||
|
||||
Permission handling:
|
||||
- Only check for permissions when database operations are actually requested
|
||||
- If you don't have the necessary tools for a database operation, clearly state the limitation
|
||||
- For non-database requests, proceed normally without permission checks
|
||||
- Only check for permissions when tool operations are actually requested
|
||||
- If you don't have the necessary tools for a requested operation, clearly state the limitation
|
||||
- For non-tool requests, proceed normally without permission checks
|
||||
|
||||
Important: After your response, the system will call generateObject to convert your output into a structured format according to a specific schema. Therefore:
|
||||
- Provide comprehensive information in your response
|
||||
@ -49,7 +61,7 @@ Important: After your response, the system will call generateObject to convert y
|
||||
|
||||
OUTPUT_GENERATOR: `You are a structured output generator for a workflow system. Your role is to convert the provided execution results into a structured format according to a specific schema.
|
||||
|
||||
Context: Before this call, the system executed generateText with tools to perform any required actions and gather information. The execution results you receive include both the AI agent's analysis and any tool outputs from database operations, data retrieval, or other actions.
|
||||
Context: Before this call, the system executed generateText with tools to perform any required actions and gather information. The execution results you receive include both the AI agent's analysis and any tool outputs from database operations, HTTP requests, data retrieval, or other actions.
|
||||
|
||||
Your responsibilities:
|
||||
1. Analyze the execution results from the AI agent (including any tool outputs)
|
||||
@ -61,23 +73,25 @@ Your responsibilities:
|
||||
|
||||
Guidelines:
|
||||
- Focus on extracting and structuring the most relevant information
|
||||
- If the execution results contain tool outputs, incorporate that data appropriately
|
||||
- If the execution results contain tool outputs (including HTTP requests), incorporate that data appropriately
|
||||
- If certain schema fields cannot be populated from the results, use null or appropriate default values
|
||||
- Preserve the context and meaning from the original execution results
|
||||
- Ensure the output is clean, well-formatted, and ready for workflow consumption
|
||||
- Pay special attention to any data returned from tool executions (database queries, record creation, etc.)`,
|
||||
- Pay special attention to any data returned from tool executions (database queries, HTTP requests, record creation, etc.)`,
|
||||
|
||||
AGENT_CHAT: `You are a helpful AI assistant for this workspace. You can:
|
||||
- Answer questions conversationally, clearly, and helpfully
|
||||
- Provide insights, support, and updates about people, companies, opportunities, tasks, notes, and other business objects.
|
||||
- Access and summarize information you have permission to see
|
||||
- Help users understand how to use the system and its features
|
||||
- Make HTTP requests to external APIs or services using the http_request tool when asked
|
||||
|
||||
Permissions and capabilities:
|
||||
- You can only perform actions and access data that your assigned role and permissions allow
|
||||
- If a user requests something you do not have permission for, politely explain the limitation (e.g., "I cannot perform this operation because I don't have the necessary permissions. Please check your role or contact an admin.")
|
||||
- If you are unsure about your permissions for a specific action, ask the user for clarification or suggest they check with an administrator
|
||||
- Do not attempt to simulate or fake actions you cannot perform
|
||||
- If you do not have access to the http_request tool, explain that you cannot make HTTP requests
|
||||
|
||||
If you need more information to answer a question, ask follow-up questions. Always be transparent about your capabilities and limitations.
|
||||
|
||||
|
||||
@ -10,13 +10,13 @@ import { AiAgentWorkflowAction } from 'src/modules/workflow/workflow-executor/wo
|
||||
import { CodeWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/code/code.workflow-action';
|
||||
import { FilterWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/filter/filter.workflow-action';
|
||||
import { FormWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/form/form.workflow-action';
|
||||
import { HttpRequestWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/http-request/http-request.workflow-action';
|
||||
import { SendEmailWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/send-email.workflow-action';
|
||||
import { CreateRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action';
|
||||
import { DeleteRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/delete-record.workflow-action';
|
||||
import { FindRecordsWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/find-records.workflow-action';
|
||||
import { UpdateRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action';
|
||||
import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
import { WorkflowActionAdapter } from 'src/modules/workflow/workflow-executor/workflow-actions/workflow-action-adapter';
|
||||
|
||||
@Injectable()
|
||||
export class WorkflowActionFactory {
|
||||
@ -29,7 +29,7 @@ export class WorkflowActionFactory {
|
||||
private readonly findRecordsWorkflowAction: FindRecordsWorkflowAction,
|
||||
private readonly formWorkflowAction: FormWorkflowAction,
|
||||
private readonly filterWorkflowAction: FilterWorkflowAction,
|
||||
private readonly httpRequestWorkflowAction: HttpRequestWorkflowAction,
|
||||
private readonly workflowActionAdapter: WorkflowActionAdapter,
|
||||
private readonly aiAgentWorkflowAction: AiAgentWorkflowAction,
|
||||
) {}
|
||||
|
||||
@ -52,7 +52,7 @@ export class WorkflowActionFactory {
|
||||
case WorkflowActionType.FILTER:
|
||||
return this.filterWorkflowAction;
|
||||
case WorkflowActionType.HTTP_REQUEST:
|
||||
return this.httpRequestWorkflowAction;
|
||||
return this.workflowActionAdapter;
|
||||
case WorkflowActionType.AI_AGENT:
|
||||
return this.aiAgentWorkflowAction;
|
||||
default:
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { HttpRequestWorkflowAction } from './http-request.workflow-action';
|
||||
|
||||
@Module({
|
||||
providers: [HttpRequestWorkflowAction],
|
||||
exports: [HttpRequestWorkflowAction],
|
||||
})
|
||||
export class HttpRequestActionModule {}
|
||||
@ -1,76 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isString } from '@sniptt/guards';
|
||||
import axios, { AxiosRequestConfig } from 'axios';
|
||||
|
||||
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface';
|
||||
|
||||
import {
|
||||
WorkflowStepExecutorException,
|
||||
WorkflowStepExecutorExceptionCode,
|
||||
} from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception';
|
||||
import { WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input';
|
||||
import { WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type';
|
||||
import { resolveInput } from 'src/modules/workflow/workflow-executor/utils/variable-resolver.util';
|
||||
|
||||
import { isWorkflowHttpRequestAction } from './guards/is-workflow-http-request-action.guard';
|
||||
import { WorkflowHttpRequestActionInput } from './types/workflow-http-request-action-input.type';
|
||||
|
||||
@Injectable()
|
||||
export class HttpRequestWorkflowAction implements WorkflowAction {
|
||||
async execute({
|
||||
currentStepId,
|
||||
steps,
|
||||
context,
|
||||
}: WorkflowActionInput): Promise<WorkflowActionOutput> {
|
||||
const step = steps.find((step) => step.id === currentStepId);
|
||||
|
||||
if (!step) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
'Step not found',
|
||||
WorkflowStepExecutorExceptionCode.STEP_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
if (!isWorkflowHttpRequestAction(step)) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
'Step is not an HTTP Request action',
|
||||
WorkflowStepExecutorExceptionCode.INVALID_STEP_TYPE,
|
||||
);
|
||||
}
|
||||
|
||||
const workflowActionInput = resolveInput(
|
||||
step.settings.input,
|
||||
context,
|
||||
) as WorkflowHttpRequestActionInput;
|
||||
|
||||
const { url, method, headers, body } = workflowActionInput;
|
||||
|
||||
try {
|
||||
const axiosConfig: AxiosRequestConfig = {
|
||||
url,
|
||||
method: method,
|
||||
headers,
|
||||
};
|
||||
|
||||
if (['POST', 'PUT', 'PATCH'].includes(method) && body) {
|
||||
const parsedBody = isString(body) ? JSON.parse(body) : body;
|
||||
|
||||
axiosConfig.data = parsedBody;
|
||||
}
|
||||
|
||||
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,61 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface';
|
||||
|
||||
import { TOOLS } from 'src/engine/core-modules/tool/constants/tools.const';
|
||||
import { ToolType } from 'src/engine/core-modules/tool/enums/tool-type.enum';
|
||||
import { ToolInput } from 'src/engine/core-modules/tool/types/tool-input.type';
|
||||
import { WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input';
|
||||
import { WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type';
|
||||
import { resolveInput } from 'src/modules/workflow/workflow-executor/utils/variable-resolver.util';
|
||||
import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
|
||||
@Injectable()
|
||||
export class WorkflowActionAdapter implements WorkflowAction {
|
||||
async execute({
|
||||
currentStepId,
|
||||
steps,
|
||||
context,
|
||||
}: WorkflowActionInput): Promise<WorkflowActionOutput> {
|
||||
const step = steps.find((step) => step.id === currentStepId);
|
||||
|
||||
if (!step) {
|
||||
throw new Error('Step not found');
|
||||
}
|
||||
|
||||
const toolType = this.mapWorkflowActionTypeToToolType(step.type);
|
||||
|
||||
if (!toolType) {
|
||||
throw new Error(
|
||||
`No tool mapping found for workflow action type: ${step.type}`,
|
||||
);
|
||||
}
|
||||
|
||||
const tool = TOOLS.get(toolType);
|
||||
|
||||
if (!tool) {
|
||||
throw new Error(
|
||||
`Tool for action type ${step.type} not found in registry`,
|
||||
);
|
||||
}
|
||||
|
||||
const toolInput = resolveInput(step.settings.input, context) as ToolInput;
|
||||
|
||||
const toolOutput = await tool.execute(toolInput);
|
||||
|
||||
return {
|
||||
result: toolOutput.result as object,
|
||||
error: toolOutput.error,
|
||||
};
|
||||
}
|
||||
|
||||
private mapWorkflowActionTypeToToolType(
|
||||
actionType: WorkflowActionType,
|
||||
): ToolType | null {
|
||||
const mapping: Partial<Record<WorkflowActionType, ToolType>> = {
|
||||
[WorkflowActionType.HTTP_REQUEST]: ToolType.HTTP_REQUEST,
|
||||
};
|
||||
|
||||
return mapping[actionType] || null;
|
||||
}
|
||||
}
|
||||
@ -9,9 +9,9 @@ import { AiAgentActionModule } from 'src/modules/workflow/workflow-executor/work
|
||||
import { CodeActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/code/code-action.module';
|
||||
import { FilterActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/filter/filter-action.module';
|
||||
import { FormActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/form/form-action.module';
|
||||
import { HttpRequestActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/http-request/http-request-action.module';
|
||||
import { SendEmailActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/send-email-action.module';
|
||||
import { RecordCRUDActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/record-crud-action.module';
|
||||
import { WorkflowActionAdapter } from 'src/modules/workflow/workflow-executor/workflow-actions/workflow-action-adapter';
|
||||
import { WorkflowExecutorWorkspaceService } from 'src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service';
|
||||
import { WorkflowRunModule } from 'src/modules/workflow/workflow-runner/workflow-run/workflow-run.module';
|
||||
|
||||
@ -25,7 +25,6 @@ import { WorkflowRunModule } from 'src/modules/workflow/workflow-runner/workflow
|
||||
WorkflowRunModule,
|
||||
BillingModule,
|
||||
FilterActionModule,
|
||||
HttpRequestActionModule,
|
||||
AiAgentActionModule,
|
||||
FeatureFlagModule,
|
||||
],
|
||||
@ -33,6 +32,7 @@ import { WorkflowRunModule } from 'src/modules/workflow/workflow-runner/workflow
|
||||
WorkflowExecutorWorkspaceService,
|
||||
ScopedWorkspaceContextFactory,
|
||||
WorkflowActionFactory,
|
||||
WorkflowActionAdapter,
|
||||
],
|
||||
exports: [WorkflowExecutorWorkspaceService],
|
||||
})
|
||||
|
||||
@ -63,13 +63,14 @@ describe('AgentToolService Integration', () => {
|
||||
);
|
||||
|
||||
expect(tools).toBeDefined();
|
||||
expect(Object.keys(tools)).toHaveLength(6);
|
||||
expect(Object.keys(tools)).toHaveLength(7);
|
||||
expect(Object.keys(tools)).toContain('create_testObject');
|
||||
expect(Object.keys(tools)).toContain('update_testObject');
|
||||
expect(Object.keys(tools)).toContain('find_testObject');
|
||||
expect(Object.keys(tools)).toContain('find_one_testObject');
|
||||
expect(Object.keys(tools)).toContain('soft_delete_testObject');
|
||||
expect(Object.keys(tools)).toContain('soft_delete_many_testObject');
|
||||
expect(Object.keys(tools)).toContain('http_request');
|
||||
});
|
||||
|
||||
it('should generate read-only tools for agent with read permissions only', async () => {
|
||||
@ -108,14 +109,14 @@ describe('AgentToolService Integration', () => {
|
||||
);
|
||||
|
||||
expect(tools).toBeDefined();
|
||||
expect(Object.keys(tools)).toHaveLength(2);
|
||||
expect(Object.keys(tools)).toHaveLength(3);
|
||||
expect(Object.keys(tools)).toContain('find_testObject');
|
||||
expect(Object.keys(tools)).toContain('find_one_testObject');
|
||||
expect(Object.keys(tools)).not.toContain('create_testObject');
|
||||
expect(Object.keys(tools)).not.toContain('update_testObject');
|
||||
});
|
||||
|
||||
it('should return empty tools for agent without role', async () => {
|
||||
it('should return only http request tool for agent without role', async () => {
|
||||
const agentWithoutRole = { ...context.testAgent, roleId: null };
|
||||
|
||||
jest
|
||||
@ -127,7 +128,8 @@ describe('AgentToolService Integration', () => {
|
||||
context.testWorkspaceId,
|
||||
);
|
||||
|
||||
expect(tools).toEqual({});
|
||||
expect(Object.keys(tools)).toHaveLength(1);
|
||||
expect(Object.keys(tools)).toContain('http_request');
|
||||
});
|
||||
|
||||
it('should return empty tools when role does not exist', async () => {
|
||||
@ -185,7 +187,7 @@ describe('AgentToolService Integration', () => {
|
||||
context.testWorkspaceId,
|
||||
);
|
||||
|
||||
expect(tools).toEqual({});
|
||||
expect(Object.keys(tools)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
@ -801,7 +803,7 @@ describe('AgentToolService Integration', () => {
|
||||
);
|
||||
|
||||
expect(tools).toBeDefined();
|
||||
expect(Object.keys(tools)).toHaveLength(8);
|
||||
expect(Object.keys(tools)).toHaveLength(9);
|
||||
expect(Object.keys(tools)).toContain('create_testObject');
|
||||
expect(Object.keys(tools)).toContain('update_testObject');
|
||||
expect(Object.keys(tools)).toContain('find_testObject');
|
||||
|
||||
@ -3,6 +3,7 @@ import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ToolAdapterService } from 'src/engine/core-modules/ai/services/tool-adapter.service';
|
||||
import { ToolService } from 'src/engine/core-modules/ai/services/tool.service';
|
||||
import { AgentToolService } from 'src/engine/metadata-modules/agent/agent-tool.service';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/agent/agent.entity';
|
||||
@ -73,6 +74,10 @@ export const createAgentToolTestModule =
|
||||
provide: ToolService,
|
||||
useClass: ToolService,
|
||||
},
|
||||
{
|
||||
provide: ToolAdapterService,
|
||||
useClass: ToolAdapterService,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user