第四章 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)

相关推荐
文青的青7 分钟前
不写一行代码,用 WorkBuddy 搭建并上线个人静态博客
人工智能
IT_陈寒10 分钟前
Redis连接池泄漏那次,我差点把生产环境搞崩了
前端·人工智能·后端
Dawson Zhu11 分钟前
从空间 Scaling 到时间 Scaling
人工智能·语言模型·架构·aigc·agi
灿飞屋22 分钟前
Kotlin与Java虚拟机三:控制流
android·java·开发语言·后端·python·kotlin·安卓
阿里云大数据AI技术1 小时前
本体如何落地?阿里云 Milvus 把企业文档变成知识图谱和 Wiki
人工智能
NE_STOP1 小时前
Spring AI Alibaba - MCP入门
java
nice_lcj5201 小时前
【从零理解 Spring AI · 07】对话记忆:让模型记住上一句说了什么
java·spring ai·ai应用开发·chatmemory·对话记忆
QXWZ_IA1 小时前
公安视频专网如何与定位数据融合?架构与实施要点
人工智能
小林ixn1 小时前
GraphRAG 实战:用 Neo4j 知识图谱,让 RAG 从“找货“进化到“梳理关系“
人工智能·知识图谱·neo4j
敖老师1 小时前
Dify 连局域网 Ollama 的两个坑:docker0 网段撞车,和请求里少掉的那个参数
人工智能