Chuyển đến nội dung chính

第 9 課:多代理編排 — Supervisor、路由和切換協議

代理路由、主管模式、代理切換協定、共享記憶體、代理專業化、對話分支、並發代理執行。

🏗️ 建築 — 第 9 課 第 9 課:多代理編排 — 主管,路由和切換協議

企業人工智慧聊天機器人平台架構-從原型到生產

第 3 部分:代理架構

亞洲開發網

1. Why Multi-Agent? — 單一代理限制

擁有 50 多個工具的單一代理程式將會遇到問題: 工具混亂 (選擇錯誤的工具), 上下文超載 (系統提示太長),以及 專業化程度差。多代理分為專門的代理,每個代理只處理一個域。


┌──────────────────── MULTI-AGENT ARCHITECTURE ────────────────────┐
│                                                                   │
│  User Message                                                     │
│       │                                                           │
│       ▼                                                           │
│  ┌──────────────┐                                                 │
│  │  Supervisor   │  (Router + Orchestrator)                       │
│  │  Agent        │                                                │
│  └──┬───┬───┬───┘                                                 │
│     │   │   │                                                     │
│     ▼   ▼   ▼                                                     │
│  ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐                        │
│  │Order│ │ FAQ │ │ HR  │ │ IT  │ │Sales│  Specialist Agents      │
│  │Agent│ │Agent│ │Agent│ │Agent│ │Agent│                          │
│  └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘                        │
│     │       │       │       │       │                             │
│     └───────┴───────┴───────┴───────┘                             │
│                     │                                             │
│              ┌──────▼──────┐                                      │
│              │ Shared State│ (Memory, Context, Conversation)      │
│              └─────────────┘                                      │
└───────────────────────────────────────────────────────────────────┘

2. 代理的定義和專業化


interface AgentDefinition {
  id: string;
  name: string;
  description: string;          // For supervisor's routing decision
  systemPrompt: string;
  tools: string[];              // Tool names this agent can use
  capabilities: string[];       // What this agent can do (for routing)
  escalationRules: EscalationRule[];
  modelConfig: {
    model: string;              // GPT-4o, Claude 3.5, etc.
    temperature: number;
    maxTokens: number;
  };
}

const AGENTS: AgentDefinition[] = [
  {
    id: 'order-agent',
    name: 'Order Management Agent',
    description: 'Handles order inquiries: status, tracking, cancellation, returns',
    systemPrompt: `You are an order management specialist. Help customers with:
- Checking order status and tracking
- Processing cancellations and returns
- Updating shipping addresses
Always verify the order ID before taking any action.`,
    tools: ['get_order_status', 'cancel_order', 'create_return', 'update_shipping'],
    capabilities: ['order_status', 'order_cancel', 'order_return', 'shipping_update'],
    escalationRules: [
      { condition: 'refund_amount > 1000000', action: 'escalate_to_human' },
      { condition: 'customer_sentiment < -0.5', action: 'escalate_to_human' },
    ],
    modelConfig: { model: 'gpt-4o-mini', temperature: 0.3, maxTokens: 1024 },
  },
  {
    id: 'knowledge-agent',
    name: 'Knowledge Base Agent',
    description: 'Answers questions from company documentation, policies, and FAQs',
    systemPrompt: `You are a knowledge base specialist. Answer questions using RAG-retrieved documents.
Always cite sources. If unsure, say you don't know.`,
    tools: ['search_knowledge_base', 'get_document'],
    capabilities: ['faq', 'policy_inquiry', 'documentation', 'how_to'],
    escalationRules: [
      { condition: 'no_relevant_documents', action: 'transfer_to_human' },
    ],
    modelConfig: { model: 'gpt-4o', temperature: 0.2, maxTokens: 2048 },
  },
  {
    id: 'sales-agent',
    name: 'Sales Assistant Agent',
    description: 'Helps with product recommendations, pricing, and promotions',
    systemPrompt: `You are a sales assistant. Help customers find the right products.
Recommend based on their needs. Mention active promotions when relevant.`,
    tools: ['search_products', 'get_promotions', 'calculate_quote', 'create_cart'],
    capabilities: ['product_recommendation', 'pricing', 'promotion', 'quote'],
    escalationRules: [
      { condition: 'deal_value > 50000000', action: 'transfer_to_sales_rep' },
    ],
    modelConfig: { model: 'gpt-4o', temperature: 0.5, maxTokens: 1536 },
  },
];

3. Supervisor模式-智慧路由


