Building with the Claude API | Lesson 34「Implementing multiple turns」(全文ナレッジ)
動画 16分26秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04
日本語ナレッジ(要点整理)
- 前回(Lesson 33)のリファクタ(ヘルパーの複数ブロック対応・chat の tools 対応・text_from_message 追加)が完了した状態から、残っていた最後のステップ=「1つの会話内での複数回ツール呼び出しのサポート」を実装する回。
- この関数の目標はただ1つ:Claude がツール使用を要求しなくなるまで Claude を呼び出し続けること。ツールを要求しなくなったら、それがユーザーへ返せる最終レスポンスができたサイン。
- 実装は Lesson 33 で見た擬似コード(Conversation with tools スライド)とほぼ同一の形になる。
2. ループ継続判定は stop_reason フィールドで行う
- レスポンスメッセージに ToolUse ブロックが入っているかを直接見る方法もあるが、もっと便利な方法がある:Message の
stop_reason フィールド。
stop_reason は「Claude がなぜテキスト生成を停止したか」を教えてくれる。値が 'tool_use' なら「Claude がツールを呼ぶ必要があると判断した」明確で即座のサイン。
- スライド「Stop Reason」の値一覧(転記):
"tool_use" Claude has decided that it needs to call a tool
"end_turn" Claude has finished generating it's assistant message
"max_tokens" Claude has hit the token output limit and can't generate any more output
"stop_sequence" Claude has encountered one of your provided stop sequences
- 他の値もチェックしようと思えばできるが、実務で最も頻繁にチェックするのは tool_use。ループ判定は
if response.stop_reason != "tool_use": break で実装する。
3. run_conversation の完成形(画面転記)
def run_conversation(messages):
while True:
response = chat(messages, tools=[get_current_datetime_schema])
add_assistant_message(messages, response)
print(text_from_message(response))
if response.stop_reason != "tool_use":
break
tool_results = run_tools(response)
add_user_message(messages, tool_results)
return messages
chat はリファクタ済(tools 対応・Message 全体を返す)。現時点でツールは get_current_datetime 1つだけなので tools にはそのスキーマのみを渡す。
- 返ってきたレスポンスは
add_assistant_message で履歴に積み、print(text_from_message(response)) で「Claude が今何をしているか」を可視化する(デバッグ用の出力)。
break しなければ Claude はツールを呼びたいということ → run_tools(response) を実行し、得られた ToolResult ブロックのリストを user メッセージとして履歴に追加し、while ループ先頭に戻ってもう一度 Claude を呼ぶ。return messages は while の外。
- Claude が返すアシスタントメッセージには ToolUse ブロックが複数入っている可能性がある(スライド例:「10+10 と 30+30 を足して」→ calculator ツールへの ToolUseBlock が2つ返る)。
Text Block “I can help you add those numbers together”
ToolUse Block One ToolUseBlock(id='ab3', input={'expression': '10 + 10'}, name='calculator', type='tool_use')
ToolUse Block Two ToolUseBlock(id='po9', input={'expression': '30 + 30'}, name='calculator', type='tool_use')
- run_tools がやること(スライド「message.content list of blocks」の4ステップ):
For each ToolUseBlock…
Run the specified tool with the given inputs
Take the output from the tool and put it into a ToolResultBlock
Return all the ToolResultBlocks
- content リスト先頭の Text ブロック(Claude が今考えていること)は run_tools では気にしない(図からも削除)。ToolUse ブロックだけを対象に、name フィールドで実行すべきツール関数を特定し、input を渡して実行する。
def run_tools(message):
tool_requests = [
block for block in message.content if block.type == "tool_use"
]
tool_result_blocks = []
for tool_request in tool_requests:
try:
tool_output = run_tool(tool_request.name, tool_request.input)
tool_result_block = {
"type": "tool_result",
"tool_use_id": tool_request.id,
"content": json.dumps(tool_output),
"is_error": False
}
except Exception as e:
tool_result_block = {
"type": "tool_result",
"tool_use_id": tool_request.id,
"content": f"Error: {e}",
"is_error": True
}
tool_result_blocks.append(tool_result_block)
return tool_result_blocks
- 内包表記で ToolUse ブロックだけをフィルタ。変数名は tool_use より意味が通る tool_requests(Claude からの「ツールを使ってくれ」というリクエスト群)とした。
- ツール出力は
json.dumps で文字列にエンコードして content に入れる(セル先頭に import json を追加)。
ToolUseBlock(Claude's request to use a tool)
id ID of this tool use
input Dictionary with the arguments to the tool
name Name of the tool to run
type Type of this block ("tool_use")
ToolResultBlock(Response to Claude's tool request)
tool_use_id ID of this tool use
content Output from the tool call, encoded as a string
is_error True if an error occurred
type Type of this block ("tool_result")
- 最重要:ToolResult 側の
tool_use_id は、元となった ToolUse ブロックの id と厳密に一致させる。左は id、右は tool_use_id とプロパティ名がまったく違うのに、値は同一でなければならない点に注意。
- content はツール実行の出力を文字列としてエンコードしたもの。is_error はオプションで、ツール実行時にエラーが起きたら付ける。
7. 改良①:try/except によるエラーハンドリング
- 最初の実装は常に
"is_error": False — それは正確ではない。ツール関数の実行中にエラーが起きるシナリオがあり得る。
- ツール実行〜ブロック組み立てを
try で包み、except Exception as e: ではそれでも ToolResult ブロックを作ってリストに加える。ただし "is_error": True にし、content には f"Error: {e}" でエラーメッセージを入れる。
- 狙い:何のエラーが起きたかの情報を Claude に返す。エラーが起きると Claude はより良い引数・より整形された引数でツールを再実行しようとすることがある。
- 現状は get_current_datetime という1種類のツールだけを if 文でチェックしている。この先 add_duration_to_datetime と set_reminder(さらにその後もう1つ)を追加するので、このパターンはスケールしない。
- 「どのツールを実行するか判定して実際に実行する」役割を、run_tools のすぐ上の小さなヘルパー関数 run_tool に切り出す:
import json
def run_tool(tool_name, tool_input):
if tool_name == "get_current_datetime":
return get_current_datetime(**tool_input)
- ツールを追加するときは、ここに if を1本足すだけでよい。run_tools 側は if 文を消し、
tool_output = run_tool(tool_request.name, tool_request.input) に置き換える(try/except/tool_result のブロックをインデントし直す、少し骨の折れるリファクタ)。以降 run_tools はもう変更不要。
9. 動作検証 — 2ツールコールを強制するクエリと messages の解剖
messages = []
add_user_message(
messages,
"What is the current time in HH:MM format? Also, what is the current time in SS format?"
)
run_conversation(messages)
- 大量に変更したので Run All で全セル再実行。print 出力:
I can help you get the current time in the requested formats. Let me fetch that for you.
The current time in HH:MM format is 11:16.
The current time in SS format (seconds only) is 30.
- messages 履歴(6メッセージ・画面出力より):
1. user:「What is the current time in HH:MM format? Also, what is the current time in SS format?」
2. assistant: TextBlock + ToolUseBlock(get_current_datetime,
{'date_format': '%H:%M'}, id=toolu_013M9HM18jyBXPLTTWieH2GV)
3. user: tool_result(tool_use_id 同一、content '"11:16"', is_error False)
4. assistant: ToolUseBlock のみ(テキスト部なし)(get_current_datetime, {'date_format': '%S'}, id=toolu_01Dv5RUxjEpxCzav4ZAseRVZ)
5. user: tool_result(content '"30"', is_error False)
6. assistant: TextBlock のみ(最終回答『The current time in HH:MM format is 11:16.\nThe current time in SS format (seconds only) is 30.』)
- 2回目のツールコールにはテキスト部が付かない——単一メッセージ内の複数種ブロックを正しく扱うことの重要性を裏付ける完璧な実例。
10. 次のステップ
- このプロジェクトの残りは複数の異なるツールへの対応:add_duration_to_datetime ツールと set_reminder ツールのサポートを追加する(Tools We Need スライドの3つ:Get the current date time / Add duration to date time / Set a reminder)。
実務ポイント(このレッスンから持ち帰るもの)
- マルチターンのツールループは
stop_reason != "tool_use" で break が定石。content の中身を走査するより明確・即座に判定できる。
stop_reason の主要4値(tool_use / end_turn / max_tokens / stop_sequence)のうち、実装で最も頻繁にチェックするのは tool_use。
- run_tools は複数 ToolUseBlock 前提で書く:
[block for block in message.content if block.type == "tool_use"] でフィルタ → 各ブロックを実行 → ToolResult ブロックのリストを返す。
- ToolResult ブロックの
tool_use_id は ToolUse ブロックの id と厳密一致(フィールド名が違うのに値は同じ、が罠)。
- ツール出力は
json.dumps で文字列化して content へ。エラー時も必ず ToolResult を返す(is_error: True + f"Error: {e}")——Claude がエラー内容を見て、より良い引数で再試行できる。
- ツールのディスパッチは run_tool(tool_name, tool_input) に分離しておくと、ツール追加が if 1本で済む。run_tools 本体は以後変更不要になる。
- ツール実行結果は user ロールのメッセージとして履歴に追加し、全履歴を持って Claude を再度呼ぶ。
return messages は while の外に置く。
- 検証では print だけでなく messages 全体を読む:2回目以降のツール要求にはテキストブロックが付かないなど、複数ブロック処理の挙動が確認できる。
- 大きくリファクタした後は Run All で全セル再実行してから検証する(定義の食い違い事故を防ぐ)。
本文(時系列・日本語逐語+画面差し込み)
[0:00] さて、あのリファクタが終わったので、この run conversation 関数のような関数の実装を始めていきます。実際のところ、私たちの本物の実装は、ここの右側に見えているものとほとんど同一の見た目になります。思い出してください。この関数の目標全体は、Claude がツールの使用を求めなくなるまで、Claude を呼び出し続けることです。もうツールの使用を求めなくなったら、それは私たちにとって、ユーザーに送り返す準備のできた最終レスポンスを Claude が持っている、というサインです。
🖥 [0:00] 画面: スライド「Conversation with tools」(Lesson 33 と同じ擬似コードスライド)。左に箇条書き、右に黒背景のコード。
・Provide an initial list of messages
・Feed messages into Claude
・If Claude isn't asking for a tool use, then we must have a final answer to send back to our user
・If Claude wants to use a tool, then we will run the tool, put the results into a user message, and run Claude again
```python
def run_conversation(messages):
while True:
response = chat(messages)
add_user_message(messages, response)
# Pseudo code
if response isn't asking for a tool:
break
tool_result_blocks = run_tools(response)
add_user_message(tool_result_blocks)
return messages
```
[0:23] そして、ここで最初に本当に理解すべきことは、Claude がツールを使いたがっているかどうかを、どうやって知るかです。レスポンスメッセージを見て、その中にツール使用ブロックがあるかを確認するだけでもよいのですが、これにはもう少し便利なやり方があります。
[0:37] ノートブックの中に戻って、あの小さなサンプルをもう一度実行します。ここでは私たちの chat 関数を使わず、client messages create を直接使っています。これを実行すると、はい、これが私のメッセージレスポンスです。
🖥 [0:40] 画面: VS Code の Jupyter ノートブック 001_tools.ipynb — 06(.venv Python 3.9.6)。サンプルセル:
```python
add_user_message(messages, "Whats the current time in HH:MM:SS format?")
response = client.messages.create(
model=model,
max_tokens=1000,
messages=messages,
tools=[get_current_datetime_schema]
)
response
```
(実行カウント [38]・1.5s)
[0:48] さて、この中に埋もれていますが、stop reason という名前のフィールドがあり、それが tool アンダースコア use という文字列にセットされていることに気づくでしょう。では、このフィールドについて少しだけお話しします。
🖥 [0:48] 画面: セルの実行出力。
Message(id='msg_013bpUZqHJjPmubU5bbcv71s', content=[TextBlock(citations=None, text='I can tell you the current time in HH:MM:SS format. Let me get that for you.', type='text'), ToolUseBlock(id='toolu_01QNu2jFagvYQDqNiP3VNpKh', input={'date_format': '%H:%M:%S'}, name='get_current_datetime', type='tool_use')], model='claude-3-7-sonnet-20250219', role='assistant', stop_reason='tool_use', stop_sequence=None, type='message', usage=Usage(cache_creation_input_tokens=0, cache_read_input_tokens=0, input_tokens=613, output_tokens=86))
(stop_reason='tool_use' の箇所にカーソル)
[0:58] このフィールドは、Claude がなぜそれ以上のテキスト生成をやめる決断をしたのか、その理由を教えてくれます。現在の値である tool use は、Claude がツールを呼ぶ必要があると判断したことを示すサインです。ですから、Claude から返ってきたレスポンスメッセージ、つまりアシスタントメッセージが tool use という stop reason を持っていたら、それは Claude がツールを使いたがっているという、非常に明確で即座のサインなのです。
🖥 [0:58] 画面: スライド「Stop Reason」。サブタイトル「Tells us why Claude stopped generating text」。2列の表:
"tool_use" Claude has decided that it needs to call a tool
"end_turn" Claude has finished generating it's assistant message
"max_tokens" Claude has hit the token output limit and can't generate any more output
"stop_sequence" Claude has encountered one of your provided stop sequences
[1:17] stop reason には他にも取り得る値がいくつかあり、もちろんそれらをチェックすることもできますが、間違いなく、あなたが最も頻繁にチェックすることになるのは、おそらく tool use でしょう。というわけで、あの小さな if 文はこうやって実装していきます。
🖥 [1:26] 画面: スライド「Conversation with tools」再掲(擬似コードの if response isn't asking for a tool: を指しての説明。内容は [0:00] と同一)
[1:29] よし、それではノートブックに戻りましょう。この run conversation 関数の実装を始めます。ここに戻ってきて、あのセルをクリアします。あれはデモ用に置いてあっただけですから。
🖥 [1:35] 画面: ノートブック。サンプルセルの中身が削除されて空になっている(下に [38] 1.5s の古い Message 出力がまだ表示されたまま)
[1:40] run conversation 関数を定義していきます。メッセージのリストを受け取り、それからループを組みます。この中で、新しくアップグレードした chat 関数——今はツールをサポートしています——を通じて Claude を呼び、レスポンスを受け取ります。メッセージのリストと一緒に、Claude が呼べるツールをいくつか渡します。
🖥 [1:44] 画面: 新しいセルに入力中(ブレッドクラムは def run_conversation())。
python
def run_conversation(messages)
(上に ✓ # Tools and Schemas / ✓ # get_current_datetime tool function のセクション見出し)
[1:58] 今回のケースでは、現時点でツールは1つしかありません。なのでここに追加します。私たちの get current date time schema です。
[2:06] 次に、受け取ったそのレスポンスを、新しくアップグレードした add assistant message 関数を使って、メッセージの会話履歴に追加します。
🖥 [2:10] 画面: 同セル・入力中。
```python
def run_conversation(messages):
while True:
response = chat(messages, tools=[get_current_datetime_schema])
add_assistant_message(m)
``
(add_assistant_message(m` まで入力・カーソル表示)
[2:16] それから、さっき組み立てた text from message 関数を使って、そのレスポンスを print します。これを print するのは、Claude が今何をしているのかを私たちが把握できるようにするためだけです。text from message に response を渡して print します。
🖥 [2:28] 画面: 同セル。
```python
def run_conversation(messages):
while True:
response = chat(messages, tools=[get_current_datetime_schema])
add_assistant_message(messages, response)
print(text_from_message(response))
i
``
(if 文のi` を入力し始めたところ)
[2:30] オーケー。さて、ここが stop reason を使う部分です。Claude から返ってきたばかりのメッセージを見て、Claude がツールを使いたがっているかどうかを理解する必要があります。ツールを使いたくないのであれば、この while ループから即座に抜けたい。
[2:44] そこで、こう書きます。もし response ドット stop アンダースコア reason が tool アンダースコア use と等しくなければ、それは Claude がすべて完了していて、これ以上ツールを使う必要がないというサインです。だから即座に break します。この if 文を通過したなら、Claude はツールを呼びたがっていると分かります。
[2:56] そこで、すぐ後で新しい関数を組み立てます。run tools と呼ぶことにします。Claude から返ってきたばかりのメッセージを渡します。run tools の目標は、このメッセージの中のすべてのツール使用ブロックを見て、それぞれに対して適切なツールを実行することです。run tools 関数は、run conversation のすぐ上の新しいセルに定義します。
🖥 [3:00] 画面: 同セル完成形(run_tools 呼び出しまで)。
```python
def run_conversation(messages):
while True:
response = chat(messages, tools=[get_current_datetime_schema])
add_assistant_message(messages, response)
print(text_from_message(response))
if response.stop_reason != "tool_use":
break
run_tools(response)
``
(run_tools(response)` の行末にカーソル・未定義のため赤波線)
[3:19] というわけで、この上に run tools という新しい関数を組み立てます。これは単一のメッセージを受け取ります。さて、この関数を組み立てるのは少しだけトリッキーです。というのも、この中に複数のツールコールがあるかもしれないという前提で書き出さなければならないからです。そこで、run tools の中で何が起きる必要があるのかを本当に明確にするために、図をお見せしましょう。
[3:39] ごく簡単な復習ですが、Claude がアシスタントメッセージを返してくるとき、その中には複数のツール使用ブロックが入っている可能性があります。そしてこれは以前に見ました。最初に Claude に「10 足す 10 と、30 足す 30 を足し合わせて」と頼むと、Claude は2つの別々のツール使用ブロックを送り返してくることがあります。1つは 10 足す 10 を評価するために calculator ツールを実行するよう求め、2つ目のツール使用ブロックは 30 足す 30 を評価するよう求めるかもしれません。ですから、この run tools 関数は、複数のツール使用ブロックがあるかもしれない前提でセットアップする必要があります。
🖥 [3:41] 画面: スライド「There might be multiple 'ToolUse' blocks!」。左に「Assistant Message's Content List」、3行の表:
Text Block “I can help you add those numbers together”
ToolUse Block One ToolUseBlock(
id='ab3',
input={'expression': '10 + 10'},
name='calculator',
type='tool_use'
)
ToolUse Block Two ToolUseBlock(
id='po9',
input={'expression': '30 + 30'},
name='calculator',
type='tool_use'
)
[4:06] この関数はこう動きます。受け取ったばかりのそのメッセージ、特にその content プロパティを見ます。content プロパティはブロックのリストになっていることを思い出してください。その中には、Claude が今何を考えているか、あるいは今何をしているかを教えてくれるテキストブロックがあるかもしれません。私たちはそのテキストブロックのことはあまり気にしません。なので、この図からは削除してしまいます。
🖥 [4:13] 画面: スライド「message.content list of blocks」。上段にブロック3つ、下に矢印と4ステップ。
TextBlock( ToolUseBlock( ToolUseBlock(
text="I'll solve these", id='ab3', id='po9',
type='text' input={'expression': '10 + 10'}, input={'expression': '30 + 30'},
) name='calculator', name='calculator',
type='tool_use' type='tool_use'
) )
For each ToolUseBlock…
Run the specified tool with the given inputs
Take the output from the tool and put it into a ToolResultBlock
Return all the ToolResultBlocks
[4:30] すると残るのは、2つの別々の——場合によってはもっと多いかもしれない——ツール使用ブロックだけです。
🖥 [4:30] 画面: 同スライド。左端の TextBlock が削除され、ToolUseBlock 2つだけが残った状態
[4:36] それで、この run tools 関数の中では、受け取ったすべての異なるツール使用ブロックに対して反復処理を行います。そして各ブロックについて、指定されたツールを与えられた入力で実行します。ここにあるこの name フィールドを見て、実行すべき適切なツール関数を見つけ、与えられた入力でそれを実行します。
[4:53] それから、これらの異なるツール実行それぞれの出力をすべて取り、別々のツールリザルトブロックへと組み立てます。思い出してください。ツールリザルトブロックは、ツールを実行した結果を Claude に伝え返すための手段です。これらの異なるツールリザルトブロックをすべて組み立てたら、それらを全部1つのリストにまとめて、run tools 関数から返します。
[5:15] よし、では実装してみましょう。混乱するのは分かります。ここで多くのことが起きているのも分かります。ですがコード自体は、最初に見えるほど悪くはありません。
[5:23] ここに戻って、run tools の中で最初にやるのは、このメッセージの content プロパティを見ることです。あれはブロックのリストで、そこからツール使用ブロックだけを抽出します。こう書きます。tool requests は、message.content 内の各 block について、block.type が tool use に等しい場合の block、と。
🖥 [5:35] 画面: run_conversation の上の新しいセルで run_tools を入力中。
python
def run_tools(message):
tool_requests = [
block for block in message.content if b
]
(内包表記の if b まで入力・カーソル表示。下に run_conversation セルが見える)
[5:48] 繰り返しますが、ここはちょっとしたフィルタ操作です。ツール使用ブロックだけを取得しています。私たちが気にするのはそれだけだからです。そして、これを特に tool requests と呼んでいるのは、tool use よりも少し意味が通ると思うからです。これらは、私たちにツールを使ってほしいという Claude によるリクエストなのです。
[6:03] 次に、tool result blocks という空のリストを作ります。これは最終的に、私たちが作成するすべての異なるツールリザルトを格納することになります。
🖥 [6:00] 画面: 同セル。
python
def run_tools(message):
tool_requests = [
block for block in message.content if block.type == "tool_use"
]
tool_result_blocks = []
(tool_requests が選択ハイライトされ、命名について説明中)
[6:11] それから、これらの異なるツールリクエストすべてに対して反復処理します。for tool request in tool requests、と。これで、個々のツールリクエストを1つずつ処理していくことになります。ここが、指定されたツールを与えられた入力で実行する必要がある場所です。そして、どのツールを実行したいかは、この name プロパティに基づいて分かります。
🖥 [6:24] 画面: 同セル(スライドへ切り替わる遷移中のフレーム)。
```python
def run_tools(message):
tool_requests = [
block for block in message.content if block.type == "tool_use"
]
tool_result_blocks = []
for tool_request in tool_requests:
``
直後に「message.content list of blocks」スライドへ戻り、ToolUseBlock One のname='calculator',` がハイライトされる
[6:31] そこでこう書きます。もし tool request ドット name が、現在私たちが持っている唯一のツール、つまり get current date time に等しければ、ツールリクエストからの star star(**)の入力を渡して get current date time 関数を実行したい。つまり tool request ドット input です。それが何らかのツール出力(tool output)を私にくれます。
🖥 [6:50] 画面: 同セル。
python
for tool_request in tool_requests:
if tool_request.name == "get_current_datetime":
get_current_datetime()
(get_current_datetime() の括弧内にカーソル。この直後 tool_output = get_current_datetime(**tool_request.input) の形になる)
[6:57] そして今度は、このツール出力を使って、真新しいツールリザルトブロックを組み立てます。ツールリザルトブロックを使ってからしばらく経っているので、これが何なのか、簡単に思い出してもらいましょう。
[7:07] はい。左側にあるのがツール使用ブロック(tool use block)です。これは今まさに扱っているもので、ツールを使いたいという Claude のリクエストです。右側にあるのがツールリザルトブロック(tool result block)です。これは、ツールを実行してほしいという Claude のリクエストに対して私たちが組み立てる応答です。ツールリザルトブロックには、割り当てるべきプロパティがいくつかあることを思い出してください。まず、tool use ID を持ちます。これは、私たちに特定のツールを実行させる原因となっているツール使用ブロックの ID と、厳密に等しくなければなりません。
🖥 [7:10] 画面: スライド「ToolUseBlock / ToolResultBlock」。左右2つの表、左の id から右の tool_use_id へ矢印。
```
ToolUseBlock — Claude's request to use a tool
id | ID of this tool use
input | Dictionary with the arguments to the tool
name | Name of the tool to run
type | Type of this block ("tool_use")
ToolResultBlock — Response to Claude's tool request
tool_use_id | ID of this tool use
content | Output from the tool call, encoded as a string
is_error | True if an error occurred
type | Type of this block ("tool_result")
```
[7:37] ここで注意してください。左側のこの ID フィールドは、ID と呼ばれています。そしてツールリザルトブロック側では、まったく別のプロパティで、tool use ID です。しかし両者は厳密に等しくなければなりません。
🖥 [7:40] 画面: 同スライド。右表の tool_use_id セルが選択ハイライトされている
[7:46] 次に content は、ツール実行の出力になります。つまり実際のツール関数の出力で、これは必ず文字列としてエンコードするようにします。そこは問題ありません。さらに、あのオプションの is error プロパティも付けられます。ツールを実行したときにエラーが発生した場合のためのものです。そして最後に、tool アンダースコア result という type も追加する必要があります。
[8:04] はい、ではここに戻って。ツール出力が手に入ったので、ツールリザルトブロックを組み立てます。先ほど指摘したプロパティをすべて持たせます。type は tool result、tool use ID は tool request ドット id。content には、ツールから得たものを何であれ、JSON の dump string を使って JSON としてエンコードして入れます。
🖥 [8:20] 画面: ノートブック(ブレッドクラムが import json に変化)。run_tools のループ内でツールリザルトブロックの辞書を入力中。
python
for tool_request in tool_requests:
if tool_request.name == "get_current_datetime":
tool_output = get_current_datetime(**tool_request.input)
tool_result_block = {
"type"
}
("type" まで入力・カーソル表示)
[8:37] JSON を必ずインポートしないといけません。セルの先頭でそれをやります。はい、できました。そして最後に is error(原文ママ・is air と発音)、これは今のところ false にセットしておきます。もう少し堅牢なエラーハンドリングはすぐ後で追加します。
[8:54] ツールリザルトブロックを作成できたので、これをツールリザルトブロックのリストに追加します。tool result blocks に tool result block を append します。そして最後に、for ループの外で、tool result blocks を return します。
🖥 [9:00] 画面: 同セル(この時点の run_tools 全体)。
```python
def run_tools(message):
tool_requests = [
block for block in message.content if block.type == "tool_use"
]
tool_result_blocks = []
for tool_request in tool_requests:
if tool_request.name == "get_current_datetime":
tool_output = get_current_datetime(**tool_request.input)
tool_result_block = {
"type": "tool_result",
"tool_use_id": tool_request.id,
"content": json.dumps(tool_output),
"is_error": False
}
tool_result_blocks.append(tool_result_block)
return tool_result_blocks
``
(セル先頭にimport json追加済み。return tool_result_blocks` の行末にカーソル)
[9:16] オーケー。これが run tools 関数のスタート地点です。少なくとも、この関数が何をしてくれるのかのイメージは掴めました。すべての異なるツール使用ブロックをフィルタで取り出し、それぞれについて、与えられた何らかのツール関数を実行し、その結果をツールリザルトブロックに入れ、すべての結果を組み立てて、そのリストを返す。
[9:33] ではこれから、この関数に2つの簡単な改良を加えます。まず、もう少しましなエラーハンドリングを追加します。今は常に「エラーはない」と言っている状態です。それは間違いなく正確ではありません。ツール関数を実行しているときに、何らかのエラーに遭遇するシナリオがあり得ます。
[9:48] そこで、ツールを実行する際に発生しうるあらゆるエラーを捕捉するために、ここで即座に小さな改良を施します。そのために、あれを try except 文で包みます。インデントをこんなふうに直します。
🖥 [10:05] 画面: 同セル・try/except 追加中。
```python
for tool_request in tool_requests:
if tool_request.name == "get_current_datetime":
try:
tool_output = get_current_datetime(**tool_request.input)
tool_result_block = {
"type": "tool_result",
"tool_use_id": tool_request.id,
"content": json.dumps(tool_output),
"is_error": False
}
except Exception as e:
tool_result_blocks.append(tool_result_block)
``
(except の中身はまだ空。tool_result_blocks.append(...)` が if の外に移動しつつある状態でハイライト)
[10:08] そして、下のこの except 文の中に入った場合でも、私はやはりツールリザルトブロックを組み立てて、このリストに追加したいのです。ただし今度は is error を true にしたい。そしておそらく、エラーメッセージを取って、ここの content フィールドに入れたい。そうすれば、たった今どんなエラーが起きたのかを、Claude がより良く理解できるからです。そこで、ここにあるツールリザルトブロックをコピーして、except(原文は accept)文の中に貼り付けます。
[10:29] is error を true に変更します。そして content には、error と E を入れた F 文字列を入れます。繰り返しますが、なぜエラーが起きたのかを理解する手助けになる情報を、Claude に返しているのです。そして思い出してください。エラーが実際に起きたとき、Claude はより良い引数、あるいはより良く整形された引数で、あなたのツールをもう一度実行しようとするかもしれません。
🖥 [10:45] 画面: 同セル・except 節が完成。
python
except Exception as e:
tool_result_block = {
"type": "tool_result",
"tool_use_id": tool_request.id,
"content": f"Error: {e}",
"is_error": True
}
("content": f"Error: {e}", が選択ハイライトされている)
[10:52] オーケー、これが1つ目の改良です。さて、今すぐ加えたい2つ目の改良ですが、私たちは現在、たった1種類のツール、get current date time ツールしか考慮していません。思い出してください。後で複数の異なるツールを持つことになります。get current date time だけでなく、ゆくゆくは add duration to date time と set a reminder をサポートします。それに正直なところ、その後にさらにもう1つ追加します。ですから、get current date time だけをチェックする if 文を置いたこのパターンは、あまりうまくスケールしません。
🖥 [11:10] 画面: スライド「Tools We Need」(スライドデッキを移動して表示。途中で Client App/Our Server/Claude の「Repeat back and forth until Claude stops asking for tool calls」シーケンス図も一瞬通過)。3枚のカード:
```
Get the current date time
・Claude needs to know the current date and time
Add duration to date time
・Claude isn't perfect with date time addition
Set a reminder
・Need a way to set a reminder
```
[11:20] そこで、どのツールを実行すべきかを判定して実際に実行するために、run tools のすぐ上にもう1つヘルパー関数を作ります。run tool と呼ぶことにします。これはツール名(tool name)と、そのツールへの入力(tool input)を受け取ります。
[11:35] そしてこの中で、一連の if 文なり何らかのチェックなりを行って、どのツール関数を実行する必要があるかを判定し、実際に実行して結果を返します。もし tool name が get current date time なら、star star tool input を渡して get current date time を実行した結果を return します。
🖥 [11:50] 画面: run_tools の上に新しいセルを作成し run_tool を入力中。
```python
import json
def run_tool(tool_name, tool_input):
if tool
``
(if toolまで入力・カーソル表示。下に run_tools/run_conversation のセルが続く。完成形はif tool_name == "get_current_datetime": return get_current_datetime(**tool_input)`)
[11:57] これで、このアプローチなら、今後追加のツールを入れることがあっても、ここに追加の if のテキストを入れるだけで済みます。tool name が、他に持っているどのツールであれそれに一致したら、その特定のツール関数を実行できる、と。というわけで、すぐ後で run tool に戻ってきて、もう少ししたら追加のツールをいくつか入れていきます。
[12:13] はい。ではこの関数を使うために、ここに戻ってきます。あそこのブロック——try と、accept(原文ママ・except のこと)と、tool result——を、インデントし直します。if 文を取り除き、そしてここの get current date time を run tool に置き換えます。そこに渡すのは、tool request ドット name と tool request ドット input です。
🖥 [12:20] 画面: run_tools セル。try/except/tool_result のブロック全体が選択され(インデントガイドのドット表示)、if tool_request.name == "get_current_datetime": を外してインデントを1段戻す操作中
[12:36] はい、できました。オーケー。これはちょっと骨の折れるリファクタでしたが、これが私たちの run tools 関数です。もう変更は必要ありません。かなりうまく動いてくれます。それに、run tool 関数も組み上がって、こちらも問題なく動きます。
🖥 [12:38] 画面: run_tools 最終形。
```python
def run_tools(message):
tool_requests = [
block for block in message.content if block.type == "tool_use"
]
tool_result_blocks = []
for tool_request in tool_requests:
try:
tool_output = run_tool(tool_request.name, tool_request.input)
tool_result_block = {
"type": "tool_result",
"tool_use_id": tool_request.id,
"content": json.dumps(tool_output),
"is_error": False
}
except Exception as e:
tool_result_block = {
"type": "tool_result",
"tool_use_id": tool_request.id,
"content": f"Error: {e}",
"is_error": True
}
tool_result_blocks.append(tool_result_block)
return tool_result_blocks
```
[12:49] さて、最後にやるべきことは、下の conversation 関数に戻って、run tools を使うことです。ここに run tools の呼び出しがありますね。run tools は今や、ツールリザルトブロックのリストを返すようになっています。なので tool results として受け取ります。
[13:04] それを会話履歴に追加します。add user message に messages と tool results を渡します。それから、while ループの外で——確実に while の外であることを確認して——メッセージのリストを return します。以上です。
🖥 [13:10] 画面: run_conversation セル最終形。
```python
def run_conversation(messages):
while True:
response = chat(messages, tools=[get_current_datetime_schema])
add_assistant_message(messages, response)
print(text_from_message(response))
if response.stop_reason != "tool_use":
break
tool_results = run_tools(response)
add_user_message(messages, tool_results)
return messages
```
[13:23] オーケー。これでこの run conversation 関数は、私たちが議論したループ全体を捉えています。run conversation に入るたびに、Claude を呼びます。何らかのアシスタントメッセージが返ってきます。そのアシスタントメッセージが何らかのツールを求めていれば、if 文を通過して先へ進み、それらのツールを実行し、結果を取得して、user メッセージとしてメッセージのリストに追加します。それから while ループの先頭へ戻り、それらのツールリザルトをメッセージに含めた状態で、もう一度 Claude を呼びます。そして、Claude がツール使用を求めなくなるまで、このプロセスを何度も何度も繰り返すのです。
[13:59] さあ、ここからは、願わくばすべてがうまく動く場面です。下の方で簡単なテストをします。メッセージのリストを作り、そこにユーザーメッセージを追加します。そして Claude に、おそらく2つの別々のツールコールが必要になるようなことを頼みます。Claude に現在時刻を hour hour、minute minute のフォーマットで尋ね、それから同じことを秒(second)フォーマットでも尋ねます。理屈の上では、Claude はおそらくこれを2つの別々のツールコールに分割するはずです。それから run conversation を呼び、メッセージのリストを渡します。
🖥 [14:10] 画面: テストセル。
python
messages = []
add_user_message(
messages,
"What is the current time in HH:MM format? Also, what is the current time in SS format?"
)
run_conversation(messages)
[14:30] このノートブック内のすべてのセルを再実行します。ものすごい量の変更を加えたばかりですからね。Run All をやって、それから返ってくるレスポンスを見てみましょう。
[14:39] どうやら、ここにあるのは間違いなく正しい答えのようです。ですが、何が起きているのかを本当に理解するために、メッセージのリストを見てみたいと思います。最初に、私たちのユーザーメッセージがあります。それから Claude からの最初のレスポンスが返ってきます。テキストブロックとツール使用ブロックを持っています。ここでもまた、複数のブロックを内部に持つメッセージです。だからこそ今は、私たちのコードすべてが複数の異なるブロックを正しく扱えるようにしておくことが、これほど重要なのです。
🖥 [14:40] 画面: テストセルの実行結果([45] 0.0s/[46] 4.0s)。print 出力:
```
I can help you get the current time in the requested formats. Let me fetch that for you.
The current time in HH:MM format is 11:16.
The current time in SS format (seconds only) is 30.
```
[15:00] この最初のメッセージの中には、Claude が hour minute フォーマットで現在時刻を取得しようとしているツール使用ブロックがあります。それに対して私たちはツールリザルトで応答します。そしてここが興味深いところです。Claude は次に、私たちへの2回目のツールコールを行う決断をします。この2回目のツールコールには、もうテキスト部分はありません。ここでもまた、単一のメッセージ内の複数の異なるツールパートを正しく扱うことの重要性が浮き彫りになっています。
🖥 [15:05] 画面: messages の出力(前半)。
[{'role': 'user',
'content': 'What is the current time in HH:MM format? Also, what is the current time in SS format?'},
{'role': 'assistant',
'content': [TextBlock(citations=None, text='I can help you get the current time in the requested formats. Let me fetch that for you.', type='text'),
ToolUseBlock(id='toolu_013M9HM18jyBXPLTTWieH2GV', input={'date_format': '%H:%M'},
name='get_current_datetime', type='tool_use')]},
{'role': 'user',
'content': [{'type': 'tool_result',
'tool_use_id': 'toolu_013M9HM18jyBXPLTTWieH2GV',
'content': '"11:16"',
'is_error': False}]},
{'role': 'assistant',
'content': [ToolUseBlock(id='toolu_01Dv5RUxjEpxCzav4ZAseRVZ', input={'date_format': '%S'},
[15:25] この2番目のメッセージの中には、今度は Claude が seconds フォーマットで get current date time を呼ぼうとしているツール使用ブロックが入っています。私たちはその結果を Claude へ送り返し、そして Claude から、テキストブロックだけを持つ最終レスポンスが返ってきます。「これがあなたの元々のクエリへの答えです」というものです。
🖥 [15:30] 画面: messages の出力(後半)。
name='get_current_datetime', type='tool_use')]},
{'role': 'user',
'content': [{'type': 'tool_result',
'tool_use_id': 'toolu_01Dv5RUxjEpxCzav4ZAseRVZ',
'content': '"30"',
'is_error': False}]},
{'role': 'assistant',
'content': [TextBlock(citations=None, text='The current time in HH:MM format is 11:16.\nThe
current time in SS format (seconds only) is 30.', type='text')]}]
[15:39] つまりこれは、私たちがたった今たどってきたすべてのステップを、間違いなく浮き彫りにしてくれる完璧な結果です。ツール使用を求めていないレスポンスが得られるまで会話を回し続けるプロセス全体。そして run tools 関数の中では、返ってきたすべての異なるブロックを見て、ツール使用ブロックだけを引き抜き、それらのブロックそれぞれに対してツールを実行し、その応答をツールリザルトへと組み立て、そしてすべての異なるツールリザルトを Claude(原文ママ・Cloud と発音)へ送り返すことの重要性です。
[16:12] この動画は長く、おそらくかなり混乱するものだったでしょう。ですが私たちは今、マルチターンのツールコーリングの素晴らしい実例を手にしています。というわけで、このプロジェクトの中で最後にやるべきことは、複数の異なるツールを確実にサポートできるようにすることです。add duration to date time ツールと、set reminder ツールのサポートも追加する必要があります。
英語逐語(ASR全文)
Now that we are done with that refactor, we are going to start to implement a function like this run conversation function. In reality, our real implementation is going to look almost identical to what you see here on the right hand side. Remember, the entire goal of this function is to keep calling Claude until it no longer asks to use a tool. When it doesn't ask to use a tool anymore, that is a sign to us that Claude has a final response that is ready for us to send back to our users.
And the first thing for us to really understand here is how we know if Claude wants to use a tool or not. We could just take a look at the response message and see if there's a tool use block inside there. But there's a little bit more convenient way of doing this.
Back inside my notebook, I'm going to run that little sample again, where I'm making use of client messages create directly, not using our chat function. I'm going to run that, and here's my message response. Now, buried inside of here, you'll notice that there is a field named stop reason, and it is set to a string of tool underscore use. So let me tell you about that field just a little bit.
This field tells us about why Claude decided to stop generating any more text. Our current value of tool use is a sign to us that Claude has decided that it needs to call a tool. So if our response message, the assistant message we get back from Claude, has a stop reason of tool use, that is a very clear and immediate sign that Claude wants to use a tool. There are some other possible values of stop reason, and we could definitely check for these, but definitely the one you're going to check for the most is probably going to be tool use. So that's how we're going to implement that little if statement right there.
All right, so let's go back over to our notebook. We're going to start to implement this run conversation function. So back over here, I'm going to clear out that cell. It was just there for demonstration purposes. I'm going to define my run conversation function. It's going to take in a list of messages, and then we will set up our loop. Inside of here, we will get a response from calling Claude through our newly upgraded chat function, which now supports tools. So I'll pass in my list of messages along with some tools that Claude can call. In this case, we currently only have one tool. So I'll add in right here. It's our get current date time schema.
Then I'm going to take that response. I get back and add it into my message conversation history using the newly upgraded add assistant message function. I'm then going to print out that response using the text from message function we just put together. I'm printing it out just so we have an understanding of what Claude is currently doing. So I will print out text from message with response.
Okay. Now here is the part where we are going to use the stop reason. We need to take a look at the message that we just got back from Claude and understand whether or not Claude wants to use a tool. If it doesn't want to use a tool, then we want to immediately break out of this while loop. So we'll say if response.stop underscore reason is not equal to tool underscore use, then that is a sign that Claude is all done and it doesn't need to make use of any more tools. So we will immediately break. If we get past that if statement, then we know that Claude wants to call a tool. So we're going to put together a new function in just a moment. We're going to call it run tools. We're going to pass in the message that we just got back from Claude. The goal of run tools is to take a look at all the tool use blocks inside this message and run the appropriate tool for each one. I'm going to define the run tools function in a new cell right above run conversation. So up here, I will put together a new function called run tools, and this is going to take in a single message. Now this function is going to be just a little tricky to put together because we have to write it out, assuming that there might be multiple tool calls inside of here. So let me show you a diagram to just make sure it's really clear what needs to happen inside of run tools.
As a very quick reminder, whenever Claude gives us back an assistant message, it can possibly have more than one tool use block inside of it. And we took a at this earlier so if we ask claude initially to add together 10 plus 10 and 30 plus 30 it can send back to us two separate tool use blocks one might ask us to run a calculator tool to evaluate 10 plus 10 and the second tool use block might ask us to evaluate 30 plus 30. so we need to set up this run tools function assuming that we might have more than one tool use block so here's how this function is going to work we're going to take a look at that message that we just got specifically the content property on it. Remember that content property is going to be a list of blocks. Inside there, we might have a text block that tells us what Claude is currently thinking or what it's currently doing. We don't really care about that text block too much. So I'm going to delete it out of this diagram. And then we're left with just the two separate or possibly more for that matter, tool use blocks.
So inside of this run tools function, we are going to iterate over all the different tool use blocks we got. And for each one, we're going to run the specified tool with a given input. So we'll take a look at this name field right here. We'll find the appropriate tool function to run, and we will run it with the given input. Then we're going to take all the outputs from each of these different tool runs, and we're going to assemble them into separate tool result blocks. Remember, a tool result block is how we communicate the result of running a tool back over to Claude. Once we have assembled all these different tool result blocks, we're going to put them all together into a list and return them from the run tools function.
Okay, so let's try to implement this. I know it's confusing. I know there's a lot going on here, but the code itself is actually not as bad as it might seem initially. So back over here inside of run tools, first thing I'm going to do is take a look at this messages content property. That is the list of blocks and I'm going to extract only the tool use blocks. So I'll say tool requests is block for block in message.content. if block.type is equal to tool use. So again, a little bit of a filter operation here. We are getting just the tool use blocks because those are the only ones we care about. And I'm calling this specifically tool requests because I think it makes a little bit more sense than tool use. These are requests by Claude for us to use a tool.
Then I'm going to make a empty list called tool result blocks. This is going to eventually contain all the different tool results that we create. Then I'm going to iterate over all these different tool requests. So for tool request in tool requests. So now we are iterating over each individual tool request. So this is where we now need to run a specified tool with the given inputs. And we know which tool we want to run based upon this name property. So we will say if tool request dot name is equal to currently the only tool that we have, which is get current date time. Then I want to run the get current date time function with the star star inputs from the tool request. So tool request dot input. That's going to give me some tool output.
And I'm now going to take this tool output and use it to assemble a brand new tool result block. It has been a while since we have made use of a tool result block. So let me just give you a quick reminder on what these things are. All right. So on the left-hand side is a tool use block. This is what we are currently working with. This is Claude's request to use a tool. On the right-hand side is a tool result block. So this is the response that we are formulating to Claude's request to run a tool. Remember that a tool result block has a couple of different properties that we need to assign to it. First, it's going to have a tool use ID. This needs to be exactly equal to the ID from the tool use block that is causing us to run a particular tool. Note here that the ID field over here on the left-hand side, it's called ID. And then on the tool result block, it's a totally different property. It's tool use ID, but they need to be exactly equal. Then content is going to be the output from our tool run. So the actual tool function, we need to make sure we just encode it as a string. No problem there. We can then also add on that optional is error property. If an error occurred when we ran the tool. And then finally, we also need to add in a type of tool underscore results.
All right, so back over here, now that we have our tool output, we're going to assemble our tool result block. And it's going to have all those properties I just pointed out to you. It will have a type of tool result, a tool use ID of the tool request.id. it will have a content and I'm going to take whatever I get out of my tool and I'm going to encode it as JSON using JSON dump string. I need to make sure that I import JSON. So I'll do that at the top of the cell. There we go. And then finally the is air, I'm going to set that to false for right now. And we're going to add in a little bit more robust air handling in just a moment.
So now that we have created our tool result block, we're going to add it into our list of tool result blocks. So I'll then do a tool result blocks append in tool result block. And then finally, outside of the for loop, I will return tool result blocks. Okay, so this is the start to our run tools function. So we've at least got an idea of what it does for us. We're going to filter out all the different tool use blocks for each one. We're going to run some given tool function and then put the result into a tool result block, assemble all the results and return that list.
So now we're going to add in two quick improvements to this function. First, we're going to add in a little bit better error handling. So we are currently always saying that there is no error. That's definitely not accurate. There might be a scenario where we run into some kind of error when we are running our tool function. So I'm going to immediately make a small improvement here to capture any error that might occur as we run our tool to do so. I'm going to wrap that with a try except statement. I'm going to fix my indentation like so. And then if I get down into the except statement down here, I still want to put together a tool result block and add it into this list. But now I want to have an is air of true, and I probably want to take the air message and put it into the content field right here so that Claude gets some better understanding of what error just occurred. So I'm going to copy tool result block right here, paste it down inside of the accept statement. I'm going to change is error to true. And then for content, I'm going to put in an F string where I put in error with E. So again, I'm providing some information back to Claude to help you understand why an error occurred. And remember, whenever an error does occur, Claude might try to run your tool again with some better arguments or better formed arguments.
Okay, so that's our first improvement. Now the second improvement I want to make right now, we only are considering one single type of tool, the get current date time tool. Remember later on, we might have multiple different tools. So besides just get current date time, we're going to eventually support add duration to date time and set a reminder. And honestly, we're going to add in another one even after that. So using this pattern right here, where we have an if statement that's just checking for get current date time, not really going to scale too well. So to figure out what tool to run and actually run it, I'm going to make another helper function right above run tools, and I'm going to call it run tool. This is going to take in a tool name and an input to that tool. And then inside of here, this is where we are going to do a series of if statements or any kind of check to figure out which tool function we need to run and then actually run it and return the result. So if tool name is get current date time, then I'm going to return get current date time with star star tool input. So now with this approach, if we ever add in additional tools, we can just put in additional if text right here. So if tool name is whatever other tool we have, we can run that particular tool function. So we are going to very shortly come back to run tool and add in some additional tools in just a little bit.
All right. So now to make use of that function, I'll come back down here. I'm going to indent that block right there to the try, the accept, and the tool result. I'm going to remove the if statement and then replace get current date time right here with run tool. And I'm going to pass into it our tool request dot name and tool request dot input. There we go. Okay. So this was a little bit of a painful refactor, but this is our run tools function, no more changes required. It's going to work pretty well. And we've also got our run tool function put together also working pretty well.
So now the very last thing we need to do is go back down to our conversation function and make use of run tools. So here's our call to run tools right here. So run tools is now going to return our list of tool result blocks. So I'll get tool results. I'm going to add that into my conversation history. So add user message with messages and tool results. Then outside of the while loop. So I'm making sure them outside of while I'll return the list of messages and that's it.
Okay. So now this run conversation function captures that entire loop that we discussed. So whenever we go into run conversation, we're going to call Claude. We're going to get back some assistant message. If the assistant message is asking for any tools, then we're going to continue on past the if statement, we're going to run those tools, get the results and add them in as a user message to our list of messages. Then we're going to go back up to the top of the while loop again and call Claude another time with the list of tool results inside of those messages. And we're going to repeat this process over and over again until Claude doesn't ask for a tool use anymore.
So now here's the point where hopefully everything's going to work. We'll do a quick test down here. So I'm going to make a list of messages. I'll add a user message to it. And I'm going to ask Claude to do something that's going to probably require two separate tool calls. So I'm going to ask Claude what the current time is in hour, hour, minute, minute format. And then same thing for second format. So in theory, Claude is probably going to break this up into two separate tool calls. I will then call run conversation and pass in the list of messages. I'm going to rerun all the cells inside this notebook because we have now changed a tremendous amount. So we'll do a run all, and then we'll take a look at the response we get.
So it looks like it definitely is the right answer right here, but I want to take a look at the list of messages to really understand what is happening. So initially we have our user message. We then get the initial response back from Claude. It has a text block and a tool use block. So once again, a message with multiple blocks inside of it. And this is why it's so critical now to make sure that all of our code will correctly handle multiple different blocks. Inside this first message, we have a tool use block where Claude is trying to get the current time in hour minute format. We then respond with a tool result. And then here's the interesting part. Claude then decides to make a second tool call back to us. So in the second tool call, we don't have a text part anymore. Once again, highlighting the importance of correctly handling multiple different tool parts inside of a single message. Inside this second message, we've now got a tool use block where Claude is now going to try to call get current date time in seconds format. We then send the result back to Claude and then we get a final response from Claude that has just a text block that says, here's the answer to your original query.
So this is a perfect result that definitely highlights every step that we just went through the entire process of running our conversation until we have a response that is not asking for tool use. And inside of our run tools function, the importance of taking a look at all the different blocks we get back, pulling out just the tool use blocks, and then running a tool for each of those blocks, formulating the response into a tool result, and then sending all the different tool results back into Cloud. This video has been long and probably rather confusing, but we've now got an excellent example of multi-turn tool calling. So now the last thing for us to do inside of this project is make sure that we can support multiple different tools. We need to add in support for the add duration to date time tool and the set reminder tool as well.