第四章 05:4.5 Multi-Agent —— 五个专职智能体协作

第四章 05:4.5 Multi-Agent ------ 五个专职智能体协作

课程专题 :《AI Agents 开发实践》

原文链接 :https://s.juejin.cn/ds/TAqu_hbSmd8/

章节 :第四章 4.5 Multi-Agent

作者:Java 开发者转型 AI 全栈


4.5.1 单个 prompt 装下所有职责的问题

假如把「抽取 + 澄清 + 分析 + 风险 + 汇总」全塞进一个 system prompt:

  • 提示膨胀:规则越堆越多,模型顾此失彼
  • 职责混淆:要求它「输出 JSON」的同时又要求「输出 Markdown 报告」,格式打架
  • 无法并行:分析(长)和风险(长)明明互不依赖,却只能串行等
  • 不好改:调整澄清规则要在一篇千行 prompt 里找位置

解法和微服务拆分一个思路:按职责拆成 5 个专职 Agent,每个只干一件事。

4.5.2 流水线全景

复制代码
用户输入
   │
   ▼
① extractAgent    抽取结构化需求(JSON:coreFeature / isComplete / missingFields...)
   │
   ▼
② clarifyAgent    判断是否需要澄清(JSON:needsClarification / questions)
   │
   ├─ 需要 → 短路返回澄清问题(只用了 2 个 Agent)
   │
   ▼ 不需要
③ analysisAgent ┐
   riskAgent    ┘ Promise.all 并行(互不依赖)
   │
   ▼
④ summaryAgent   汇总生成完整 Markdown 报告

4.5.3 代码:5 个 Agent 提示词(节选)

services/chat/src/llm/prompts/requirement.prompts.ts(节选)

typescript 复制代码
// Agent 1:extractAgent --- 从用户输入抽取结构化需求字段(输出纯 JSON)
export const extractPrompt = ChatPromptTemplate.fromMessages([
    [
        'system',
        `你是一个需求分析专家,擅长从用户描述中提取结构化的需求信息。

请从用户输入中提取以下字段,输出**纯 JSON 对象**,不要包含任何 markdown 代码块(不要用 \`\`\`json):

{{
  "requirementType": "功能需求 | 非功能需求 | 约束需求 | 业务需求",
  "coreFeature": "核心功能一句话描述",
  "targetUsers": "目标用户群体描述",
  "businessGoal": "业务目标描述",
  "constraints": ["约束1", "约束2"],
  "priority": "high | medium | low",
  "isComplete": true,
  "missingFields": []
}}

判断 isComplete 的标准:
- 必须知道核心功能是什么(coreFeature 不为空)
- 必须知道目标用户是谁(targetUsers 不为空)
- 必须知道业务目标(businessGoal 不为空)
以上三项任一为空,isComplete = false,并在 missingFields 中列出缺失项。`,
    ],
    new MessagesPlaceholder({ variableName: 'history', optional: true }),
    ['human', '{input}'],
]);

// Agent 2:clarifyAgent --- 宽松原则:提了核心功能就继续,不追问
export const clarifyPrompt = ChatPromptTemplate.fromMessages([
    [
        'system',
        `你是一个需求澄清专家。......

判断标准(必须同时满足以下条件才要求澄清):
- 用户描述过于简略(少于 15 字)或完全没有提到核心功能
- 并且缺少关键信息使得无法进行基本的需求分析

**宽松原则**:
- 如果已经提到了核心功能描述,即使缺少目标用户或业务目标,也可以继续分析,不需要澄清
- 如果用户提供了基本的功能描述(如"开发一个XX系统,支持XX功能"),就可以继续分析
- 只在真正缺少核心信息、无法进行分析时才要求澄清`,
    ],
    ['human', '抽取结果:{extractResult}\n\n原始输入:{input}'],
]);

analysis / risk / summary 三个 prompt 结构类似(输出 Markdown,summary 有固定的报告章节结构),完整版见仓库。

⚠️ 踩坑复盘:prompt 里的 JSON 示例要用 {{ }} 转义

