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)
  }
}
1
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('超过最大执行步数')
}
1
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)
}
1
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 }
}
1
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
  })
}
1
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
    })
  }
}
1
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
1
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,重点理解每轮模型应该看到哪些信息,以及怎样控制上下文的质量、边界和成本。

# 参考资料

上次更新时间: 2026年09月10日 00:05:23