Skip to content

流式响应优化指南

本文档详细介绍白山智算平台 LLM API 的流式响应功能,帮助您实现更好的用户体验和性能优化。


1. 什么是流式响应?

流式响应(Streaming Response)是指模型在生成文本时,逐块返回内容而非一次性返回完整结果。这使得用户可以:

  • 即时看到输出:首字节延迟可低至数百毫秒
  • 提升感知速度:无需等待完整生成即可开始阅读
  • 支持长文本:避免长时间等待完整响应

2. 基础用法

2.1 开启流式响应

在请求中设置 stream: true 即可开启流式响应:

json
{
  "model": "DeepSeek-R1-0528",
  "messages": [
    {"role": "user", "content": "写一篇关于春天的散文"}
  ],
  "stream": true
}

2.2 响应格式(SSE 协议)

流式响应采用 Server-Sent Events(SSE)协议,返回格式如下:

data: {"choices":[{"delta":{"content":"春风"},"index":0,"finish_reason":null}]}

data: {"choices":[{"delta":{"content":"轻拂"},"index":0,"finish_reason":null}]}

data: {"choices":[{"delta":{"content":"着杨柳枝","index":0,"finish_reason":null}]}

...

data: [DONE]

2.3 delta 字段说明

字段说明
content本次返回的文本片段
index结果索引(多结果时)
finish_reason结束原因:null(生成中)、length(达到上限)、stop(正常结束)

3. 客户端实现

3.1 JavaScript / TypeScript

原生 Fetch API

javascript
async function* streamChat(prompt) {
  const response = await fetch('https://api.edgefn.net/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      model: 'DeepSeek-R1-0528',
      messages: [{ role: 'user', content: prompt }],
      stream: true
    })
  });

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = '';

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    buffer += decoder.decode(value, { stream: true });
    const lines = buffer.split('\n');
    buffer = lines.pop() || '';

    for (const line of lines) {
      if (line.startsWith('data: ')) {
        const data = line.slice(6);
        if (data === '[DONE]') return;

        const parsed = JSON.parse(data);
        const content = parsed.choices?.[0]?.delta?.content;
        if (content) yield content;
      }
    }
  }
}

// 使用示例
async function main() {
  for await (const chunk of streamChat('写一首关于春天的诗')) {
    process.stdout.write(chunk);
  }
}

main();

使用 EventSource(仅支持 GET 请求,不适用于此场景)

注意:EventSource 仅支持 GET 请求,无法用于 POST 请求。建议使用上面 fetch 的方式。

3.2 Python

使用 requests 库

python
import requests
import json

def stream_chat(prompt):
    url = "https://api.edgefn.net/v1/chat/completions"
    headers = {
        "Authorization": "Bearer YOUR_API_KEY",
        "Content-Type": "application/json"
    }
    data = {
        "model": "DeepSeek-R1-0528",
        "messages": [{"role": "user", "content": prompt}],
        "stream": True
    }

    response = requests.post(url, headers=headers, json=data, stream=True)

    for line in response.iter_lines():
        if line:
            line = line.decode('utf-8')
            if line.startswith('data: '):
                data_str = line[6:]
                if data_str == '[DONE]':
                    break

                parsed = json.loads(data_str)
                content = parsed['choices'][0]['delta'].get('content', '')
                if content:
                    yield content

# 使用示例
for chunk in stream_chat("写一篇关于春天的散文"):
    print(chunk, end='', flush=True)

使用 SSE 库

python
import sseclient
import requests

def stream_chat_sse(prompt):
    url = "https://api.edgefn.net/v1/chat/completions"
    headers = {
        "Authorization": "Bearer YOUR_API_KEY",
        "Content-Type": "application/json"
    }
    data = {
        "model": "DeepSeek-R1-0528",
        "messages": [{"role": "user", "content": prompt}],
        "stream": True
    }

    response = requests.post(url, headers=headers, json=data, stream=True)
    client = sseclient.SSEClient(response)

    for event in client.events():
        if event.data == '[DONE]':
            break

        parsed = json.loads(event.data)
        content = parsed['choices'][0]['delta'].get('content', '')
        if content:
            yield content

# 使用示例
for chunk in stream_chat_sse("解释量子计算原理"):
    print(chunk, end='', flush=True)

3.3 Go

go
package main

import (
    "bufio"
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "strings"
)

type StreamResponse struct {
    Choices []struct {
        Delta struct {
            Content string `json:"content"`
        } `json:"delta"`
    } `json:"choices"`
}

