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 为真才允许转移,用于表达"重试次数未超限才回到执行态"一类约束。

q0 planning calling observing answering done need_tool tool_result enough finish need_more · 条件边 五元组对应:Q = 全部节点 | Σ = 边上事件 | δ = 边 | q0 = 入口箭头 | F = done(双圈终态)
图 1Agent 工具调用循环建模为 FSM:observing 之后由条件边决定回到 calling 继续取证,还是进入 answering 收束。

Python 实现按复杂度分三档。第一档:dict 转移表,把 δ 直接写成 {(state, event): next_state} 的字典,配一个 step() 函数,状态与事件少时零依赖、一眼看全:

fsm_table.py · 第一档:dict 转移表Python
# δ: (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 一类任务编排:

task_machine.py · 第二档:Enum + match + guardPython
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)。

60 秒口头版本

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(持有 / 不持有两态 + 转移方程)与本题同构。

追问预判

追问 1

状态数量增长导致转移表爆炸,怎么办?

应对思路

引入层级状态机:子状态共享父状态的转移规则,公共逻辑(如"任意状态收到 cancel 都终止")只写一次;transitions 的 HierarchicalMachine 直接支持。Agent 场景的等价手段是把子流程下沉为独立子图(LangGraph subgraph),父图只看到子图的入口与出口。

追问 2

Mealy 和 Moore 在工程上怎么取舍?

应对思路

Mealy 动作挂在边上,同一状态的不同入边可执行不同副作用,表达力强但副作用散落各处;Moore 动作挂在状态入口,副作用集中、单测友好。工程实践常混用:入态做初始化与不变量检查(Moore),出边做与事件强相关的动作(Mealy)。

追问 3

什么时候从手写 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 兜底
Chain · 固定流水线 步骤 1 步骤 2 步骤 3 输出 单轮 Tool Use LLM 决策 tool call 工具执行 返回即结束 ReAct loop Thought Action Observation 未完成 完成/上限 路径:直线,构造期定死 路径:单跳,调完即止 路径:带环,运行期决策
图 2三种模式的路径形状:Chain 是直线,单轮 Tool Use 是单跳,ReAct 是带条件退出的环。

ReAct 的收益由"环"带来:动态决策(步数未知也能推进)、错误恢复(一条路失败后换工具、换查询重试)、可观测性(推理轨迹逐轮留痕,便于调试与审计)。代价同样由"环"带来:多轮交互的延迟、token 用量随轮数倍增、循环失控需要 max_iterations 一类硬上限兜底。

面试作答的正确顺序是先确认任务形状,再给结论:步骤可预枚举 → Chain 更便宜更稳;单步查询 → 一次 Tool Use 足够;开放式多步、步数未知 → 才轮到 ReAct loop。

60 秒口头版本

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 架构思想活着。

追问预判

追问 1

ReAct 循环不收敛、原地打转,怎么处理?

应对思路

三层防线:max_iterations 硬上限(LangGraph 对应 recursion_limit);把已尝试过的动作与结果写进上下文,避免重复同一查询;失败计数触发降级路径——换工具、直接基于已有信息作答、或升级到人工。条件边必须显式给出退出分支,这是图 1 中"条件边"的工程含义。

追问 2

现在还需要手写 Thought: / Action: 提示词吗?

应对思路

不需要。原生 tool calling 由 API 返回结构化的 tool_calls 字段,推理由模型内化;手写文本格式反而引入解析失败的脆弱点。保留的有效做法是要求模型在行动前输出一句简短 rationale——不参与解析,只服务可观测性。

追问 3

Chain 和 ReAct 能混合使用吗?

应对思路

能,且是生产系统的常态:外层用固定 workflow(检索 → agent → 校验 → 格式化)保证确定性,把 ReAct loop 收敛为其中一个节点。原则是"确定的部分用 Chain 省成本,开放的部分才交给 loop"。

Q6多 Agent 框架怎么设计?划分 Agent 的依据是什么?

