流式输出与分块
OpenClaw 有两个独立的流式层:
- 块流(渠道): 在助手写作过程中逐块发出已完成的内容块。这些是普通的渠道消息(不是 token 级增量)。
- 预览流(Telegram/Discord/Slack): 生成过程中更新一条临时的预览消息。
目前没有真正的 token 增量流式推送到渠道消息。预览流是基于消息的(发送 + 编辑/追加)。
块流(渠道消息)
块流在助手输出可用时,以较粗粒度的块发送出去。
Model output
└─ text_delta/events
├─ (blockStreamingBreak=text_end)
│ └─ chunker emits blocks as buffer grows
└─ (blockStreamingBreak=message_end)
└─ chunker flushes at message_end
└─ channel send (block replies)
图例说明:
text_delta/events:模型流事件(非流式模型可能比较稀疏)。chunker:EmbeddedBlockChunker,按最小/最大边界 + 断点偏好处理。channel send:实际的出站消息(块回复)。
控制项:
agents.defaults.blockStreamingDefault:"on"/"off"(默认 off)。- 渠道覆盖:
*.blockStreaming(及按账号变体)可以按渠道强制"on"/"off"。 agents.defaults.blockStreamingBreak:"text_end"或"message_end"。agents.defaults.blockStreamingChunk:{ minChars, maxChars, breakPreference? }。agents.defaults.blockStreamingCoalesce:{ minChars?, maxChars?, idleMs? }(发送前合并流式块)。- 渠道硬上限:
*.textChunkLimit(如channels.whatsapp.textChunkLimit)。 - 渠道分块模式:
*.chunkMode(length默认,newline在空行(段落边界)处分割后再按长度分块)。 - Discord 软上限:
channels.discord.maxLinesPerMessage(默认 17)分割过长回复以避免 UI 截断。
边界语义:
text_end:chunker 产出时即刻发送块;每个text_end时刷新。message_end:等待助手消息完成后再刷新缓冲区输出。
message_end 在缓冲文本超过 maxChars 时仍会使用 chunker,因此结尾可能发出多个块。
分块算法(低/高边界)
块分块由 EmbeddedBlockChunker 实现:
- 低边界: 缓冲区未达到
minChars时不发送(除非强制)。 - 高边界: 优先在
maxChars之前分割;被迫时在maxChars处硬切。 - 断点偏好:
paragraph→newline→sentence→whitespace→ 硬切。 - 代码围栏: 不在围栏内分割;强制在
maxChars分割时,会关闭 + 重新打开围栏以保持 Markdown 格式正确。
maxChars 会被限制在渠道的 textChunkLimit 以内,所以不会超过各渠道的上限。
合并(合并流式块)
启用块流时,OpenClaw 可以在发送前合并连续的块。这减少了”一行一行刷屏”的问题,同时仍提供渐进式输出。
- 合并会等待空闲间隔(
idleMs)后再刷新。 - 缓冲区受
maxChars限制,超出时立即刷新。 minChars防止过小的片段发出,直到积累了足够的文本(最后的刷新始终发送剩余文本)。- 连接符由
blockStreamingChunk.breakPreference派生(paragraph→\n\n,newline→\n,sentence→ 空格)。 - 可通过
*.blockStreamingCoalesce按渠道覆盖(包括按账号配置)。 - Signal/Slack/Discord 的默认合并
minChars会提高到 1500(除非被覆盖)。
块间的类人节奏
启用块流后,你可以在块回复之间(首块之后)添加随机暂停。这让多条消息的回复感觉更自然。
- 配置:
agents.defaults.humanDelay(可按 agent 覆盖:agents.list[].humanDelay)。 - 模式:
off(默认)、natural(800–2500ms)、custom(minMs/maxMs)。 - 仅适用于块回复,不影响最终回复或工具摘要。
“流式分块还是一次性发送”
对应关系:
- 流式分块:
blockStreamingDefault: "on"+blockStreamingBreak: "text_end"(边生成边发送)。非 Telegram 渠道还需要设置*.blockStreaming: true。 - 结束时一次性发送:
blockStreamingBreak: "message_end"(刷新一次,如果文本很长可能分成多块)。 - 不使用块流:
blockStreamingDefault: "off"(只发最终回复)。
渠道说明: 除非显式设置 *.blockStreaming 为 true,否则块流默认关闭。渠道可以在不启用块回复的情况下使用实时预览流(channels.<channel>.streaming)。
配置位置提醒:blockStreaming* 默认值在 agents.defaults 下,不在根配置中。
预览流模式
标准配置 key:channels.<channel>.streaming
模式:
off:关闭预览流。partial:单条预览消息,持续替换为最新文本。block:预览以分块/追加方式更新。progress:生成过程中显示进度/状态预览,完成后显示最终答案。
渠道映射
| 渠道 | off | partial | block | progress |
|---|---|---|---|---|
| Telegram | ✅ | ✅ | ✅ | 映射为 partial |
| Discord | ✅ | ✅ | ✅ | 映射为 partial |
| Slack | ✅ | ✅ | ✅ | ✅ |
Slack 专属:
channels.slack.nativeStreaming在streaming=partial时切换 Slack 原生流式 API 调用(默认:true)。
旧版 key 迁移:
- Telegram:
streamMode+ 布尔值streaming自动迁移为streaming枚举。 - Discord:
streamMode+ 布尔值streaming自动迁移为streaming枚举。 - Slack:
streamMode自动迁移为streaming枚举;布尔值streaming自动迁移为nativeStreaming。
运行时行为
Telegram:
- 使用
sendMessage+editMessageText在私聊和群组/话题中更新预览。 - 当 Telegram 块流显式启用时,跳过预览流(避免重复流式输出)。
/reasoning stream可以将推理过程写入预览。
Discord:
- 使用发送 + 编辑的预览消息。
block模式使用草稿分块(draftChunk)。- 当 Discord 块流显式启用时,跳过预览流。
Slack:
partial在可用时可以使用 Slack 原生流式 API(chat.startStream/append/stop)。block使用追加式草稿预览。progress使用状态预览文本,然后显示最终答案。