用 MDP 思维设计 AI Agent:ReAct 与必须回答的四个问题

Cabinet, Johann Georg Hainz / Baumgartner workshop, late 17th century
17 世纪末的一座"奇物柜"(Wunderkammer)—— 一百个小抽屉,每个抽屉里躺着一件功能截然不同的器物:镊子、放大镜、天平、星盘、骨针。使用者面对一项任务时的第一个动作不是"干活",而是"打开哪一个抽屉"。AI Agent 的工具调用在形式上几乎一模一样:一个离散动作空间、一个任务目标、一套关于"每件工具大概能做成什么事"的概率认知。区别只在于,今天做决策的是 LLM,而不是 17 世纪的那位学者
这篇文章的价值上一篇讲了"状态机只是概率状态机的退化特例",并用 MDP 重新建模了重试、熔断、缓存。这篇把同一个视角推到 2026 年最热的事情上:AI Agent。核心论点是——ReAct 不是一种"提示词技巧",它是一个跑在 MDP 上的最简策略。理解这个映射之后,设计 Agent 就有了一份非常朴素的核查清单:在动手写任何 prompt 和 tool 之前,先把四个问题答清楚——状态是什么?动作空间是什么?转移概率怎么处理?奖励函数怎么定义?这四个问题答不清楚,Agent 上线后遇到的所有问题——无限循环、调错工具、上下文爆炸、无法收敛——都不是"再调一调 prompt"能解决的。

一、ReAct 的 30 秒速览

先把 ReAct(Reason + Act)简化到最小:

while not done:
    thought     = LLM("你现在知道什么?下一步应该做什么?", context)
    action      = LLM("基于上面的思考,调用哪个工具?参数是什么?", context)
    observation = execute(action)          # tool call / API / user
    context.append(thought, action, observation)
return LLM("总结给用户", context)

就三步一循环:想 → 做 → 看结果 → 再想。所有当代 agent 框架(LangGraph、AutoGen、Claude Code、Cursor Agent)在核心循环上都是 ReAct 的一种变体,区别只在于细节:上下文怎么管理、工具怎么组织、循环怎么终止。

这个循环简单到让人误以为 ReAct 是一种 "prompt engineering pattern"。但如果你把它画出来:


   ┌──────────────────── 用户目标 g ───────────────────┐
   │                                                    │
   ▼                                                    │
  ┌─────────┐    think    ┌─────────┐    act    ┌────────────┐
  │  状态 s │ ──────────► │ 思考 τ  │ ───────►  │ 动作 a     │
  │ (上下文) │              │         │           │ (tool call)│
  └─────────┘              └─────────┘           └─────┬──────┘
       ▲                                                │
       │                                                │ execute
       │        observation o (tool 结果)              │
       └────────────────────────────────────────────────┘
                      把 o 加入上下文,循环

这张图骨架就是一个 MDP 的执行轨迹。思考 τ 只是动作 a 的"提案生成器",真正改变世界的是 a 和它带来的 observation。

二、ReAct 是 MDP 上的一种策略

把 ReAct 形式化为 MDP ⟨S, A, P, R, γ⟩

MDP 元素ReAct Agent 中的对应物
状态 S到目前为止的完整上下文:用户目标、思考历史、动作历史、观测历史、外部记忆引用
动作 A所有可调用工具 × 所有合法参数 + 特殊动作 finalize / ask_user / give_up
转移概率 PLLM 采样 + 工具执行的联合分布——执行 a 之后,observation 服从 P(o | s, a)
奖励 R任务完成度 − 成本(token、tool 调用数、延迟)± 过程奖励(单步工具是否成功)
策略 π就是 LLM 本身:π(a | s) = LLM(a | prompt(s))

这里最关键的一句话是:ReAct 是一种"贪心一步前瞻"的策略。它在每一步只看当前状态 s,让 LLM 挑一个看起来最好的动作 a,不做任何向前搜索。这对应于 MDP 里的 π(s) = argmax_a Q(s, a),而且 Q 函数还是 LLM 隐式估计的,既不保证准确也不保证稳定。