class SupervisorAgent {
  private routerPrompt = `
You are a routing supervisor. Analyze the user's message and determine which specialist agent should handle it.

Available agents:
{{#each agents}}
- **{{this.name}}** ({{this.id}}): {{this.description}}
  Capabilities: {{this.capabilities}}
{{/each}}

Rules:
1. Route to the most relevant agent based on user intent
2. If unclear, ask a clarifying question (respond with agent_id: "clarify")
3. If it's a greeting or general chat, handle it yourself (agent_id: "self")
4. You can route to multiple agents if the query spans multiple domains

Respond with JSON:
{
  "reasoning": "brief explanation",
  "agent_id": "selected-agent-id",
  "confidence": 0.0-1.0
}`;

  async route(
    message: string,
    conversationHistory: Message[],
    agents: AgentDefinition[],
  ): Promise<RoutingDecision> {
    const response = await this.llm.chat({
      messages: [
        { role: 'system', content: this.buildRouterPrompt(agents) },
        ...conversationHistory.slice(-3), // Last 3 messages for context
        { role: 'user', content: message },
      ],
      response_format: { type: 'json_object' },
      model: 'gpt-4o-mini', // Fast, cheap for routing
      temperature: 0,
    });

    const decision = JSON.parse(response.content);

    // Low confidence → use classifier fallback
    if (decision.confidence < 0.6) {
      return this.classifierFallback(message, agents);
    }

    return decision;
  }

  private async classifierFallback(
    message: string,
    agents: AgentDefinition[],
  ): Promise<RoutingDecision> {
    // Embedding-based classification
    const messageEmbedding = await this.embedder.embed(message);

    let bestMatch = { agentId: 'self', score: 0 };
    for (const agent of agents) {
      const agentEmbedding = await this.embedder.embed(
        `${agent.description} ${agent.capabilities.join(' ')}`,
      );
      const similarity = this.cosineSimilarity(messageEmbedding, agentEmbedding);
      if (similarity > bestMatch.score) {
        bestMatch = { agentId: agent.id, score: similarity };
      }
    }

    return {
      reasoning: 'Fallback: embedding similarity routing',
      agent_id: bestMatch.agentId,
      confidence: bestMatch.score,
    };
  }
}

4. 代理切換協議


interface HandoffContext {
  fromAgentId: string;
  toAgentId: string;
  reason: string;
  summary: string;         // Summary of what happened so far
  pendingActions: string[]; // What the new agent should do
  metadata: Record<string, unknown>; // Additional context
}

class AgentHandoffManager {
  async handoff(context: HandoffContext, conversation: Conversation): Promise<void> {
    // 1. Generate summary from departing agent
    const summary = await this.generateHandoffSummary(
      context.fromAgentId,
      conversation,
    );

    // 2. Create handoff message (visible to new agent, not to user)
    const handoffMessage: Message = {
      role: 'system',
      content: `
[AGENT HANDOFF]
Previous agent: ${context.fromAgentId}
Reason: ${context.reason}
Summary: ${summary}
Pending actions: ${context.pendingActions.join(', ')}
Continue helping the user from where the previous agent left off.
Do NOT repeat information already provided.`,
      metadata: { isHandoff: true, fromAgent: context.fromAgentId },
    };

    // 3. Update conversation state
    await this.conversationService.addSystemMessage(
      conversation.id,
      handoffMessage,
    );
    await this.conversationService.setActiveAgent(
      conversation.id,
      context.toAgentId,
    );

    // 4. Emit event
    await this.eventBus.publish('agent.handoff', {
      conversationId: conversation.id,
      from: context.fromAgentId,
      to: context.toAgentId,
      reason: context.reason,
    });
  }

  private async generateHandoffSummary(
    agentId: string,
    conversation: Conversation,
  ): Promise<string> {
    const recentMessages = conversation.messages.slice(-10);

    const response = await this.llm.chat({
      messages: [
        {
          role: 'system',
          content: 'Summarize this conversation concisely for handoff to another agent. Include: user intent, actions taken, pending issues.',
        },
        ...recentMessages,
      ],
      model: 'gpt-4o-mini',
      maxTokens: 256,
    });

    return response.content;
  }
}

5. Agent Orchestrator-將它們放在一起


class AgentOrchestrator {
  constructor(
    private supervisor: SupervisorAgent,
    private agentRegistry: Map<string, AgentDefinition>,
    private handoffManager: AgentHandoffManager,
    private toolExecutor: ToolChainExecutor,
  ) {}

