Replace agent handoff system with planning-based router (#16003)
## Overview This PR replaces the dynamic agent handoff system with a more predictable planning-based router that decides upfront how to handle multi-agent coordination. ## Major Changes ### 🔄 Architecture Shift: Handoffs → Planning **Removed:** - `AgentHandoffEntity` and handoff tracking system - `AgentHandoffService` and `AgentHandoffExecutorService` - Dynamic agent-to-agent transfers during execution - Handoff tool generation and description templates **Added:** - `AiRouterService` with two strategies: `simple` (single agent) and `planned` (multi-agent) - `AgentPlanExecutorService` for executing multi-step plans - Plan validation (cycle detection, dependency resolution) - `UnifiedRouterResult` type with discriminated union ### 🤖 New Standard Agents Added two new specialized agents: - **Researcher Agent**: Web search, fact-finding, competitive intelligence - **Code Agent**: TypeScript function generation for serverless workflows ### 🏗️ Router Refactoring (Latest) Split router responsibilities into focused services: - `AiRouterStrategyDeciderService`: Decides simple vs planned strategy - `AiRouterPlanGeneratorService`: Generates and validates execution plans - `AiRouterService`: Coordinates between services (reduced from 426→275 lines) ### ⚙️ Configuration Improvements - Added `outputStrategy` to agent definitions (`direct` vs `synthesize`) - Removed hardcoded special cases for workflow-builder - Added `plannerModel` field to workspace entity - Increased `MAX_STEPS` from 10 to 25 for complex workflows ### 📝 Agent Prompt Refinements Significantly simplified prompts for better clarity: - Workflow Builder: 51→36 lines - Helper: 49→28 lines - Data Manipulator: Enhanced with sorting guidance ### 🔍 Enhanced Debugging - Plan reasoning and step count in data message parts - Router debug info with token usage tracking - Better logging throughout execution pipeline ## Benefits 1. **Simpler Mental Model**: Router decides upfront vs dynamic transfers 2. **Better Predictability**: Users see the plan before execution 3. **Cleaner Architecture**: SRP with focused services 4. **Configuration Over Code**: Agent behavior via config, not hardcoded logic 5. **Plan Validation**: Catches invalid dependencies and cycles ## Migration Notes - Database migration removes `agentHandoff` table - Adds `plannerModel` column to workspace table - No API breaking changes (agent endpoints unchanged) ## Testing - Integration tests updated to remove handoff dependencies - Agent tool test utilities simplified - Plan validation covered by new logic ## Next Steps (Future PRs) - Parallel execution of independent plan steps - Dynamic re-planning based on results - Plan caching for common routing patterns - Error recovery strategies in plan executor
This commit is contained in:
@@ -3,7 +3,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { AiModule } from 'src/engine/core-modules/ai/ai.module';
|
||||
import { AiBillingModule } from 'src/engine/metadata-modules/ai-billing/ai-billing.module';
|
||||
import { AiModelsModule } from 'src/engine/metadata-modules/ai-models/ai-models.module';
|
||||
import { AiToolsModule } from 'src/engine/metadata-modules/ai-tools/ai-tools.module';
|
||||
import { BillingResolver } from 'src/engine/core-modules/billing/billing.resolver';
|
||||
import { BillingSyncCustomerDataCommand } from 'src/engine/core-modules/billing/commands/billing-sync-customer-data.command';
|
||||
import { BillingSyncPlansDataCommand } from 'src/engine/core-modules/billing/commands/billing-sync-plans-data.command';
|
||||
@@ -42,7 +44,9 @@ import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permi
|
||||
StripeModule,
|
||||
MessageQueueModule,
|
||||
PermissionsModule,
|
||||
AiModule,
|
||||
AiBillingModule,
|
||||
AiModelsModule,
|
||||
AiToolsModule,
|
||||
WorkspaceDomainsModule,
|
||||
TypeOrmModule.forFeature([
|
||||
BillingSubscriptionEntity,
|
||||
|
||||
+1
@@ -71,6 +71,7 @@ export class BillingUsageService {
|
||||
eventName: billingEvents[0].eventName,
|
||||
value: billingEvents[0].value,
|
||||
stripeCustomerId: workspaceStripeCustomer.stripeCustomerId,
|
||||
dimensions: billingEvents[0].dimensions,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new BillingException(
|
||||
|
||||
+21
-4
@@ -5,6 +5,7 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import type Stripe from 'stripe';
|
||||
|
||||
import { type BillingMeterEventName } from 'src/engine/core-modules/billing/enums/billing-meter-event-names';
|
||||
import { type BillingDimensions } from 'src/engine/core-modules/billing/types/billing-dimensions.type';
|
||||
import { StripeSDKService } from 'src/engine/core-modules/billing/stripe/stripe-sdk/services/stripe-sdk.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
|
||||
@@ -29,17 +30,33 @@ export class StripeBillingMeterEventService {
|
||||
eventName,
|
||||
value,
|
||||
stripeCustomerId,
|
||||
dimensions,
|
||||
}: {
|
||||
eventName: BillingMeterEventName;
|
||||
value: number;
|
||||
stripeCustomerId: string;
|
||||
dimensions?: BillingDimensions;
|
||||
}) {
|
||||
const payload: Record<string, string> = {
|
||||
value: value.toString(),
|
||||
stripe_customer_id: stripeCustomerId,
|
||||
};
|
||||
|
||||
if (dimensions) {
|
||||
payload.execution_type = dimensions.execution_type;
|
||||
|
||||
if (dimensions.resource_id !== undefined) {
|
||||
payload.resource_id = dimensions.resource_id || 'none';
|
||||
}
|
||||
|
||||
if (dimensions.execution_context_1 !== undefined) {
|
||||
payload.execution_context_1 = dimensions.execution_context_1 || 'none';
|
||||
}
|
||||
}
|
||||
|
||||
await this.stripe.billing.meterEvents.create({
|
||||
event_name: eventName,
|
||||
payload: {
|
||||
value: value.toString(),
|
||||
stripe_customer_id: stripeCustomerId,
|
||||
},
|
||||
payload,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
/* @license Enterprise */
|
||||
|
||||
export type BillingExecutionType =
|
||||
| 'workflow_execution'
|
||||
| 'code_execution'
|
||||
| 'ai_token';
|
||||
|
||||
export type BillingDimensions = {
|
||||
execution_type: BillingExecutionType;
|
||||
resource_id?: string | null;
|
||||
execution_context_1?: string | null;
|
||||
};
|
||||
+2
@@ -3,8 +3,10 @@
|
||||
import { type NonNegative } from 'type-fest';
|
||||
|
||||
import { type BillingMeterEventName } from 'src/engine/core-modules/billing/enums/billing-meter-event-names';
|
||||
import { type BillingDimensions } from 'src/engine/core-modules/billing/types/billing-dimensions.type';
|
||||
|
||||
export type BillingUsageEvent = {
|
||||
eventName: BillingMeterEventName;
|
||||
value: NonNegative<number>;
|
||||
dimensions?: BillingDimensions;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user