# 01 - Coordinator Agent (主协调器) 设计详解 ## 1. 核心职责 Coordinator 是唯一直接面对用户的 Agent。它的职责是: 1. **理解用户意图** — 无需额外的 IntentClassifier,LLM 自行判断 2. **编排专家 Agent** — 决定调用哪些专家、传什么参数 3. **综合信息** — 将多个专家的结果整合为自然的对话回复 4. **直接回答简单问题** — 闲聊、简单确认等不需要启动专家 Agent 5. **管理对话节奏** — 控制提问频率、推进对话进程 6. **维护咨询状态** — 在回复中自报当前阶段和收集到的信息 ## 2. 模型与参数 ```typescript { model: 'claude-sonnet-4-20250514', max_tokens: 4096, // Prompt Caching: system prompt 使用 cache_control system: [ { type: 'text', text: coordinatorPrompt, cache_control: { type: 'ephemeral' } } ], } ``` ## 3. 可用工具(Tools) Coordinator 拥有两类工具: ### 3.1 Agent 调用工具(核心) | 工具名 | 描述 | 输入参数 | 返回值 | |--------|------|----------|--------| | `invoke_policy_expert` | 查询移民政策详情 | `{query: string, category?: string}` | 政策解读文本 | | `invoke_assessment_expert` | 评估用户移民资格 | `{userInfo: object, targetCategories?: string[]}` | 评估报告 JSON | | `invoke_strategist` | 获取对话策略建议 | `{conversationSummary: string, currentStage: string}` | 策略建议文本 | | `invoke_objection_handler` | 处理用户异议 | `{objection: string, userContext: string}` | 回应方案文本 | | `invoke_case_analyst` | 查找类似案例 | `{userProfile: object, targetCategory: string}` | 案例分析文本 | | `invoke_memory_manager` | 管理用户记忆 | `{action: 'load'\|'save'\|'extract', ...}` | 记忆数据 | ### 3.2 直接工具(不经过 Agent) | 工具名 | 描述 | 来源 | |--------|------|------| | `generate_payment` | 生成支付链接 | payment-service | | `get_current_datetime` | 获取当前时间 | 本地 | | `web_search` | 搜索网页 | Google API | | `get_exchange_rate` | 获取汇率 | API / 缓存 | | `fetch_immigration_news` | 获取移民新闻 | API / 缓存 | ## 4. Agent Loop 控制流 ```typescript // agent-loop.ts - 核心控制流伪代码 interface AgentLoopParams { messages: Message[]; // 对话历史 systemPrompt: string[]; // 系统提示(含缓存控制) tools: ToolDefinition[]; // 所有可用工具 maxTurns: number; // 最大递归轮次 (default: 15) maxBudgetUsd: number; // 单次对话最大成本 (default: 0.50) conversationId: string; userId: string; abortSignal?: AbortSignal; // 用户中断信号 } async function* agentLoop(params: AgentLoopParams): AsyncGenerator { let turnCount = 0; let totalCost = 0; // === Guard: 超限检查 === if (turnCount >= params.maxTurns) { yield { type: 'error', code: 'MAX_TURNS_REACHED' }; return; } if (totalCost >= params.maxBudgetUsd) { yield { type: 'error', code: 'BUDGET_EXCEEDED' }; return; } if (params.abortSignal?.aborted) { yield { type: 'error', code: 'USER_ABORTED' }; return; } // === Step 1: 动态上下文注入 === const enrichedMessages = await contextInjector.inject({ messages: params.messages, userId: params.userId, conversationId: params.conversationId, }); // === Step 2: 上下文压缩(如果接近 token 上限)=== const compactedMessages = await autoCompactIfNeeded(enrichedMessages); // === Step 3: 调用 Claude API(流式)=== const stream = anthropic.messages.stream({ model: 'claude-sonnet-4-20250514', system: params.systemPrompt, messages: compactedMessages, tools: params.tools, max_tokens: 4096, }); // === Step 4: 流式收集响应 === const assistantBlocks: ContentBlock[] = []; for await (const event of stream) { // 流式传输文本给前端 if (event.type === 'content_block_delta' && event.delta.type === 'text_delta') { yield { type: 'text', content: event.delta.text }; } // 收集所有 content blocks collectBlocks(event, assistantBlocks); } // 记录 token 使用 const usage = stream.finalMessage().usage; totalCost += calculateCost(usage); yield { type: 'usage', usage, cost: totalCost }; // === Step 5: 提取 tool_use blocks === const toolUses = assistantBlocks.filter(b => b.type === 'tool_use'); // === Step 6: 无工具调用 → 自然结束 === if (toolUses.length === 0) { // 从回复中提取状态更新 const stateUpdate = extractConsultingState(assistantBlocks); if (stateUpdate) { yield { type: 'state_update', state: stateUpdate }; } return; // 递归终止 } // === Step 7: 执行工具(并发队列)=== const toolResults: ToolResult[] = []; for (const toolUse of toolUses) { // 通知前端 Agent 开始工作 if (toolUse.name.startsWith('invoke_')) { yield { type: 'agent_start', agentName: toolUse.name.replace('invoke_', '') }; } } // 通过 ToolExecutionQueue 并发执行 const results = await toolExecutionQueue.executeAll(toolUses); for (const result of results) { toolResults.push(result); // 通知前端 Agent 完成 if (result.toolName.startsWith('invoke_')) { yield { type: 'agent_complete', agentName: result.toolName.replace('invoke_', '') }; } } // === Step 8: 递归 — 把工具结果喂回 Coordinator === turnCount++; const newMessages = [ ...params.messages, { role: 'assistant', content: assistantBlocks }, ...toolResults.map(r => ({ role: 'user', content: [{ type: 'tool_result', tool_use_id: r.id, content: r.output }] })), ]; yield* agentLoop({ ...params, messages: newMessages, }); } ``` ## 5. 递归终止条件 | 条件 | 触发 | 行为 | |------|------|------| | 无 tool_use | LLM 认为回复完成 | 自然终止,返回文本 | | maxTurns 超限 | turnCount >= 15 | 强制终止,yield error | | maxBudgetUsd 超限 | 累计 API 成本超限 | 强制终止,yield error | | abortSignal | 用户点击停止 | 立即终止 | | API 错误 | Claude API 返回错误 | 尝试 fallback model,否则终止 | ## 6. Coordinator System Prompt 结构 详见 [11-prompt-templates.md](./11-prompt-templates.md),核心结构: ``` # 身份定义 (Identity) 你是 iConsulting 的资深香港移民顾问... # 你的专家团队 (Your Expert Team) 你拥有 6 个专家助手,通过工具调用来获取他们的帮助... - Policy Expert: 调用时机、输入输出格式 - Assessment Expert: ... - ... # 对话策略 (Conversation Strategy) ## 咨询阶段(你自行判断当前处于哪个阶段) - 开场阶段:目标、行为、判断条件 - 需求了解:... - 信息收集:... - 评估推荐:... - 异议处理:... - 转化促成:... # 回复规范 (Response Guidelines) - 语气:专业但亲和 - 长度:根据场景自适应 - 结构:每次回复以推进性问题结尾 - 禁忌:不承诺成功率、不一次问超过2个问题 # 状态报告 (State Reporting) 每次回复结束时附加 标签... # 六大移民类别详解 (Immigration Categories) QMAS/GEP/IANG/TTPS/CIES/TECHTAS 的详细条件... # 业务规则 (Business Rules) 付费评估服务说明、专家对接流程... ``` ## 7. 与 ConversationService 的接口 ```typescript // CoordinatorAgentService 对外暴露的接口 // 与旧的 ClaudeAgentServiceV2 保持相同的 AsyncGenerator 模式 interface CoordinatorAgentService { sendMessage(params: { conversationContext: ConversationContext; userMessage: string; attachments?: Attachment[]; userId: string; conversationId: string; deviceInfo?: DeviceInfo; }): AsyncGenerator; } ``` ConversationService 只需将注入从 `ClaudeAgentServiceV2` 切换到 `CoordinatorAgentService`,内部聚合逻辑不变。 ## 8. 错误处理 ```typescript // Coordinator 的错误处理策略 try { yield* agentLoop(params); } catch (error) { if (error instanceof RateLimitError) { // 等待后重试 await sleep(error.retryAfter); yield* agentLoop(params); } else if (error instanceof ModelOverloadedError) { // 降级到 Haiku yield* agentLoop({ ...params, model: 'claude-haiku' }); } else { // 返回友好错误消息 yield { type: 'text', content: '抱歉,系统遇到了临时问题。请稍后重试,或联系我们的人工顾问。' }; yield { type: 'error', code: 'INTERNAL_ERROR', message: error.message }; } } ```