前面实现 AI chatbot 和 编程 Agent 的时候,每次调 API 都要等好几秒,然后一下子拿到整段回复。但你用 ChatGPT 或 Claude Code 的时候,文字是一个个冒出来的,就像对面有人在实时打字。
这只是前端做的打字动画效果吗?并不是,底层获取数据就是一个字一个字来的,这种方式叫流式传输。
为什么要这样?因为 LLM 核心原理 讲过,模型是一个 token 一个 token 往外蹦的,不是想好一整段话再开口。
LLM 生成一段几百字的回复可能需要十几秒,如果等它全部生成完再一口气返回,用户就得对着空白屏幕干等,体验很差。流式传输让模型每生成一个字就立刻发过来,用户几乎瞬间看到第一个字,边看边等,体验好得多。
除了体验,流式传输还能防超时:很多 HTTP 代理和负载均衡器默认几十秒没收到数据就会断开连接,同步模式下模型在思考的这段时间连接上没有任何数据流过,容易被判定为超时,而流式响应模式下数据持续在传输,就不会触发超时。
另外,流式响应下你看到回答方向不对可以随时关掉,不用干等模型说完。
当然,流式传输也有代价:代码复杂度会上升。
比如前端渲染时,模型返回的 markdown 内容是不完整的(代码块还没闭合、加粗还没结束),普通的 markdown 渲染器处理不了这种中间状态,需要专门的增量渲染方案。但总体来说利大于弊,实际项目中和 LLM 的交互基本都用流式传输。
这篇文章就来搞清楚它的原理和用法,动手环节还是会用到 DeepSeek API Key,没有的话可以先去创建一个。
"stream": true 的魔法
messages 和 token 里用过的 curl 命令你应该还记得:
curl -s https://api.deepseek.com/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer 你的API_KEY" \
-d '{
"model": "deepseek-v4-flash",
"thinking": {"type": "disabled"},
"messages": [
{"role": "user", "content": "用一句话介绍你自己"}
]
}'等几秒,返回一个完整的 JSON,模型的回答在 choices[0].message.content 里,一次性全部到手,这就是我们一直在用的同步模式。
现在在请求体里加一个 "stream": true:
curl -s https://api.deepseek.com/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer 你的API_KEY" \
-d '{
"model": "deepseek-v4-flash",
"thinking": {"type": "disabled"},
"stream": true,
"messages": [
{"role": "user", "content": "用一句话介绍你自己"}
]
}'这次终端里的效果完全不同,数据一行一行地涌出来(下方展示时省略了不重要的字段):
data: {"id":"33b5f97e...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
data: {"id":"33b5f97e...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"你好"},"finish_reason":null}]}
data: {"id":"33b5f97e...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":","},"finish_reason":null}]}
data: {"id":"33b5f97e...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"我是"},"finish_reason":null}]}
data: {"id":"33b5f97e...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Deep"},"finish_reason":null}]}
data: {"id":"33b5f97e...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Se"},"finish_reason":null}]}
data: {"id":"33b5f97e...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"ek"},"finish_reason":null}]}
... 更多类似的行 ...
data: {"id":"33b5f97e...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":""},"finish_reason":"stop"}],"usage":{"prompt_tokens":8,"completion_tokens":28,"total_tokens":36,...}}
data: [DONE]每一行以 data: 开头,后面是一个 JSON 片段,每个片段只携带回答的一小部分。把所有片段里的文字拼起来,就是同步模式下一次性拿到的完整回复。最后的 data: [DONE] 表示传输结束。
流式传输不改变 API 返回的内容,只改变了内容送达的方式。