AI对话状态管理:useReducer还是XState

团队里为这事吵过一架。新功能要给 AI 对话加一堆状态------空闲、流式输出、等工具回调、被中断、报错重试......写到第三个 if 嵌套的时候,同事说该上 XState 了,我说 useReducer 够了。最后我俩各写了一版,对着跑了两周,结论比我开吵时想的复杂。

useReducer 版:朴素但很快能跑

对话状态其实就是一台状态机,useReducer 天生适合:

go 复制代码
type State =
  | { phase: 'idle' }
  | { phase: 'streaming'; text: string }
  | { phase: 'tool_waiting'; toolName: string }
  | { phase: 'error'; reason: string };

type Action =
  | { type: 'SEND' }
  | { type: 'CHUNK'; delta: string }
  | { type: 'TOOL_CALL'; name: string }
  | { type: 'DONE' }
  | { type: 'FAIL'; reason: string };

function reducer(s: State, a: Action): State {
  switch (a.type) {
    case 'SEND': return { phase: 'streaming', text: '' };
    case 'CHUNK':
      return s.phase === 'streaming'
        ? { phase: 'streaming', text: s.text + a.delta }
        : s; // 非 streaming 收到 chunk 直接忽略
    case 'TOOL_CALL': return { phase: 'tool_waiting', toolName: a.name };
    case 'DONE': return { phase: 'idle' };
    case 'FAIL': return { phase: 'error', reason: a.reason };
    default: return s;
  }
}

零依赖,上手五分钟,类型收得也干净。CHUNK 那行的守卫很重要------我早期没写,结果对话被中断后,迟到的 SSE 包还在往一个已经结束的会话里塞字,界面诈尸。

痛点:副作用没地方放

reducer 必须是纯函数,可对话里到处是副作用------进 streaming 要发起 fetch,进 tool_waiting 要起个超时定时器,离开时要清掉。useReducer 不管这些,我只能在组件里堆 useEffect 盯着 state.phase 变化做反应:

javascript 复制代码
useEffect(() => {
  if (state.phase !== 'tool_waiting') return;
  const t = setTimeout(() => dispatch({ type: 'FAIL', reason: '工具超时' }), 30000);
  return () => clearTimeout(t);
}, [state.phase]);

一两个还好,状态多了之后这种「effect 散落在各处、跟状态机本体对不上号」的味道就上来了。某次我加了个 paused 态,忘了同步改超时那个 effect,线上出现「暂停了但 30 秒后照样报超时」的诡异 bug,查了俩小时。

XState 版:副作用是一等公民

XState 把转移和副作用(它叫 actions / actors)绑在一起声明,那个忘同步的 bug 在它这儿结构上就不容易发生:

php 复制代码
const machine = createMachine({
  initial: 'idle',
  states: {
    idle: { on: { SEND: 'streaming' } },
    streaming: {
      invoke: { src: 'streamLLM' }, // 进入即发起,离开自动 cleanup
      on: { TOOL_CALL: 'toolWaiting', DONE: 'idle', FAIL: 'error' },
    },
    toolWaiting: {
      after: { 30000: { target: 'error' } }, // 超时是状态的一部分,不会漏
      on: { TOOL_CALL: 'streaming' },
    },
    error: { on: { SEND: 'streaming' } },
  },
});

after 那行最香------超时直接挂在状态上,进了就计时,离开自动清,没有手动 clearTimeout 的心智负担。还能用可视化工具把整张图画出来,给产品讲流程不用比划。

我最后选了哪个

说实话,按场景分。这次对话状态拢共五六个态、转移关系也不绕,我留了 useReducer------XState 的 bundle 体积(gzip 十几 KB)和那套 createActorsend 的心智成本,对这个量级是过度设计,新人接手还得先学一遍它的概念。

但隔壁那个带多 Agent 编排、嵌套子流程、并行分支的项目,我毫不犹豫推了 XState,散装 reducer 根本压不住。分水岭大概是:状态超过八个、或者出现并行/嵌套,就别硬扛 useReducer 了。

模型和工具那层的编排我没在前端自己写,直接走讯飞这种 MaaS 把脏活兜了,前端只管把状态机收干净。

你们的对话状态最后落在哪边?评论区报个状态数量,我猜你选了啥。

相关推荐
HyperAI超神经25 分钟前
15亿参数挑战机器人控制,MiniCPM-RobotManip正式开源;覆盖文本/图/视频/动作序列,英伟达发布全模态模型Cosmos3-Edge
人工智能·深度学习·机器人·多模态·图像生成·具身智能·智能体
搞科研的小刘选手32 分钟前
【国际生态学协会主办】2026年生态环境与人工智能国际学术会议(ICAIEE 2026)
人工智能·生态环境·学术会议·会议推荐
笨鸟先飞,勤能补拙32 分钟前
AI 赋能网络安全领域深度剖析
网络·人工智能·windows·安全·web安全·网络安全·github
小鸟你好啊34 分钟前
搭建基于 Solon AI 的 Streamable MCP 服务并部署至阿里云百炼
人工智能·阿里云·云计算
Summer-Bright35 分钟前
深度 | Agent框架大洗牌:AutoGen退场后的新秩序
人工智能·ai·语言模型·ai软件
ThsPool39 分钟前
【遥感学习整理 02】ENVI遥感图像处理基础:从数据读取到遥感信息产品
图像处理·人工智能·学习
非凸科技1 小时前
非凸智能APP
人工智能
土星云SaturnCloud1 小时前
抽水蓄能设备智慧运营:基于土星云边缘计算实现机组全生命周期预测性维护
服务器·人工智能·ai·边缘计算
江畔柳前堤1 小时前
系统的本质——从LTI系统到神经网络
人工智能·深度学习·神经网络·机器学习·计算机视觉·数据挖掘·语音识别
HackTwoHub1 小时前
轻量化AI渗透武器库|ARTEX自主攻防系统、资产梳理、漏洞挖掘、链路分析全流程智能化落地
人工智能·安全·web安全·网络安全·系统安全·网络攻击模型·安全架构