  async processMessage(
    conversationId: string,
    message: string,
  ): Promise<AsyncIterable<StreamChunk>> {
    const conversation = await this.conversationService.get(conversationId);

    // 1. Check if there's an active specialist agent
    let activeAgentId = conversation.activeAgentId;

    if (!activeAgentId) {
      // 2. Route via supervisor
      const routing = await this.supervisor.route(
        message,
        conversation.messages,
        Array.from(this.agentRegistry.values()),
      );

      if (routing.agent_id === 'self') {
        // Supervisor handles directly (greetings, general chat)
        return this.streamSupervisorResponse(conversation, message);
      }

      if (routing.agent_id === 'clarify') {
        return this.streamClarificationQuestion(conversation, message);
      }

      activeAgentId = routing.agent_id;
      await this.conversationService.setActiveAgent(conversationId, activeAgentId);
    }

    // 3. Execute with specialist agent
    const agent = this.agentRegistry.get(activeAgentId)!;
    const tools = this.toolRegistry.getToolsForAgent(agent);

    // 4. Build messages with agent's system prompt
    const messages: LLMMessage[] = [
      { role: 'system', content: agent.systemPrompt },
      ...conversation.messages.slice(-20), // Recent context
      { role: 'user', content: message },
    ];

    // 5. Run tool chain
    const result = await this.toolExecutor.executeChain(messages, tools, {
      tenantId: conversation.tenantId,
      userId: conversation.userId,
      userPermissions: conversation.userPermissions,
    });

    // 6. Check for escalation
    if (this.shouldEscalate(result, agent)) {
      return this.handleEscalation(conversation, agent, result);
    }

    // 7. Check for handoff
    if (this.shouldHandoff(result, message)) {
      const newRouting = await this.supervisor.route(message, conversation.messages,
        Array.from(this.agentRegistry.values()));
      await this.handoffManager.handoff({
        fromAgentId: activeAgentId,
        toAgentId: newRouting.agent_id,
        reason: 'Topic changed',
        summary: '',
        pendingActions: [],
        metadata: {},
      }, conversation);

      return this.processMessage(conversationId, message); // Re-process with new agent
    }

    return this.streamResponse(result.finalResponse!);
  }

  private shouldHandoff(result: ChainResult, message: string): boolean {
    // Detect topic change patterns
    const topicChangeKeywords = [
      'khác', 'chuyển sang', 'câu hỏi khác',
      'another question', 'different topic',
    ];
    return topicChangeKeywords.some(kw =>
      message.toLowerCase().includes(kw),
    );
  }
}

6. Agent之間共享內存


class AgentSharedMemory {
  // Agents can read/write to shared conversation context
  async getSharedContext(conversationId: string): Promise<SharedContext> {
    const data = await this.redis.hgetall(`shared:${conversationId}`);
    return {
      customerInfo: data.customerInfo ? JSON.parse(data.customerInfo) : null,
      identifiedIntents: data.intents ? JSON.parse(data.intents) : [],
      collectedData: data.collected ? JSON.parse(data.collected) : {},
      agentNotes: data.notes ? JSON.parse(data.notes) : [],
    };
  }

  async updateSharedContext(
    conversationId: string,
    agentId: string,
    updates: Partial<SharedContext>,
  ): Promise<void> {
    const pipeline = this.redis.pipeline();

    if (updates.customerInfo) {
      pipeline.hset(`shared:${conversationId}`, 'customerInfo',
        JSON.stringify(updates.customerInfo));
    }

    if (updates.collectedData) {
      // Merge, don't overwrite
      const existing = await this.redis.hget(`shared:${conversationId}`, 'collected');
      const merged = { ...JSON.parse(existing ?? '{}'), ...updates.collectedData };
      pipeline.hset(`shared:${conversationId}`, 'collected', JSON.stringify(merged));
    }

    if (updates.agentNotes?.length) {
      const note = {
        agentId,
        notes: updates.agentNotes,
        timestamp: Date.now(),
      };
      pipeline.rpush(`shared:${conversationId}:notes`, JSON.stringify(note));
    }

    pipeline.expire(`shared:${conversationId}`, 86400); // 24h TTL
    await pipeline.exec();
  }
}

第 9 課總結

  • 多Agent:將聊天機器人分為專門的代理,每個代理都有自己的工具+提示
  • 主管模式:基於LLM的路由器決定處理哪個代理,回退到嵌入相似性
  • 切換協定:透過摘要+系統訊息在代理之間傳輸上下文
  • 共享記憶體:基於Redis的共享上下文(客戶資訊、收集的資料、代理註釋)
  • 主題變化偵測:自動偵測使用者何時改變主題 → 切換代理

下一篇: 規劃與反思-ReAct 模式、自我反思循環、複雜任務的規劃-執行-審查循環。