首页 > Technology > 正文

前端接大模型流式输出断流?三招自救

fenij 2026-09-04 41 Technology

前端用 fetch + ReadableStream 接大模型的流式输出,跑到一半断了、中文变乱码、进度条卡死——这多半不是模型挂了,而是中间层把流缓冲了,或者前端解码姿势不对。按出现频率,这三招基本能救回来。

第一招:关掉反向代理的缓冲

Nginx 默认会攒满 buffer 才下发响应,对普通 JSON 没问题,对流式就是灾难:表现成”等很久突然一次性刷出”,甚至连接超时直接断流。在流式接口所在的 location 关掉它:

location /v1/chat/stream {
    proxy_pass http://backend;
    proxy_buffering off;
    proxy_cache off;
    gzip off;
    # 关键响应头,告诉所有中间层别缓冲
    add_header X-Accel-Buffering no;
}

如果是 Cloudflare 等 CDN 在前面,记得在边缘也给该路径设 X-Accel-Buffering: no,否则 CDN 层还会再缓冲一轮。

第二招:UTF-8 多字节别在中间切断

流式是按 chunk 二进制下发的,一个中文可能被切成两半。如果用 new TextDecoder().decode(value) 不带 stream 模式,半截字符会乱码甚至抛错。正确写法累积 buffer 并开 stream:

const decoder = new TextDecoder();
let acc = "";
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  acc += decoder.decode(value, { stream: true }); // 关键:stream:true
  render(acc); // 增量渲染
}

第三招:监听断开 + 可取消

用户切走页面或重发消息时,旧的流还在后台烧 token。用 AbortController 在组件卸载/重发时 abort,并 catch 网络错误做重试或提示:

const ctrl = new AbortController();
const res = await fetch("/api/chat", {
  method: "POST",
  body: JSON.stringify({ messages }),
  signal: ctrl.signal,
});
// 循环里 try/catch 网络错误
// 组件卸载:ctrl.abort()

SSE 场景还可记下 lastEventId,断线后用它续传,而不是把半句话当最终结果。

踩坑实录

接某国产大模型时,本地 curl 流式完全正常,前端一接就”显示到一半没了”。抓包看响应头带着 Content-Encoding: gzip,而且网关 Nginx 开着 proxy_buffering:4KB 的 buffer 没攒满,连接就超时被切断,半句中文直接丢。在网关层给 /v1/chat/stream 加上 X-Accel-Buffering: no 并关掉 gzip 之后才稳定。教训很明确:流式接口和常规 JSON 接口的配置不能共用一套,否则你以为是模型不稳,其实是管道在憋。

三招对照表

现象 根因 修法
等很久突然全刷出 / 断流 Nginx/CDN 缓冲了流 proxy_buffering off + X-Accel-Buffering: no
中文变乱码、解码报错 多字节被 chunk 切断 TextDecoder decode 带 stream:true
切页后 token 还在烧 没 abort 旧流 AbortController 卸载时取消

常见问题

Q:本地好端端的,一上服务器就断?
十有八九是网关/Nginx 的缓冲或 gzip,照第一招在边缘关掉。

Q:用 EventSource 还是 fetch?
EventSource 只支持 GET 且自带重连,但大模型接口多是 POST,所以普遍用 fetch + ReadableStream。

Q:断流后怎么续?
SSE 记 lastEventId 续传;fetch 流没有原生续传,前端做”重试整段”或后端按 checkpoint 重发更稳。

你接的是哪家模型的流式接口、断在哪一步?欢迎到 fenij.com 留言,把响应头和报错贴出来,我帮你对着三招定位。

排错自查清单

  • ☐ 第一招:关掉反向代理的缓冲:Nginx 默认会攒满 buffer 才下发响应,对普通 JSON 没问题,对流式就是灾难:表现成"等很久突然
  • ☐ 第二招:UTF-8 多字节别在中间切断:流式是按 chunk 二进制下发的,一个中文可能被切成两半。如果用 new TextDecoder().dec
  • ☐ 第三招:监听断开 + 可取消:用户切走页面或重发消息时,旧的流还在后台烧 token。用 AbortController 在组件卸载/重发时
  • ☐ 踩坑实录:接某国产大模型时,本地 curl 流式完全正常,前端一接就"显示到一半没了"。抓包看响应头带着 Content
  • ☐ 三招对照表
  • ☐ 常见问题:Q:本地好端端的,一上服务器就断?十有八九是网关/Nginx 的缓冲或 gzip,照第一招在边缘关掉。

关键命令速查

  • location /v1/chat/stream { proxy_pass http://backend; proxy_buffering off; proxy_cache off
  • const decoder = new TextDecoder(); let acc = ""; while (true) { const { done, value } = aw
  • const ctrl = new AbortController(); const res = await fetch("/api/chat", { method: "POST",

相关延伸

更系统的排查思路,见 Technology 分类归档

相关完整手册

系统化的排查与配置思路,建议顺手收藏这几篇完整手册: