🌊 Streaming Response:响应流式输出

等AI回复就像等外卖——流式输出让你看着它"做",不用等它"做完"

📅 2026-04-28 🏷️ 响应模式 ⏱️ 阅读约8分钟 ⚡ 实时体验

什么是 Streaming Response?

传统模式下,你问AI一个问题,它思考10秒,然后一次性把答案甩给你——这10秒你盯着屏幕发呆。Streaming Response打破了这个等待:AI一边思考一边输出,你看着文字像打字机一样逐字显现。

以前点外卖,下单后盯着进度条转圈圈,30分钟后突然"您的餐已送达"——这是传统模式。

现在外卖App实时显示:商家接单→备菜中→烹饪中→骑手取餐→骑行中→即将送达——每一步你都看得见。这就是Streaming。

好处不只是不焦虑,还有即时反馈:如果你发现AI输出的第一句话就跑偏了,可以立刻打断,不用等它整篇废话写完。

传统 vs 流式对比

❌ 传统模式

延迟感:等10秒才开始显示

无法干预:生成过程不可见

内存占用:完整响应后才处理

用户焦虑:怀疑是不是卡住了

✅ 流式模式

即时反馈:100ms就开始显示

可打断:随时可以停止生成

内存友好:边生成边消费

体验流畅:看着AI"思考"

技术实现对比

// 传统模式:请求 → 等待 → 完整响应
const response = await openai.chat.completions.create({
  messages: [{role: "user", content: "写首诗"}]
});
// 用户等了5秒...
console.log(response.choices[0].message.content);

// 流式模式:请求 → 立即开始接收 → 逐块显示
const stream = await openai.chat.completions.create({
  messages: [{role: "user", content: "写首诗"}],
  stream: true  // 开启流式
});
// 100ms后就开始输出
for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content || "");
}
// 输出:春眠...不觉晓...处处闻...啼鸟...

流式输出原理

📡 SSE (Server-Sent Events)

# 服务端推送格式
data: {"content": "春眠", "done": false}
data: {"content": "不觉", "done": false}
data: {"content": "晓", "done": false}
data: {"content": "", "done": true}

# 客户端接收
eventSource.onmessage = (event) => {
  const data = JSON.parse(event.data);
  if (!data.done) {
    outputDiv.textContent += data.content;
  }
};

🔌 WebSocket(双向通信)

// WebSocket更适合需要交互的场景
const ws = new WebSocket("wss://api.example.com/stream");

ws.onmessage = (event) => {
  const chunk = JSON.parse(event.data);
  appendToOutput(chunk.content);
};

// 用户可以随时打断
stopButton.onclick = () => {
  ws.send(JSON.stringify({action: "stop"}));
};
选择建议:纯展示用SSE(简单高效),需要交互(打断、追问)用WebSocket。OpenClaw主要用SSE,因为大多数Agent场景是单向输出。

OpenClaw 流式配置

启用流式输出

# Agent配置中启用
response:
  streaming: true
  chunk_size: "auto"  # auto | token | sentence
  buffer_ms: 50  # 缓冲50ms后发送

# 客户端调用
const response = await agent.run(prompt, {
  stream: true,
  onChunk: (chunk) => { console.log(chunk); },
  onComplete: (full) => { console.log("Done!"); }
});

Tool Call 流式进度

# 工具执行过程的实时反馈
tools:
  - name: search_web
    stream_progress: true  # 显示执行进度
    progress_messages:
      - "正在搜索..."
      - "分析结果中..."
      - "整理答案中..."
踩坑:SSE在Nginx默认配置下可能超时断开。解决方案:设置proxy_read_timeout和proxy_bufferingproxy_read_timeout 86400s; proxy_buffering off;

Chunk处理最佳实践

📦 Chunk大小选择

# 按Token切分(精确控制)
chunk_size: token
chunk_tokens: 5  # 每5个token发送一次

# 按句子切分(更自然)
chunk_size: sentence  # 完整句子再发送

# 自动模式(平衡性能和体验)
chunk_size: auto  # OpenClaw智能调整

🔄 前端渲染技巧

// Markdown流式渲染
let fullContent = "";

function handleChunk(chunk) {
  fullContent += chunk;
  // 使用debounce减少渲染频率
  clearTimeout(renderTimer);
  renderTimer = setTimeout(() => {
    outputDiv.innerHTML = marked.parse(fullContent);
  }, 100);
}

🛑 中断生成

// 用户主动中断
const controller = new AbortController();

const stream = agent.run(prompt, {
  stream: true,
  signal: controller.signal
});

// 用户点击"停止生成"
stopBtn.onclick = () => controller.abort();

// 已接收的内容仍然保留
// 不必浪费已经生成的部分

性能优化

Buffer策略:不要每个token都发送网络请求,设置合理buffer

渲染节流:前端用debounce控制渲染频率(50-100ms)

渐进渲染:先显示纯文本,再逐步渲染Markdown格式

错误恢复:网络断开时缓存已接收内容,重连后继续

超时处理:设置stream timeout,避免无限等待

另一个坑:流式输出时Markdown代码块可能渲染不完整(收到开头```但还没收到结尾```)。解决方案:不完整的代码块先不渲染,等闭合后再显示

相关链接