LLM Streaming 分层架构
一种 LLM Streaming 职责划分:Token Stream → Chunk Aggregator → Semantic Router → Event Transport Adapter → Frontend State Manager,在稳定契约下隔离改动。
#synthesis
#evergreen
#ai
#architecture
[!info] related notes
- 所属 MOC: LLM Streaming MOC
- 各层概念: Chunk Aggregator, Semantic Router
- 输出模型: LLM 输出四层模型
- 协议设计: Delta Stream, Append Event Stream
- 前端消费: 前端 SSE 消费
- SSE 管道: 多服务 SSE 管道
LLM Streaming 分层架构
范围
描述一种从 Provider 接口到 UI 投影的职责划分,不是所有生产系统的标准架构;持久化、执行授权与恢复等还需独立设计。
为什么要分层
一个简单的 SSE demo 可以不做分层——直接消费 Provider 的文本增量。但生产系统需要:
- 聚合:按事件开销与显示延迟选择是否合并文本增量
- 分流:输出不只有文本,还有工具调用、结构化数据、推理过程
- 统一协议:前端、日志、监控、回放系统需要统一的事件格式
- 状态管理:前端需要集中管理流式状态,而不是散落在各组件
五层架构
┌─────────────────────────────────┐
│ ① Token Stream Layer │ 模型层:适配 Provider 事件
│ (OpenAI / Claude / Qwen) │
└──────────────┬──────────────────┘
↓
┌─────────────────────────────────┐
│ ② Chunk Aggregator │ 聚合层:合并同目标文本增量
│ (按标点/Markdown/tool边界) │
└──────────────┬──────────────────┘
↓
┌─────────────────────────────────┐
│ ③ Semantic Router │ 路由层:按类型分流输出
│ (text/tool/thinking/cite) │
└──────────────┬──────────────────┘
↓
┌─────────────────────────────────┐
│ ④ Event Transport Adapter │ 传输层:统一协议输出
│ (Structured Event Stream) │
└──────────────┬──────────────────┘
↓
┌─────────────────────────────────┐
│ ⑤ Frontend State Manager │ 状态层:管理 UI 状态
│ (Zustand / Redux / XState) │
└─────────────────────────────────┘
各层职责详解
① Token Stream Layer(模型层)
职责:适配 Provider 返回的文本、工具等事件;通常不是原始 token ID。
输入:OpenAI / Claude / Qwen 等 provider 的 streaming API
输出:
delta: "你"
delta: "好"
关键点:
- 文本片段不必与模型 token 或网络字节块一一对应
- 有些 provider 已经做了初步聚合
- 这一层通常由 LLM Provider SDK 封装
② Chunk Aggregator(聚合层)
职责:按目标消息/通道合并文本增量,必要时设置时间与长度上限。
聚合策略:
- 按标点(句号、换行)
- 按 Markdown 块(代码块、列表)
- 按 function call boundary
- 按时间窗口(每 N ms 发送一次)
- 按长度(累积 N 个字符后发送)
输出:
chunk: "你好世界。"
详见 Chunk Aggregator。
③ Semantic Router(路由层)
职责:根据内容类型将输出分流到不同处理通道。
分流目标:
| 通道 | 目标 |
|---|---|
text | 主聊天 UI |
tool | 后端执行器 + 工具状态 UI |
thinking | 仅展示接口允许公开的摘要;折叠不是保密机制 |
citation | 引用面板 |
structured | 专用 UI 组件 |
详见 Semantic Router。
④ Event Transport Adapter(传输层)
职责:将应用事件编码为 SSE / WebSocket 输出。Event Bus 可以作为内部事件分发机制,但不等同于网络传输。
协议格式(推荐 Structured Streaming):
{ "type": "message.delta", "channel": "text", "data": { "content": "你好" } }
{ "type": "tool_call.started", "channel": "tool", "data": { "name": "search" } }
{ "type": "message.completed", "channel": "text", "data": { "message_id": "m1" } }
传输方式选择:
| 方式 | 适用场景 |
|---|---|
| SSE | 基于 HTTP 的服务端事件流格式,可由 EventSource 或 fetch 消费 |
| WebSocket | 同一连接双向通信;SSE 也可配合另一路 HTTP 控制请求 |
| 其他 HTTP Streaming | fetch + ReadableStream 消费 JSONL 等格式,需自行解析 |
⑤ Frontend State Manager(状态层)
职责:集中管理流式状态,供 UI 组件消费。
状态结构示意(Message、ToolCall 由应用定义;并发消息需独立 buffer,任务终态也需另建模):
interface UIState {
messages: Message[], // 消息列表
streamingBuffer: string, // 当前正在生成的文本 buffer
toolCalls: ToolCall[], // 工具调用列表
extractedInfo: object, // 结构化提取信息
status: "idle" | "streaming" | "error" // 流状态
}
推荐工具:
- Zustand(轻量,适合中小项目)
- Redux Toolkit(成熟,适合大型项目)
- XState(状态机,适合复杂状态流转)
依赖路径
Token Stream → Chunk Aggregator → Semantic Router → Event Transport Adapter → Frontend State Manager
↑ ↑ ↑ ↑ ↑
LLM Provider 聚合策略 分流规则 传输协议 状态模型
稳定契约可以减少改动范围,但下列隔离均有条件:
- 换 LLM Provider → 若事件语义兼容,可主要改第 ① 层
- 优化聚合策略 → 主要改第 ② 层,同时验证延迟与缓冲
- 增加新的输出类型 → 改第 ③ 层 + 前端 reducer
- 从 SSE 切到 WebSocket → 修改传输适配及客户端连接/恢复逻辑
- 换状态管理库 → 主要改第 ⑤ 层
对比与易混淆点
- 分层不是必须的:简单场景可以跳过某些层(比如不需要 Semantic Router,直接透传 text)。
- 层与层之间不是严格串行:有些事件可能跳过 Chunk Aggregator 直接到 Semantic Router(比如 tool_call 事件)。
- 聚合不能保证改善卡顿;需区分 Provider 节奏、代理缓冲和前端渲染瓶颈。
- 重连不自动恢复业务状态,EOF 不等于任务完成;恢复需要明确的序号范围、保留日志和终态契约。