后面我们会看到,Tree of Thoughts、Reflexion、MCTS-based planning 等"更强"的 agent 技术,本质上就是在 ReAct 之上引入多步前瞻价值函数学习——它们在 MDP 语言里都不是新东西。

现在进入正题:用 MDP 视角设计 Agent,必须回答的四个问题。

三、问题 1 — 状态是什么?

Agent 项目最常在这里翻车。很多团队的默认答案是:"状态就是所有历史消息拼起来"。这在 demo 里可以工作,但在真实任务下会立刻暴露问题:

正确的做法是把状态设计成一个显式的数据结构,而不是"消息数组"。工程上常见的几种状态形态:

形态内容适合场景风险
原始 transcript所有 think/action/observation 按时间拼接短任务、demo上下文爆炸、信号稀释
滑窗 + 摘要最近 N 轮原文 + 历史摘要中等长度对话摘要丢关键信息
结构化 workspacetask、subgoals、scratchpad、findings 各占一个字段长任务、code agent设计 schema 本身有成本
外置记忆 + 检索把历史写进向量库/KV 存储,每步只检索相关片段超长对话、知识任务检索失败 = 决策失败

一个被反复验证的经验法则:"状态应当恰好包含做下一步决策所需的所有信息,且不包含更多"。这其实就是 MDP 的 Markov 性质——在 s 给定之后,未来不再依赖更早的历史。如果你发现 LLM 在上下文里翻找 10 轮之前的信息,说明你的"状态"没有把那条信息沉淀到正确的位置。

另一个常被忽略的事实:ReAct 场景严格来说是 POMDP 而不是 MDP。因为 Agent 并不能直接观测到"真实世界",只能通过 tool 的 observation 间接窥探。比如你问 agent "PR 合并了吗?"——它看不到 GitHub 的真实状态,只能看到 gh pr view 的返回。工程实践中,我们把上下文当成"信念状态"(belief state),当成 MDP 来处理——这是一种可接受的近似。

四、问题 2 — 动作空间是什么?

动作空间就是 agent 可以做的所有事:工具调用 + 特殊动作 + 参数组合。这是 agent 设计里最容易失控的一个维度。

典型的失控模式有三种:

  1. 工具爆炸:为了"保险"塞进 50 个工具。LLM 在 prompt 里读完所有工具描述就已经消耗了几千 token,且容易在相似工具间混淆(search_file vs find_file vs grep?)
  2. 参数泛滥:一个工具有 15 个可选参数。LLM 需要记住每个参数的语义,填错参数的概率随数量指数上升
  3. 没有终止动作:agent 不知道"我现在该收尾了",于是一直 loop 下去,直到被超时或 token limit 强杀

MDP 视角下的设计原则非常朴素:

  好的动作空间                          坏的动作空间
  ─────────────────                     ─────────────────
  · 正交(工具之间语义无重叠)          · 相似工具一大堆
  · 粒度一致(都是同一抽象层)          · 有的超细(read_line)
  · 参数少且必填                        · 有的超粗(do_everything)
  · 显式的 finish 动作                  · 无终止信号
  · 显式的 ask_user 动作                · 只能瞎猜或硬答
  · 每个工具都有幂等/回滚策略           · 副作用不清晰

几条实操建议:

五、问题 3 — 转移概率是什么?(错误处理)

这个问题的潜台词是:你执行 a 之后,世界真的会按你预期变化吗?

在确定性世界里,action a → observation o 是函数关系。在真实世界里,它是一个分布:

  调用一个 "search_github_issues" 动作:

                   ┌── (75%) 返回相关 issue 列表     ← happy path
                   │
                   ├── (10%) rate limit → 429        ← 需要重试
  execute(a)  ────►│
                   ├── (8%)  返回空结果              ← 查询不对?需要改 query
                   │
                   ├── (5%)  网络超时                ← 可能重试成功
                   │
                   └── (2%)  返回看似正确但过时的结果 ← 最危险!

