前言

大家好,我是咪的Coding

今天我想给大家介绍状态机的思想。首先让我们来假设这样一个场景:

你正在开发一个智能助手 Agent。它一开始只需要处理简单的一问一答:收到用户消息,调用 LLM,返回结果。用一个 if-else 就能跑起来,看起来很美好。

但很快,迭代要求 Agent 能调用外部工具 —— 查天气、发邮件、读写数据库。接着又需要多轮澄清:用户说“帮我订张票”,它得主动问“去哪?什么时间?”。再后来,工具调用可能超时,LLM 推理可能出错,用户还可能中途取消……你的代码逐渐变成了这种样子:

public void handle(Agent agent, Event event) {
    if ("IDLE".equals(agent.getState())) {
        if (event instanceof UserMessage) {
            // 推理...
        }
    } else if ("THINKING".equals(agent.getState())) {
        if (event instanceof ReasoningDone) {
            if (needTool) { /* ... */ }
            else if (needClarify) { /* ... */ }
            else { /* ... */ }
        }
    } else if ("TOOL_CALLING".equals(agent.getState())) {
        if (event instanceof ToolResult) {
            // ...
        } else if (event instanceof Timeout) {
            // ...
        }
    } // 还有更多...
}

每次增加一个新行为,整套逻辑就得大改一次,测试时不时冒出“Agent 卡在思考中不动了”的诡异问题。根源在于:状态流转被写死在了嵌套堆砌的条件分支里

其实,这个问题有一个诞生于半个多世纪前的解决方案——状态机。用状态机重构 Agent 的控制逻辑,可以让它既聪明又可控。

一、什么是状态机

这里我们介绍**“有限状态机”(Finite State Machine, FSM)。一句话介绍:一个东西在任意时刻只能处于有限个状态中的一个,当某个事件发生时,它从当前状态转移到另一个状态,同时可能执行一些动作**。

当前状态 + 遇到事件 → 下一个状态 + 执行动作

对于 AI Agent,它的生命周期天然适合用状态机来描述。

二、状态机的四个要素

一个状态机包含四个核心:

  1. 现态(State) —— 当前状态。比如 Agent 正在“思考中”。
  2. 事件(Event) —— 触发转移的条件。比如 LLM 推理完成,返回了结果。
  3. 动作(Action) —— 事件引发的操作。比如判断是否需要调用工具、生成回复。
  4. 次态(Next State) —— 转移后的新状态。比如进入“工具调用”或“生成回复”。

画成状态转移图,Agent 的行为就一目了然:

有了这张图,Agent 的整个控制流就像地铁线路图一样清晰,任何非法跳转(比如从“空闲”直接进“调用工具”)都会被一眼识别。

三、状态机为什么能解决问题

回头看开头那个“一团乱麻”的 Agent 控制器,它的根本问题有三个:状态与行为紧耦合;非法转移靠运行时报错才发现;增加新行为需要改动大量分支。这是一种以事件分支为中心的编程,关注的是“发生了什么”。

状态机则彻底扭转了思路——以状态为中心

每个状态只关心自己能响应哪些事件,能跳转到哪些状态。这种内聚性带来了三大好处:

  • 隔离变更:新增“重试”或“超时处理”状态,只需定义它自己的转移规则,已有状态代码不受影响。
  • 编译期安全:如果有人试图定义一个“从空闲直接跳转到回复完成”的非法转移,状态机框架在设计阶段就能直接报错,而不是等线上出现 “Agent 凭空说了一句话” 的灵异事件。
  • 业务对齐:状态图直接映射产品文档中的 Agent 行为规范,开发、测试、产品用同一张图沟通,极大减少理解偏差。

用状态机写 Agent,就像给它装上了规则清晰的“轨道”,既保留了其灵活性,又避免了它跳脱出预定的边界。

四、简单实现一个 Agent 状态机

Java 的枚举天生适合实现有限状态机。我们可以把 Agent 的每个状态定义为一个枚举值,并让它们自己决定“能往哪里走”。

先定义事件:

enum AgentEvent { USER_MSG, REASONING_DONE, NEED_TOOL, NEED_CLARIFY,
                  TOOL_RESULT, TIMEOUT, RESPONSE_SENT }

核心是状态枚举——每个状态重写 next 方法,把自己允许的转移规则写进去:

