全ドメインの土台になる1本。これを理解すると「Claude Codeが裏で何をしているか」が透けて見えるようになり、D1(27%)の問題が「知識問題」から「当たり前の話」に変わる。英語のSkilljarコースの代わりに、このページだけで Week 1 のインプットが完結するように書いてある。
最初に知っておくべきことは1つだけ。Claude API は「会話の全履歴を毎回送りつけて、次の1通の返事をもらう」仕組みだということ。
tomoさんが毎日見ている「Claude Codeがツールを呼んでは考え、また呼ぶ」動きは、すべてこの手紙の往復でできている。
まず道具なしの素の呼び出し。Pythonの公式SDK(pip install anthropic)を使う。
# 最小のClaude API呼び出し
import anthropic
client = anthropic.Anthropic() # APIキーは環境変数 ANTHROPIC_API_KEY から読む
response = client.messages.create(
model="claude-sonnet-5", # 使うモデル
max_tokens=1024, # 返事の長さの上限
system="あなたは簡潔に答えるアシスタント。", # 役割設定(システムプロンプト)
messages=[
{"role": "user", "content": "日本で一番高い山は?"}
],
)
print(response.content) # 返事の中身(ブロックのリスト)
print(response.stop_reason) # ← 今週の主役。なぜ止まったか
覚えるパラメータは4つだけ:model(誰に聞くか)、max_tokens(返事の上限)、system(役割)、messages(会話履歴)。messages は role(user か assistant)と content のペアが並んだリストで、会話が進むほどこのリストが長くなっていく。
返ってくる response には「返事の本文(content)」と一緒に、「なぜそこで書くのをやめたか」を示す stop_reason が入っている。ここが試験の最重要ポイント。
| stop_reason の値 | 意味 | プログラムがすべきこと |
|---|---|---|
"end_turn" | 言いたいことを言い終わった | ループ終了。返事をユーザーに見せる |
"tool_use" | 「このツールを使いたい」と要求して一時停止した | ループ継続。要求されたツールを実行し、結果を返す |
"max_tokens" | 上限に達して途中で切れた | 上限を増やす・続きを促す等の異常系処理 |
Claudeに「天気を調べる」などの能力を持たせるには、ツールのカタログ(名前・説明文・入力の型)を送信時に同封する。すると Claude は必要なときに「そのツールをこの引数で実行して」とお願いしてくる。実行するのはこちらのプログラム——Claudeは自分では何も実行できない。ここを誤解しないこと。
# ツールのカタログを定義して同封する
tools = [
{
"name": "get_weather",
"description": "指定した都市の現在の天気を返す。都市名は日本語でも英語でもよい。"
"天気・気温の質問に使う。ニュースや歴史の質問には使わない。", # ←説明文が命(D2)
"input_schema": { # 入力の型(JSONスキーマ)
"type": "object",
"properties": {
"city": {"type": "string", "description": "都市名。例: 東京"}
},
"required": ["city"],
},
}
]
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
tools=tools, # ← カタログを同封
messages=[{"role": "user", "content": "大阪の天気は?"}],
)
# stop_reason が "tool_use" になり、content にツール要求ブロックが入る:
# ToolUseBlock(id="toolu_abc123", name="get_weather", input={"city": "大阪"})
ツール要求が来たら、こちらで実行して結果を会話履歴に追記して送り返す。このとき「どの要求への返answerか」を tool_use_id で対応付ける。
# ツールを実行して、結果を「返信」する
weather = get_weather("大阪") # ←自分のプログラムで実行(API天気サービスを呼ぶ等)
messages.append({"role": "assistant", "content": response.content}) # Claudeの要求を履歴に残す
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": "toolu_abc123", # どの要求への答えか
"content": "晴れ、28度",
}],
})
# → この messages でもう一度 create() を呼ぶと、Claudeは結果を踏まえた返事をする
STEP 2〜4 の部品を while ループで繋ぐと、エージェントが完成する。これが今週の到達点。
# エージェントループ完全版(約40行)
import anthropic
client = anthropic.Anthropic()
def run_tool(name, args):
"""要求されたツールを実際に実行する(ここは自分のコード)"""
if name == "get_weather":
return f"{args['city']}は晴れ、28度" # 本物はAPIを呼ぶ
return f"不明なツール: {name}"
def agent(user_input):
messages = [{"role": "user", "content": user_input}]
while True: # ← エージェントループ
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
tools=tools, # STEP 4 のカタログ
messages=messages,
)
# Claudeの応答(テキストやツール要求)を履歴に追記
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason == "tool_use": # ★ ツールを使いたい → 続行
results = []
for block in response.content:
if block.type == "tool_use":
output = run_tool(block.name, block.input) # 実行するのは自分
results.append({
"type": "tool_result",
"tool_use_id": block.id, # 要求と結果を対応付け
"content": output,
})
messages.append({"role": "user", "content": results}) # 結果を履歴に追記
continue # → もう一周
if response.stop_reason == "end_turn": # ★ 話し終わった → 終了
return "".join(b.text for b in response.content if b.type == "text")
agent("大阪と札幌の天気を比べて")
読み解きのポイント:
tool_use なら実行して continue、end_turn なら return。試験のD1.1はこの構造を言葉で問うているmessages に追記される(=会話履歴が育つ)。「結果を履歴に足すからこそ、モデルは新情報を踏まえて次を考えられる」——これも頻出の言い回しいま書いた40行と、毎日使っている Claude Code の対応表:
| 40行のループの部品 | Claude Code での姿 |
|---|---|
tools カタログ | Read / Write / Bash / Grep / Glob / Task / MCPツール群 |
run_tool() | Claude Code本体(ファイルを読む・コマンドを実行する側) |
stop_reason == "tool_use" の一周 | 画面で「ツールを呼んでは結果を読む」1回分 |
stop_reason == "end_turn" | ターンが終わってあなたに返事が表示される瞬間 |
messages が育っていく | コンテキスト消費が増えていく(→限界が来ると /compact) |
| ループの外側の安全装置 | hooks(larc-approval-gate が実行前に割り込む等) |
この対応が腹落ちすると、D1の問題は「仕様の暗記」ではなく「いつも見ている光景の言語化」になる。
tool use には有名な副産物がある。「実在しないツール」を定義して無理やり呼ばせると、確実にスキーマ通りのJSONが手に入る。
# 「抽出結果を記録する」という体のツールを定義し、
# tool_choice で強制的に呼ばせる → 出力は必ずこのスキーマに従う
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
tools=[{
"name": "record_invoice",
"description": "請求書から抽出した情報を記録する",
"input_schema": {
"type": "object",
"properties": {
"issuer": {"type": "string"},
"total": {"type": "number"},
"due_date": {"type": ["string", "null"]}, # ←無いかもしれない欄はnull許容(捏造防止)
},
"required": ["issuer", "total"],
},
}],
tool_choice={"type": "tool", "name": "record_invoice"}, # ← 必ずこのツールを呼べ
messages=[{"role": "user", "content": f"この請求書から情報を抽出して: {document}"}],
)
# response.content の tool_use ブロックの input が、そのまま検証済みJSONになっている
「JSONで返して」とお願いするより確実(壊れたJSONが来ない)。ただし形式の保証であって中身の保証ではない(合計が合わない等の意味エラーは残る)——この区別はD4.3で深掘りする。
公式ガイド指定の演習を、この教室のレベル感に合わせて再構成したもの。全部で2〜3時間。私(Claude)に「W1演習やる」と言えば、環境準備から伴走する。
get_weather に加えて get_weather_history(過去の天気)を定義し、説明文で境界を明確に書き分ける。「昨日の東京の天気は?」が正しく履歴側に行くかテストする(→D2.1の体験){"errorCategory": "validation", "isRetryable": false, "message": "都市が見つからない"} の形でツール結果を返し、Claudeがユーザーに適切に説明することを確認する(→D2.2の体験)D1ページの 1.1〜1.3 を照合して、チェックリストを更新。Skilljarの動画版(Building with the Claude API の序盤)を見たい場合はChromeの日本語翻訳を併用。次は Week 2(マルチエージェント+Claude Code設定の「翻訳学習」)へ。