跳到主要内容

在写第一个 Prompt 之前,先设计好你的 Agent 状态机

· 阅读需 11 分钟
Tian Pan
Software Engineer

大多数工程师在构建第一个 LLM agent 时,都会遵循相同的流程:写一个系统提示词,添加一个调用模型的循环,撒上一些工具调用逻辑,然后看着它在简单的测试用例上运行。六周后,这个 agent 变成了一团难以理解的嵌套条件、粘贴在 f-string 里的 prompt 片段,以及散落在三个文件中的重试逻辑。添加一个功能需要通读整个代码。遇到生产 bug 就得盯着一个上千 token 的上下文窗口,试图重建模型当时在"想"什么。

这就是"意大利面式 agent"问题,在以 prompt 为起点而非设计为起点的团队中几乎普遍存在。解决方案不是更好的提示技巧,也不是换一个框架,而是一种纪律:在写第一个 prompt 之前,先设计好状态机

为什么 Prompt 优先的构建方式在规模化时总会失败

Prompt 优先开发的吸引力是真实存在的。你可以在一小时内让系统跑起来。模型能处理那些显式编码需要数周时间的歧义。感觉就像直接跳到了有趣的部分。

但 prompt 优先的 agent 携带着快速积累的结构性债务。没有显式状态,agent 的行为被隐式编码在 prompt、上下文历史以及模型调用周围散落的条件逻辑中。"agent 现在处于什么状态?"这个问题没有权威答案,因为这个概念在你的系统中根本不存在——只有一个不断增长的对话历史 blob,模型每次运行时对它的解读都不同。

一项分析了七个主要框架中超过 1,600 条标注 agent 执行轨迹的研究发现,42% 的失败可以追溯到系统设计问题:规范不完整、agent 被赋予了模糊的需求、没有清晰地定义"完成"意味着什么。另有 37% 是多 agent 场景下的协调失败,agent 在不同的共享状态心智模型下运行,并悄悄覆盖彼此的工作。这些不是模型失败,而是架构失败——而且通过显式状态设计完全可以预防。

状态机能给你什么

agent 的状态机在概念上很简单:一组有限的命名状态、状态之间的定义转换,以及触发每个转换的显式条件。它对 LLM agent 的强大之处在于,它将所有你本来会烘焙进 prompt 的隐式决策外部化了。

考虑一个客服 agent。没有显式状态,你会有一个系统提示词,大意是"你是一个有帮助的支持 agent,帮助用户解决问题,必要时进行升级"。"必要时"这个词承担了巨大的工作量。有了状态机,你会有:

  • greeting — 从用户那里收集问题陈述
  • classifying — LLM 确定问题类别,路由到适当的解决路径
  • resolving — LLM 提出解决方案,查询知识库
  • confirming — 用户确认解决方案是否有效
  • escalating — 触发人工交接,并附上完整上下文
  • closed — 终止状态,记录结果

每个状态都有明确的目的、明确的输入和明确的退出条件。LLM 是特定状态的组件——而不是所有行为的全局协调者。当出现问题时,你能精确知道它处于哪个状态、收到了什么输入、以及哪个转换触发错误了。

这不仅仅是理论上的整洁。它改变了在操作层面什么是可能的:你可以记录每个状态转换。你可以从保存的检查点重放任何执行。你可以编写单元测试,断言"在状态 X 给定这个输入,agent 应该转换到状态 Y"。当 agent 的逻辑主要存在于 prompt 内部时,这些都是不可行的。

在接触 LLM 之前设计拓扑结构

实际操作过程是:将 agent 设计视为与设计数据库 schema 或 API 合同一样的一等工件——在写任何代码之前进行审查。

从映射快乐路径开始。成功完成任务有哪些离散阶段?给每个阶段起一个对阅读日志文件的人有意义的名字,而不是对 LLM 有意义的名字。然后映射失败边——不是作为异常处理器,而是作为一等状态。retrying_tool_call 状态比 try/except 块更好。waiting_for_human_review 状态比塞在条件语句里的升级 prompt 更好。

对于每个状态,明确回答三个问题:

进入这个状态时,agent 知道什么? 这定义了你的状态 schema——携带跨转换上下文的类型化数据结构。LangGraph 鼓励使用带有标注 reducer 函数的 TypedDict;具体工具不重要,但定义哪些数据流经其中的纪律至关重要。状态 schema 迫使你决定哪些信息是真正需要的,哪些只是"在对话历史的某个地方"。

agent 在这个状态下可以采取哪些行动? 这约束了 LLM 可用的工具调用。处于 classifying 状态的 agent 可能不应该能够发起退款。按状态限制可用工具消除了整类幻觉工具调用——这是长视野任务中最常见的失败模式之一。

从这个状态出去的有效转换是什么,是什么触发了它们? 这是你将 LLM 调用映射到拓扑结构的地方。模型的输出——一个分类、一个是/否判断、一个结构化决策——是触发转换的事件。模型不决定下一步去哪里;状态机根据模型返回的内容来决定。这是一个微妙但至关重要的反转。LLM 提供推理;状态机提供控制流。

将 LLM 调用映射到节点

一旦有了拓扑结构,每个 LLM 交互就映射到一个特定节点。节点内的规范循环看起来是:读取当前状态,从状态数据构建 prompt,调用模型,解析输出,更新状态 schema,发出转换事件。就这些。没有嵌套的 if-else 逻辑。没有说"如果你认为用户感到沮丧,那么……"的 prompt。挫折感检测是它自己的状态,由它自己的转换条件触发。

