前言大家好我是咪的Coding。今天我想给大家介绍状态机的思想。首先让我们来假设这样一个场景你正在开发一个智能助手 Agent。它一开始只需要处理简单的一问一答收到用户消息调用 LLM返回结果。用一个if-else就能跑起来看起来很美好。但很快迭代要求 Agent 能调用外部工具—— 查天气、发邮件、读写数据库。接着又需要多轮澄清用户说“帮我订张票”它得主动问“去哪什么时间”。再后来工具调用可能超时LLM 推理可能出错用户还可能中途取消……你的代码逐渐变成了这种样子publicvoidhandle(Agentagent,Eventevent){if(IDLE.equals(agent.getState())){if(eventinstanceofUserMessage){// 推理...}}elseif(THINKING.equals(agent.getState())){if(eventinstanceofReasoningDone){if(needTool){/* ... */}elseif(needClarify){/* ... */}else{/* ... */}}}elseif(TOOL_CALLING.equals(agent.getState())){if(eventinstanceofToolResult){// ...}elseif(eventinstanceofTimeout){// ...}}// 还有更多...}每次增加一个新行为整套逻辑就得大改一次测试时不时冒出“Agent 卡在思考中不动了”的诡异问题。根源在于状态流转被写死在了嵌套堆砌的条件分支里。其实这个问题有一个诞生于半个多世纪前的解决方案——状态机。用状态机重构 Agent 的控制逻辑可以让它既聪明又可控。一、什么是状态机这里我们介绍**“有限状态机”Finite State Machine, FSM。一句话介绍一个东西在任意时刻只能处于有限个状态中的一个当某个事件发生时它从当前状态转移到另一个状态同时可能执行一些动作**。即当前状态 遇到事件 → 下一个状态 执行动作对于 AI Agent它的生命周期天然适合用状态机来描述。二、状态机的四个要素一个状态机包含四个核心现态State—— 当前状态。比如 Agent 正在“思考中”。事件Event—— 触发转移的条件。比如 LLM 推理完成返回了结果。动作Action—— 事件引发的操作。比如判断是否需要调用工具、生成回复。次态Next State—— 转移后的新状态。比如进入“工具调用”或“生成回复”。画成状态转移图Agent 的行为就一目了然有了这张图Agent 的整个控制流就像地铁线路图一样清晰任何非法跳转比如从“空闲”直接进“调用工具”都会被一眼识别。三、状态机为什么能解决问题回头看开头那个“一团乱麻”的 Agent 控制器它的根本问题有三个状态与行为紧耦合非法转移靠运行时报错才发现增加新行为需要改动大量分支。这是一种以事件分支为中心的编程关注的是“发生了什么”。状态机则彻底扭转了思路——以状态为中心。每个状态只关心自己能响应哪些事件能跳转到哪些状态。这种内聚性带来了三大好处隔离变更新增“重试”或“超时处理”状态只需定义它自己的转移规则已有状态代码不受影响。编译期安全如果有人试图定义一个“从空闲直接跳转到回复完成”的非法转移状态机框架在设计阶段就能直接报错而不是等线上出现 “Agent 凭空说了一句话” 的灵异事件。业务对齐状态图直接映射产品文档中的 Agent 行为规范开发、测试、产品用同一张图沟通极大减少理解偏差。用状态机写 Agent就像给它装上了规则清晰的“轨道”既保留了其灵活性又避免了它跳脱出预定的边界。四、简单实现一个 Agent 状态机Java 的枚举天生适合实现有限状态机。我们可以把 Agent 的每个状态定义为一个枚举值并让它们自己决定“能往哪里走”。先定义事件enumAgentEvent{USER_MSG,REASONING_DONE,NEED_TOOL,NEED_CLARIFY,TOOL_RESULT,TIMEOUT,RESPONSE_SENT}核心是状态枚举——每个状态重写next方法把自己允许的转移规则写进去enumAgentState{IDLE{publicAgentStatenext(AgentEvente){if(eAgentEvent.USER_MSG)returnTHINKING;throwillegal(e);}},THINKING{publicAgentStatenext(AgentEvente){if(eAgentEvent.NEED_TOOL)returnTOOL_CALLING;if(eAgentEvent.NEED_CLARIFY)returnWAITING_USER;if(eAgentEvent.REASONING_DONE)returnRESPONDING;throwillegal(e);}},TOOL_CALLING{publicAgentStatenext(AgentEvente){if(eAgentEvent.TOOL_RESULT)returnTHINKING;// 回去继续推理if(eAgentEvent.TIMEOUT)returnERROR;throwillegal(e);}},WAITING_USER{publicAgentStatenext(AgentEvente){if(eAgentEvent.USER_MSG)returnTHINKING;throwillegal(e);}},RESPONDING{publicAgentStatenext(AgentEvente){if(eAgentEvent.RESPONSE_SENT)returnIDLE;throwillegal(e);}},ERROR{publicAgentStatenext(AgentEvente){if(eAgentEvent.USER_MSG)returnTHINKING;// 重新开始throwillegal(e);}};publicAgentStatenext(AgentEvente){thrownewIllegalStateException(非法事件: e);}privatestaticIllegalStateExceptionillegal(AgentEvente){returnnewIllegalStateException(当前状态不支持事件: e);}}使用时状态流转自然又安全AgentStatestateAgentState.IDLE;// 初始化状态为 IDLEstatestate.next(AgentEvent.USER_MSG);// → THINKINGstatestate.next(AgentEvent.NEED_TOOL);// → TOOL_CALLINGstatestate.next(AgentEvent.TOOL_RESULT);// → THINKING (二次推理)statestate.next(AgentEvent.REASONING_DONE);// → RESPONDINGstatestate.next(AgentEvent.RESPONSE_SENT);// → IDLE这段代码虽然精简却体现了状态机的核心每个状态都精确地定义了自己的“出口”任何不在规则内的路径都会立即报错。如果将来 Agent 需要支持“并行工具调用”或“中止任务”等新状态只需新增枚举值并实现其转移规则现有逻辑纹丝不动。这种模式如今已经在许多 AI Agent 框架中以更复杂的形式出现*比如 LangGraph 用状态图来编排 Agent 的决策流程。*但无论框架如何演变其底层思想始终如一。五、状态机的背后 —— 一种思维方式状态机之所以优雅是因为它把“在什么情况下该做什么”这个程序设计的核心问题用结构化的方式表达了出来。它不再是散落各处的 if-else 碎片而是一个有明确边界、可验证、可推理的系统。你可以在设计阶段画出状态图直观地检查有没有遗漏的状态或非法的转移路径 ——很多 Bug 在编码之前就被消灭了。这让我想到我在一开始学习时的感悟代码之所以难维护往往不是因为写得太少而是因为想得太少。在动手前先梳理业务中的状态和流转画出状态图这本身就是对领域理解的深化。你会发现无论是用 AI Agent 的决策引擎还是 Kubernetes Pod 的生命周期管理亦或是前端路由的守卫逻辑……凡是涉及“生命周期管理”的问题都能看到状态机的影子。它不单是一种设计模式更是一种分析复杂系统的思维。总结状态机不是什么高深的技术它只是一个被时间反复验证的、好用的编程思想。核心就一句话把系统拆成有限个状态定义清楚每个状态下响应什么事件、转移到什么状态。用状态机写出的代码路径清晰、非法转移被直接杜绝新增状态时改动可控不会牵一发而动全身。同时技术没有银弹。对于极简的、只有一两种状态切换的场景if-else 已经足够。但当你的 Agent 越来越智能行为越来越复杂时状态机将是帮你守住“行为边界”的最可靠哨兵。最后你也可以思考一下你正在开发的应用中是否存在靠无数 flag 和嵌套 if-else 驱动的“混乱决策”或许你也可以试一试把这些逻辑画成一张状态图发现潜伏其中的逻辑黑洞。试着用状态机的思维重构它 —— 然后你会惊讶地发现原来逻辑控制的决策可以变得如此清晰。感谢你看到这里如果喜欢咪的Coding的话可以点个关注支持一下吧欢迎各位在评论区留言
用状态机重写 AI Agent 后,几千行的 if-else 变成了一张状态逻辑图
发布时间:2026/5/19 6:57:17
前言大家好我是咪的Coding。今天我想给大家介绍状态机的思想。首先让我们来假设这样一个场景你正在开发一个智能助手 Agent。它一开始只需要处理简单的一问一答收到用户消息调用 LLM返回结果。用一个if-else就能跑起来看起来很美好。但很快迭代要求 Agent 能调用外部工具—— 查天气、发邮件、读写数据库。接着又需要多轮澄清用户说“帮我订张票”它得主动问“去哪什么时间”。再后来工具调用可能超时LLM 推理可能出错用户还可能中途取消……你的代码逐渐变成了这种样子publicvoidhandle(Agentagent,Eventevent){if(IDLE.equals(agent.getState())){if(eventinstanceofUserMessage){// 推理...}}elseif(THINKING.equals(agent.getState())){if(eventinstanceofReasoningDone){if(needTool){/* ... */}elseif(needClarify){/* ... */}else{/* ... */}}}elseif(TOOL_CALLING.equals(agent.getState())){if(eventinstanceofToolResult){// ...}elseif(eventinstanceofTimeout){// ...}}// 还有更多...}每次增加一个新行为整套逻辑就得大改一次测试时不时冒出“Agent 卡在思考中不动了”的诡异问题。根源在于状态流转被写死在了嵌套堆砌的条件分支里。其实这个问题有一个诞生于半个多世纪前的解决方案——状态机。用状态机重构 Agent 的控制逻辑可以让它既聪明又可控。一、什么是状态机这里我们介绍**“有限状态机”Finite State Machine, FSM。一句话介绍一个东西在任意时刻只能处于有限个状态中的一个当某个事件发生时它从当前状态转移到另一个状态同时可能执行一些动作**。即当前状态 遇到事件 → 下一个状态 执行动作对于 AI Agent它的生命周期天然适合用状态机来描述。二、状态机的四个要素一个状态机包含四个核心现态State—— 当前状态。比如 Agent 正在“思考中”。事件Event—— 触发转移的条件。比如 LLM 推理完成返回了结果。动作Action—— 事件引发的操作。比如判断是否需要调用工具、生成回复。次态Next State—— 转移后的新状态。比如进入“工具调用”或“生成回复”。画成状态转移图Agent 的行为就一目了然有了这张图Agent 的整个控制流就像地铁线路图一样清晰任何非法跳转比如从“空闲”直接进“调用工具”都会被一眼识别。三、状态机为什么能解决问题回头看开头那个“一团乱麻”的 Agent 控制器它的根本问题有三个状态与行为紧耦合非法转移靠运行时报错才发现增加新行为需要改动大量分支。这是一种以事件分支为中心的编程关注的是“发生了什么”。状态机则彻底扭转了思路——以状态为中心。每个状态只关心自己能响应哪些事件能跳转到哪些状态。这种内聚性带来了三大好处隔离变更新增“重试”或“超时处理”状态只需定义它自己的转移规则已有状态代码不受影响。编译期安全如果有人试图定义一个“从空闲直接跳转到回复完成”的非法转移状态机框架在设计阶段就能直接报错而不是等线上出现 “Agent 凭空说了一句话” 的灵异事件。业务对齐状态图直接映射产品文档中的 Agent 行为规范开发、测试、产品用同一张图沟通极大减少理解偏差。用状态机写 Agent就像给它装上了规则清晰的“轨道”既保留了其灵活性又避免了它跳脱出预定的边界。四、简单实现一个 Agent 状态机Java 的枚举天生适合实现有限状态机。我们可以把 Agent 的每个状态定义为一个枚举值并让它们自己决定“能往哪里走”。先定义事件enumAgentEvent{USER_MSG,REASONING_DONE,NEED_TOOL,NEED_CLARIFY,TOOL_RESULT,TIMEOUT,RESPONSE_SENT}核心是状态枚举——每个状态重写next方法把自己允许的转移规则写进去enumAgentState{IDLE{publicAgentStatenext(AgentEvente){if(eAgentEvent.USER_MSG)returnTHINKING;throwillegal(e);}},THINKING{publicAgentStatenext(AgentEvente){if(eAgentEvent.NEED_TOOL)returnTOOL_CALLING;if(eAgentEvent.NEED_CLARIFY)returnWAITING_USER;if(eAgentEvent.REASONING_DONE)returnRESPONDING;throwillegal(e);}},TOOL_CALLING{publicAgentStatenext(AgentEvente){if(eAgentEvent.TOOL_RESULT)returnTHINKING;// 回去继续推理if(eAgentEvent.TIMEOUT)returnERROR;throwillegal(e);}},WAITING_USER{publicAgentStatenext(AgentEvente){if(eAgentEvent.USER_MSG)returnTHINKING;throwillegal(e);}},RESPONDING{publicAgentStatenext(AgentEvente){if(eAgentEvent.RESPONSE_SENT)returnIDLE;throwillegal(e);}},ERROR{publicAgentStatenext(AgentEvente){if(eAgentEvent.USER_MSG)returnTHINKING;// 重新开始throwillegal(e);}};publicAgentStatenext(AgentEvente){thrownewIllegalStateException(非法事件: e);}privatestaticIllegalStateExceptionillegal(AgentEvente){returnnewIllegalStateException(当前状态不支持事件: e);}}使用时状态流转自然又安全AgentStatestateAgentState.IDLE;// 初始化状态为 IDLEstatestate.next(AgentEvent.USER_MSG);// → THINKINGstatestate.next(AgentEvent.NEED_TOOL);// → TOOL_CALLINGstatestate.next(AgentEvent.TOOL_RESULT);// → THINKING (二次推理)statestate.next(AgentEvent.REASONING_DONE);// → RESPONDINGstatestate.next(AgentEvent.RESPONSE_SENT);// → IDLE这段代码虽然精简却体现了状态机的核心每个状态都精确地定义了自己的“出口”任何不在规则内的路径都会立即报错。如果将来 Agent 需要支持“并行工具调用”或“中止任务”等新状态只需新增枚举值并实现其转移规则现有逻辑纹丝不动。这种模式如今已经在许多 AI Agent 框架中以更复杂的形式出现*比如 LangGraph 用状态图来编排 Agent 的决策流程。*但无论框架如何演变其底层思想始终如一。五、状态机的背后 —— 一种思维方式状态机之所以优雅是因为它把“在什么情况下该做什么”这个程序设计的核心问题用结构化的方式表达了出来。它不再是散落各处的 if-else 碎片而是一个有明确边界、可验证、可推理的系统。你可以在设计阶段画出状态图直观地检查有没有遗漏的状态或非法的转移路径 ——很多 Bug 在编码之前就被消灭了。这让我想到我在一开始学习时的感悟代码之所以难维护往往不是因为写得太少而是因为想得太少。在动手前先梳理业务中的状态和流转画出状态图这本身就是对领域理解的深化。你会发现无论是用 AI Agent 的决策引擎还是 Kubernetes Pod 的生命周期管理亦或是前端路由的守卫逻辑……凡是涉及“生命周期管理”的问题都能看到状态机的影子。它不单是一种设计模式更是一种分析复杂系统的思维。总结状态机不是什么高深的技术它只是一个被时间反复验证的、好用的编程思想。核心就一句话把系统拆成有限个状态定义清楚每个状态下响应什么事件、转移到什么状态。用状态机写出的代码路径清晰、非法转移被直接杜绝新增状态时改动可控不会牵一发而动全身。同时技术没有银弹。对于极简的、只有一两种状态切换的场景if-else 已经足够。但当你的 Agent 越来越智能行为越来越复杂时状态机将是帮你守住“行为边界”的最可靠哨兵。最后你也可以思考一下你正在开发的应用中是否存在靠无数 flag 和嵌套 if-else 驱动的“混乱决策”或许你也可以试一试把这些逻辑画成一张状态图发现潜伏其中的逻辑黑洞。试着用状态机的思维重构它 —— 然后你会惊讶地发现原来逻辑控制的决策可以变得如此清晰。感谢你看到这里如果喜欢咪的Coding的话可以点个关注支持一下吧欢迎各位在评论区留言