Skip to content

使用AI聊天的时候,字儿是咋一个一个蹦出来的?

· 5 min

当你盯着AI聊天界面,看着文字一个字一个字蹦出来时; 表面:哇,好智能! 内心OS:这玩意儿怎么不卡顿?请求不会超时吗?

欢迎来到SSE流式输出的世界 - 这里没有魔法,只有协议

一、SSE:不是WebSocket的表弟#

想象两个场景:

  1. 传统HTTP:打电话问朋友午饭吃什么,他说”我想想…(10秒)…吃拉面吧” - 全程静音等待
  2. SSE流式输出:同个问题,朋友实时碎碎念:“呃…火锅?…太贵…烤肉?…排队…算了…还是拉面!“
// 真实世界中的SSE
const sse = new EventSource('/api/food-suggestion');
sse.onmessage = e => {
// 输出流:
// data: 正在思考...
// data: 排除选项:火锅
// data: 最终决定:拉面(加蛋)
};

二、sse方案对比(技术选型)#

1. EventSource:浏览器原生工具人#

适用场景:

// 监控服务器状态这类只读操作
const serverMonitor = new EventSource('/api/server-status');
serverMonitor.addEventListener('cpu', e => {
console.log(`CPU负载:${e.data}%`);
// 当数值>90时,GG 😱
});

2. Fetch + ReadableStream:手工耿的焊枪#

代码实战:

// 给AI发送量子物理问题的正确姿势
fetch('/api/quantum-physics', {
method: 'POST',
body: JSON.stringify({ question: "薛定谔的猫死了吗?" }),
headers: { 'X-XX-XXX': 'true' }
}).then(async res => {
const reader = res.body.getReader();
const decoder = new TextDecoder();
while(true) {
const { done, value } = await reader.read();
if(done) break;
// 逐字解析AI的哲学思考
const text = decoder.decode(value);
console.log(text); // 输出:既...死...又...活...
}
});

最佳场景:需要提交复杂参数(比如让AI写3000字论文提纲)

3. fetch-event-source:自动步枪#

企业级用法:

import { fetchEventSource } from '@microsoft/fetch-event-source';
// 和AI讨论核聚变技术的安全方案
await fetchEventSource('/api/nuclear-fusion', {
method: 'POST',
headers: { 'Authorization': 'Bearer top-secret' },
body: JSON.stringify({ clearanceLevel: 5 }),
onmessage: (event) => {
if(event.id === 'WARNING') {
// AI说:建议先购买人身保险
console.warn(event.data);
}
}
});

适用场景:需要Token验证的敏感业务(比如让AI计算年终奖…)

4. WebSocket伪装术:航母送外卖#

迷惑行为大赏:

const ws = new WebSocket('wss://api/ai-chat');
ws.onmessage = (e) => {
/*
实际传输的数据:
{"type":"sse","data":"为什么用WebSocket模拟SSE?"}
{"type":"sse","data":"因为PM说'反正都是流嘛'"}
*/
};

使用建议:当CTO说”就用现有WebSocket吧”时的妥协方案

三、方案选型决策树#

image.png

四、SSE翻车现场实录#

案例1:忘记关闭连接

// 组件卸载时
useEffect(() => {
const sse = new EventSource('/api/chat');
// 忘记写清理函数的结果:
// 用户离开页面后,AI还在后台自言自语
// 服务器日志:《编号89751》已持续连接18小时...
}, []);

案例2:错误的内容类型

// 服务端错误响应
HTTP/1.1 200 OK
Content-Type: application/json // 应该是text/event-stream!
{"message": "说好的流式输出呢?..."}

案例3:代理服务器缓冲

Terminal window
location /chat {
proxy_pass http://ai-backend;
proxy_buffering on; // 默认开启的灾难
# 结果:数据攒到4KB才发送,用户看AI在"思考人生"
}

五、终极建议#

选择SSE方案就像选外卖:

最后提醒:
当AI回复”让我想想…”时
它可能不是真的在思考 😉
而是你的SSE配置出问题了