Appearance
第2章 Agent 架构模式全景
在上一章中我们讨论了为什么需要 Harness 工程——LLM 本身只是引擎,真正让它在复杂任务中跑起来的,是围绕它搭建的架构骨架。本章将系统梳理当前业界主流的六种 Agent 架构模式。对于每一种模式,我们会回答三个问题:它是什么、怎么实现、什么时候该用。
这六种模式并非互斥。在实际系统中,你经常会看到它们的组合——例如一个 Multi-Agent 系统中的每个子 Agent 可能各自采用 ReAct 模式,而整个协作流程则由一个状态机来编排。理解每种模式的核心思想和取舍,是做出正确架构决策的前提。
2.1 Tool-Augmented LLM:最简模式
核心思想
这是最朴素的 Agent 形态:给 LLM 提供一组工具(函数),让它根据用户输入决定是否调用工具、调用哪个工具、传什么参数。整个流程只有一轮决策。
这就是 OpenAI Function Calling 和 Claude Tool Use 最基础的使用方式。很多开发者在第一次接触 Agent 概念时,其实已经在不知不觉中使用了这种模式——你定义了一组函数签名,LLM 选择调用哪个函数,你执行函数并把结果返回给 LLM,然后 LLM 组织最终回答。整个过程非常直观,没有复杂的状态管理,也不需要额外的框架支持。
值得注意的是,即便是这个最简模式,工具定义的质量也至关重要。一个好的工具 schema 应该包含清晰的描述、准确的参数类型以及有意义的示例值。这决定了 LLM 能否正确选择工具并传入合理的参数。我们会在第5章详细讨论工具设计的最佳实践。
伪代码实现
python
def tool_augmented_llm(user_message: str, tools: list[Tool]) -> str:
"""最简单的 Tool-Augmented LLM 模式"""
# 第一步:把用户消息和工具定义一起发给 LLM
response = llm.chat(
messages=[{"role": "user", "content": user_message}],
tools=[t.schema for t in tools],
)
# 第二步:如果 LLM 决定调用工具
if response.tool_calls:
tool_results = []
for call in response.tool_calls:
tool = find_tool(call.name, tools)
result = tool.execute(**call.arguments)
tool_results.append(result)
# 第三步:把工具结果返回给 LLM,生成最终回答
final = llm.chat(
messages=[
{"role": "user", "content": user_message},
{"role": "assistant", "content": response},
{"role": "tool", "content": tool_results},
],
)
return final.content
return response.content适用场景
- 任务只需要一次工具调用即可完成(查天气、汇率换算、数据库单次查询)
- 延迟敏感,需要快速响应
- 工具数量有限(10 个以内效果最好)
局限性
这个模式最大的问题是没有循环。如果第一次工具调用的结果不够好,或者任务需要多步操作,它就束手无策了。举一个具体的例子:用户问"帮我查一下北京明天的天气,如果会下雨就提醒我带伞"。单轮模式可以查到天气,但"判断是否下雨 → 决定是否提醒"这个条件逻辑就需要在一轮调用中全部完成,稍微复杂一点的条件分支就会超出它的能力范围。这正是后续模式要解决的问题。
尽管如此,永远从最简模式开始是一个重要的工程原则。如果你的需求用 Tool-Augmented LLM 就能满足,那就不要引入更复杂的架构。过度设计是 Agent 工程中最常见的错误之一。
2.2 ReAct:推理与行动交织
核心思想
ReAct(Reasoning + Acting)是当前最主流的 Agent 模式,由 Yao 等人于 2022 年提出。其核心是让 LLM 在一个循环中交替进行推理(Thought)和行动(Action),每次行动后观察结果(Observation),再决定下一步。
LangChain 的标准 Agent、Claude Code 的主循环,都是这个模式的变体。
相比于 Tool-Augmented LLM,ReAct 的关键进步在于引入了循环。LLM 不再是一次性决策,而是在一个 while 循环中持续运行,直到它判断任务已完成或外部条件触发终止。这个循环就是我们常说的 "Agent Loop"(Agent 主循环),它是几乎所有复杂 Agent 系统的基础构件。
ReAct 模式之所以有效,还有一个重要原因:推理过程(Thought)本身就是一种"自我提示"。当 LLM 在输出中写下"我需要先查询用户的订单信息"时,这段文字会出现在下一轮的输入上下文中,相当于给自己设定了一个明确的短期目标。这种思维链(Chain-of-Thought)与行动的交织,显著提升了 LLM 在多步任务中的表现。
伪代码实现
python
def react_agent(user_message: str, tools: list[Tool], max_steps: int = 10) -> str:
"""ReAct 模式:推理-行动循环"""
messages = [{"role": "user", "content": user_message}]
for step in range(max_steps):
# LLM 同时输出推理过程和工具调用决策
response = llm.chat(messages=messages, tools=[t.schema for t in tools])
messages.append({"role": "assistant", "content": response})
# 如果 LLM 没有调用工具,说明它认为任务完成了
if not response.tool_calls:
return response.content
# 执行所有工具调用,把结果追加到对话历史
for call in response.tool_calls:
tool = find_tool(call.name, tools)
result = tool.execute(**call.arguments)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": str(result),
})
return "达到最大步数限制,任务未完成"关键设计要素
循环终止条件是 ReAct 模式最重要的工程问题之一。上面的代码用了两个条件:LLM 不再调用工具(自然终止),或达到最大步数(强制终止)。实际系统中还需要考虑:
- Token 预算耗尽:上下文窗口快满时需要决策——是截断历史、做摘要、还是直接输出当前最好的结果?
- 错误累积:连续多次工具调用失败时,应该提前退出并告知用户,而不是反复重试。
- 用户中断:交互式系统(如 Claude Code)允许用户随时中断 Agent 循环。
适用场景
- 需要多步信息收集和推理的任务
- 任务路径不确定,需要根据中间结果动态调整
- 大多数通用型 Agent 的默认选择
局限性
ReAct 的推理是逐步的,每一步只看当前状态决定下一步。对于需要全局规划的复杂任务(比如"把这个 Python 项目重构为微服务架构"),逐步推理容易迷失在细节中,忘记全局目标。这就好比一个人在迷宫中只看脚下一步,虽然每一步都是合理的,但可能走了很多弯路甚至走进死胡同。
另一个实际问题是上下文窗口的压力。ReAct 循环中,每一轮的推理文本和工具返回结果都会累积在对话历史中。当任务需要几十步操作时,对话历史可能会膨胀到数万甚至数十万 Token,逼近模型的上下文窗口限制。如何在循环中管理上下文——何时截断、何时做摘要、何时丢弃旧的工具结果——是 ReAct 模式落地时必须解决的工程问题。我们会在第11章(短期记忆)中深入讨论这个话题。
2.3 Plan-and-Execute:先规划,后执行
核心思想
Plan-and-Execute 模式将任务分为两个阶段:先由一个 Planner 生成完整计划,再由一个 Executor 逐步执行。BabyAGI 和 AutoGPT 是这个模式的早期代表。
伪代码实现
python
def plan_and_execute(user_message: str, tools: list[Tool]) -> str:
"""Plan-and-Execute 模式"""
# 阶段一:生成计划
plan = planner_llm.chat(
messages=[{
"role": "user",
"content": f"""请为以下任务制定执行计划,输出为步骤列表。
任务:{user_message}
可用工具:{[t.name for t in tools]}"""
}]
)
steps = parse_plan(plan.content) # 解析为结构化步骤列表
# 阶段二:逐步执行
results = []
for i, step in enumerate(steps):
# 每一步都可以是一个小型 ReAct 循环
step_result = react_agent(
user_message=f"执行以下步骤:{step}\n\n背景:{user_message}\n已完成步骤:{results}",
tools=tools,
max_steps=5,
)
results.append({"step": step, "result": step_result})
# 可选:检查是否需要重新规划
if should_replan(results, steps[i+1:]):
remaining_steps = replan(user_message, results, steps[i+1:])
steps = steps[:i+1] + remaining_steps
# 阶段三:汇总
summary = llm.chat(
messages=[{
"role": "user",
"content": f"任务:{user_message}\n执行结果:{results}\n请汇总最终答案。"
}]
)
return summary.content关键设计要素
**重规划(Replanning)**是这个模式的核心难点。计划一旦生成,执行过程中必然会遇到预期之外的情况——工具返回了意外结果、某个步骤失败了、甚至发现原始计划遗漏了关键步骤。好的 Plan-and-Execute 系统需要在以下时机触发重规划:
- 某一步执行失败且重试无效
- 某一步的结果与预期严重偏离
- 发现了新信息,使得后续步骤不再适用
重规划本身也有成本(额外的 LLM 调用),因此需要设定合理的重规划预算。
适用场景
- 复杂任务,需要多步骤协调
- 任务有明确的阶段性(调研 → 设计 → 实现 → 测试)
- 需要给用户展示执行计划、让用户确认后再执行
局限性
- 初始规划的质量高度依赖 LLM 对任务的理解,如果理解有偏差,整个计划都会跑偏
- 两阶段架构引入了额外的延迟和 Token 消耗
- 对于简单任务来说过于重量级
2.4 Reflexion / Self-Critique:自我评估与迭代
核心思想
Reflexion 模式的核心洞察是:LLM 可以评估自己(或另一个 LLM)的输出质量,并基于评估结果进行改进。这形成了一个"生成 → 评估 → 改进"的迭代循环。
生成初始输出
│
▼
┌──────────────────────────────────────┐
│ ┌──────────┐ 评估意见 ┌─────┐ │
│ │ Critic │ ────────────→ │生成器│ │
│ │ (评估LLM)│ ←──────────── │(LLM)│ │
│ └──────────┘ 改进后输出 └─────┘ │
│ 迭代循环 │
└──────────────────────────────────────┘
│
▼
质量达标 → 最终输出伪代码实现
python
def reflexion_agent(
user_message: str,
max_iterations: int = 3,
quality_threshold: float = 0.8,
) -> str:
"""Reflexion 模式:自我评估与迭代改进"""
# 第一轮:生成初始输出
output = generator_llm.chat(
messages=[{"role": "user", "content": user_message}]
).content
for iteration in range(max_iterations):
# 评估当前输出
critique = critic_llm.chat(
messages=[{
"role": "user",
"content": f"""请评估以下输出的质量。
原始任务:{user_message}
当前输出:{output}
请给出:
1. 质量评分(0-1)
2. 具体问题列表
3. 改进建议"""
}]
).content
score = parse_score(critique)
if score >= quality_threshold:
break # 质量达标,退出循环
# 基于评估意见改进输出
output = generator_llm.chat(
messages=[{
"role": "user",
"content": f"""请根据以下反馈改进你的输出。
原始任务:{user_message}
当前输出:{output}
评估反馈:{critique}
请输出改进后的版本。"""
}]
).content
return output关键设计要素
**Critic 的质量决定了整个系统的上限。**一个糟糕的 Critic 会让系统在错误方向上迭代,越改越差。设计 Critic 时的关键考量:
- 评估维度要具体:不是笼统地问"好不好",而是从正确性、完整性、格式规范等具体维度评估
- Critic 和 Generator 可以用不同模型:例如用 Claude Opus 做 Critic,用 Haiku 做 Generator,平衡质量和成本
- 引入外部验证:对于代码生成任务,可以用单元测试作为客观 Critic,比 LLM 评估更可靠
适用场景
- 代码生成(写代码 → 跑测试 → 修 bug → 再跑测试)
- 文案撰写、翻译等需要反复打磨的创作任务
- 任何有明确质量评估标准的任务
局限性
- 每轮迭代都消耗额外的 Token,成本是线性增长的
- LLM 评估 LLM 存在"自我认同偏差"——倾向于认为自己的输出已经足够好
- 对于没有明确评估标准的开放性任务,Critic 的效果有限
2.5 Multi-Agent:多智能体协作
核心思想
Multi-Agent 模式将一个复杂任务拆分给多个专门化的 Agent,每个 Agent 有自己的角色定义、工具集和系统提示词。Agent 之间通过某种协议进行通信和协作。
CrewAI、AutoGen、以及 Claude Code 的 subagent 系统都属于这一类。
伪代码实现
python
@dataclass
class AgentConfig:
name: str
system_prompt: str
tools: list[Tool]
model: str # 不同 Agent 可以用不同模型
def multi_agent_system(user_message: str, agents: list[AgentConfig]) -> str:
"""Multi-Agent 模式:多智能体协作"""
# 协调者决定任务分配
coordinator_response = coordinator_llm.chat(
messages=[{
"role": "system",
"content": "你是任务协调者。根据用户任务和可用 Agent 列表,制定协作方案。",
}, {
"role": "user",
"content": f"任务:{user_message}\n可用Agent:{[a.name for a in agents]}",
}]
)
task_assignments = parse_assignments(coordinator_response.content)
# 按依赖顺序执行各 Agent 的子任务
context = {} # 共享上下文
for assignment in topological_sort(task_assignments):
agent_config = find_agent(assignment.agent_name, agents)
# 每个子 Agent 内部可以用 ReAct 模式
result = react_agent(
user_message=f"{assignment.task}\n\n共享上下文:{context}",
tools=agent_config.tools,
system_prompt=agent_config.system_prompt,
model=agent_config.model,
)
context[assignment.agent_name] = result
# 协调者汇总各 Agent 的输出
final = coordinator_llm.chat(
messages=[{
"role": "user",
"content": f"原始任务:{user_message}\n各Agent执行结果:{context}\n请汇总最终答案。",
}]
)
return final.content协作拓扑
Multi-Agent 系统的通信拓扑有几种典型形式:
星型拓扑(Hub-and-Spoke):一个协调者 Agent 管理所有子 Agent,子 Agent 之间不直接通信。Claude Code 的 subagent 模式就是这种——主 Agent 可以启动子 Agent 处理独立任务,子 Agent 完成后把结果返回给主 Agent。这是最容易理解和调试的拓扑。
链式拓扑(Pipeline):Agent A 的输出是 Agent B 的输入,形成流水线。适合有明确阶段的任务,例如"调研 → 撰写 → 审校"。
网状拓扑(Mesh):Agent 之间可以自由通信。AutoGen 的 GroupChat 模式就是这种——多个 Agent 在一个"聊天室"里讨论,轮流发言。这种拓扑表达力最强,但也最难控制。
适用场景
- 任务涉及多个差异很大的专业领域
- 需要"分而治之"的大规模任务
- 希望通过角色分离来提高输出质量(写代码的和审代码的分开)
局限性
- 通信开销大:每次 Agent 间传递信息都需要 LLM 调用
- 协调复杂:任务分配、冲突解决、结果合并都是工程难题
- 调试困难:问题可能出在任何一个 Agent 或者 Agent 之间的接口上
2.6 State Machine / Graph-based:显式状态转移
核心思想
前面的模式或多或少都有一个隐含假设:流程的走向由 LLM 动态决定。State Machine 模式则反其道而行——用显式的状态图来定义流程,LLM 在每个状态节点内执行具体任务,但状态之间的转移逻辑是预定义的。
LangGraph 是这个模式的代表性框架。
伪代码实现
python
from enum import Enum
from typing import Callable
class State(Enum):
UNDERSTAND = "understand"
SEARCH = "search"
GENERATE_CODE = "generate_code"
REVIEW = "review"
RESPOND = "respond"
END = "end"
@dataclass
class Node:
state: State
action: Callable # 该状态要执行的动作(通常包含 LLM 调用)
transitions: dict[str, State] # condition_name → next_state
def state_machine_agent(user_message: str, graph: dict[State, Node]) -> str:
"""State Machine 模式:显式状态转移"""
current_state = State.UNDERSTAND
context = {"user_message": user_message, "history": []}
while current_state != State.END:
node = graph[current_state]
# 在当前状态执行动作
result, condition = node.action(context)
context["history"].append({
"state": current_state.value,
"result": result,
})
# 根据条件转移到下一个状态
next_state = node.transitions.get(condition)
if next_state is None:
raise ValueError(f"状态 {current_state} 没有条件 {condition} 的转移")
current_state = next_state
return context["history"][-1]["result"]
# 定义状态图
def understand_action(ctx):
response = llm.chat(messages=[{
"role": "user",
"content": f"分析用户意图:{ctx['user_message']}\n输出:need_search / can_answer_directly",
}])
intent = parse_intent(response.content)
return response.content, intent # (结果, 转移条件)
graph = {
State.UNDERSTAND: Node(
state=State.UNDERSTAND,
action=understand_action,
transitions={
"need_search": State.SEARCH,
"can_answer_directly": State.RESPOND,
},
),
State.SEARCH: Node(
state=State.SEARCH,
action=search_action,
transitions={"done": State.RESPOND},
),
State.RESPOND: Node(
state=State.RESPOND,
action=respond_action,
transitions={"done": State.END},
),
}关键设计要素
确定性与灵活性的平衡是这个模式的核心张力。状态图越详细,系统行为越可预测,但灵活性越低。反之,状态节点越少、每个节点内的 LLM 自由度越高,系统越灵活但越难控制。
实践中的建议是:把流程层面的确定性交给状态机,把内容层面的灵活性交给 LLM。例如"先搜索再回答"这个流程是确定的,但"搜索什么关键词""如何组织回答"则由 LLM 自由发挥。
适用场景
- 业务流程有严格的合规要求(金融、医疗)
- 需要精确控制 Agent 行为,确保不会"跑偏"
- 多团队协作开发 Agent,需要清晰的接口定义
- 需要可视化监控 Agent 的执行过程
局限性
- 设计状态图本身需要对任务有深入理解,前期投入大
- 难以处理完全开放性的任务("帮我做一个有创意的东西")
- 状态爆炸问题:复杂任务的状态图可能变得非常庞大
2.7 架构模式横向对比
理解了每种模式的原理之后,关键问题是:在实际项目中,该如何选择?我们从四个维度来对比。
对比表
| 模式 | 延迟 | Token 成本 | 可靠性 | 实现复杂度 |
|---|---|---|---|---|
| Tool-Augmented LLM | 低 (1-2 轮 LLM 调用) | 低 | 中 (无纠错机制) | 极低 |
| ReAct | 中 (3-10 轮) | 中 | 中高 (可以自我纠错) | 低 |
| Plan-and-Execute | 高 (规划 + 执行多轮) | 高 | 中 (依赖计划质量) | 中 |
| Reflexion | 高 (每次迭代翻倍) | 高 | 高 (显式质量把关) | 中 |
| Multi-Agent | 极高 (多 Agent 协作) | 极高 | 中 (协调是薄弱环节) | 高 |
| State Machine | 中 (取决于图复杂度) | 中 | 极高 (行为可预测) | 中高 |
选择决策树
面对一个具体任务,可以按以下顺序思考:
**第一问:任务能在一轮工具调用中完成吗?**如果是,用 Tool-Augmented LLM。不要过度设计。一个查天气的功能不需要 Multi-Agent 架构。
**第二问:任务需要多步操作,但路径大致可预测吗?**如果路径可预测(比如"先查询数据库,再格式化结果,最后发邮件"),用 State Machine。明确的流程用明确的控制。
**第三问:任务需要多步操作,但路径不确定?**这是 ReAct 的主场。大多数通用型 Agent(聊天助手、代码助手)都该从 ReAct 起步。
**第四问:任务很复杂,需要全局规划?**在 ReAct 的基础上加一层 Planner,变成 Plan-and-Execute。但要注意重规划机制,否则一旦计划出错就全盘皆输。
**第五问:输出质量至关重要,可以牺牲速度?**叠加 Reflexion。让 Agent 生成初稿后自我审查并迭代。特别适合代码生成——写完代码跑测试,测试不过就改。
**第六问:任务涉及多个差异很大的领域?**考虑 Multi-Agent。但务必从最简单的星型拓扑开始,不要一上来就搞网状通信。
模式组合的实际案例
以 Claude Code 的架构为例,它实际上组合了多种模式:
- 主循环是 ReAct:用户输入 → 推理 → 调用工具(读文件、写文件、执行命令)→ 观察结果 → 继续推理
- 子任务用 Multi-Agent:遇到独立的子任务时,可以启动 subagent 并行处理
- 隐式的 Reflexion:当工具执行失败(比如代码编译报错),Agent 会分析错误信息并修正,这本质上就是一个 Reflexion 循环
- 权限控制是 State Machine:某些操作(如写文件、执行命令)需要用户确认,这部分是确定性的状态转移逻辑
这说明真正的生产系统很少只用一种模式。理解每种模式的核心思想,才能在合适的层次选择合适的模式。
2.8 从架构到工程
本章梳理的六种模式提供了一张"菜单"。但选好了菜式,还需要厨师的手艺。后续章节将深入每一个工程细节:
- 第3章将深入 Agent 主循环的实现——循环控制、Token 管理、错误恢复
- 第5-7章将讨论工具设计与编排——工具是 Agent 的"手脚",设计不好再好的架构也白搭
- 第8-10章将讨论 Prompt 架构——系统提示词如何组织、指令优先级如何处理
- 第16章将专门深入 Multi-Agent 模式的工程实践
架构模式给出了骨架,而接下来的章节将为这个骨架填充血肉。
本章小结
- Tool-Augmented LLM 是最简模式,适合单轮工具调用场景,务必从这里开始,不要过度设计
- ReAct 是当前最主流的 Agent 模式,通过推理-行动循环实现多步任务,是大多数通用 Agent 的默认选择
- Plan-and-Execute 在 ReAct 之上增加了全局规划,适合复杂的多阶段任务,但要注意重规划机制
- Reflexion 通过自我评估和迭代来提升输出质量,特别适合有明确质量标准的任务(如代码生成)
- Multi-Agent 通过角色分工来应对跨领域复杂任务,但通信和协调的工程开销不可低估
- State Machine 通过显式状态图来确保行为可预测,适合合规性要求高或流程明确的场景
- 真实系统通常是多种模式的组合——在不同层次、不同模块选择最合适的模式