流式架构设计实践
为什么需要流式
Agent 回答通常需要多步推理和工具调用,用户等待时间可达数秒甚至十秒以上。流式输出让用户边看边等,体验改善显著——首字延迟降低到 300ms 以内,用户感知等待时间减少 60%。
流式 vs 非流式
| 维度 | 非流式 | 流式 |
|---|---|---|
| 首 Token 延迟 | 完整推理后返回 | ~300ms |
| 用户感知 | 等待期不确定 | 边生成边看 |
| 实现复杂度 | 简单 | 中等 |
| 中间状态反馈 | 无 | 可显示"正在检索..." |
| 中断恢复 | 不支持 | 部分支持 |
SSE (Server-Sent Events) 方案
Agent 系统最推荐的流式方案。单向、基于 HTTP、天然支持断线重连、兼容性好。
// FastAPI SSE 示例
from sse_starlette.sse import EventSourceResponse
async def agent_stream(query: str):
async for event in agent.generate_stream(query):
yield {
"event": event.type, // "thinking", "tool_call", "token", "done"
"data": event.data
}
return EventSourceResponse(agent_stream(query))
// 前端消费
const evtSource = new EventSource("/api/chat/stream?q=...");
evtSource.addEventListener("token", (e) => {
appendText(JSON.parse(e.data).text);
});
evtSource.addEventListener("done", () => {
evtSource.close();
});
事件类型设计
- thinking:Agent 正在思考(显示推理过程或加载动画)
- tool_call:正在调用工具,带工具名称和参数
- tool_result:工具返回结果,可展示给用户
- token:流式生成的文本片段
- error:错误信息,带错误码
- done:流结束,附带最终元数据(Token 数、耗时)
中断与恢复
用户中断:发送 AbortController 信号 → 后端取消 LLM 请求 → 清理未完成的工具调用。
网络断线:SSE 自动重连(EventSource 原生支持),带上 lastEventId 恢复。
服务端异常:发送 error 事件,客户端显示错误状态。
实践建议
- 流式输出配合打字机效果(逐字渲染)优于整段刷新
- 思考过程和最终回答分开渲染(思考用灰色背景折叠区域)
- 工具调用结果流式展示——用户能看到 "正在查询数据库..."
- 前端缓冲使用 requestAnimationFrame 而非 setInterval 渲染
- 长回答(超过 5 秒)建议在流中定期发送时间戳心跳包