流式响应优化指南
本文档详细介绍白山智算平台 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 注意事项
- 确保网络稳定:流式传输依赖持续连接
- 处理中断:实现断点续传或优雅降级
- 资源清理:流完成后释放 reader 资源
- 用户体验:避免过快刷新导致闪烁
8. 常见问题
Q1: 流式响应会影响输出内容吗?
不会。流式响应只是返回方式不同,模型生成的内容完全一致。
Q2: 为什么有时会收到空片段?
有时模型会返回不包含内容的片段(用于保持连接),这是正常现象。
Q3: 如何判断流式响应是否完成?
当收到 data: [DONE] 信号时,表示流式响应已完成。
Q4: 流式响应支持哪些编程语言?
任何支持读取流式响应的语言都可以使用,包括 JavaScript、Python、Go、Java 等。
Q5: 流式响应的延迟是多少?
首字节延迟通常在 200-500ms 之间,受网络、模型复杂度、服务器负载等因素影响。
联系方式:技术支持请发送邮件至 support@baishan.com文档最后更新时间:2025-08-28