这种映射纪律通过构建解决了上下文窗口问题。每个节点只需要与其功能相关的状态切片。分类节点不需要完整的对话历史——它需要问题陈述和类别分类法。确实需要历史的节点可以从状态 schema 中读取它,在那里它被显式维护,而不是作为无差别的 token 质量积累。

模型上下文协议(MCP)现已被所有主要 AI 提供商采用,它以一种强化这种架构的方式标准化了工具连接。工具是附加到特定执行上下文的离散能力,而不是注入每个 prompt 的全局能力列表。当你的状态机显式地为每个状态控制可用工具时,MCP 集成变得更简洁,审计追踪也变得可处理。

测试回报

状态机最重要的原因不是可调试性或可观测性——那些是下游收益。核心原因是它们使你的 agent 以有意义的方式可测试。

在单元级别测试 prompt 优先的 agent 实际上是不可能的。LLM 输出的非确定性,加上与 prompt 文本纠缠在一起的逻辑,意味着你只能进行端到端运行并希望它们通过。有了显式状态机,你可以将控制流层与 LLM 层分开测试。给定一个模拟"分类返回:billing_issue"的 LLM 响应,状态机是否正确转换到 resolving_billing 状态?这个测试是确定性的、快速的,不需要 API 调用。

对于 LLM 节点本身,你可以使用检查点来用不同的模型版本或 prompt 变体重放特定状态。LangGraph 的时间旅行功能直接支持这一点——你在每次节点执行后保存检查点,并可以从任何先前的检查点分叉执行。这将"我的 prompt 更改破坏了什么吗?"这个问题从三小时的端到端评估运行转变为针对保存检查点的有针对性的状态级回归测试。

采用显式状态机设计的团队报告了调试 bug 方式的质变。当 agent 失败时,第一个问题是"它处于哪个状态?"而不是"它说了什么?"状态是日志中的事实。LLM 输出是证据。这种反转——将状态机视为发生了什么的权威来源——是将被调试的 agent 与被重写的 agent 区分开来的关键。

人在回路需要状态机才能安全工作

状态机最有力的实际论据之一是人在回路(HITL)支持。随着 agent 进入更高风险的工作流——代码部署、金融交易、客户通信——暂停执行并请求人工批准的能力变得不可或缺。

暂停 prompt 优先的 agent 几乎不可能安全地做到。"状态"是对话历史,这意味着暂停就是序列化一个 token 流,然后在没有上下文丢失或 prompt 注入风险的情况下稍后恢复它。实际上,团队最终构建了复制 agent 逻辑的独立审批工作流,这立即造成了漂移。

有了显式状态机,中断点是一等概念。你定义特定状态,这些状态在向前转换之前需要人工批准——例如 awaiting_deployment_approval。状态 schema 携带人工审核员做出决定所需的一切。批准后,机器转换;拒绝后,它路由到替代路径或终止错误状态。agent 可以被序列化、持久化,并在数天后恢复,不会丢失任何执行上下文,因为上下文就是状态,而不是对话。

OpenAI Agents SDK 和 LangGraph Platform 都在 2025 年初发布了原生 HITL 支持,两者在概念层面的设计是相同的:中断发生在状态机边界,而不是在 LLM 调用内部。

失败边是特性,不是异常

最后一个纪律是以与快乐路径相同的设计严谨性对待失败路径。构建 agent 时的直觉是先写成功流程,然后将错误作为稍后处理的边缘情况。状态机设计迫使你显式地设计错误。

retrying_after_rate_limit 状态不同于 retrying_after_tool_failure 状态,后者又不同于 recovering_from_partial_state 状态。每个状态都有不同的重试语义、不同的日志记录要求和不同的人工升级阈值。当你将所有这些编码为 catch 块内的条件逻辑时,你无法单独监控或隔离测试它们。当它们是状态时,你可以。

MAST 失败分类法发现,21% 的 agent 失败可追溯到任务验证差距——agent 在没有检测到自身失败的情况下错误地完成了任务。解决这一问题的状态机模式是在任何终止状态之前设置验证门:agent 必须在到达 completed 之前通过显式的 verifying_output 状态转换。如果验证失败,它会路由回早期状态,而不是静默地返回错误答案。这种模式在 prompt 优先的 agent 中几乎不可能一致地实现,因为"成功"条件是用自然语言在系统提示词中定义的,而不是作为控制流中可机器检查的条件。

正确的操作顺序

这个纪律,简单地说:在打开代码编辑器之前画出状态机。命名每个状态。定义每个转换。指定状态 schema 中的内容。标记失败边和人在回路中断点。

对于一个中等复杂的 agent,这大约需要一个小时。另一个选择——从 prompt 开始,发布到生产,然后在第一次生产事故后试图从意大利面式逻辑中逆向工程出状态机——需要更长时间,结果也更差。

框架已经赶上了这种方法。LangGraph 1.0、OpenAI 的 Agents SDK、AWS Step Functions 的新 AI 特定状态类型,以及 CrewAI 的 Flows 界面都以显式状态机设计为中心。生态系统出于充分理由已经汇聚到这种架构:这是唯一能产生可测试、可观测、可恢复到足以在生产中无需持续监督运行的 agent 的模式。问题是你是在第一次生产事故之前还是之后采用它。

先设计拓扑结构。Prompt 自然会随之而来。

References:Let's stay in touch and Follow me for more thoughts and updates