🌊 OpenClaw 流式响应处理

用户等了3秒,屏幕还是白的。他开始怀疑是不是卡死了。然后突然蹦出一大段文字。这不是好的用户体验。流式响应让AI像在跟你聊天——说一句,你听一句。

为什么需要流式响应

传统API调用是这样的:

  1. 用户发送请求
  2. 等待...(可能5-30秒)
  3. 一次性返回完整结果

问题在于:等待焦虑。用户不知道是网络慢、服务器忙、还是真的卡死了。

流式响应是这样的:

  1. 用户发送请求
  2. 0.5秒后,第一个字出现
  3. 持续输出,像打字一样
  4. 用户知道"它在工作"

SSE(Server-Sent Events)

SSE是最简单的流式方案。单向:服务器推给客户端。适合"AI说话,用户听"的场景。

// OpenClaw SSE 流式响应
const response = await fetch('/api/chat', {
  method: 'POST',
  body: JSON.stringify({ message: '讲个笑话' })
});

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);
  // 实时显示每个chunk
  displayText(chunk);
}

SSE数据格式

data: {"text": "为什么"}

data: {"text": "程序员"}

data: {"text": "喜欢"}

data: {"text": "黑暗?"}

data: [DONE]

WebSocket 双向流

WebSocket是双向的。适合"AI和用户轮流说话"的场景,比如实时对话。

// OpenClaw WebSocket 连接
const ws = new WebSocket('wss://api.openclaw.ai/ws');

ws.onopen = () => {
  ws.send(JSON.stringify({
    type: 'chat',
    message: '开始对话'
  }));
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  
  switch (data.type) {
    case 'token':
      // 实时追加文字
      appendText(data.content);
      break;
    case 'tool_call':
      // 显示工具调用
      showToolCall(data.tool, data.args);
      break;
    case 'done':
      // 对话完成
      finalizeResponse();
      break;
  }
};

OpenClaw 流式配置

启用流式输出

# agent.yaml
model:
  stream: true
  stream_options:
    include_usage: true  # 包含token使用统计
    
output:
  format: stream
  buffer_size: 100  # 每100字符flush一次

处理流式响应

// OpenClaw SDK 流式调用
import { OpenClaw } from '@openclaw/sdk';

const client = new OpenClaw({ apiKey: process.env.OPENCLAW_KEY });

const stream = await client.chat.stream({
  messages: [{ role: 'user', content: '写一首诗' }],
  agent: 'creative-writer'
});

for await (const chunk of stream) {
  process.stdout.write(chunk.content);  // 实时输出
}

console.log('\n完成!');

用户体验优化

打字机效果

// 打字机效果 - 模拟真人打字
function typewriterEffect(text, element, speed = 30) {
  let index = 0;
  
  const interval = setInterval(() => {
    if (index < text.length) {
      element.textContent += text[index];
      index++;
    } else {
      clearInterval(interval);
    }
  }, speed);
}

进度指示

// 显示思考过程
function showThinking() {
  const thoughts = [
    '正在理解你的问题...',
    '分析相关上下文...',
    '生成回答中...',
    '优化表达...'
  ];
  
  let index = 0;
  const interval = setInterval(() => {
    updateStatus(thoughts[index % thoughts.length]);
    index++;
  }, 2000);
  
  return () => clearInterval(interval);
}

工具调用可视化

当Agent调用工具时,告诉用户它在干什么:

// 工具调用状态显示
ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  
  if (data.type === 'tool_start') {
    showNotification(`🔧 正在调用 ${data.tool}...`);
  } else if (data.type === 'tool_result') {
    showNotification(`✅ ${data.tool} 完成`);
  }
};

错误处理

流中断

// 处理流中断
const stream = await client.chat.stream({ messages });

try {
  for await (const chunk of stream) {
    process.stdout.write(chunk.content);
  }
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('\n用户取消了请求');
  } else {
    console.log('\n流式响应出错:', error.message);
  }
}

超时处理

// 流式超时
const timeout = setTimeout(() => {
  stream.abort();
  showFallback();  // 显示降级内容
}, 30000);  // 30秒超时

stream.on('end', () => clearTimeout(timeout));

最佳实践

SSE vs WebSocket 对比

特性SSEWebSocket
方向单向(服务器→客户端)双向
协议HTTPWS/WSS
重连自动手动
兼容性好(HTTP)可能被防火墙拦截
适用场景AI输出、通知实时对话、游戏

最后更新:2026-04-29 | 作者:妙趣AI