enum AgentState {
    IDLE {
        public AgentState next(AgentEvent e) {
            if (e == AgentEvent.USER_MSG) return THINKING;
            throw illegal(e);
        }
    },
    THINKING {
        public AgentState next(AgentEvent e) {
            if (e == AgentEvent.NEED_TOOL)     return TOOL_CALLING;
            if (e == AgentEvent.NEED_CLARIFY)  return WAITING_USER;
            if (e == AgentEvent.REASONING_DONE) return RESPONDING;
            throw illegal(e);
        }
    },
    TOOL_CALLING {
        public AgentState next(AgentEvent e) {
            if (e == AgentEvent.TOOL_RESULT) return THINKING; // 回去继续推理
            if (e == AgentEvent.TIMEOUT)     return ERROR;
            throw illegal(e);
        }
    },
    WAITING_USER {
        public AgentState next(AgentEvent e) {
            if (e == AgentEvent.USER_MSG) return THINKING;
            throw illegal(e);
        }
    },
    RESPONDING {
        public AgentState next(AgentEvent e) {
            if (e == AgentEvent.RESPONSE_SENT) return IDLE;
            throw illegal(e);
        }
    },
    ERROR {
        public AgentState next(AgentEvent e) {
            if (e == AgentEvent.USER_MSG) return THINKING; // 重新开始
            throw illegal(e);
        }
    };

    public AgentState next(AgentEvent e) {
        throw new IllegalStateException("非法事件: " + e);
    }
    private static IllegalStateException illegal(AgentEvent e) {
        return new IllegalStateException("当前状态不支持事件: " + e);
    }
}

使用时,状态流转自然又安全:

AgentState state = AgentState.IDLE;             // 初始化状态为 IDLE
state = state.next(AgentEvent.USER_MSG);        // → THINKING
state = state.next(AgentEvent.NEED_TOOL);       // → TOOL_CALLING
state = state.next(AgentEvent.TOOL_RESULT);     // → THINKING (二次推理)
state = state.next(AgentEvent.REASONING_DONE);  // → RESPONDING
state = state.next(AgentEvent.RESPONSE_SENT);   // → IDLE

这段代码虽然精简,却体现了状态机的核心:每个状态都精确地定义了自己的“出口”,任何不在规则内的路径都会立即报错。如果将来 Agent 需要支持“并行工具调用”或“中止任务”等新状态,只需新增枚举值并实现其转移规则,现有逻辑纹丝不动。

这种模式如今已经在许多 AI Agent 框架中以更复杂的形式出现,*比如 LangGraph 用状态图来编排 Agent 的决策流程。*但无论框架如何演变,其底层思想始终如一。

五、状态机的背后 —— 一种思维方式

状态机之所以优雅,是因为它把“在什么情况下该做什么”这个程序设计的核心问题,用结构化的方式表达了出来。

它不再是散落各处的 if-else 碎片,而是一个有明确边界、可验证、可推理的系统。你可以在设计阶段画出状态图,直观地检查有没有遗漏的状态或非法的转移路径 —— 很多 Bug 在编码之前就被消灭了。

这让我想到我在一开始学习时的感悟:代码之所以难维护,往往不是因为写得太少,而是因为想得太少

在动手前先梳理业务中的状态和流转,画出状态图,这本身就是对领域理解的深化。

你会发现,无论是用 AI Agent 的决策引擎,还是 Kubernetes Pod 的生命周期管理,亦或是前端路由的守卫逻辑……凡是涉及“生命周期管理”的问题,都能看到状态机的影子。它不单是一种设计模式,更是一种分析复杂系统的思维。

总结

状态机不是什么高深的技术,它只是一个被时间反复验证的、好用的编程思想。

核心就一句话:把系统拆成有限个状态,定义清楚每个状态下响应什么事件、转移到什么状态

用状态机写出的代码,路径清晰、非法转移被直接杜绝,新增状态时改动可控,不会牵一发而动全身。

同时,技术没有银弹。对于极简的、只有一两种状态切换的场景,if-else 已经足够。但当你的 Agent 越来越智能,行为越来越复杂时,状态机将是帮你守住“行为边界”的最可靠哨兵。

最后,你也可以思考一下:你正在开发的应用中,是否存在靠无数 flag 和嵌套 if-else 驱动的“混乱决策”? 或许你也可以试一试,把这些逻辑画成一张状态图,发现潜伏其中的逻辑黑洞。试着用状态机的思维重构它 —— 然后你会惊讶地发现,原来逻辑控制的决策可以变得如此清晰。

感谢你看到这里,如果喜欢咪的Coding的话可以点个关注支持一下吧!欢迎各位在评论区留言!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