数据截至 (上游 commit 8d6cbee1b527)
回复流水线:指令、运行队列与分块投递
30 秒导读: 上一章把入站消息归一化并算出了会话键(03)。本章接手之后的全部编排:这条消息要不要进模型(指令快路径)、现在能不能跑(每会话运行队列)、模型吐出来的东西怎么变成一条条聊天消息(分块流式投递)、投失败了怎么办(pending final 兜底)。这是
src/auto-reply/这个子系统的本体。
1. 先把坐标定下来
1.1 本章负责的那一段
一条消息从进门到出门要过三个大关,本 章是中间那一关:
[03 入站与会话] >>> 本章 04 <<< [05 智能体运行时]
通道归一化 dispatch 编排 模型循环
安全闸 指令层 失败转移
会话路由 运行队列 上下文压缩
──────────► SessionKey ──────────► 提示词 ──────────►
◄────────── 文本流 ◄──────────
分块 / 去重 / 投递
1.2 一句话定义
回复流水线 = 一个「一进多出」的编排器。
进来的是一条入站消息;出去的可能是 0 条、1 条,也可能是几十条— —工具进度、流式文本块、最终答案、TTS 音频,全都是独立的出站消息。这个「一变多」以及「多条之间的顺序、去重、失败重试」,就是本章要讲的全部难点。
1.3 三个必须先记住的名词
这三个词在源码里一词一义,后文不换说法:
| 名词 | 是什么 | 定义处 |
|---|---|---|
| dispatcher | 出站投递器。持有一条串行发送链,暴露 sendToolResult / sendBlockReply / sendFinalReply 三个入口 | src/auto-reply/reply/reply-dispatcher.types.ts:53 ReplyDispatcher |
| ReplyPayload | 通道无关的一条出站消息(文本 + 媒体 + 呈现 + 投递偏好) | src/auto-reply/reply-payload.ts:33 |
| run(运行) | 一次模型调用的完整生命周期,按 sessionId 注册在运行注册表里 | src/auto-reply/reply/reply-run-registry.ts |
2. 顶层全景
2.1 主链路
从左到右是调用顺序;虚线箭头是回流(模型产出经 dispatcher 反向出站)。
通道插件 / 网关 RPC / 心跳
│
▼
① dispatch 入口层 src/auto-reply/dispatch.ts
裸 / 缓冲 / 指定 三选一
│
▼
② dispatchReplyFromConfig reply/dispatch-from-config.ts(装配)+ dispatch-from-config.*.ts(分段实现)
钩子、抑制策略、回调装配
│
▼
③ getReplyFromConfig reply/get-reply.ts
指令快路径 → 指令解析 → 会话状态
│
▼
④ runPreparedReply reply/get-reply-run.ts
队列裁决(跑 / 插队 / 排队 / 丢)
│
▼
⑤ runReplyAgent ─────────► [05 模型循环] (reply/agent-runner-run.ts)
│
╎ 流式回调
▼
⑥ 分块流水线 reply/block-reply-pipeline.ts
切分 → 合并 → 去重
│
▼
⑦ dispatcher.sendXxx ──► deliver() ──► 通道