流式输出与分块

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:模型流事件(非流式模型可能比较稀疏)。
  • chunkerEmbeddedBlockChunker,按最小/最大边界 + 断点偏好处理。
  • 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)。
  • 渠道分块模式:*.chunkModelength 默认,newline 在空行(段落边界)处分割后再按长度分块)。
  • Discord 软上限:channels.discord.maxLinesPerMessage(默认 17)分割过长回复以避免 UI 截断。

边界语义:

  • text_end:chunker 产出时即刻发送块;每个 text_end 时刷新。
  • message_end:等待助手消息完成后再刷新缓冲区输出。

message_end 在缓冲文本超过 maxChars 时仍会使用 chunker,因此结尾可能发出多个块。

分块算法(低/高边界)

块分块由 EmbeddedBlockChunker 实现:

  • 低边界: 缓冲区未达到 minChars 时不发送(除非强制)。
  • 高边界: 优先在 maxChars 之前分割;被迫时在 maxChars 处硬切。
  • 断点偏好: paragraphnewlinesentencewhitespace → 硬切。
  • 代码围栏: 不在围栏内分割;强制在 maxChars 分割时,会关闭 + 重新打开围栏以保持 Markdown 格式正确。

maxChars 会被限制在渠道的 textChunkLimit 以内,所以不会超过各渠道的上限。

合并(合并流式块)

启用块流时,OpenClaw 可以在发送前合并连续的块。这减少了”一行一行刷屏”的问题,同时仍提供渐进式输出。

  • 合并会等待空闲间隔idleMs)后再刷新。
  • 缓冲区受 maxChars 限制,超出时立即刷新。
  • minChars 防止过小的片段发出,直到积累了足够的文本(最后的刷新始终发送剩余文本)。
  • 连接符由 blockStreamingChunk.breakPreference 派生(paragraph\n\nnewline\nsentence → 空格)。
  • 可通过 *.blockStreamingCoalesce 按渠道覆盖(包括按账号配置)。
  • Signal/Slack/Discord 的默认合并 minChars 会提高到 1500(除非被覆盖)。

块间的类人节奏

启用块流后,你可以在块回复之间(首块之后)添加随机暂停。这让多条消息的回复感觉更自然。

  • 配置:agents.defaults.humanDelay(可按 agent 覆盖:agents.list[].humanDelay)。
  • 模式:off(默认)、natural(800–2500ms)、customminMs/maxMs)。
  • 仅适用于块回复,不影响最终回复或工具摘要。

“流式分块还是一次性发送”

对应关系:

  • 流式分块: blockStreamingDefault: "on" + blockStreamingBreak: "text_end"(边生成边发送)。非 Telegram 渠道还需要设置 *.blockStreaming: true
  • 结束时一次性发送: blockStreamingBreak: "message_end"(刷新一次,如果文本很长可能分成多块)。
  • 不使用块流: blockStreamingDefault: "off"(只发最终回复)。

渠道说明: 除非显式设置 *.blockStreamingtrue,否则块流默认关闭。渠道可以在不启用块回复的情况下使用实时预览流(channels.<channel>.streaming)。

配置位置提醒:blockStreaming* 默认值在 agents.defaults 下,不在根配置中。

预览流模式

标准配置 key:channels.<channel>.streaming

模式:

  • off:关闭预览流。
  • partial:单条预览消息,持续替换为最新文本。
  • block:预览以分块/追加方式更新。
  • progress:生成过程中显示进度/状态预览,完成后显示最终答案。

渠道映射

渠道offpartialblockprogress
Telegram映射为 partial
Discord映射为 partial
Slack

Slack 专属:

  • channels.slack.nativeStreamingstreaming=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 使用状态预览文本,然后显示最终答案。