流式输出(SSE)接入指南:原理与三种语言实现

作者:

在

为什么用 SSE

大模型生成一段话需要几秒到几十秒。如果等全部生成完再返回,用户会盯着空白屏幕。SSE(Server-Sent Events)让服务端边生成边推送,首字延迟能降到几百毫秒。

相比 WebSocket,SSE 更简单:单向推送、纯 HTTP、自动重连。

协议格式

data: {"choices":[{"delta":{"content":"你"}}]}

data: {"choices":[{"delta":{"content":"好"}}]}

data: [DONE]

每行以 data: 开头,两个换行表示一条消息结束,[DONE] 表示流结束。

Python 实现

from openai import OpenAI

client = OpenAI(api_key="你的KEY", base_url="https://api.ns666.cfd/v1")

stream = client.chat.completions.create(
    model="deepseek-v4-flash",
    messages=[{"role": "user", "content": "讲个故事"}],
    stream=True,
)

for chunk in stream:
    delta = chunk.choices[0].delta.content if chunk.choices else None
    if delta:
        print(delta, end="", flush=True)

Node.js 实现

const resp = await fetch("https://api.ns666.cfd/v1/chat/completions", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${process.env.API_KEY}`,
  },
  body: JSON.stringify({
    model: "deepseek-v4-flash",
    messages: [{ role: "user", content: "讲个故事" }],
    stream: true,
  }),
});

const reader = resp.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  for (const line of decoder.decode(value).split("n")) {
    if (!line.startsWith("data: ")) continue;
    const p = line.slice(6);
    if (p === "[DONE]") continue;
    try {
      const d = JSON.parse(p).choices[0].delta.content;
      if (d) process.stdout.write(d);
    } catch (e) {}
  }
}

浏览器端

const resp = await fetch("https://api.ns666.cfd/v1/chat/completions", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${API_KEY}`,
  },
  body: JSON.stringify({
    model: "deepseek-v4-flash",
    messages: [{ role: "user", content: "你好" }],
    stream: true,
  }),
});

const reader = resp.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  for (const line of decoder.decode(value).split("n")) {
    if (!line.startsWith("data: ")) continue;
    const p = line.slice(6).trim();
    if (p === "[DONE]") continue;
    try {
      const delta = JSON.parse(p).choices[0].delta.content;
      if (delta) document.body.innerText += delta;
    } catch (e) {}
  }
}

注意:浏览器直连会暴露 Key

前端代码里的 Key 任何人都能看到。生产环境必须经由你自己的后端中转,后端持 Key 调随性 API,前端只连你的后端。

相关文档