第四类(返回看似正确但错误的结果)是最恶劣的——agent 没法通过状态判断自己被骗了,会基于错误信息继续推理。这也是 LLM agent 比传统程序更难调试的根本原因。

从 MDP 视角,这里你要做三件事:

1. 显式建模每个工具的失败分布

每个工具不只是返回"成功 or 失败",而是一个多峰分布。设计工具签名时,把各种"失败类型"显式表达在返回值里,而不是吞到 exception 或者笼统的错误字符串里。让 LLM 能看懂"这次是 rate limit,我该等会再试"vs"这次是参数错误,我该改 query"。

2. 在 agent 循环里实现四种错误恢复动作

恢复动作适用情况MDP 解释
retry临时性错误(429、网络超时)同一 a,期望下一次 P(o|s,a) 采样到 happy path
fallback工具不可用,但有备选换一个动作 a',期望 E[R | s, a'] 虽低但可行
ask_useragent 不确定下一步用 user 的观测填充 belief state
abort累计成本已超过任务收益提前终止,避免 Σγ^t·R_t 继续变负

3. 检测并打破循环

ReAct 最常见的故障模式是动作循环:agent 反复调用同一组工具、拿到同样的结果、再做同样的决策。在 MDP 语言里这是策略 π 陷入了一个低价值的自环——每步奖励接近 0,但又没有任何状态上的突破。实操中的解法:

另一个容易被忽略的问题:动作的幂等性。如果 agent 可能重试,它调用的工具必须是幂等的(或者你得在外层做去重)。一个典型 bug:agent 调了 send_email,工具超时重试,结果用户收到了两封同样的邮件。这不是模型的锅,这是你在设计动作空间时没把 Markov 转移和副作用分清楚。

六、问题 4 — 奖励函数怎么设计?

这是四个问题里最少被明确回答的一个。大部分 agent 项目不写奖励函数——他们写的是"prompt 要求 LLM 做对",然后希望 LLM 自己知道什么叫"做对"。

但奖励函数无处不在,只是你没把它形式化:

把它显式写出来就变成:


  R(trajectory) =
        α_correct  · TaskCorrectness(final_output, ground_truth)   ← 终末:对不对
      + α_helpful  · UserThumbsUp                                   ← 终末:用户满意吗
      − α_tokens   · TokensUsed                                     ← 过程:贵不贵
      − α_calls    · ToolCallsCount                                 ← 过程:折腾不折腾
      − α_latency  · WallClockTime                                  ← 过程:快不快
      − α_danger   · IrreversibleSideEffects                        ← 过程:搞坏东西没
      + α_progress · PartialProgressSignal                          ← shaping:中间进度

这不是要让你把所有 agent 都跑 RL——而是要你把评价函数写清楚。一旦写清楚:

  1. 你立刻能量化不同 prompt / 不同工具组合的效果差异
  2. 你能做真正的 A/B 评估,而不是靠"感觉模型变聪明了"
  3. 如果将来要做 RLAIF / DPO / Reflexion,这就是现成的 reward signal
  4. LLM-as-judge 也需要一个明确的打分维度,奖励函数就是它的 rubric

奖励设计的两个经典陷阱:

陷阱 1:奖励太稀疏

只有任务做对(+100)和做错(−100)两种反馈,中间全是零。Agent 面对长链条任务完全不知道自己做得好不好。对策:加入过程奖励(process reward),比如"每次成功调用工具 +1"、"每推进一个 subgoal +5"。但过程奖励本身也可能被利用——就到了陷阱 2。

陷阱 2:Reward Hacking

Agent 学到"只要多调几次工具就能拿过程奖励",于是疯狂调用但不真正解决问题。这和 RL 里 CoastRunners 论文里的经典例子(船不跑完赛道,就原地转圈刷分)是同一回事。对策:

