feat: Add AI Agent workflow action node (#12650)
https://github.com/user-attachments/assets/8593e488-cb00-4fd2-b903-5ba5766e0254 --------- Co-authored-by: Antoine Moreaux <moreaux.antoine@gmail.com> Co-authored-by: martmull <martmull@hotmail.fr> Co-authored-by: Félix Malfait <felix.malfait@gmail.com> Co-authored-by: Baptiste Devessier <baptiste@devessier.fr> Co-authored-by: Joseph Chiang <josephj6802@gmail.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Guillim <guillim@users.noreply.github.com> Co-authored-by: Raphaël Bosi <71827178+bosiraphael@users.noreply.github.com> Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com> Co-authored-by: Marie <51697796+ijreilly@users.noreply.github.com> Co-authored-by: Naifer <161821705+omarNaifer12@users.noreply.github.com> Co-authored-by: prastoin <paul@twenty.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions <github-actions@twenty.com> Co-authored-by: Thomas Trompette <thomas.trompette@sfr.fr> Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com> Co-authored-by: Ajay A Adsule <103304466+AjayAdsule@users.noreply.github.com> Co-authored-by: bosiraphael <raphael.bosi@gmail.com> Co-authored-by: Charles Bochet <charles@twenty.com> Co-authored-by: Marty <91310557+real-marty@users.noreply.github.com> Co-authored-by: Félix Malfait <felix@twenty.com> Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Paul Rastoin <45004772+prastoin@users.noreply.github.com> Co-authored-by: Weiko <corentin@twenty.com> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: nitin <142569587+ehconitin@users.noreply.github.com>
This commit is contained in:
@ -5,6 +5,7 @@ export type Leaf = {
|
||||
type?: InputSchemaPropertyType;
|
||||
icon?: string;
|
||||
label?: string;
|
||||
description?: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
value: any;
|
||||
};
|
||||
@ -14,6 +15,7 @@ export type Node = {
|
||||
type?: InputSchemaPropertyType;
|
||||
icon?: string;
|
||||
label?: string;
|
||||
description?: string;
|
||||
value: OutputSchema;
|
||||
};
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
|
||||
|
||||
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
|
||||
import { AgentModule } from 'src/engine/metadata-modules/agent/agent.module';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless-function/serverless-function.module';
|
||||
import { WorkflowSchemaModule } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.module';
|
||||
@ -11,6 +12,7 @@ import { WorkflowRunnerModule } from 'src/modules/workflow/workflow-runner/workf
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
AgentModule,
|
||||
WorkflowSchemaModule,
|
||||
ServerlessFunctionModule,
|
||||
WorkflowRunnerModule,
|
||||
|
||||
@ -8,6 +8,7 @@ import { v4 } from 'uuid';
|
||||
import { BASE_TYPESCRIPT_PROJECT_INPUT_SCHEMA } from 'src/engine/core-modules/serverless/drivers/constants/base-typescript-project-input-schema';
|
||||
import { CreateWorkflowVersionStepInput } from 'src/engine/core-modules/workflow/dtos/create-workflow-version-step-input.dto';
|
||||
import { WorkflowActionDTO } from 'src/engine/core-modules/workflow/dtos/workflow-step.dto';
|
||||
import { AgentService } from 'src/engine/metadata-modules/agent/agent.service';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
@ -50,6 +51,7 @@ export class WorkflowVersionStepWorkspaceService {
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly workflowSchemaWorkspaceService: WorkflowSchemaWorkspaceService,
|
||||
private readonly serverlessFunctionService: ServerlessFunctionService,
|
||||
private readonly agentService: AgentService,
|
||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
private readonly workflowRunWorkspaceService: WorkflowRunWorkspaceService,
|
||||
@ -350,11 +352,13 @@ export class WorkflowVersionStepWorkspaceService {
|
||||
}): Promise<WorkflowAction> {
|
||||
// We don't enrich on the fly for code and HTTP request workflow actions.
|
||||
// For code actions, OutputSchema is computed and updated when testing the serverless function.
|
||||
// For HTTP requests, OutputSchema is determined by the expamle response input
|
||||
// For HTTP requests and AI agent, OutputSchema is determined by the expamle response input
|
||||
if (
|
||||
[WorkflowActionType.CODE, WorkflowActionType.HTTP_REQUEST].includes(
|
||||
step.type,
|
||||
)
|
||||
[
|
||||
WorkflowActionType.CODE,
|
||||
WorkflowActionType.HTTP_REQUEST,
|
||||
WorkflowActionType.AI_AGENT,
|
||||
].includes(step.type)
|
||||
) {
|
||||
return step;
|
||||
}
|
||||
@ -396,6 +400,17 @@ export class WorkflowVersionStepWorkspaceService {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WorkflowActionType.AI_AGENT: {
|
||||
const agent = await this.agentService.findOneAgent(
|
||||
step.settings.input.agentId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (agent) {
|
||||
await this.agentService.deleteOneAgent(agent.id, workspaceId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -578,6 +593,37 @@ export class WorkflowVersionStepWorkspaceService {
|
||||
},
|
||||
};
|
||||
}
|
||||
case WorkflowActionType.AI_AGENT: {
|
||||
const newAgent = await this.agentService.createOneAgent(
|
||||
{
|
||||
name: 'AI Agent Workflow Step',
|
||||
description: 'Created automatically for workflow step',
|
||||
prompt: '',
|
||||
modelId: 'gpt-4o',
|
||||
},
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!isDefined(newAgent)) {
|
||||
throw new WorkflowVersionStepException(
|
||||
'Failed to create AI Agent Step',
|
||||
WorkflowVersionStepExceptionCode.FAILURE,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
id: newStepId,
|
||||
name: 'AI Agent',
|
||||
type: WorkflowActionType.AI_AGENT,
|
||||
valid: false,
|
||||
settings: {
|
||||
...BASE_STEP_DEFINITION,
|
||||
input: {
|
||||
agentId: newAgent.id,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
default:
|
||||
throw new WorkflowVersionStepException(
|
||||
`WorkflowActionType '${type}' unknown`,
|
||||
|
||||
@ -6,6 +6,7 @@ import {
|
||||
WorkflowStepExecutorException,
|
||||
WorkflowStepExecutorExceptionCode,
|
||||
} from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception';
|
||||
import { AiAgentWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/ai-agent/ai-agent.workflow-action';
|
||||
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';
|
||||
@ -29,6 +30,7 @@ export class WorkflowExecutorFactory {
|
||||
private readonly formWorkflowAction: FormWorkflowAction,
|
||||
private readonly filterWorkflowAction: FilterWorkflowAction,
|
||||
private readonly httpRequestWorkflowAction: HttpRequestWorkflowAction,
|
||||
private readonly aiAgentWorkflowAction: AiAgentWorkflowAction,
|
||||
) {}
|
||||
|
||||
get(stepType: WorkflowActionType): WorkflowExecutor {
|
||||
@ -51,6 +53,8 @@ export class WorkflowExecutorFactory {
|
||||
return this.filterWorkflowAction;
|
||||
case WorkflowActionType.HTTP_REQUEST:
|
||||
return this.httpRequestWorkflowAction;
|
||||
case WorkflowActionType.AI_AGENT:
|
||||
return this.aiAgentWorkflowAction;
|
||||
default:
|
||||
throw new WorkflowStepExecutorException(
|
||||
`Workflow step executor not found for step type '${stepType}'`,
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { AiDriver } from 'src/engine/core-modules/ai/interfaces/ai.interface';
|
||||
|
||||
import { AiModule } from 'src/engine/core-modules/ai/ai.module';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/agent/agent.entity';
|
||||
import { AgentModule } from 'src/engine/metadata-modules/agent/agent.module';
|
||||
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
||||
|
||||
import { AiAgentWorkflowAction } from './ai-agent.workflow-action';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
AgentModule,
|
||||
AiModule.forRoot({
|
||||
useFactory: () => ({ type: AiDriver.OPENAI }),
|
||||
}),
|
||||
TypeOrmModule.forFeature([AgentEntity], 'core'),
|
||||
],
|
||||
providers: [ScopedWorkspaceContextFactory, AiAgentWorkflowAction],
|
||||
exports: [AiAgentWorkflowAction],
|
||||
})
|
||||
export class AiAgentActionModule {}
|
||||
@ -0,0 +1,98 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { WorkflowExecutor } from 'src/modules/workflow/workflow-executor/interfaces/workflow-executor.interface';
|
||||
|
||||
import { AIBillingService } from 'src/engine/core-modules/ai/services/ai-billing.service';
|
||||
import { AgentExecutionService } from 'src/engine/metadata-modules/agent/agent-execution.service';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/agent/agent.entity';
|
||||
import {
|
||||
AgentException,
|
||||
AgentExceptionCode,
|
||||
} from 'src/engine/metadata-modules/agent/agent.exception';
|
||||
import {
|
||||
WorkflowStepExecutorException,
|
||||
WorkflowStepExecutorExceptionCode,
|
||||
} from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception';
|
||||
import { WorkflowExecutorInput } from 'src/modules/workflow/workflow-executor/types/workflow-executor-input';
|
||||
import { WorkflowExecutorOutput } from 'src/modules/workflow/workflow-executor/types/workflow-executor-output.type';
|
||||
|
||||
import { isWorkflowAiAgentAction } from './guards/is-workflow-ai-agent-action.guard';
|
||||
|
||||
@Injectable()
|
||||
export class AiAgentWorkflowAction implements WorkflowExecutor {
|
||||
constructor(
|
||||
private readonly agentExecutionService: AgentExecutionService,
|
||||
private readonly aiBillingService: AIBillingService,
|
||||
@InjectRepository(AgentEntity, 'core')
|
||||
private readonly agentRepository: Repository<AgentEntity>,
|
||||
) {}
|
||||
|
||||
async execute({
|
||||
currentStepId,
|
||||
steps,
|
||||
context,
|
||||
}: WorkflowExecutorInput): Promise<WorkflowExecutorOutput> {
|
||||
const step = steps.find((step) => step.id === currentStepId);
|
||||
|
||||
if (!step) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
'Step not found',
|
||||
WorkflowStepExecutorExceptionCode.STEP_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isWorkflowAiAgentAction(step)) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
'Step is not an AI Agent action',
|
||||
WorkflowStepExecutorExceptionCode.INVALID_STEP_TYPE,
|
||||
);
|
||||
}
|
||||
|
||||
const { agentId } = step.settings.input;
|
||||
const workspaceId = context.workspaceId as string;
|
||||
|
||||
try {
|
||||
const agent = await this.agentRepository.findOne({
|
||||
where: {
|
||||
id: agentId,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!agent) {
|
||||
throw new AgentException(
|
||||
`Agent with id ${agentId} not found`,
|
||||
AgentExceptionCode.AGENT_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const executionResult = await this.agentExecutionService.executeAgent({
|
||||
agent,
|
||||
context,
|
||||
schema: step.settings.outputSchema,
|
||||
});
|
||||
|
||||
await this.aiBillingService.calculateAndBillUsage(
|
||||
agent.modelId,
|
||||
executionResult.usage,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return { result: executionResult.object };
|
||||
} catch (error) {
|
||||
if (error instanceof AgentException) {
|
||||
return {
|
||||
error: `${error.message} (${error.code})`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
error:
|
||||
error instanceof Error ? error.message : 'AI Agent execution failed',
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
import {
|
||||
WorkflowAction,
|
||||
WorkflowActionType,
|
||||
WorkflowAiAgentAction,
|
||||
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
|
||||
export const isWorkflowAiAgentAction = (
|
||||
action: WorkflowAction,
|
||||
): action is WorkflowAiAgentAction => {
|
||||
return action.type === WorkflowActionType.AI_AGENT;
|
||||
};
|
||||
@ -0,0 +1,3 @@
|
||||
export type WorkflowAiAgentActionInput = {
|
||||
agentId: string;
|
||||
};
|
||||
@ -0,0 +1,6 @@
|
||||
import { WorkflowAiAgentActionInput } from 'src/modules/workflow/workflow-executor/workflow-actions/ai-agent/types/workflow-ai-agent-action-input.type';
|
||||
import { BaseWorkflowActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-settings.type';
|
||||
|
||||
export type WorkflowAiAgentActionSettings = BaseWorkflowActionSettings & {
|
||||
input: WorkflowAiAgentActionInput;
|
||||
};
|
||||
@ -1,4 +1,5 @@
|
||||
import { OutputSchema } from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
|
||||
import { WorkflowAiAgentActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/ai-agent/types/workflow-ai-agent-action-settings.type';
|
||||
import { WorkflowCodeActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/code/types/workflow-code-action-settings.type';
|
||||
import { WorkflowFilterActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/filter/types/workflow-filter-action-settings.type';
|
||||
import { WorkflowFormActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type';
|
||||
@ -32,4 +33,5 @@ export type WorkflowActionSettings =
|
||||
| WorkflowFindRecordsActionSettings
|
||||
| WorkflowFormActionSettings
|
||||
| WorkflowFilterActionSettings
|
||||
| WorkflowHttpRequestActionSettings;
|
||||
| WorkflowHttpRequestActionSettings
|
||||
| WorkflowAiAgentActionSettings;
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { WorkflowAiAgentActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/ai-agent/types/workflow-ai-agent-action-settings.type';
|
||||
import { WorkflowCodeActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/code/types/workflow-code-action-settings.type';
|
||||
import { WorkflowFilterActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/filter/types/workflow-filter-action-settings.type';
|
||||
import { WorkflowFormActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type';
|
||||
@ -21,6 +22,7 @@ export enum WorkflowActionType {
|
||||
FORM = 'FORM',
|
||||
FILTER = 'FILTER',
|
||||
HTTP_REQUEST = 'HTTP_REQUEST',
|
||||
AI_AGENT = 'AI_AGENT',
|
||||
}
|
||||
|
||||
type BaseWorkflowAction = {
|
||||
@ -77,6 +79,11 @@ export type WorkflowHttpRequestAction = BaseWorkflowAction & {
|
||||
settings: WorkflowHttpRequestActionSettings;
|
||||
};
|
||||
|
||||
export type WorkflowAiAgentAction = BaseWorkflowAction & {
|
||||
type: WorkflowActionType.AI_AGENT;
|
||||
settings: WorkflowAiAgentActionSettings;
|
||||
};
|
||||
|
||||
export type WorkflowAction =
|
||||
| WorkflowCodeAction
|
||||
| WorkflowSendEmailAction
|
||||
@ -86,4 +93,5 @@ export type WorkflowAction =
|
||||
| WorkflowFindRecordsAction
|
||||
| WorkflowFormAction
|
||||
| WorkflowFilterAction
|
||||
| WorkflowHttpRequestAction;
|
||||
| WorkflowHttpRequestAction
|
||||
| WorkflowAiAgentAction;
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { BillingModule } from 'src/engine/core-modules/billing/billing.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
||||
import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module';
|
||||
import { WorkflowExecutorFactory } from 'src/modules/workflow/workflow-executor/factories/workflow-executor.factory';
|
||||
import { AiAgentActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/ai-agent/ai-agent-action.module';
|
||||
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';
|
||||
@ -24,6 +26,8 @@ import { WorkflowRunModule } from 'src/modules/workflow/workflow-runner/workflow
|
||||
BillingModule,
|
||||
FilterActionModule,
|
||||
HttpRequestActionModule,
|
||||
AiAgentActionModule,
|
||||
FeatureFlagModule,
|
||||
],
|
||||
providers: [
|
||||
WorkflowExecutorWorkspaceService,
|
||||
|
||||
Reference in New Issue
Block a user