ストリーミング
ストリーミングはケイパビリティ駆動です。ストリーミングメソッドを実装し、supports_streaming() == true を返すプロバイダーはトークンデルタを送出できます。それ以外のプロバイダーは非ストリーミングのレスポンスパスを使用します。ランタイムは、利用可能なストリームを部分更新をサポートするチャネルアダプターに転送します。
ストリーミングされる内容
プロバイダトレイトは、モデルが出力を生成する際に StreamEvent 値を発行します。具体的には、テキストデルタ、構造化されたツール呼び出し、プロバイダ側で事前実行されたツール呼び出しとその結果、トークン使用量レポート、最終的な完了マーカーです。各バリアントの正式な定義は、crates/zeroclaw-api/src/model_provider.rs(enum StreamEvent)の型とともに記述されています。推論トークンは独立したバリアントではなく、テキストデルタとして到着します。
ランタイムはこれらのイベントを消費します。チャネルオーケストレーターは、Channel トレイトのドラフト配信メソッドとケイパビリティフラグを使用して、サポートされている場合にプログレッシブ出力を提示します。
機能フラグ
プロバイダーは2つのフラグを公開し、ランタイムが何を期待すべきかを知るようにします:
#![allow(unused)]
fn main() {
fn supports_streaming(&self) -> bool { false }
fn supports_streaming_tool_events(&self) -> bool { false }
}
supports_streaming: 具象プロバイダーがストリーミングを有効にした場合にのみ true になります。トレイトのデフォルトは false ですsupports_streaming_tool_events: プロバイダーがストリームの最後ではなくストリーム中にToolCallイベントを発行する場合は true
OpenAI互換のプロバイダーは異なります。一部はツール呼び出しの引数デルタをチャンクごとにストリーミングしますが、他のプロバイダーは呼び出しが完了してからのみイベントを出力します。compatible.rs の SSE パーサーは両方に対応しています。
チャネル側のストリーミング
チャネルは Channel トレイトを通じて、自身のストリーミング機能を通知します。
#![allow(unused)]
fn main() {
fn supports_draft_updates(&self) -> bool; // メッセージをその場で編集する
fn supports_multi_message_streaming(&self) -> bool; // 1つの返信を複数のメッセージに分割する
}
チャネルの機能はその構成に従います。stream_mode 列挙型 (off / partial / multi_message) を持つチャネルは、ドラフト更新とマルチメッセージの両方をサポートします。stream_drafts ブール値を持つチャネルは、ドラフト更新のみをサポートします。このテーブルはチャネル構成スキーマから生成されるため、チャネルがストリーミングサポートを獲得または喪失しても正確性が保たれます。
| チャンネル | 更新ドラフト | 複数メッセージ |
|---|---|---|
discord | ✓ | ✓ |
lark | ✓ | ✓ |
matrix | ✓ | ✓ |
nextcloud_talk | ✓ | ✓ |
slack | ✓ | |
telegram | ✓ | ✓ |
wecom_ws | ✓ | ✓ |
プロバイダーとチャンネルの両方がストリーミングをサポートしている場合のフローは次のとおりです: プロバイダーが TextDelta を発行 → ランタイムがチャンネルに渡す → チャンネルが送信済みメッセージを編集します。編集のタイミングは、レート制限を回避するためにそのチャンネルの draft_update_interval_ms 設定によって制御されます。デフォルト値はチャンネルによって異なります。
推論ブロック
StreamEventには独立したReasoningDeltaバリアントは存在しません。プロバイダーがストリーミング中に推論を公開する場合、TextDeltaが運ぶStreamChunkのreasoningフィールドを使用します。そのコンテンツがリクエストされるか公開されるかは、プロバイダーおよびランタイムの設定によって決まります。コンシューマーは、存在しないイベントバリアントにマッチングするのではなく、StreamChunkコントラクトに従ってください。
ストリーム中のツール呼び出し
ストリーミングプロバイダーがツールを呼び出すことを決定すると、構造化された ToolCall ストリームイベントを発行します。ランタイム:
- ストリームを最後まで読み取り、構造化された
ToolCallイベントを収集し、Finalまで表示可能なテキストを転送します。 - ストリーム終了後にツール呼び出しを復元します
- ツールを実行します(セキュリティ検証が適用されます。セキュリティ → 概要を参照してください)
- ツールの結果を会話に追加したうえで、次のアシスタントターン用にプロバイダーへの新しいストリーミング呼び出しを開始します
現在のプロバイダーストリームは読み取りの途中で一時停止・再開されることはなく、ツールの実行はそのストリームが Final に到達した後に行われ、次のターンでは新しいストリーミング呼び出しが行われます。
ユーザーの視点では、テキスト、エージェントがツールを実行したことを示す可視インジケーター(チャネル固有のヒント経由)、そしてさらにテキストが続きます。タイピングインジケーターがないチャネルでは、ツール呼び出しと次のテキストチャンクの間のギャップが唯一のシグナルとなります。
トランスポートの完了とタイムアウト
ストリーミングトランスポートは、接続の終了を成功のシグナルとして依存しません。OpenAI 互換ストリームは [DONE] で完了し、OpenAI Responses ストリームは終端レスポンスイベントで完了し、Anthropic ストリームは message_stop で完了します。サーバーは、これらのイベント後も HTTP 接続を開いたままにすることがあります。
ストリーミングクライアントでは、バイト無通信タイムアウトを使用します。OpenAI Responses および OpenAI 互換プロバイダーでは 300 秒、Anthropic では 90 秒です。受信した本文の読み取りごとに該当するタイムアウトがリセットされるため、アクティブな生成が、非ストリーミング呼び出しで使用されるリクエスト全体のタイムアウトによって制限されることはありません。接続の確立、レスポンスヘッダー、およびバッファーされたエラー本文には、引き続き上限が設けられます。
ストリーミング以外のプロバイダー
supports_streaming() が false の場合、呼び出し元はプロバイダーの非ストリーミングチャットパスを使用します。チャンネルアダプターは完了した返信を送信できますが、プロバイダーからのインクリメンタルなストリームイベントは受信しません。
コード参照
crates/zeroclaw-api/src/model_provider.rs:ModelProviderトレイト、StreamEvent列挙型crates/zeroclaw-providers/src/compatible.rs: OpenAI互換SSEパーサーcrates/zeroclaw-providers/src/anthropic.rs: Anthropic ストリーミングcrates/zeroclaw-providers/src/ollama.rs: Ollama ストリーミングcrates/zeroclaw-channels/src/orchestrator/mod.rs: チャネル側のストリーム消費