第四章 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);
}
编排层的三个工程细节:
- 短路:澄清命中就 return,后面三个 Agent 一个都不跑(省钱省时);
- 并行 :
Promise.all同时发 analysis / risk(总耗时 ≈ 较慢者,而非两者之和); - 兜底 :整体 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:
usedAgents5 个全齐,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)