做了一个AI聊天应用后,我决定试试这个状态管理库

15 阅读2分钟

AI应用前端最大的坑,不是LLM调用,而是状态管理

背景

最近做了个AI聊天应用,类似ChatGPT的那种。

本来想用Redux,毕竟老牌方案,结果被毒打了一遍。

Redux的痛

痛点1:状态类型爆炸

// AI聊天应用需要管理的状态
interface ChatState {
  // 对话
  messages: Message[];

  // 流式响应
  streamingText: string;
  isStreaming: boolean;

  // 工具调用
  toolCalls: ToolCall[];
  currentToolCall: ToolCall | null;
  toolResults: Record<string, any>;

  // 上下文
  contextWindow: Message[];
  longTermMemory: MemoryItem[];

  // 用户意图
  currentIntent: Intent | null;
  intentHistory: Intent[];

  // 执行状态
  currentStep: number;
  stepResults: Record<number, any>;

  // 错误处理
  errors: Error[];
  retryQueue: RetryItem[];

  // ...
}

这还只是一个聊天模块的状态。

痛点2:action type能写死你

// 光是状态更新action就能写30个
const ADD_MESSAGE = "chat/ADD_MESSAGE";
const UPDATE_STREAMING = "chat/UPDATE_STREAMING";
const APPEND_STREAMING = "chat/APPEND_STREAMING";
const START_TOOL_CALL = "chat/START_TOOL_CALL";
const COMPLETE_TOOL_CALL = "chat/COMPLETE_TOOL_CALL";
const UPDATE_CONTEXT = "chat/UPDATE_CONTEXT";
const ADD_INTENT = "chat/ADD_INTENT";
// ... 还有几十个

痛点3:跨组件同步难

// Chat组件
const messages = useSelector((s) => s.chat.messages);

// Status组件
const toolCalls = useSelector((s) => s.chat.toolCalls);

// 怎么保证两个组件状态一致?靠redux-thunk?middleware?

然后我用了easy-model

// 一个类搞定AI聊天全状态
class AIChatModel {
  // 对话
  messages: Message[] = [];

  // 流式响应
  streamingText = "";
  isStreaming = false;

  // 工具调用
  toolCalls: ToolCall[] = [];
  currentToolCall: ToolCall | null = null;
  toolResults: Map<string, any> = new Map();

  // 上下文
  contextWindow: Message[] = [];
  longTermMemory: MemoryItem[] = [];

  // 用户意图
  currentIntent: Intent | null = null;
  intentHistory: Intent[] = [];

  // 执行
  currentStep = 0;
  stepResults: Map<number, any> = new Map();

  // 错误
  errors: Error[] = [];

  // 方法
  @loader.load()
  async sendMessage(content: string) {
    this.messages.push({ role: "user", content });
    this.isStreaming = true;

    const response = await llm.streamChat(this.messages);

    for await (const chunk of response) {
      this.streamingText += chunk;
    }

    this.messages.push({ role: "assistant", content: this.streamingText });
    this.streamingText = "";
    this.isStreaming = false;
  }

  async executeToolCall(tool: Tool, params: any) {
    this.currentToolCall = { tool, params, status: "running" };
    this.toolCalls.push(this.currentToolCall);

    const result = await tool.execute(params);

    this.currentToolCall.status = "completed";
    this.currentToolCall.result = result;
    this.toolResults.set(tool.name, result);
    this.currentToolCall = null;
  }
}

一个类,200行代码搞定Redux 500行都搞不定的事。

还能解决什么问题?

1. 撤销重做,调试AI回复

const chat = useModel(AIChatModel, []);
const history = useModelHistory(chat);

// 用户想撤回AI的上一次回复?
history.back();

// 想重做?
history.forward();

2. 跨组件状态共享

// 聊天区域
function ChatArea() {
  const chat = useModel(AIChatModel, ["main"]);
  return <MessageList messages={chat.messages} />;
}

// 状态显示
function StatusPanel() {
  const chat = useModel(AIChatModel, ["main"]);
  return <StatusBadge isStreaming={chat.isStreaming} />;
}

// 工具调用面板
function ToolPanel() {
  const chat = useModel(AIChatModel, ["main"]);
  return <ToolList calls={chat.toolCalls} />;
}

// 三个组件,状态自动同步

3. 深度监听

// 监听任意状态变化
watch(chat, (keys, prev, next) => {
  console.log(`状态变化: ${keys.join(".")}`, prev, "→", next);

  // "messages.5.content" - 第6条消息内容变了
  // "toolCalls.0.status" - 第一个工具调用状态变了
  // "streamingText" - 流式文本更新了
});

对比其他方案

特性ReduxZustandMobXeasy-model
类模型
无装饰器
依赖注入
撤销重做
深度监听⚠️
TypeScript友好⚠️⚠️

结论

AI应用前端,状态管理选easy-model就对了。


GitHub: github.com/ZYF93/easy-…

npm: pnpm add @e7w/easy-model

做AI应用前端,状态管理别再踩坑了,点个⭐️!