公司动态
AI Agent 前端自学路线(firstDay)
1.LLM 大模型、流式输出 SSE / Websocket前端重中之重对话页面核心在大模型LLM应用中流式输出是提升用户体验的核心技术它能将首字延迟从数十秒缩短至毫秒级实现“打字机”效果。针对 SSE (Server-Sent Events) 和 WebSocket 两种主流协议以下是基于 2026 年工程实践的深度解析与选型建议。a.核心协议对比与选型SSE 和 WebSocket 各有优劣选择取决于具体的交互场景和部署环境SSE (Server-Sent Events)基于 HTTP 协议的单向通信服务器→客户端。它是 LLM 文本生成场景的首选方案因为大模型输出通常是单向的。其优势在于开发复杂度极低、天然支持自动重连且能无缝穿透企业防火墙和 CDN无需特殊配置 。WebSocket独立的全双工通信协议。适用于需要高频双向交互的场景如实时语音对话允许用户打断 AI、多人协同编辑或复杂的 Agent 工具调用。虽然性能略优但需要手动处理心跳、重连和状态同步运维成本较高 。性能差异在 HTTP/2 普及的当下SSE 的延迟表现已非常接近 WebSocket。除非追求极致吞吐如微服务内部调用或必须双向通信否则 SSE 的工程性价比更高 。b.关键工程实现细节无论选择哪种协议在生产环境中都需注意以下核心要点以避免常见陷阱禁用反向代理缓冲在使用 Nginx 等反向代理时必须显式禁用缓冲如设置X-Accel-Buffering: no或proxy_buffering off否则数据会被缓存直到生成完毕导致流式效果失效用户仍看到“一次性返回” 。中断与资源释放当用户停止生成时后端必须立即感知并终止 LLM 推理任务释放显存KV Cache。WebSocket 和 gRPC 可通过流控制实现SSE 则需监听连接断开事件 。心跳与超时管理长连接需配置心跳机制以防超时断开。SSE 协议自带简易心跳而 WebSocket 需手动实现 ping/pong 逻辑gRPC 则有内置 keepalive 参数 。c.核心协议选择为什么首选 SSE在 90% 的 LLM 文本对话场景中SSE (Server-Sent Events) 是最佳选择。LLM 的特性单向通信服务端推 Token - 客户端展示。SSE 优势基于 HTTP天然穿透防火墙/CDN浏览器原生支持自动重连开发成本极低。WebSocket 适用场景仅当你需要实时语音双向对讲、高频中断控制或复杂 Agent 工具调用反馈时才考虑。d.SSE 实战从入门到生产级稳定1. 基础 API 使用 (EventSource)浏览器原生提供了EventSourceAPI这是最标准的实现方式。// 基本用法 const eventSource new EventSource(/api/chat/stream); eventSource.onmessage (event) { // event.data 是服务器推送的字符串 console.log(收到数据:, event.data); // 注意LLM 通常返回 JSON 字符串需要解析 try { const chunk JSON.parse(event.data); if (chunk.content) { appendToUI(chunk.content); } } catch (e) { // 处理非 JSON 数据或结束标记 [DONE] if (event.data [DONE]) { eventSource.close(); } } }; eventSource.onerror (err) { console.error(SSE 连接错误, err); // 浏览器会自动重连但你可以在这里做 UI 提示 };2. 关键痛点与解决方案痛点 A中文乱码与 UTF-8 截断现象EventSource默认假设数据是 UTF-8 文本。如果后端发送的二进制流被错误切割或者包含非 UTF-8 字符可能会乱码。解决确保后端设置Content-Type: text/event-stream; charsetutf-8。前端通常无需额外处理但若遇到乱码需检查后端是否直接传输了二进制 Buffer 而未正确编码。痛点 B消息格式规范规范SSE 要求每条消息以\n\n结尾。陷阱如果后端忘记加双换行前端会一直缓冲直到超时或连接关闭导致“一次性吐出”而非流式。调试技巧在浏览器 Network 面板查看 Response 流确认是否有连续的data: {...}\n\n。痛点 C自动重连与状态恢复机制SSE 断开后浏览器会自动重连并发送Last-Event-ID头。前端任务如果你希望断线后从上次位置继续后端需支持id字段。对于 LLM 对话通常不建议依赖此机制恢复长对话上下文因为后端可能已释放资源而是应在onerror中提示用户“连接中断请重试”。痛点 D携带认证信息限制原生EventSource不支持自定义 Header如Authorization: Bearer token。解决方案URL 参数传参new EventSource(/api/stream?tokenxyz)注意安全性Token 会出现在日志中。Cookie将 Token 放在 HttpOnly Cookie 中浏览器自动携带。Polyfill/Fetch 方案放弃原生EventSource使用fetchReadableStream手动实现见下文高级部分。e.高级方案Fetch ReadableStream推荐用于复杂场景当你需要自定义 Header、更好的错误处理或取消请求时原生EventSource不够用。现代前端应掌握基于fetch的手动流式解析。async function streamChat(prompt, signal) { const response await fetch(/api/chat/stream, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer your-token // 支持自定义 Header }, body: JSON.stringify({ prompt }), signal: signal // 支持 AbortController 取消请求 }); const reader response.body.getReader(); const decoder new TextDecoder(utf-8); let buffer ; while (true) { const { done, value } await reader.read(); if (done) break; // 解码字节流 buffer decoder.decode(value, { stream: true }); // 按行分割处理 SSE 格式 const lines buffer.split(\n); buffer lines.pop(); // 保留最后一行不完整的部分 for (const line of lines) { if (line.startsWith(data: )) { const jsonStr line.slice(6); if (jsonStr [DONE]) return; try { const chunk JSON.parse(jsonStr); handleChunk(chunk); // 更新 UI } catch (e) { console.warn(解析失败, line); } } } } } // 取消请求示例 const controller new AbortController(); streamChat(Hello, controller.signal); // 用户点击停止按钮时 // controller.abort();掌握要点TextDecoder处理流式字节到字符串的转换注意{ stream: true }选项以处理多字节字符如中文被截断的情况。Buffer 管理网络包可能截断 SSE 的行必须维护一个缓冲区只处理完整的行。AbortControllerLLM 生成时间长用户常需“停止生成”必须能立即中断网络请求并释放后端资源。f.WebSocket 实战仅在必要时使用如果项目强制要求 WebSocket你需要掌握心跳保活 (Ping/Pong)LLM 生成可能耗时数十秒中间无数据传输代理服务器如 Nginx可能因超时而切断连接。前端任务每隔 30s 发送{ type: ping }或监听后端的心跳。手动重连策略WebSocket 断开后不会自动重连。实现使用指数退避算法Exponential Backoff进行重连避免雪崩效应。消息队列与顺序保证网络抖动可能导致消息乱序。虽然 TCP 保证顺序但应用层可能需要处理“停止”指令优先于“后续 Token”的逻辑。g、 UI/UX 渲染优化让“打字机”更丝滑拿到数据只是第一步如何展示才是用户体验的关键。1. 防抖与批量渲染问题LLM 可能每秒推送 10-50 个 Token频繁操作 DOM 会导致页面卡顿。解决React/Vue不要每个 Token 都触发一次setState。可以使用requestAnimationFrame或简单的节流Throttle每 50-100ms 更新一次 UI。累积字符串在内存中累积完整文本定期刷新视图。2. Markdown 实时渲染挑战流式输出的 Markdown 是不完整的如**bold只有开头直接渲染会导致闪烁或样式错误。解决方案使用支持增量解析的 Markdown 库如react-markdown配合remark-gfm。技巧在渲染前对未闭合的标签进行临时补全如自动添加缺失的**或}或在 CSS 中隐藏未完成的块。3. 光标与滚动自动滚动确保容器在有新内容时自动滚动到底部但如果用户手动向上滚动查看历史应暂停自动滚动。光标效果在最后一个字符后添加一个闪烁的光标动画增强“正在生成”的心理暗示。4. 处理特殊内容代码块流式输出代码时语法高亮库可能在代码未完成时报错。建议仅在代码块闭合检测到 后才进行完整高亮过程中使用纯文本显示。思维链 (Reasoning)如果模型返回think.../think内容前端需识别并折叠/展开显示避免干扰主回答。h.异常处理与兜底超时处理设置前端超时如 60s。如果首字延迟过高提示用户“网络繁忙”。不完整 JSON 处理网络中断可能导致最后一个 JSON 片段截断。前端解析时需try-catch忽略最后一条无效数据。敏感词过滤如果后端返回的内容包含被拦截标记前端需友好提示“内容不符合规范”而不是直接报错崩溃。