websocket流式问答需连接后立即用ws.send()发送含message_id的精简请求体,后端将其转为大模型流式调用并分帧推送start/中间content/end事件,前端按seq校验、批量追加text节点渲染,避免丢包与截断。

WebSocket连接建立后,如何正确发送AI请求消息
流式问答不是靠“等响应”实现的,而是靠“发完就收”。连接建立后,前端必须立刻用 ws.send() 发送结构清晰的请求体,否则后端无从触发流式调用。
常见错误是把 WebSocket 当成 HTTP:先发请求、再等整个响应回来。这完全违背流式逻辑——WebSocket 连接一旦打开,通信就是持续的,send() 只是启动信号,后续数据由后端主动推送。
- 请求体必须包含唯一
message_id,用于前后端日志对齐和异常追溯 - 避免在请求中携带冗余字段(如
timestamp、user_agent),这些信息应在连接握手阶段通过 URL 参数或 HTTP Header 传递 - 如果使用 SpringBoot + STOMP,不要误用
@MessageMapping处理流式响应——它只适合单次消息,流式必须走原生WebSocketSession的sendMessage()
后端收到请求后,怎样对接大模型的流式API
关键不是“怎么调”,而是“怎么转”。后端本身不生成文本,它只是管道:把 WebSocket 请求 → 转成大模型 SDK 的流式调用 → 把每个 chunk 封装后立刻推回前端。
以调用 OpenAI 的 chat.completions.create(stream=True) 为例,容易踩的坑是直接把原始 delta.content 原样发给前端。实际要处理三类内容:
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 首帧需带
event: start或标记is_first: true,让前端知道回答开始,可清空 loading 状态 - 中间帧只传
content字段(纯字符串),不要嵌套choices[0].delta.content这种 SDK 内部结构 - 结束帧必须显式发送
event: end或is_final: true,否则前端无法判断流是否真正结束,可能卡在“正在输入”状态
前端如何逐字符渲染,避免 DOM 频繁重排
用户看到的“打字机效果”不是靠 setTimeout 模拟出来的,而是真实接收、真实追加。但直接对每个 chunk 都执行 element.innerHTML += chunk 会导致严重性能问题。
真正高效的做法是攒批 + requestIdleCallback:
- 用
Array<string></string>缓存连续收到的chunk,每 3–5 个或累计达 20 字符时批量插入 - 插入操作放在
requestIdleCallback中,避免阻塞主线程影响滚动或输入 - 绝对不要在
onmessage里调用innerHTML或innerText——改用Text节点追加:node.append(document.createTextNode(chunk))
连接异常时,chunk 丢失怎么感知和补偿
WebSocket 是可靠传输,但不是“不丢包”。网络抖动时,某个 chunk 可能未送达,而下一个又到了,导致文字跳字或乱序。这不是前端 bug,是协议层特性。
解决思路不是重传(WebSocket 不支持),而是靠服务端加序号 + 前端校验:
- 后端每个推送消息必须带单调递增的
seq字段,例如{"seq": 12, "content": "今"} - 前端维护一个
lastReceivedSeq,收到新seq时检查是否等于lastReceivedSeq + 1;若不等,说明丢包,立即触发重连并清空当前回答缓存 - 不要依赖
onerror或onclose来判断丢包——它们只反映连接级异常,chunk 级丢失静默发生,必须靠序号机制主动发现
finish_reason 可能是 stop、length、content_filter,每种对应不同语义。前端仅靠收到 is_final: true 就停止等待,可能截断合法回答;后端若不透传该字段,前端就无法区分“答完了”还是“被截断了”。这个细节不处理,用户就会遇到“话说到一半突然没了”的体验断层。