面试官在考什么

系统设计能力:拆分 Agent 是否有明确依据,是否清楚多 Agent 的成本曲线与不适用的边界。

参考答案

多 Agent 设计沿三条正交轴展开,三轴独立选择、组合成最终架构:

  • 拓扑:supervisor(协调者)星形 / hierarchical 树形 / swarm 任意图 + handoff(交接);
  • 协调:中心调度(supervisor 统一分派与汇总)vs 去中心交接(执行中把控制权 handoff 给下一个 Agent);
  • 通信:共享 state vs 消息传递,并按上下文隔离档位决定向对方暴露多少中间态。
supervisor · 星形 supervisor W1 W2 W3 中心调度,worker 不互联 hierarchical · 树形 S S1 S2 W W W W 分层委派,每层只看直属下级 swarm · 网状 handoff A B C handoff 去中心交接,无统一调度
图 3三种拓扑:supervisor 星形中心调度;hierarchical 树形逐层委派;swarm 网状由当前 Agent 决定 handoff(交接)给谁。

划分 Agent 的依据按优先级排列为四条:

  1. 按上下文隔离需求(最重要):subagent 拥有独立上下文窗口,长流程的中间产物不污染主线,主 Agent 只接收浓缩结论;
  2. 按领域 / 职责:检索、写作、审校各成一职,单一职责降低提示词复杂度;
  3. 按工具集:每个 Agent 只挂自己用得到的工具,缩小决策空间、降低误调用率;
  4. 按模型能力分层:orchestrator 用强模型做规划与汇总,worker 用便宜模型跑量。

工程标杆是 Anthropic 的多 Agent 研究系统:orchestrator-worker 拓扑,在内部研究评测上比单 Opus 4 提升 90.2%,代价是约 15 倍 token 消耗。该系统给出的另一条硬经验:委派指令必须包含目标、输出格式、工具指引、任务边界四要素,缺失任何一项都会导致 subagent 重复劳动或偏离主题。

60 秒口头版本

设计沿三条正交轴:拓扑(supervisor 星形、hierarchical 树形、swarm 网状 handoff)、协调方式(中心调度还是去中心交接)、通信方式(共享 state 还是消息传递)。划分 Agent 的依据按优先级是:上下文隔离需求、领域职责、工具集、模型能力分层——隔离最重要,因为 subagent 独立上下文窗口能防止长流程互相污染。Anthropic 的研究系统验证了 orchestrator-worker 比单模型提升 90.2%,但 token 成本约 15 倍,所以子任务强耦合或步骤可枚举时退回单 Agent。

加分点

深一层

何时不用多 Agent:子任务之间强依赖、需要共享大量中间状态、或步骤本身可预枚举时,多 Agent 只增加 token 成本与协调失败面,正确做法是退回单 Agent + 工具,或固定 workflow。设计准则一句话:架构跟随任务结构——任务天然可并行分解才配得上多 Agent。

追问预判

追问 1

subagent 之间需要共享大量中间结果,怎么办?

应对思路

首先把它识别为"不该拆"的信号——强共享意味着强耦合。确需拆分时,用外置共享介质(文件系统、对象存储、共享 state)存放中间产物,Agent 之间传引用而非全文,避免同一份内容在多个上下文窗口里重复计费。

追问 2

supervisor 和 swarm 怎么选?

应对思路

任务能在入口处中心规划、结果需要统一汇总 → supervisor;该谁接手要到执行中才知道(客服路由、专家切换)→ swarm handoff。swarm 省去经由中心的一跳转发,但全局可观测性与失败回滚比中心调度难做。