func streamChat(prompt string) {
    url := "https://api.edgefn.net/v1/chat/completions"
    reqBody := map[string]interface{}{
        "model": "DeepSeek-R1-0528",
        "messages": []map[string]string{{"role": "user", "content": prompt}},
        "stream": true,
    }

    jsonData, _ := json.Marshal(reqBody)
    req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
    req.Header.Set("Authorization", "Bearer YOUR_API_KEY")
    req.Header.Set("Content-Type", "application/json")

    client := &http.Client{}
    resp, _ := client.Do(req)
    defer resp.Body.Close()

    reader := bufio.NewReader(resp.Body)

    for {
        line, err := reader.ReadString('\n')
        if err != nil {
            break
        }

        line = strings.TrimSpace(line)
        if strings.HasPrefix(line, "data: ") {
            data := line[6:]
            if data == "[DONE]" {
                break
            }

            var streamResp StreamResponse
            if json.Unmarshal([]byte(data), &streamResp) == nil {
                content := streamResp.Choices[0].Delta.Content
                fmt.Print(content)
            }
        }
    }
}

func main() {
    streamChat("写一个快速排序算法")
}

4. 性能优化

4.1 减少首字节延迟

  • 选择就近接入点:使用距离最近的 API 端点
  • 预热请求:首次请求可能较慢,可在页面加载时发送预热请求

4.2 优化网络传输

  • 启用压缩:确保网络支持 gzip 压缩
  • 减少不必要参数:只传递必要的请求参数

4.3 前端渲染优化

javascript
// 错误示例:每次片段都触发完整重渲染
element.innerHTML += chunk;

// 正确示例:使用 textContent 追加内容
element.textContent += chunk;

// 更佳示例:使用 documentFragment 批量更新
const fragment = document.createDocumentFragment();
fragment.textContent = chunk;
element.appendChild(fragment);

4.4 处理速度控制

javascript
class StreamController {
  constructor() {
    this.buffer = '';
    this.updateInterval = 50; // 每50ms更新一次UI
    this.lastUpdate = Date.now();
  }

  addChunk(chunk) {
    this.buffer += chunk;

    const now = Date.now();
    if (now - this.lastUpdate >= this.updateInterval) {
      this.flush();
      this.lastUpdate = now;
    }
  }

  flush() {
    if (this.buffer) {
      outputElement.textContent += this.buffer;
      this.buffer = '';
    }
  }

  finish() {
    this.flush();
  }
}

5. 错误处理与重试

5.1 常见错误

错误类型说明处理方式
网络中断连接意外断开自动重试
401 鉴权失败API Key 无效检查密钥
429 超限请求过快指数退避重试
500 服务错误服务器异常等待后重试

5.2 自动重试实现

javascript
async function* streamChatWithRetry(prompt, maxRetries = 3) {
  let retries = 0;
  let lastError = null;

  while (retries <= maxRetries) {
    try {
      const response = await fetch('https://api.edgefn.net/v1/chat/completions', {
        method: 'POST',
        headers: {
          'Authorization': 'Bearer YOUR_API_KEY',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          model: 'DeepSeek-R1-0528',
          messages: [{ role: 'user', content: prompt }],
          stream: true
        })
      });

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }

      const reader = response.body.getReader();
      const decoder = new TextDecoder();
      let buffer = '';

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;

        buffer += decoder.decode(value, { stream: true });
        const lines = buffer.split('\n');
        buffer = lines.pop() || '';

        for (const line of lines) {
          if (line.startsWith('data: ')) {
            const data = line.slice(6);
            if (data === '[DONE]') {
              return;
            }

            const parsed = JSON.parse(data);
            const content = parsed.choices?.[0]?.delta?.content;
            if (content) yield content;
          }
        }
      }

      return; // 成功完成

    } catch (error) {
      lastError = error;
      retries++;

      if (retries <= maxRetries) {
        const delay = Math.pow(2, retries) * 1000; // 指数退避
        console.log(`重试中 (${retries}/${maxRetries}), 等待 ${delay}ms...`);
        await new Promise(r => setTimeout(r, delay));
      }
    }
  }

  throw new Error(`达到最大重试次数: ${lastError?.message}`);
}

6. 完整应用示例

6.1 实时聊天界面

