Message State Management
Message State Management 是前端管理 AI 对话消息状态的模式,包括消息列表、流式更新、工具调用状态、错误处理和乐观更新。
#type / concept
#status / evergreen
#tech / frontend
#tech / ai
[!info] related notes
- 所属 MOC: AI Agent Application MOC
- 相关: Chat UI, Event Reducer
- 框架: Vercel AI SDK UI, [[assistant-ui|assistant-ui]]
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. 消息版本控制
支持重新生成时保留旧版本,用户可以切换。
常见坑
- 直接修改消息对象: React 状态应该是不可变的
- 不做消息去重: SSE 重连后消息重复
- 流式更新太频繁: 每个 token 都触发 re-render,应该用防抖
- 不做错误恢复: SSE 断连后消息状态丢失
- 消息 ID 不稳定: 导致 React key 变化,组件重建