追问 3

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 vs LangGraph 1.0
维度LangChain 1.0LangGraph 1.0
定位高层 API(快速路径)低层运行时(精控路径)
核心抽象create_agent + middleware;LCEL RunnableSequenceStateGraph:节点 + 边 + 共享 state(TypedDict + reducer)
控制流链为静态序列,构造期定死,无回边条件边支持分支与循环,Pregel 模型并发执行
持久化由底层运行时提供checkpointer 按 thread_id 快照:断点续跑 / time-travel / HITL interrupt
适用标准 tool-calling agent,数行代码可用自定义控制流、长时运行、多 Agent 子图编排
deepagents · 全装 harness write_todos · spawn subagent · 虚拟文件系统 · SKILL.md 构建于 LangChain 1.0 · create_agent + middleware 高层 API · 快速路径 · HITL / 摘要 / PII 脱敏 构建于 LangGraph 1.0 · StateGraph runtime 低层运行时 · 条件边 / 循环 · checkpointer · Pregel
图 4三层抽象栈:deepagents 构建于 create_agent 之上,create_agent 构建于 LangGraph runtime 之上。

两者的关系:create_agent 底层就是 LangGraph runtime——LangChain 是快速路径、LangGraph 是精控路径,不是两条竞争路线。选型规则:标准 tool-calling agent → create_agent;自定义控制流、长时运行、需要持久化 → 直接写 LangGraph;复杂长程任务 → deepagents(2025 下半年发布,create_agent 之上的全装 harness:write_todos 任务规划、spawn subagent、虚拟文件系统、SKILL.md 技能装载)。

60 秒口头版本

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 下半年生态演进的跟踪。

追问预判

追问 1

LCEL 链为什么做不了循环?

应对思路

RunnableSequence 在构造期把步骤编译为固定的有向无环结构,运行期数据只能顺序流过,没有"运行期决定下一步"的控制点。循环需要两样东西:条件边 + 可读写的共享 state,这正是 StateGraph 的核心能力,也是 LangGraph 存在的理由。

追问 2

checkpointer 具体解决什么问题?

应对思路

每个超步结束后把整张图的 state 快照按 thread_id 写入存储。由这一个机制派生出四种能力:进程崩溃后断点续跑、time-travel 回放任意历史步、HITL interrupt(暂停等待人工批准后恢复)、跨会话记忆(同一 thread_id 续聊,呼应 01 章)。

追问 3

什么时候从 create_agent 迁移到 LangGraph?

应对思路

出现三类信号即迁移:需要自定义控制流(分支、循环、并行扇出);需要长时运行与持久化(人工审批卡点、跨天任务);需要多 Agent 子图编排。迁移成本低于直觉——create_agent 本身跑在 LangGraph runtime 上,迁移是把隐式的图改写为显式的图。

本章自测

  1. 写出 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(如"未达迭代上限")与转移动作(发起工具调用、写入观察结果)。

  2. 给出三种任务各自适配 Chain、单轮 Tool Use、ReAct loop 的判断依据。
    参考答案

    判断维度是"路径何时被决定":步骤可预枚举的确定流程(如固定的检索→改写→格式化)→ Chain,构造期定死、成本最低;单步明确查询(查天气、查汇率)→ 单轮 Tool Use,一跳完成;步数未知的开放式任务(多跳调研、调试修复)→ ReAct loop,运行期逐轮决策,配 max_iterations 兜底。

  3. 多 Agent 划分的四条依据是什么?哪条优先级最高,为什么?
    参考答案

    四条:上下文隔离需求、领域/职责、工具集、模型能力分层。上下文隔离优先级最高——subagent 独立上下文窗口让长流程的中间产物不污染主线,主 Agent 只接收浓缩结论;其余三条解决的是效率与成本,隔离解决的是正确性。

  4. create_agent 与 StateGraph 是什么关系?各自的选型信号是什么?
    参考答案

    create_agent(LangChain 1.0)构建在 LangGraph runtime 之上,是同一栈的高层快速路径与低层精控路径,不是竞品。选 create_agent:标准 tool-calling agent + middleware 即可满足。选 StateGraph:需要自定义控制流(分支/循环/并行)、长时运行与 checkpointer 持久化、多 Agent 子图编排。更长程的任务再考虑 deepagents。

延伸阅读