1. 如何设计 idle、streaming、awaiting-tool、awaiting-confirmation、error、success 等消息状态及转换
如何设计聊天消息的 idle、streaming、awaiting-tool、awaiting-confirmation、error、success 等状态机,以及它们之间的合法转换?
- 消息状态机的建模与有限状态转换
- 各状态与运行中请求、工具执行、用户确认的关联
- 状态转换的驱动源(事件)与并发安全
用显式状态机(如用字符串字面量联合类型或 XState)建模每条消息的状态,而不是用布尔标志组合。常见状态为:idle(就绪/未开始)、streaming(正在流式输出)、awaiting-tool(正在等待工具执行)、awaiting-confirmation(等待用户确认高风险工具)、error(出错)、success(完成)。转换只能由明确事件驱动,例如:idle→streaming(收到首个 token)、streaming→awaiting-tool(遇到工具调用点)、awaiting-tool→streaming(工具返回、继续生成)、streaming→success(收到结束标记)、任意运行态→error(请求失败/超时)、streaming→idle(用户点击停止,回到可重试状态)。状态机应放在一个可穷举的 reducer 或 store 中,保证同一时刻只有一个合法状态,并让渲染层只根据状态渲染占位符、光标动画或错误卡片。
显式状态机避免了"多个布尔标志互相矛盾"的经典 bug(例如正在 error 又显示 streaming 动画)。把状态切换收敛到单一事件处理函数,便于 debug、测试和审计,也便于后续加入"已取消"等新状态。状态机还能作为"可恢复性"的依据——刷新后根据服务端持久化的状态节点恢复 UI。
type MsgState = 'idle' | 'streaming' | 'awaiting-tool' | 'awaiting-confirmation' | 'error' | 'success';
type MsgEvent =
| { type: 'FIRST_TOKEN' }
| { type: 'TOOL_START' }
| { type: 'TOOL_RESULT' }
| { type: 'STREAM_END' }
| { type: 'FAIL' }
| { type: 'STOP' };
function reduce(state: MsgState, ev: MsgEvent): MsgState {
switch (state) {
case 'idle':
return ev.type === 'FIRST_TOKEN' ? 'streaming' : state;
case 'streaming':
if (ev.type === 'TOOL_START') return 'awaiting-tool';
if (ev.type === 'STREAM_END') return 'success';
if (ev.type === 'FAIL') return 'error';
if (ev.type === 'STOP') return 'idle';
return state;
case 'awaiting-tool':
if (ev.type === 'TOOL_RESULT') return 'streaming';
if (ev.type === 'FAIL') return 'error';
return state;
case 'awaiting-confirmation':
if (ev.type === 'TOOL_RESULT') return 'streaming';
if (ev.type === 'STOP') return 'idle';
return state;
default:
return state;
}
}