Abort Generation
Abort Generation 是用户在 AI 流式输出过程中取消生成的机制,涉及前端请求中断、后端停止推理和状态同步。
#type / concept
#status / evergreen
#tech / frontend
#tech / ai
[!info] related notes
- 所属 MOC: AI Agent Application MOC
- 相关: Cancellation, SSE Client, Chat UI
- 后端: Cancellation Propagation
Abort Generation
一句话定义
Abort Generation 是用户在 AI 流式输出过程中取消生成的完整机制。它不只是前端断开连接,还涉及后端停止 LLM 推理、停止工具执行、保存已生成内容和状态同步。
它解决什么问题
LLM 生成可能持续几秒到几分钟。用户可能:
- 发现 Agent 理解错了意图,想重新输入
- 不想等了,想用已生成的部分内容
- 发现 Agent 走偏了,想中断
- 页面要关闭,需要清理资源
如果没有取消机制,Agent 会继续生成无用的 token,浪费算力和费用。
核心原理
取消的完整流程
用户点击"停止"按钮
│
▼
前端: AbortController.abort()
│
▼
fetch 请求被取消 → 后端检测到连接关闭
│
▼
后端: 停止 LLM 流式生成
│
▼
后端: 如果正在执行工具 → 尝试取消工具执行
│
▼
后端: 保存已生成内容到数据库 (status: cancelled)
│
▼
后端: 通知 AI Service Run 被取消
│
▼
前端: 更新消息状态为 cancelled,显示已生成的部分内容
前端实现
function useChat() {
const [messages, setMessages] = useState([]);
const abortRef = useRef<AbortController | null>(null);
const sendMessage = async (content: string) => {
abortRef.current = new AbortController();
// 添加用户消息
setMessages(prev => [...prev, { role: 'user', content }]);
// 创建 assistant 消息占位
const assistantMsg = { role: 'assistant', content: '', status: 'streaming' };
setMessages(prev => [...prev, assistantMsg]);
try {
const response = await fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({ message: content }),
signal: abortRef.current.signal,
});
const reader = response.body!.getReader();
// ... 流式读取
} catch (err) {
if (err.name === 'AbortError') {
// 用户取消,保留已生成内容
setMessages(prev => prev.map((msg, i) =>
i === prev.length - 1
? { ...msg, status: 'cancelled' }
: msg
));
}
}
};
const stopGeneration = () => {
abortRef.current?.abort();
};
return { messages, sendMessage, stopGeneration };
}
后端感知取消
// Go 后端检测连接关闭
func chatHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// 当前端断开连接时,ctx 会被 cancel
go func() {
<-ctx.Done()
// 连接关闭,通知 AI Service 停止
aiService.CancelRun(runID)
}()
// 调用 AI Service
stream, _ := aiService.Chat(ctx, request)
for event := range stream {
// 转发事件
writeSSE(w, event)
}
}
# Python AI Service 检测取消
async def agent_run(context, cancel_event):
async for event in engine.run(context):
if cancel_event.is_set():
# 保存已生成内容
save_partial_result(context.run_id)
yield DoneEvent(status="cancelled")
return
yield event
常见设计模式
1. 保留已生成内容
取消后保留已经生成的部分内容,而不是丢弃。
2. 状态机管理
消息状态: streaming → cancelling → cancelled
3. 资源清理
取消时清理 LLM 连接、工具执行、临时文件。
常见坑
- 取消后内容丢失: 用户取消时已经生成的内容被丢弃
- 后端不停止: 前端断开了,后端继续生成,浪费资源
- 不清理 AbortController: 组件卸载时没有 abort
- 取消状态不同步: 前端显示取消了,后端还在跑
- 并发取消: 快速点击发送和取消导致状态混乱