Chapter 02 · Agent Architecture
Agent 架构
01 章的记忆系统回答"Agent 带着什么上下文行动",本章回答"Agent 按什么流程行动"——从单 Agent 的 FSM 状态机与 ReAct 循环,到多 Agent 拓扑,再到 LangChain / LangGraph 的运行时选型。
本章四题 · 答案骨架
- Q3 FSM:五元组形式定义 + Python 实现三档(dict 表 / Enum+match / transitions 库)
- Q4 ReAct:任务形状决定 Chain、单轮 Tool Use 还是 ReAct loop
- Q6 多 Agent:三条正交轴(拓扑 × 协调 × 通信)× 四条划分依据
- Q12 LangChain = 高层 API,LangGraph = 低层运行时,前者构建在后者之上
Q3FSM 状态机分哪几部分?如何代码实现?
能否把"状态机"从直觉概念落到形式定义与可维护的代码结构,并迁移到 Agent 流程控制的真实场景。
参考答案
FSM(有限状态机)的形式定义是一个五元组:
- Q — 状态集合:系统全部合法状态
- Σ — 输入 / 事件集:触发转移的外部输入
- δ — 转移函数:δ(状态, 事件) → 新状态
- q₀ — 初始态
- F — 终态集
工程实现在五元组之外再补两个构件。其一是动作 / 副作用,按挂载位置分两种机型:Mealy 机把动作挂在转移边上(出边动作,同一状态因不同事件触发不同副作用),Moore 机把动作挂在状态入口(入态动作,副作用集中、易测试)。其二是 guard 条件:转移上的布尔谓词,事件到达且 guard 为真才允许转移,用于表达"重试次数未超限才回到执行态"一类约束。
Python 实现按复杂度分三档。第一档:dict 转移表,把 δ 直接写成 {(state, event): next_state} 的字典,配一个 step() 函数,状态与事件少时零依赖、一眼看全:
# δ: (state, event) -> next_state,转移表即状态图本身
TRANSITIONS = {
("idle", "user_input"): "planning",
("planning", "need_tool"): "calling",
("planning", "can_answer"): "answering",
("calling", "tool_result"): "observing",
("observing", "need_more"): "calling", # 条件回边
("observing", "enough"): "answering",
("answering", "finish"): "idle",
}
def step(state: str, event: str) -> str:
key = (state, event)
if key not in TRANSITIONS:
raise ValueError(f"非法转移: {state} x {event}")
return TRANSITIONS[key]
第二档:Enum + match,把状态、事件、guard 与副作用全部显式写出,适合 plan → execute → verify → done 一类任务编排:
from enum import Enum, auto
class State(Enum):
PLAN = auto(); EXECUTE = auto(); VERIFY = auto()
DONE = auto(); FAILED = auto() # DONE/FAILED ∈ 终态集 F
class TaskMachine:
MAX_RETRY = 3
def __init__(self):
self.state = State.PLAN # q0 初始态
self.retries = 0
def on_event(self, event: str) -> State:
match (self.state, event):
case (State.PLAN, "plan_ready"):
self.state = State.EXECUTE
case (State.EXECUTE, "result"):
self.state = State.VERIFY
case (State.VERIFY, "pass"):
self.state = State.DONE
case (State.VERIFY, "fail") if self.retries < self.MAX_RETRY:
self.retries += 1 # guard 为真:重试回边
self.state = State.EXECUTE
case (State.VERIFY, "fail"):
self.state = State.FAILED # guard 为假:进失败终态
case _:
raise ValueError(f"非法转移: {self.state} x {event}")
return self.state
第三档:transitions 库。声明式定义状态与转移,原生支持 before/after callback(对应 Mealy 出边动作)、on_enter(对应 Moore 入态动作)、conditions guard,以及 HierarchicalMachine 层级状态机与状态图导出。状态、事件数量增长,或需要在 code review 时核对状态图,引入此档。
Agent 场景中 FSM 出现在三个典型位置:对话流程控制(槽位收集、多轮确认)、任务编排(plan → execute → verify → done)、工具调用循环(calling → observing → answering,即图 1)。
FSM 形式上是五元组:状态集合 Q、事件集 Σ、转移函数 δ、初始态 q₀、终态集 F;工程上再补两件事——动作(Mealy 挂在出边、Moore 挂在入态)和 guard 条件。代码实现分三档:最轻是 dict 转移表加一个 step 函数;中间档用 Enum + match 把转移、guard、副作用写显式;再往上用 transitions 库拿声明式定义和层级状态机。Agent 里典型用法是工具调用循环:calling、observing、answering 三态加一条条件回边。
加分点
LangGraph 本质是 FSM 的超集:节点 = 状态,条件边 = 转移函数 δ。差别在执行模型——LangGraph 基于 Pregel 模型按超步(super-step)调度,允许多个节点并发激活,与经典 FSM"任意时刻只处于一个状态"不同。图中的环必须由条件边给出退出条件,否则触发 GraphRecursionError(默认 25 步上限)。
状态机视角同样覆盖算法题:05 章股票买卖的状态机 DP(持有 / 不持有两态 + 转移方程)与本题同构。
追问预判
状态数量增长导致转移表爆炸,怎么办?
应对思路
引入层级状态机:子状态共享父状态的转移规则,公共逻辑(如"任意状态收到 cancel 都终止")只写一次;transitions 的 HierarchicalMachine 直接支持。Agent 场景的等价手段是把子流程下沉为独立子图(LangGraph subgraph),父图只看到子图的入口与出口。
Mealy 和 Moore 在工程上怎么取舍?
应对思路
Mealy 动作挂在边上,同一状态的不同入边可执行不同副作用,表达力强但副作用散落各处;Moore 动作挂在状态入口,副作用集中、单测友好。工程实践常混用:入态做初始化与不变量检查(Moore),出边做与事件强相关的动作(Mealy)。
什么时候从手写 dict 升级到 transitions 库?
应对思路
状态 ≤ 5、事件 ≤ 10 时 dict 足够且零依赖;出现 guard / callback / 层级嵌套需求,或团队需要把状态图导出成图片做 review 时引库——声明式定义把状态图从代码中显式化,变更的 diff 即状态图的 diff。
Q4为什么选择 ReAct,而不是纯 Chain 或 Tool Use?
架构选型的判断依据——是否理解三种模式各自适用的任务形状,而不是背诵 ReAct 的名词解释。
参考答案
ReAct(Yao et al., 2022)的核心是把 Reasoning 与 Acting 交织进同一循环:Thought 决定下一步动作,Action 执行工具调用,Observation 把结果送回推理,推理再据此修正方向。三种模式的本质差异在于"路径在什么时候被决定":
| 模式 | 路径形状 | 决策时机 | 适用任务 | 局限 |
|---|---|---|---|---|
| 纯 Chain | 直线(固定流水线) | 构造期一次定死 | 步骤可预枚举的确定流程 | 处理不了"不知道要几步"的任务 |
| 单轮 Tool Use | 单跳 | 运行期决策一次 | 单步查询、一次性调用 | 调完即止:无法基于结果迭代,无错误恢复 |
| ReAct loop | 带环 | 每一轮运行期决策 | 开放式多步任务 | 延迟与 token 随轮数倍增,需 max_iterations 兜底 |
ReAct 的收益由"环"带来:动态决策(步数未知也能推进)、错误恢复(一条路失败后换工具、换查询重试)、可观测性(推理轨迹逐轮留痕,便于调试与审计)。代价同样由"环"带来:多轮交互的延迟、token 用量随轮数倍增、循环失控需要 max_iterations 一类硬上限兜底。
面试作答的正确顺序是先确认任务形状,再给结论:步骤可预枚举 → Chain 更便宜更稳;单步查询 → 一次 Tool Use 足够;开放式多步、步数未知 → 才轮到 ReAct loop。
ReAct 把推理和行动交织成 Thought–Action–Observation 循环:推理指导行动,观察结果修正推理。纯 Chain 在构造期定死路径,处理不了步数未知的任务;单轮 Tool Use 调完即止,无法基于结果迭代,也没有错误恢复。所以开放式多步任务才用 ReAct loop,换来的是动态决策和失败重试,代价是延迟和 token 倍增,要配 max_iterations 兜底。反过来,步骤可枚举就退回 Chain,单步查询一次 Tool Use 就够——选型由任务形状决定。
加分点
2025 之后的演化:显式 Thought:/Action: 文本格式已经过时——现代模型把 tool calling 与 reasoning 原生化到 API 层,结构化输出取代了脆弱的文本解析;LangGraph 的 create_react_agent 已废弃,由 LangChain 1.0 的 create_agent 接替。一句话总结:ReAct 作为 prompt 格式死了,作为 loop 架构思想活着。
追问预判
ReAct 循环不收敛、原地打转,怎么处理?
应对思路
三层防线:max_iterations 硬上限(LangGraph 对应 recursion_limit);把已尝试过的动作与结果写进上下文,避免重复同一查询;失败计数触发降级路径——换工具、直接基于已有信息作答、或升级到人工。条件边必须显式给出退出分支,这是图 1 中"条件边"的工程含义。
现在还需要手写 Thought: / Action: 提示词吗?
应对思路
不需要。原生 tool calling 由 API 返回结构化的 tool_calls 字段,推理由模型内化;手写文本格式反而引入解析失败的脆弱点。保留的有效做法是要求模型在行动前输出一句简短 rationale——不参与解析,只服务可观测性。
Chain 和 ReAct 能混合使用吗?
应对思路
能,且是生产系统的常态:外层用固定 workflow(检索 → agent → 校验 → 格式化)保证确定性,把 ReAct loop 收敛为其中一个节点。原则是"确定的部分用 Chain 省成本,开放的部分才交给 loop"。
Q6多 Agent 框架怎么设计?划分 Agent 的依据是什么?
系统设计能力:拆分 Agent 是否有明确依据,是否清楚多 Agent 的成本曲线与不适用的边界。
参考答案
多 Agent 设计沿三条正交轴展开,三轴独立选择、组合成最终架构:
- 拓扑:supervisor(协调者)星形 / hierarchical 树形 / swarm 任意图 + handoff(交接);
- 协调:中心调度(supervisor 统一分派与汇总)vs 去中心交接(执行中把控制权 handoff 给下一个 Agent);
- 通信:共享 state vs 消息传递,并按上下文隔离档位决定向对方暴露多少中间态。
划分 Agent 的依据按优先级排列为四条:
- 按上下文隔离需求(最重要):subagent 拥有独立上下文窗口,长流程的中间产物不污染主线,主 Agent 只接收浓缩结论;
- 按领域 / 职责:检索、写作、审校各成一职,单一职责降低提示词复杂度;
- 按工具集:每个 Agent 只挂自己用得到的工具,缩小决策空间、降低误调用率;
- 按模型能力分层:orchestrator 用强模型做规划与汇总,worker 用便宜模型跑量。
工程标杆是 Anthropic 的多 Agent 研究系统:orchestrator-worker 拓扑,在内部研究评测上比单 Opus 4 提升 90.2%,代价是约 15 倍 token 消耗。该系统给出的另一条硬经验:委派指令必须包含目标、输出格式、工具指引、任务边界四要素,缺失任何一项都会导致 subagent 重复劳动或偏离主题。
设计沿三条正交轴:拓扑(supervisor 星形、hierarchical 树形、swarm 网状 handoff)、协调方式(中心调度还是去中心交接)、通信方式(共享 state 还是消息传递)。划分 Agent 的依据按优先级是:上下文隔离需求、领域职责、工具集、模型能力分层——隔离最重要,因为 subagent 独立上下文窗口能防止长流程互相污染。Anthropic 的研究系统验证了 orchestrator-worker 比单模型提升 90.2%,但 token 成本约 15 倍,所以子任务强耦合或步骤可枚举时退回单 Agent。
加分点
何时不用多 Agent:子任务之间强依赖、需要共享大量中间状态、或步骤本身可预枚举时,多 Agent 只增加 token 成本与协调失败面,正确做法是退回单 Agent + 工具,或固定 workflow。设计准则一句话:架构跟随任务结构——任务天然可并行分解才配得上多 Agent。
追问预判
subagent 之间需要共享大量中间结果,怎么办?
应对思路
首先把它识别为"不该拆"的信号——强共享意味着强耦合。确需拆分时,用外置共享介质(文件系统、对象存储、共享 state)存放中间产物,Agent 之间传引用而非全文,避免同一份内容在多个上下文窗口里重复计费。
supervisor 和 swarm 怎么选?
应对思路
任务能在入口处中心规划、结果需要统一汇总 → supervisor;该谁接手要到执行中才知道(客服路由、专家切换)→ swarm handoff。swarm 省去经由中心的一跳转发,但全局可观测性与失败回滚比中心调度难做。
15 倍 token 成本怎么压?
应对思路
模型分层(orchestrator 强模型、worker 便宜模型);限制 subagent 并发数与单任务步数上限;委派指令中明确输出格式与长度上限,禁止 worker 返回原始全文;对重复出现的子任务做结果缓存。
Q12LangChain 和 LangGraph 的区别?
对工具栈分层的理解——能否说清两者是"高层 API 与底层运行时"的关系,而非两个并列竞品。
参考答案
两者在 2025-10-22 同日发布 1.0 GA,分工是高层 API vs 低层运行时。LangChain 1.0 提供 create_agent + middleware 体系(HITL 人在回路、对话摘要、PII 脱敏等横切能力以中间件形式插入 agent loop);其 LCEL 链是静态的 RunnableSequence,执行顺序在构造期定死,无法表达回边。LangGraph 1.0 提供 StateGraph 运行时:图 = 节点 + 边 + 共享 state(TypedDict + reducer,reducer 即"合并函数",定义并发写入同一 key 时的合并协议);条件边支持分支与循环;checkpointer 在每个超步后按 thread_id 持久化 state 快照,由此支撑断点续跑、time-travel 调试、HITL interrupt(checkpointer 也是 01 章跨会话记忆的承载机制)。
| 维度 | LangChain 1.0 | LangGraph 1.0 |
|---|---|---|
| 定位 | 高层 API(快速路径) | 低层运行时(精控路径) |
| 核心抽象 | create_agent + middleware;LCEL RunnableSequence | StateGraph:节点 + 边 + 共享 state(TypedDict + reducer) |
| 控制流 | 链为静态序列,构造期定死,无回边 | 条件边支持分支与循环,Pregel 模型并发执行 |
| 持久化 | 由底层运行时提供 | checkpointer 按 thread_id 快照:断点续跑 / time-travel / HITL interrupt |
| 适用 | 标准 tool-calling agent,数行代码可用 | 自定义控制流、长时运行、多 Agent 子图编排 |
两者的关系:create_agent 底层就是 LangGraph runtime——LangChain 是快速路径、LangGraph 是精控路径,不是两条竞争路线。选型规则:标准 tool-calling agent → create_agent;自定义控制流、长时运行、需要持久化 → 直接写 LangGraph;复杂长程任务 → deepagents(2025 下半年发布,create_agent 之上的全装 harness:write_todos 任务规划、spawn subagent、虚拟文件系统、SKILL.md 技能装载)。
LangChain 1.0 是高层 API:create_agent 加 middleware,几行代码起一个标准 tool-calling agent;LCEL 链是静态序列,构造期定死、做不了循环。LangGraph 1.0 是低层运行时:StateGraph 用节点、条件边和带 reducer 的共享 state 表达任意控制流,checkpointer 按 thread_id 持久化,提供断点续跑、time-travel 和 HITL。关键是两者不是竞争关系——create_agent 底层就是 LangGraph runtime。选型:标准 agent 用 create_agent,自定义控制流或长时运行下沉到 LangGraph,复杂长程任务用 deepagents。
加分点
表达"分层"而非"对比"本身就是加分项:从 create_agent 迁到 LangGraph 是"下沉一层"而非重写,middleware 与 checkpointer 在两层之间通用。再补一句 deepagents 的定位(create_agent 之上的全装 harness),即展示出对 2025 下半年生态演进的跟踪。
追问预判
LCEL 链为什么做不了循环?
应对思路
RunnableSequence 在构造期把步骤编译为固定的有向无环结构,运行期数据只能顺序流过,没有"运行期决定下一步"的控制点。循环需要两样东西:条件边 + 可读写的共享 state,这正是 StateGraph 的核心能力,也是 LangGraph 存在的理由。
checkpointer 具体解决什么问题?
应对思路
每个超步结束后把整张图的 state 快照按 thread_id 写入存储。由这一个机制派生出四种能力:进程崩溃后断点续跑、time-travel 回放任意历史步、HITL interrupt(暂停等待人工批准后恢复)、跨会话记忆(同一 thread_id 续聊,呼应 01 章)。
什么时候从 create_agent 迁移到 LangGraph?
应对思路
出现三类信号即迁移:需要自定义控制流(分支、循环、并行扇出);需要长时运行与持久化(人工审批卡点、跨天任务);需要多 Agent 子图编排。迁移成本低于直觉——create_agent 本身跑在 LangGraph runtime 上,迁移是把隐式的图改写为显式的图。
本章自测
-
写出 FSM 五元组,并说明 Agent 工具调用循环中每一部分分别对应什么。
参考答案
Q 状态集合 = {planning, calling, observing, answering, done};Σ 事件集 = {need_tool, tool_result, need_more, enough, finish};δ 转移函数 = 图上的边(如 δ(observing, need_more) = calling);q₀ = planning;F = {done}。工程上另加条件边上的 guard(如"未达迭代上限")与转移动作(发起工具调用、写入观察结果)。
-
给出三种任务各自适配 Chain、单轮 Tool Use、ReAct loop 的判断依据。
参考答案
判断维度是"路径何时被决定":步骤可预枚举的确定流程(如固定的检索→改写→格式化)→ Chain,构造期定死、成本最低;单步明确查询(查天气、查汇率)→ 单轮 Tool Use,一跳完成;步数未知的开放式任务(多跳调研、调试修复)→ ReAct loop,运行期逐轮决策,配 max_iterations 兜底。
-
多 Agent 划分的四条依据是什么?哪条优先级最高,为什么?
参考答案
四条:上下文隔离需求、领域/职责、工具集、模型能力分层。上下文隔离优先级最高——subagent 独立上下文窗口让长流程的中间产物不污染主线,主 Agent 只接收浓缩结论;其余三条解决的是效率与成本,隔离解决的是正确性。
-
create_agent 与 StateGraph 是什么关系?各自的选型信号是什么?
参考答案
create_agent(LangChain 1.0)构建在 LangGraph runtime 之上,是同一栈的高层快速路径与低层精控路径,不是竞品。选 create_agent:标准 tool-calling agent + middleware 即可满足。选 StateGraph:需要自定义控制流(分支/循环/并行)、长时运行与 checkpointer 持久化、多 Agent 子图编排。更长程的任务再考虑 deepagents。
延伸阅读
- ReAct: Synergizing Reasoning and Acting in Language Models — Yao et al., 2022,ReAct 原始论文
- How we built our multi-agent research system — Anthropic 工程博客,orchestrator-worker 的第一手经验
- LangChain & LangGraph 1.0 — 官方发布说明,两者分工的权威表述
- deepagents — create_agent 之上的全装 harness
- 本库相关教程:langgraph 教程 · multi-agent-patterns 教程