ChatPromptTemplate 用 {xxx} 做变量占位。上面 JSON 示例里的 {、} 如果单写,LangChain 会把它们当变量解析,直接报「变量未提供」或渲染错乱。

写法 :prompt 字符串里所有「字面量花括号」都写成 {``{ 和 }}。这是从 mustache 系模板延续下来的约定,Java 的 MessageFormat 用单引号转义、String.format 用 %% 转义,同属一个家族。

4.5.4 代码:5 条小链 + 编排器

services/chat/src/llm/agents/sub-agents.ts

typescript 复制代码
// 每个 Agent 都是一条独立的小链:prompt.pipe(model).pipe(StringOutputParser)
const model = createChatModel();
const parser = new StringOutputParser();

export const extractAgent  = extractPrompt.pipe(model).pipe(parser);
export const clarifyAgent  = clarifyPrompt.pipe(model).pipe(parser);
export const analysisAgent = analysisPrompt.pipe(model).pipe(parser);
export const riskAgent     = riskPrompt.pipe(model).pipe(parser);
export const summaryAgent  = summaryPrompt.pipe(model).pipe(parser);

services/chat/src/llm/agents/orchestrator.service.ts

typescript 复制代码
// Fixed Workflow 编排:顺序确定,不需要模型来决策流程
async orchestrate(input: string): Promise<OrchestratorResult> {
    try {
        const extractResult = await extractAgent.invoke({ input });

        // 澄清判断:信息不足时短路返回澄清问题
        const clarifyRaw = await clarifyAgent.invoke({ extractResult, input });
        let clarificationQuestions: string[] = [];
        try {
            const clarify = parseJsonLoose(clarifyRaw);
            if (clarify?.needsClarification && Array.isArray(clarify.questions)) {
                clarificationQuestions = clarify.questions;
            }
        } catch {
            // 澄清结果解析失败时不阻塞主流程,按"无需澄清"继续
        }

        if (clarificationQuestions.length > 0) {
            return {
                mode: 'fixed_workflow',
                status: 'need_clarification',
                clarificationQuestions,
                usedAgents: ['extractAgent', 'clarifyAgent'],
                fallback: 'ask_user',
            };
        }

        // analysis 与 risk 互不依赖 → 并行
        const [analysisResult, riskResult] = await Promise.all([
            analysisAgent.invoke({ extractResult, input }),
            riskAgent.invoke({ extractResult, input }),
        ]);

        const report = await summaryAgent.invoke({
            input,
            extractResult,
            analysisResult,
            riskResult,
            retrievedContext: '无相关参考文档',  // 4.4 向量检索的预留位
        });

        return {
            mode: 'fixed_workflow',
            clarificationQuestions: [],
            usedAgents: ['extractAgent', 'clarifyAgent', 'analysisAgent', 'riskAgent', 'summaryAgent'],
            fallback: null,
            steps: { extract: extractResult, analysis: analysisResult, risk: riskResult },
            report,
        };
    } catch (error) {
        return {
            mode: 'fixed_workflow',
            clarificationQuestions: [],
            usedAgents: ['extractAgent'],
            fallback: 'manual_review',
            report: '分析流程失败,请转人工复核。',
            error: String(error),
        };
    }
}

// LLM 偶尔会带 ```json 代码块包裹,做一次容错解析
function parseJsonLoose(raw: string): any {
    const cleaned = raw
        .trim()
        .replace(/^```(?:json)?/i, '')
        .replace(/```$/i, '')
        .trim();
    return JSON.parse(cleaned);
}

编排层的三个工程细节:

  1. 短路:澄清命中就 return,后面三个 Agent 一个都不跑(省钱省时);
  2. 并行 :Promise.all 同时发 analysis / risk(总耗时 ≈ 较慢者,而非两者之和);
  3. 兜底 :整体 catch 返回 fallback: 'manual_review',AI 流程失败不能把 API 打挂。

4.5.5 验收

cmd 复制代码
:: a2. 信息极简 ------ 应触发澄清短路
curl.exe -s -X POST http://localhost:4001/api/agents/orchestrate -H "Content-Type: application/json" -d "{\"input\":\"帮我做一个工具\"}"

