Agent 设计模式:不要让一个循环解决所有问题
# Agent 设计模式:不要让一个循环解决所有问题
认识常见 Agent 模式的职责、代价和适用场景,能够根据任务结构选择最简单可控的方案。
# 先记住一句话
Agent 模式不是越复杂越先进;先识别任务中的不确定性,再用最少的模型决策解决它,其余步骤尽量由确定性代码控制。
# 六种常用模式
| 模式 | 核心做法 | 适合场景 | 主要风险 |
|---|---|---|---|
| Router | 判断请求类型并路由到专用流程 | 多意图入口、客服分类 | 误分类使后续全错 |
| ReAct | 决策、行动、观察交替循环 | 搜索、排障、研究 | 循环和成本失控 |
| Plan-and-Execute | 先形成计划,再逐步执行和修订 | 长任务、依赖关系明显 | 计划过早失效 |
| Evaluator-Optimizer | 生成后用标准评审并迭代 | 文案、代码、结构化报告 | 评审器偏差、自我循环 |
| Orchestrator-Workers | 主控拆分,多个 worker 并行处理 | 可分解研究和批处理 | 合并冲突、上下文成本 |
| Handoff | 当前 Agent 把会话控制交给专家 | 多角色客服、专业分工 | 责任边界不清 |
下面把说明和框架无关的 TypeScript 风格伪代码放在一起,对照观察六种模式在控制流上的差异。
# Router:先分流,再执行
路由输出应是有限枚举,例如 search_notes | mock_interview | study_plan | unsupported。路由失败要有兜底,而不是让模型生成任意流程名。
控制流: 输入 → 分类 → 固定分支
type Route = 'search' | 'interview' | 'study_plan'
async function run(input: string) {
const route = await model.generateObject<Route>({
prompt: `判断请求类型:${input}`,
schema: ['search', 'interview', 'study_plan']
})
switch (route) {
case 'search':
return searchNotes(input)
case 'interview':
return startInterview(input)
case 'study_plan':
return createStudyPlan(input)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# ReAct:让观察结果改变下一步
ReAct 的价值不在于展示隐藏推理,而在于运行层面的循环:选择动作、执行工具、读取 observation、重新判断。生产系统只需记录可审计的决策摘要和工具轨迹,不需要暴露模型的内部思维过程。
控制流: 决策 → 工具 → 观察 → 再决策
async function runReact(goal: string) {
const messages = [{ role: 'user', content: goal }]
for (let step = 0; step < MAX_STEPS; step++) {
const decision = await model.decide({
messages,
tools: toolRegistry.schemas
})
if (decision.type === 'final') {
return verifyAnswer(decision.answer)
}
const observation = await toolRegistry.execute(
decision.toolName,
decision.arguments
)
messages.push(decision.message, {
role: 'tool',
content: serialize(observation)
})
}
throw new Error('超过最大执行步数')
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Plan-and-Execute:计划是可修改状态
计划不是一次生成后永久正确。每完成一步都要根据新信息更新剩余计划,并设置依赖、成功条件和最大修订次数。
控制流: 生成计划 → 执行一步 → 修改计划 → 继续
type PlanStep = {
id: string
task: string
status: 'pending' | 'running' | 'completed' | 'failed'
}
async function runPlanAndExecute(goal: string) {
let plan = await planner.createPlan(goal)
while (!isPlanFinished(plan)) {
const step = getNextExecutableStep(plan)
step.status = 'running'
const observation = await executor.execute(step)
plan = await planner.updatePlan({
goal,
plan,
completedStep: step,
observation
})
}
return buildFinalAnswer(plan)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Evaluator-Optimizer:评审必须有标准
“再优化一下” 没有停止条件。应先定义评分维度,例如准确性、引用完整性、是否覆盖关键点和长度限制;达到阈值停止,未达到才给出具体反馈并重写。
控制流: 生成 → 评审 → 改写 → 再评审
async function runEvaluatorOptimizer(task: string) {
let answer = await generator.generate(task)
for (let round = 0; round < MAX_REVISIONS; round++) {
const evaluation = await evaluator.evaluate({
task,
answer,
criteria: {
accuracy: 0.9,
completeness: 0.8,
citationRequired: true
}
})
if (evaluation.passed) return answer
answer = await generator.rewrite({
task,
previousAnswer: answer,
feedback: evaluation.feedback
})
}
return { status: 'incomplete', answer }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
工程提醒:代码必须明确评审标准、通过阈值和最大修改次数,不能只让模型无限 “再优化一下”。
# Orchestrator-Workers:并行只用于可独立子任务
这里的 Worker 是逻辑上的子任务执行单元,不是浏览器的 Web Worker,也不一定对应一个真实线程或进程。它通常由一次独立的模型调用或一个只负责特定任务的子 Agent 承担;主控 Orchestrator 负责拆分、分配、合并和验收,Worker 只完成分配给自己的局部任务。
例如分析三家公司时,主控可以把三家公司分别交给三个 Worker 并行研究,再统一汇总结论。每个 Worker 应只拿到完成其任务所需的目标、资料、约束和输出 Schema,避免无关上下文增加成本和干扰。强依赖任务盲目并行则会增加冲突和等待。
控制流: 拆分 → 并行执行 → 汇总验收
async function runOrchestrator(goal: string) {
const subtasks = await orchestrator.decompose(goal)
const workerResults = await Promise.all(
subtasks.map(subtask =>
runWorker({
task: subtask,
outputSchema: WorkerResultSchema
})
)
)
return orchestrator.synthesize({
goal,
results: workerResults
})
}
async function runWorker(input: WorkerInput) {
return model.generateObject({
prompt: input.task,
schema: input.outputSchema
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
工程提醒:只有相互独立的子任务才适合放入 Promise.all;主控仍要负责结果合并、冲突处理和最终验收。
# Handoff:把控制权交给另一个 Agent
Router 是在入口处选择流程;Handoff 则是当前 Agent 已经参与处理,随后把任务状态、必要上下文和后续控制权交给另一个 Agent。
实际项目使用 Agent 框架时,通常声明当前 Agent 允许移交给哪些 Agent,由框架的 Runner 或状态图完成切换;自己实现轻量运行时时,更常见的是使用显式状态机,而不是递归调用。这样可以直接看到当前由谁处理,也更容易加入权限、次数限制、追踪和暂停恢复。
控制流: Agent A 处理 → 移交上下文 → Agent B 接管
type AgentResult =
| { type: 'answer'; content: string }
| { type: 'handoff'; target: AgentName; reason: string }
async function runAgentRuntime(
initialAgentName: AgentName,
initialContext: ConversationContext
): Promise<string> {
let currentAgentName = initialAgentName
let context = initialContext
let handoffCount = 0
while (true) {
const currentAgent = agents[currentAgentName]
const result = await currentAgent.run(context)
if (result.type === 'answer') {
return result.content
}
if (!currentAgent.allowedHandoffs.includes(result.target)) {
throw new Error(`不允许移交给 ${result.target}`)
}
handoffCount += 1
if (handoffCount > MAX_HANDOFFS) {
throw new Error('超过最大移交次数')
}
const previousAgentName = currentAgentName
// 切换当前 Agent;下一轮将执行 agents[result.target]
currentAgentName = result.target
// 只移交目标、已验证事实、关键产物、待办事项和必要权限
context = buildHandoffContext(context, {
from: previousAgentName,
to: currentAgentName,
reason: result.reason
})
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
工程提醒:Handoff 不是普通函数转发。运行时还要限制允许的目标 Agent 和最大移交次数,记录 from、to、reason 与 Trace,并防止两个 Agent 相互移交形成循环。
# 如何选择
路径固定吗? ─ 是 → Workflow
│否
只有一次分类吗? ─ 是 → Router
│否
需要边查边决定吗? ─ 是 → ReAct
│否
长任务且步骤有依赖? ─ 是 → Plan-and-Execute
│
可拆成独立子任务? ─ 是 → Orchestrator-Workers
结果可用明确标准反复评审? ─ 是 → Evaluator-Optimizer
2
3
4
5
6
7
8
9
10
模式可以组合,但每增加一个模型节点,都会增加延迟、成本、失败路径和测试组合。先建立单 Agent 基线,证据表明上下文或能力边界确实成为瓶颈,再拆分多 Agent。
# 贯穿项目怎样组合
笔记与面试助手可以这样设计:
- Router 判断是知识问答、模拟面试还是学习计划;
- 问答使用 ReAct,在检索不足时改写查询并补查;
- 学习计划用 Plan-and-Execute,保存完成进度;
- 生成面试回答后用 Evaluator 检查概念、项目例子和表达长度;
- 批量整理多个主题时才用 workers 并行,最后由主控去重和验收。
# 高频面试题与回答
回答顺序:先说结论,再解释原因,最后补一个例子或工程边界。不要逐字背诵,记住这条表达主线即可。
1. 30 秒回答:你常用哪些 Agent 模式参考答案
我会根据任务的控制方式选模式:只需要先分类再走固定分支,用 Router;需要执行一步、看结果再决定,用 ReAct;长任务先规划且计划会调整,用 Plan-and-Execute;结果有明确评分标准时,用 Evaluator-Optimizer;只有子任务真正独立时,才用 Orchestrator-Workers 并行。多 Agent 之间需要转交控制权时用 Handoff。无论哪种模式,外层仍用程序控制权限、预算和停止条件。
2. ReAct 是否等于把模型思维过程展示出来?参考答案
不是。工程上真正需要的是 行动 → 观察 → 再行动 的外部循环,不需要读取或展示模型的隐藏思维。日志记录它选了什么工具、传了什么参数、拿到什么结果以及状态怎样变化,就足以调试和审计。
3. Plan-and-Execute 为什么不一定比 ReAct 好?参考答案
因为提前生成计划本身需要额外调用,而且环境一变化,计划可能马上失效。任务很短或变化很快时,ReAct 执行一步看一步反而更简单。只有任务较长、步骤有依赖,并且计划能帮助跟踪进度时,Plan-and-Execute 才真正有价值。
4. 多 Agent 为什么经常不如单 Agent?参考答案
多 Agent 会多出任务拆分、上下文移交、结果合并和冲突处理,延迟、费用和调试难度都会上升。如果几个 Agent 没有独立能力,只是在互相转述消息,那么一个 Agent 配合清晰工具和 Workflow 往往更可靠。只有职责和信息边界确实能分开时,多 Agent 才值得。
5. Evaluator 如何避免无限修改?参考答案
要在开始前写清评分标准、通过分数和最大修改次数。如果连续几轮没有明显提升,就停止并保留得分最高的版本。重要结果还要加入测试、Schema 或人工评审,不能让同一个模型一边生成、一边无限给自己打分。
# 接下来学什么
下一篇学习 Context Engineering,重点理解每轮模型应该看到哪些信息,以及怎样控制上下文的质量、边界和成本。