🌊 OpenClaw 流式响应处理
用户等了3秒,屏幕还是白的。他开始怀疑是不是卡死了。然后突然蹦出一大段文字。这不是好的用户体验。流式响应让AI像在跟你聊天——说一句,你听一句。
为什么需要流式响应
传统API调用是这样的:
- 用户发送请求
- 等待...(可能5-30秒)
- 一次性返回完整结果
问题在于:等待焦虑。用户不知道是网络慢、服务器忙、还是真的卡死了。
流式响应是这样的:
- 用户发送请求
- 0.5秒后,第一个字出现
- 持续输出,像打字一样
- 用户知道"它在工作"
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));
最佳实践
- 首字节快速响应 - 500ms内开始输出,让用户知道系统在工作
- 合理的chunk大小 - 太小频繁更新DOM,太大不够流畅。推荐50-100字符
- 显示思考过程 - 长时间任务(如代码生成)显示进度
- 工具调用透明 - 让用户知道AI在调用什么工具
- 优雅降级 - 流式失败时回退到普通请求
- 取消支持 - 用户随时可以停止生成
SSE vs WebSocket 对比
| 特性 | SSE | WebSocket |
|---|---|---|
| 方向 | 单向(服务器→客户端) | 双向 |
| 协议 | HTTP | WS/WSS |
| 重连 | 自动 | 手动 |
| 兼容性 | 好(HTTP) | 可能被防火墙拦截 |
| 适用场景 | AI输出、通知 | 实时对话、游戏 |
相关链接
最后更新:2026-04-29 | 作者:妙趣AI