Appearance
课 2 · Multi-Agent 实现
本课目标
用 Vercel AI SDK 实现 Boss-Worker 模式,完成任务拆解 → 执行 → 汇总的完整流程。课后你会拿到一个可运行的 Multi-Agent 系统。
这一课先掌握“用显式函数把多角色流程编排起来”就够了,不要求一上来把 Multi-Agent 做成完全自主规划、自主讨论的复杂系统。
上一课分析了三种设计模式。这一课选编排型(Boss-Worker)来实现——它是最常用、最实用的模式。
为什么这里不是单 Agent 多工具就够了
如果只是“查一个资料再回答”,单 Agent 多工具通常已经够用。但这节课要演示的是另一类任务:不同步骤的职责差异很大,而且你希望研究、写作、审校彼此隔离。
把研究、写作、审校都塞进一个 Agent 里,往往会遇到三个问题:
- 角色要求互相干扰,一个 Prompt 里既要“只整理事实”又要“负责润色表达”
- 调试时不容易看出问题出在哪一段
- 后续想替换某个环节时,边界不清楚
所以这里用 Multi-Agent,不是为了显得更复杂,而是为了把角色分工讲清楚。
架构设计
我们要实现一个"研究助手":用户给一个主题,系统自动完成 研究 → 撰写 → 审校 三步。
用户: "帮我研究一下 RAG 的最佳实践"
↓
Boss Agent(规划任务)
├── Researcher(搜索资料、整理要点)
├── Writer(基于资料撰写内容)
└── Reviewer(检查质量、提修改建议)
↓
Boss Agent(汇总输出)
↓
最终报告三个角色:
- Researcher:负责信息收集,返回结构化要点
- Writer:基于研究结果撰写内容
- Reviewer:审查质量,决定是否通过
这三个角色是一个可替换的分工模板,不是唯一标准答案。课程想让你学会的是“把职责拆开”,不是要求所有 Multi-Agent 系统都必须叫这三个名字。
定义 Agent 角色
每个 Agent 是一个独立的函数,有自己的 System Prompt 和输出 Schema。这一版先用代码显式编排流程,不给每个角色单独挂工具。
这也是课程推荐的起步路线:先把角色边界和数据流写死,确保流程稳定可调试;等你确认这套协作关系真的有效,再考虑把更多调度权交给模型。
Researcher Agent
typescript
import { generateText, Output, tool } from 'ai'
import { createOpenAI } from '@ai-sdk/openai'
import { z } from 'zod'
const model = createOpenAI({
apiKey: process.env.CHAT_API_KEY,
baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
}).chat('qwen3.6-plus')
const ResearchResult = z.object({
keyPoints: z.array(z.string()).describe('核心要点,每条一句话'),
sources: z.array(z.string()).describe('信息来源'),
summary: z.string().describe('总结,100字以内'),
})
async function runResearcher(topic: string) {
const { output } = await generateText({
model,
output: Output.object({ schema: ResearchResult }),
system: `你是研究员。职责:
- 围绕给定主题整理关键信息
- 只返回事实和要点,不做创作
- 标注信息来源`,
prompt: `请研究以下主题,整理核心要点:${topic}`,
})
return output
}注意 System Prompt 的写法:明确告诉 Agent 做什么、不做什么。
Writer Agent
typescript
const DraftResult = z.object({
title: z.string(),
content: z.string().describe('正文内容,500-1000字'),
wordCount: z.number(),
})
async function runWriter(
topic: string,
research: z.infer<typeof ResearchResult>
) {
const { output } = await generateText({
model,
output: Output.object({ schema: DraftResult }),
system: `你是技术写手。职责:
- 基于研究员提供的资料撰写内容
- 面向前端工程师,技术准确、表达清晰
- 不自行补充未经验证的信息`,
prompt: `主题:${topic}
研究资料:
${research.keyPoints.map((p, i) => `${i + 1}. ${p}`).join('\n')}
总结:${research.summary}
请基于以上资料撰写一篇技术文章。`,
})
return output
}Writer 的输入是 Researcher 的输出。Agent 之间传递结构化数据(Zod Schema),不是自由文本。
Reviewer Agent
typescript
const ReviewResult = z.object({
approved: z.boolean().describe('是否通过审核'),
score: z.number().min(1).max(10).describe('质量评分'),
issues: z.array(z.string()).describe('发现的问题'),
suggestions: z.array(z.string()).describe('改进建议'),
})
async function runReviewer(draft: z.infer<typeof DraftResult>) {
const { output } = await generateText({
model,
output: Output.object({ schema: ReviewResult }),
system: `你是技术审校。职责:
- 检查内容的准确性、完整性、可读性
- 评分 1-10,7 分以上通过
- 指出具体问题和改进建议`,
prompt: `请审查以下文章:
标题:${draft.title}
${draft.content}`,
})
return output
}Reviewer 决定是否通过。不通过则打回修改,这是质量保障的关键。
这里的审校回路是一个“质量兜底手段”:它让输出更稳,但不是 Multi-Agent 的最低门槛。即使你先只实现 Researcher → Writer 两段,只要角色分工清楚,依然是在做有效的 Multi-Agent 编排。
Boss Agent:编排整个流程
Boss Agent 负责调度三个 Worker,根据 Reviewer 的反馈决定是否需要修改。
typescript
async function runBossAgent(userRequest: string) {
console.log(`\n📋 Boss Agent 收到任务: ${userRequest}\n`)
// 阶段 1:研究
console.log('🔍 阶段 1/3:调用 Researcher...')
const research = await runResearcher(userRequest)
console.log(` 找到 ${research.keyPoints.length} 个要点`)
// 阶段 2:撰写
console.log('✍️ 阶段 2/3:调用 Writer...')
let draft = await runWriter(userRequest, research)
console.log(` 生成初稿:${draft.title}(${draft.wordCount} 字)`)
// 阶段 3:审校(最多修改 3 次)
const MAX_REVISIONS = 3
for (let i = 0; i < MAX_REVISIONS; i++) {
console.log(`🔎 阶段 3/3:调用 Reviewer(第 ${i + 1} 轮)...`)
const review = await runReviewer(draft)
console.log(` 评分: ${review.score}/10`)
if (review.approved) {
console.log(' ✅ 审核通过!')
return { draft, review, research, revisions: i }
}
console.log(` ❌ 未通过,问题:${review.issues.join('、')}`)
console.log(' 📝 打回修改...')
// 基于反馈修改
draft = await runWriter(
`${userRequest}(请根据以下建议修改:${review.suggestions.join(';')})`,
research
)
}
console.log('⚠️ 达到最大修改轮次')
return { draft, research, revisions: MAX_REVISIONS }
}流程中的设计要点:
- 结构化传递:每个 Agent 输出都是 Zod Schema,下游 Agent 可以直接使用
- 反馈循环:Reviewer 不通过,Writer 基于建议修改,最多 3 轮
- 日志可观测:每步打印状态,方便调试
- 终止保障:限制最大修改轮次,防止无限循环
如果你是第一次做 Multi-Agent,建议先把第 1、3、4 点做好,再决定要不要加完整的审校反馈循环。
显示执行过程
Multi-Agent 系统的调试比单 Agent 复杂,清晰的日志至关重要。
typescript
function logAgentStep(agent: string, action: string, detail?: string) {
const timestamp = new Date().toLocaleTimeString()
const icons: Record<string, string> = {
Boss: '👔',
Researcher: '🔍',
Writer: '✍️',
Reviewer: '🔎',
}
const icon = icons[agent] || '🤖'
console.log(`[${timestamp}] ${icon} ${agent}: ${action}`)
if (detail) console.log(` ${detail}`)
}生产环境中通常用结构化日志(JSON),配合 tracing 系统追踪每个 Agent 的执行耗时和 token 消耗。
用 generateText 实现简化版
上面的实现把每个 Agent 写成独立函数。也可以用 Vercel AI SDK 的 tool 机制,让 Boss Agent 通过工具调用来触发子 Agent:
typescript
const result = await generateText({
model,
system: `你是任务协调者。根据用户需求,按顺序调用以下工具完成任务:
1. research — 研究主题
2. write — 撰写内容
3. review — 审查质量
如果审查未通过,基于建议重新调用 write。`,
tools: {
research: tool({
description: '调用研究员搜索和整理资料',
inputSchema: z.object({ topic: z.string() }),
execute: async ({ topic }) => runResearcher(topic),
}),
write: tool({
description: '调用写手撰写内容',
inputSchema: z.object({
topic: z.string(),
keyPoints: z.array(z.string()),
}),
execute: async ({ topic, keyPoints }) =>
runWriter(topic, { keyPoints, sources: [], summary: '' }),
}),
review: tool({
description: '调用审校检查质量',
inputSchema: z.object({
title: z.string(),
content: z.string(),
}),
execute: async ({ title, content }) =>
runReviewer({ title, content, wordCount: content.length }),
}),
},
stopWhen: stepCountIs(10),
prompt: userRequest,
})两种方式的对比:
| 独立函数 | generateText + tools | |
|---|---|---|
| 控制粒度 | 精确,每步可控 | 依赖模型决策 |
| 灵活度 | 代码控制流程 | 模型自动选择 |
| 可预测性 | 高 | 中 |
| 适用场景 | 流程固定 | 流程需要灵活调整 |
推荐起步方式:先用独立函数实现(可预测、好调试),等流程稳定后再考虑是否要让模型自主编排。 这也是这节课的默认路线。
并入基础项目
本模块能力会在基础项目 basic/project/ 中收束,当前课内 Demo 见下方运行方式。
为什么这一课属于 basic/project: 前面的版本已经把聊天、Prompt、RAG、检索优化、Agent、记忆与规划逐步接进主线;到了 basic/project,主线才真正从“单 Agent 多工具”升级为“多角色协作”。
这一课给主线新增了什么能力:
| 模块 | 能力 |
|---|---|
| 模块 1-6 | 聊天、Prompt、RAG、检索优化、Agent、记忆与规划 |
| 模块 7 | Boss 负责任务编排,Researcher / Writer / Reviewer 分工执行,多轮审校后汇总最终结果 |
当前 basic/project 的落地口径:
- 默认入口切换为 Boss-Worker 多角色流程
- 已支持研究、撰写、审校三角色编排
- 已支持审校反馈驱动的修改循环
- 还没有把更多协作模式写成主线默认能力
本课产物
- ✅ 实现 Boss-Worker 编排模式
- ✅ 三个角色:Researcher → Writer → Reviewer
- ✅ Agent 之间传递结构化数据(Zod Schema)
- ✅ 反馈循环:审校不通过则打回修改
- ✅ 终止条件:最大修改轮次
完整代码在 basic/examples/07-multi-agent/02-implementation/index.ts。
试试看
bash
cd daqi-ai-agent
pnpm exec tsx basic/examples/07-multi-agent/02-implementation/index.ts- 输入一个技术主题,观察 Researcher → Writer → Reviewer 三个角色如何分工协作
- 故意输入一个范围很大或要求很模糊的主题,观察 Reviewer 是否会提出修改意见,以及修改循环是否生效
- 在现有基础上新增一个 Sub-Agent 或调整某个角色的 system prompt,接入协作流程并观察结果变化
面试追问
Q:Boss-Worker 模式怎么实现?
Boss Agent 负责任务拆解和调度,Worker Agent 负责执行。实现上,每个 Worker 是一个独立函数,有自己的 System Prompt 和输出 Schema。Boss 按顺序调用 Worker,根据结果决定下一步。关键设计:用 Zod Schema 约束 Agent 之间传递的数据格式,设置最大轮次防止循环。
Q:Agent 之间怎么传递信息?
用结构化数据,不用自由文本。每个 Agent 的输出用 generateText + Output.object() + Zod Schema 约束格式,下游 Agent 直接使用上游的输出字段。这样做的好处:格式可预测、不依赖模型的表述方式、出错容易定位。
Q:怎么防止 Multi-Agent 系统无限循环?
三个手段:1. 设最大轮次(如审校最多 3 轮)。2. 设超时时间。3. 自然终止条件(如审校评分 >= 7 通过)。至少要有前两个硬限制,第三个是设计上的终止条件。