Chunk Aggregator
聚合已解码的文本增量,平衡发送频率和显示延迟;说明按目标隔离、尾部 flush、定时器清理与工具参数累积的边界。
#concept
#evergreen
#ai
#architecture
[!info] related notes
- 所属 MOC: LLM Streaming MOC
- 架构位置: LLM Streaming 分层架构
- 上游: LLM 输出四层模型(通常已解码为 text deltas)
- 下游: Semantic Router
- 相关: Function Calling 流式累积
Chunk Aggregator
一句话定义
Chunk Aggregator 将同一目标的文本增量合并为较大的片段,以平衡事件开销与显示延迟。输入通常是 Provider 已解码的文本,不是 tokenizer 的原始 token ID;它是可选策略,不保证每个输出片段都有完整语义。
核心机制
为什么需要这一层
当输入文本片段较小时,可以选择透传或聚合:
delta: "你" → 可逐片显示,也可能增加渲染次数
delta: "好"
delta: "世"
delta: "界"
delta: "。"
Chunk Aggregator 将它们聚合为语义单元:
chunk: "你好世界。" → 减少更新次数,但须等到句号
聚合策略
按标点聚合
一种策略是遇到句号、感叹号、问号、换行符时发送 chunk;无标点长文本还需时间或长度上限。
"你好" + "世界" + "。" → "你好世界。"
按 Markdown 块聚合
适合需要渲染 Markdown 的场景。代码块、列表、标题等作为完整 chunk 发送。
"```python\n" + "print('hello')\n" + "```" → 整个代码块作为一个 chunk
按 function call boundary 聚合
工具调用的开始/结束作为 chunk 边界。
text delta → text chunk
[tool_call 开始] → tool_call chunk(切换通道)
tool_call delta → 累积到 tool_call chunk
[tool_call 结束] → 完整 tool_call chunk
按时间窗口聚合
每隔 N 毫秒发送一次累积的 chunk。适合需要控制帧率的场景。
let buffer = ""
const timer = setInterval(() => {
if (buffer) {
emit(buffer)
buffer = ""
}
}, 50) // 示例窗口,不是精确定时或 UI 帧率保证
// 完成时先发送剩余 buffer,再 clearInterval(timer)。
// 失败/取消时清理 timer,并按协议决定如何保留未完成内容。
按长度聚合
累积到一定字符数后发送。
// JS length 计 UTF-16 code units,不是用户感知字符数。
if (buffer.length >= 10) {
emit(buffer)
buffer = ""
}
实现示例
class ChunkAggregator {
private buffer = ""
constructor(private readonly onChunk: (chunk: string) => void) {}
addText(delta: string) {
this.buffer += delta
// 发现标点就发送整个 buffer;不声称精确按句切分。
if (/[。!?\n]/.test(delta)) {
this.flush()
}
}
flush() {
if (this.buffer) {
const chunk = this.buffer
this.buffer = ""
this.onChunk(chunk)
}
}
}
调用方必须在正常结束时调用 flush(),否则无末尾标点的文本会留在 buffer。示例假定同步回调不抛错,不含超时、背压与重试;每个消息/通道应使用独立实例。
最小场景
按延迟需求选择逐片或聚合显示:
// 逐片显示
onDelta(delta => render(buffer += delta))
// 聚合后显示
onChunk(chunk => render(buffer += chunk))
char-level split 问题
显示卡顿可能来自输入节奏、代理缓冲、事件过密或渲染开销,不能只凭逐字显示就判定缺少后端聚合。应分别观察发送、接收与渲染时序。
可根据瓶颈在后端合并事件或在前端批量渲染;聚合会增加等待时间,不能无条件改善体验。
边界与易混淆点
- Chunk Aggregator 不是必须的:可直接消费 text delta,是否聚合取决于实际开销。
- 聚合粒度需要平衡:细粒度增加更新开销,粗粒度增加等待;应设最大等待与缓冲上限。
- 和 Function Calling 流式累积的关系:Function Calling 的 arguments 累积也是一种 chunk aggregation,只是聚合的是 tool_call 的 JSON 碎片。
- 可以在前端做:后端发送文本增量,前端在浏览器端聚合。后端聚合减少事件封装次数,不减少正文字符总量;前端聚合只减少渲染次数。