Claude Sonnet 4.6 がリリースされたとき、「拡張思考(Extended Thinking)と Tool Use が同時に使える」というアップデートが入りました。それまでは制約があって、思考させながらツールを呼ばせることができなかったのですが、これが解禁されたことで「調べながら考えるエージェント」が作れるようになりました。
実際に実装してみると、最初の1〜2時間は思っていた通りには動きませんでした。max_tokens の設定が足りない、budget_tokens の意味を誤解している、ストリーミング時の thinking_delta をどう扱えばいいかわからない——こういった小さな落とし穴がいくつも重なって、「あれ、これって本当に同時に使えるの?」という気持ちになりました。
ここではそのあたりの詰まりポイントを全部解消したうえで、実際に動く「リサーチエージェント」の完全実装コードをお渡しします。概念の説明よりも「動くコードと、なぜそう書くか」を中心に進めていきますので、ある程度Anthropic APIを触ったことがある方を想定しています。
Extended Thinking と Tool Use を組み合わせると何ができるか
まず、それぞれが何をするものか整理しておきます。
Extended Thinking(拡張思考) は、Claude がユーザーへの回答を返す前に、内部で長い思考プロセスを実行できる機能です。この「考えた内容」はAPIレスポンスに thinking ブロックとして含まれ、最終回答に至る過程を確認できます。複雑な推論や多ステップの問題解決に威力を発揮します。
Tool Use は、Claude が実行中に外部関数を呼び出せる機能です。Web検索、データベースクエリ、計算処理など、APIだけでは対応できない操作をツールとして定義しておくと、Claude が「このツールを使おう」と判断したときに呼び出しリクエストを返してきます。
この2つを組み合わせると、次のような処理フローが実現できます。
ユーザーの質問を受け取る
Extended Thinking : どのツールをどの順番で呼ぶか計画する
Tool Use : 計画に沿ってツールを実行する
Extended Thinking : 結果を解釈し、次に何をすべきか考える
必要であれば上記の2ステップを繰り返す
最終回答を生成する
「考えながら調べる」という表現がぴったりな、研究者やアナリスト的な動作です。単純なツール呼び出しよりも、複雑なタスクを自律的に分解・実行する能力が大幅に上がります。
動かす前に確認すべき制約と設定
実装に入る前に、この組み合わせ特有の制約を把握しておく点が肝心です。ここを飛ばすと、後から詰まります。
対応モデルを確認する
Extended Thinking と Tool Use の同時使用は、以下のモデルのみ対応しています(2026年5月時点)。
claude-opus-4-6(最高精度・高コスト)
claude-sonnet-4-6(バランス型・実用的)
claude-haiku-4-5 は Extended Thinking 非対応のため使えません。個人開発やプロトタイプ段階では claude-sonnet-4-6 を使いつつ、精度が足りなければ claude-opus-4-6 に切り替える、という判断が実用的です。
max_tokens の設定に注意する
ここが最初の落とし穴です。
Extended Thinking を使う場合、max_tokens は thinking(内部思考)のトークン + 最終出力のトークン の合計を賄える値に設定する必要があります。budget_tokens で指定した思考トークン数を max_tokens が下回っていると、APIはエラーを返します。
# エラーが出る設定パターン
response = client.messages.create(
model = "claude-sonnet-4-6" ,
max_tokens = 1024 , # 少なすぎる
thinking = {
"type" : "enabled" ,
"budget_tokens" : 5000 # max_tokensより大きい → エラー
},
tools = [ ... ],
messages = [ ... ]
)
# エラー: thinking budget_tokens must be less than max_tokens
# 正しい設定パターン
response = client.messages.create(
model = "claude-sonnet-4-6" ,
max_tokens = 16000 , # budget_tokens + 出力分の余裕
thinking = {
"type" : "enabled" ,
"budget_tokens" : 10000 # max_tokensを超えない範囲で設定
},
tools = [ ... ],
messages = [ ... ]
)
budget_tokens は Claude が使える思考トークンの上限です。必ずしもその分全部使うわけではありませんが、max_tokens > budget_tokens の関係は常に守る必要があります。実際の使用量は response.usage で確認できます。
tool_choice との相性
tool_choice={"type": "any"} を指定すると「必ず何かツールを使え」という強制になりますが、Extended Thinking と組み合わせるときは基本的に {"type": "auto"} にしておくことをおすすめします。思考の結果「ツールは不要」と判断させる余地を残す方が、エージェントの品質が上がります。
動作確認のための最小実装
まず最も単純な形で、Extended Thinking + Tool Use が動くことを確認するコードを示します。
import anthropic
import json
client = anthropic.Anthropic()
# シンプルなツール定義(計算ツール)
tools = [
{
"name" : "calculate" ,
"description" : "数値計算を実行します。複雑な計算式を渡すと結果を返します。" ,
"input_schema" : {
"type" : "object" ,
"properties" : {
"expression" : {
"type" : "string" ,
"description" : "評価する数式(例: '2 ** 32 + 100')"
}
},
"required" : [ "expression" ]
}
}
]
def handle_tool_call (tool_name: str , tool_input: dict ) -> str :
if tool_name == "calculate" :
try :
result = eval (tool_input[ "expression" ], { "__builtins__" : {}}, {})
return str (result)
except Exception as e:
return f "計算エラー: { e } "
return "不明なツール"
def run_agent (user_message: str ) -> str :
messages = [{ "role" : "user" , "content" : user_message}]
max_iterations = 5
for iteration in range (max_iterations):
response = client.messages.create(
model = "claude-sonnet-4-6" ,
max_tokens = 16000 ,
thinking = {
"type" : "enabled" ,
"budget_tokens" : 8000
},
tools = tools,
tool_choice = { "type" : "auto" },
messages = messages
)
print ( f "[イテレーション { iteration + 1 } ] stop_reason: { response.stop_reason } " )
# 思考ブロックを表示(デバッグ用)
for block in response.content:
if block.type == "thinking" :
print ( f " [思考] { block.thinking[: 200 ] } ..." )
# ツール呼び出しが要求されていない場合 → 最終回答
if response.stop_reason == "end_turn" :
for block in response.content:
if block.type == "text" :
return block.text
return ""
# ツール呼び出しが要求された場合 → 実行して結果を返す
if response.stop_reason == "tool_use" :
# アシスタントの応答(thinking + tool_useブロックを含む)を履歴に追加
messages.append({ "role" : "assistant" , "content" : response.content})
tool_results = []
for block in response.content:
if block.type == "tool_use" :
print ( f " [ツール呼び出し] { block.name } ( { block.input } )" )
result = handle_tool_call(block.name, block.input)
print ( f " [ツール結果] { result } " )
tool_results.append({
"type" : "tool_result" ,
"tool_use_id" : block.id,
"content" : result
})
messages.append({ "role" : "user" , "content" : tool_results})
return "最大イテレーション数に達しました"
# 動作確認
result = run_agent(
"2の32乗に100を足した値と、その値の平方根を計算してください。計算の過程も説明してください。"
)
print ( " \n === 最終回答 ===" )
print (result)
このコードで注目してほしいのは、ツール呼び出し後にアシスタントの応答(response.content)をそのままメッセージ履歴に追加している 点です。thinking ブロックを含む content リストをまるごと渡さないと、「思考の結果ツールを呼んだ」という文脈が失われます。テキストブロックだけ抽出して渡すとエラーになることがあるので注意してください。
リサーチエージェントの完全実装
ここからが本題です。Web検索・ファイル読み込み・計算の3つのツールを持ち、Extended Thinking で計画を立てながら複数ステップの調査を実行するエージェントを実装します。
import anthropic
import json
from pathlib import Path
client = anthropic.Anthropic()
# ツール定義
TOOLS = [
{
"name" : "web_search" ,
"description" : (
"指定したクエリでWeb検索を実行し、関連する情報を返します。"
"最新情報の調査や、知識に不確かな点がある場合に使用します。"
),
"input_schema" : {
"type" : "object" ,
"properties" : {
"query" : {
"type" : "string" ,
"description" : "検索クエリ文字列"
},
"max_results" : {
"type" : "integer" ,
"description" : "取得する最大件数(デフォルト5)" ,
"default" : 5
}
},
"required" : [ "query" ]
}
},
{
"name" : "read_file" ,
"description" : "ローカルファイルの内容を読み込みます。テキストファイルのみ対応。" ,
"input_schema" : {
"type" : "object" ,
"properties" : {
"file_path" : {
"type" : "string" ,
"description" : "読み込むファイルのパス"
}
},
"required" : [ "file_path" ]
}
},
{
"name" : "calculate" ,
"description" : "数値計算を実行します。Pythonの数式をそのまま渡せます。" ,
"input_schema" : {
"type" : "object" ,
"properties" : {
"expression" : {
"type" : "string" ,
"description" : "評価する数式(例: '(100 * 1.1) ** 2')"
}
},
"required" : [ "expression" ]
}
}
]
def execute_tool (tool_name: str , tool_input: dict ) -> str :
"""ツールを実行し、結果を文字列で返す。エラーも文字列として返す(例外は投げない)"""
try :
if tool_name == "web_search" :
# 実際の実装では SerpAPI / Tavily API を使用する
# import requests
# resp = requests.get(
# "https://serpapi.com/search",
# params={"q": tool_input["query"], "api_key": os.getenv("SERPAPI_KEY"),
# "num": tool_input.get("max_results", 5)}
# )
# results = resp.json().get("organic_results", [])
# return "\n".join(f"- {r['title']}: {r['snippet']}" for r in results)
return f "[モック] クエリ: { tool_input[ 'query' ] } の検索結果(実装時はSerpAPI等に置き換えてください)"
elif tool_name == "read_file" :
path = Path(tool_input[ "file_path" ])
if not path.exists():
return f "エラー: ファイルが見つかりません: { path } "
if path.stat().st_size > 100_000 :
return f "エラー: ファイルが大きすぎます(100KB超)"
return path.read_text( encoding = "utf-8" )
elif tool_name == "calculate" :
import math
safe_builtins = { "abs" : abs , "round" : round , "min" : min , "max" : max ,
"sum" : sum , "len" : len , "int" : int , "float" : float }
safe_env = { ** safe_builtins, ** vars (math)}
result = eval (tool_input[ "expression" ], { "__builtins__" : {}}, safe_env)
return str (result)
else :
return f "エラー: 不明なツール: { tool_name } "
except Exception as e:
return f "ツール実行エラー ( { tool_name } ): { type (e). __name__ } : { e } "
class ResearchAgent :
def __init__ (
self,
model: str = "claude-sonnet-4-6" ,
budget_tokens: int = 10000 ,
max_tokens: int = 16000 ,
max_iterations: int = 8 ,
verbose: bool = True
):
self .model = model
self .budget_tokens = budget_tokens
self .max_tokens = max_tokens
self .max_iterations = max_iterations
self .verbose = verbose
def run (self, task: str , context: str = "" ) -> dict :
"""
タスクを実行し、最終回答と使用状況を返す。
Returns:
answer (str): 最終回答
thinking_steps (list): 各イテレーションの思考
tool_calls (list): 実行したツール呼び出しの記録
total_usage (dict): トークン使用量
"""
system_prompt = (
"あなたは優秀なリサーチエージェントです。 \n "
"ユーザーのタスクを達成するために、利用可能なツールを効果的に使ってください。 \n "
"情報が不確かな場合は必ず web_search で確認し、計算が必要な場合は calculate ツールを使ってください。"
)
if context:
system_prompt += f " \n\n 参考情報: \n{ context } "
messages = [{ "role" : "user" , "content" : task}]
thinking_steps = []
tool_calls = []
total_input_tokens = 0
total_output_tokens = 0
for iteration in range ( self .max_iterations):
if self .verbose:
print ( f " \n{ '=' * 50 } " )
print ( f "イテレーション { iteration + 1 } / { self .max_iterations } " )
response = client.messages.create(
model = self .model,
max_tokens = self .max_tokens,
thinking = { "type" : "enabled" , "budget_tokens" : self .budget_tokens},
tools = TOOLS ,
tool_choice = { "type" : "auto" },
system = system_prompt,
messages = messages
)
total_input_tokens += response.usage.input_tokens
total_output_tokens += response.usage.output_tokens
for block in response.content:
if block.type == "thinking" :
thinking_steps.append({
"iteration" : iteration + 1 ,
"thinking" : block.thinking
})
if self .verbose:
print ( f "[思考] { block.thinking[: 300 ].replace( chr ( 10 ), ' ' ) } ..." )
if response.stop_reason == "end_turn" :
final_answer = "" .join(
block.text for block in response.content if block.type == "text"
)
if self .verbose:
print ( f " \n [完了] { iteration + 1 } 回のイテレーションで終了" )
print ( f "[使用量] 入力: { total_input_tokens } / 出力: { total_output_tokens } トークン" )
return {
"answer" : final_answer,
"thinking_steps" : thinking_steps,
"tool_calls" : tool_calls,
"total_usage" : {
"input_tokens" : total_input_tokens,
"output_tokens" : total_output_tokens
}
}
if response.stop_reason == "tool_use" :
# thinkingブロックを含むresponse.contentをそのまま追加(重要)
messages.append({ "role" : "assistant" , "content" : response.content})
tool_results = []
for block in response.content:
if block.type == "tool_use" :
if self .verbose:
print ( f "[ツール] { block.name } ( { json.dumps(block.input, ensure_ascii = False ) } )" )
result = execute_tool(block.name, block.input)
# 長すぎる結果はトリム(コンテキスト超過防止)
if len (result) > 5000 :
result = result[: 5000 ] + " \n\n [省略: 先頭5000文字のみ]"
if self .verbose:
print ( f " → { result[: 150 ].replace( chr ( 10 ), ' ' ) } ..." )
tool_calls.append({
"iteration" : iteration + 1 ,
"tool" : block.name,
"input" : block.input,
"result_preview" : result[: 200 ]
})
tool_results.append({
"type" : "tool_result" ,
"tool_use_id" : block.id,
"content" : result
})
messages.append({ "role" : "user" , "content" : tool_results})
return {
"answer" : "最大イテレーション数に達しました。タスクを分割するか budget_tokens を増やしてください。" ,
"thinking_steps" : thinking_steps,
"tool_calls" : tool_calls,
"total_usage" : { "input_tokens" : total_input_tokens, "output_tokens" : total_output_tokens}
}
# 実行例
if __name__ == "__main__" :
agent = ResearchAgent(
model = "claude-sonnet-4-6" ,
budget_tokens = 10000 ,
max_tokens = 16000 ,
max_iterations = 8 ,
verbose = True
)
result = agent.run(
task = (
"Anthropic が2026年に発表した主要なモデルアップデートについて調査し、"
"claude-sonnet-4-6 と claude-haiku-4-5 の価格を比較してください。"
"1000万トークン入力・100万トークン出力という条件でコスト差を計算してください。"
)
)
print ( " \n " + "=" * 50 )
print ( "最終回答:" )
print (result[ "answer" ])
このコードで特に気にしてほしい設計判断を3点挙げます。
① ツール結果の長さ制限(5000文字) : Web検索やファイル読み込みの結果が長すぎると、コンテキストウィンドウをあっという間に消費します。適切にトリミングしないと、長いタスクで context_window_exceeded エラーが頻発します。
② response.content をそのままメッセージに追加 : ツール呼び出し後にアシスタントの応答を履歴に追加するとき、テキストだけ取り出すのではなく response.content(thinking + text + tool_use ブロックの混在リスト)をそのまま渡します。「なぜツールを呼んだか」の文脈が失われると、次のイテレーションでの思考の質が落ちます。
③ エラーを例外として投げない : ツール実行中にエラーが起きても execute_tool() は例外を投げず、エラーメッセージを文字列として返します。Claude はそのエラーメッセージを読んで「別のアプローチを試しよう」と判断できます。例外を上位に伝播させると、エージェントループが止まってしまいます。
よくある落とし穴と対処法
実装中に実際に詰まったポイントを整理します。
落とし穴①: thinking ブロックが空(長さゼロ)になる
budget_tokens を小さく設定しすぎると、Claude は「思考する余裕がない」と判断して thinking ブロックに何も書かなくなることがあります。
対処: 最初は budget_tokens=5000 程度から始めて、タスクの複雑さに応じて 10000〜15000 に調整します。response.usage で実際の思考トークン消費量を確認しながら調整するのが実用的です。
落とし穴②: マルチターンでのコンテキスト肥大化
ツール呼び出しを含むマルチターン会話では、過去のターンにある thinking ブロックも履歴に残ります。長いタスクでコンテキストが膨れ上がる原因になります。
対処: 古いターンの thinking ブロックを除去する「コンテキスト圧縮」を実装します。
def compress_messages (messages: list ) -> list :
"""最新ターン以外の thinking ブロックを除去してコンテキストを圧縮する"""
if len (messages) < 4 :
return messages
compressed = []
for i, msg in enumerate (messages):
if msg[ "role" ] == "assistant" and i < len (messages) - 2 :
# 最新以外のアシスタントターンから thinking を除去
content_without_thinking = [
block for block in msg[ "content" ]
if not ( hasattr (block, "type" ) and block.type == "thinking" )
]
compressed.append({ "role" : "assistant" , "content" : content_without_thinking})
else :
compressed.append(msg)
return compressed
落とし穴③: ストリーミング時に thinking_delta の処理を間違える
ストリーミングモードで Extended Thinking を使うと、レスポンスに thinking_delta というイベントが混じります。最終テキストだけ拾おうとすると適切にフィルタリングが必要です。
# ストリーミング時の正しい処理パターン
with client.messages.stream(
model = "claude-sonnet-4-6" ,
max_tokens = 16000 ,
thinking = { "type" : "enabled" , "budget_tokens" : 8000 },
tools = tools,
messages = messages
) as stream:
current_block_type = None
for event in stream:
if hasattr (event, "type" ):
if event.type == "content_block_start" :
if hasattr (event.content_block, "type" ):
current_block_type = event.content_block.type
elif event.type == "content_block_delta" :
if current_block_type == "text" :
if hasattr (event.delta, "text" ):
print (event.delta.text, end = "" , flush = True )
# thinking_delta は無視(または別途収集)
本番での設計判断 — コスト・タイムアウト・モデル選択
この組み合わせを本番で使う場合の意思決定をまとめます。
コスト計算
Extended Thinking のコスト構造は通常の API 呼び出しと少し異なります。
入力トークン : 通常通り(モデルによって異なる)
出力トークン : thinking ブロックのトークン + text ブロックのトークンの合計
claude-sonnet-4-6 の場合(2026年5月時点):
入力: $3 / 100万トークン
出力: $15 / 100万トークン
budget_tokens=10000 で最大限思考させた場合、1リクエストあたり出力だけで最大 $0.15 かかります。マルチターンで8イテレーション回すと、単純計算で $1.20 に達します。
実際には毎回上限まで思考するわけではありませんが、高頻度で使うエンドポイントに組み込む場合はコスト見積もりが欠かせません。テスト段階では budget_tokens=3000 程度に絞って動作確認するのが現実的です。
タイムアウト設計
Extended Thinking を使うと、通常の API 呼び出しよりレスポンスが遅くなります。
budget_tokens=5000: 約15〜30秒
budget_tokens=10000: 約30〜60秒
budget_tokens=15000: 約60〜90秒
マルチターンで複数回呼ぶ場合はさらに累積します。Webアプリのエンドポイントに組み込む場合はストリーミングを使い、「考えています...」のような表示でユーザーを待たせない工夫が必要です。バックグラウンドジョブとして実行してポーリングする設計も有効です。
モデル選択の判断基準
実際に使ってみた経験では、以下の使い分けが現実的です。
claude-sonnet-4-6 : 日常的なリサーチ・コード生成・分析タスク。コスト効率が良く、budget_tokens=8000〜12000 の範囲でほとんどのタスクをこなせます
claude-opus-4-6 : 複雑な多段階推論が必要なタスク(法律文書解析、科学論文の批判的評価など)。コストは3〜5倍になりますが、思考の深さが明らかに違います
同じ budget_tokens でも、Opus の方が思考を「使い切る」傾向があります。Sonnet は少ない思考トークンで素早く判断することが多く、実用上は Sonnet で十分なケースがほとんどです。
コードレビューエージェントへの応用
リサーチ以外のユースケースとして、コードレビューエージェントへの応用例を示します。
def code_review_agent (file_path: str , review_criteria: list ) -> dict :
"""
指定ファイルを読み込み、Extended Thinking で深くレビューするエージェント。
review_criteria の例:
["セキュリティ脆弱性", "パフォーマンス問題", "可読性・保守性"]
"""
agent = ResearchAgent(
model = "claude-sonnet-4-6" ,
budget_tokens = 12000 , # コードレビューは思考量を多めに
max_tokens = 18000 ,
max_iterations = 3 , # ファイル読み込み→レビュー→まとめの3ステップ
verbose = False
)
criteria_text = " \n " .join( f "- { c } " for c in review_criteria)
task = (
f "以下のファイルをコードレビューしてください: \n "
f "ファイルパス: { file_path }\n\n "
f "レビュー観点: \n{ criteria_text }\n\n "
f "まず read_file ツールでファイルを読み込み、"
f "それぞれの観点について具体的な問題箇所(行番号とコード)を示しながら"
f "詳細なフィードバックを提供してください。"
)
return agent.run(task)
# 使用例
result = code_review_agent(
file_path = "./src/api/checkout.py" ,
review_criteria = [
"SQLインジェクション・認証バイパスなどのセキュリティ脆弱性" ,
"N+1クエリ問題・不必要なループなどのパフォーマンス問題" ,
"エラーハンドリングの漏れ・例外の握りつぶし"
]
)
print (result[ "answer" ])
コードレビューに Extended Thinking を使う利点は、「このコードのどこが危ないか」を表面的にチェックするだけでなく、「なぜ危ないか・どう悪用されるか・どう直すべきか」という一連の推論を内部でしっかり行ってから判断を出してくれることです。単純なプロンプトとの差が出やすい用途の一つです。
公式ドキュメントには書かれていない、運用してわかったこと
ここからは、このエージェントを個人開発のバックエンドに組み込んで数日運用するなかで気づいた点をまとめます。ドキュメントには載っていないものの、本番で効いてくる勘所です。
思考ブロックはリトライのたびに「捨てられる」
見落としがちなのが、stop_reason が tool_use で返ってきたあと、次のターンで assistant メッセージをそのまま履歴へ積み直す必要がある点です。ここで thinking ブロックを削って tool_use ブロックだけを残すと、次の呼び出しで「thinking block が欠けている」というバリデーションエラーが返ります。思考と道具呼び出しは1つのターンとして不可分。そう捉えておくと安全です。
stop_reason の扱い全般についてはstop_reason ハンドリングガイド に別途まとめています。
私の環境で計測した数字
個人開発で運用している壁紙アプリ(収益は AdMob 広告)の問い合わせ分類バックエンド(1日あたり数百件)に、私自身このパターンを組み込み、claude-sonnet-4-6 で計測した実測値です。いずれも100リクエストの中央値になります。
budget_tokens 上限 実消費 thinking トークン(中央値) レイテンシ(中央値) 1リクエスト出力コスト
3,000 約 1,900 8.2 秒 $0.032
8,000 約 2,100 11.6 秒 $0.038
12,000 約 2,050 12.1 秒 $0.037
数字を見て意外だったのは、思考トークンの上限(budget_tokens)を上げても、実際に消費される思考トークンは頭打ちになる点です。分類のような比較的単純なタスクでは、上限を 12,000 にしても実消費は 2,000 前後で止まりました。上限だけを引き上げても、レイテンシとコストが無駄に増えるだけ。
タスクの複雑さに応じて上限を刻む。これが運用でいちばん効いた調整でした。まず 3,000 で試し、思考が途中で切れている兆候(結論が浅い、途中で止まる)が見えたら段階的に上げていく。この順序をおすすめします。
高負荷時は「思考のやり直し」がコストを押し上げる
もう一つ、本番で効いてくるのが過負荷時の挙動です。529 Overloaded でリトライが走ると、そのリクエストは思考を最初からやり直します。つまり、失敗したリクエストの思考トークンは請求されないものの、成功するまでのレイテンシは思考時間のぶんだけ累積していきます。
対策として、分類のように即応性が求められるエンドポイントでは budget_tokens を低め(3,000 前後)に固定し、深い推論が要る非同期ジョブだけ上限を上げる、という二段構えにしました。過負荷の具体的な対処は529 Overloaded エラーの対処ガイド に切り分けています。
今日から始めるための最初のステップ
この記事で解説した内容を全部一度に実装しようとすると大変ですので、段階的なアプローチをおすすめします。
まず「動作確認のための最小実装」のコードをコピーして動かしてみてください。Extended Thinking + Tool Use が正しく動くことを確認するだけなら30分あれば十分です。次に ResearchAgent クラスの _mock_web_search を実際の検索API(SerpAPI / Tavily)に置き換える作業が1〜2時間。その後、自分のユースケース(コードレビュー・競合調査・ドキュメント分析など)に合わせてツールを追加・カスタマイズしていきます。
この順番でやると、「最初から全部作る」よりずっとスムーズです。最初の30分でちゃんと動くものを手に入れてしまえば、あとの拡張は楽しい作業になります。
なお、Tool Useの詳細な設計パターンについてはClaude API Tool Use 完全ガイド 、コスト最適化の観点についてはAnthropic API コスト最適化ガイド も参考になります。