読み込み中

ツール呼び出しのストリーミング: 固まったスピナーとはもうお別れ

レッスン 48 分
読み込み中

ツール呼び出しのストリーミング: 固まったスピナーとはもうお別れ

要約トランスクリプト

ストリーミングされたツール呼び出しは、まずツールの名前が届き、続いてその入力が断片的に届くため、画面には何が起きているかを表示できます。この動画では、架空のエンジニアリングコンサルティング会社Meridianにおける画面の2つのバージョンを並べて示します。一方はストリーミングなし、もう一方はストリーミングありです。このデモはシミュレーションであり、3分の1の速度で再生されます。

この動画は2026年9月に収録されたもので、コードではClaude Sonnet 5(claude-sonnet-5)を使用しています。新しいモデルでは結果が異なる場合があります。

ユーザーが8秒間スピナーを見つめているとします。これは、あなたのツールループが各リクエストを送信し、待機し、完成した応答を受け取るために起こります。

その間に、Claudeはツールを選んでその入力を書き、あなたのコードがそのツールを実行し、Claudeが回答を書きます。あなたのコードはそれぞれの応答が完成して初めてそれを目にするため、Claudeが応答を書いている間、あなたのUIには表示できるものが何もありません。

Meridianは今まさにこの問題を抱えています。同社のサポートエージェントはPythonで書かれており、従業員のプロジェクトに関する質問に答えます。ある従業員が「ポンプ場案件の状況はどうなっていますか?」と尋ねると、画面には答えが返ってくるまで「考え中...」と表示されます。

ストリーミングは、その従業員が目にするものを変えます。ストリーミングを使うと、あなたのコードは応答が完成する前に、少しずつ受け取れるようになります。 そのため、Meridianの画面は、Claudeがツールを選んだ瞬間に「プロジェクトを検索中...」と表示できます。

ストリーミングされたツール呼び出しの見た目

ツールループがリクエストを送信している箇所で、client.messages.create(...)をclient.messages.stream(...)に置き換えてください。すると、応答は一連のサーバー送信イベントとして届きます。これは、それぞれがtypeを持つ小さなJSONメッセージです。

応答はコンテンツブロック(略してブロック)で構成されており、Claudeのテキストの各まとまりに1つ、各ツール呼び出しに1つのブロックが割り当てられます。ツール呼び出しのブロックはtool_useという型を持ちます。現行モデルは思考ブロックも追加できますが、このレッスンのコードではこれを無視します。

友人が電話口で電話番号を読み上げてくれる場面を思い浮かべてください。まず、それが誰の番号かを伝えます。「これが配管業者の番号だよ」。それから、好きなタイミングで区切りながら、数桁ずつ読み上げていきます。最後に「以上です」と言います。

ツール呼び出しのブロックは、この電話番号と同じように届きます。 1つのイベントがそれを開き、1つ以上のイベントがそれを埋め、1つのイベントがそれを閉じます。以下は、Meridianのツール呼び出しのイベントです。

text
{"type": "content_block_start", "index": 0,
 "content_block": {"type": "tool_use", "id": "toolu_01",
                   "name": "lookup_project_status", "input": {}}}
{"type": "content_block_delta", "index": 0,
 "delta": {"type": "input_json_delta", "partial_json": "{\"project_"}}
{"type": "content_block_delta", "index": 0,
 "delta": {"type": "input_json_delta", "partial_json": "id\": \"MEG-2"}}
{"type": "content_block_delta", "index": 0,
 "delta": {"type": "input_json_delta", "partial_json": "026-0142\"}"}}
{"type": "content_block_stop", "index": 0}

ブロックが開いた時点で選択されたツールを表示する

ツールの名前は、ブロックの最初のイベントであるcontent_block_startに含まれています。 このイベントが届くと、あなたのコードはステータス行(ユーザーが待っている間にUIに表示される1行のテキスト)を更新できます。

MeridianはSTATUSという小さなdictを保持しており、各ツールの名前をユーザー向けの言葉に対応付けています。そのため、lookup_project_statusは画面上で「プロジェクトを検索中...」となります。

ストリーミングリクエストを組み立てる

ストリームが終了したら、stream.get_final_message()を呼び出して、完成した応答を取得します。これはclient.messages.create(...)が返すのと同じ種類のオブジェクトです。 そのため、ツールループの残りの部分は以前のままで構いません。

以下はMeridianのストリーミングリクエストです。on_statusは、文字列をステータス行として表示するために自分で書く関数です。

読み込み中

重要なポイント

  • ストリーミングされたツール呼び出しは、まずツールの名前が届き、続いてその入力が断片的に届きます。
  • content_block_startが届いたらすぐに、選択されたツールをユーザーが理解できる言葉で表示してください。
  • stream.get_final_message()は完成した応答を返すため、ツールループの残りの部分を変更する必要はありません。