:: b. 信息完整 ------ 应走完全流程
curl.exe -s -X POST http://localhost:4001/api/agents/orchestrate -H "Content-Type: application/json" -d "{\"input\":\"帮我做一个面向需求分析师的积分系统,支持按评审贡献发放积分,目标是提升需求评审参与度\"}"

实际结果:

  • a2:status: "need_clarification",usedAgents 只有 2 个,fallback: "ask_user",返回 2 个具体澄清问题;
  • b:usedAgents 5 个全齐,report 是带「一、需求概述 / 二、多维度分析 / 三、风险评估」章节的完整 Markdown。

⚠️ 踩坑复盘 ×2

坑一:宽松原则的边界行为

用「我想做一个积分系统」测 a 用例时,预期触发澄清,实际却走了全流程。原因:宽松原则的设计是「提了核心功能就继续」------「积分系统」算提了核心功能,clarifyAgent 放行。改用更模糊的「帮我做一个工具」(连做啥都没说)才正确触发短路。

认知修正 :extract 的 isComplete(三项全空才算不完整)和 clarify 的宽松原则是两道独立判断 ,不要混为一谈。「信息不全」不等于「必须追问」------产品的取舍是少一轮追问,优于多一轮打扰。

坑二:报告被截断(finish_reason: length)

b 用例的 report 结尾偶尔戛然而止。排查:response_metadata.finish_reason: "length"------思考模型默认输出上限到了。本章不改(可通过 max_tokens 参数调大),但要知道:「输出不完整」先查 finish_reason,再怀疑代码。

Java 对照

java 复制代码
// Fixed Workflow ≈ 编排服务 + CompletableFuture 并行
CompletableFuture<Analysis> analysisFuture =
    CompletableFuture.supplyAsync(() -> analysisAgent.run(input));
CompletableFuture<Risk> riskFuture =
    CompletableFuture.supplyAsync(() -> riskAgent.run(input));
CompletableFuture.allOf(analysisFuture, riskFuture).join();  // ≈ Promise.all

// 澄清短路 ≈ 提前 return;fallback ≈ 降级策略(人工复核)
// 每个Agent ≈ 一个独立 @Service,prompt.pipe(model).pipe(parser) ≈ 三步封装的 Pipeline

判断标准

什么时候拆多 Agent? 单 prompt 出现「既要求 A 格式又要求 B 格式」的矛盾、或职责块之间明确可以并行时。

用什么编排策略? 流程确定 → Fixed Workflow(代码写死顺序);流程需要模型临场决策 → 才轮到第八九章的 LangGraph。别为简单任务引入图编排。


回到索引 :第四章博客索引 | 下一节:06 4.6 统一入口:analyze() 能力收束(./06-4.6 统一入口:analyze() 能力收束.md)

相关推荐
A小码哥1 小时前
大模型成本怎么降,5 个动作先做前两个
java·前端·spring
鬼手点金1 小时前
构建有效 Agent 的核心原则
java·人工智能·git·学习·自动化
小星星_ios1 小时前
从 3Blue1Brown 到 NumPy:手写一个神经网络训练 MNIST
人工智能·python·神经网络
神魔皆以血嗜1 小时前
Java 实现简单状态机(订单状态流转)
java·后端
老牛日常随笔1 小时前
Google把Android测试搬进AI对话:ARTEMIS如何用自然语言操作你的手机
android·人工智能·智能手机
信可维1 小时前
AI 读 Word 表格总读错?先查合并单元格和表头这 3 处
开发语言·人工智能·c#·word·软件工程
Csvn1 小时前
A/B 实验:85% 比 78% 好,居然不算赢?(R03)
人工智能·aigc·agent
随性而行3601 小时前
企业微信 API 如何实现业务消息自动通知?接口应用场景解析
java·人工智能·后端·微信·机器人·企业微信
FPGA信号处理1 小时前
【雷达原理与系统】第四节课:雷达波形与脉冲压缩
人工智能·计算机视觉·目标跟踪