Message State Management

Message State Management 是前端管理 AI 对话消息状态的模式,包括消息列表、流式更新、工具调用状态、错误处理和乐观更新。

#type / concept #status / evergreen #tech / frontend #tech / ai

[!info] related notes

Message State Management

一句话定义

Message State Management 是前端管理 AI 对话消息状态的模式。它不只是维护一个消息数组,而是要处理流式更新(文本逐步到达)、工具调用状态(进行中/完成/失败)、错误恢复和乐观更新。

它解决什么问题

AI 对话的消息状态比传统聊天复杂:

  • 流式更新: assistant 消息的内容在逐步变化
  • 工具调用: 一条消息可能包含多个工具调用,每个有独立状态
  • 中断恢复: SSE 断连后需要恢复消息状态
  • 乐观更新: 用户发送消息后立即显示,不等后端确认
  • 消息去重: SSE 重连可能导致消息重复

核心原理

消息数据结构

interface Message {
  id: string;
  role: 'user' | 'assistant' | 'system';
  content: string;
  status: 'pending' | 'streaming' | 'done' | 'error' | 'cancelled';
  toolCalls?: ToolCall[];
  createdAt: number;
  metadata?: Record<string, any>;
}

interface ToolCall {
  id: string;
  name: string;
  arguments: any;
  result?: any;
  error?: string;
  status: 'running' | 'done' | 'error';
  duration?: number;
}

Event Reducer 模式

消息状态通过 Event Reducer 更新:

function messageReducer(state: ChatState, event: AgentEvent): ChatState {
  switch (event.event) {
    case 'text_delta':
      return updateAssistantMessage(state, msg => ({
        ...msg,
        content: msg.content + event.data.delta,
        status: 'streaming',
      }));

    case 'tool_call_start':
      return updateAssistantMessage(state, msg => ({
        ...msg,
        toolCalls: [...(msg.toolCalls || []), {
          id: event.data.tool_call_id,
          name: event.data.tool_name,
          arguments: event.data.arguments,
          status: 'running',
        }],
      }));

    case 'tool_call_result':
      return updateToolCall(state, event.data.tool_call_id, {
        result: event.data.result,
        status: 'done',
        duration: event.data.duration_ms,
      });

    case 'done':
      return updateAssistantMessage(state, msg => ({
        ...msg,
        status: 'done',
      }));

    case 'error':
      return updateAssistantMessage(state, msg => ({
        ...msg,
        status: 'error',
        error: event.data.message,
      }));

    default:
      return state;
  }
}

典型工程实现

基于 Vercel AI SDK 的状态管理

import { useChat } from '@ai-sdk/react';

function Chat() {
  const {
    messages,      // 消息列表
    input,         // 输入框内容
    handleInputChange,
    handleSubmit,
    isLoading,     // 是否正在生成
    stop,          // 停止生成
    reload,        // 重新生成
    error,         // 错误信息
    setMessages,   // 手动设置消息
  } = useChat({
    api: '/api/chat',
    onError: (error) => console.error('Chat error:', error),
  });

  return (/* ... */);
}

基于 useReducer 的自定义实现

const [state, dispatch] = useReducer(messageReducer, {
  messages: [],
  status: 'idle',
});

// SSE 事件处理
useEffect(() => {
  const es = new EventSource('/api/chat/stream');
  es.addEventListener('text_delta', (e) => {
    dispatch({ type: 'text_delta', data: JSON.parse(e.data) });
  });
  es.addEventListener('tool_call_start', (e) => {
    dispatch({ type: 'tool_call_start', data: JSON.parse(e.data) });
  });
  // ...
  return () => es.close();
}, []);

常见设计模式

1. 乐观更新

用户发送消息后立即添加到消息列表,不等后端确认。

2. 流式累积

text_delta 事件逐步累积到 assistant 消息的 content 字段。

3. 工具调用状态嵌套

一条 assistant 消息包含多个 toolCall,每个有独立状态。

4. 消息版本控制

支持重新生成时保留旧版本,用户可以切换。

常见坑

  1. 直接修改消息对象: React 状态应该是不可变的
  2. 不做消息去重: SSE 重连后消息重复
  3. 流式更新太频繁: 每个 token 都触发 re-render,应该用防抖
  4. 不做错误恢复: SSE 断连后消息状态丢失
  5. 消息 ID 不稳定: 导致 React key 变化,组件重建

参考资料

创建于 2026/6/30 更新于 2026/7/15