ストリーミング + チャンキング
OpenClawには2つの独立した「ストリーミング」レイヤーがあります:
- ブロックストリーミング(チャンネル): アシスタントが書き込む際に完成したブロックを送信します。これらは通常のチャンネルメッセージです(トークンのデルタではありません)。
- トークン風ストリーミング(Telegramのみ): 生成中にドラフトバブルを部分テキストで更新します。最終メッセージは最後に送信されます。
今日、外部チャンネルメッセージへの実際のトークンストリーミングはありません。Telegramのドラフトストリーミングが唯一の部分ストリーム機能です。
ブロックストリーミング(チャンネルメッセージ)
ブロックストリーミングは、利用可能になったアシスタント出力を粗いチャンクで送信します。
モデル出力
└─ text_delta/events
├─ (blockStreamingBreak=text_end)
│ └─ チャンカーはバッファが増えるとブロックを発行
└─ (blockStreamingBreak=message_end)
└─ チャンカーはmessage_endでフラッシュ
└─ チャンネル送信(ブロック返信)
凡例:
- 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: チャンカーが発行するとすぐにブロックをストリーム。各text_endでフラッシュ。
- message_end: アシスタントメッセージが終了するまで待機し、バッファされた出力をフラッシュ。
message_endは、バッファされたテキストがmaxCharsを超える場合、チャンカーを使用するため、最後に複数のチャンクを発行できます。
チャンキングアルゴリズム(下限/上限)
ブロックチャンキングはEmbeddedBlockChunkerによって実装されています:
- 下限: バッファが minChars 以上になるまで発行しません(強制される場合を除く)。
- 上限: maxCharsの前に分割を優先。強制される場合はmaxCharsで分割。
- ブレーク優先順位: paragraph → newline → sentence → whitespace → ハードブレーク。
- コードフェンス: フェンス内では決して分割しません。maxCharsで強制される場合、Markdownを有効に保つためにフェンスを閉じて再開します。
maxCharsはチャンネルのtextChunkLimitにクランプされるため、チャンネルごとのキャップを超えることはできません。
結合(ストリームされたブロックのマージ)
ブロックストリーミングが有効な場合、OpenClawは送信前に連続するブロックチャンクをマージできます。これにより、「1行スパム」を減らしながら段階的な出力を提供します。
- 結合は、フラッシュする前にアイドルギャップ (idleMs) を待ちます。
- バッファはmaxCharsで制限され、超えた場合はフラッシュされます。
- minCharsは、十分なテキストが蓄積されるまで小さな断片が送信されるのを防ぎます(最終フラッシュは常に残りのテキストを送信します)。
- 結合文字はblockStreamingChunk.breakPreferenceから導出されます(paragraph → \n\n、newline → \n、sentence → スペース)。
- チャンネル上書きは*.blockStreamingCoalesce経由で利用可能です(アカウントごとの設定を含む)。
- デフォルトの結合minCharsは、上書きされない限り、Signal/Slack/Discordでは1500に引き上げられます。
ブロック間の人間らしいペーシング
ブロックストリーミングが有効な場合、ブロック返信間(最初のブロックの後)にランダム化された一時停止を追加できます。これにより、複数バブルの応答がより自然に感じられます。
- 設定: agents.defaults.humanDelay (エージェントごとにagents.list[].humanDelayで上書き)。
- モード: off (デフォルト)、natural (800–2500ms)、custom (minMs/maxMs)。
- ブロック返信にのみ適用され、最終返信やツールサマリーには適用されません。
「チャンクをストリームするか、すべてをストリームするか」
これは次のようにマッピングされます:
- チャンクをストリーム: blockStreamingDefault: "on" + blockStreamingBreak: "text_end" (進行中に発行)。Telegram以外のチャンネルも*.blockStreaming: trueが必要です。
- 最後にすべてをストリーム: blockStreamingBreak: "message_end" (1回フラッシュ、非常に長い場合は複数のチャンク)。
- ブロックストリーミングなし: blockStreamingDefault: "off" (最終返信のみ)。
チャンネルの注意: Telegram以外のチャンネルでは、*.blockStreamingが明示的にtrueに設定されていない限り、ブロックストリーミングはオフです。Telegramは、ブロック返信なしでドラフトをストリームできます(channels.telegram.streamMode)。
設定場所のリマインダー: blockStreaming*のデフォルトは、ルート設定ではなくagents.defaultsの下にあります。
Telegramドラフトストリーミング(トークン風)
Telegramは、ドラフトストリーミングを持つ唯一のチャンネルです:
- トピック付きプライベートチャットでBot API sendMessageDraftを使用します。
- channels.telegram.streamMode: "partial" | "block" | "off"。
- partial: 最新のストリームテキストでドラフトを更新。
- block: チャンクブロックでドラフトを更新(同じチャンカールール)。
- off: ドラフトストリーミングなし。
- ドラフトチャンク設定(streamMode: "block"の場合のみ): channels.telegram.draftChunk (デフォルト: minChars: 200、maxChars: 800)。
- ドラフトストリーミングはブロックストリーミングとは別です。ブロック返信はデフォルトでオフで、Telegram以外のチャンネルでは*.blockStreaming: trueによってのみ有効化されます。
- 最終返信は依然として通常のメッセージです。
- /reasoning streamは推論をドラフトバブルに書き込みます(Telegramのみ)。
ドラフトストリーミングがアクティブな場合、OpenClawは二重ストリーミングを避けるためにその返信のブロックストリーミングを無効にします。
Telegram(プライベート + トピック)
└─ sendMessageDraft(ドラフトバブル)
├─ streamMode=partial → 最新テキストを更新
└─ streamMode=block → チャンカーがドラフトを更新
└─ 最終返信 → 通常のメッセージ
凡例:
- sendMessageDraft: Telegramドラフトバブル(実際のメッセージではありません)。
- final reply: 通常のTelegramメッセージ送信。