七、ReAct 只是最简单的策略——MDP 视角下的进阶技术

一旦把 Agent 写成 MDP,许多"看起来是新东西"的 agent 技术就都能归位:

技术MDP 视角下它在做什么
ReAct贪心一步前瞻策略:argmax_a Q(s,a),Q 由 LLM 估计
Tree of Thoughts多步前瞻搜索:展开若干候选轨迹,取 V 最大的那条
MCTS + LLM在大动作空间上用 UCB 采样,用 LLM 当 rollout policy 和 value network
Reflexion执行完一整条轨迹后用 LLM 自我评价 → 生成"经验"注入下次 prompt。本质是在线 reward shaping + memory-based 策略改进
RLHF / RLAIF从偏好数据里学一个显式的 reward model,然后用 PPO 优化策略 π
DPO跳过 reward model 直接用偏好数据端到端更新策略
Multi-AgentMulti-agent MDP / Markov Game:每个 agent 的最优动作取决于其他 agent 的策略
Agent with memory把 POMDP 的 belief state 显式化存储,跨会话维持

换句话说:agent 领域目前的绝大多数"创新",都能映射到强化学习过去三十年发展出来的一组经典工具。不是说这些技术不值得做——而是你能用一个统一的语言去比较它们,判断什么时候需要什么,而不是被每一篇 arxiv 新标题牵着走。

八、四个问题的核查清单

在动手写 agent 代码之前,请按这份清单逐项作答。答不出来就先别动 prompt。

  ┌─────────────────────────────────────────────────────────────┐
  │ Q1. 状态是什么?                                              │
  │  □ 状态用什么数据结构表示(transcript? workspace? memory?)  │
  │  □ 每步决策需要的信息是否都在状态里?                         │
  │  □ 是否有无关信息污染状态?                                   │
  │  □ 长任务下状态如何压缩?谁负责压缩?                         │
  │                                                               │
  │ Q2. 动作空间是什么?                                          │
  │  □ 列出所有工具,有没有语义重叠?                             │
  │  □ 每个工具的参数是否最小化?                                 │
  │  □ 有没有显式的 finalize / ask_user / give_up?               │
  │  □ 有没有工具带副作用?是否幂等?                             │
  │                                                               │
  │ Q3. 转移概率 = 错误处理怎么做?                               │
  │  □ 每个工具的失败模式有哪些?各自概率大致多少?               │
  │  □ 针对临时错误、永久错误、错误但看似正确,各有什么策略?     │
  │  □ 有没有循环检测?超过多少步 abort?                         │
  │  □ 写动作是否可重入?                                         │
  │                                                               │
  │ Q4. 奖励函数是什么?                                          │
  │  □ 终末奖励怎么定义?怎么量化?                               │
  │  □ 有没有过程奖励?和终末奖励是否对齐?                       │
  │  □ 成本项是否显式(token / tool call / latency)?            │
  │  □ 是否有 eval set 能持续衡量奖励函数本身?                   │
  └─────────────────────────────────────────────────────────────┘

九、总结

上一篇到这一篇,我们把同一个观点推到了两个尺度:

ReAct 的优雅在于它把"在概率世界里做决策"这件事压缩成了三行伪代码:想一下 / 做一步 / 看结果。它的简陋也在这里——它是一个短视的、贪心的、Q 函数极其不稳定的策略。

要让 Agent 变得可靠、可控、可优化,你真正要做的不是堆更多 prompt 技巧,而是把那四个问题答清楚:状态、动作、转移、奖励。答清楚之后你会发现,prompt 是其中最不重要的一部分——它只是策略网络的一种表达方式,而策略网络只是 MDP 的其中一个组件。

在 LLM 让软件从"函数"变成"分布"的这一刻,MDP 是少数几个仍然成立的思考框架之一。它不是在提供新的答案,而是在提供一套把问题问对的语言。