ツール入力の初動を速くする eager_input_streaming — 引き換えに失うものを把握する

Claude API でツールを使うとき、標準のストリーミングは各パラメータの値をサーバ側でいったんバッファし、JSONとして妥当かを検証してからクライアントへ返す。安全だが、パラメータが大きいほど最初の1文字が届くまで待たされる。

ファイル1本ぶんのテキストやコードブロックをツール入力として生成させる場合、この待ち時間がそのまま体感の遅さになる。eager_input_streaming はこのバッファと検証を省く。

有効化はツール単位

Claude API、Amazon Bedrock、Claude Platform on AWS、Google Cloud、Microsoft Foundry のいずれでも、全モデルが対応している。使いたいツール定義に eager_input_streaming: true を足し、リクエスト側でストリーミングを有効にするだけだ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
with client.messages.stream(
model="claude-opus-4-8",
max_tokens=65536,
tools=[{
"name": "make_file",
"description": "Write text to a file",
"eager_input_streaming": True,
"input_schema": {
"type": "object",
"properties": {
"filename": {"type": "string"},
"lines_of_text": {"type": "array"},
},
"required": ["filename", "lines_of_text"],
},
}],
messages=[{"role": "user", "content": "長い詩を書いて poem.txt を作って"}],
) as stream:
for event in stream:
if event.type == "input_json":
print(event.partial_json, end="", flush=True)

ツール単位で指定できるのが実務上は効く。全ツールで有効にする必要はない。大きなパラメータを生成するものだけに付け、他は既定のバッファ付きのままにしておけば、検証を失う範囲を最小限にできる。

省略すれば従来どおりのバッファ付き。例外として、旧来の fine-grained-tool-streaming-2025-05-14 ベータヘッダを送り続けているリクエストでは、フィールド未設定のツールも有効になる。この場合、明示的に false を指定したツールだけがバッファ付きのまま残る。ヘッダからの移行中なら、ここは意識しておいたほうがいい。

失うもの

公式ドキュメントの警告が本質だ。

Because the API does not buffer or validate a tool’s input before streaming it, you might receive partial or invalid JSON.

蓄積した文字列が妥当なJSONである保証がなくなる。 加えて、レスポンスが max_tokens に達してパラメータの途中で切れることもある。

したがって、有効にするなら次の3つがセットで必要になる。

1. パースのガード

蓄積の手順自体は標準のツールストリーミングと同じだ。content_block_start で空文字列を初期化し、input_json_delta のたびに partial_json を追記し、content_block_stop でパースする。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
tool_inputs: dict[int, str] = {}

for event in stream:
match event.type:
case "content_block_start" if event.content_block.type == "tool_use":
tool_inputs[event.index] = ""
case "content_block_delta" if event.delta.type == "input_json_delta":
tool_inputs[event.index] += event.delta.partial_json
case "content_block_stop" if event.index in tool_inputs:
raw = tool_inputs[event.index]
try:
parsed = json.loads(raw)
except json.JSONDecodeError:
handle_invalid(raw) # ここを必ず用意する
else:
run_tool(parsed)

Python、TypeScript、Go、Java、Ruby のSDKにはアキュムレータのヘルパがあり、組み立ては任せられる。手動パターンが要るのは、ヘルパの無いSDKか、組み立てを自分で制御したい場合だ。なお「断片に反応すること」と「組み立てること」は別の関心事で、届くたびに画面へ流しつつ組み立てはヘルパに任せる、という構成もできる。

2. 停止理由の確認

stop_reasonmax_tokens なら、最後のブロックが不完全な tool_use になっている可能性がある。max_tokens を上げて再試行するか、部分入力を修復するかを決める必要がある。

3. 不正入力をClaudeへ返す経路

パースできなかったとき、ツールは実行できない。黙って落とすのではなく失敗を伝える。公式が示す形はこうだ。

1
2
3
4
5
6
{
"type": "tool_result",
"tool_use_id": "toolu_...",
"is_error": true,
"content": "{\"INVALID_JSON\": \"<受け取ったパース不能な入力>\"}"
}

生の文字列を単一キーのオブジェクトで包むと、「不正なJSONを受け取った」ことがClaudeに曖昧でなく伝わり、元の入力もデバッグ用に残る。このラッパーは文字列連結ではなくJSONライブラリで組み立てること。 不正入力に引用符が混ざっていたときに壊れる。

使いどころ

得るのは初動のレイテンシ、失うのは入力の妥当性保証。この交換が見合うのは、入力が大きく、かつ最初の断片が早く届くことに意味がある場面に限られる。

  • 向く: ファイルの中身やコードを生成させ、そのまま画面へ流す
  • 向かない: 都市名を1つ渡すだけのような小さい入力(差を体感できない)

上の3つを用意しないまま有効にすると、稀に出る不正JSONでアプリケーションが落ちる。逆に言えば、3つ揃っているなら副作用なく速くなる。

eager_input_streaming の詳細と各言語のコード例は きめ細かいツールストリーミング に日本語でまとめた。あわせて stop_reason の7種類と対処を整理した stop_reasonの扱い方 も置いてある。とくに「tool_result ブロックの後にテキストを足してはいけない」理由は、知らないと踏む類の落とし穴なので一読をおすすめする。


ツール入力の初動を速くする eager_input_streaming — 引き換えに失うものを把握する
https://blog.hashito.biz/2026/07/20/claude-eager-input-streaming-tool-input-latency/
著者
hashito
作成日
2026年7月20日
著作権