FastAPI的 StreamingResponse在被Next.js的 fetch调用时,会把语言模型输出的token缓冲到完成才会输出。如何解决?
我正在用Next.js作为前端、FastAPI作为后端,构建一个AI聊天功能。
后端从LLM提供商获取token,并通过StreamingResponse将它们返回给浏览器。我的目标是让浏览器在后端产出每个token/分块时就渲染:
chunk 1
chunk 2
chunk 3
...
问题在于前端只有在生成完成后才收到整条响应。后端日志显示分块一个接一个地产出,但浏览器在完整流完成之前不会显示任何内容:
chunk 1 chunk 2 chunk 3 ...
后端示例:
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio
app = FastAPI()
async def token_generator():
for i in range(10):
chunk = f"token-{i}\n"
print("yielding:", chunk.strip(), flush=True)
yield chunk
await asyncio.sleep(0.5)
@app.get("/stream")
async def stream():
return StreamingResponse(
token_generator(),
media_type="text/plain",
headers={
"Cache-Control": "no-cache",
"X-Accel-Buffering": "no",
},
)
前端示例:
export default async function readStream() {
const response = await fetch("http://localhost:8000/stream", {
method: "GET",
cache: "no-store",
});
if (!response.body) {
throw new Error("No response body");
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { value, done } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
console.log("received:", chunk);
}
}
我尝试了以下方法:
- 使用
StreamingResponse代替返回一个普通的JSON响应。 - 在产出之间添加
await asyncio.sleep()以确认生成器不会立即完成。 - 在后端日志中添加
flush=True。 - 设置
Cache-Control: no-cache。 - 添加
X-Accel-Buffering: no。 - 使用
response.body.getReader()读取响应,而不是await response.text()。
后端日志确认生成器每500ms产出一次,但浏览器仍然在完整响应完成后才接收数据。
在这样的设置中,缓冲的常见原因有哪些?
具体来说,我想了解问题很可能由以下哪些因素引起:
- FastAPI/Starlette行为
- 浏览器
fetch()行为 text/plain与text/event-stream的差异- 本地开发服务器行为
- 反向代理缓冲,例如Nginx
- Next.js路由/运行时行为
- 缺少换行符或SSE格式的问题
生产环境中从FastAPI向 Next.js前端无缓冲地流式传输LLM tokens的正确且安全的方法是什么?
解决方案
Use “text/event-stream” instead of a content type of “text/plain”.
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。