html
<!DOCTYPE html>
<html>
<head>
  <style>
    #chat-container {
      max-width: 600px;
      margin: 20px auto;
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    }
    .message {
      padding: 10px 15px;
      margin: 5px 0;
      border-radius: 8px;
      white-space: pre-wrap;
      word-break: break-word;
    }
    .user-message {
      background: #e3f2fd;
      margin-left: 20%;
    }
    .assistant-message {
      background: #f5f5f5;
      margin-right: 20%;
    }
    .typing {
      color: #666;
      font-style: italic;
    }
    #input-form {
      display: flex;
      gap: 10px;
      margin-top: 20px;
    }
    #user-input {
      flex: 1;
      padding: 10px;
      border: 1px solid #ddd;
      border-radius: 4px;
    }
    #send-btn {
      padding: 10px 20px;
      background: #1976d2;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
    #send-btn:disabled {
      background: #ccc;
    }
  </style>
</head>
<body>
  <div id="chat-container">
    <div id="messages"></div>
    <div id="input-form">
      <input type="text" id="user-input" placeholder="输入消息..." />
      <button id="send-btn">发送</button>
    </div>
  </div>

  <script>
    const messagesDiv = document.getElementById('messages');
    const userInput = document.getElementById('user-input');
    const sendBtn = document.getElementById('send-btn');
    let currentAssistantMessage = null;

    async function sendMessage() {
      const message = userInput.value.trim();
      if (!message) return;

      // 添加用户消息
      addMessage(message, 'user');
      userInput.value = '';
      sendBtn.disabled = true;

      // 创建助手消息容器
      currentAssistantMessage = document.createElement('div');
      currentAssistantMessage.className = 'message assistant-message';
      messagesDiv.appendChild(currentAssistantMessage);

      // 流式获取响应
      try {
        const response = await fetch('https://api.edgefn.net/v1/chat/completions', {
          method: 'POST',
          headers: {
            'Authorization': 'Bearer YOUR_API_KEY',
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            model: 'DeepSeek-R1-0528',
            messages: [{ role: 'user', content: message }],
            stream: true
          })
        });

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

        while (true) {
          const { done, value } = await reader.read();
          if (done) break;

          const chunk = decoder.decode(value, { stream: true });
          const lines = chunk.split('\n');

          for (const line of lines) {
            if (line.startsWith('data: ')) {
              const data = line.slice(6);
              if (data === '[DONE]') continue;

              try {
                const parsed = JSON.parse(data);
                const content = parsed.choices?.[0]?.delta?.content;
                if (content) {
                  currentAssistantMessage.textContent += content;
                  // 滚动到底部
                  window.scrollTo(0, document.body.scrollHeight);
                }
              } catch (e) {}
            }
          }
        }
      } catch (error) {
        currentAssistantMessage.textContent = '抱歉,发生错误: ' + error.message;
      } finally {
        sendBtn.disabled = false;
        currentAssistantMessage = null;
      }
    }

    function addMessage(content, role) {
      const div = document.createElement('div');
      div.className = `message ${role}-message`;
      div.textContent = content;
      messagesDiv.appendChild(div);
    }

    sendBtn.addEventListener('click', sendMessage);
    userInput.addEventListener('keypress', e => {
      if (e.key === 'Enter') sendMessage();
    });
  </script>
</body>
</html>

7. 最佳实践

7.1 适用场景

  • 实时对话:聊天机器人、客服系统
  • 长文本生成:文章写作、代码生成
  • 流式展示:打字机效果、实时翻译

7.2 不适用场景

  • 批量处理:需要一次性获取完整结果
  • 精确全文检索:需要处理完整响应后进行搜索
  • 服务端缓存:需要对完整响应进行缓存

7.3 注意事项

  1. 确保网络稳定:流式传输依赖持续连接
  2. 处理中断:实现断点续传或优雅降级
  3. 资源清理:流完成后释放 reader 资源
  4. 用户体验:避免过快刷新导致闪烁

8. 常见问题

Q1: 流式响应会影响输出内容吗?

不会。流式响应只是返回方式不同,模型生成的内容完全一致。

Q2: 为什么有时会收到空片段?

有时模型会返回不包含内容的片段(用于保持连接),这是正常现象。

Q3: 如何判断流式响应是否完成?

当收到 data: [DONE] 信号时,表示流式响应已完成。

Q4: 流式响应支持哪些编程语言?

任何支持读取流式响应的语言都可以使用,包括 JavaScript、Python、Go、Java 等。

Q5: 流式响应的延迟是多少?

首字节延迟通常在 200-500ms 之间,受网络、模型复杂度、服务器负载等因素影响。


联系方式:技术支持请发送邮件至 support@baishan.com文档最后更新时间:2025-08-28

贵州白山云科技