上周接了个需求,给内部知识库问答加「打字机效果」——就是像 ChatGPT 那样一个字一个字蹦出来,而不是等半天突然啪一大坨文字糊你脸上。
说实话之前一直觉得这个很简单,不就是 SSE 嘛。结果真上手写才发现,光是把各家大模型的流式响应跑通、前端正确解析、处理各种断流异常,就折腾了两天。
把完整的踩坑过程和最终方案整理出来,前后端代码都能直接跑。
流式输出本身不复杂,但把链路串起来的细节非常多,任何一环出问题用户看到的都是「卡住了」或者「乱码」。
非流式请求:你发请求 → 模型花 10 秒生成完整回答 → 一次性返回。用户这 10 秒看到的是一片空白或者 loading 转圈。
流式请求:你发请求 → 模型生成一个 token 就推一个 token → 前端拿到就渲染。用户几乎毫秒级就能看到第一个字,体验完全不同。
底层是 SSE(Server-Sent Events),本质上是一个持久的 HTTP 连接,服务端不断往里写 格式的文本。不是 WebSocket,比 WebSocket 简单得多,单向推送够用了。
跑起来就能在终端看到打字机效果了。几个注意点:
- 不是 :流式响应里每个 chunk 的内容在 ,不是 ,刚开始很容易搞混。
- 可能是 :第一个和最后一个 chunk 通常 content 为 None,必须判空。
- 很重要:不加的话 Python 会缓冲输出,看到的还是一坨一坨的。
实际项目里不能让前端直接调大模型 API(Key 会暴露),得有个后端中转。用 FastAPI 的 很方便:
GPT plus 代充 只需 145
这里有个大坑要单独说一下。
这个排查了大半天。本地开发一切正常,打字机效果丝滑。一部署到测试服务器,前端又变成等半天一坨出来了。
原因是 Nginx 默认开着 ,会把上游响应攒够一定量才往客户端发。普通请求没问题,SSE 就是灾难。
在 Nginx 配置里加:
也可以像上面代码那样加 响应头,Nginx 看到这个头会自动关闭缓冲。两种方式都行,我两个都加了。
浏览器原生有个 API 专门接 SSE,看起来很美好:
GPT plus 代充 只需 145
问题在于:
- 只支持 GET 请求——聊天接口通常是 POST,消息体可能很长,不适合放 URL 参数里
- 不能自定义 Header——加个 Authorization?不支持
- 断线重连是黑盒——自动重连,但你控制不了重连策略
用 + 才是正解:
这个比较隐蔽。 返回的 chunk 是按网络包切的,不是按 SSE 事件切的。也就是说一个 可能被切成两次 read:
GPT plus 代充 只需 145
这就是上面代码里 的作用——把没处理完的数据攒着,等下一次 read 拼起来。不做这个缓冲, 就会疯狂报错。
网上很多教程都没处理这个,本地测试没问题是因为数据量小一般不会被切,上了生产数据量一大就炸。
不同模型粒度差异很大。GPT-5.4 通常一两个词一个 chunk,Claude Opus 4.6 有时候攒几个词,Gemini 3 偶尔一大段一大段地吐。
导致前端打字机效果体验不一致——有些模型一顿一顿的,有些又太快像直接刷出来的。
解决办法是在前端加一个渲染队列,用 控制节奏:
不管后端吐得多快多慢,前端渲染都是均匀的。
用户点了「停止生成」,后端那个流式连接不主动断开的话,模型会继续生成到结束,白白浪费 token。
前端用 :
GPT plus 代充 只需 145
后端也要处理连接断开,不然生成器会继续跑:
顺便说下这段时间测的几个模型:
这几个模型都是通过同一个聚合接口调的,协议都是 OpenAI 兼容,上面的代码换个 model 名字就行,不用去适配各家不同的 SDK 和鉴权方式。
流式输出就三件事:
- 后端: 拿到生成器,逐 chunk yield 成 SSE 格式
- 中间链路:确保 Nginx、CDN 等不要缓冲 SSE 响应
- 前端:用 + ,做好 buffer 拼接和异常处理
chunk 被切割、Nginx 缓冲、用户取消这三个坑,基本每个做流式输出的人都会踩一遍。
代码都是完整可运行的,直接 copy 改改配置就能用。有问题评论区聊。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/235920.html