← Academy 日本語で学ぶ(一覧へ戻る)

claude api

Anthropic Academy コースの日本語ナレッジ(学習用)/ 全 76 項目

Building with the Claude API 全文ナレッジ(DA式・日本語)

Anthropic Academy 公式コース「Building with the Claude API」の全85レッスンを DA式完全版でナレッジ化。動画ノートは4層(要点整理→実務ポイント→時系列・日本語逐語+画面差し込み→英語逐語)、テキストレッスンは2層(日本語完全版+英語原文)。

  • 修了証: https://verify.skilljar.com/c/nntji5z59k24(NAOKI NAKASHIMA / 2026-07-03 / Anthropic Education)
  • 85 of 85 lessons completed(100%) = 動画74本 + テキスト2本(04・簡易)+ 章クイズ7 + Final Assessment + サーベイ
  • 章クイズ7個(各6問)+ Final Assessment 18問、すべて合格("You have passed!")
  • 動画74本 計約6時間・逐語 88,890語 / 講師: Colt Steele(Anthropic)ほか
  • 生成パイプライン: 署名なし HLS manifest(cdn.jwplayer.com/manifests/<mediaID>.m3u8 に Referer 付きで 200)→ ffmpeg(-nostdin)→ mlx_whisper large-v3-turbo(srt)+ フレーム抽出(scene 検出+30秒毎)→ 並列エージェントで DA式4層統合 → 2パス検証(60秒超ギャップ0)+整合性検収(2026-07-04)
  • 演習素材(実物): assets/images/(画像/PDF 教材)・assets/app_starter/(Claude Code 章のツールアプリ雛形)/ MCP章の cli_project 実物は ../introduction-to-mcp/assets/ と共通

目次

1. Accessing Claude with the API(01–11)

# ノート ひとこと
01 Welcome to the course コース全体像
02 Overview of Claude models Opus/Sonnet/Haiku の3ファミリーと選定軸(Intelligence↔Cost/Speed)
03 Accessing the API リクエスト5ステップと生成4ステージ(Tokenization→Embedding→Contextualization→Generation)
04 Getting an API key console.anthropic.com でのキー作成5ステップ(テキスト)
05 Making a request 初ハンズオン:.env+load_dotenv/client/messages.create
06 Multi-Turn conversations APIはステートレス=3ヘルパー関数で会話履歴を自前管理
07 Chat exercise while True+input のループ型チャットボット演習
08 System prompts 数学チューター題材・chat() の params 辞書リファクタ
09 System prompts exercise 「簡潔な Python エンジニア」system prompt 演習
10 Temperature 決定性↔創造性のダイヤル(0.0=事実/1.0=創作)
11 Response streaming 待ち時間体感の解消=逐次トークン配信

2. Structured data & Prompt evaluation(12–20)

# ノート ひとこと
12 Structured data stop sequences × prefill で JSON だけを取り出す
13 Structured data exercise AWS CLI 生成の3段階改善(bash → 宣言文 prefill)
14 Prompt evaluation Prompt Engineering vs Evaluation・「3つの道」導入
15 A typical eval workflow eval の全体像(dataset→run→grade)
16 Generating test datasets Haiku 等の速いモデルでテストケース生成
17 Running the eval プロンプトに dataset を通して応答収集
18 Model based grading 別 AI が採点=strengths/weaknesses/reasoning を求める
19 Code based grading 構文・形式のプログラム的検証
20 Exercise on prompt evals eval パイプライン総仕上げ

3. Prompt engineering(21–26)

# ノート ひとこと
21 Prompt engineering ゴール→初期→eval→手法→再eval ループ(初回 2.32/10)
22 Being clear and direct 動作動詞始まりの明確な1行目(2.32→3.92)
23 Being specific ガイドライン+ステップで誘導(→7.86)
24 Structure with XML tags 自作 XML タグでコンテンツを区切る(→7.94)
25 Providing examples one-shot/multi-shot でコーナーケースを押さえる
26 Exercise on prompting 弱いプロンプト(2.8)を採点理由から逆算し 9.5/10 へ

4. Tool use(27–38)

# ノート ひとこと
27 Introducing tool use Claude が外部システムにアクセスする仕組み
28 Project overview tool use プロジェクトの全体像
29 Tool functions ツール=Claude が呼ぶ普通の関数
30 Tool schemas JSON Schema でツールの引数を宣言
31 Handling message blocks TextBlock+ToolUseBlock のマルチブロック構造
32 Sending tool results tool_result(tool_use_id/content/is_error)で返す
33 Multi-turn conversations with tools ツール込みの複数ターン設計
34 Implementing multiple turns run_conversation の while ループ実装
35 Using multiple tools 複数ツールの併用
36 Fine grained tool calling ストリーミング中の細粒度ツール呼び出し
37 The text edit tool Anthropic 提供のテキスト編集ツール
38 The web search tool server tool=web_search_20250305・citations

5. Retrieval Augmented Generation(39–45)

# ノート ひとこと
39 Introducing RAG RAG の必要性と全体像
40 Text chunking strategies チャンク分割の戦略
41 Text embeddings 埋め込みによる意味検索
42 The full RAG flow RAG フロー全景
43 Implementing the RAG flow RAG フローの実装
44 BM25 lexical search 語彙一致検索
45 A Multi-Index RAG pipeline 意味+語彙のハイブリッド

6. Features of Claude(46–53)

# ノート ひとこと
46 Extended thinking 推論過程+最終回答の2部構成
47 Image support 画像入力(base64/メディアタイプ)
48 PDF support type="document"・media_type="application/pdf"
49 Citations 応答→原文への裏取りトレイル
50 Prompt caching 同一プレフィックス再利用で高速・低コスト化
51 Rules of prompt caching 最低1024トークン・キャッシュブレークポイント
52 Prompt caching in action キャッシュの実演
53 Code execution and the Files API ネットワーク遮断の隔離 Docker・Files API で事前アップロード

7. Model Context Protocol(54–64)

# ノート ひとこと
54 Introducing MCP MCP=ツール自作負担をサーバーへ移譲する通信レイヤー
55 MCP clients transport agnostic(stdio/HTTP/WS)・4メッセージ型
56 Project setup CLI チャットボット仕様・実務は client/server 片方だけ
57 Defining tools with MCP FastMCP+@mcp.tool+Pydantic Field(実物コード引用)
58 The server inspector MCP Inspector でツールを接続前テスト
59 Implementing a client クライアント実装
60 Defining resources @メンションで公開する resources
61 Accessing resources resources へのアクセス
62 Defining prompts 高品質・テスト済みの prompts を配布
63 Prompts in the client クライアントでの prompts 利用
64 MCP review MCP 章のまとめ

8. Anthropic apps(65–68)

# ノート ひとこと
65 Anthropic apps Claude Code+Computer Use=エージェントの実例(導入予告)
66 Claude Code setup ターミナル常駐・MCP クライアント内蔵・3ステップ導入
67 Claude Code in action /init・CLAUDE.md 3スコープ・Explore→Plan→Code/TDD
68 Enhancements with MCP servers claude mcp add で自作サーバー接続・拡張カタログ

9. Agents and Workflows(69–76)

# ノート ひとこと
69 Agents and workflows 定義と使い分け・Evaluator-Optimizer 導入
70 Parallelization workflows 材料別特化プロンプト並列+アグリゲータ
71 Chaining workflows 直列分解・初稿→書き直しの2段チェーン
72 Routing workflows トピック分類→専用パイプラインへ1経路転送
73 Agents and tools ツールは reasonably abstract に
74 Environment inspection エージェントは自分の行動結果を観測する必要がある
75 Workflows vs agents 精度・テスト性 vs 柔軟性=可能な限りワークフロー
76 Course Wrap Up 7領域リキャップ(最重要=prompt evals)+次の5トピック

章クイズ7個(各6問・完全収録/✅=正答)

Q章1「Accessing Claude with the API」

  1. Claude API に送る最小情報は? → ✅ API key, model name, messages, and max tokens
  2. 「ピザとは?」の後「人気のトッピングは?」が通じない原因は? → ✅ Claude は過去メッセージを覚えていない(履歴を毎回送る必要)
  3. Claude がテキストを処理する最初の工程は? → ✅ トークンという小さな塊に分解する
  4. 20秒スピナー後に全文が一度に出る遅さの解決策は? → ✅ レスポンスストリーミングを有効化
  5. Web アプリで API キーを置く場所は? → ✅ ユーザーがアクセスできないサーバー上
  6. 数学チューターにヒントだけ出させるには? → ✅ チューター役を説明する system prompt
  7. 事実 Q&A で一貫した回答が欲しい時の temperature は? → ✅ 低温(0.0 付近)
  8. 余分なテキストなしの生 JSON を得るには? → ✅ prefill メッセージと stop sequences の組み合わせ

Q章2「Prompt evaluation」

  1. 1回テストしただけで本番投入するリスクは? → ✅ ユーザーが想定外の入力で壊す
  2. テストケース生成に使うモデルは? → ✅ Haiku のような速いモデル
  3. 応答を得た後の次工程は? → ✅ グレーダーに通す
  4. 実運用での効きを測るには? → ✅ プロンプト評価(eval)
  5. モデルグレーダーでスコアと一緒に求めるべきものは? → ✅ 強み・弱み・理由
  6. 別の AI で品質評価するグレーダーは? → ✅ モデルグレーダー

Q章3「Prompt engineering」

  1. ワークアウトプラン生成の良い出だしは? → ✅ 「初心者向け30分のワークアウトプランを作成して」
  2. 皮肉を見落とす問題の最善策は? → ✅ 皮肉投稿をネガティブとラベル付けした例を提示
  3. プロンプトエンジニアリングとは? → ✅ より信頼性・品質の高い出力を得るためにプロンプトを改善すること
  4. 「入出力例を与える」技法は? → ✅ one-shot / multi-shot プロンプティング
  5. XML タグの主目的は? → ✅ 構造と明確さを加える(特に大量コンテンツ時)

Q章4「Tool use with Claude」

  1. Claude が次のツール呼び出しを望むか判別するには? → ✅ stop_reason フィールドが "tool_use" か見る
  2. ツール使用時のメッセージ構造は? → ✅ テキスト+ツール使用ブロックのマルチブロック
  3. JSON スキーマの主目的は? → ✅ 関数が期待する引数と使い方を Claude に伝える
  4. batch ツールが解決する問題は? → ✅ 複数ツール必要時の往復回数を減らす
  5. ツール使用ワークフローの正しい順序は? → ✅ Initial Request → Tool Request → Data Retrieval → Final Response
  6. リアルタイム情報を得る手段は? → ✅ ツールで外部情報にアクセス
  7. 組込みツール(text editor/web search)の特徴は? → ✅ Claude がスキーマを提供するが実装が必要な場合もある

Q章5「Features of Claude」

  1. Files API の用途は? → ✅ 事前アップロードし後で参照(毎回埋め込まない)
  2. 同じ大きな system prompt を何度も送る時に速く安くする機能は? → ✅ プロンプトキャッシュ
  3. Citations の主目的は? → ✅ 応答から原文の特定箇所への明確なトレイルを作る
  4. Extended thinking の応答の2部構成は? → ✅ 推論過程と最終回答
  5. PDF 分析と画像送信の主な違いは? → ✅ type を "document"、media_type を "application/pdf" に
  6. Code Execution ツールの主要な制限は? → ✅ ネットワークアクセスなし・隔離 Docker で実行
  7. プロンプトキャッシュが効く最低要件は? → ✅ コンテンツが最低1024トークン

Q章6「Model Context Protocol」

  1. ツールを接続前にテストする最善策は? → ✅ ブラウザの MCP Inspector を使う
  2. @document_name で文書内容を公開する MCP 機能は? → ✅ Resources
  3. 開発中のサーバーとクライアントの最も一般的な通信は? → ✅ 同一マシンの標準入出力(stdio)
  4. 自前 GitHub 統合より MCP を使う利点は? → ✅ MCP がツール定義と実行を肩代わりしてくれる
  5. Python SDK でツールを定義する最も簡単な方法は? → ✅ 関数に @mcp.tool デコレータ
  6. 高品質・テスト済みの整形指示を配布する MCP 機能は? → ✅ Prompts

Q章7「Agents and Workflows」

  1. 想定外リクエストに最も柔軟なツール設計は? → ✅ read_file/write_file/run_command のような抽象ツール
  2. レポート作成→良否判定→改善の反復パターンは? → ✅ Evaluator-Optimizer パターン
  3. トピック別に別パイプラインへ振り分けるパターンは? → ✅ 特化した処理パイプラインへルーティング
  4. 長い多要件プロンプトでルールを無視される時の対策は? → ✅ 焦点を絞った直列ステップにチェーン
  5. 材料別に異なる基準で最適材料を選ぶには? → ✅ 材料種別ごとに並列でリクエスト
  6. 信頼性・予測性が最重要な時の選択は? → ✅ ワークフロー(より信頼でき・テスト可能)
  7. 写真アップ→常に修理費見積の既知ステップなら? → ✅ あらかじめ決めたステップのワークフロー

Final Assessment(18問・完全収録/✅=正答)

  1. tool function とは? → ✅ Claude が追加情報や動作を必要とした時に実行される普通の関数
  2. プロンプト改善の手法適用後にすべきことは? → ✅ prompt eval で本当に改善したか確認
  3. 数学チューターにヒントを出させるには? → ✅ チューター役を説明する system prompt
  4. 予測不能で創造的な物語を書かせる temperature は? → ✅ 1.0(とても高い)
  5. 最も明確・直接的な出だしは? → ✅ 「ランニングシューズの商品説明を書いて」
  6. 実運用での効きを測る approach は? → ✅ 自動テスト付きのプロンプト評価
  7. Web アプリで API キーを置く場所は? → ✅ サーバー上・ユーザーから隠す
  8. モデルグレーダーとは? → ✅ 出力品質を評価する別の AI モデル
  9. レビューと売上データを整理する方法は? → ✅ <reviews> <sales_data> のような XML タグで区切る
  10. API に絶対必要な4つは? → ✅ API key, model name, messages, max tokens
  11. tool use の主目的は? → ✅ 訓練データを超えたリアルタイム情報・外部システムへのアクセス
  12. eval で応答取得後の次工程は? → ✅ グレーダーに通して採点
  13. 「ピザ」の文脈が続かない原因は? → ✅ 会話履歴を毎リクエストで送る必要がある
  14. MCP Server と Client の役割の違いは? → ✅ Server がツール/プロンプト/リソースを持ち、Client がアクセスの橋渡し
  15. Computer Use とは? → ✅ Claude が人間のようにデスクトップ環境を直接操作する能力
  16. "transport agnostic" の意味は? → ✅ HTTP や標準入出力など異なる方法で通信できる
  17. batch ツールの主目的は? → ✅ 複数ツール呼び出しを同時実行
  18. Claude がテキスト+ツール使用ブロックで返す構造は? → ✅ 異なるコンテンツタイプのマルチブロックメッセージ

(受験結果: 章クイズ7個+Final Assessment すべて "You have passed!" / 2026-07-03)

サーベイ回答

満足度=Satisfied、推奨度=4-Likely、自由記述=「API 基礎からエージェントまで網羅的。eval ワークフローと prompt caching のルールが特に有用。動画に字幕があると非英語話者に助かる」

逐語データ

  • transcripts/ — ASR 原文 txt+srt(74本・計88,890語)
↑ 目次へ戻る

Building with the Claude API | Lesson 02「Overview of Claude models」(全文ナレッジ)

動画 3分56秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

  • Claude には3つのモデルファミリー(Opus / Sonnet / Haiku)がある。3つとも Claude のコア機能(テキスト生成・コーディング・画像分析ほか)は共通で、違いは「何に最適化されているか」だけ。
  • Opus = 知能特化。Claude で得られる最高レベルの知能。複雑な要件・高度な計画性が必要なシナリオ向け。数時間に及ぶ長時間タスクを、人間の介入をほぼ受けずにマルチステップで自走できる。リーズニング(推論)対応:単純タスクは即答、複雑タスクは考える時間をかける。トレードオフはレイテンシ中程度・コスト高。
  • Sonnet = バランス型(スイートスポット)。知能・速度・コストの好バランスで、ほとんどの実用ユースケースに向く。強力なコーディング能力+高速なテキスト生成。複雑なコードベースへの「既存機能を壊さない精密な編集」が得意で開発者人気が高い。リーズニング対応。
  • Haiku = 速度・コスト特化。Claude 最速。応答時間が重要なリアルタイム・ユーザー向けアプリに最適。リーズニング非対応(Opus/Sonnet との明確な差分)。知能は中程度・低コスト・最速。
  • 選定フレームワーク:軸は「知能 ←→ コスト/速度」の1本のトレードオフ。
  • 知能最優先(強い推論が要る複雑タスク)→ Opus(品質>速度・コスト)
  • 速度最優先(リアルタイム対話・大量処理)→ Haiku
  • バランス重視(大半のアプリはこれ)→ Sonnet
  • 1アプリ=1モデルに固定しないのが実務。ユーザー向け対話は Haiku、メインのビジネスロジックは Sonnet、深い推論が要る複雑タスクは Opus、と同一アプリ内で使い分けるチームが多い。
  • 本コースでは以降、主に Sonnet を使用(3資質のバランスが最良のため)。

実務ポイント(このレッスンから持ち帰るもの)

  1. モデル選定は「何が最重要か」の一問に還元する:知能か、速度/コストか、バランスか。迷ったら Sonnet がデフォルト。
  2. リーズニング対応の有無が Haiku 採用の分岐点:思考時間が必要なタスクを Haiku に流さない。
  3. マルチモデル構成を前提に設計する:UI 応答(Haiku)/中核ロジック(Sonnet)/重い推論(Opus)のルーティングは公式スライドが明示するパターン。
  4. スライドの比較表(Cost: High/Medium/Low、Latency: Moderate/Fast/Fastest、Reasoning: Yes/Yes/No、Best used for)は顧客説明・社内モデル選定資料にそのまま転用できる。

本文(時系列・日本語逐語+画面差し込み)

🖥 [0:00] 画面: 黒背景のタイトルスライド。左上に ANTHROP\C ロゴ、中央に大見出し「Models Overview」。

[0:00] この動画では、Claude の3つのモデルファミリーを検証し、あなたの特定のユースケースにはどれが適しているのかを理解していきます。

[0:07] これらのモデルがどう違うのかを理解してもらうために、各モデルの主要な特性を一つずつ説明し、その後で、適切なモデルを選ぶためのシンプルなフレームワークをお見せします。

🖥 [0:16] 画面: スライド「Claude model family」。3列比較表(Claude Opus / Claude Sonnet / Claude Haiku): | | Claude Opus | Claude Sonnet | Claude Haiku | |---|---|---|---| | Description | Highest level of intelligence | Intelligent model that balances quality, speed, cost | Most cost-efficient and latency-optimized model | | Cost | High | Medium | Low | | Comparative latency | Moderate | Fast | Fastest | | Supports reasoning | Yes | Yes | No | | Best used for | ・Advanced software development, especially large-scale architecting ・Long running tasks that require sustained focus ・Strategic planning with multi-step problem solving ・Tasks that could benefit from advanced reasoning | ・Common coding tasks ・Document creation and editing ・Content marketing and copywriting ・Data analysis and visualization projects ・Image analysis ・Process automation | ・Quick code completions and suggestions ・Content moderation and filtering ・Data extraction and categorization ・Language translation ・Q&A systems and knowledge retrieval ・Most high-volume, straightforward text processing tasks |

[0:15] 詳細に入る前に、一つはっきりさせておきたいことがあります。

[0:18] これら3つのモデルはすべて、Claude のコア機能を共有しています。つまり、どのモデルもテキスト生成、コーディング、画像分析、その他多くのタスクを扱うことができます。

[0:26] 本当の違いは、それぞれがどのように最適化されているかにあります。

[0:29] 1つは知能に焦点を当てて作られ、1つは速度とコスト効率のために、そしてもう1つは知能と速度のバランスをより重視して作られています。

[0:37] 最初は Opus です。Opus は Claude の最も高性能なモデルです。「高性能」と言うのは、Claude から引き出せる最高レベルの知能を提供するモデルだ、という意味です。

[0:46] 実際には、Opus は、完了までに高いレベルの知能と計画性を必要とする複雑な要件があるシナリオのために設計されている、ということです。

[0:54] 複雑なプロジェクトに長時間、独立して取り組むことができます。たとえば数時間走り続けるようなタスクで、モデルが人間の介入をあまり受けずに、マルチステップのプロセスを管理し、多くの異なる要件を自力でさばいていく必要がある場合です。

[1:08] Opus は私たちが「リーズニング(推論)」と呼ぶものをサポートしています。つまり、シンプルなタスクには素早い応答を返し、より複雑なタスクにはしばらく考える時間をかけることができる、ということです。

[1:17] 欠点は、Opus のレイテンシが中程度で、コストが高いことです。それがまさに、あなたが受け入れるトレードオフです。

[1:23] 非常に高い知能が得られる一方で、リクエストごとに少し多くの時間とコストがかかります。

[1:29] 次は Sonnet です。

[1:31] Sonnet は Claude のラインナップの中で、いわばスイートスポットに位置しています。

[1:33] 知能・速度・コストのバランスが良く、ほとんどの実用的なユースケースで本当に役立ちます。

[1:40] Sonnet の素晴らしさは、強力なコーディング能力と高速なテキスト生成にあります。

[1:45] 多くの開発者は、複雑なコードベースに正確な編集を加えられる能力を気に入っています。つまり、既存の機能を大きく壊すことなくプロジェクトに変更を加えられる、ということです。

[1:54] 最後に Haiku です。Haiku は Claude の最速モデルで、応答時間が非常に重要なアプリケーションのために特別に作られています。

[2:02] Haiku について注意すべき重要な点は、Opus と Sonnet が持つリーズニング機能をサポートしていないことです。

[2:09] その代わり、Haiku は速度とコスト効率のために最適化されており、これにより Haiku は、リアルタイムのやり取りを必要とするユーザー向けアプリにとって非常に良い選択肢になります。

[2:14] では、あなたの特定のアプリケーションに、この3つのモデルのどれを使うかをどう決めるのかについて話しましょう。

🖥 [2:18] 画面: スライド「Picking the right model」。左右に伸びる両矢印の軸(左端 Intelligence / 右端 Cost/Speed)の上に3つのカード: - Claude Opus — Highly intelligent, more expensive, higher latency(左=知能側) - Claude Sonnet — Strong balance of intelligence, cost, and speed(中央) - Claude Haiku — Moderate intelligence, low cost, highest speed(右=コスト/速度側)

[2:19] モデル選択の考え方は、実のところ、これらのモデル間のトレードオフを理解することに尽きます。

[2:23] 一方の側には非常に高い知能があり、もう一方の側にはコストと速度があります。

[2:32] Opus は知能側に位置します。非常に知能が高く、より高価で、レイテンシも高めです。Haiku はコストと速度の側に位置します。中程度の知能、低コスト、そして最高の速度を持ちます。そして Sonnet はちょうどその中間にいて、これらの異なる資質の良いバランスを取っています。

[2:47] そこで、どのモデルを使うかの決め方です。あなたの特定のユースケースで何が最も重要なのかを特定する、つまり見極める必要があります。

[2:56] 知能が最優先なら——つまり本当に強力な推論を必要とする複雑なタスクがあるなら——おそらく Opus を使うべきです。速度とコストよりも品質を選んでいるわけです。

[3:06] 速度が優先なら——つまりリアルタイムのユーザーとのやり取りがあるか、できるだけ速くレスポンスを返す必要がある大量処理があるなら——Haiku を選ぶべきです。

[3:16] 知能・速度・コストのバランスがより必要なら——これはほとんどのアプリケーションに当てはまるケースですが——おそらく Sonnet が最良の選択です。

[3:26] ここで注意すべき重要な点は、多くのチームは1つのモデルを選んでそれに固執するわけではない、ということです。代わりに、同じアプリケーションの中で複数の異なるモデルを使うことがあります。

[3:31] 速度が本当に重要なユーザー向けのやり取りには Haiku を、メインのビジネスロジックにはおそらく Sonnet を、そして、より深い推論を必要とする本当に複雑なタスクには Opus を使う、というように。

[3:44] 以上で、Claude の3つのモデルファミリーと、その選び方をカバーしました。参考までに、このコースでは主に Claude の Sonnet を使っていきます。この3つの資質の実に素晴らしいバランスを提供してくれるからです。

英語逐語(ASR全文)

In this video, we are going to examine Claude's three model families and understand which one is right for your specific use case. To help you understand how these models differ, I'm going to walk you through each model's key characteristics and then show you a simple framework for picking the right one. Before we dive into the specifics, let me make one thing clear. All three of these models share Claude's core capabilities, so they can all handle text generation, coding, image analysis, and many other tasks. The real difference between them is how they are optimized. One is built to focus on intelligence, one for speed and cost efficiency, and one for more of a balance between intelligence and speed. The first is Opus. Opus is Claude's most capable model, and when I say capable, I mean to say that this is a model that delivers the highest level of intelligence that you can get out of Claude. In practice, that means that Opus is designed for scenarios where you have complex requirements that need a high level of intelligence and planning to complete. It can work independently on complex projects for a long period of time, like a task that can run on for several hours, where the model needs to manage multi-step processes and navigate a lot of different requirements on its own without a lot of human intervention. Opus supports what we call reasoning, which means it can provide a quick response for simple tasks, or it can spend some time thinking for a more complex task. The downside is that Opus has a moderate latency and a higher cost, and that's really the trade-off that you are making. While you get really high intelligence, it also takes a little bit more time and cost for every request that you make. Next up is Sonnet. Sonnet sits in a kind of sweet spot in Claude's lineup. It has a good balance of intelligence, speed, and cost that makes it really useful for most practical use cases. What makes Sonnet great is its strong coding ability along with its fast text generation. Many developers like its ability to make precise edits to complex codebases, meaning it can make changes to a project without breaking a lot of existing functionality. Finally, we have Haiku. Haiku is Claude's fastest model, and it's made specifically for applications where response time is really important. One important thing to note around Haiku is that it doesn't support the reasoning capabilities that Opus and Sonnet have. Instead, Haiku is optimized for speed and cost efficiency, and this makes Haiku a really good choice for user-facing apps that need some real-time interactions. Now let's talk about how you decide which of these three models to use for your particular application. The way to think about model selection really comes down to understanding the trade-off between these different models. On the one hand side, you've got really high intelligence, and on the other side, you've got more cost and speed. Opus sits on the intelligent side. It's really intelligent, more expensive, and also has higher latency. Haiku sits on the cost and speed side. It has moderate intelligence, low cost, and the highest speed. And SANA is right there in the middle, striking a good balance between these different qualities. (※ASR注:「SANA」は Sonnet の誤認識) So here's how you decide which model to use. You really need to identify or figure out what matters most for your specific use case. If intelligence is your top priority, meaning you have a complex task that needs really strong reasoning, then you probably want to make use of Opus. You are choosing quality over speed and cost. If speed is your priority, meaning you have real-time user interactions or you've got some high-volume processing where you need to get some responses back as fast as possible, then you want to choose Haiku. If you need more of a balance between intelligence, speed, and cost, which is often the case for most applications, then SANA is probably your best choice. One important thing to note here is that many teams don't just pick one model and stick with it. Instead, you might use multiple different models in the same application. You might use Haiku for user-facing interactions where speed is really important, maybe sonnet for your main business logic, and opius for the really complex tasks that need some deeper reasoning. (※ASR注:「opius」は Opus の誤認識) So that covers Claude's three model families and how to choose between them. Just so you know, we are most often going to use Claude's sonnet in this course just because it gives us a really fantastic balance of these three different qualities.

↑ 目次へ戻る

Building with the Claude API | Lesson 03「Accessing the API」(全文ナレッジ)

動画 5分18秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

  • チャットアプリを題材に、Anthropic API へのリクエストの完全なライフサイクルを5ステップで俯瞰する回。5ステップ= ①Request to Server → ②Request to Anthropic API → ③Model Processing → ④Response to Server → ⑤Response to Client。
  • ①クライアント→自前サーバー:Web/モバイルアプリから Anthropic API を直接呼んではいけない。API リクエストには秘密の API キーが必須で、キーを秘密に保つ唯一確実な方法は「クライアントサイドに一切埋め込まず、自分が実装したサーバー経由でのみ API を呼ぶ」こと。
  • ②サーバー→Anthropic API:通常は公式 SDK 経由(Python / TypeScript / JavaScript / Go / Ruby に公式実装あり)。SDK を使わず素の HTTP リクエストでも可。リクエストに必須のデータは4つ:API キー(リクエストの主を識別)/model(実行するモデル名)/messages(ユーザー入力テキストを「user メッセージ」として入れたリスト)/max_tokens(生成テキスト長の上限)。
  • ③モデル内部のテキスト生成は4ステージ(簡略化したハイレベル解説): 1. Tokenization(トークン化):入力を小さな文字列=トークンに分解。トークンは単語全体・単語の一部・スペース・記号のいずれもあり得る(本講では1単語=1トークンと仮定)。 2. Embedding(埋め込み):各トークンを「数の長いリスト」=その単語の数値的定義に変換。1つの単語は複数の意味を持ち得る(例:quantum)ため、埋め込みは「その単語のすべての可能な意味」を含むと考える。 3. Contextualization(コンテキスト化):周囲の埋め込みに基づいて各埋め込みを調整し、隣接語の文脈で最も筋が通る意味を際立たせる(多義性の解消)。 4. Generation(生成):処理済み埋め込みを出力層(Output Layer)に渡し、「次に来る単語」ごとの確率を算出。最高確率を自動採用するのではなく、確率+ランダム性のミックスで選ぶ(応答が自然で多様になる)。選ばれた単語を末尾に足してプロセス全体を繰り返す。
  • 生成停止の判定:1トークン生成するごとにモデルは自問する——(a) 生成トークン数が max_tokens を超えたか? (b) ストップシーケンスに一致するトークンを生成したか? (c) EOS(End of Sequence)トークン(通常の単語ではなく「自然な終わり」を示す特別なシグナル)を出力したか?
  • ④APIレスポンスには3つが入る:message(生成テキスト本体。「assistant メッセージ」として格納)/usage(入力+出力トークン数のカウント)/stop reason(なぜ生成を止めたか:自然な EOS 到達か、max_tokens 超過か等)。
  • ⑤サーバー→クライアント:サーバーが生成テキストをアプリへ返し、画面に表示して1往復が完了。
  • この回のゴールは暗記ではなく、API 経由で Claude を扱う際の共通用語(token / embedding / messages / max_tokens / usage / stop reason)に慣れること

実務ポイント(このレッスンから持ち帰るもの)

  1. API キーは絶対にクライアントに置かない。フロントエンドから直接 Anthropic API を叩く構成は設計段階で却下し、必ず自前サーバー(またはエッジ関数)を中継させる。
  2. リクエスト必須4点セット(API key / model / messages / max_tokens)を暗唱レベルで。SDK でもプレーン HTTP でも同じ。
  3. max_tokens は「目標」ではなく「安全上限」、そして stop_reason で「なぜ止まったか」を必ず確認する運用(max_tokens 到達での途中切れ検知)が後続レッスンの伏線。
  4. usage(入出力トークン数)がコスト管理の一次データ。レスポンスに毎回含まれるので、ログに残す設計にする。
  5. 出力層の「確率+ランダム性」の説明は、同じプロンプトでも毎回応答が変わる理由を非エンジニアに説明する際にそのまま使える。

本文(時系列・日本語逐語+画面差し込み)

🖥 [0:00] 画面: 黒背景のタイトルスライド。左上に ANTHROP\C ロゴ、中央に大見出し「Working with the API」。

[0:00] このモジュールでは、Claude にアクセスしてテキストを生成させる方法を検証していきます。

[0:04] この仕組みを理解してもらうために、Anthropic API へのリクエストの完全なライフサイクルを一通り説明します。あわせて、Claude の内部で舞台裏で何が起きているのかも簡単に見ていきます。

🖥 [0:16] 画面: スライド「Chat example」。シンプルなチャットUIのモック:メッセージ欄に吹き出し「You — What is quantum computing?」、下部に入力ボックスと「Send」ボタン。

[0:13] このウォークスルーを始めるにあたって、ごく標準的なチャットボットアプリを考えます。

[0:17] あなたが Web アプリを作っていて、ブラウザ上でユーザーにチャットウィンドウを表示したいと想像してください。

[0:21] ユーザーがメッセージを入力して送信をクリックすると、ユーザーの期待は「何らかの応答がただ魔法のように現れる」というものです。

[0:27] さて、先ほど言ったとおり、このテキストを生成して画面に表示するために、ここで舞台裏で何が起きているのかを検証したいのです。

🖥 [0:36] 画面: 全体図が登場。上部に5ステップのプロセスバー「Request to Server → Request to Anthropic API → Model Processing → Response to Server → Response to Client」。下部に3ブロック:左「Web or Mobile App」(チャットUI)→(Request 矢印)→ 中央「Your Server」(内部に Anthropic SDK ボックス)→(Request 矢印)→ 右「Anthropic API」。逆向きに Response 矢印が2本。

[0:36] これを5つの別々のステップに分解します。この図の上部に並べて示してあります。そして各ステップを1つずつ見ていきます。

[0:41] ユーザーが何かテキストを入力して送信をクリックすると、そのテキストは、開発者であるあなたが実装するサーバーへ送られます。

🖥 [0:45] 画面: プロセスバーの「Request to Server」だけがハイライト。図は Web or Mobile App →(Request)→ Your Server のみ表示。右側に注記ボックス2つ: - 「Requests to Anthropic should not be made directly from client code(Anthropic へのリクエストをクライアントコードから直接行ってはいけない)」 - 「API requests requires a secret API key, which shouldn't be exposed to the client(API リクエストには秘密の API キーが必要で、クライアントに露出させてはならない)」

[0:50] このステップに触れたのは、1つのことを明確にするためです。Web アプリやモバイルアプリから直接 Anthropic API にアクセスしようとしてはいけません。

[0:55] API にリクエストを送るときは常に、秘密の API キーを含めることが必須です。

[1:01] そして、このキーを確実に秘密のままにしておく最良の方法は、クライアントサイドのアプリの中には決して含めず、あなたが実装するサーバーを通してのみ API にリクエストを送ることです。

🖥 [1:15] 画面: プロセスバーの「Request to Anthropic API」がハイライト。Your Server(Anthropic SDK)→ Anthropic API への Request 矢印上に Field / Purpose 表: | Field | Purpose | |---|---| | API Key | Identifies your request to Anthropic | | Model | Name of the model to use | | Messages | List of "messages" containing the user's input text | | Max Tokens | Limit to how many "tokens" the model can generate |

左側に注記ボックス2つ:「Requests to the API can be made through a SDK or with a plain HTTP request」「Input text is placed inside of a "user" message, which is then placed in a list of messages

[1:12] ステップ2に進みます。サーバーがクライアントからリクエストを受け取ると、サーバーは Anthropic API に直接リクエストを送ります。

[1:17] 通常、このリクエストは Anthropic が公開している SDK のいずれかを通して行います。

[1:23] 公式の SDK 実装は、Python、TypeScript、JavaScript、Go、Ruby にあります。

[1:27] さて、使いたくなければ SDK を使う必要はありません。望むなら、素の HTTP リクエストを送ることもできます。

[1:33] このリクエストを行うとき、いくつかのデータを一緒に渡すことが要求されます。

[1:39] 具体的には、API キー、実行したいモデルの名前、ユーザーが送信したテキストを含むことになるメッセージのリスト、そして Claude が生成するテキストの長さを制限する max tokens 値を含める必要があります。

🖥 [1:58] 画面: プロセスバーの「Model Processing」がハイライト。左に「Anthropic API」ボックス、内部に4段のステージ:Tokenization(強調)/ EmbeddingContextualizationGeneration。左端に Request(入)/ Response(出)矢印。右側の点線枠内:上部「User input」=「What is quantum computing?」の箱から分岐して、5つのトークン箱「What | is | quantum | computing | ?」、右下に「Tokens」ラベル。

[1:54] 次は Anthropic API です。ここで私たちのテキストが実際に生成されます。ここでは、言語モデル内部のテキスト生成プロセスについて、少し詳しく踏み込みます。

[2:07] このプロセスは複雑なので、簡略化したハイレベルの概要をお伝えします。

[2:12] テキスト生成プロセスを、4つの別々のステージに分解します。

[2:17] 最初のステージでは、ユーザーの入力がより小さな文字列に分解されます。これらのテキストの断片は、それぞれ「トークン」と呼ばれます。

[2:22] トークンは単語全体のこともあれば、単語の一部のことも、さらにはスペースや記号のこともあります。

[2:28] 話を明確にするために、ここでは各単語が1つのトークンを形成すると仮定します。

[2:33] 各トークンは、次に「埋め込み(エンベディング)」に変換されます。埋め込みとは数の長いリストのことで、このリストは、ある単語の「数値ベースの定義」のようなものだと考えることができます。

🖥 [2:43] 画面: スライド「Possible meanings of "quantum"」。左の「quantum」の箱から矢印が4つの定義ボックスへ分岐: 1. A discrete, indivisible unit of a physical quantity, especially energy. From Latin "quantus" meaning "how much." 2. Relating to quantum mechanics or quantum physics, a branch of physics dealing with atomic and subatomic particles. 3. Extremely small or occurring at the subatomic level. 4. Relating to quantum computing, which uses quantum-mechanical phenomena to perform operations on data.

[2:43] さて、書き言葉の興味深い側面は、1つの単語が多くの可能な意味を持ち得ることです。

[2:48] そして、文中でのその単語の位置と、周囲にある他の単語の存在によってのみ、定義が1つの特定の意味へと絞り込まれます。

[2:53] たとえば quantum は多くの異なる定義を持つ単語で、この単語を見ただけでは、周りに他の単語が現れるまで、それが何を意味するのか実際には分かりません。

[3:02] 同様に、各埋め込みは「その単語のすべての可能な意味」を含んでいる、と考えることができます。

🖥 [3:04] 画面: プロセスバー「Model Processing」ハイライトのまま、Anthropic API ボックス内は「Contextualization」が強調。右の点線枠「Embeddings」内に5本の数値列カラム(例:-0.34, 0.87, -0.92, 0.15, 0.59, -0.61, …, -0.08 / -0.11, 0.83, -0.99, 0.37, 0.64, -0.48, …, 0.75 / 0.27, -0.94, 0.42, -0.16, 0.88, 0.09, …, 0.55 / -0.02, 0.77, -0.63, 0.29, 0.54, -0.98, …, 0.85 / 0.35, -0.86, 0.50, -0.21, 0.97, 0.04, …, 0.62)。カラム同士を結ぶ弧状の線が上部に多数描かれ、相互参照を表現。

[3:07] 各埋め込みを1つの正確な定義へと磨き込むために、「コンテキスト化(contextualization)」と呼ばれるプロセスが使われます。

[3:12] コンテキスト化では、各埋め込みが、その周囲の他の埋め込みに基づいて調整されます。

[3:19] このプロセスは、隣接するものを踏まえて最も意味の通る、各埋め込みの意味を際立たせる助けになります。

🖥 [3:32] 画面: Anthropic API ボックス内は「Generation」が強調。上部に Embeddings の5カラム、そこから「Output Layer」(濃色の帯)へ矢印。Output Layer の下に確率付き候補トークン6箱:「30% Quantum | 23% Great | 19% Are | 10% What | 10% This | 8% To」。

[3:23] 最後のステップは生成(generation)で、ここで実際にテキストが書かれます。

[3:28] この時点までに、各埋め込みは隣接するものから膨大な量の情報を吸収しています。

[3:32] 最終的に処理された埋め込みは出力層(output layer)へ渡され、そこで「可能な次の単語」それぞれについての確率が生成されます。

[3:42] さて、モデルは自動的に最も高い確率のものを選ぶわけではありません。代わりに、確率とランダム性のミックスを使って単語を選択します。これが、より自然で変化に富んだ応答を作り出す助けになります。

[3:52] 選択された単語は、埋め込みのリストの末尾に追加され、そしてプロセス全体が最初からもう一度繰り返されます。

🖥 [4:00] 画面: Generation 強調のまま、右の点線枠に「Output tokens」:「Quantum | mechanics | is | a | form | of | computing. | EOS」。EOS から線が伸び、下に3つの判定質問ボックス: - Have we exceeded the "max tokens" provided with the request?(リクエストで与えられた max tokens を超えたか?) - Did we generate a token matching a stop sequence?(ストップシーケンスに一致するトークンを生成したか?) - Did we output an End of Sequence token?(End of Sequence トークンを出力したか?)

[3:57] 出力トークンを1つ生成するたびに、モデルは一時停止し、テキストの生成を終えたかどうかを判断するために、いくつかの質問を自分自身に投げかけます。

[4:02] まず、これまでに生成したトークンの数を数え、それが入力リクエストと一緒に提供された max tokens パラメータより大きいかどうかを確認します。

[4:12] この max tokens パラメータは、モデルが生成するトークンの総数を制限します。

[4:18] また、モデルが生成できる特別な「エンドオブシーケンス(end of sequence)」トークンというものもあります。これは通常の単語ではありません。

[4:24] それは、モデルが「自分が考える生成の自然な終わり」に達したこと、そして停止すべきであることを示すために使う、特別なシグナルです。

🖥 [4:32] 画面: プロセスバーの「Response to Server」までハイライト。Anthropic API → Your Server(Anthropic SDK)への Response 矢印上に Data / Purpose 表: | Data | Purpose | |---|---| | Message | Single "message" that contains the generated text | | Usage | Number of input + output tokens | | Stop Reason | Why the model stopped generation |

左に注記ボックス:「Output text placed into an "assistant" message(出力テキストは "assistant" メッセージに格納される)」

[4:28] 生成が完了すると、API はあなたのサーバーへレスポンスを返します。

[4:33] レスポンスには、生成されたテキストがその中に入った message が、usage と stop reason とともに含まれます。

[4:38] usage は、あなたがモデルに与えた入力トークン数と、生成されたトークン数のカウントです。

[4:47] stop reason は、モデルがなぜテキスト生成を停止することにしたのかを正確に教えてくれます。自然なエンドオブシーケンストークンに到達したのか、あるいは割り当てられたトークン数を超えてしまったのか、といったことです。

🖥 [4:58] 画面: プロセスバーの「Response to Client」までハイライト。Your Server(Anthropic SDK)→ Web or Mobile App へ「Generated text」矢印。チャットUIには「You — Define quantum computing」の下に「AI — Quantum computing is a form of computing that uses quantum mechanical phenomena...」の吹き出しが表示。

[4:57] あなたのサーバーがこのレスポンスを受け取ると、生成されたテキストを Web またはモバイルアプリへ送り返し、そこであなたはそれを画面に表示します。

🖥 [5:05] 画面: 締めくくりとして5ステップ全体図(Web or Mobile App / Your Server=Anthropic SDK / Anthropic API、Request/Response 矢印)を再表示。

[5:05] 以上が、全体の流れです。

[5:07] さて、この動画では多くのトピックをカバーしました。

[5:09] 今の時点でこれを暗記してほしい、とは思っていません。

[5:12] 唯一のゴールは、API を通して Claude にアクセスする際の一般的な用語に、あなたが慣れ始めることです。

英語逐語(ASR全文)

In this module, we are going to examine how we access Claude and use it to generate some text. To help you understand how this works, I'm going to walk you through the full lifecycle of requests to the Anthropoc API. We are going to also take a brief look at what is going on behind the scenes inside of Claude. (※ASR注:「Anthropoc」は Anthropic の誤認識。以下同) To get started with this walkthrough, we are going to consider a straightforward standard chatbot app. Let's imagine that you are building a web app and want to show a chat window to a user in the web browser. When the user enters a message and clicks send, their expectation is that some response will just magically appear. Now, like I said, I want to examine what is going on behind the scenes here to generate this text and display it on the screen. We are going to break this down into five separate steps, which I have outlined at the top of this diagram. And we're going to walk through each step one by one. When a user enters some text and clicks send, that text is going to be sent off to a server that you, the developer, implement. I mentioned this step just to make one thing clear. You should not attempt to access the Anthropoc API directly from a web or mobile app. Whenever you make a request to the API, you are required to include a secret API key. And the best way to make sure that this key stays secret is by never including it inside of your client-side app and only making requests to the API through a server that you implement. On to step two. Once your server has received a request from the client, the server will make a request directly to the Anthropoc API. Usually, you'll make this request through one of the SDKs that Anthropic has published. There are official SDK implementations for Python, TypeScript, JavaScript, Go, and Ruby. Now you don't have to use an SDK if you don't want to. You can also make a plain HTTP request if you wish. When you make this request, you are required to pass along several pieces of data. In particular, you need to include an API key, the name of the model you wish to run, a list of messages, which will include the text that your user submitted, and a max tokens value, which limits the length of the text that Claude will generate. Next up is the Anthropic API, which is where our text will actually be generated. This is where we are going to go into a little bit of detail on the text generation process within the language model. This process is complex, so I'm going to give you a simplified high-level overview. We are going to break down the text generation process into four separate stages. In the first stage, the user's input will be broken down into smaller strings. Each of these text chunks are referred to as a token. These tokens can be whole words, or a part of word, or even a space or a symbol. To keep things clear, we are going to assume that each word forms one single token. Each token is then converted into an embedding. An embedding is a long list of numbers, and you can think of these lists as being like a number-based definition of a given word. Now, an interesting aspect of written language is that a single word can have many possible meanings, and it is only the word's position in a sentence and presence of other words around it that narrows the definition down to one particular meaning. For example, quantum is a word that has many different definitions, and when we see this word, we don't really know what it means until we see other words around it. Likewise, each embedding can be thought of as containing all possible meanings of each word. To refine each embedding down to a single precise definition, a process known as contextualization is used. In contextualization, each embedding is adjusted based upon other embeddings around it. This process helps highlight the meaning of each embedding that makes the most sense given its neighbors. The last step is generation, which is where the text actually gets written. By this point, each of the embeddings has absorbed a tremendous amount of information from their neighbors. The final processed embeddings are then passed to an output layer, which produces probabilities for each possible next word. Now, the model doesn't automatically pick the highest probability. Instead, it uses a mix of probability and randomness to select words, which helps create more natural and varied responses. The selected word is then added on to the end of our list of embeddings, and the entire process repeats itself all over. After generating each output token, the model will then pause and ask itself several questions to decide if it is done generating text. First, it will count the number of tokens it has generated and see if it is larger than the max tokens parameter that was provided with the input request. This max tokens parameter will limit the total number of tokens that the model will generate. There is also a special end of sequence token that the model can generate. This is not a regular word. It is a special signal that the model uses to indicate that it has reached what it considers to be a natural end to its generation and that it should stop. Once the generation is complete, the API will send a response back to your server. The response will contain a message, which has the generated text inside of it, along with usage and stop reason. The usage is a count of the number of tokens that you fed into the model and the number of tokens that were generated. The stop reason will tell you exactly why the model decided to stop generating text, whether it hits a natural end-of-sequence token or maybe it exceeded the allotted number of tokens. Once your server has received this response, it will send the generated text back to your web or mobile app, where you will display it on the screen. So that is the entire flow. Now, we covered many topics in this video. I don't expect you to memorize any of this just yet. The only goal is to start to get you familiar with some common terminology around accessing Claude through the API.

↑ 目次へ戻る

Building with the Claude API | Lesson 04「Getting an API key」(全文ナレッジ)

テキストレッスン(TEXT lesson 296766)/ 出典: Anthropic Academy / 2026-07-03

日本語ナレッジ(完全版)

次の動画(Lesson 05「Making a request」)では Anthropic API へ実際にリクエストを送る。そのためには秘密の API キーが必要になる。このガイドは、その API キーの作成手順を1つずつ説明するもの。

手順(5ステップ)

  1. ステップ1https://console.anthropic.com/ にアクセスし、ログインする。
  2. ステップ2:ダッシュボード右上の「Get API Keys」ボタンをクリックする。
  3. ステップ3:右上の「Create Key」ボタンをクリックする。
  4. ステップ4:workspace に「Default」を指定し、キーの名前を入力する(例:「Anthropic Course」)。
  5. ステップ5キーをコピーする。キーはポップアップで一度しか表示されない。誤ってポップアップを閉じてしまった場合は、古いキーを削除して、もう一度キーを生成し直せばよい。

補足(コース文脈での位置づけ) - ここで作成したキーは、Lesson 05 で .env ファイル(ANTHROPIC_API_KEY="...")に保存し、python-dotenv で読み込んで使う。 - キーは秘密情報。クライアントサイドのコードに埋め込まない(Lesson 03)、Git にコミットしない(Lesson 05 で .gitignore 運用を指示)。

英語原文

[TEXT lesson 296766] Getting an API key

In the next video we will be making a request to the Anthropic API. To do so, you will need a secret API key. This guide walks through creating one. Step One: Navigate to https://console.anthropic.com/ and log in. Step Two: Click the 'Get API Keys' button (top right of dashboard). Step Three: Click the 'Create Key' button (top right). Step Four: Enter workspace 'Default' and a name (e.g., 'Anthropic Course'). Step Five: Copy the Key — displayed only once in a popup. If closed accidentally, delete the old key and generate again.

↑ 目次へ戻る

Building with the Claude API | Lesson 05「Making a request」(全文ナレッジ)

動画 6分2秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

  • 初めてのハンズオン回。Jupyter ノートブック(VS Code 内)で Anthropic API へ最初のリクエストを送る。セットアップは4ステップ: 1. SDK インストール%pip install anthropic python-dotenv(VS Code だと % に赤い構文エラー表示が出ることがあるが無視してよい)。 2. API キーの保存と読み込み:ノートブックと同じディレクトリに .env ファイルを作り、ANTHROPIC_API_KEY="<キー>" を記述。python-dotenv の load_dotenv() で環境変数として安全に読み込む。.env は必ず Git の ignore 対象にする(公開リポジトリへの誤 push でキーが漏れるのを防ぐため)。 3. クライアント作成from anthropic import Anthropicclient = Anthropic()。あわせて model = "claude-sonnet-4-0"(Claude Sonnet 4)を変数として宣言。 4. リクエスト実行client.messages.create() を呼ぶ。
  • create 関数の必須キーワード引数は3つ
  • model:実行するモデル名(事前に定義した変数を渡す)。
  • max_tokens生成トークン数の上限予算。例:1000 を渡すと、それより長い生成は自動停止され最初の1000トークンだけが返る。Claude はこの数を「目標」にはしない(1000トークンの応答を書こうとするのではなく、適切と思う長さを書くだけ)。つまり max_tokens は「生成しすぎ防止の安全機構」と捉える。
  • messages:モデルへの入力。今後の動画の大きな焦点
  • メッセージは2種類
  • user メッセージ:Claude に与えたいテキスト。ユーザー本人または開発者、つまり「人間が書いたテキスト」が入る。
  • assistant メッセージ:モデルが生成して返してきたテキスト。
  • user メッセージの作り方{"role": "user", "content": "<Claude に送る文字列>"} の辞書を messages リストに入れる。
  • レスポンスの読み方:返ってくる messageMessage(id=..., content=[TextBlock(text=...)], model=..., role='assistant', stop_reason='end_turn', usage=Usage(input_tokens=..., output_tokens=...)) のように多くのプロパティを含む深い構造。生成テキストだけ欲しいときは message.content[0].text で取り出す。

実務ポイント(このレッスンから持ち帰るもの)

  1. 鉄板の最小コード一式(そのまま雛形に使える): ```python %pip install anthropic python-dotenv

from dotenv import load_dotenv load_dotenv()

from anthropic import Anthropic client = Anthropic() model = "claude-sonnet-4-0"

message = client.messages.create( model=model, max_tokens=1000, messages=[ { "role": "user", "content": "What is quantum computing? Answer in one sentence" } ] ) message.content[0].text `` 2. **キー管理の作法**:.envに保存 →load_dotenv()で読み込み →.gitignoreに追加。この3点セットを新規プロジェクトの初手にする。 3. **max_tokens は安全弁**として常に明示する(1000 は講師いわく「間違いなく安全な上限」)。長さのコントロール手段ではない点に注意。 4. **message.content[0].textがテキスト取り出しのイディオム**。content はブロックのリストである(後のレッスンで複数ブロックを扱う伏線)。 5. usage(input_tokens / output_tokens)と stop_reason(例:end_turn)がレスポンスに毎回含まれる——Lesson 03 の座学が実データで確認できる。 6. (画面上の事実として)コード側はmodel = "claude-sonnet-4-0"だが、収録されたレスポンス表示はmodel='claude-3-7-sonnet-20250219'` になっており、収録時点のエイリアス/収録タイミングの差が見える。モデル名はエイリアスでなくレスポンスの実名で確認する癖をつけるとよい。

本文(時系列・日本語逐語+画面差し込み)

🖥 [0:00] 画面: スライド「Getting Started」。縦のタイムラインに4ステップ: 1. Install the Anthropic Python SDK and 'python-dotenv' 2. Use 'python-dotenv' to store and load your API key(Directions for creating API key in previous lecture) 3. Create a client to make requests to the Anthropic API 4. Make a request and examine the response!

[0:00] ここまでかなり話が続いたので、この動画では趣向を変えて、実際に少しコードを書いて手を動かしていきます。

[0:06] Anthropic API へのシンプルで基本的なリクエストの送り方を学びます。

[0:09] 4つのセットアップステップを順に説明します。

[0:12] ステップ1では、Jupyter ノートブックを開き、Anthropic Python SDK と、python-dotenv というパッケージをインストールします。

🖥 [0:21] 画面: VS Code に切り替わり、Jupyter ノートブック 001_requests.ipynb が開く。あらかじめ4つのコメント入り Python セルが用意されている:# Install dependencies# Load env variables# Create an API client# Make a request。カーネルは .venv (Python 3.9.6)

[0:20] 始めるにあたって、自分のノートブックを開きます。

[0:22] このプロセスの道しるべになるよう、ノートブックにはあらかじめコメントをいくつか追加してあります。

🖥 [0:30] 画面: 最初のセルに入力: ```python

Install dependencies

%pip install anthropic python-dotenv ```

[0:26] 上のステップ1では、マジックインストールコマンドを追加します。つまり、%pip install anthropic python-dotenv です。

[0:33] 私のように Visual Studio Code の中でノートブックを書いている場合、この % の部分から赤い構文エラーが出ているのに気づくかもしれません。その構文エラーが見えても、まったく問題ありません。無視してかまいません。

🖥 [0:48] 画面: セル実行結果([2] 0.3s)。出力に「Requirement already satisfied: anthropic in ./.venv/lib/python3.9/site-packages (0.50.0)」「Requirement already satisfied: python-dotenv in ./.venv/lib/python3.9/site-packages (1.1.0)」以下、anyio (4.9.0)・distro (1.9.0)・httpx (0.28.1)・jiter (0.9.0)・pydantic (2.11.4)・sniffio (1.3.1)・typing-extensions などの依存が続く。直後、出力はクリアされ ✓ 0.4s 表示のみに。

[0:45] このコマンドを書き終えたら、実行してこれらのパッケージをインストールします。

[0:48] それから、私の画面で何が起きているかを少し見やすくするためだけに、出力をクリアしておきます。

[0:52] 次に、その python-dotenv パッケージを使って、API キーを保存し、読み込みます。

[0:59] 念のためですが、前のレクチャーに API キーの作成手順を載せてあります。まだ API キーを作成していない場合は、前のレクチャーに戻ってその手順を見つけることをお勧めします。

🖥 [1:09] 画面: エクスプローラーでノートブックと同じ階層に .env ファイルを新規作成し、1行記述: ANTHROPIC_API_KEY="sk-ant-api03-KQpNVXzr1tWamz60D2X0z0vV857XV3iEuSohAjN9K7Dql-9DT…" (デモ用に発行されたキー。行の右端は画面外で見切れている)

[1:07] このキーを保存するために、エディタの中で、ノートブックと同じディレクトリに、非常に特別な名前のファイルを作成します。このファイルを .env と名づけます。

[1:18] そしてこの中に、先ほど生成した API キーを配置します。

[1:22] 正確に、anthropic アンダースコア API アンダースコア key、それからイコール記号、と書きます。

[1:28] そしてダブルクォートの中に、そのキーを入れます。

[1:31] ちょっとした補足です。このファイルを作ってキーをこの中に入れる理由はまるごと、バージョン管理を使うときにこのファイルを ignore できるようにするためです。うっかりこのファイルを、たとえば Git にコミットして、さらにうっかり、誰でもこのファイルを見られるどこかの公開リポジトリにプッシュしてしまわないように、ということです。

[1:46] ですから繰り返しますが、Git や類似のバージョン管理システムを使っているなら、作業をコミットする際には必ずこのファイルを ignore するようにしてください。

🖥 [2:00] 画面: ノートブックに戻り、2つのセルにコードが入る: ```python

Load env variables

from dotenv import load_dotenv

load_dotenv() python

Create an API client

from anthropic import Anthropic

client = Anthropic() model = "claude-sonnet-4-0" `` (この画面ではカーネル表示が.venv (Python 3.13.3)` になっている)

[1:55] さて、ノートブックの中に戻って、その環境変数を安全に読み込むことができます。

[2:00] それからステップ3に進みます。ここでは anthropic パッケージを使って API クライアントを作成します。

[2:05] このセルの中では、model という名前の変数も宣言します。これは文字列で、Anthropic API の中で実行したいモデルの名前が入ります。Claude Sonnet 4 を使います。

[2:16] さて最後のステップに進みます。ここでは、いま作成したそのクライアントを使って、実際にリクエストを送ります。

[2:20] ただし、コードを書き出す前に、ちょっとした用語をお見せしたいと思います。この先の話を少し楽にしてくれる内容です。

🖥 [2:28] 画面: スライド「The 'create' function」。サブタイトル「Generates text by making a request to Claude」。右にコードブロック: python client.messages.create( model=model, max_tokens=1000, messages=[ # List of messages to send ] ) 左の注釈ボックス3つが各引数を指す:「Name of the model to use for generating text」→ model=model,/「Caps the length of the response to 1000 tokens」→ max_tokens=1000,/「Input to the model」→ messages=[...]

[2:28] ここでまず理解すべきことは、Anthropic SDK の中にある create 関数を使って Claude にアクセスする、ということです。

[2:33] この関数は、3つの異なるキーワード引数を必要とします。model、max tokens、そして messages です。

[2:39] model キーワード引数は、単に、実行したいモデルの名前です。その変数は前のセルで事前に定義済みです。

[2:44] 2つ目の必須キーワード引数は max_tokens です。これは、Claude が生成できるトークン数に上限の予算を設定します。

[2:55] たとえば、max tokens に 1000 を渡した場合、Claude がそれより長いものを生成しようとすると、生成は自動的に停止され、最初に生成された 1000 トークンが私たちに返されます。

[3:05] ここで注意すべき点は、Claude はあなたの max tokens の数を目標にしようとはしない、ということです。言い換えると、Claude は 1000 トークンの応答を書こうとするのではなく、自分が適切だと考える応答をただ書くだけです。

[3:22] そういうわけで、max tokens は、テキストを生成しすぎないことを保証するための安全機構のようなものだと捉えるべきです。

[3:26] 最後に messages です。ここが私が本当に注目してほしい部分です。というのも、messages はこの先の動画で私たちの大きな焦点になるからです。

🖥 [3:40] 画面: チャットUIのモックが再登場:「You — What is quantum computing?」の吹き出しと、「AI — Quantum computing is a form of computing that uses quantum mechanical phenomena to perform operations on data in ways that can solve some problems much faster than classical computers.」の吹き出し、下部に入力欄と Send ボタン。

[3:35] messages が何なのかを理解するために、少し前に議論したチャットアプリケーションを思い出してほしいのです。

[3:40] ユーザーが Claude に何か質問を入力し、そして答えが返ってくることを期待する、というものでした。

[3:45] この create 関数に渡すものとして話している messages は、このようなやり取りを表現するためのものです。

🖥 [4:00] 画面: 同じチャットUIに注釈が付く:右上「User Message — Content we want to feed into the model(モデルに与えたい内容)」が You の吹き出しを指し、左「Assistant Message — Content the model has produced(モデルが生成した内容)」が AI の吹き出しを指す。

[3:51] メッセージには2つのタイプがあります。user メッセージと assistant メッセージです。

[3:57] user メッセージは、私たちが Claude に与えたいテキストを含みます。

[4:03] user メッセージの中の content は、ユーザー、あるいは開発者であるあなたや私が書いたテキストです。つまり言い換えると、user メッセージには、誰かしらの人間によって書かれたテキストが入る、ということです。

[4:15] 2つ目のタイプのメッセージは、assistant メッセージです。

[4:18] これらのメッセージは、モデルによって生成され、私たちに送り返されてきたテキストを含みます。

[4:23] さて、この時点で、少なくとも最初のリクエストを送るには十分な知識が揃ったと思います。

[4:28] では、それをやってみて、その後で messages についてもう少し議論しましょう。

🖥 [4:31] 画面: ノートブックに戻る。最後のセル # Make a request に入力していく。途中経過: ```python

Make a request

message = client.messages.create( model=model, max_tokens= ) ```

[4:31] ノートブックの中に戻り、いちばん最後のセルで、message という変数を宣言します。

[4:36] それは client、messages、create から得られます。

[4:39] そして、いま議論したそれらの引数を渡していきます。

[4:42] model には model を入れ、max_tokens には——ここでは 1000 を使います。これは間違いなく安全な上限だと思います——それから、入力メッセージのリストです。

🖥 [5:00] 画面: リクエストのコードが完成形に近づく: ```python

Make a request

message = client.messages.create( model=model, max_tokens=1000, messages=[ { "role": "user", "content": "What is quantum computing? Ans" } ] ) ``` (content の文字列は「Answer in one sentence」までタイプ中)

[4:50] この中に、user メッセージを1つだけ入れます。その中に、Claude へ送りたい私の質問、つまりクエリが入ります。

[4:57] user メッセージを作るには、辞書を作ります。それは user という role を持ち、そして content には、Claude の中に送り込みたい実際の文字列が入ります。

[5:07] 今回のケースでは、Claude に量子コンピューティングの定義を尋ねます。「What is quantum computing? Answer in one sentence.(量子コンピューティングとは何ですか?1文で答えてください)」といった感じです。

[5:18] では、これを実行します。

[5:20] ここでは実際に Claude にアクセスしているので、実行には少し時間がかかります。

[5:23] それから、その1つ下のセルで、message 変数をプリントしてみます。

[5:27] 何が得られるか見てみましょう。

🖥 [5:30] 画面: 実行結果。リクエストセルは ✓ 2.4s で完了([3])。次のセルに message と入力して実行([4] 0.0s)。出力: Message(id='msg_01DMrPLrVkvabMuZNig3btyc', content=[TextBlock(citations=None, text='Quantum computing is a type of computation that leverages quantum mechanics principles like superposition and entanglement to process information using quantum bits (qubits), potentially solving certain complex problems exponentially faster than classical computers.', type='text')], model='claude-3-7-sonnet-20250219', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(cache_creation_input_tokens=0, cache_read_input_tokens=0, input_tokens=16, output_tokens=47)) 生成テキスト部分(text='Quantum computing is …')がハイライト選択されている。(※コード側の model 変数は "claude-sonnet-4-0" だが、この収録済み出力では model='claude-3-7-sonnet-20250219' と表示されている)

[5:29] はい。この中を見ると、たくさんのものが出てきているのが分かります。ただ、注目すべきは、ちょうどこのあたりに、量子コンピューティングが実際に何であるかの定義がある、ということです。

[5:38] 返ってきたこの message 変数の中では、私たちのテキストはかなり深くネストされています。多くの場合、私たちは Claude が生成したテキストだけを取り出したいのであって、この中に含まれている他のプロパティのことは、たいてい特に気にしません。

[5:52] そこで、生成されたテキストだけにアクセスするには、message.content[0].text と書きます。このように。そしてそのセルをもう一度実行すると、今度は生成されたテキストだけが表示され、それ以外は何も表示されません。

英語逐語(ASR全文)

We have done quite a bit of talking, so in this video we're going to change things and get our hands dirty by writing out a little bit of code. We're going to learn how to make a simple and basic request off to the Anthropic API. I'm going to walk you through four setup steps. So step one, we're going to open up a Jupyter notebook and install the Anthropic Python SDK along with a package called python.env. (※ASR注:「python.env」は python-dotenv の誤認識。以下同) To get started, I'm going to open up a notebook on my own. I've already added in some comments to my notebook just to guide myself through this process. In step one up here, I'm going to add in a magic install command. So percent pip install anthropic python.env. If you're writing your notebook inside a visual studio code, as I am, you might notice a red syntax error coming from the percent over here. If you see that syntax error, it is totally fine. You can ignore it. Once I've written out this command, I'm going to run it to install these packages. I'm then going to clear the output just so you can see what's going on my screen a little bit more easily. Next, we are going to use that python.env package to store and load our API key. As a reminder, I put in some directions on creating an API key in the previous lecture, so if you did not create an API key, I would encourage you to go back to the previous lecture and find those directions. To store this key, inside of my editor, I'm going to create a file in the same directory as my notebook with a very special name. I'm going to name this file .env, and then inside of here, I'm going to place the API key that I generated a moment ago. I will write out exactly anthropic underscore API underscore key, and then an equal sign. And then inside of double quotes, I will put in that key. As a quick side note, the whole reason that we are creating this file and putting our key inside of here is so that we can now ignore this file when we are making use of version control so that we do not accidentally commit this file, say to Git, and then accidentally push it up to some public repository where anyone can see this file. So again, if you're making use of Git or a similar version control system, I would encourage you to make sure that you ignore this file anytime you are committing your work. Now back inside my notebook, I can securely load up that environment variable. Then on to step number three, where we will create our API client using the anthropic package. Inside the cell, I'm also going to declare a variable name model. This will be a string. It's going to contain the name of the model that we want to run inside of the anthropic API. We are going to use Claude Sonnet 4. Now on to the last step, where we are going to actually make a request using that client that we just created. Before we write out any code, however, I want to show you a little bit of terminology, just some stuff that's going to make things a little bit easier down the line. So the first thing to understand here is that we are going to access Claude by using the create function inside the Anthropic SDK. This function requires three different keyword arguments, a model, max tokens, and messages. The model keyword argument is just going to be the name of the model we want to run. We already defined that variable ahead of time in our previous cell. The second required keyword argument is max underscore tokens. This sets a maximum budget on the number of tokens that Claude can generate. For example, if we pass in a max tokens of 1000, if Claude tries to generate anything longer than that, then the generation will be automatically stopped and we will receive back the first 1000 tokens that were generated. One thing to note here is that Claude doesn't try to target your number of max tokens. In other words, Claude won't try to write a response of 1000 tokens, it'll just write whatever response it thinks is appropriate. And as such, you should really view max tokens as being like a safety mechanism to ensure that you're not generating too much text. Finally, messages. And this is the part that I really want to focus on, because messages are going to be a huge focus for us in the coming videos. To understand what messages are all about, I would like you to think back to the chat application we discussed a moment ago. So a user might type in some question to Claude and then expect to get an answer back. The messages that we're talking about when we pass these things into this create function are meant to represent exchanges like this. There are two types of messages, a user message and assistant message. User messages contain text that we want to feed into clod. (※ASR注:「clod」は Claude の誤認識) The content inside of a user message is text that either a user or you and I as developers have authored. So in other words, a user message will contain text that has been written by some person. The second type of message is an assistant message. These messages contain text that have been produced by a model and sent back to us. Now, at this point, I think we have enough knowledge to at least make our first request. So let's do that and then discuss messages a little bit more. Back inside of my notebook, in the very last cell, I'm going to declare a variable of message. That will come from client, messages, create. And I'm going to pass in those different arguments that we just discussed. So I'll put in a model of model, a max tokens, and I'll use 1000 here, which I think is definitely a safe limit, and then a list of input messages. So inside of here, I'm going to put in one single user message, and it will contain my question or my query that I want to send off to Claude. To create a user message, we'll create a dictionary that will have a role of user, and then a content that will contain the actual string that we want to send into Claude. So in this case, I'm going to ask Claude to define quantum computing with something like, what is quantum computing? Answer in one sentence. Then I'm going to run this. We'll take a moment or two to run because we are actually accessing Claude here. And then in the next cell down, I'm going to try to print out the message variable. We'll see what we get. All right. So inside of here, we can see there's a lot of stuff coming out, but noticeably we have a definition right around here of what quantum computing actually is. So inside of this message variable that we got back, our text is kind of deeply nested. We very often want to get just the text that Claude has generated, and very often we don't really care about any of these other properties that are contained inside this thing. So to access just the generated text, we would write out message.content0.text, like so. And if I run that cell again. Now I'll see just the generated text and nothing else.

↑ 目次へ戻る

Building with the Claude API | Lesson 06「Multi-Turn conversations」(全文ナレッジ)

動画 8分55秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

1. Claude API はステートレス(このレッスンの最重要前提)

  • Anthropic API / Claude は送信したメッセージを一切保存しない。返ってきた応答も保存しない。
  • 毎回のリクエストは完全に独立しており、Claude 側に「前回のやり取りの記憶」は存在しない。
  • そのため複数メッセージにまたがる「会話」を成立させるには、開発者(呼び出し側)が明示的に次の2つを行う必要がある。 1. コード内でこれまでのメッセージ全部をリストとして自前で保持する 2. フォローアップのリクエストのたびに、そのリスト全体を丸ごと再送信する

2. 失敗パターンの実演:文脈を渡さずに単発リクエストを2回送るとどうなるか

  1. 1回目:messages=[{"role": "user", "content": "What is quantum computing? Answer in one sentence"}] を送信 → 量子コンピューティングの1文定義が返ってくる(期待通り)。
  2. 2回目:新しい独立したリクエストとして messages=[{"role": "user", "content": "Write another sentence"}] だけを送信 → Claude は1回目のやり取りを一切知らないため、量子コンピューティングとは無関係な文(実演では夕焼けの情景描写)が返ってくる。 - 原因:2回目のリクエストの messages に「量子コンピューティングについて聞いていた」という文脈が一切含まれていないため。Claude はゼロから「もう一文書いて」という依頼だけを受け取り、best effort で無関係な一文を生成する。

3. 正しいやり方:assistant の返答をリストに追記してから続ける

図解された正しいフロー: 1. user メッセージ「Define quantum computing in one sentence」だけを送る 2. assistant からの返答(「Quantum computing is a method...」)を受け取る 3. その assistant メッセージを messages リストに追記(append) する 4. 続けて user メッセージ「Write another sentence」を追記(append) する 5. この時点で messages[user, assistant, user] の3件構成になっている 6. この3件全部を次のリクエストの messages に乗せて送信する → Claude は文脈込みで一貫した応答を返せる

4. 3つのヘルパー関数(このレッスンの成果物・以降のコースで頻出)

def add_user_message(messages, text):
    user_message = {"role": "user", "content": text}
    messages.append(user_message)

def add_assistant_message(messages, text):
    assistant_message = {"role": "assistant", "content": text}
    messages.append(assistant_message)

def chat(messages):
    message = client.messages.create(
        model=model,
        max_tokens=1000,
        messages=messages,
    )
    return message.content[0].text
  • add_user_message / add_assistant_message:role を固定した上で、渡されたテキストを dict に包み messages リストへ append するだけの薄いラッパー関数。add_assistant_messageadd_user_message をコピーして userassistant に置換して作成されている。
  • chatclient.messages.create(...) の呼び出し自体を関数化したもの。引数は messages リスト1つのみ、戻り値は message.content[0].text(テキスト本体だけを直接返す)。これにより呼び出し側のコードが大幅に簡潔になる。

5. ヘルパー関数を使った実演(quantum computing の会話継続)

# Make a starting list of messages
messages = []

# Add in the initial user question of "Define quantum computing in one sentence"
add_user_message(messages, "Define quantum computing in one sentence")

# Pass the list of messages into 'chat' to get an answer
answer = chat(messages)
answer

# Take the answer and add it as an assistant message into our list
add_assistant_message(messages, answer)

# Add in the user's follow-up question
add_user_message(messages, "Write another sentence")

# Call chat again with the list of messages to get a final answer
answer = chat(messages)
answer
  • 各ステップの後に messages を print して構造を目視確認しながら進める。最終的な形は次のような配列になる。 python [ {'role': 'user', 'content': 'Define quantum computing in one sentence'}, {'role': 'assistant', 'content': 'Quantum computing is a type of computation that harnesses quantum mechanical phenomena such as superposition and entanglement to perform operations on data in ways that classical computers cannot efficiently achieve.'} ]
  • 最終的に2回目の chat(messages) は、量子コンピューティングという話題を正しく引き継いだ追加の1文(暗号・最適化・分子シミュレーションへの応用に触れる内容)を返す。1回目の単発失敗例(夕焼けの話)とは対照的に、今度は文脈が保持されていることが確認できる。

実務ポイント(このレッスンから持ち帰るもの)

  • 「会話(コンテキストの維持)」は API 側の機能ではなく、呼び出し側が毎回全履歴を再送することで実現される設計だと正しく理解する。ここを誤解すると、チャットボット実装で「文脈を覚えてくれない」バグを踏む。
  • messages は単なる Python の list(dict の配列)。DB・セッションストレージ・ファイル等、保持方法は完全に自由=自前で設計・管理する責任があるということでもある。
  • 会話が長くなるほど、毎回のリクエストで送信するトークン量(=コスト・レイテンシ)が増える。将来的な文脈圧縮・要約・トリミングの必要性を早い段階から意識しておく。
  • add_user_message / add_assistant_message / chat の3ヘルパーは、このコースの以降のレッスンでも繰り返し登場する基礎パーツ。早期にこの3点セットのパターンを理解しておくと後続レッスンの習得が速くなる。
  • chat()message.content[0].text を直接返す設計は、マルチターン処理や会話ループを書く際のボイラープレートを大幅に削減する典型パターンとして覚えておく価値がある。

本文(時系列・日本語逐語+画面差し込み)

[0:00] ここまで書いてきたコードは、モデルとの非常にシンプルなやり取りをシミュレートするものでした。この会話は、こんなふうにチャットボックスの中で視覚化できます。私たちは「量子コンピューティングとは何ですか?一文で答えてください」というようなリクエストを送りました。そして、非常にシンプルな一文の返答を得ました。当然、私たちはある時点でこの会話を続けたくなるかもしれません。ですから、「もう一文書いてください」というようなフォローアップを送りたくなるかもしれません。そしてその場合、量子コンピューティングについて何らかの形で拡張してくれる応答が返ってくることを期待するでしょう。

🖥 [0:00] 画面: チャットボックスのモックアップ図。 ``` You What's quantum computing? Answer in one sentence.

AI Quantum computing is a type... ``` 下部に空のテキスト入力欄と「Send」ボタン。左下に「ANTHROPIC」ロゴ。

[0:30] このような複数メッセージにまたがる会話を実現するには、Anthropic API と Claude 自体について理解しておくべき本当に重要なことがあります。それは、Anthropic API と Claude は、あなたが送信したメッセージを一切保存しない、ということです。あなたが送信したメッセージはどんな形であれ保存されませんし、返ってくる応答もどんな形であれ保存されません。

🖥 [0:30] 画面: オレンジ色の見出しスライド。 The Anthropic API and Claude do not store any messages その下に「To have a 'conversation', you need to:」という帯があり、2つの箱に分岐している。 Manually maintain a list of messages in your code Provide that list of messages with each follow up request

[0:50] ですから、もし何らかのコンテキストや流れを維持する複数メッセージの会話を成立させたいのであれば、行うべきことが2つあります。まず、コードの中で、やり取りしているすべてのメッセージのリストを手動で維持する必要があります。そして2つ目に、フォローアップのリクエストを送るたびに、そのメッセージリスト全体を必ず提供する必要があります。

[1:11] それでは、この考え方全体について少し詳しく見ていき、何が起きているのかを本当にはっきり理解できるようにしましょう。最初にやりたいのは、Claude がメッセージなどを一切保存していないことをあなたに証明するために、少しサンプルコードを書き出すことです。そして、量子コンピューティングとは何かをまず尋ね、その後「もう一文書いて」と尋ねる、というこの種の会話をシミュレートすることでこれを証明します。

[1:33] ノートブックに戻り、最初に「量子コンピューティングとは何か」を尋ねたセルに移動します。そして、あらかじめ書いておいた2つ目のリクエストを Claude に貼り付けます。この2つ目のリクエストでは、Claude に「もう一文書いて」と依頼しており、そのフォローアップリクエストの中のテキストを出力させます。

🖥 [1:33] 画面: 001_requests.ipynb のセル。既存の message = client.messages.create(...) 呼び出しの messages=[{"role": "user", "content": "What is quantum computing? Answer in one sentence"}] を、新しい単発リクエストに書き換えている途中。ハイライトされているのが新しく貼り付けられた部分。 python message = client.messages.create( model=model, max_tokens=1000, messages=[ { "role": "user", "content": "Write another sentence", } ], ) message.content[0].text 下のセルには前段の出力 message.content[0].text がそのまま残っている。

[1:48] さて、ここでちょっとしたシミュレーションができました。最初の質問をしてからフォローアップを送る、という流れです。そして、ここでは読める、あるいは使えるようなテキストが返ってこないことがわかるでしょう。実行して、何が起こるか見てみましょう。

[2:02] では、こちらでそのセルを今すぐ実行してみると、量子コンピューティングとはまったく関係のないものが返ってくることがわかります。この結果がなぜ出てきて、量子コンピューティングについての何かが出てこないのか、それを本当にはっきりさせるために、いくつか図を見ていきましょう。

🖥 [2:02] 画面: 実行結果が表示された状態。 python message.content[0].text 出力: 'The vibrant sunset painted the sky with hues of orange and purple, casting a warm glow over the peaceful countryside.' (量子コンピューティングとは無関係な文が返ってきている) 下のセルには最初の実行結果が残っている: 'Quantum computing is a type of computation that leverages quantum mechanics principles like superposition and entanglement to process information using quantum bits (qubits), potentially solving certain complex problems exponentially faster than classical computers.'

[2:20] さて、この図では、まず現在書いたコードで実際に何が起きているのか、つまりなぜ量子コンピューティングと無関係な応答が返ってくるのかをお見せします。最初、私は Claude に1つのユーザーメッセージだけを含む単発のリクエストを送り、量子コンピューティングを一文で定義するよう依頼しています。そして、まさに私が期待した通りの応答が返ってきます。Claude は量子コンピューティングの一文の定義を返してくれます。

🖥 [2:20] 画面: 図解「Messages In Request → Claude」。 Messages In Request [user] Define quantum computing in one sentence ──────────────────────────────▶ Claude [assistant] Quantum computing is a method... ◀──────────────────────────────

[2:44] 次に、私は本文の中に1つだけメッセージを含む2つ目のリクエストを送ります。その唯一のメッセージは、Claude に「もう一文書いて」と依頼するものです。これを送信すると、Claude は過去のやり取りや、私と交わした以前のメッセージについての記憶を一切持っていません。ですから Claude は、私のリクエストを満たすために最善を尽くすだけです。何らかの文章を書き出しますが、それは間違いなく、おそらく量子コンピューティングについてのものにはなりません。

🖥 [2:44] 画面: 図解「Messages In Request → Claude」。今度は左側の Messages In Request に user メッセージ「Write another sentence」だけが入っており、右側から返ってくる assistant の吹き出しは「???」(内容不明・無関係)と表示されている。

[3:04] では、この問題を解決するために何をする必要があるかをお見せしましょう。ここからが解決方法です。まず、もう一度、たった1つのユーザーメッセージで最初のリクエストを行います。次に、返ってきた assistant メッセージを受け取り、それをメッセージのリストに追加します。ですから、この assistant のメッセージを、左側にあるリストの中に追加していくイメージです。

🖥 [3:04] 画面: 図解が更新され、Messages In Request の箱の中に user「Define quantum computing in one sentence」と assistant「Quantum computing is a method...」の2件が積み上がった状態で表示される。

[3:28] それから、この会話をフォローアップしたい、あるいは何らかの形で続けたいときには、一番下に user メッセージを追加していきます。そうすると、これを本物の会話のように読むことができます。私は量子コンピューティングの定義を尋ねました。応答を得ました。そして今、私は Claude に尋ねたいもう1つの質問、もう1つの問い合わせを追加しています。今回の場合は「もう一文書いて」です。

🖥 [3:28] 画面: Messages In Request の箱に3件が積み上がった状態。 [user] Define quantum computing in one sentence [assistant] Quantum computing is a method... [user] Write another sentence 右側の Claude への矢印はまだ応答待ちの向き(右向き矢印のみ、左向きの返答矢印はまだ描かれていない)。

[3:46] さて、このメッセージのリストを Claude に送信すると、Claude は会話全体のコンテキストと履歴をすべて持つことになります。この一連の質問のやり取りの中で交わされたこれまでのメッセージをすべて見ているのです。そして願わくば、Claude はより理にかなった答えを返してくれるでしょう。願わくば、前の答えをもう少し拡張してくれる一文のフォローアップです。

🖥 [3:46] 画面: 図解に4件目(assistant からの新しい返答)が加わる。 [user] Define quantum computing in one sentence [assistant] Quantum computing is a method... [user] Write another sentence ◀── [assistant] The applications of quantum computing... 3件のメッセージが Claude へ送られ、Claude から新しい assistant 応答が返ってくる、という一連の矢印が完成した状態。

[4:06] この一連の流れを実際に動かして見るために、ノートブックに戻り、会話の完全なコンテキストを維持できるようなコードを書き出してみましょう。

[4:15] ノートブックの中に戻り、会話の履歴やコンテキストを維持するのに役立つ、3つの異なるヘルパー関数を作ることから始めます。これらのヘルパー関数は、このコースの残りの部分を通じてかなり頻繁に使うことになります。

[4:28] ではこのセルで、上に少し余白を作ってから、この履歴を維持するのに役立つ最初のヘルパー関数を定義します。この関数を add_user_message と名付けます。これはメッセージのリストと何らかのテキストを受け取ります。

🖥 [4:28] 画面: 新しいセルに関数定義を書き始めている。 python def add_user_message(messages, text): useruser まで入力した途中の状態。下には既存の # Make a request セルが残っている)

[4:44] それから user_message という変数を作ります。これは user という role と、渡されたテキストがそのまま content になる値を持ちます。そして、この新しい user_message をメッセージのリストに追加(append)します。

[4:57] 次に、履歴に assistant メッセージを追加することに特化した2つ目のヘルパー関数を追加します。少し時間を節約するために、この関数をここでコピーします。名前を add_assistant_message に変更し、user という単語が出てくる箇所をすべて assistant に置き換えていきます。ここと、ここと、ここですね。はい。

🖥 [4:57] 画面: add_user_message の完成形と、それをコピーして作り始めた add_assistant_message が並んでいる(コピー直後、まだ userassistant へのリネーム前)。 ```python def add_user_message(messages, text): user_message = {"role": "user", "content": text} messages.append(user_message)

def add_assistant_message(messages, text): user_message = {"role": "user", "content": text} messages.append(user_message) (下部にリネーム後の完成形も確認できる)python def add_assistant_message(messages, text): assistant_message = {"role": "assistant", "content": text} messages.append(assistant_message) ```

[5:26] さて、3つ目のヘルパー関数に移ります。ここにある messages.create の関数呼び出しを取り出し、chat という名前に変更します。chat を呼び出すときはいつも、メッセージのリストを渡します。これは私のメッセージ履歴のようなものになります。

[5:38] それから、この呼び出しをインデントします。messagesmessages という引数に置き換えます。そして、この関数からの戻り値は message.content[0].text になります。

🖥 [5:38] 画面: 3つのヘルパー関数がすべて完成した状態。 ```python def add_assistant_message(messages, text): assistant_message = {"role": "assistant", "content": text} messages.append(assistant_message)

def chat(messages): message = client.messages.create( model=model, max_tokens=1000, messages=messages, ) `` (この時点ではまだreturn message.content[0].text` の行を追加する前)

[5:52] はい、というわけで、これが3つのヘルパー関数です。繰り返しになりますが、これらはこのコース全体の残りの部分でかなり頻繁に使っていきます。これらのヘルパー関数のおかげで、時間をまたいで何らかの履歴やコンテキストを維持する会話を行うことが、著しく簡単になります。

🖥 [5:52] 画面: 完成済みの def chat(messages):return message.content[0].text まで含まれた最終形が表示されている。 python def chat(messages): message = client.messages.create( model=model, max_tokens=1000, messages=messages, ) return message.content[0].text

[6:08] では、これらをどう活用していくかのデモンストレーションをお見せしましょう。よし。この下の次のセルに、会話の履歴を維持していくプロセスを導くためのコメントをいくつか書いていきます。まずは空のメッセージリストを作ることから始めます。

🖥 [6:08] 画面: コメントだけが書かれた新しいセル。 ```python

Make a starting list of messages

Add in the initial user question of "Define quantum computing in one sentence"

Pass the list of messages into 'chat' to get an answer

Take the answer and add it as an assistant message into our list

Add in the user's follow-up question

Call chat again with the list of messages to get a final answer

```

[6:22] ですから、この messages 変数は、私たちの会話履歴全体を保存していると考えることができます。時間の経過とともに、さまざまな user と assistant のメッセージの集まりをこれに追加していきます。

[6:32] 次に、最初の user メッセージを追加します。add_user_message 関数を呼び出します。メッセージを追加していく対象のメッセージリストを渡します。そして、user のテキストは「Define quantum computing in one sentence」にします。

🖥 [6:32] 画面: コードが順に埋まっていく。 ```python

Make a starting list of messages

messages = []

Add in the initial user question of "Define quantum computing in one sentence"

add_user_message(messages, "Define quantum computing in one sentence") ```

[6:46] さて、ここで正しい方向に進んでいることを確認するために、メッセージのリストを出力してセルを実行してみます。すると、正しいメッセージの構造になっていることがすぐにわかります。リストがあり、その中に role が user で、Claude に食べさせたい何らかの内容を持った content を含む辞書が入っています。

🖥 [6:46] 画面: messages を出力した結果。 [{'role': 'user', 'content': 'Define quantum computing in one sentence'}]

[7:03] では、先ほど組み立てた chat 関数を使うことで、簡単に Claude を呼び出すことができます。chat を呼び出し、私のメッセージリストを渡します。そして、そこから何らかの答えが返ってきます。その答えを出力して、セルをもう一度実行します。

🖥 [7:03] 画面: answer = chat(messages) を書き加え、answer を出力させるところ。 ```python

Pass the list of messages into 'chat' to get an answer

answer = chat(messages) answer ```

[7:17] さて、応答が返ってきました。ここには量子コンピューティングについての一文が見えるはずです。さて、今こういう状況になっています。私たちは最初のメッセージを Claude に送信し、応答として assistant メッセージを受け取りました。今度は、この答えを受け取り、会話履歴に追加する必要があります。

🖥 [7:17] 画面: 実行結果。 'Quantum computing is a form of computing that leverages quantum mechanical phenomena such as superposition and entanglement to perform calculations using quantum bits (qubits) that can exist in multiple states simultaneously, potentially solving certain problems exponentially faster than classical computers.'

[7:33] ですから、先ほど定義した add_assistant_message 関数を使って、この答えを履歴に追加する必要があります。こちらに戻って、add_assistant_message を呼び出します。メッセージのリストに追加したいのと、そして具体的には、先ほど得た答えの中身(content)を追加したいのです。

🖥 [7:33] 画面: add_assistant_message を入力している途中(messa まで打った状態)。 ```python

Take the answer and add it as an assistant message into our list

add_assistant_message(messages, messa... 完成形:python add_assistant_message(messages, answer) ```

[7:50] さて、もう一度チェックをして、メッセージのリストが正しく見えることを確認しましょう。messages を出力すれば、user メッセージ、それに続く assistant のフォローアップ(Claude から返ってきた content を含む)が見えるはずです。はい。良さそうです。

🖥 [7:50] 画面: messages を出力した結果。 [{'role': 'user', 'content': 'Define quantum computing in one sentence'}, {'role': 'assistant', 'content': 'Quantum computing is a type of computation that harnesses quantum mechanical phenomena such as superposition and entanglement to perform operations on data in ways that classical computers cannot efficiently achieve.'}]

[8:03] さて、最後のステップに移ります。最後にもう1つ user メッセージを追加し、会話履歴全体をもう一度 Claude に送信します。そのために、メッセージのリストに対してもう一度 add_user_message を行います。そして、今回のフォローアップの質問、つまりフォローアップのリクエストは「もう一文書いて」にします。

🖥 [8:03] 画面: add_user_message を入力中(add_user_mes まで)。 ```python

Add in the user's follow-up question

add_user_mes... 完成形:python

Add in the user's follow-up question

add_user_message(messages, "Write another sentence") ```

[8:23] それから、更新されたメッセージのリストを使って、もう一度 chat を呼び出します。それを answer に代入し、answer を出力します。では、これを実行して、うまくいっているか見てみましょう。

🖥 [8:23] 画面: 最終セクションのコード全体。 ```python

Call chat again with the list of messages to get a final answer

answer = chat(messages) answer ```

[8:35] さて。少し間を置いた後、間違いなく量子コンピューティングについての内容が続く、フォローアップのメッセージが返ってきました。というわけで、会話履歴全体を正しく維持できたようです。

🖥 [8:35] 画面: 最終実行結果。 'Leveraging the bizarre properties of quantum physics, these advanced machines manipulate qubits that can exist in multiple states simultaneously, potentially solving complex problems like cryptography, optimization, and molecular simulation that would remain practically impossible for traditional computers.'

[8:47] よし、というわけで、なかなか良い感じです。これで私たちは、このコースの残りの部分を通じて引き続き活用していく、3つの再利用可能なヘルパー関数を手に入れました。

英語逐語(ASR全文)

The code that we've written out so far simulates a very simple exchange with our model, and we can kind of visualize this conversation inside of a chat box like this. We sent in a request asking something like, what's quantum computing? Answer in one sentence. And we got a very simple single sentence reply. Naturally, we might want to continue this conversation at some point in time. So we might want to be able to send in a follow-up asking something like, write another sentence. And we would then expect to get back a response that expands on quantum computing in some way.

To have a multi-message conversation like this, there's something really critical that you need to understand around the Anthropic API and Claude itself. And that is that the Anthropic API and Claude do not store any messages that you send to it. None of the messages you send get stored in any way, and none of the responses that you get back are stored in any way.

So if you ever want to have some kind of conversation going on where you have multiple messages that kind of maintain a context or a flow, then there are two things that you need to do. You need to manually inside of your code, maintain a list of all the messages that you are exchanging. And second, you need to make sure that you provide that entire list of messages with every follow-up request that you make.

So let's go into some detail on this entire idea, just to make sure it's really clear and you understand what's going on here. The first thing I want to do is write out a little bit of sample code, just to prove to you that Claude doesn't store any messages or anything like that. And I'm going to prove this by trying to simulate this kind of conversation, where I first ask what quantum computing is, and then ask write another sentence. So back inside of my notebook, I'm going to go to the cell where I initially asked what is quantum computing, and I'm going to paste in a second request to Claude that I wrote out ahead of time. So in this second request, I'm asking Claude to write another sentence, and I'm going to print out the text inside of that follow-up request.

So now we've kind of got a simulation of what's going on right here, where we ask that first question and then send in a follow-up. And we're going to see that we don't get back any kind of legible or any usable text here. So I'm going to run this and we'll see what happens.

So back over here, I'm going to run that cell now, and we're going to see that we get back something that has absolutely nothing to do with quantum computing at all. So again, to make sure that it's really clear why we are seeing this result and not seeing something about quantum computing, let's go over a couple of diagrams. All right, so in this diagram, I'm going to first show you exactly what's going on with the current code that I've written out, where we are getting back a response that has nothing to do with quantum computing. So initially I make a single request to Claude where I have one user message and I'm asking Claude to define quantum computing in one sentence. I then get back a response that is exactly what I would expect. Claude gives me back a one sentence definition of quantum computing. Then I make a second request where I have only one message inside the body and the only message is asking Claude to write another sentence. When I send this in, Claude has no memory of any past conversation or any previous messages that I exchanged with it. So Claude is just going to do the best it can to fulfill my request. It's going to write out some sentence, but it definitely is probably not going to be about quantum computing.

So now let me show you what we need to do to fix this problem. Here's how we're going to solve the issue. First, we're going to once again, make that initial request with just one user message. Then we're going to take that assistant message that we get back and append it into a list of messages. So we're going to take the assistant right there and we can imagine we're going to add it into our list on the left-hand side. Then when we want to follow up on this conversation or continue it in some way, we're going to append on a user message at the bottom. So now we can read this as like a real conversation. I asked to find quantum computing. I got back a response. And now I'm adding in another question or another query that I want to ask Claude. In this case, write another sentence.

Now when I send in this list of messages to Claude, it will have the entire context and history of the whole conversation. It's seen all the previous messages that we've exchanged around this line of questioning. And hopefully, Claude will be able to give us back a more reasonable answer. Hopefully, a one-sentence follow-up that's going to extend its previous answer a little bit more.

To see this entire flow in action, I'm going to go back over to my notebook and we're going to try to write out some code that will allow us to maintain the full context for a conversation.

Back inside of my notebook, I'm going to get started by making three different helper functions that are going to aid us in maintaining the history or context of a conversation. We're going to end up using these helper functions quite a bit throughout the remainder of this course.

So in this cell right here, I'm going to give myself a little bit of space at the top and then to find our first helper function that will aid us in maintaining this history. I'm going to name this function, add user message. It's going to take in a list of messages and some text. I'm then going to make a variable of user message. That's going to have a role of user and some content of whatever text we pass in. And then I'm going to append this new user message into the list of messages.

Next, I'm going to add in a second helper function that's going to specialize in adding in assistant messages to a history. So I'm going to copy this function right here just to save a little bit of time. I'll rename it to add assistant message and I'll go through and wherever I see the word user, I'm going to change it out to assistant. So right there, right there, and right there. Okay.

Now onto our third helper function. I'm going to take our messages.create function call down here, and I'm going to rename it to chat. Whenever I call chat, I'm going to pass in a list of messages. So this is going to be like my message history. Then I'm going to indent our call right here. I'm going to replace messages with the messages argument. And then return from this function is going to be message content at zero dot text.

All right, so here are the three helper functions. And again, we're going to use these quite a bit throughout the remainder of this entire course. These helper functions are going to make it significantly easier for us to have a conversation that maintains some history or context over time. So now let me give you a demonstration of how we're going to put them to use. All right. So down here in the next cell down, I'm going to write in a couple of comments that's just going to guide us through the process of maintaining a conversation that has some history tied to it. I'll first begin by making an empty list of messages. So this messages variable right here, we can imagine is storing our entire conversation history. Over time, we're going to add in a collection of different user and assistant messages to it.

Next, I'm going to add in my initial user message. So I will call the add user message function. I'll pass in the list of messages that I'm appending messages to. And then my user text is going to be define quantum computing in one sentence. Now, just to make sure we're going down the right path here, I'm going to print out the list of messages and run the cell. And we can see right away that we have a correct structure of messages. So I have a list. It has a dictionary inside of it with a role of user and a content that contains something that I want to feed into Claude.

So now we can easily call Claude by making use of that chat function that we just put together. I'm going to call chat and pass in my list of messages. And then out of that, we'll get back some kind of answer. I'm going to print out the answer and run the cell again. So we get the response. We should see a sentence here about quantum computing. So now we are in this situation. We have sent an initial message into Claude and gotten an assistant message back in response. Now we need to take this answer and append it into our conversation history. So we need to add in or append it in by making use of the add assistant message function that we just defined. So back over here, I'm going to call add assistant message. I want to add into our list of messages and I want to add in specifically the content out of the answer that we just got back.

So now let's do another check and make sure that our list of messages is looking correct. If I print out messages, I should see my user message, then the follow-up assistant with the content that we got back from Claude. Okay. So that looks good.

So now onto the last step, we're going to append in one last user message and send the entire conversation history into Claude once again. So for that, I'll do another add user message with my list of messages. And then my follow-up question here, or my follow-up request is going to be write another sentence. I'm then going to call chat again with the updated list of messages. I'll assign that to answer, and then I will print answer out. So let's now run this and see how we are doing.

All right. After a brief pause, we get what is definitely a follow-up message that is definitely still about quantum computing. And so it appears that we have correctly maintained our entire conversation history.

Alright, so this is looking pretty good. We now have three reusable helper functions that we're going to continue to make use of throughout the remainder of the course.

↑ 目次へ戻る

Building with the Claude API | Lesson 07「Chat exercise」(全文ナレッジ)

動画 3分54秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンのゴール

前レッスン(06 Multi-Turn conversations)で作成した3つのヘルパー関数(add_user_message / add_assistant_message / chat)を使って、Jupyterノートブック上で動くごく小さなチャットボットを自作する演習。

仕様(スライド「Chat Bot Exercise」より)

  1. 組み込みの input 関数でユーザーにテキスト入力を促す
  2. 入力内容をメッセージのリストに追加する
  3. API を呼び出す
  4. 生成されたテキストをメッセージのリストに追加する
  5. 生成されたテキストを表示する
  6. 1に戻って繰り返す

全体の流れ(講師の説明)

  • セルを実行するたびに、組み込みの input 関数でユーザーにテキスト入力を促す
  • ユーザーが入力した内容をメッセージのリストに追加する
  • メッセージのリストを、直前に作った chat 関数を使って API に渡す
  • Claude からの応答(生成テキスト)を受け取る
  • その生成テキストをメッセージのリストに追加する
  • 生成テキストを表示し、ステップ1に戻ってループする → これを永遠に繰り返す

デモ(隠しセルの動作確認)

講師はあらかじめ解答を書いたセルを用意しており、中身を隠した状態(# Solution hidden!)でまず動作だけを見せる。 - セル実行 → input 関数がすぐにユーザー入力を促す - What's 1+1? と入力 → 自分の入力がすぐに表示される → chat 関数で Claude からの応答が返り表示される(1+1 = 2) - 再度入力を促される → add two to that answer(その答えに2を足して)と入力 - 会話の履歴・文脈が維持されていることを確認 → Claude が「前の答え2に2を足すと4になる」という趣旨の返答をする - このループは無限に続く。停止するには、ノートブックの Interrupt(中断)ボタン をクリックするか、Escape キーを押す - 実際に中断すると KeyboardInterrupt: Interrupted by user という例外がトレースバック付きで表示される(ipykernel/kernelbase.pyKernel._input_request 内で送出される、Jupyter組み込みの中断処理)

input 関数に不慣れな場合のテンプレート

input 関数に馴染みがない受講者向けに、講師は以下の骨組み(テンプレート)コードを提示する。

# Make an initial list of messages
messages = []

# Use a 'while True' loop to run the chatbot forever
while True:
    # Get user input
    user_input = input("> ")
    print(">", user_input)

    # Add user input to the list of messages
    # Call Claude with the 'chat' function
    # Add generated text to the list of messages
    # Print the generated text
  • messages を空リストで初期化
  • while True: ループでチャットボットを永遠に走らせる
  • ループ内で input() を使ってユーザーにテキスト入力を促し、結果を user_input 変数に代入
  • 残りのステップはコメントとしてガイドのみ用意されており、受講者が自分で実装する

解答の実装(講師によるライブコーディング)

コメントに沿って1行ずつ実装していく。

# Make an initial list of messages
messages = []

# Use a 'while True' loop to run the chatbot forever
while True:
    # Get user input
    user_input = input("> ")
    print(">", user_input)

    # Add user input to the list of messages
    add_user_message(messages, user_input)
    # Call Claude with the 'chat' function
    answer = chat(messages)
    # Add generated text to the list of messages
    add_assistant_message(messages, answer)
    # Print the generated text
    print("---")
    print(answer)
    print("---")

実装のポイント(講師の言葉ベース): 1. ユーザー入力を、前レッスンで作った add_user_message 関数でメッセージのリストに追加する(add_user_message(messages, user_input)) 2. 組み込みの chat 関数で Claude を呼び出し、メッセージのリストを渡す(answer = chat(messages)) 3. 返ってきた answer を add_assistant_message で assistant メッセージとしてメッセージのリストに追加する(add_assistant_message(messages, answer)) 4. 生成テキストを表示する。任意で、AIが生成した部分だと利用者にわかりやすいように、ダッシュ(---)で前後を区切って表示する

動作確認

セルを実行し、What's 1+1? と入力 → 応答が返る → 続けて And 2 more? と入力 → 「1+1に2を足すと4になる」という趣旨の応答が返り、履歴が維持されたループ型チャットボットが完成したことを確認。

実務ポイント(このレッスンから持ち帰るもの)

  • ループ型チャットボットの最小構成は「①ユーザー入力 → ②add_user_message でリストに追加 → ③chat でAPI呼び出し → ④add_assistant_message でリストに追加 → ⑤表示 → ①に戻る」の5ステップに集約できる。これは本番のチャットUIでも同じ骨格になる。
  • Jupyterノートブックでの input() の無限ループは、Interrupt ボタン/Escapeキーで明示的に止める必要がある(自然終了しない設計)。KeyboardInterrupt はエラーではなく正常な停止手段として扱われている。
  • 会話の「文脈維持」は特別な仕組みではなく、毎ターン messages リストに user/assistant のメッセージを追記し続け、その全体を毎回 chat 関数(=API呼び出し)に渡しているだけ、という点が本質。API側は状態を持たず、呼び出し側が全履歴を渡す設計であることが体感できる。
  • 出力に --- のような区切り線を入れるのは必須ではないが、「これはAIが生成した部分だ」とユーザーにわかりやすくする実務上のちょっとした工夫として紹介されている。
  • 前レッスンで作った3つのヘルパー関数(add_user_message / add_assistant_message / chat)を土台にすれば、チャットボットの実装自体は十数行で完結する、という規模感が確認できる。

本文(時系列・日本語逐語+画面差し込み)

[0:00] 前回までの内容がきちんと理解できているか確認するために、簡単な演習を一つやってみましょう。

[0:04] この演習では、Jupyterノートブック上で動く、とても小さなチャットボットを作っていきます。

[0:10] 仕組みはこうです。

[0:11] セルを実行するたびに、組み込みの input 関数を使ってユーザーにテキストを入力してもらいます。

[0:17] それから、ユーザーが入力した内容を受け取り、それをメッセージのリストに追加します。

[0:21] 次に、そのメッセージのリストを、先ほど作った chat 関数を使って API に渡します。

[0:26] そうすると、Claude からの応答が返ってきます。

[0:29] つまり生成されたテキストです。その生成テキストを、こんどはメッセージのリストに追加します。

[0:34] 生成テキストを表示し、そしてまたループしてステップ1に戻ります。

[0:38] こうして、このプロセス全体をもう一度繰り返すわけです。

[0:41] これがどう動くのか、手早くお見せしましょう。

[0:44] 実はすでに解答を用意してあります。

[0:46] セルの中に隠してあるので、もう一度そのセルを実行して、これがどう動くことを意図しているのか見てもらいます。

🖥 [0:45] 画面: タイトルスライド「Chat Bot Exercise」。 ``` Make a chat bot using the three helper functions we just put together.

  1. Prompt the user to enter some input using the built-in "input" function
  2. Add it to a list of messages
  3. Call the API
  4. Add generated text to the list of messages
  5. Print the generated text
  6. Repeat from #1 ``` このスライドから、VS Code(Jupyter拡張)のノートブック画面へ画面が切り替わる。

[0:52] セルを実行すると、すぐに組み込みの input 関数を使ってユーザーに入力を促します。

[0:57] これについては、もし input 関数に馴染みがなくても大丈夫、この後すぐに使い方を説明します。

[1:00] それでは、たとえば「what's one plus one(1足す1は?)」のように入力してみます。

[1:04] 自分が入力した内容がすぐに表示されるはずです。

[1:07] それから、chat 関数を使って Claude から応答を取得し、それも表示されるはずです。

[1:11] そして再び、もう少しテキストを追加するよう促されるはずです。

[1:14] そこで今度は「add two to that answer(その答えに2を足して)」のように言ってみます。

🖥 [1:00] 画面: ノートブックのセル # Solution hidden! を実行した状態。次の入力を待つポップアップ("Press 'Enter' to confirm or 'Escape' to cancel")が画面上部に表示されている。 ```

Solution hidden!

> What's 1+1?

1+1 = 2

```

[1:19] そうすると、自分の会話の履歴、つまり文脈が維持されていることが確認できるはずです。

[1:22] ですから今度は、「前の答え(2)に2を足すと4になる」といった趣旨の応答が返ってくるはずです。

[1:28] そしてこれはまた同じように、こちらから明示的にプロセスを中断するまで、永遠に続きます。中断は、ここにあるインタラプトボタンをクリックするか、Escapeキーを押すことで行います。

🖥 [1:36] 画面: Interruptボタン/Escapeキーでセルの実行を中断した際に表示されるトレースバック。 ``` 1281 raise StdinNotImplementedError(msg) --> 1282 return self._input_request( 1283 str(prompt), 1284 self._parent_ident["shell"], 1285 self.get_parent("shell"), 1286 password=False, 1287 )

File ~/code/enablements/1p/prod/03/.venv/lib/python3.9/site-packages/ipykernel/kernelbase.py:1325, in Kernel._input_request(self, prompt, ident, parent, password) 1322 except KeyboardInterrupt: 1323 # re-raise KeyboardInterrupt, to truncate traceback 1324 msg = "Interrupted by user" -> 1325 raise KeyboardInterrupt(msg) from None 1326 except Exception: 1327 self.log.warning("Invalid Message:", exc_info=True)

KeyboardInterrupt: Interrupted by user ```

[1:37] さて、先ほども触れましたが、もし組み込みの input 関数に馴染みがなくても問題ありません。

[1:41] ここで少しヒントを出して、私たちが何をやりたいのかをはっきりさせておきましょう。

[1:45] テンプレートとして使えるコードを少し貼り付けます。

[1:49] はい。

[1:50] 使いたい全体構造はこんな感じです。

[1:52] まず、空の状態から始まる、メッセージの初期リストを用意します。

[1:57] それから、永遠に実行され続ける while True ループを用意します。

[2:01] そして、その while ループの中で、ユーザーにテキストを入力してもらいます。ここでもやはり、組み込みの input 関数を使います。

[2:08] ユーザーが表示された入力欄に入力した内容は、この user_input という変数に代入されます。

[2:13] そして、私たちがこれから何をする必要があるのか、その手順をガイドするためのコメントをいくつか書いておきました。

🖥 [1:50] 画面: 講師が提示するテンプレートコード。「input」という単語がハイライトされている(# Get user input の "input" と input("> ") の "input")。 ```

Make an initial list of messages

messages = []

Use a 'while True' loop to run the chatbot forever

while True: # Get user input user_input = input("> ") print(">", user_input)

# Add user input to the list of messages
# Call Claude with the 'chat' function
# Add generated text to the list of messages
# Print the generated text

```

[2:17] はい。

[2:18] というわけで、ここで一度動画を止めてください。

[2:19] ぜひこの演習に挑戦してみることをおすすめします。

[2:22] それ以外の場合は、このまま見続けてください。今から一緒に解答を見ていきます。

[2:26] それでは、取り掛かりましょう。

[2:27] これをどう組み立てるか、お見せします。

[2:29] この続きを実装するには、ここに書いておいたそれぞれのコメントに沿って進めるだけです。

[2:33] まず最初にやることは、そのユーザー入力を受け取り、先ほど作った add_user_message 関数を使ってメッセージのリストに追加することです。

[2:41] add_user_message を呼び出します。

[2:44] メッセージのリストと、自分のユーザー入力を渡します。

🖥 [2:41] 画面: add_user_message を入力途中の状態。 # Add user input to the list of messages add_user_message(messages, ) (第2引数はまだ空、カーソルが括弧内にある状態から user_input を追加していく)

[2:49] 次に、組み込みの chat 関数を使って Claude を呼び出します。メッセージのリストを渡します。

[2:54] そうすると、何かしらの answer(回答)が返ってきます。

[2:57] その answer を受け取り、assistant メッセージとしてメッセージのリストに追加します。

[3:02] つまり add_assistant_message を、このように呼び出します。

🖥 [3:02] 画面: while True ループの実装がここまで進んだ状態(print部分はまだ未実装、カーソルは空行で待機)。 ``` while True: # Get user input user_input = input("> ") print(">", user_input)

# Add user input to the list of messages
add_user_message(messages, user_input)
# Call Claude with the 'chat' function
answer = chat(messages)
# Add generated text to the list of messages
add_assistant_message(messages, answer)
# Print the generated text
|

```

[3:07] そして最後に、生成されたテキストを表示するだけです。

[3:10] これは任意ですが、小さなダッシュの形の区切り記号を入れることもできます。これは、このアプリケーションを使う人にとって、それがAIによって生成されたものだとはっきりわかるようにするためです。

[3:20] ですので、ダッシュ・ダッシュ・ダッシュを表示します。

[3:23] そして、その2つの間に、返ってきた自分の answer を表示します。

[3:29] これで完成です。

[3:30] では、これをテストするために、セルを実行してみましょう。

[3:33] Claude とのチャットを始めた後、このセルの下に何らかの出力が表示されるはずです。

[3:37] Claude に「1足す1は?」と聞いてみます。

[3:41] 応答が返ってきます。

[3:43] それから「あと2足して」と聞きます。

[3:45] 「1足す1にさらに2を足すと4になる」という趣旨の応答が表示されるはずです。

[3:48] はい、とても良い感じです。これで、ループするチャットボットの、とてもシンプルな実装ができあがりました。

🖥 [3:20] 画面: 最終コードの実行結果。 ``` # Print the generated text print("---") print(answer) print("---")

[13] 12.2s

> What's 1+1?

1+1 = 2

And 2 more? ```

英語逐語(ASR全文)

Let's go through a quick exercise just to make sure everything's making sense so far. In this exercise, we're going to be building a very small chat bot that is going to run out of our Jupyter notebook. Here's how it's going to work. Whenever we run a cell, we're going to prompt the user to enter in some text using the built-in input function. We're then going to take whatever the user enters and add it into a list of messages. We'll then take the list of messages and pass it off to the API through the use of our chat function that we just put together. That's going to give us some feedback from Claude. So some generated text, we're going to take that generated text and add it into our list of messages. We'll print that generated text and we will loop and go back to step one. So that we repeat the entire process all over again. I'm going to show you how this thing is intended to work very quickly. So I've already put a solution together. I've hidden it inside the cell and I'm going to run the cell again, just so you can see how I intend for this thing to work. So whenever I run the cell, I'm going to immediately prompt the user for some inputs. using the built-in input function. And I'll show you how to use that in just a moment in case you're not familiar with it. Then I'm going to enter in something like what's one plus one. I should see my message printed right away. And then I should make use of the chat function to get a response from Claude and print that up as well. And then I should be prompted once again to add in some more text. So now I could say something like add two to that answer. And then I should see that I'm maintaining the history or the context of my conversation. So I should now get a response back that says taking the previous answer to and adding two to it should result in four. And again, this is go on forever until I eventually interrupt the process by clicking the interrupt button right here and hitting escape. Now, as I mentioned, if you're not familiar with the built-in input function, no problem. I'm going to give you a little hint right now and just make sure that's really clear what we want to do. So I'm going to paste in a little bit of code that you can use as a little template. All right. So here's the general structure that we want to use. We want to initialize a starting list of messages that's going to start off entirely empty. And then we're going to have a while true loop that is going to run forever. And then inside the while loop, we're going to ask the user to enter in some text, making use of the, again, built in input function. Whatever the user types into the displayed input will be assigned to this user input variable. And then I put some comments in here just to guide you through the steps of what we need to do. All right. So go ahead and pause the video right here. I would encourage you to give this exercise a shot. Otherwise stick around and we'll go over a solution right now. So let's get to it. Let me show you how we would put this together. So to implement the rest of this, we really just have to follow the different comments that I put in here. The first thing we're going to do is take that user input and add it into our list of messages using the add user message function that we just put together a moment ago. So I'm going to call add user message. I'll pass in the list of messages and my user input. Then I'm going to call Claude using the built-in chat function. and pass in my list of messages. That's going to give me back some answer. I'm going to take that answer and add it in as an assistant message to my list of messages. So add assistant message, like so. And then I just need to print out the generated text. And optionally, I can put in some delimitators in the form of little dashes, just to make sure that it's clear to whoever's using this application that it's being generated by our AI. So I'll print a dash, dash, dash. And in between two of those, I'll print out my answer that I got back. And that's it. So now to test this out, I'm going to run the cell. And then I should see some output up here underneath the cell after I start chatting with Claude. So I'm going to ask Claude, what's 1 plus 1? And I'll get my response back. And 2 more. And I should see adding 2 more to 1 plus 1 should give us 4. all right very good so there is our very simple implementation of a looping chatbot

↑ 目次へ戻る

Building with the Claude API | Lesson 08「System prompts」(全文ナレッジ)

動画 6分20秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンのテーマ

system prompt(システムプロンプト)を使って、Claude の応答のトーンとスタイルをカスタマイズする方法。

お題:数学チューター chatbot

生徒が数学の問題(例:5x + 2 = 3 を解け)を chatbot に質問するシナリオを想定する。

チューターにしてほしいこと - 最初はヒントだけを与える(一言二言、アプローチのきっかけになる程度) - 生徒がそれでも分からなければ、初めて解法をステップバイステップで案内する - 類似問題の解き方を見せて、着想のヒントにする

チューターにしてほしくないこと - いきなり完全な答えを返す - 「電卓を使え」のような投げやりな回答をする

system prompt の技術的な仕組み

  • system prompt はプレーンな文字列として定義し、messages.create() 呼び出しの system= キーワード引数に渡す(messages 配列の中には入れない)
  • 1行目でロール(役割)を付与するのが定石。例:「あなたは忍耐強い数学チューターです」
  • ロールを与えると、Claude はそのロールの人物が実際に応答するであろうトーン・スタイルに寄せて答えるようになる(忍耐強く、説明を多めに、直接答えは言わず解法へ導く、など)

ノートブックでの実験(before / after)

① system prompt なしの場合

messages = []
add_user_message(messages, "How do I solve 5x+3=2 for x?")
answer = chat(messages)
answer

→ Claude はいきなりステップバイステップの完全な解答を返す。生徒にとって有用ではあるが、「生徒自身に考えさせて解に辿り着かせたい」というチューターの狙いには合わない。

② system prompt ありの場合 chat() 関数の中に system という多行文字列の変数を用意し、あらかじめ書いておいたシステムプロンプトを入れる:

system = """
You are a patient math tutor.
Do not directly answer a student's questions.
Guide them to a solution step by step.
"""

これを client.messages.create()system= 引数として渡す。すると同じ質問に対して、Claude はいきなり答えを教えず「まず何をすべきだと思う?」と生徒に問いかけ、ステップバイステップで一緒に解を導く、対話型の応答に変化する。

chat() ヘルパー関数のリファクタリング

講師は「system prompt をハードコードするのではなく、chat() を呼び出すたびに system prompt を指定できるようにしたい」という理由でリファクタを行う。

  1. system 変数の定義を chat() 関数の外(呼び出し側)に移動し、chat(messages, system=None) のようにキーワード引数として受け取れるようにする
  2. ところが create()system=None をそのまま渡すとエラーになるNone という値そのものを system に渡すことは許可されていない)
  3. そこで、渡すパラメータを動的に組み立てる方式に変更する: - まず params という辞書を作り、model / max_tokens / messages を先に入れておく - if system: の分岐で、system prompt が渡された時だけ params["system"] = system を追加する - 最後に client.messages.create(**params) のように辞書を展開して呼び出す

この形にすることで、chat() は system prompt を渡しても渡さなくてもエラーなく動く、汎用的な再利用可能関数になる。

実務ポイント(このレッスンから持ち帰るもの)

  • system prompt は messages.create() のトップレベル引数 system= に渡す。messages 配列の中に紛れ込ませない。
  • system prompt の1行目でロールを与えると、Claude はそのロールらしいトーン・スタイルで応答しやすくなる(忍耐強い先生/簡潔な技術者、等)。
  • 「直接答えを教えない」「誘導的に振る舞う」といった振る舞いレベルの制約は system prompt に書くのが効果的。同じユーザー入力でも応答の質が劇的に変わる好例。
  • Python SDK では system=None を明示的に渡すと API エラーになる。「値がある時だけキーを足す」動的パラメータ構築パターン(params 辞書 + if 分岐 + **params 展開)は、system 以外の任意引数(toolstemperature など)にも応用できる実務の定石。
  • ヘルパー関数を作る時、system prompt をハードコードせず引数化しておくと、同じ chat() 関数を複数のユースケース(別のロール・別の制約)に使い回せる。

本文(時系列・日本語逐語+画面差し込み)

[0:00] このビデオでは、Claude が生成する応答のトーンとスタイルをどのようにカスタマイズできるかを見ていきます。これがなぜ重要なのかを理解してもらうために、私たちが何らかの数学チューター・チャットボットを作っているところを想像してみてください。ユーザーはこのチャットボットを使って、数学の問題を解く手助けを求めます。例えば、ユーザーは 5x + 2 = 3 を解く手助けを求めるかもしれません。

🖥 [0:14] 画面(スライド「Math Tutor Specialist」) 左側に箇条書き、右側にチャットUIのモックアップ。 - Responses should:(応答してほしいこと) - Initially, only give the student hints - Patiently walk the student through a solution - Show solutions for similar problems - Responses should not:(応答してほしくないこと) - Immediately give a direct answer - Tell the student to use a calculator

右側のチャットUI: You: How do I solve 5x + 2 = 3 for x? AI: …

[0:20] さて、この数学チューターにやってほしいことがいくつかあり、そして絶対にやってほしくないこともいくつかあります。例えば、Claude には最初は生徒にヒントだけを与えてほしいかもしれません。問題への最初の取り組み方について、ちょっとしたコツを1つか2つ与えるだけにする、というようにです。そして、それでも生徒が問題の解き方をよく理解できない場合に、そこで初めて解法をステップバイステップで案内するようにします。また、Claude には類似の問題の解法を見せてもらって、この特定の問題にどう取り組めばよいか、生徒に少しインスピレーションを与えてほしい、ということもあるでしょう。

[0:47] 同様に、Claude に絶対にやってほしくないこともあります。例えば、Claude にいきなり完全な答えを返してほしくはありません。また、「問題を解くには電卓でも使いに行け」といったようなことを生徒に言ってほしくもありません。

[1:04] そこで、この問題を解決するために、system prompting(システムプロンプティング)として知られるテクニックを使います。system prompt は、Claude が応答するスタイルとトーンをカスタマイズするために使われます。system prompt はプレーンな文字列として定義し、create 関数呼び出しに渡します。system prompt の1行目は通常、Claude にロール(役割)を割り当てるために使います。例えば、Claude に「あなたは忍耐強い数学チューターです」と直接伝えることができます。こうすると Claude は、本物の数学チューターが応答するのと同じように応答するよう促されます。おそらく忍耐強くなり、説明を多めに提供し、しかし生徒の質問に直接答えることはおそらくせず、代わりに解へと導いてくれるでしょう。

🖥 [1:08] 画面(スライド「System prompts」) 見出し:System prompts/サブ見出し:Customizes Claude's tone and style of response 左側の3つの説明ボックス: - System prompts provide Claude guidance on how to respond - Claude will try to respond in the same way someone in the specified role would respond - Helps keep Claude on task

右側のコード例: ```python system_prompt=""" You are a patient math tutor. Do not directly answer a student's questions. Guide them to a solution step by step. """

client.messages.create( model=model, messages=messages, max_tokens=1000, system=system_prompt ) ```

[1:37] では、これを実際に動かして見るために、ノートブックに戻って、system prompt がある場合とない場合とで、Claude が数学の質問にどう応答するかを見てみましょう。

[1:45] ノートブックに戻りました。私は新しいノートブックを作り、最初のクライアント生成コードと、例の3つのヘルパー関数だけを引き継ぎました。皆さんが新しいノートブックを作る必要はありません。私はコードを整理するためにそうした、とお知らせしているだけです。

🖥 [1:45] 画面(Jupyter ノートブック 002_system_prompt.ipynb VS Code + Jupyter 拡張の画面。ファイル名 002_system_prompt.ipynb。上部に既存のヘルパー関数セル(# Helper functions 見出し配下、assistant_message = {"role": "assistant", "content": text} / messages.append(assistant_message) 等)があり、その下に空のコードセルが続く。

[1:57] それでは、この下の次のセルの中で、Claude にとてもシンプルな質問をします。簡単な数学の問題を解くように頼んで、最初にどう応答するかを見てみます。おそらく直接的な答えをそのまま返してくるでしょう。その後で戻って system prompt を追加し、もう少し説明を加えるよう、いわばチューター的なアプローチを取るように促します。

[2:13] では、system メッセージをまったく使わない状態で、Claude が私たちにどう応答するか見てみましょう。メッセージのリストを作ります。そのメッセージのリストにユーザーメッセージを追加し、5x + 3 = 2 を x について解くように頼みます。それから、メッセージのリストを渡して chat を呼び出すことで回答を取得し、その回答を出力します。

🖥 [2:23] 画面(system prompt なしの実験) ```python messages = []

add_user_message(messages, "How do I solve 5x+3=2 for x?") answer = chat(messages)

answer 出力(`chat()` の戻り値、3.8秒で応答): "# Solving 5x + 3 = 2 for x

I'll walk you through this step by step:

1) First, we need to isolate the variable term (5x) by moving the constant 3 to the right side of the equation. 5x + 3 = 2 5x = 2 - 3 (Subtract 3 from both sides) 5x = -1

2) Next, we need to isolate x by dividing both sides by 5: 5x = -1 x = -1/5 (Divide both sides by 5)

Therefore, x = -1/5 or x = -0.2 in decimal form.

You can verify this answer by substituting it back into the original equation: 5(-1/5) + 3 = 2 -1 + 3 = 2 2 = 2 ✓" ```

[2:30] そしてこのセルを実行すると、おそらくこれを解くための正確なステップバイステップの解法が表示されるはずです。さて、これは生徒にとってはおそらく有用でしょう。ステップバイステップの解法を見せてくれるわけですから。しかし、私たちが目指しているものとは少し違います。私たちは生徒に考えさせたいのです。生徒自身の力で解に辿り着いてほしいのです。小さなステップだけを与えて、正しい方向へと導いてあげたいのです。そこで、system prompt を使って、Claude の応答の仕方をカスタマイズします。

[2:56] そのやり方をお見せしましょう。上にある私の chat 関数の中で、system という新しい変数を作ります。それに複数行文字列を代入します。そしてその中に、あらかじめ書いておいた system prompt を組み立てて入れます。Claude に、あなたは忍耐強い数学チューターだと伝えます。生徒の質問に直接答えてはいけません。代わりに、問題の解き方について少しだけ手引きを与えるように、と。

🖥 [3:04] 画面(chat() 関数内に system prompt をハードコード) ```python def chat(messages): system = """ You are a patient math tutor. Do not directly answer a student's questions. Guide them to a solution step by step. """

message = client.messages.create(
    model=model,
    max_tokens=1000,
    messages=messages,
)

return message.content[0].text

`` (この時点ではまだsystem=systemcreate()` の引数に追加されていない)

[3:17] この system prompt を、create 関数の system キーワード引数として確実に渡すようにします。それからセルを再実行します。それから下に戻って、今度は Claude がどう応答するか見てみましょう。

[3:31] なるほど。これは、生徒に問題の解き方をただ直接教えるよりも、はるかに良い回答に見えます。Claude は今、ユーザーに解法を順を追って進めるよう促しています。ステップバイステップで、Claude はまず生徒に、方程式の片側に x を孤立させてはどうかと求め、それから、それをどうやって行えばよいかを生徒に問いかけています。

🖥 [3:35] 画面(system prompt 適用後の実験結果) ```python messages = []

add_user_message(messages, "How do I solve 5x+3=2 for x?") answer = chat(messages)

answer 出力(2.5秒で応答): "I'd be happy to guide you through solving this equation step by step.

Let's work with the equation 5x + 3 = 2.

When solving for x, our goal is to isolate x on one side of the equation.

What do you think would be a good first step to isolate x? Consider what operation we might need to perform on both sides to start moving terms around." ```

[3:47] これで、生徒にとってはるかにインタラクティブな体験になりました。直接答えを渡すよりも、ここで何が起きているのかを生徒が少しでもよく学べる助けに、きっとなるはずです。

[3:55] さてこれで明らかなように、system prompt を使うことは、与えられたユーザー入力にどう答えるべきかについて、Claude を特定の方向へ誘導(ステアリング)するための強力なツールです。先へ進む前に、chat 関数に少しリファクタリングを施したいと思います。この中に system prompt をハードコードして持たせるのではなく、chat 関数を呼び出すたびに system prompt を指定できるようにしたいのです。言い換えると、ここにあるこれを切り取って、この下のセルの中に置き、そしてこんなふうに system prompt を渡せるようにしたいのです。そうすれば、内部にハードコードされた system prompt を持たない、将来さまざまな問題に使える、はるかに再利用性の高い chat 関数になります。

🖥 [4:15] 画面(system prompt を関数外へ切り出すリファクタ開始) chat() 関数内の system = """...""" ブロックが選択(ハイライト)されている状態。この時点で create() 呼び出しにはすでに system=system が含まれている: ```python def chat(messages): system = """ You are a patient math tutor. Do not directly answer a student's questions. Guide them to a solution step by step. """

message = client.messages.create(
    model=model,
    max_tokens=1000,
    messages=messages,
    system=system
)

return message.content[0].text

```

[4:34] そこで次に、この system 引数を受け取って、create 関数に確実に渡すようにする必要があります。ただ、これをやるには、皆さんが思っているより少しだけ余分な作業が必要になります。その理由をお見せしましょう。まず手早く、chat 関数に system キーワード引数を追加し、デフォルト値を None にします。

🖥 [4:45] 画面(system パラメータ化の途中経過) 関数シグネチャを def chat(messages): から def chat(messages, system) へ編集中の途中フレーム(syst まで入力中)。最終的に: ```python def chat(messages, system=None):

message = client.messages.create(
    model=model,
    max_tokens=1000,
    messages=messages,
    system=system
)

return message.content[0].text

```

[4:50] ここでこのセルを実行し、それからこの下のセルを実行すると、すべて期待どおり、まったく問題なく動きます。ところが、chat 関数(の呼び出し側)まで下がっていって、ここでは system prompt をまったく渡したくない、と決めたとしましょう。つまり、それを削除してセルを実行すると、エラーメッセージが出てしまいます。system prompt として None を渡すことは許可されていないのです。

[5:10] そこで、create 関数に渡すことになるパラメータを、もう少し動的に組み立てる必要があります。system が None の場合には、このパラメータ自体をまったく含めたくないのです。それをどうやるか、小さなリファクタでお見せしましょう。

[5:23] まず、すぐ上に params という辞書を作ります。次に、model、max_tokens、messages を切り取って貼り付けます。それから、これを辞書の構文に変換します。ダブルクォート、ダブルクォート、コロン、という具合にです。

🖥 [5:23] 画面(params 辞書によるリファクタ・途中経過) ```python def chat(messages, system=None): params = { "model": model, "max_tokens": 1000, "messages": messages, }

if system:
    (この時点では中身未記入/記入中)

message = client.messages.create(
    system=system
)
return message.content[0].text

`` (その後if system:の中身がparams["system"] = system` として書き込まれる)

[5:40] それから、system prompt が渡されたかどうかをチェックします。もし渡されていれば、それを params 辞書の中の system キーとして、こんなふうに追加します。

[5:52] 次に、この下の create 呼び出しを、スター・スター・params(**params)に更新します。これで完了です。ではこのセルを再実行して、次のセルに戻ります。

🖥 [5:52〜6:16] 画面(最終形の chat() 関数と2種類の呼び出し) ```python def chat(messages, system=None): params = { "model": model, "max_tokens": 1000, "messages": messages, }

if system:
    params["system"] = system

message = client.messages.create(**params)
return message.content[0].text

呼び出し側:python messages = []

system = """ You are a patient math tutor. Do not directly answer a student's questions. Guide them to a solution step by step. """

add_user_message(messages, "How do I solve 5x+3=2 for x?") answer = chat(messages, system=system)

answer 出力例: "I'd be happy to guide you through solving this equation step by step.

When solving for x in 5x+3=2, we need to isolate x on one side of the equation.

What do you think the first step..." ```

[6:02] そして、system キーワード引数を一切渡さずに chat を呼び出しても、問題ありません。すべてまったく問題なく動きます。そして、やはり system prompt を渡したい、と決めた場合も、はい、それも同様にまったく問題なく動きます。

[6:15] オーケー。これで良さそうです。これで、私たちの chat 関数の中に system prompt のサポートが組み込まれました。

英語逐語(ASR全文)

In this video, we're going to take a look at how we can customize the tone and style of response that Claude generates. To help you understand why this is important, I want you to imagine that we are making some kind of math tutor chatbot. So a user will use this chatbot to ask for help in solving math problems. For example, a user might ask for help in solving 5x plus 2 equals 3. Now there's a couple of things that we'd want our math tutor to do and a couple of things that we would definitely not want it to do. So for example, we might want Claude to initially only give the student some hints. Maybe just give them a little tip or two in how they might initially approach the problem. And then if the student still doesn't quite understand how to solve the problem, only then maybe walk the student through a solution step by step. We might also want Claude to show solutions for a similar problem to give the student a little bit of inspiration on how they might approach this particular problem. Likewise, there are some things that we would definitely not want Claude to do. For example, we would not want Claude to just immediately respond with a complete answer. And we would also not want Claude to tell a student to just go use a calculator to solve the problem or something like that. So to solve this problem, we're going to use a technique known as system prompting. System prompts are used to customize the style and tone that Claude will respond with. We define a system prompt as a plain string and then pass it into the create function call. The first line of a system prompt will usually assign Claude a role, so we might directly tell Claude that they are a patient math tutor. This will encourage Claude to respond in the same way that a real math tutor would respond. You would probably end up being patient, provide a lot of explanation, but probably not directly answer a student's question. It'll instead guide them to a solution. Now, to see this in action, let's go back over to our notebook and see how Claude responds to math questions with and without a system prompt. So back inside my notebook, I've made a new notebook and I've carried over just our initial client creation and those three helper functions. You do not have to create a new notebook. I'm just letting you know that I did just to organize my code. Then down here inside of the next cell, I'm going to ask Claude a very simple question. I'm going to ask it to solve a simple math problem and we'll see how it initially responds. It will probably just give us a direct answer and then we'll go back and add in a system prompt to encourage it to give us a little bit more explanation, kind of a tutor approach. So let's see how Claude responds to us without a system message at all. I'm going to make a list of messages. I'll add in a user message to my list of messages and I'm going to ask it to solve 5x plus 3 equals 2 for x. I'll then get an answer by calling chat with my list of messages and print out the answer. And then if I run the cell, I'm probably going to see an exact step-by-step solution on how to solve this. Now this probably is going to be useful for a student because it's going to show them a step-by-step solution, but it's not quite what we are going for. We want to make a student think and we want them to arrive at the solution on their own. We just want to give them small steps and kind of guide them in the right direction. So we're going to customize the way in which Claude responds by using a system prompt. Let me show you how we do that. Up inside my chat function, I'm going to make a new variable of system. I'm going to assign to it a multi-line string. And inside there, I'm going to put together a system prompt that I wrote ahead of time. So I'm going to tell Claude that it is a patient math tutor. It should not directly answer students questions. Instead, give them a little bit of guidance on how to solve the problem. I'm going to make sure that I pass in the system prompt as the system keyword argument to the create function. I'm then going to rerun the cell. I'll then go back down and let's see how Claude responds now. All right. This looks like a much better answer rather than just directly telling the student how to solve the problem. Claude is now prompting the user to go through a solution. Step-by-step Claude is asking the student to first maybe isolate X on one side of the equation and then ask the student how we might go about that. So now we've got a much more interactive experience for the student, and this will hopefully help them learn what's going on here a little bit better than just giving them a direct answer. All right. So clearly using a system prompt is a powerful tool for steering Claude in a particular direction on how it should answer a given user input. Before we move on, I want to do a little bit of a refactor to our chat function. Rather than having a hard-coded system prompt inside of here, I want to be able to specify a system prompt whenever we call the chat function. So in other words, I want to cut this right here, put it down inside this cell underneath, and then I want to be able to pass in a system prompt like so. So now we have a much more reusable chat function that we can use on a wide variety of different problems in the future without having a hard-coded system prompt inside of it. So now we need to make sure that we take this system argument and pass it into the create function. Now doing so is going to require just a little bit more work than you might think. Let me show you why. I'm going to very quickly add in a system keyword argument to the chat function and I'll default it to be none. If I now run the cell and then run the cell down here, everything is going to work fine, exactly as expected. However, if I go down to the chat function and I decide that I do not want to provide a system prompt here at all, so if I delete that and then run the cell, I'll end up getting an error message. So we are not allowed to pass in a system prompt of none. So we need to assemble our parameters that we're going to pass into the create function a little bit more dynamically. And if we have a system of none, we do not want to include this parameter at all. So let me show you how we are going to do that with a small refactor. First, I'm going to make a params dictionary right above. I'm then going to cut and paste in model max tokens and messages. I'm then going to convert this to dictionary syntax. So I'll use double quote, double quote, colons like so. I'll then check and see if a system prompt was passed in. So if one was passed in, then I want to add it in as the system key inside of the params dictionary like so. Then I'm going to update the create call down here to star star params and that's it. So now I'm going to rerun that cell. I'll go back down to the next one. And if I call chat without any system keyword argument being passed in, no problem. Everything is going to work just fine. And if I decide that I do want to provide a system prompt, yep, that's going to work just fine as well. Okay. So this looks good. So we've now got support for a system prompt inside of our chat function.

↑ 目次へ戻る

Building with the Claude API | Lesson 09「System prompts exercise」(全文ナレッジ)

動画 1分56秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンの位置づけ

  • Lesson 08(system prompt の解説回)の内容を使った演習(exercise)回。動画自体は1分56秒と短く、実質は「お題の提示 → 一時停止して自分で解く → 模範解答の実演」という3段構成。
  • お題:Jupyter notebook 002_system_prompt.ipynb 上で、Claude に「文字列中の重複文字をチェックする Python 関数」を書かせる。system prompt を使わずに聞くと、コードに加えて大量の説明・コメントが返ってくる。これを system prompt で役割(role)を与えることで、できるだけ簡潔な実装に絞り込むのが課題。

Before:system prompt なしの状態

使用コード(messages = [] から):

messages = []

add_user_message(
    messages,
    "Write a Python function that checks a string for duplicate characters.",
)
answer = chat(messages)

answer

このセル(実行番号 [32], 6.4秒)の戻り値(answer を print せず素の値として評価しているため、Jupyter 上は \n がエスケープされたままの文字列 repr で表示されている):

'# Python Function to Check for Duplicate Characters in a String\n\n```python\ndef has_duplicate_characters(string):\n    """\n    Checks if a string contains any duplicate characters.\n    \n    Args:\n        string (str): The input string to check for duplicates\n    \n    Returns:\n        bool: True if the string has duplicate characters, False otherwise\n    """\n    # Create an empty set to keep track of seen characters\n    seen_chars = set()\n    \n    # Iterate through each character in the string\n    for char in string:\n        # If we\'ve seen this character before, there\'s a duplicate\n        if char in seen_chars:\n            return True\n        # Otherwise, add it to our set of seen characters\n        seen_chars.add(char)\n    \n    # If we made it through the entire string without finding duplicates\n    return False\n\n# Example usage\nif __name__ == "__main__":\n    test_cases = [\n        "abcde",      # No duplicates\n        "hello",      # Duplicate \'l\'\n        "python",     # No duplicates\n        "programming", # Duplicate \'r\', \'g\', \'m\'\n        "",           # Empty string\n        "aaa"         # Multiple duplicates\n    ]\n    \n    for test in test_cases:\n        result = has_duplicate_characters(test)\n        print(f"\'{test}\' has duplicate characters: {result}")\n```\n\nThis function:\n1. Uses a set to track characters we\'ve seen while iterating through the string\n2. Returns True as soon as it finds any duplicate character\n3. Returns False if it processes the entire string without finding duplicates\n4. Has O(n) time complexity where n is the length of the string\n5. Has …'

(5番目の項目はフレームの表示範囲外で切れており、判読不能のため で示す。おそらく「O(1) auxiliary space(最良・平均ケース)」等の空間計算量に関する記述が続くと推測されるが、画面上に文字が写っていないため断定・捏造はしない)

→ docstring・コメント・使用例・実行後の解説(1〜5の箇条書き)まで含む、かなり長い応答になっている。これが「reduce したい code」の実例。

課題の指示(動画内で一時停止を促す部分)

  1. chat() 関数呼び出しに system パラメータとして system prompt を渡す。
  2. その system prompt は Claude に役割(role)を割り当てできるだけ簡潔に応答するよう促す内容にする。

After:system prompt ありの解答例

answer = chat(messages, system="You are a Python engineer who writes very concise code")

実行結果(実行番号 [33], 2.1秒):

def has_duplicates(s):
    return len(set(s)) < len(s)

This function returns True if the string contains any duplicate characters, and False otherwise. It works by comparing the length of the string with the length of its set (which removes duplicates). (日本語訳:この関数は、文字列に重複文字が含まれていれば True、なければ False を返す。文字列の長さと、それを set 化した長さ〔=重複を除去した長さ〕を比較することで判定している。)

Before → After の差:docstring・詳細コメント・使用例・箇条書き解説まで付いた長い実装 → 1行の関数本体+短い説明のみ、へと劇的に圧縮された。

実務ポイント(このレッスンから持ち帰るもの)

  • system prompt に「役割(role)」を与えるだけで出力の冗長さが激変する"You are a Python engineer who writes very concise code" のように職能+スタイル指示を1文で与えるだけで、docstring・コメント・使用例・解説の箇条書きが軒並み削れる。
  • role + style を1つの system prompt に同居させてよい:「誰であるか(Python engineer)」と「どう振る舞うか(very concise code を書く)」を同じ文に入れる書き方が最短で効く。
  • before/after を同じ messages 履歴に対して比較できるmessages はそのままで chat(messages)chat(messages, system=...) を撃ち分けるだけで、system prompt の効果を最小差分で検証できる(プロンプトエンジニアリングの反復サイクルとして実務でもそのまま使える型)。
  • 「大量のコードが返ってくる」問題への処方箋:ユーザープロンプト側で細かく制約するのではなく、system prompt 側で「簡潔に書くエンジニア」という人格を固定する方が、繰り返し呼び出しても安定して効く(session/複数ターンにわたって効くのが system prompt の強み)。
  • 演習の型として持ち帰れるもの:「①冗長な出力を先に観察する→②system prompt で role+style を1文与える→③同じ入力で再実行し差分を確認する」という最小ループは、他のAPI利用(要約、分類、コード生成全般)にも横展開できる。

本文(時系列・日本語逐語+画面差し込み)

[0:00] さて、system prompt に関する簡単な演習をやっていきましょう。

🖥 [0:00] 画面: ノートブック 002_system_prompt.ipynb(VS Code / Jupyter拡張、.venv (Python 3.9.6))が開いた状態。画面はスクロール移行中でほぼ空白(「answer」というラベルの断片と実行セル [32] 4.6s のインジケータのみが見える)。

[0:03] 私はノートブックを更新して、いま Claude に、文字列中の重複文字をチェックする Python 関数を書き出すよう依頼しています。

[0:09] これを実行して answer を出力すると、とんでもない量のコードが生成されるのが分かります。

[0:15] その中には確かにコードそのものも含まれていますが、それと同じくらい、たくさんの説明とたくさんのコメントも含まれています。

🖥 [0:09–0:30] 画面: 入力セルは以下の内容。 ```python messages = []

add_user_message( messages, "Write a Python function that checks a string for duplicate characters.", ) answer = chat(messages)

answer 実行結果(`[32]`, 6.4秒)は、講師がスクロールして見せている通り、`# Python Function to Check for Duplicate Characters in a String` という見出しから始まり、python`` コードブロック(docstring・コメント付きのhas_duplicate_characters関数+if name == "main":` の使用例)、そのあとに「This function: 1. Uses a set to track characters we've seen while iterating through the string/2. Returns True as soon as it finds any duplicate character/3. Returns False if it processes the entire string without finding duplicates/4. Has O(n) time complexity where n is the length of the string/5. Has …(以降は画面外で判読不能)」という箇条書き解説が続く、非常に長い応答になっている(全文は上記「日本語ナレッジ」節に転記済み)。

[0:20] そこで今回は、生成されるコードの量を減らす演習をやっていきたいと思います。

[0:26] この関数の実装を、できる限り簡潔なものにまで落とし込みたいのです。

[0:31] そのために、みなさんには system prompt を書いて、それを chat 関数呼び出しに渡してもらいたいと思います。

[0:35] system prompt では、Claude に役割(role)を割り当てたうえで、できるだけ簡潔に応答するよう促してください。

[0:41] ということで、ここでもう一度、動画を一時停止してください。

[0:43] ぜひ挑戦してみてください。それが終わったら、ちょうど今から解答を見ていきましょう。

[0:47] これを解くために、私は chat 関数呼び出しに system prompt を渡します。

[0:51] その中で、Claude に役割を割り当てて、非常に簡潔なコードを書くよう促します。

[0:55] こう書きます。「あなたは非常に簡潔なコードを書く Python エンジニアです(You are a Python engineer who writes very concise code)」。

[1:01] では、セルを実行して、今度はどんな応答が返ってくるか見てみましょう。

🖥 [0:55–1:05] 画面: 入力セルの answer = chat(messages) の行が python answer = chat(messages, system="You are a Python engineer who writes very concise code") に書き換えられている。実行後([33], 2.1秒)、出力セル内で以下のテキストが選択(ハイライト)表示されている。 python def has_duplicates(s): return len(set(s)) < len(s) This function returns True if the string contains any duplicate characters, and False otherwise. It works by comparing the length of the string with the length of its set (which removes duplicates).

[1:05] よし、これはまさに私が求めていたものにかなり近いですね。

[1:09] ご覧の通り、この一意性チェックを実装するために実際に書く必要があったコードは、非常に非常に短くなっています。

[1:15] 少し前に見たコードよりもずっと短いです。

[1:17] この演習でうまくいったなら幸いです。

[1:19] もしうまくいかなかったとしても、それはまったく問題ありません。

[1:21] このコース全体を通して、みなさんが自分のスキルを試せる演習は、他にもたくさん用意されています。

[1:25]以降:srt では 1:25〜1:55 の30秒間 "Thank you." という字幕が続くが、この長さは不自然であり、無音区間を ASR が誤認識した可能性が高い。英語逐語txtにはそのまま "Thank you." が残っているため参考として下記に掲載するが、実際の講師発話としては [1:21] の締めの一文で実質的にレッスンが終わっている可能性が高い。)

🖥 [1:00前後〜終了] 画面: int_003 フレーム以降、追加の画面遷移フレームは提供されておらず、最後まで上記の「system prompt あり」のセル・出力がそのまま表示されていると判断される。

英語逐語(ASR全文)

Let's go through a quick exercise on system prompts. I've updated my notebook and I'm now asking Claude to write out a Python function that is going to check a string for duplicate characters. If I run this and print out the answer, I'm going to see a tremendous amount of code gets generated here. So some of it is going to be a little bit of code, but there's also going to be a lot of explanation and a lot of comments as well. So I would like to go through an exercise here where we try to reduce the amount of code that's being generated. I want to get down to as concise an implementation of this function as possible. To do so, I would like you to write out a system prompt and pass it into the chat function call. Your system prompt should assign a role to Claude and encourage it to respond as concisely as possible. So again, go ahead and pause the video here. Go ahead and give it a shot and we'll go over solution right about now. To solve this, I'm going to pass in a system prompt to the chat function call. And inside that, I'm going to assign a role to Claude that will encourage it to write very concise code. I'll say you are a Python engineer who writes very concise code. So let's now run the cell and see what kind of response we get back now. All right, that is definitely much more in line with what I was looking for. You'll see that the actual code we had to write to implement this uniqueness check is very, very short. Much shorter than the code we saw a moment ago. Hopefully you had some success with this exercise. And if you didn't, that is totally fine. There are going to be many other exercises throughout the course where you can test out your skills. Thank you.

↑ 目次へ戻る

Building with the Claude API | Lesson 10「Temperature」(全文ナレッジ)

動画 6分6秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

テキスト生成のおさらい(tokenize → prediction → sampling)

  • Claude は入力テキスト(例:「What do you think」)を tokenize(小さな塊に分割)し、prediction フェーズで「次に来うる単語」候補それぞれに確率を割り当て、sampling フェーズでその確率に基づいて実際のトークンを1つ選ぶ。
  • スライドの例では、入力「What do you think」に対する次トークン候補と選択確率(Selection probability)は about 0.3 / would 0.2 / of 0.1 / is 0.1 / when 0.1 / makes 0.05 / we 0.05。ここで about が選ばれれば「What do you think about」となり、この過程を文・メッセージが完成するまで繰り返す。
  • 講師は以降この確率を棒グラフ(左から確率の大きい順にソート。内部的なソートがあるわけではなく、見やすさのため)で表示する。

temperature とは

  • temperature は、この確率分布そのものを直接操作するパラメータ。モデル呼び出し時に渡す 0〜1 の小数値
  • temperature → 0:最高確率のトークンがさらに選ばれやすくなり、0 では最高確率トークンの選択確率が 100% に。= 決定論的(deterministic)出力(常に初期確率が最大のトークンを選ぶ)。
  • temperature を上げる:初期確率が低いトークンが選ばれる確率が上がる(例: we が 0% → 9%)。= 出力がよりランダム・創造的に。
  • 講師は Claude 自身で作ったデモ(claude.ai の Artifact「LLM Temperature Parameter Demo」)でスライダーを動かし、分布が変化する様子を実演。

タスク別の目安(Temperature Ranges スライド)

帯域 用途例
Low Temp (0.0–0.3) Factual responses / Coding assistance / Data extraction / Content moderation
Medium Temp (0.4–0.7) Summarization / Educational content / Problem-solving / Creative writing with constraints
High Temp (0.8–1.0) Brainstorming / Creative writing / Marketing content / Joke generation
  • データ抽出のようにランダム性・創造性が不要なタスクは低 temperature、ブレインストーミング・ライティング・ジョーク(予想外の言葉の使い方に依存する)のような創造系タスクは高 temperature。

実装(chat ヘルパーへの追加)

  • chat() 関数に temperature=1.0 のデフォルト引数を追加し、params dict に "temperature": temperature として渡すだけ。
  • 実験:「Generate a one sentence movie idea」を temperature=0.0 で3回実行 → 毎回「time traveling ○○(タイムトラベルもの)」の似た案が返る。temperature=1.0 に上げても最初の1回はまたタイムトラベルものが出た——temperature を上げても「必ず違う案になる」わけではなく、違う案が出る確率が上がるだけ。続く実行では画家の話など創造的な案が出た。

実務ポイント(このレッスンから持ち帰るもの)

  1. temperature はタスク種別で決める:抽出・分類・モデレーション=0 寄り、要約・教育コンテンツ=中間、ブレスト・コピー生成・ジョーク=1 寄り。ECいんぷる系ならレポート数値抽出は 0.0、広告コピー案出しは 0.8–1.0 が定石。
  2. temperature=0 でも「毎回完全に同一」を保証する仕様ではないが、実用上は決定論的出力(同傾向の出力)に収束する。再現性が欲しい評価パイプラインでは 0 を使う。
  3. 高 temperature ≠ 毎回違う出力。確率が変わるだけなので、1回の試行で判断せず複数回サンプリングして比較する。
  4. 実装は client.messages.create(**params) の params に temperature を1キー足すだけ。ヘルパー関数にデフォルト値付き引数で通すパターンが再利用しやすい。
  5. ASR 上は「converse function」と聞こえる箇所があるが、このコースの実装で呼んでいるのは client.messages.create()(Anthropic SDK)。

本文(時系列・日本語逐語+画面差し込み)

[0:00] このコースの前の方で、Claude が実際にどうやってテキストを生成するのかをごく簡単に話しました。思い出してください。私たちは Claude に何らかのテキスト、例えば「what do you think」という単語列を入力します。

[0:10] Claude はこのテキストを tokenize、つまり小さな塊に分割します。次に Claude は prediction(予測)フェーズに入り、次に来る可能性のある単語を判断し、その選択肢それぞれに確率を割り当てます。最後に sampling(サンプリング)フェーズで、これらの確率に基づいて実際にトークンが1つ選ばれます。

🖥 スライド(ニューラルネットワークの図): - 左に「Inputs」として「What」「do」「you」「think」の4ボックス。ネットワークの層を通って右側の出力ノードへ。 - 右に「Selection probability」の見出しで候補トークンと確率の一覧:

about   0.3
would   0.2
of      0.1
is      0.1
when    0.1
makes   0.05
we      0.05
  • さらに右に「Next Token」の空ボックス(点線枠)。

[0:24] つまり、いま画面に出しているこの図では、「what do you think」という入力に対して、あり得る次のトークンは about、would、of などなど——右側に見えているものすべてです。それぞれに確率が割り当てられます。そしてこの場合、Claude は about を次のトークンとして最良だと決めたとしましょう。すると「what do you think about」というフレーズが出来上がります。

[0:53] このプロセス全体が繰り返されて、文が完成し、メッセージ全体が完成します。念のためはっきりさせておくと、ここで示している数値は確率、つまり各トークンが選択されるパーセンテージです。

[1:04] この確率をもう少し分かりやすくするため、このビデオの残りではチャートで表示します。同じ確率を、私たちが理解しやすい形式にしただけです。左から右にソートしてあることにも気づくと思いますが、内部で実際にソートが行われているわけではありません。チャートを分かりやすくするために、確率の大きい順に並べただけです。

🖥 スライド(変換図):左に上記の候補と確率のリスト、矢印を挟んで右に棒グラフ。縦軸「Selection probability」、横軸「Token」。棒の値は about 30% / would 20% / of 10% / is 10% / when 10% / makes 5% / we 5%。

[1:28] さて、Claude がどうテキストを生成するかを思い出したところで、この確率に直接影響を与え、Claude が実際にどのトークンを選ぶかをコントロールできる方法を1つお見せします。

[1:40] この確率は temperature というパラメータで制御できます。temperature は 0 から 1 の間の小数値で、モデル呼び出しを行うとき——つまりあの converse 関数を呼ぶとき——に渡します(※ASR原文ママ。本コースの実装では client.messages.create())。temperature は確率の分布そのものに影響を与えます。

[1:59] これはちょっと理解しにくいので、ここにあるプロットやチャートを見てもらってもいいですし、あるいは Claude 自身でちょっとしたデモを作ったので、それで何が起きているかをより良く掴んでもらえます。そのデモをお見せしましょう。

[2:10] はい、これはさっきの図で見ていたのと同じチャートです。

🖥 画面(Chrome・Claude の Artifact デモ):URL は claude.ai/artifacts/539bc43a-7396-4288-a291-586888469b81。タイトル「LLM Temperature Parameter Demo」。「Temperature: 0.49」のスライダー(左端 0 (Deterministic)〜右端 1 (More Random))。下に棒グラフ(縦軸 Selection Probability (%)、横軸 about / would / of / is / when / makes / we。about が約60%で突出、他は10%以下)。さらに下に説明ボックス:

How Temperature Works in LLMs: Temperature controls how deterministic or random the token selection is in language models: - Low Temperature (near 0): Model becomes very deterministic, always picking the highest probability token. - High Temperature (near 1): Model distributes probability more evenly across all possible tokens, introducing more randomness.

[2:14] temperature の値をゼロに向かって下げていくと——ここに temperature がありますね——最高確率のものがさらに起こりやすくなります。私たちの最高確率は about でしたが、それが 100% まで増えていきます。つまり temperature が 0 のとき、いわゆる決定論的(deterministic)出力が得られるようになります。常に初期確率が最も高いトークンを選ぶ、ということです。

[2:37] そして temperature を上げ始めると、初期確率がより低いトークンを選ぶ確率が上がっていきます。例えば we を次のトークンとして選ぶ確率が 0% から、そうですね 9% まで上がる、というように。

🖥 画面(同デモ):スライダーを右端へ。「Temperature: 0.99」〜「Temperature: 1.00」。棒グラフは about 約23%を先頭に、would / of / is / when / makes / we まで確率がなだらかに分散した形に変化。

🖥 スライド「Temperature changes how likely each token is to be selected」: - 左のグラフ=Low temperature More deterministic output:about 100%、would〜we はすべて 0%。 - 右のグラフ=High temperature More random output:about 23% / would 18% / of 15% / is 12% / when 12% / makes 11% / we 9%。

[2:54] これが temperature の背後にある理論ですが、実世界では実際に何を意味するのでしょうか。私たちは、実際に達成しようとしているタスクに応じて、異なる temperature の値を使うようになります。これがサンプルの範囲と、各範囲に当てはまりそうなタスクの例です。

🖥 スライド「Temperature Ranges」: - Low Temp (0.0 - 0.3):Factual responses / Coding assistance / Data extraction / Content moderation - Medium Temp (0.4 - 0.7):Summarization / Educational content / Problem-solving / Creative writing with constraints - High Temp (0.8 - 1.0):Brainstorming / Creative writing / Marketing content / Joke generation - 下部に両矢印:「Less creative」←→「More creative」

[3:03] 例えばデータ抽出のようなものでは、ランダム性や創造性はまったく欲しくありません。Claude に大きなテキストの塊を渡して、非常に特定の情報を抽出するよう頼む場合、そこに創造性は一切不要です。私たちが提供したまさにそのテキストを Claude に見てもらい、最も関連する情報を引き出してほしいだけです。

[3:25] そして高 temperature 側では、より創造的になり始め、あまり一般的でないトークンが使われるのを見るようになります。本当に創造性重視のタスク——ブレインストーミング、ライティング、すごくクリエイティブなマーケティング、あるいはジョークのようなもの——をするときはいつでも、高めの temperature を使いたくなるでしょう。ジョークの多くは、必ずしも予想どおりではない言葉の使い方に大きく依存しますから。

[3:50] temperature が何なのか理解できたので、ノートブックに戻って、temperature をその場で調整する方法を理解しましょう。chat 関数を更新して、temperature 引数を受け取り、それを create 関数呼び出しに渡すようにしたいと思います。

[4:02] 引数のリストの中に temperature を追加し、デフォルトを 1.0 にします。つまり、より創造的な側に倒しておきたいわけです。それからその引数を受け取って、params オブジェクトに temperature として追加します。

🖥 画面(VS Code / 003_temperature.ipynb):# Helper functions セルの chat 関数を編集中("tempera まで入力した瞬間のフレームあり)。完成形:

def chat(messages, system=None, temperature=1.0):
    params = {
        "model": model,
        "max_tokens": 1000,
        "messages": messages,
        "temperature": temperature,
    }

    if system:
        params["system"] = system

    message = client.messages.create(**params)
    return message.content[0].text

(編集前のフレームでは def chat(messages, system=None): で params は model / max_tokens / messages の3キーのみ)

[4:18] アプリケーション内で temperature を調整できるようにするために必要なのは、これだけです。ではテストするために、このセルを再実行します。次のセルに降りて、Claude に一文の映画アイデアを生成するよう頼みます。最初は temperature 0.0 を渡します。

🖥 画面(同ノートブック・次セル):

messages = []

add_user_message(
    messages,
    "Generate a one sentence movie idea",
)
answer = chat(messages, temperature=0.0)

answer

[4:42] 理論上は、いつも性質が少し似通った映画アイデアが返ってくるはずです。実行してみます。1回目は time traveling archaeologist(タイムトラベルする考古学者)が返ってきます。これは非常によくあるパターンだと分かるはずです。少なくとも私の場合、temperature が 0 のときは「タイムトラベルする何か」についての映画アイデアをかなり頻繁に得ます。

[5:02] もう一度実行すれば、おそらくまた別のタイムトラベルものが出るでしょう。ほら、同じ。もう1回やってみましょうか。ええ、同じ種類のアイデア、a jaded time traveling historian(すり切れたタイムトラベル歴史学者)です。

🖥 画面(実行結果・セル [30] 2.3s):

'A jaded time-traveling historian accidentally alters a pivotal moment in medieval history and
must navigate court intrigue, avoid paradoxes, and find a way home while reluctantly mentoring
the young peasant who will become an unexpected legend.'

[5:08] では temperature を少し調整して、Claude にもっと独創的・創造的なアイデアを出すよう促してみましょう。temperature を 1.0 まで上げてみます。

[5:17] さて、もう一度実行すると、願わくば jaded time traveler のようなアイデアは出ないでほしいのですが……見てのとおり、即座に出ました。これは知っておくべきことです。temperature を上げたからといって、劇的に違うアイデアが必ず得られるわけではありません。違うものが得られる確率が上がるだけです。

[5:36] もう一度実行すれば、もっと創造的なアイデアが見られるかもしれません。よし、これは確実により創造的ですね。今回はタイムトラベルについては何もなし。もう1回テストしましょう。ほら。今回もタイムトラベラーとかそういうものではありません。

🖥 画面(temperature=1.0 の実行結果・セル [33] 1.6s):

answer = chat(messages, temperature=1.0)
'A dejected but optimistic painter discovers her artwork comes to life at night, guiding her
on a journey to reconnect with her estranged daughter through hidden messages in her
paintings.'

[5:46] はい、これが temperature です。覚えておいてください、一般的な指針があります。創造性があまり要らないタスクをするとき、あるいは非常に決定論的な出力が欲しいときは、低い temperature 値を使いたい。そして、もう少し創造性が必要なタスクがあるときこそ、temperature を少し上げることを考え始めたいのです。

英語逐語(ASR全文)

Earlier on inside this course, we spoke very briefly about how Claude actually generates text. Remember, we feed some amount of text into Claude, like the words, what do you think? Claude is then going to tokenize this text or break it up into smaller chunks. Claude is then going to go through a prediction phase where it decides what possible words could come next and assign a probability to each of those different options. Finally, in the sampling phase, a token is actually chosen based upon these probabilities. So in this diagram, I have on the screen, given inputs of what do you think, possible next tokens might be about, would, of, and so on. Everything you see here on the right-hand side. Each of these gets assigned a probability. And then maybe in this case, Claude settles on about as being the best possible next token. So we would end up with the phrase, what do you think about? This entire process is then repeated to complete the sentence or complete the entire message. Now, just to make sure things are really clear, the numbers I'm showing here are probabilities, the percentage chance of each token being selected. Just to make things a little bit more clear with these probabilities, I'm going to display them in a chart for the rest of this video. So still the same probabilities, just in a format that's easier for us to understand. You also notice I've kind of sorted them from left to right. There's no actual internal sorting going on. I'm just sorting them, greatest to least probability, just to make this chart a little bit easier to understand. So now that we have a reminder on how Claude generates text, I want to show you one way that we can directly influence these probabilities and control which token Claude might actually decide to select. So we can control these probabilities using a parameter called temperature. Temperature is a decimal value between 0 and 1 that we provide when we make our model call. So whenever we call that converse function. Temperature is going to influence the exact distribution of probabilities. This is a little bit tricky to understand, so you can look at the plot or these charts I've got right here, or alternatively, I put together a quick little demo with Claude itself, just to give you a better idea of what's going on. So let me show you that demo. Okay, so this is the same chart we were just looking at in that diagram a moment ago. Whenever we provide a temperature value going down to zero, so you'll see I got temperature right here, the highest probability becomes more likely to occur. So our highest probability was about, and it's going to increase all the way up to 100%. So at temperatures of zero, we start to get what we call deterministic output, where we always select the token that has the highest initial probability. Then as we start to increase our temperature, it increases the chances of us selecting a token that has a lower initial probability. So we go from maybe having a 0% chance of selecting we as the next best token all the way up to say 9%. So this is the theory behind temperature but what does this actually mean in the real world? Well we start to use different values of temperature given the actual task that we're trying to complete. These are some example ranges and tasks that might fit into each sample range. For something like say data extraction we really don't want a lot of randomness or creativity. If we give Claude a big chunk of text and ask it to extract very specific pieces of information. No real creativity required there whatsoever. We just want Claude to look at the exact text we provided and pull out the most relevant information. And then on the higher temperature side, this is where we start to get more creative and we start to see less common tokens being used. We probably are going to want to use higher temperatures anytime we are doing any kind of really creative focused task, such as brainstorming, writing, maybe doing some really creative marketing or something like a joke where a lot of jokes really depend upon using words in ways that are not always quite expected. Now that we understand what temperature is all about, let's go back over to our notebook and understand how we can adjust temperature on the fly. I would like to update our chat function so that it takes in a temperature argument that we're going to pass through to our create function call. So inside of the list of arguments, I'm going to add in temperature and I'm going to default my temperature to be at 1.0. So I want to fall on the more creative side of things. Then I'm going to take in that argument and add it into the params object as temperature. And that's all we have to do to add in support for adjusting the temperature inside of our application. So now to test this out, I'm going to rerun this cell. I'm then going to go down to the next cell and I'm going to ask Claude to generate a one sentence movie idea. And initially I'm going to provide a temperature of 0.0. So now in theory, I should be getting back movie ideas that always tend to be a little bit similar in nature. So I'm going to run this. And the first time I'm going to get back a time traveling archaeologist, you're going to see that this is a very common pattern, at least for me, I very often when I have a temperature of zero, get movie ideas that are about a time traveling something. So if I run this again, I'm probably going to see another time traveling thing. Yep, Same thing. Maybe one more time. And yeah, same kind of idea, a jaded time traveling historian. Let's now try adjusting our temperature a little bit to hopefully encourage Claude to give us some more original or creative ideas. I'm going to try adjusting my temperature up to 1.0. Now, if I run this again, I hopefully will not get an idea about a jaded time traveler or something like that. And almost immediately you can see that I do. So this is something to be aware of. Just because you dial up the temperature doesn't mean you're always going to get dramatically different ideas. It just increases the chances of getting a different one. So if I run this again, I might end up seeing a more creative idea. Okay, that's definitely more creative. Nothing about time travel this time. And we one more test here. And there we go. Again, not about a time traveler or anything like that. All right, so that is temperature. Now, remember, there's some general guidance here. Whenever we are doing tasks that require less creativity, or whenever we want to have a very deterministic output, we want to use that lower temperature value. And whenever we have a task that requires a little bit more creativity, that's when we want to start to think about dialing up the temperature a little bit.

↑ 目次へ戻る

Building with the Claude API | Lesson 11「Response streaming」(全文ナレッジ)

動画 8分23秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

なぜ streaming が要るのか

  • チャットUIの基本フロー:Webアプリ/モバイルアプリのチャット窓 → ユーザーの質問をサーバーへ → user message にして Claude へ → assistant message が返る → テキストを抽出してアプリへ返し画面表示。
  • 問題は待ち時間。user message 送信から assistant message 完成までは、入出力サイズ次第で 10〜30秒かかり得る。スピナー表示だけでは UX が悪い。ユーザーは「What is quantum computing」と打ったらほぼ即座に何かが表示され始めることを期待している。
  • 解決策が streaming:Claude はリクエスト受領後ほぼ即座に初期レスポンス(テキストを含まない「受け付けた・生成を始める」合図)を返し、その後イベントのストリームが届く。各イベントは生成中メッセージの断片(1語とは限らず複数語〜文になることもある。各断片の生成にかかった時間次第)。サーバーはイベントからテキストを取り出して即座にアプリへ流し、画面にチャンクごとに文字が現れていく。これら全イベントは1つのリクエストの一部

生イベント方式(stream=True

  • client.messages.create(..., stream=True) にすると、最終回答ではなくイベントのイテレータが返る。for event in stream: print(event) で観察できる。
  • 届くイベント列(実測):RawMessageStartEventRawContentBlockStartEventRawContentBlockDeltaEvent ×多数 → RawContentBlockStopEventRawMessageDeltaEventRawMessageStopEvent
  • 最重要イベントは RawContentBlockDeltaEvent。生成テキストの実体(chunk)を含むのはこれ。

Common Events(スライドの表)

Event Type Purpose
RawMessageStartEvent A new message is being sent(新しいメッセージ送出の開始)
RawContentBlockStartEvent Start of a new block, which contains a text message, tool use, or other(テキスト/ツール使用等を含む新ブロックの開始)
RawContentBlockDeltaEvent Chunk related to the latest content block that was started(開始済み最新ブロックのチャンク)
RawContentBlockStopEvent The current content block has been completed(現在のブロック完了)
RawMessageDeltaEvent The current message is complete(現在のメッセージ完了)
RawMessageStopEvent End of information about the current message(メッセージ情報の終端)

SDK の高水準 API(client.messages.stream()

  • 生イベントを自分で type 判定して delta からテキストを抜くのはコードが増える。SDK には別のストリーム作成方法がある:
with client.messages.stream(model=model, max_tokens=1000, messages=messages) as stream:
    for text in stream.text_stream:
        print(text, end="")
  • stream=True 引数は不要。stream.text_stream を回すとテキスト部分だけが順に得られる。
  • ストリーム完了後に stream.get_final_message() を呼ぶと、全イベントを1つの最終 Message に組み立てたものが得られる——会話全体を DB に保存する用途に便利(ストリーミング表示と保存の両立)。

実務ポイント(このレッスンから持ち帰るもの)

  1. チャットUIでは streaming が事実上必須。10〜30秒の無反応はユーザー離脱要因。最初のトークンが見えるまでの時間(TTFT)を縮めるのが streaming の役割。
  2. 通常は client.messages.stream()(context manager)+ text_stream を使う。生イベント(stream=True)を触るのは、tool use など content block 単位の制御が要るときだけで十分。
  3. 表示は text_stream、保存は get_final_message() の2段構え。ストリームを流し終えた後に完全な Message オブジェクト(usage 含む)が手に入るので、DB 記録・トークン集計はそこで行う。
  4. イベント1個=1単語ではない。複数語・文単位で届くことがある UI 前提で実装する(描画は受け取ったチャンクをそのまま追記)。
  5. 既存の chat() ヘルパー(最終テキストを return する設計)は streaming とは相性が悪い——ストリーミング経路は別関数に分けるのが素直。

本文(時系列・日本語逐語+画面差し込み)

[0:00] このセクションの前の方で見た、最初のチャットインターフェースの例に立ち返りたいと思います。思い出してください。考え方はこうでした。Webアプリやモバイルアプリの中でチャットウィンドウが動いている。ユーザーが質問を入力する。それが私たちのサーバーに送信される。私たちはそれを user message に詰めて Claude に送る。すると Claude は assistant message を返してくる。そこからテキストを抽出して、モバイルアプリや Web アプリに送り返す。うまくいけば、そのコンテンツが画面に表示される、と。

🖥 スライド(シーケンス図):左にスマホ風チャットUI(吹き出し「You: What is quantum computing?」「AI: …」、下部に入力欄と Send ボタン)。右に「Our Server」「Claude」の2本のライフライン。「What is quantum computing?」→ Our Server → User Message → Claude、Claude → Assistant Message(「Quantum computing is...」)→ 画面へ。

[0:25] さて、ここまでは実に単純明快に聞こえますが、まだ扱っていない小さな問題が1つあります。user message を Claude に送ってから、最終的に assistant message が返ってくるまでの時間は、思ったよりずっと長くなりがちなのです。場合によっては 10 秒、あるいは 30 秒までかかることもあります。入力の user message と出力の assistant message のサイズ次第です。

🖥 スライド(同図に注記):Claude のライフライン上に赤茶のボックス「Can take 10-30s to generate a full response!(完全な応答の生成に10〜30秒かかることがある!)」。

[0:53] ユーザーが応答を待っているこの間ずっと、画面にスピナーを表示しておくこともできますが、それはあまり良いユーザー体験ではありません。ほとんどのユーザーの期待は、「what is quantum computing」のような最初のメッセージを入力したら、ほぼ即座に画面上に何らかの応答が表示され始めることです。

[1:08] このより良いユーザー体験を得るために、streaming(ストリーミング) という技法を使います。streaming がどう動くのか少しお話ししましょう。サーバーは変わらず最初の user message を Claude に送りますが、Claude はほぼ即座に初期レスポンスを送り返してきます。この初期レスポンスには実際のテキストコンテンツは含まれません。そうではなく、Claude がこちらの最初のリクエストを受け取ったこと、そしてこれからテキストの生成を始めようとしていることの、サーバーへの合図に過ぎません。

[1:33] その後、私たちはイベントのストリームを受け取り始めます。これらのイベントが正確に何なのかは後で詳しく掘り下げますが、今のところは、ユーザーに送り返して最終的に表示したい「生成された応答の断片」を含んでいる、と理解しておいてください。受け取るイベントの数は、生成するテキスト量に依存します。各イベントは、生成中のメッセージ全体のほんの一部だけを含みます。

🖥 スライド「With streaming enabled」:同じシーケンス図。Claude → Our Server に「Initial Response」ボックス、その後「Quantum event」「computing event」「is... event」の矢印が順にアプリまで届く。下部注記「All of these events are parts of a single request(これらのイベントはすべて単一リクエストの一部)」。チャットUI側の AI 吹き出しが「Quantum computing is...」と徐々に埋まっていく。

[1:56] 例えば最初のイベントには「quantum」というテキストだけがあり、2番目は「computing」、3番目は「is」……という具合です。ただし各イベントが1単語だけを含むとは限りません。多くの単語、あるいは丸ごと1文を含むこともあります。それは本当に、Claude が各断片のテキストを生成するのにどれだけ時間がかかったか次第です。

[2:16] さて、申し上げたとおり、サーバーはこれらのイベントを受け取ります。そしてサーバーは任意で、各イベントからテキストを取り出し、即座に Web アプリなりモバイルアプリなりへ送り返すことができます。そこでその小さなテキストの塊を画面に表示できます。サーバーで受け取る追加のイベントごとに、このプロセスを繰り返せます。正味の効果として、ユーザーはチャットインターフェースに、テキストがチャンクごとに現れ始めるのを見ることになります。

[2:40] それではノートブックに戻って、streaming がどう動くのかをより深く理解するために、少しコードを書いていきます。

[2:45] はい、ノートブックの中です。例の3つのヘルパー関数はまだここにあります。ただ、このビデオでは chat 関数は無視します。というのも、streaming を始めると、私たちが実装した形の chat 関数とはあまり相性が良くないからです。代わりにこの下で、メッセージのリストを作り、client.messages.create 関数を手動で呼びます。

[3:03] 空のメッセージリストを作ります。user message を messages に追加して、Claude に「架空のデータベースの1文の説明を書いて」とでも頼みましょう。それから client messages create を呼びます。モデル名を渡します。max_tokens も相変わらず必要です。メッセージのリストを渡します。そして最後に、追加のキーワード引数 stream を True で入れます。

🖥 画面(VS Code / 004_streaming.ipynb):

messages = []

add_user_message(messages, "Write a 1 sentence description of a fake database")

stream = client.messages.create(
    model=model,
    max_tokens=1000,
    messages=messages,
    stream=True
)
for event in stream:
    print(event)

[3:37] これで返ってくるのは最終的な答えではなく、さまざまなイベントのストリームです。これは普通のイテレータなので、順に回せます。for event in stream と書いて、event を print します。

[3:51] さて、これを実行すると、画面上にさまざまなイベントのストリームが一気に流れます。これらの1つ1つが、Claude から送り返されてくるデータの小さな塊を表しています。

🖥 画面(実行結果・セル [8] 2.1s。出力冒頭):

RawMessageStartEvent(message=Message(id='msg_01Mqzv5yuvHDfhm3hmTMpc2c', content=[],
model='claude-3-7-sonnet-20250219', role='assistant', stop_reason=None, stop_sequence=None,
type='message', usage=Usage(cache_creation_input_tokens=0, cache_read_input_tokens=0,
input_tokens=18, output_tokens=2)), type='message_start')
RawContentBlockStartEvent(content_block=TextBlock(citations=None, text='', type='text'),
index=0, type='content_block_start')
RawContentBlockDeltaEvent(delta=TextDelta(text='"Re', type='text_delta'), index=0,
type='content_block_delta')
RawContentBlockDeltaEvent(delta=TextDelta(text='almDB: A cutting-edge, quantum',
type='text_delta'), index=0, type='content_block_delta')
RawContentBlockDeltaEvent(delta=TextDelta(text='-encrypted NoSQL database platform',
type='text_delta'), index=0, type='content_block_delta')
RawContentBlockDeltaEvent(delta=TextDelta(text=' that leverages metacognitive',
type='text_delta'), index=0, type='content_block_delta')
RawContentBlockDeltaEvent(delta=TextDelta(text=' algorithms and temporal sharding to process',
type='text_delta'), index=0, type='content_block_delta')
RawContentBlockDeltaEvent(delta=TextDelta(text=' both past and future data simultaneously
while', type='text_delta'), index=0, type='content_block_delta')
RawContentBlockDeltaEvent(delta=TextDelta(text=' requiring zero maintenance and infinite
scalability."', type='text_delta'), index=0, type='content_block_delta')
RawContentBlockStopEvent(index=0, type='content_block_stop')
RawMessageDeltaEvent(delta=Delta(stop_reason='end_turn', stop_sequence=None),
type='message_delta', usage=MessageDeltaUsage(output_tokens=48))
RawMessageStopEvent(type='message_stop')

[4:01] raw message start event という名前のイベントから始まっていることに気づくでしょう。次に raw content block start、raw content block delta が来ます。実際、それが何個も届きます。そして下の方に行くと、最終的に content block stop イベント、message delta イベント、message stop イベントが届きます。

[4:18] これらはすべて、単一のリクエストという文脈の中で私たちに送り返されてくるイベントで、すべて Claude から来ています。これらの各イベントには、Claude から受け取る応答全体の文脈における何らかの意味があります。ですが、他のどれよりも少しだけ重要視することが多いイベントタイプが1つあります。それが raw content block delta イベントです。このイベントこそ、Claude によって生成され、チャンクごとに送り返されてくる実際のテキストを含むものです。

🖥 スライド「Common Events」(表+右にイベント列の図): - 表:RawMessageStartEvent=A new message is being sent / RawContentBlockStartEvent=Start of a new block, which contains a text message, tool use, or other / RawContentBlockDeltaEvent=Chunk related to the latest content block that was started / RawContentBlockStopEvent=The current content block has been completed / RawMessageDeltaEvent=The current message is complete / RawMessageStopEvent=End of information about the current message - 中央に「Many different types of events get yielded」の注記。右は Claude のライフラインに「Quantum event / computing event / is... event」。

[4:47] 実際のところ、私たちはたいてい同じイベントの並びを何度も受け取ることになります。Claude から応答を受け取るとき、ほぼ必ず message start から始まり、次に content block start、そして content block delta の連続が届きます。繰り返しますが、それが実際のテキストを含むものです。ですから通常は、これらの各イベントを集めてテキストを抽出し、そのテキストを Web アプリなりモバイルアプリなり、使っているものへ送り返したいわけです。

🖥 スライド(イベント順の図):MessageStart → ContentBlockStart → ContentBlockDelta ×3(左に波括弧で「These contain the actual generated text(これらが実際の生成テキストを含む)」)→ ContentBlockStop → MessageDelta → MessageStop。

[5:12] さて、ノートブックに戻って、この for ループの中に、いま扱っているのがどの種類のイベントなのかを調べるチェックを追加することもできます。そして、それが例の raw content block delta イベントの1つだったら、その中に手を伸ばして、本当に欲しいテキストを取り出す。しかしそれには余分なコードがかなり必要になります。

[5:28] ありがたいことに、Anthropic SDK は、いまお見せしているものとは別のストリーム作成方法を公開しています。この代替のストリーム作成方法——すぐお見せします——を使うと、応答からテキストだけを取り出すのがずっと簡単になります。繰り返しますが、テキストは通常、応答の中で私たちが本当に関心のある部分です。

[5:45] では、応答をストリーミングする別の方法をお見せしましょう。次のコードセルに降ります。ここでもう一度、メッセージのリストを作り、「write a one sentence description of a fake database」という user message を追加します。そして少し違う関数を呼び、with ブロックで包みます。with client.messages.stream と書きます。

[6:14] この中に、再びモデル、max_tokens、messages を入れます。ただし stream=True 引数を追加する必要はありません。それから as stream: と書きます。インデントして、この中で for text in stream.text_stream と、このように書きます。

🖥 画面(VS Code / 新しいセル):

messages = []

add_user_message(messages, "Write a 1 sentence description of a fake database")

with client.messages.stream(
    model=model,
    max_tokens=1000,
    messages=messages
) as stream:
    for text in stream.text_stream:
        print(text, end="")

[6:38] これで text は、あの各種イベントのテキスト部分だけになります。つまり本当に欲しいテキストだけ。繰り返しますが、Claude から応答をストリーミングするとき、実際に本当に関心があるのはほぼ常にそれです。

[6:49] これが実際どう動くかをお見せするために、print 文を追加してそのテキストをログに出します。end に空文字列を渡しておきます(※ASR原文は「end true of empty string」と重複して聞こえるが、実コードは end="")。この print 文が末尾に改行文字を追加しないようにするためです。そうすれば、テキストの各断片が隣り合って表示されます。

[7:06] では実行します。すごく速く進みますが、応答がチャンクごとにストリームで返ってきているのが分かるはずです。もう一度やりましょう。再実行すると、チャンク、チャンク、チャンクと見えますね。ほら。各チャンクが複数の異なる単語を含んでいることに気づくでしょう。つまり、各イベントの中に単語が1つだけ返ってくる保証はありません。複数入っていることもあります。

🖥 画面(実行結果・セル [9] 1.8s):

The UnicornVault is an imaginary database that claims to securely store magical coordinates,
mythical creature blueprints, and interdimensional travel logs using quantum encryption
powered by pixie dust.

[7:26] 最後にもう1つお見せしたい機能があります。申し上げたとおり、私たちはモバイルアプリや Web アプリに応答をストリーミングで返したいことがとても多い。ユーザーが各テキストチャンクをできるだけ早く画面で見られるようにするためです。しかし、ストリーム完了後にやりたいことがもう1つよくあります。それは、メッセージ全体を取り出して、データベースに保存しておくことです。特定のユーザーと交わされた会話全体の記録を持てるように。

[7:50] これらの各イベントを収集して、1つの最終メッセージとして組み立てた形で提示する方法をお見せしましょう。ここの print 文を pass に置き換えて、何も print しないようにします。そしてその後に stream.get_final_message() を実行します。

🖥 画面(VS Code / 編集後のセル):

with client.messages.stream(
    model=model,
    max_tokens=1000,
    messages=messages
) as stream:
    for text in stream.text_stream:
        # print(text, end="")
        pass

stream.get_final_message()

[8:08] もう一度実行すると、依然として応答はストリーミングで返ってきていて、望めば print もできますが、同時に、受け取った個々のイベントをすべて集めて1つの最終メッセージに組み立てることもできます。それをデータベースに保存するなり、他に必要なことに使うなりできるのです。

🖥 画面(実行結果・セル [18] 1.7s):

Message(id='msg_01SV4Sv2rJKVXTBHpYrZBoRw', content=[TextBlock(citations=None, text='A
comprehensive virtual repository called "ByteSync" seamlessly integrates non-existent data
across illusory platforms while maintaining the appearance of robust functionality despite
being entirely fabricated.', type='text')], model='claude-3-7-sonnet-20250219',
role='assistant', stop_reason='end_turn', stop_sequence=None, type='message',
usage=Usage(cache_creation_input_tokens=0, cache_read_input_tokens=0, input_tokens=18,
output_tokens=40))

英語逐語(ASR全文)

I want to think back to the original chat interface example that we looked at earlier on inside this section. So remember the thought process here is that we've got a chat window running inside of a web app or a mobile app. A user is going to enter a question. That's going to be submitted to our server. We're then going to stuff that into a user message and send it off to Claude. Claude is then going to send us back an assistant message. We are going to extract the text from it and send it back down to our mobile app or web app. And hopefully that content is going to appear on the screen. Now, this all sounds pretty straightforward and easy at this point in time, but there's one little issue here that we haven't really addressed just yet. You see the time between sending that user message to Claude and then eventually getting an assistant message back can very easily take a lot more time than we expect. In some cases, it might take 10 seconds or all the way up to 30 seconds, depending upon the size of the input user message and the output assistant message. Now, during this entire time that the user is waiting for a response, we could just show a spinner on the screen, but that's not really a great user experience. Most users' expectations are that whenever they enter in some kind of initial message, like what is quantum computing, they should almost immediately start to see some response up here on the screen. To get this better user experience, we are going to use a technique known as streaming. So let me tell you a little bit about how streaming works. Our server is still going to send an initial user message off to Claude, but then Claude is going to almost immediately send back an initial response to us. This initial response doesn't actually contain any text content. Instead, it is really just a sign to our server that Claude has received our initial request and that Claude is about to start to generate some amount of text. We are then going to start to receive a stream of events. We're going to go into a lot of detail around exactly what these events are, but for right now, just understand that they contain pieces of the generated response that we want to send back and eventually display to our users. The number of events that we receive depends upon how much text we are generating. Each event is going to contain just a little bit of the overall message that is being generated. So maybe the first event just has the text quantum, and then the second one says computing, and the third one is, and so on. Now each event doesn't just contain one word, it might contain many words, or even an entire sentence. It really just depends upon how much time it takes for Claude to generate each little bit of text. Now as I mentioned, our server is going to receive these events, and our server can optionally take the text out of each event, and immediately send it back down to our web app, or mobile app, or whatever else, where we can display that little chunk of text on the screen. We can then repeat this process for each additional event that we receive on our server. So the net effect is that a user is going to start to see some text start to appear chunk by chunk in the chat interface. Let's now go back over to our notebook and we're going to write out a little bit of code to better understand how streaming works. All right, so back inside of my notebook, I still have these three helper functions put together. Now we are going to ignore the chat function inside of this video because when we start to streaming doesn't work quite so well with the chat function as we have implemented it. So I'm going to instead down here, make a list of messages and then manually call the client messages dot create function. So I'm going to make an empty list of messages. I'll add in a user message to messages, and I'm going to ask Claude to maybe write a one sentence description of a fake database. I'm then going to call client messages, create. I will pass in the name of the model. Still need to provide a max tokens. Provide our list of messages. And then finally, we'll put in an additional keyword argument here of stream is true. This is going to give us back not a final answer, but instead we're going to get a stream of different events. We can iterate through this thing because it is a normal iterator. So we can say for event in stream and then print out event. Now, if I run this, we're going to very quickly see a stream of different events up here on screen. So each of these represents a different little chunk of data that is being sent back to us by Claude. You'll notice that we start off with a event named raw message start event. We then get a raw content block start, a raw content block delta. We get several of those as a matter of fact. And then down towards the bottom, we eventually get a content block stop event, a message delta event, and a message stop event. So these are all events that are being sent back to us inside of the context of a single request, all coming from Claude. Each of these different events has some meaning in the context of the overall response that we are getting back from Claude. However, there is one event type that we usually care about a little bit more than all the others, and that is the raw content block delta event. This event is what contains the actual text that is being generated by Claude and sent back to us chunk by chunk. In practice, we usually end up getting the same sequence of events over and over. So when we get a response back from Claude, we're almost always going to start off with getting a message start, then a content block start, and then we are going to get a sequence of content block deltas. And again, those are what contain the actual text. So we usually want to collect all those different events and extract the text from them and send that text back down to our web app or mobile app or whatever else we are using. Now, back inside of our notebook, inside this for loop right here, we could add in a check to take a look and figure out what kind of event we are dealing with. And then if it is one of those raw content block delta events, we could reach into it and get the text we actually care about. But that would require a lot of extra code from us. Thankfully, the Anthropic SDK exposes a different way of creating a stream than what I'm showing to you right here. This alternative way of creating a stream, which I'm going to show you in just one moment, makes it a lot easier to just get the text out of the response. And again, the text is usually the part of the response that we really care about. So let me show you an alternative way of streaming a response. I'm going to go down to the next code cell. So down here, I'm going to again, make a list of messages, add a user message with a write a one sentence description of a fake database. And then we're going to call a slightly different function and wrap it inside of a with block. So we'll say with client messages dot stream. And inside of here, we will again put in our model, our max tokens and our messages. But we do not need to add in the stream to true argument. We'll then say as stream colon. I'll then indent. And inside of here, it will say for text in stream dot text stream like so. So now text is going to be just the text part of those different events. So just the text we actually care about. Again, that's almost always the thing that we actually really care about when we are streaming a response from Claude. Now to show you how this actually works, I'm going to add in a print statement and log out that text. I'm going to add in a end true of empty string and true of empty string. Just make sure that these print statements are not going to add in a new line character to the end of the print statement. So we'll see each bit of text logged out next to each other. Now I'm going to run this and it's going to occur really quickly, but you'll see that now we are getting a response streamed back to us chunk by chunk. So let me do that again. So run again, I'll see chunk, chunk, chunk. There we go. You'll notice that each chunk contains multiple different words. So again, we're not guaranteed to just get back one single word inside of each event. We might get several. There is one last feature here that I want to show you. Now, as I've mentioned, we very often want to stream a response back to a mobile app or a web app. So at that, a user can see each chunk of text appear on the screen as soon as possible. But something else we very often want to do after completing a stream is take the entire message and maybe store it inside of a database. So we have a record of the entire conversation that was had with a particular user, let me show you how we can collect all these different events and present them all assembled together inside of one single final message. I'm going to replace this print statement right here with a pass, just so we don't have any printing there. And then after it, I'll do a stream dot get final message. And now if I run this again, we are still streaming back a response and we could print it if we wanted to, but we're also going to take all the individual events we get back and assemble them together into one final message, which we could then store inside the database or do whatever else we need to do with it.

↑ 目次へ戻る

Building with the Claude API | Lesson 12「Structured data」(全文ナレッジ)

動画 5分59秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04

日本語ナレッジ(要点整理)

stop sequences × assistant prefill = 構造化データ生成の定番コンボ

  • stop sequence と assistant message prefilling は組み合わせると非常に強力。構造化データ(structured data)を生成する場面では頻繁に使うことになる。
  • 題材:ユーザー入力から AWS EventBridge ルール(実体は小さな JSON スニペット)を生成する Web アプリ。UX の要件は「生成されたルールの JSON だけを表示し、コピーボタンでそのまま使える」こと。
  • Claude は放っておくと、生成物の前後にヘッダー・フッター・解説コメントを付けたがる(「仕事を説明したがる」性質)。これが付くと「Copy all」ボタンが使えず、ユーザーが手動で JSON 部分を選択する羽目になる。
  • この問題は JSON に限らない。JSON / Python / 箇条書きリストなど、あらゆる構造化データ生成で起きる。欲しいのは「頼んだ生コンテンツだけ」。

手法(ノートブック実装)

  1. 素のリクエスト add_user_message(messages, "Generate a very short event bridge rule as json") → 返答は ```json と閉じ ``` に包まれる(Markdown 整形用のバッククォート)。
  2. prefilladd_assistant_message(messages, "```json") — Claude に「もう JSON ブロックは書き始めた」と思わせる。
  3. stop sequencechat(messages, stop_sequences=["```"]) — 閉じフェンスを書いた瞬間に生成を停止。
  4. 結果は前後に改行が残る生 JSON。json.loads(text.strip()).strip() で除去すれば完全な JSON として扱える。

内部で起きていること(ダイアグラム)

  • Claude はリクエスト全体(user message+prefilled assistant+stop sequences)を見る。
  • まず user message から「完全なルールを書いて、説明も付けるべき(ヘッダー・フッター)」と自然に考える。
  • しかし prefill された assistant message に出会うと、「それは既に自分が書いた」と仮定する →「JSON パートはもう始めてある。あとは中身の JSON を書くだけ」。
  • JSON を書き終えると、先に開いた(と思っている)Markdown コードブロックを閉じようとして ``` を出力 → その瞬間 stop sequence に引っかかり生成が完全停止、レスポンスが返る。
  • 要するに「これで始めて、これで終われ。間のものだけよこせ」と指定しているのと同じ。

実務ポイント(このレッスンから持ち帰るもの)

  1. 構造化データ生成の定石=「prefill で開始位置を固定」+「stop sequence で終了位置を固定」。この2点で前置き・後書き・解説を物理的に排除できる。
  2. prefill は ```json のような開始トークンにすると、Claude は閉じフェンスで自然に終わろうとするので、stop sequence ``` と綺麗に噛み合う。
  3. 取り出したテキストには改行が残る前提で、json.loads(text.strip()) まで含めてパイプラインにする(パースが通る=構造検証も兼ねる)。
  4. JSON 専用ではない。Python コード・箇条書き・CSV 等どんな構造化出力にも同じ型が効く。
  5. UI にそのまま流し込む出力(コピー機能付きの生成結果など)では、この技法を使って「余計な親切」を消すことがUX 要件そのものになる。

本文(時系列・日本語逐語+画面差し込み)

[0:00] stop sequence と assistant message prefilling は、組み合わせることで本当に強力な使い方ができます。何らかの構造化データを生成する必要があるときはいつでも、おそらくかなり頻繁にやることになるものです。これらがどう連携するのかを理解してもらうために、ごく簡単な例を一つ通してみましょう。

[0:15] 画面に見えているような Web アプリを作っていると想像してください。これは、ユーザー入力に基づいて EventBridge ルールを生成する Web アプリです。馴染みがない方のために言うと、EventBridge ルールは AWS で使われるもので、本質的には小さな JSON スニペットです。ユーザーはこんな感じのプロンプトを入力して、generate をクリックします。

🖥 スライド(Web アプリのモック):ブラウザ風フレームに「EventBridge Rule Generator」。入力欄に「Rule to monitor EC2 instances」、右下に「Generate」ボタン。その下に黒い出力パネル(右上にコピーボタンのアイコン)。

[0:33] そしておそらくユーザーは、生成されたルールがこの上の方に表示されて、すぐに選択できたり、この小さなコピーボタンをクリックして別の場所で使えたりすることを望むでしょう。ここでのポイントは、重要なユーザー体験の一部として、生成されたルールの JSON だけを表示して、それ以外は何も表示したくないということです。

[0:52] もし代わりにこんな見た目のレスポンスを表示したら、ユーザーにとって間違いなく役に立ちにくいものになります。ルール自体は生成されていますが、今度は上部にこのヘッダーが、下部にこの解説フッターが付いています。こうなるとユーザーはこの「copy all」ボタンを実質使えません。中に入って、そこの JSON を手動で選択しなければならなくなります。

🖥 スライド(悪い例):同じアプリの出力パネルに

# EventBridge Rule
```json
{
  "source": ["aws.ec2"],
  "detail-type": ["EC2 Instance State-change
Notification"],
  "detail": {
    "state": ["running", "stopped"]
  }
}

This rule captures EC2 instance state changes when instances start running or stop.

(ヘッダー「# EventBridge Rule」と末尾の解説文が余計に付いている)

**[1:10]** つまりこれは、Claude に「そこまで親切に仕事を説明してほしくない」例です。欲しいのは非常に特定のデータだけで、それ以外は要りません。

**[1:19]** はっきりさせておくと、これは JSON 生成に限った問題ではありません。実は、Claude で何らかの構造化データ——JSON でも、Python でも、単なるテキスト項目の箇条書きでも——を生成するときはいつでも、Claude はヘッダーやフッター、何かしらの追加の解説を挿入しようとすることが非常に多いのです。そして多くのシナリオでは、その追加の解説は不要です。Claude に作ってと頼んだ生のコンテンツだけが欲しいのです。

🖥 スライド:左に大きく「**In each case, we want only the content we're asking for and nothing else.**(どのケースでも、欲しいのは頼んだコンテンツだけで、それ以外は何も要らない)」。右に Claude のライフライン図:「Generate a user profile as JSON」→「**JSON**」、「Generate a bulleted list of tasks」→「**Bulleted list of tasks**」、「Generate Python for data analysis」→「**Python**」。

**[1:45]** そこで、Claude を軌道に乗せ、頼んでいる生コンテンツだけを返させて、追加のヘッダーもフッターも解説も一切付けさせないために、**stop sequence を prefill した assistant message と組み合わせて**使えます。やり方をお見せしましょう。

**[2:01]** ノートブックに戻ります。この下に新しいセルを作って続けます。もう一度メッセージのリストを作ります。user message を追加して、「generate a very short event bridge rule as JSON(ごく短い EventBridge ルールを JSON で生成して)」のように言います。それをこのように渡して、まずはこの最初の素の状態で何が返るか見てみましょう。

🖥 画面(VS Code / `004_Controlling_Output.ipynb`。上のセルには前レッスンの `add_assistant_message(messages, "They are the same because")` や `chat(messages, stop_sequences=["5", "3, 4"])` → 出力 `'1, 2, '` が残っている)。新しいセル:
```python
messages = []

add_user_message(messages, "Generate a very short event bridge rule as json")

chat(messages)

[2:26] すぐに分かるとおり、JSON は返ってきていますが、残念ながらあの小さなバッククォート×3+json がそこに付いていて、対応する閉じが向こうにあります。念のためはっきりさせておくと、これらのバッククォートは全体を Markdown として整形するためのものです。Markdown テキストとしてレンダリングすれば、とても綺麗に整形されます。

🖥 画面(実行結果・セル [11] 1.3s):

'```json\n{\n  "source": ["aws.ec2"],\n  "detail-type": ["EC2 Instance State-change Notification"],\n  "detail": {\n    "state": ["running"]\n  }\n}\n```'

(先頭の ```json と末尾の ``` がハイライトされる)

[2:46] しかし私たちのケースでは、そうした追加の文字は一切欲しくありません。生の JSON 単体だけが欲しいのです。そのためには2つのことをやります。assistant message と stop sequence の両方を使います。今はまずそのためのコードを書き出して、その後で全体がどう動くかを説明するダイアグラムをお見せします。

[3:03] まず、assistant message を prefill します。add assistant message と書いて、prefill するメッセージは「バッククォート、バッククォート、バッククォート、json」にします。そして chat 呼び出しに stop sequences を追加します。バッククォート×3 を見かけたら、即座に生成を停止してほしい、と。

🖥 画面(セル編集後):

messages = []

add_user_message(messages, "Generate a very short event bridge rule as json")
add_assistant_message(messages, "```json")

text = chat(messages, stop_sequences=["```"])

[3:26] それではセルを実行して、何が返るか見てみましょう。はい、これで JSON 単体だけが返るようになりました。中に改行文字がいくつか入っているのに気づくでしょうが、まったく問題ありません。レスポンスを JSON としてパースするか、strip を呼ぶだけで、この余分な改行は簡単に除去できます。

🖥 画面(実行結果・セル [12] 1.5s):

'\n{\n  "source": ["aws.ec2"],\n  "detail-type": ["EC2 Instance State-change Notification"],\n  "detail": {\n    "state": ["running"]\n  }\n}\n'

[3:43] そこで、text は chat の結果として、text を出力します。そしてその下のセルで、json を import して、text に対して JSON loads を、あわせて text に strip も行います。これを実行すると——はい、期待どおりあらゆる方法でアクセスできる、非常に綺麗に整形された JSON が確かに返ってきます。

🖥 画面(セル [13] 2.0s と [14] 0.0s):

messages = []

add_user_message(messages, "Generate a very short event bridge rule as json")
add_assistant_message(messages, "```json")

text = chat(messages, stop_sequences=["```"])
text
import json

json.loads(text.strip())

出力:

{'source': ['aws.ec2'],
 'detail-type': ['EC2 Instance State-change Notification'],
 'detail': {'state': ['running']}}

[4:05] さて、assistant message と stop sequence では正確には何が起きているのでしょうか。分解して超明確にするために、ダイアグラムをお見せします。もう一度、user message を出し、prefill した assistant と stop sequence を提供しています。Claude はこのリクエストのさまざまな部分すべてに目を通します。最初に user message の内容を見て、こう言います。「よし、完全なルールを書く必要があるのは明白だ。おそらく説明も付けるべきだろう。だからヘッダーとフッターを付けようか」——それが Claude が自然にやりたがることだからです。自分のやっている仕事を説明したいのです。

🖥 スライド(ダイアグラム):左にリクエストの表——「Stop Sequences["```"]」「User Message:Generate an Event Bridge rule as JSON」「Assistant Message```json(注記 We put this in to guide Claude's response=Claude の応答を導くために入れる)」、下段に返ってくる「Assistant Message:…」。右に Claude ボックスと吹き出し:①「OK, I need to write a full rule and describe it(よし、完全なルールを書いて説明も付けなきゃ)」②「Oh, I've already started the JSON part!(あっ、JSON パートはもう始めてた!)」③「OK, all done with the json, I'll close it with (json は書き終わった、 で閉じよう)」。

[4:37] しかしその後、あの assistant message に出会います。前回のビデオで学んだとおり、Claude は自分のレスポンスの中でそれを既に書き出したものと仮定します。つまり「ああ、JSON パートはもう始めてあるんだ。なら、あとやるべきことは実際の JSON を書き出すだけだ」と言うわけです。そしてこの JSON 全部をレスポンスとして書き出していきます。

[4:59] そして最後の最後に差しかかると、先ほど自分が作った(と思い込んでいる)Markdown コードブロックを自然に閉じたくなります。そこで Claude は閉じのバッククォート×3 を入れようとします。しかしそれを入れた途端、stop sequence に遭遇します。これが生成を完全に停止させ、即座にレスポンスを私たちに送り返します。

[5:18] つまり、ここで実際に起きているのは、こう言っているようなものだと想像できます。「これで始めて、あれで終われ。そして、その間にあるものを全部よこせ」と。その結果、本当に欲しい部分——JSON 単体——だけが返ってくるのです。

[5:33] そして申し上げたとおり、これは今後非常によく使うことになる本当に強力なテクニックです。何らかの構造化データを生成して、そのデータだけを、他に何も付けずに得たいときにはいつでも使います。このテクニックはあらゆる種類の構造化データに使えることを覚えておいてください。JSON への使用に限定されるものではありません。生成したい非常に特定のコンテンツがあり、追加の解説なしでそのコンテンツだけを得たいときはいつでも、assistant message prefilling と stop sequences の併用を検討していきます。

英語逐語(ASR全文)

Stop sequences and assistant message prefilling can be combined together in a really powerful way. Something that you're probably going to end up doing rather frequently anytime you need to generate some kind of structured data. So to help you understand how these things work together, let me walk you through a really quick example. Let's imagine that we are building a web app like the one you see on the screen. This is a web app that's going to generate event bridge rules based upon some user input. If you're not familiar with them, event bridge rules are used in AWS. They're essentially little JSON snippets. So user is going to enter in some prompt like this and then click on generate. And chances are the user is going to want to see some generated rule just up here that they can very easily select right away or click on this little copy button and go use somewhere else. The point there is that part of the critical user experience here is that we want to show just the JSON for the generated rule and nothing else. So if we instead displayed a response that looks like this, it would definitely not be as helpful for our users. We're still generating the rule, but now it also has this header up top and this commentary footer down at the bottom. So now a user can't really use this copy all button. They would have to go in and manually select that JSON right there. So this is an example where we really don't want Claude to be that helpful and explain its work. We want just some very particular data and nothing else. Now, to be clear, this is not a problem that is just limited to generating JSON. It turns out that anytime you are using Claude to generate any kind of structured data, so it could be JSON, or it could be Python, or even just a bulleted list of text items, Claude is very often going to try to insert a header or a footer or some additional kind of commentary. And in many of these scenarios, you don't want that additional commentary. You just want the raw content that you asked Claude to create. So to help keep Claude on track here and only give us the raw content we're asking for and no additional header or footer or commentary or anything like that, we can use our stop sequence in combination with a pre-filled assistant message. Let me show you how. I'm going to go back over to my notebook. I'm going to continue on by making a new cell down here. I'm going to again make a list of messages. I'll add in a user message. and I'll say something like generate a very short event ridge rule as JSON. I'll then pass that off like so and then let's just see what we get with this kind of initial take. So right away we can see that we do get back some JSON but it has unfortunately that little back tick back back tick back tick JSON right there and then a matching closing one over there. And just to make sure it's super clear, these backticks are in place to format this all as Markdown. So it gets formatted very nicely if you were to render it as Markdown text. But in our case, we don't want any of those additional characters. We want just the raw JSON by itself. So to do so, we can do two things. We're going to use both an assistant message and a stop sequence. For right now, we're just going to write out the code to do so. And then I'll show you a diagram that explains how it all works. First, I'm going to pre-fill an assistant message. So let's say add assistant message. And my pre-filled message will be backtick, backtick, backtick, JSON. And then on my chat call, I'll add in stop sequences. And anytime we see a backtick, backtick, backtick, I want to immediately stop generation. So let's now run the cell and see what we get back. Okay, so now we get just the JSON by itself. You will notice that there are some new line characters in here, but that's totally fine. We can very easily remove those extra new lines by just parsing the response as JSON or by doing a strip call. So I could say text is chat. I'll print out text. And then on the next cell down, I might import JSON. And do a JSON loads with text and strip on text as well. And if I run that, yes, we definitely get back some very well formatted JSON here that we can access in any way that we expect. All right. So what exactly is going on with the assistant message and the stop sequence? Well, let me show you a diagram just to break it all down and make sure it is super clear. So once again, we are doing our user message. We're providing a prefilled assistant and a stop sequence. Claude is going to take a look at all the different parts of this request. It's going to initially take a look at that user message content and say, all right, It's very clear that I need to write a full rule and I should probably also describe it. So maybe put on a header and a footer because that's kind of what Claude naturally wants to do. It wants to explain the work that is doing, but then it's going to encounter that assistant message. And just as we learned in the last video, Claude is going to assume that it already wrote that out in its response. So it's going to say, oh, I've already started the JSON part. So now all I have to do is write out the actual JSON. It's then going to write out all of this JSON and the response. And then as it gets to the very end, it's going to naturally want to close off that markdown code block that it thought it created earlier. So Claude is going to want to put in a closing backtick, backtick, backtick. As soon as it does so, however, it's going to encounter the stop sequence, which stops the generation entirely and immediately sends us back the response. So you can really imagine that what's really going on here is we're kind of saying, start with this and with that, and just give us everything in between. And that results in us just getting back the part we really care about just the JSON by itself. And like I mentioned, this is a really powerful technique that we're going to use very often. Anytime we want to generate some kind of structured data and get just that data with nothing else besides it. And remember this technique can be used for any kind of structured data. It is not limited just being used on JSON. So anytime we have any kind of very specific content we want to generate and get just that content with no additional commentary on it. We're going to take a look at using assistant message, pre-filling along with stop sequences.

↑ 目次へ戻る

Building with the Claude API | Lesson 13「Structured data exercise」(全文ナレッジ)

動画 4分56秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04

日本語ナレッジ(要点整理)

演習の課題

  • 前レッスン(stop sequences × prefill)の理解確認演習。プロンプトは「Generate three different sample AWS CLI commands. Each should be very short.」で固定。
  • 素で実行すると、3コマンドは返るがヘッダー+番号付きの解説まみれの出力になる。
  • 課題:message prefilling と stop sequences だけを使い(プロンプト変更は禁止)、3コマンドを1レスポンスに、解説・コメント一切なしで並べて取得する。
  • ヒント:prefill は ``` のような記号に限らず、任意の文章を入れて回答の方向づけができる

解法の反復プロセス(実際に3段階かかる)

  1. 第1手:素の出力を観察 → 各コマンドが ``` で包まれている → prefill "```"+stop_sequences ["```"] を追加。 - 結果:3コマンドは出るが、先頭に bash という単語が混入。理由=Markdown コードフェンスには言語識別子(```bash)を付けられるため、Claude がシンタックスハイライト指定として書き足した。
  2. 第2手:prefill を "```bash" に変更し、言語識別子をこちら側で書いてしまうbash 混入は解消。 - ただし残る問題が2つ:(a) 1コマンドしか返らないことがある(Claude が3つの別々のコードブロックに分けたがるサイン)、(b) bash ブロックなので # this command does XYZ のような bash コメントを挟んでくることがある。
  3. 第3手(ヒントの活用):prefill を文章にする——"Here are all three commands in a single block without any comments:\n"(コロン+改行で、次行から Markdown 部分が始まるようにする)→ 安定して3コマンド・コメントなしの出力が得られる。何回実行しても期待どおり。

この演習が教える本質

  • prefill は「開始文字の固定」だけでなく、「Claude 自身の宣言文」を先に書かせておくことで出力仕様を強制する手段。「〜を1ブロックで・コメントなしで出します:」と Claude の口で言わせれば、その後の生成はそれに従う。
  • 出力が不安定なときは「観察 → 仮説(Claude が何をしようとしているか)→ prefill/stop の調整」を繰り返す。

実務ポイント(このレッスンから持ち帰るもの)

  1. まず素の出力を観察してから prefill を設計する。Claude が自然に選ぶフォーマット(フェンス・言語識別子・番号付け)を見て、その「開始部分」を prefill で先取りするのが第一歩。
  2. 言語識別子は自分で prefill に含める```bash)。さもないと Claude が識別子を書き足して出力先頭に混入する。
  3. それでも乱れる場合は、prefill を宣言文に格上げする:「Here are all three commands in a single block without any comments:+改行」のように、出力仕様を Claude の一人称で先に言い切らせる。これが信頼性を劇的に上げる。
  4. 信頼性は連続実行で確認する。1回通っただけで完成とせず、何度か回して同じ形が出ることを見る(次章の eval 思想への伏線)。
  5. プロンプト本文を変えられない制約下でも、prefill+stop sequences だけで出力形式は制御できる——API 側パラメータの引き出しとして覚えておく。

本文(時系列・日本語逐語+画面差し込み)

[0:00] stop sequence と message prefilling の考え方が完全にクリアになっているか確認するために、ごく短い演習をやりましょう。この演習では、画面のここにあるとおりのコードを書き出してほしいのです。このコードはどれも見慣れたもののはずです。プロンプトを見ると、「generate three different sample AWS CLI commands(3つの異なるサンプル AWS CLI コマンドを生成して)」とあります。

🖥 画面(VS Code / 004_Controlling_Output.ipynb・「Exercise!」セクション)。Markdown セルの箇条書き: - Use message prefilling and stop sequences only to get three different commands in a single response - There shouldn't be any comments or explanation - Hint: message prefilling isn't limited to just characters like ```

コードセル([16] 1.8s):

messages = []

prompt = """
Generate three different sample AWS CLI commands. Each should be very short.
"""

add_user_message(messages, prompt)

text = chat(messages)
text.strip()

出力(冒頭):'Here are three different sample AWS CLI commands, each designed to be very short:\n\n1. List ...'

[0:17] さて、このコードを実行すると、おそらくここにあるのとだいたい似た出力が返ってきます。少し読みやすくするために、下で Markdown としてレンダリングしました。これが私の得たサンプルの初期出力です。3つの異なるサンプルコマンドを返してくれてはいるものの、その周りに大量の解説が付いているのが分かるでしょう。ヘッダーがあり、それから各コマンドを列挙する番号があります。

🖥 画面(レンダリング例・別実行 [19] 2.5s の出力):

'Here are three different sample AWS CLI commands, each kept very short:\n\n1. List S3
buckets:\n```\naws s3 ls\n```\n\n2. Describe EC2 instances:\n```\naws ec2 describe-
instances\n```\n\n3. Get IAM users:\n```\naws iam list-users\n```'

下のセル:

from IPython.display import Markdown

Markdown(text)

(レンダリング結果:見出し文+「1. List S3 buckets: aws s3 ls」「2. Describe EC2 instances: aws ec2 describe-instances」「3. List IAM users: aws iam list-users」が番号付きで表示される)

[0:39] この演習では、このコードを使って、message prefilling と stop sequences だけを使い、3つの異なるコマンド全部を1つのレスポンスで、余計なコメントや説明などを一切付けずに、すべて隣り合わせで取得してほしいのです。そしてこれを message prefilling と stop sequences のみでやってください。つまり、このプロンプトの調整は一切なしです。

[1:03] さあ、挑戦してみてください。小さなヒントを載せておきました。念のため思い出してほしいのですが、message prefilling はバッククォート×3 のような文字の使用に限定されません。どんな prefill レスポンスでも入れられます。ここでビデオを一時停止して、この演習に挑戦することをおすすめします。そうでなければ、すぐに解答を説明していきます。

[1:22] では行きましょう。こう解いていきます。これを解くには、まず prefilling も stop sequences も何もない状態の出力をよく見ることをおすすめします。ここにあるものを見ると、3つのコマンドのそれぞれが3連バッククォートの並びで包まれているのに気づくでしょう。

[1:39] ということは、手始めに良さそうなのは、3連バッククォートの prefill 済み assistant message を入れて、Claude に「最初の解説はスキップして、いきなりコマンドを書き始めろ」と伝えることでしょう。そしてさらに、3連バッククォートの stop sequence も入れることにするかもしれません。それでどこまで行けるか見てみましょう。

[1:56] add assistant message を入れて、すべてを3連バッククォートで始めます。そして閉じの3連バッククォートの stop sequences を入れます。実行して、どこまで行けるか見てみましょう。

🖥 画面(セル [47] 9.7s):

messages = []

prompt = """
Generate three different sample AWS CLI commands. Each should be very short.
"""

add_user_message(messages, prompt)
add_assistant_message(messages, "```")

text = chat(messages, stop_sequences=["```"])
text.strip()

出力:'bash\naws s3 ls\naws ec2 describe-instances\naws lambda list-functions'

[2:13] 最初の出力はそれなりに妥当に見えますが、まだ完璧ではありません。コマンドは3つ得られています。1、2、3とあります。しかし、先頭に bash という単語も付いてきているのに気づくでしょう。この単語は一体どこから来ているのでしょうか?

[2:29] Claude がここで本当は何をしようとしているのかお見せしましょう。私たちはあの3連バッククォートの最初の assistant message を提供しました。3連バッククォートを置くというのは、何らかの Markdown を書いていることを示すようなものです。そしてバッククォートで Markdown のコードブロックを書くとき、ちょうどここに、言語識別子を任意で入れることができます。入れることを選ぶと、これを Markdown としてレンダリングするときに、バッククォート内のコンテンツがその言語のシンタックスハイライトでレンダリングされます。

🖥 画面(説明用の Markdown セル):

```bash
aws s3 ls
aws ec2 describe-instances
aws lambda list-functions
```

bash の部分がハイライトされ、「言語識別子」の位置を指し示す)

[2:53] つまりこの場合、Claude は「この内容をレンダリングするときは bash スタイルのシンタックスハイライトを使うべきだよ」と言うためだけに、ここに bash を入れることにしたのです。私たちとしては、それはまったく要りません。これに対処する一つの方法は、ここの prefill 済みメッセージを調整して、bash を自分たちで含めてしまうことです。こうすれば Claude 自身に対して「そう、君は Markdown コードブロックの中にいて、このコードブロックの中では bash 形式のコマンドを書き出すべきだ」ということが超明確になります。

🖥 画面(セル修正):

add_assistant_message(messages, "```bash")

[3:24] ではもう一度実行して、今度はどうなるか見てみましょう。よし、良くなりましたね。

🖥 画面(セル [49] 0.7s・実行結果):出力 'aws s3 ls' — コマンドが1つしか返っていない(次に説明される「追加の問題」の実例)。

[3:30] さて、ここから先、対処したくなるであろう追加のエラーがおそらく2つあります。1つ目は、時々1つのコマンドだけが返ってくることです。これは Claude が3つの別々の Markdown コードブロックを書きたがっているかもしれないという、一種のサインです。もう1つ遭遇しうる問題は、今や bash コードブロックを使っているので、Claude がその中に bash 形式のコメントも挿入しようとするかもしれないことです。「this command does XYZ(このコマンドは XYZ をする)」のような感じで、それが繰り返し現れるのを見るかもしれません。

[3:58] このコメントは絶対に要りません。それがこの演習の要件の1つだったから、というだけの理由でもね。そこで、このコメントを取り除き、さらに3つのコマンド全部をより確実に得るために、私が出したあのヒントが使えます。ヒントは「message prefilling はバッククォートのような文字の指定に限定されない」でした。message prefill を使って、Claude がどう答えるかを劇的に方向づけることもできるのです。

[4:24] この場合、たとえばこんなものを追加できます。「here are all three commands in a single block without any comments(コメント一切なしの単一ブロックに、3つのコマンド全部を示します)」。そしてちょうどそこにコロンを置き、その後に改行を入れます。Markdown の部分がすべて次の行から始まるようにするためです。

🖥 画面(セル [50] 実行中 0.3s):

messages = []

prompt = """
Generate three different sample AWS CLI commands. Each should be very short.
"""

add_user_message(messages, prompt)
add_assistant_message(messages, "Here are all three commands in a single block without any comments:\n```bash")

text = chat(messages, stop_sequences=["```"])
text.strip()

[4:42] これを実行してみます。今度はずっと信頼できる出力が得られるはずです。よし、良さそうです。そしてもちろん、一日中実行し続けることもできますが、おそらく私たちが望むまさにその結果が見え続けるでしょう。オーケー、これで良さそうです。

英語逐語(ASR全文)

Let's go through a very quick exercise just to make sure the idea of stop sequences and message prefilling are super clear. So in this exercise, I'd like you to write out the exact code I've got right here on the screen. All this code should be really familiar. If you take a look at the prompt, it says generate three different sample AWS CLI commands. Now when you run this code, you're probably going to get back some output that looks vaguely like this right here. Just to make it a little bit easier to read, I rendered it as markdown down here. So here's the sample starting output that I get. You'll notice that it does give us three different sample commands, but it has a lot of commentary around them. So I've got a header and then some numbers listing out each individual command. For this exercise, I would like you to take this code and using only message prefilling and stop sequences, I want you to get all three different commands in a single response, all right next to each other without any additional comments or explanation or anything like that. And I'd like you to do this using only message prefilling and stop sequences. So no adjusting this prompt at all. So go ahead and give this a shot. I did put a little hint on here. Just as a reminder, with message prefilling, it's not limited just to using characters like the backtick, backtick, backtick. You can put any kind of prefill response you want. I would encourage you to pause this video now and give this exercise a shot. Otherwise, I'll go over a solution right away. So here we go. Here's how we're going to solve this. To solve this, the first thing I recommend you do is take a look at the output without any kind of prefilling or stop sequences or anything like that. So if we take a look at what we have right here, we'll notice that each of our three commands are wrapped in a series of three backticks. So a good place to get started would probably be to put in a prefilled assistant message of three backticks to kind of tell Claude to just go right into the command writing right away and skip any initial commentary. And then we might also decide to put in a stop sequence of three backticks as well. Let's see how far that gets us. So I'll put in a add assistant message and I'll start everything off with three backticks and I'll put in a stop sequences of closing three backticks. Let's run this and see how far it gets us. My initial output looks kind of reasonable but it's not perfect just yet. I do get three commands. There's one, two, and three. But you'll notice that I also get the word bash added at the very start. So where's that word coming from exactly? Well, let me show you what Claude is really trying to do here. We provided the initial assistant message of those three backticks. Whenever you put down three backticks, it's kind of indicating that you are writing out some markdown. And when you are writing a markdown code block with backticks, you can optionally put in a language identifier right here. If you choose to put one in that whenever you render this as markdown, the content inside of those backticks will be rendered using that language's syntax highlighting. So in this case, Claude decided to put in bash right here just to say, hey, we should use bash style syntax highlighting when we render this stuff out. Now for us, we don't want that at all. So one way we could address this would be to adjust our pre-filled message right here and just include bash ourselves. So that's now going to make it super clear to Claude itself that yes, you are inside of a markdown code block, and inside this code block, you should be writing out bash formatted commands. So let me try running this again and see how I do now. Okay, so that looks better. Now, I will tell you that from this point, there are probably two additional errors that you might want to address. The first is sometimes you will get back a single command, which is kind of an indication that Claude might want to write out three separate markdown code blocks. The other problem you might run into because we are now using a bash code block, Claude might try to insert some bash formatted comments in there as well. So it might be something like this command does XYZ. You might see that repeated. And so we definitely don't want those comments really just because that was one of the requirements of this exercise. So to get rid of those comments and to also make sure that we get all three commands more reliably, we can use that hint I gave you. Remember the hint was message prefilling isn't just limited to designating characters like backticks or stuff like that. We can also use the message prefill to dramatically guide Claude in how it's going to answer us. So in this case, we could add in something like, here are all three commands in a single block without any comments. And then I'll put in a colon right there and then a new line, just so it starts all the markdown stuff on the next line down. So I'm going to try running this, and we should now get some much more reliable output. Okay, that looks good. And, of course, I can keep running it all day, and we're probably going to see exactly the result we want. Okay, this looks good.

↑ 目次へ戻る

Building with the Claude API | Lesson 14「Prompt evaluation」(全文ナレッジ)

動画 1分48秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04

日本語ナレッジ(要点整理)

新セクションの導入:Prompt Engineering と Prompt Evaluation

  • ここからテーマが「API へのアクセス」から「Claude から最良の出力を引き出すプロンプトを書く」へ移る。柱は2つ:
  • Prompt Engineering(プロンプトエンジニアリング)=プロンプトを書く・編集するたびに使う一連のテクニック。Claude が「何を求められているか」「どう応答すべきか」を理解する助けになる。
  • Prompt Evaluation(プロンプト評価)=プロンプトの自動テスト。プロンプトが有効かどうかを教えてくれる客観的な指標(objective metric)を得ることが目的。
  • このセクションは主に Prompt Evaluation にフォーカスする。プロンプトの有効性を測る方法を理解してから、プロンプトエンジニアリングのテクニックを見る(=測定が先、技法は後)。

スライドの対比(Prompt Engineering vs Prompt Evaluation)

Prompt Engineering Prompt Evaluation
プロンプトを改善(improve)するためのベストプラクティスとガイダンス集 プロンプトの働きを測定(measure)する自動テスト
・Multishot prompting ・XML タグによる構造化 ・その他多数 ・期待回答とのテスト ・同一プロンプトの版比較 ・出力のエラーレビュー

プロンプトを書いた後の「3つの道」

  1. Option #1:1〜2回試して「本番投入して良し」と判断 → 本番で壊れるリスク
  2. Option #2:自分のカスタム入力で数回テストし、気づいたコーナーケース1〜2個に合わせて微調整 → ユーザーは想定外の入力を頻繁に投げてくる!
  3. Option #3評価パイプラインに通してスコアを取り、それを基に反復改善手間とコストは増えるが、確信が持てる。 - Option #1 と #2 は「全エンジニアが(講師自身も含めて)はまる罠」。本気のアプリで使うプロンプトなのに、期待どおり動くかのテストが足りないまま進んでしまう。 - 推奨は常に Option #3:客観スコア → 反復 → 可能な限りの性能まで引き上げる。

実務ポイント(このレッスンから持ち帰るもの)

  1. プロンプトは「書く」より「測る」が先。1〜2回の手動確認で本番に出すのは罠(本人比の主観確認はテストではない)。
  2. 評価の目的は客観的メトリクス。期待回答との照合・バージョン間比較・エラーレビューを自動化し、スコアで意思決定する。
  3. Engineering と Evaluation は別物として扱う:improve(技法)と measure(測定)。measure の仕組みがあって初めて、技法の効果が判定できる。
  4. ユーザー入力は必ず想定を超えてくる——コーナーケース2個の手当てでは足りない、という前提で評価データセットを組む(次レッスン以降のワークフローへ接続)。
  5. Option #3 のコスト(作業量・API 料金)は「本番で壊れる」コストとの交換。確信(confidence)を買う投資として予算化する。

本文(時系列・日本語逐語+画面差し込み)

[0:00] Claude へのアクセス方法を理解したので、少し焦点を移して、2つの新しいトピックを見ていきます。プロンプトエンジニアリングプロンプト評価です。この2つのトピックはどちらも、Claude から可能な限り最良の出力を得られるプロンプトを確実に書けるようにすることがすべてです。

🖥 スライド「Improving and Evaluating Prompts — Building effective prompts and testing how they perform(効果的なプロンプトを作り、その性能をテストする)」。左カード「Prompt Engineering:Set of best practices and guidance to improve your prompts — ・Multishot prompting ・Structuring with XML tags ・Many more!」、右カード「Prompt Evaluation:Automated testing to measure how well your prompts work — ・Test against expected answers ・Compare different versions of the same prompt ・Review outputs for errors」。

[0:14] プロンプトエンジニアリングは、プロンプトを書いたり編集したりしたいときにいつでも使う一連のテクニックです。これらのテクニックは、私たちが何を求めているのか、どう応答してほしいのかを Claude が理解する助けになります。

[0:25] 一方のプロンプト評価は、プロンプトの自動テストを行うところです。目的は、プロンプトが効果的かどうかを教えてくれる、何らかの客観的な指標を得ることです。

[0:36] このセクションでは、主にプロンプト評価にフォーカスします。プロンプトの有効性をどう測定するかを理解した後で、プロンプトエンジニアリングのテクニックをいくつか見ていきます。では始めましょう。

[0:47] 最初にやりたいのは、プロンプト評価が一般的なプロンプト作成プロセスのどこに位置づくのかを理解してもらうことです。最初にプロンプトを書いたとき、一般に目の前には3つの異なる道、そこから進める3つの異なる方向があります。

🖥 スライド(フロー図):上に「Draft a Prompt(プロンプトを起草)」ボックス。そこから3本の矢印。Option #1「Test the prompt once and decide it's good enough(1回テストして『十分』と判断)」→ 注記 Risk of breaking in production(本番で壊れるリスク)。Option #2「Test the prompt a few times and tweak it to handle a corner case or two(数回テストし、コーナーケース1〜2個に合わせて微調整)」→ 注記 Users will often provide very unexpected outputs!(ユーザーはしばしば非常に想定外のものを投げてくる!)。Option #3「Run the prompt through an evaluation pipeline to score it, then iterate on the prompt(評価パイプラインでスコアリングし、プロンプトを反復改善)」→ 注記 More work and cost, but more confidence(手間とコストは増えるが、確信が増す)。

[0:58] オプション1では、組み立てたそのプロンプトを、たぶん1〜2回テストして、本番で使うのに十分だと判断するかもしれません。オプション2では、自分のカスタム入力でプロンプトを数回テストして、気づいたコーナーケースの1つか2つに対処するために少し微調整するかもしれません。

[1:15] ここですぐに理解してほしいのは、オプション1と2は、私自身も含めてすべてのエンジニアがはまる一種の罠だということです。誰にでも起こることです。私たちは皆、最終的には本気のアプリケーションで使われることになるプロンプトを書き始めるのに、それが期待どおりに動くことを確認するための十分なテストを、実際にはしないのです。

[1:32] ですから、プロンプトを書いたときはいつでも、オプション3で行くことを強くおすすめします。プロンプトを評価パイプラインに通して、そのプロンプトがどれくらい良く機能しているかを教えてくれる客観的なスコアを得るのです。その上でプロンプトに少し反復改善を加えて、可能な限り良い性能を発揮していることを確認できます。

英語逐語(ASR全文)

Now that we understand how to access Claude, we're going to shift our focus a little bit and look at two new topics, prompt engineering and prompt evaluation. These two topics are all about making sure that we are writing prompts that will get us the best possible output from Claude. Prompt engineering is a series of techniques that we'll use anytime that we want to write or edit a prompt. These techniques will aid Claude in understanding what we're asking of it and how we want it to respond. Prompt evaluation, on the other hand, is where we do some automated testing of a prompt. with the goal of getting some kind of objective metric that tells us if our prompt is effective or not. In this section, we're going to be mostly focused on prompt evaluation. After we understand how to measure the effectiveness of a prompt, we'll then take a look at some prompt engineering techniques. So let's get to it. The first thing I want to do is help you understand where prompt evaluation fits in to the prompt writing process in general. Whenever you first write a prompt, you generally have three different paths ahead of you, three different ways you can go from there. With option number one, you might take that prompt you put together, maybe test it once or twice and decide it is good enough to use in production. With option number two, you might test the prompt a couple of times with your own custom inputs and maybe tweak it a little bit to handle a corner case or two that you notice. Now right away, I want you to understand that options number one and number two are kind of traps that all engineers fall into, myself included. It happens to everybody. We all start writing out prompts that are going to eventually be used in serious applications, and we don't really test them enough to make sure that they are working as expected. So whenever you write a prompt, I highly recommend going with option number three. Run your prompt through an evaluation pipeline to get an objective score that will tell you how well your prompt is performing. You can then try to iterate on your prompt a little bit and make sure that it's performing as well as it possibly can.

↑ 目次へ戻る

Building with the Claude API | Lesson 15「A typical eval workflow」(全文ナレッジ)

動画 4分36秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンのテーマ

典型的なプロンプト評価(prompt eval)ワークフローの全ステップを、概念レベルで一巡する回。次レッスン以降で Jupyter ノートブック上に自作の評価フレームワークをスクラッチ実装するための、全体像の把握が目的。

前提として押さえる2つのこと

  1. ワークフローの組み方に唯一の正解はない。業界標準として石に刻まれた単一の方法論は存在せず、組み立て方は多様。
  2. 自作しなくても、オープンソースパッケージや有料サービスは多数ある。それでもこのモジュールであえてスクラッチ実装するのは、①ワークフローの挙動を理解するため、②「プロンプト評価にヘビー級のソリューションは必須ではない。小さく始めて仕組みを掴み、そこからスケールアップすればよい」と体感するため。

評価ワークフローの5ステップ(全体像)

スライドでは5つの矢印型ステップで図示される: 1. Draft a Prompt(プロンプトの初期ドラフト) 2. Create an Eval Dataset(評価データセットの作成) 3. Feed Through Claude(Claude に流して応答を得る) 4. Feed Through a Grader(grader=採点器に流してスコア化) 5. Change Prompt and Repeat(プロンプトを変更して繰り返す)

Step 1: 初期プロンプトのドラフト

改善したいプロンプトをまず書き下ろす。例はごく単純な f-string:

prompt = f"""
Please answer the user's question:

{question}
"""

ユーザーからの質問 {question} を補間(interpolate)する構造。スライドの言葉どおり「このプロンプトが効果的かどうかは、客観的な方法論で評価するまで分からない」というのが出発点。

Step 2: 評価データセットの作成

  • データセットには、プロンプトに入れうる入力(この例では質問)のリストを入れる。このプロンプトの入力は question の1つだけなので、データセット=質問のリスト。
  • 例では3問だけ:「What's 2+2?」「How do I make oatmeal?」「How far away is the Moon?」
  • 実務の eval では数十・数百・数千レコードになることもある。
  • データセットは手作業で組んでも、Claude に生成させてもよい

Step 3: プロンプトへ入力を差し込み、Claude に実行させる

  • データセットの各質問をプロンプトに差し込み、完成形のプロンプト(fully fleshed out prompt)を作る。例:「Please answer the user's question: What's 2+2?」
  • これを全レコード分繰り返し、それぞれ Claude に投げて実際の応答を得る。図では「2 + 2 = 4」「Add hot water to dry oatmeal.」「The Moon is about 384,400 KM away.」の3応答。

Step 4: grader(採点器)による採点

  • データセットの各質問と Claude の各回答をペアにして、grader に1件ずつ流す
  • grader の実装方法は多数あり、方法論は後のレッスンで扱う(スコアをどう出すかには隠れた複雑さがある、と講師が予告)。
  • grader は回答品質に基づき例えば1〜10のスコアを返す。10=完璧でこれ以上改善しようがない回答、4=明確に改善余地がある回答。図では3件がそれぞれ 10 / 4 / 9。

Step 5: スコア平均 → プロンプト改訂 → 再実行・比較

  • スコアを平均する(10 + 4 + 9 を足して3で割り 7.66)。これで「元のプロンプトがどれだけ良く機能したか」を客観的に記述する手段が手に入る。
  • 次にプロンプトを何らかの形で変更し、このプロセス全体を反復する。例:出力をもっと誘導するため、プロンプト末尾に「Answer the question with ample detail(十分に詳しく質問に答えて)」を追加して v2 を作る。
  • v2 を同じパイプラインに通すと v1 と v2 のスコアが揃う(スライド例:v1 = 7.66、v2 = 8.7)。スコアが高い方が「おそらく良いバージョン」だと分かる。これは完全な証明ではないが「何もないよりましな客観的サイン」。

実務ポイント(このレッスンから持ち帰るもの)

  • プロンプト改善は勘ではなくループで回す:ドラフト → データセット → Claude 実行 → 採点 → 平均スコア → 改訂 → 再実行・比較、が基本形。
  • eval は小さく始めてよい。3問のデータセット+Jupyter ノートブックでも成立する。ヘビー級の評価基盤や有料 SaaS の導入は必須ではなく、仕組みを理解してからスケールアップすればよい。
  • ワークフローに業界標準の唯一解はない。自分のユースケースに合わせて組み替える前提で考える。
  • 評価データセット=プロンプトの入力変数の値リスト。入力が question 1つなら質問リストでよい。手作業でも Claude 生成でもよく、実務では数十〜数千件に増やす。
  • 平均スコアという単一の客観指標を持つことで、プロンプト v1 と v2 を比較可能になる。「スコアが高い方が良い」は絶対ではないが、何もないよりましな判断材料。
  • grader が肝であり難所。スコアをどう算出するかには隠れた複雑さがあり、複数の方法論(後続レッスンで詳説)を使い分ける。
  • プロンプト改訂の第一手としては「出力への要求を1行足す」程度の小さな変更でもスコアは動く(例:ample detail の追加で 7.66 → 8.7)。

本文(時系列・日本語逐語+画面差し込み)

🖥 [0:00] 画面(スライド「Prompt Eval Workflow」) 左に見出しと箇条書き、右に5つの矢印型ステップが縦に並ぶ。 - タイトル:Prompt Eval Workflow - Many different ways to assemble a workflow - Many different open source and paid options - We are going to assemble our own workflow in this module

右側のステップ(上から): 1. Draft a Prompt 2. Create an Eval Dataset 3. Feed Through Claude 4. Feed Through a Grader 5. Change Prompt and Repeat

[0:00] このビデオでは、典型的なプロンプト評価ワークフローが実装しているこれらのステップ全体を一通り見ていきます。ただし、どのステップに入る前にも、2つの重要なことをまず理解しておいてほしいのです。1つ目は、ワークフローの組み立て方には実に多くの異なるやり方がある、ということです。業界全体の標準として石に刻まれた、唯一の決まった方法論というものは存在しません。

[0:18] 理解しておくべき2つ目のことは、自分のワークフローを実装するのを助けてくれるオープンソースパッケージが多数あり、オンラインには有料の選択肢まである、ということです。さて、このビデオとこのモジュールでは、Jupyter ノートブックの中で、自分たち独自のカスタムワークフローをスクラッチから実装し始めます。そうする理由は、もちろんこれらのワークフローがどう振る舞うかを理解してもらうためでもありますが、同時に、プロンプト評価をやるのに本当にヘビー級のソリューションを用意する必要はない、と理解してもらうためでもあります。

[0:43] まずは小さく始めて、全体がどう動くのかの感覚を掴み、そこからスケールアップしていけばよいのです。それでは、始めましょう。典型的なプロンプト評価のステップ1です。ステップ1では、プロンプトの初期ドラフトを書き出します。つまり、あなたと私が腰を据えて、何らかの形で改善したいと思うプロンプトをとにかく書き出すのです。

🖥 [0:53] 画面(スライド「Initial Prompt Draft」) 左:見出し Initial Prompt Draft と説明文 「Will this be an effective prompt? We won't know until we evaluate it with some objective methodology」 右:ダークのコードボックス ```python prompt = f""" Please answer the user's question:

{question} """ ```

[1:03] この例では、とても単純なプロンプトを使います。「Please answer the user's question(ユーザーの質問に答えてください)」とだけ書かれたもので、そこに何らかのユーザー入力を補間します。つまり、ユーザーから提供された何らかの質問です(※ASR は "provided by Acer" と誤認識しているが文脈上 "a user")。ステップ2では、評価データセットを作成します。

🖥 [1:15] 画面(スライド「Create an Eval Dataset」) 上部のステップ矢印は「Draft a Prompt」→「Create an Eval Dataset」(後者が強調)。 左:グレー化した Step 1 のプロンプトコード(prompt = f""" Please answer the user's question: {question} """)。 中央:Question と題した表 - What's 2+2? - How do I make oatmeal? - How far away is the Moon?

右:見出し Eval Dataset - Contains questions that we will merge with our prompt - Can be assembled by hand or generated by Claude

[1:16] このデータセットには、プロンプトに入れたいと思うであろう入力の候補をいくつか入れておきます。私たちの場合、プロンプトの入力は1つだけ、ユーザーから提供される質問です。ですから、私たちの評価データセットには、プロンプトに入れる可能性のある様々な質問のリストを持たせます。私のデータセットには3つの異なる質問しか入れませんが、実世界の評価では、データセットの中に数十、数百、時には数千もの異なるレコードを持つこともあります。

[1:44] さて、こうしたデータセットは手作業で組み立てることもできますし、もちろん Claude に生成してもらうこともできます。評価データセットが用意できたら、次にこれらの異なる質問を1つずつプロンプトに流し込みます。そうすると、完全に肉付けされたプロンプトが得られ、それをそのまま Claude に投げ込めるようになります。

🖥 [1:55] 画面(スライド「Feed Through Claude」) ステップ矢印は「Draft a Prompt」→「Create an Eval Dataset」→「Feed Through Claude」(最後が強調)。 左2列はグレー化した Step 1(プロンプト)と Step 2(Question 表)。 右のパネル:3つの黒いカード(完成プロンプト)→ 中央の「Claude」ボックス → 3つの黒いカード(応答)。 - 「Please answer the user's question: What's 2+2?」→ Claude → 「2 + 2 = 4」 - 「Please answer the user's question: How do I make oatmeal?」→ Claude → 「Add hot water to dry oatmeal.」 - 「Please answer the user's question: How far away is the Moon?」→ Claude → 「The Moon is about 384,400 KM away.」

[2:02] つまり、ここにプロンプト1があるとします。「Please answer the user's question」とあり、その後にデータセットから取り出したサンプル質問、例えば「2たす2は?」が続きます。そして、データセット内の残りすべてのレコードについて同じことを繰り返します。これが2つ目と3つ目です。次に、これらを1つずつ Claude に流し込み、Claude から実際の応答を得ます。最初のものに対しては「2 + 2 = 4」といった応答が返ってくるでしょうし、次はオートミールの作り方について何か、その次は月までの距離について何か、が返ってくるでしょう。

[2:33] Claude から実際の回答が出てきたら、次はそれらを何らかの方法で採点(grade)します。この採点ステップでは、データセットから各質問を、そして Claude から得た各回答を取り出します。それらをすべてペアに組み合わせ、grader(採点器)に1件ずつ流し込みます。

🖥 [2:35] 画面(スライド「Feed Through a Grader」) ステップ矢印は4つ目「Feed Through a Grader」(黒)が強調。左3列はグレー化(プロンプト/Question 表/Responses:「2 + 2 = 4」「Add hot water to dry oatmeal.」「The Moon is about 384,400 KM away.」)。 右のパネル:Question と Answer をペアにした3つの黒いカード → 中央の「Grader」ボックス → スコア。 - 「Question: What's 2+2? / Answer: 2 + 2 = 4」→ Grader → 10 - 「Question: How do I make oatmeal? / Answer: Add hot water to dry oatmeal.」→ Grader → 4 - 「Question: How far away is the Moon? / Answer: The Moon is about 384,400 KM away.」→ Grader → 9

[2:49] この grader の実装方法にはたくさんの異なるやり方があります。いくつかの異なる方法論については、もう少し後で見ていきます。grader は、Claude が生成した回答の品質に基づいて、例えば1から10までのスコアを返してくれます。10であれば、完璧な回答が得られていて、これ以上改善する余地は本当にない、という意味になります。そして例えば4であれば、そこには明確に改善の余地がある、ということを示します。

[3:07] さて、お察しのとおり、この grader にはかなり多くの隠れた複雑さがあります。皆さんはおそらく「そもそもこのスコアはいったいどうやって出すのか?」と気になっている、あるいは疑問に思っているでしょうから。これも心配いりません。grader にまつわる事柄は、もう少し後でずっと詳しく扱います(※ASR は "these greater things" と誤認識しているが文脈上 "grader")。これらのスコアが得られたら、次はそれらをすべてまとめて平均します。

🖥 [3:23] 画面(スライド「Feed Through a Grader」・平均スコア表示) 同じ Grader スライドの続き。3つのスコア 10 / 4 / 9 が波括弧でまとめられ、右に大きく 7.66Averaged scores と表示される。

[3:24] この場合、私はスコアを足し合わせ、3で割り、平均スコア 7.66 を得ます。これで、元のプロンプトがどれだけ良く機能したかを記述する、何らかの客観的な方法を手に入れたことになります。このスコアが手元にあれば、次はプロンプトを何らかの形で変更して、このプロセス全体を反復、つまり繰り返すことができます。

🖥 [3:37] 画面(スライド「Change Prompt and Repeat」) ステップ矢印は5つ目「Change Prompt and Repeat」が強調。中央にはグレー化した Grader → 「7.66 Averaged scores」。 右:新しいプロンプト v2 のコードボックス(黒) ```python prompt = f""" Please answer the user's question:

{question}

Answer the question with ample detail """ ```

[3:43] そこで、スコアを改善したいなら、プロンプトにもう少し詳細を書き足してみるかもしれません。Claude をもう少し導き、私たちがどんな出力を求めているのかを理解する助けになることを期待して、です。例えば、プロンプトの末尾に「answer the question with ample detail(十分に詳しく質問に答えて)」のような一文を書き足すでしょう。プロンプトの第2バージョンができたら、それをこのパイプライン全体にもう一度通します。

🖥 [4:05] 画面(スライド「Prompt Scoring」) 左:見出し Prompt Scoring と箇条書き - Get an objective measurement of the output of each prompt - Use the version with the best score, or iterate again

右(赤茶の背景に2つのコードボックス): - Prompt v1 - Scored 7.66 ```python prompt = f""" Please answer the user's question:

{question} """ - **Prompt v2 - Scored 8.7**python prompt = f""" Please answer the user's question:

{question}

Answer the question with ample detail """ ```

[4:03] そうすると、プロンプト・バージョン1とプロンプト・バージョン2のそれぞれについてスコアが手に入ります。この2つのスコアを比較して、どちらのスコアがより大きい、つまり高いかを見れば、それは一種の客観的なサイン——何もないよりはましなもの——として、この場合はプロンプト v2 の方がおそらく私たちのプロンプトのより良いバージョンだ、と教えてくれるのです。

[4:20] さて、このプロセス全体の高レベルな概観が掴めたところで、先ほど述べたとおり、Jupyter ノートブックの中で私たち独自のカスタム評価フレームワークを実装し始めます。それでは、次のビデオで実装に取りかかりましょう。

英語逐語(ASR全文)

In this video, we're going to walk through all these steps implemented by a typical prompt evaluation workflow. Before we go through any of these steps, however, I just want you to understand two important things. First is there are many different ways you can assemble a workflow. There's no one set methodology set in stone that is standard across the industry. The second thing to understand is that there are many different open source packages and even paid options online that will help you implement your own workflows. Now in this video and this module, we're going to start to implement our own custom workflow from scratch inside of a Jupyter notebook. The reason we're doing this is to, of course, just help you understand how these workflows behave, but also to help you understand that you don't have to get a really heavyweight solution to do prompt evals. You can start small just to get started and get a sense of how everything works and then scale up from there.

All right, so let's get to it. Step one of a typical prompt eval. Step one, we're going to write out a initial prompt draft. So you and I will sit down and just write out some kind of prompt that we want to improve in some way. For this example, we're going to have a very simple prompt that just says, please answer the user's question. And then we're going to interpolate in some user input. So some question provided by Acer. In step two, we're going to create an evaluation data set. This data set is going to contain some number of possible inputs that we might want to put into our prompt. So for us, our prompt only has one input, a question provided by user. So for our eval data set, we'll have a list of different possible questions that we might want to put into our prompt. My data set is only going to have three different questions inside of it, but in real world evals, you might have tens, hundreds, even thousands of different records in your data set. Now you can assemble these data sets by hand, or you can of course also use Claude to generate them for you.

Once we have our eval data set, we're then going to feed each of these different questions into our prompt. So we get a fully fleshed out prompt that we can then feed into Claude. So we might have prompt one right here, where we have please answer the user's question, and then a sample question out of our data set, like what's two plus two. And then we will repeat for all the other records inside of our data set. So here's two and three. We'll then feed each of these into Claude and get an actual response out of Claude. So for the first one, we might get back a response of something like 2 plus 2 is 4, and then something about how to make oatmeal, and then something about the distance to the moon.

Once we have these actual answers coming out of Claude, we're then going to grade them in some way. During this grading step, we're going to take each of the questions out of our data set and the answers we got out of Claude. We'll pair them all off together and it'll feed them into a grader one by one. There are many different ways we can implement this grader. We'll take a look at some of the different methodologies a little bit later. The grader will then give us a score, maybe from one up to 10, based upon the quality of the answer that was produced by Claude. So a 10 would mean we got a perfect answer and there's really no possible way we could improve it. And maybe something like a four indicates that there's definitely room for improvement there. Now, as you can guess, there's kind of a lot of hidden complexity here with a grader because you're probably curious or wondering, well, how do we actually get these scores at all? And again, don't worry, we're going to cover these greater things in much greater detail in a little bit.

After we get these scores, we're then going to average them all together. So in this case, I would add the scores together, divide by three, and get an average score of 7.66. So I now have some kind of objective way of describing how well our original prompt performed. Now that we have this score, we can then change our prompt in some way and iterate or repeat this entire process. So if I want to improve my score, I might try adding in a little bit more detail to the prompt to hopefully guide Claude a little bit more and help understand what kind of output we want. So maybe I would add on to the end of the prompt, something like answer the question with ample detail. Once I have the second version of my prompt, I would then run it through this entire pipeline again. I would then have a score for prompt version one and prompt version two. I could then compare these two scores and whichever score is greater or higher, it's kind of a objective sign, better than nothing, that tells me that prompt v2 in this case is perhaps the better version of our prompt.

So now that we have a high level overview of this entire process, as I mentioned, we're going to start to implement our own custom eval framework inside of a Jupyter notebook. So let's get started on an implementation in the next video.

↑ 目次へ戻る

Building with the Claude API | Lesson 16「Generating test datasets」(全文ナレッジ)

動画 4分44秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンのテーマ

自前のプロンプト評価(prompt evaluation)ワークフローの構築を開始する。まずプロンプトを書き、その性能を評価するコードを書く——その第一歩として、評価用テストデータセットを Claude で自動生成する。

お題:AWS 特化のコード生成プロンプト

ユーザーが「やりたいタスク」を入力すると、AWS ユースケース特化のコードを返すプロンプトを作る。

出力は次の3種類のいずれかに限定する - Python コード - JSON 設定(config) - 生の正規表現(regular expression)

しかも「説明・ヘッダー・フッターなどを一切付けず」、この3形式のいずれかだけを出力させる必要がある。

プロンプト V1(最初のドラフト)

prompt = f"""
Please provide a solution to the following task:

{task}
"""

ユーザーのタスクを {task} に差し込むだけの素朴な版から始める。

評価用データセットの設計

  • データセット=プロンプトに流し込む入力の集合。プロンプト×入力の全組み合わせで実行して評価する
  • 今回は「task プロパティを持つ JSON オブジェクトの配列」として表現する。各 task は Claude にやってほしい作業の説明文
  • データセットは手作業で組んでも、Claude に自動生成させてもよい
  • Claude に生成させる場合、この種の作業は高速なモデル(Haiku)を使う好機——このレッスンでも Haiku を使う

generate_dataset() の実装(プレフィル+stop_sequences+json.loads)

ノートブック(001_prompt_evals.ipynb)には、コース中に開発してきたクライアント生成・環境変数ロード・3つのヘルパー関数(add_user_message / add_assistant_message / chat)を引き継いだ上で、generate_dataset() 関数を定義する。

  1. 関数内に大きめの生成用プロンプトを用意(レクチャー添付のノートブックからコピー推奨)。内容は: - AWS 関連タスク向けに Python / JSON / Regex を生成するプロンプトを評価するための「評価データセットを生成せよ」 - 出力形式の例(```json ブロック内に {"task": "Description of task"} の配列)を明示 - 「単一の Python 関数/単一の JSON オブジェクトで解けるタスクに絞る」「大量のコードを要しないタスクに絞る」という制約 - まずは「3オブジェクトだけ生成」と指定(データセットが作れることの確認にはこれで十分)
  2. JSON を確実にパースするため、以前のレッスンで学んだプレフィル+stop sequence 方式を使う:
messages = []
add_user_message(messages, prompt)
add_assistant_message(messages, "```json")
text = chat(messages, stop_sequences=["```"])
return json.loads(text)
  • assistant メッセージに ```json をプレフィル → Claude は JSON 本体から書き始める
  • stop_sequences=["```"] → JSON ブロックの閉じで生成が止まる
  • 残った中身を json.loads() でパースして返す

生成結果と dataset.json への保存

dataset = generate_dataset() を実行すると、3つのテストケースが返る(実行例:ARN から AWS リージョンを抽出する Python 関数/Lambda の環境変数・メモリ設定を定義する JSON 設定/S3 バケット名を検証する正規表現)。Python・JSON・Regex の3形式が1件ずつ揃っており、良いスタートと言える。

後で評価時に簡単にロードできるよう、ファイルに書き出しておく: - ファイルを書き込みモードで開き、dataset.json という名前で json.dump(..., indent=2) - 実行後、ノートブックと同じディレクトリに dataset.json ができ、中にタスクのリストが入る

これで評価用データセットの準備が完了。

実務ポイント(このレッスンから持ち帰るもの)

  • プロンプト評価の基本形は「プロンプト(V1ドラフト)→ 評価用データセット → 全組み合わせ実行」。まず素朴な V1 から始めてよい。
  • 評価データセットは「入力(task)だけを持つ JSON オブジェクトの配列」というミニマム構造で始めると回しやすい。
  • データセットは手組みでも良いが、Claude に自動生成させると速い。この種の大量・定型生成は Haiku のような高速モデルの出番(コスト・速度の最適化)。
  • データセット生成プロンプトには「出力例(Example output)を JSON コードブロックで明示」+「タスクの粒度制約(単一関数/単一 JSON オブジェクトで解ける、コード量が少ない)」を書くと、評価に使える粒度のタスクが得られる。
  • LLM から構造化 JSON を確実に受け取る定石=assistant プレフィル ```jsonstop_sequences=["```"]json.loads()。前レッスンのテクニックがそのまま実務部品になる。
  • 生成したデータセットはその場で使い捨てず dataset.json に永続化json.dump + indent=2)しておくと、以降の評価工程で何度でもロードできる。
  • まずは3件だけ生成してパイプラインが通ることを確認してから件数を増やす、という段階的な進め方が安全。

本文(時系列・日本語逐語+画面差し込み)

[0:00] それでは、自分たち自身のカスタムなプロンプト評価ワークフローの構築に取りかかりましょう。プロンプトを書き出し、それから、それがどれだけうまく機能するかを評価するコードを書いていきます。というわけで、まずはプロンプトを作ることに集中しましょう。

🖥 [0:00] 画面(スライド「Goal / Prompt v1」) 左側(Goal): - Write a prompt that will assist users in writing Python code, JSON config, or Regular Expressions focused on AWS-specific use cases - Input - User will request code for a specific task - Output - Python, JSON, or a regular expression without any explanation

右側(Prompt v1): ```python prompt = f""" Please provide a solution to the following task:

{task} """ ```

[0:11] 私たちのプロンプトのゴールは、AWS のユースケースに特化したコードを書くことについてユーザーを支援することです。そこで、ユーザーには、手助けしてほしい何らかのタスクを入力してもらえるようにします。そして私たちは、3種類の出力のうちの1つで応答することになります。

[0:25] 出力するのは、Python か、JSON 設定(configuration)か、あるいは生の、ただのプレーンな正規表現のいずれかです。この3つが、あり得る出力です。

[0:35] ですから、ユーザーが何らかのタスクの遂行を依頼してきたときには常に、これら3つの特定の出力形式のうちの1つで出力を返す必要があります。それ以外の説明やヘッダーやフッターといった類のものは一切付けません。それが全体のゴールです。

[0:50] さて、このゴールの最初のステップは、当然ながら、ドラフト版のプロンプトを書き出すことです。実は、こちらの右側で、それはもうある程度やってしまってあります。プロンプトの V1 があり、そこには単に「以下のタスクに対する解決策を提供してください(Please provide a solution to the following task)」と書かれています。そして、そこにユーザーのタスクを入れることになります。

[1:02] ステップ2は、データセットを組み立てることです。データセットには、私たちのプロンプトに流し込むいくつかの入力が含まれることになる、ということを思い出してください。そして、プロンプトと入力のすべての組み合わせについて、プロンプトを実行していきます。

🖥 [1:06] 画面(スライド「Sample Dataset / Eval Dataset」) 左側(Sample Dataset)のコードブロック: json [ { "task": "Create a Python function to extract the AWS account ID from an ARN" }, { "task": "Write a JSON policy document that allows read-only access to a specific S3 bucket" } …many more… ] 右側(Eval Dataset): - Each object contains a “task” that we will merge into the prompt - Can assemble by hand or with Claude - If using Claude, consider Haiku!

[1:15] 私たちの今回のケースでは、すべてのオブジェクトが task プロパティを持つ、JSON オブジェクトの配列を用意します。これらのタスクは、Claude にやってもらいたい何かを記述するものになります。そこで、これらのタスクを1つずつ取り出してプロンプトに入れ、その結果を Claude に流し込みます。

[1:31] データセットを作るときは、手作業で組み立てることもできますし、Claude で自動生成することもできる、ということを思い出してください。さて、補足ですが、こういう用途に Claude を使うのであれば、これは Haiku のような、より高速なモデルを使う本当に良い機会です。そして、ここではまさにそうしていきます。

[1:47] では、Jupyter に戻りましょう。ノートブックを開いて、左側に見えているようなサンプルデータセットを Haiku を使って生成する、ちょっとしたコードを書いていきます。

[1:56] こちらに戻ってきました。新しいノートブックを作成してあります。これには、このコースを通じて作業してきたのと同じコードがたくさん入っています。一番上のセルの中でクライアントを作成し、環境変数もいくつかロードしています。そして、私たちが開発してきたのと同じ3つのヘルパー関数を作成しています。

🖥 [1:57] 画面(Jupyter ノートブック 001_prompt_evals.ipynb — ヘルパー関数) VS Code + Jupyter。タイトルバー 001_prompt_evals.ipynb — 04、カーネル .venv (Python 3.9.6)。上部に実行済みセル # Load env variables and create client。その下のセル: ```python

Helper functions

def add_user_message(messages, text): user_message = {"role": "user", "content": text} messages.append(user_message)

def add_assistant_message(messages, text): assistant_message = {"role": "assistant", "content": text} messages.append(assistant_message)

def chat(messages, system=None, temperature=1.0, stop_sequences=[]): params = { "model": model, "max_tokens": 1000, "messages": messages, "temperature": temperature, ``` (以降は画面下端で切れて見えない)

[2:08] それから少し下の方で、generate dataset という関数を定義してあります。そしてこの中に、私たちのスタートを切るための、かなり大きなプロンプトを組み上げてあります。さて、このノートブックはこのレクチャーに添付してあります。ですから、このノートブックをダウンロードして、ここにあるこのプロンプトをコピーするか、あるいはこのノートブックを直接使うことをお勧めします。大量のタイピングの手間が省けますから。

🖥 [2:10] 画面(ノートブック — generate_dataset() とプロンプト前半) ```python import json

def generate_dataset(): prompt = """ Generate a evaluation dataset for a prompt evaluation. The dataset will be used to evaluate pro… that generate Python, JSON, or Regex specifically for AWS-related tasks. Generate an array of J… each representing task that requires Python, JSON, or a Regex to complete.

Example output: json [ { "task": "Description of task", }, ...additional ] `` (プロンプト本文の各行は画面右端で切れており、…` の先は判読不能)

[2:27] このプロンプトは、Claude に、私たちのためにいくつかの異なるテストケースを生成するよう依頼するものです。テストケースは JSON オブジェクトの配列として表現され、各オブジェクトは、実行されるべきタスクを記述した task プロパティを持ちます。今のところは、そのようなオブジェクトを3つだけ生成するよう Claude に頼んでいます。これだけあれば、間違いなくスタートを切って、実際にデータセットを作れることを確認するには十分です。

🖥 [2:30] 画面(ノートブック — プロンプト後半) json [ { "task": "Description of task", }, ...additional ] ```

  • Focus on tasks that can be solved by writing a single Python function, a single JSON object,…
  • Focus on tasks that do not require writing much code

Please generate 3 objects. """ `` (1つ目の* Focus on…` 行は画面右端で切れている)

[2:45] それでは、今いるこの generate dataset 関数に、いくらかコードを追加していきましょう。実際にこのプロンプトを取り、Claude に送信し、タスクのリストを受け取り、それを JSON としてパースするコードです。JSON をパースするにあたっては、少し前に話した、あの同じプレフィル(prefilling)と stop sequence の手法を確実に使うようにします。では、やっていきましょう。

[3:08] この下、関数の内側で——インデントを確実に入れるようにしますが——メッセージのリストを宣言します。そのプロンプトを user メッセージとして追加します。

🖥 [3:00] 画面(ノートブック — 関数本体を入力中) python messages = [] add_user_mesadd_user_mes まで入力した途中の状態)

[3:18] 次に assistant メッセージを追加します。そして、バッククォート、バッククォート、バッククォート、json と入れます。それから、メッセージのリストといくつかの stop sequences を渡して chat を呼び出します。この場合、stop sequence はバッククォート3つ、ただ1つだけです。そして最後に、json.loads した text を return します。

🖥 [3:30] 画面(ノートブック — 完成した関数本体) python messages = [] add_user_message(messages, prompt) add_assistant_message(messages, "json") text = chat(messages, stop_sequences=[""]) return json.loads(text)

[3:43] オーケー。ではセルを実行して、この関数が確実に定義されるようにします。そして、この下でさっとテストしてみましょう。それから、そのデータセットを表示して、現実味のあるデータがちゃんと返ってきていることを確認しましょう。

[3:54] オーケー、できました。これが、3つの異なるテストケースです。Python の関数を得るケース、JSON 設定を書くケース、そして正規表現を書くケースが得られています。ということで、これは良いスタートだとしましょう。

🖥 [4:00] 画面(ノートブック — dataset = generate_dataset() の実行結果) ```python dataset = generate_dataset()

dataset 出力(2.9秒で実行完了): [{'task': 'Create a Python function to extract the AWS region from an ARN (Amazon Resource Name)'}, {'task': 'Write a JSON configuration for an AWS Lambda function that defines basic environment variables and memory settings'}, {'task': 'Develop a regular expression to validate an AWS S3 bucket name (lowercase, alphanumeric, between 3-63 characters)'}] ```

[4:06] 次に、このデータセットをファイルに書き込みたいと思います。そうすれば、後でプロンプトの評価を始めるときに、とても簡単にロードし直せます。そのためには、ファイルを書き込みモード(write mode)で開きます。ファイルは dataset.json という名前にして、それから indent を 2 にして json.dump します。では、これをもう一度実行します。

[4:31] そのセルが実行された後、ノートブックと同じディレクトリの中に dataset.json ファイルが見つかるはずで、その中には私たちのタスクのリストが入っているはずです。

[4:39] オーケー、これは良いスタートです。評価用データセットが組み上がりました。

🖥 [4:39] 画面(スライド「Sample Dataset / Eval Dataset」再表示) 締めのナレーションとともに、[1:06] と同じ「Sample Dataset / Eval Dataset」スライドに戻る(内容は同一)。

英語逐語(ASR全文)

Let's get started on building our own custom prompt evaluation workflow. We're going to be writing out a prompt and then writing out some code to evaluate how well it performs. So let's first focus on making a prompt. The goal of our prompt is to help users in writing out some code specific for AWS use cases. So we're going to allow a user to enter in some kind of task that they need help with. And then we're going to respond with one of three types of output. We're either going to output Python, JSON configuration, or a raw, just plain regular expression. Those are our three possible outputs. So we need to make sure that whenever a user asks for us to complete some kind of task, we give them some output in one of these three particular outputs without any other kind of explanation or header or footer or anything like that. So that's the overall goal.

Now, the first step of our goal is, of course, to write out a draft prompt. Now I've kind of already done that for us on the right-hand side here. I've got V1 of our prompt where it just says, please provide a solution to the following task. And we'll put the user's task in there. Step two is to assemble a dataset. Remember that a dataset is going to contain some number of inputs that we're going to feed into our prompt. And then we're going to run our prompt for every combination of prompt and input. For our particular case, we're going to have an array of JSON objects where every object has a task property. These tasks are going to describe something that we want to be done by Claude. So we're going to take each of these tasks, put them into our prompt, and then feed the results into Claude. Remember that when we make a dataset, we can either assemble it by hand, or we can generate it automatically with Claude. Now, as a side note, if you're using Claude for something like this, this would be a really good opportunity to use a faster model like Haiku. And that's what we're going to be doing here. Let's go back over to Jupyter. We're going to open up our notebook, and we're going to write out a little bit of code that it's going to generate a sample data set like the one you see on the left-hand side using haiku.

Back over here, I've created a new notebook. It has a lot of the same code that we've been working on throughout the course. So I'm creating a client inside the top cell and also loading some environment variables. And I create those same three helper functions that we've been developing. Then a little bit lower, I've defined a function called generate data set. And inside of here, I put together a rather large prompt to get us started. Now I've taken this notebook and attached to this lecture. So I would encourage you to download this notebook and copy the prompt right here, or just use this notebook directly to save yourself a whole bunch of typing. This prompt is going to ask Claude to go ahead and generate some different test cases for us. Our test cases are going to be represented by an array of JSON objects, and each object is going to have a task property that describes the task to be done. For right now, I'm just asking Claude to generate three such objects. This is enough to definitely get us started and make sure that we can actually create a dataset. So now let's add in some code to this generate dataset function that we are in that will actually take this prompt, send it off to Claude, get back a list of tasks, and then parse them as JSON. To parse the JSON, we're going to make sure that we use that same prefilling and stop sequence method we spoke about a little bit ago. So let's get to it. Down here inside of the function, so I'm going to make sure that I do indent in, I will declare a list of messages. I will add a user message of that prompt. I'll then add in an assistant message. And I'm going to put in backtick, backtick, backtick JSON. I'll then call chat with our list of messages and some stop sequences. In this case, our only stop sequence is going to be backtick, backtick, backtick. And then finally, I will return json.loads text.

Okay, so I'm going to run the cell to make sure that function gets defined, and we'll test it out down here really quickly. And then let's print up that data set just to make sure that we are getting back some realistic-looking data. Okay, there we go. So there's our three different test cases. We are getting a case in which we are going to get a Python function, write some JSON configuration, and then write a regular expression. So let's say this is a good start. Next, I would like to take this dataset and write it into a file so we can very easily load it up later on when we start to evaluate our prompt. To do so, we will open up a file in my write mode. I'm going to call the file dataset.json and then json.dump with an indent of 2. So I'm going to run this again. After that cell runs, inside the same directory as my notebook, I should find a dataset.json file, and inside there should be our list of tasks. Okay, this is a good start. We've got our eval dataset put together.

↑ 目次へ戻る

Building with the Claude API | Lesson 17「Running the eval」(全文ナレッジ)

動画 6分41秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンのテーマ

前レッスンで生成した評価用データセットの各レコード(=テストケース)をプロンプトにマージして Claude に流し、結果を収集する——つまり eval パイプラインの「実行」部分を、run_prompt / run_test_case / run_eval の3関数として実装する。

評価パイプラインの全体像(4ステップ)

スライドのワークフロー図は以下の4段階: 1. Draft a Prompt — プロンプトの草稿を作る(例:prompt = f"""Please answer the user's question: {question}""") 2. Create an Eval Dataset — 評価用データセットを作る(前レッスンで完了) 3. Feed Through Claude — 各テストケースをプロンプトにマージして Claude に流し、出力を得る(今回ここ) 4. Feed Through a Grader — 入力と出力をグレーダー(採点器)に通してスコアを得る(例:10 / 4 / 9。次レッスン以降

3つの関数の役割分担

講師は「各関数が何をするか」を明確な docstring 付きで骨組みだけ先に用意している:

関数 役割
run_prompt(test_case) プロンプトとテストケース入力をマージし、Claude でテキスト生成して結果を返す
run_test_case(test_case) run_prompt を呼び、結果を採点(今回は TODO)し、内容をまとめた辞書を返す
run_eval(dataset) データセットをループし、各テストケースで run_test_case を呼んで結果を集約する

run_prompt の実装(V1 プロンプトは最小構成)

def run_prompt(test_case):
    """Merges the prompt and test case input, then returns the result"""
    prompt = f"""
Please solve the following task:

{test_case["task"]}
"""

    messages = []
    add_user_message(messages, prompt)
    output = chat(messages)
    return output
  • 「できる限りシンプルに始める」方針なので、V1 プロンプトは「Please solve the following task:」+ f-string で test_case["task"] を差し込むだけ。
  • フォーマット指示を一切入れていないため、Claude からは頼んだ以上の余計な出力(説明文など)が大量に返ってくる。本来の狙いは「Python コードだけ/JSON だけ/正規表現だけ」を返させることなので、後で必ず改善に戻ってくる前提。

run_test_case の実装(採点はハードコードの仮置き)

def run_test_case(test_case):
    """Calls run_prompt, then grades the result"""
    output = run_prompt(test_case)

    # TODO - Grading
    score = 10

    return {
        "output": output,
        "test_case": test_case,
        "score": score
    }
  • 採点(grading)は未実装なので # TODO - Grading として score = 10 をハードコード
  • 戻り値は「そのテストケースの実行で起きたこと全部」をまとめた辞書:Claude の出力・元のテストケース・スコア。

run_eval の実装(ループして集約するだけ)

def run_eval(dataset):
    """Loads the dataset and calls run_test_case with each case"""
    results = []

    for test_case in dataset:
        result = run_test_case(test_case)
        results.append(result)

    return results
  • 最初は結果を print するつもりだったが、その場で「return する方が良い」と判断して return results に変更。

実行と結果の確認

with open("dataset.json", "r") as f:
    dataset = json.load(f)

results = run_eval(dataset)
  • 上のセルを全て再実行してからこのセルを実行。初回実行はかなり時間がかかる(Haiku を使っても講師の環境で約31秒)。高速化テクニックは後のレッスンで扱う。
  • 結果は大きな JSON なので print(json.dumps(results, indent=2)) で整形出力。
  • 出力はオブジェクトの配列で、各オブジェクトが1テストケース分:output(Claude の生の出力・大量)/test_case(基になったテストケース定義)/score(今は一律10)。
  • これで「データセット×テストプロンプト→Claude 出力→収集」までが完成。残るはグレーダーに通す工程のみ(次レッスン)。

実務ポイント(このレッスンから持ち帰るもの)

  • eval パイプラインの本体は驚くほど少ないコードで書ける。run_prompt(マージ+生成)/run_test_case(1件実行+採点+辞書化)/run_eval(ループ+集約)の3関数構成は、そのまま実務のテンプレートになる。
  • 採点が未実装でもパイプラインは先に通すscore = 10 のハードコードのように仮の値を置いて end-to-end で動かし、グレーダーは後から差し替える段階的な作り方が有効。
  • run_test_case の戻り値は output / test_case / score を1つの辞書に同梱する。後段の分析・デバッグで「どの入力にどの出力とスコアが対応するか」を追える形で残すのが定石。
  • V1 プロンプトは最小構成で開始する。フォーマット指示がなければ余計な出力が返るのは織り込み済み——eval が整ってから「Python/JSON/正規表現だけを返す」ようプロンプトを改善していく(改善効果は eval のスコアで測れるようになる)。
  • eval の実行は遅い(Haiku でも3件で約31秒)。初回から並列化を考えるのではなく、まず直列で動かし、高速化(後のレッスンで解説)は別工程として扱う。
  • 結果の確認は print(json.dumps(results, indent=2)) の整形出力が手軽。大きな JSON はインデント付きで人間が読める形にしてから目視確認する。
  • データセットの読み込みは with open("dataset.json", "r") as f: dataset = json.load(f)run_eval は「ファイルを読み込む」か「引数で受け取る」かどちらの設計でもよい。

本文(時系列・日本語逐語+画面差し込み)

[0:00] データセットの生成が完了したので、次はそのデータセットの中の全てのレコード——これを「テストケース」と呼ぶことにします——を取り出す必要があります。つまり、各テストケースを取ってプロンプトとマージします。そしてその結果を Claude に流し込みます。

🖥 [0:00] 画面(スライド「Feed Through Claude」ワークフロー図) 上部バナー:Draft a Prompt → Create an Eval Dataset → Feed Through Claude(現在地) 左のコードカード: ```python prompt = f""" Please answer the user's question:

{question} """ `` 中央の表「Question」:What's 2+2? / How do I make oatmeal? / How far away is the Moon? 右の枠内:3つの入力ボックスがそれぞれ矢印で **Claude** に入り、出力が返る: -Please answer the user's question: What's 2+2?2 + 2 = 4-Please answer the user's question: How do I make oatmeal?Add hot water to dry oatmeal.-Please answer the user's question: How far away is the Moon?The Moon is about 384,400 KM away.`

[0:13] そして、これらの様々な出力が全部揃ったら、それをグレーダー(採点器)に通します。思い出してください、グレーダーについてはまだ説明していません。心配しないでください、すぐに出てきます。

🖥 [0:16] 画面(スライド「Feed Through a Grader」) バナーが1段進む:Draft a Prompt → Create an Eval Dataset → Feed Through Claude → Feed Through a Grader 中央に「Responses」列が追加:2 + 2 = 4Add hot water to dry oatmeal.The Moon is about 384,400 KM away. 右の枠内:Question+Answer のペアが Grader に入り、数値スコアが出る: - Question: What's 2+2? / Answer: 2 + 2 = 410 - Question: How do I make oatmeal? / Answer: Add hot water to dry oatmeal.4 - Question: How far away is the Moon? / Answer: The Moon is about 384,400 KM away.9

[0:22] そして今回もまた、少し時間を節約するために、ワークフローのこの次のフェーズを導くためのちょっとしたコードを、あらかじめここに書き出しておきました。3つの別々の関数を用意して、それぞれが何をするかについて非常に明確なコメントを付けてあります。

🖥 [0:23] 画面(スライド→VS Code ノートブックへ遷移) 「Feed Through Claude」のスライドに戻った後、画面が横スライドで VS Code(001_Prompt_Evals.ipynb — prod)へ切り替わっていく。

[0:35] 一番理解しやすい最初のものは run_prompt 関数です。これはテストケース、つまり先ほど生成したあの JSON オブジェクトとともに呼び出されます。これらの1つ1つがテストケースです。ですから、これらが1つずつ順番に run_prompt 関数へ流れ込んでいくとイメージしてください。

🖥 [0:30] 画面(VS Code:dataset.json タブ:001_Prompt_Evals.ipynbdataset.json(フォルダ 06)。前レッスンで生成したデータセット: json [ { "task": "Create a Python function to validate an AWS access key ID format, ensuring it st…(右端切れ) }, { "task": "Write a JSON schema to validate the structure of an AWS Lambda function configur…(右端切れ) }, { "task": "Develop a regex pattern to extract the AWS region from an ARN (Amazon Resource N…(右端切れ) } ]

[0:52] さて、この関数の中でのゴールは、生成したあのタスクを私たちのプロンプトとマージし、Claude でテキストを生成して、その結果を返すことです。では早速やってみましょう。

🖥 [1:00] 画面(ノートブック:3つの関数の骨組み) 001_Prompt_Evals.ipynb(.venv Python 3.12.9)。直前のセル末尾に json.dump(dataset, f, indent=2)[9] ✓ 3.6s)。その下に3つの空実装セル: ```python def run_prompt(test_case): """Merges the prompt and test case input, then returns the result""" prompt = """

""" python def run_test_case(test_case): """Calls run_prompt, then grades the result""" pass python def run_eval(dataset): """Loads the dataset and calls run_test_case with each case""" pass ```

[1:03] ここに私の V1 プロンプトを入れます。とてもシンプルなものです。思い出してください、ここでは可能な限りシンプルに始めます。ですから、単に「please solve the following task(次のタスクを解いてください)」のような文言にします。そしてこれを f-string にして、test_casetask を差し込みます。

[1:27] 次に、これを Claude に送りたいと思います。そこでメッセージのリストを作ります。ユーザーメッセージを追加します。それから chat を呼ぶことで、これを Claude に渡します。すると何らかの結果のテキスト、結果が返ってきます——今回は output と呼ぶことにしましょうか。そして今のところは、その output をそのまま返すだけにします。

🖥 [1:30] 画面(run_prompt の実装途中) ```python def run_prompt(test_case): """Merges the prompt and test case input, then returns the result""" prompt = f""" Please solve the following task:

{test_case["task"]} """

messages = []
add_user_message(messages, prompt)
text = chat(messages)

`` (直後に変数名textoutput` に変えて確定させる)

🖥 [1:50] 画面(run_prompt 完成形) ```python def run_prompt(test_case): """Merges the prompt and test case input, then returns the result""" prompt = f""" Please solve the following task:

{test_case["task"]} """

messages = []
add_user_message(messages, prompt)
output = chat(messages)
return output

```

[1:50] さて、思い出してください。今のところプロンプトの中には、いかなる種類のフォーマット指定もフォーマット指示も入っていません。ですから、おそらく私たちが求めた以上に多くの出力が返ってくることになるでしょう。私たちのプロンプトの本当のゴールは、Python だけ、JSON だけ、あるいはあの正規表現だけを確実に得られるようにすることです。そして今はそのための何も用意していません。ですから、ほぼ間違いなく後で戻ってきて改善を加えることになります。

[2:11] ですが今のところは、少なくとも run_prompt のスタートは切れました。次に取り組むのは run_test_case です。この関数のゴールは、個々のケースを1つ受け取り、先ほど組み立てた run_prompt 関数を呼び、Claude から何らかの出力を得て、その結果を採点し、そこで起きたこと全てを記述する辞書を返すことです。

[2:33] これは非常に複雑に聞こえますが、実際には驚くほどシンプルです。ここでやるべきことを全てお見せしましょう。output を入れます。これは、ついさっき取り組んでいた関数——この run_prompt 関数を呼ぶことから得られます。テストケースを渡します。そしてここで何らかの採点を行います。それは TODO とします。

🖥 [2:38] 画面(run_test_case の実装開始) python def run_test_case(test_case): """Calls run_prompt, then grades the result""" output =

[2:56] 今のところは、ハードコードされたスコア10がある、ということにしておきます。ですから、後で必ず戻ってきて、ここで大量の重労働をやらなくてはなりません。そして一番下では、このテストケースの実行に関する全てを要約する情報を返すだけです。そこで、output を持つ辞書を返します。これは Claude から返ってきたもの、そのものです。テストケースと、それから私たちのスコアも含めます。

🖥 [3:00] 画面(run_test_case:TODO とハードコードのスコア) ```python def run_test_case(test_case): """Calls run_prompt, then grades the result""" output = run_prompt(test_case)

# TODO - Grading
score = 10

return {
    "output": output,

}

```

🖥 [3:30] 画面(run_test_case 完成形) ```python def run_test_case(test_case): """Calls run_prompt, then grades the result""" output = run_prompt(test_case)

# TODO - Grading
score = 10

return {
    "output": output,
    "test_case": test_case,
    "score": score
}

`` セル実行[10]✓ 0.0s。下にrun_evalのスタブ(docstring+pass`)が見えている。

[3:25] そして最後のステップとして、run_eval を実装しなければなりません。この関数はデータセットを読み込むか、あるいは引数として受け取ります。どちらでも構いません。そしてそのデータセットをループします。全てのテストケースについて run_test_case を呼び、結果を全部まとめ上げるだけです。

[3:42] 実装としては、results と書きます。これは空のリストとして始まります。そして dataset の中の全ての test_case について、結果を取得します——run_test_case を呼んでテストケースを渡すことで、です。そしてそれを results のリストに追加します。そしてこの下で、結果を全部 print することにします。……いや、実は、ちょっと待ってください。やはり results を return することにしましょう。その方が少し良いと思います。

🖥 [4:00] 画面(run_eval の実装途中) ```python def run_eval(dataset): """Loads the dataset and calls run_test_case with each case""" results = []

for test_case in dataset:
    result = run_test_case(test_case)
    results.append(result)

return res

`` (return resまで入力中。この直後return results` で確定)

[4:18] よし、これで3つの主要関数のアウトラインができました。信じられないかもしれませんが、これが eval パイプラインの大部分と言っていいものです。採点という明白な例外を除けば、たった今その大部分を組み上げたのです。ご覧の通り、これに必要なコードは大した量ではありません。では、テストしてみましょう。

🖥 [4:30] 画面(run_eval 完成+実行セルの書き始め) ```python def run_eval(dataset): """Loads the dataset and calls run_test_case with each case""" results = []

for test_case in dataset:
    result = run_test_case(test_case)
    results.append(result)

return results

`` その下の新しいセルにwith open("da")` と入力中。

[4:38] それでは、この下の次のセルで、私たちのデータセット JSON ファイルを開きます。そして JSON としてパースします。それから run_eval 関数を呼びます——ちょうどここで組み立てていたあの関数です——データセット全体を渡して。最後に、その結果を results に代入します。

🖥 [5:00] 画面(実行セル・実行中) ```python with open("dataset.json", "r") as f: dataset = json.load(f)

results = run_eval(dataset) `` セル[26]` が実行中(経過 0.7s、ツールバーが Interrupt / Go To 表示に変化)。

[5:08] 上のセルを全部実行し終えたことを確認するために、上のセルを全て再実行します。それからこのセルを実行します。何が起きるか見てみましょう。それと、これを初めて実行する時は、かなりの時間がかかります。たとえ Haiku を使っていても、私の場合 Haiku でこれを完了するのに31秒ほどかかることになります。eval の実行時間を高速化するテクニックをいくつかお見せする予定ですが、今のところは少し長めにかかるままにしておきます。でも心配しないでください、ちゃんと高速化します。

[5:34] では results を見て、何が入っているか確認しましょう。results はかなり大きな JSON オブジェクトになるので、print と json.dumps に results と indent=2 を渡して、きれいに整形して出力します。はい、できました。

🖥 [5:30] 画面(実行完了と print セル) 実行セルが [26]31.2s で完了。その下の新しいセルに入力: python print(json.dumps(results, indent=2))

[5:49] これでオブジェクトの配列が得られました。それぞれのオブジェクトが、個々のテストケース1件分の出力を表しています。output がまさにここにあります。これは Claude から返ってきた出力で、ご覧の通り、ここでは大量のものが生成されています。

🖥 [5:51] 画面(結果 JSON の整形出力・1件目の output [27] ✓ 0.0s の出力。配列の先頭オブジェクト(一部抜粋・\n はエスケープのまま表示): [ { "output": "Here's a Python function that validates an AWS IAM username based on the specified naming convention:\n\npython\nimport re\n\ndef validate_aws_iam_username(username):\n \"\"\"\n Validate an AWS IAM username.\n \n Rules:\n - Must start with a letter\n - Can include letters, numbers, and these specific characters: =,.@-\n - Total length between 1-64 characters\n \n Args:\n username (str): The username to validate\n \n Returns:\n bool: True if username is valid, False otherwise\n \"\"\"\n # Regular expression pattern for AWS IAM username validation\n pattern = r'^[a-zA-Z][a-zA-Z0-9=,.@-]{0,63}$'\n \n # Check if username matches the pattern\n return bool(re.match(pattern, username))\n\n# Test cases\ndef test_username_validation():\n # Valid usernames\n valid_usernames = [\n \"john.doe\",\n \"jane-smith\",\n \"user123\",\n \"admin@company\",\n \"user=name\",\n \"a\", # Minimum length\n \"x\" * 64 # Maximum length\n ]\n \n # Invalid usernames\n invalid_usernames = [\n \"\", # Empty string\n \"1startWithNumber\", # Starts with number\n \"user name\", # Contains space\n \"user_underscore\", # Invalid special character\n \"x\" * 65, # Exceeds max length\n \"!invalidSpecialChar\"\n ]\n \n print(\"Validating usernames:\")\n \n(以下続くが省略) ```

[5:58] そして少し下にスクロールすると、これが基にしたテストケースの定義が見えます。それからスコアです。繰り返しますが、これは今のところ単に10でハードコードされているだけです。そしてこれが延々と繰り返されていきます。

🖥 [6:04] 画面(結果 JSON:test_casescore、次のオブジェクト) output の末尾(…This solution provides:\n\n1. Avalidate_aws_iam_username()function that:\n - Uses a regular expression to validate the username\n - Ensures the username starts with a letter\n - Allows letters, numbers, and specific characters (=,.@-)\n - Enforces a length between 1-64 characters\n\n2. A test function to demonstrate various valid and invalid usernames…You can use this function to validate AWS IAM usernames in your application or script.")に続いて: json "test_case": { "task": "Create a Python function to validate an AWS IAM username follows the naming convention (start with a letter, can include letters, numbers, plus these specific characters: =,.@-)", "type": "Python" }, "score": 10 }, { "output": "Here's a comprehensive JSON configuration object for an AWS S3 bucket:\n\njson\n{\n \"bucketName\": \"my-company-data-bucket\",\n \"region\": \"us-east- 1\",\n \"accessControl\": {\n \"publicAccessBlockConfig\": {\n \"blockPublicAcls\": true,\n \"blockPublicPolicy\": true,\n \"ignorePublicAcls\": true,\n \"restrictPublicBuckets\": true\n },\n \"defaultAccessLevel\": \"private\",\n \"allowedPrincipals\": [\n {\n \"type\": \"IAM\",\n \"arn\": \"arn:aws:iam::123456789012:role/data-access-role\"\n }\n ]\n },\n \"versioning\":(以下略) `` (画面上では"score": 10` がハイライト選択されている)

[6:15] さてこれで、この時点で、まさにこのステップを無事に通過したことになります。データセットとテストプロンプトをマージし、Claude から出力を得て、それらを一式まとめ上げました。ですから、ここで本当にやるべき最後のことは、入力と、Claude から得た結果を取って、これらの様々なグレーダーのどれかに流し込むことです。というわけで、ついにグレーダーについて学び始める時が来ました。次のビデオでグレーダーについて議論を始めます。

🖥 [6:17] 画面(ノートブック→スライドへ遷移) 結果 JSON の画面が横にスライドし、ワークフロー図のスライドに戻る。

🖥 [6:28] 画面(スライド「Feed Through a Grader」再掲) [0:16] と同じグレーダーのスライド。Responses 列(2 + 2 = 4 ほか)と、Question+Answer → Grader → スコア 10 / 4 / 9 の図で締め、次レッスン(グレーダー実装)へつなぐ。

英語逐語(ASR全文)

With our dataset generation complete, we now need to take every record in that dataset, which we're going to refer to as a test case. So we're going to take each test case and merge it with a prompt. We're then going to take the result and feed it into clod. And then once we have all these different outputs, we're going to feed them through our grader. Remember, we have not discussed graders just yet. Don't worry, that's going to come up very quickly.

And once again, just to save a little bit of time, I've written out a little bit of code here just to help guide this next phase of our workflow. I put together three separate functions with a very clear comment on what each one does. The first one that's easiest to understand is the run prompt function. This is going to be called with a test case and those JSON objects that we generated just a moment ago. Each of these is a test case. So you can imagine that each of these one by one are going to flow into the run prompt function.

So inside of here, our goal is to merge that task that we generated with our prompt, generate some text with Claude, and then return the result. So let's do that right away. I'm going to put in my V1 prompt right here, which is very simple. Remember, we are starting off as simple as possible here. So it'll just say something like, please solve the following task. And I want this to be a F string and I'm going to put in test case task.

Next up, we want to send this off to Claude. So I'll make a list of messages. I'll add in a user message. I'm then going to pass this off to Claude by calling chat. And we're going to get back some resulting text, some results, or maybe we'll call it output this time around. And then for right now, I'll just return the output.

Now, remember, we don't have any kind of formatting included or any formatting instructions inside the prompt right now. So we're going to probably get back a lot more output than we ever asked for. The real goal of our prompt is to make sure that we get just Python or JSON or that regular expression. And we don't have anything for that right now. So we'll almost definitely have to come back and make some improvements. but for right now we at least have a start to run prompt.

The next thing we're going to work on is run test case. The goal of this function is to take in one of those individual cases, call the run prompt function we just put together, get some output from Claude, and then grade the result and return a dictionary describing everything that happened there. Now that sounds really complicated but in reality it's going to be surprisingly simple. So let me show you all we have to do here. We're going to put in output. That's going to come from calling the function that we were just working on a moment ago. So this run prompt function, put in our test case, and then we're going to do some grading right here. That's going to be a to do. For right now, I'll just say that we have a hard-coded score of 10. So we definitely have to come back and do a lot of heavy lifting right there. And then at the bottom, we're just going to return some information that summarizes everything about running this test case. So I will return a dictionary with some output. It's going to be whatever we got back from Claude. I'll include the test case and then our score.

And then one final step here, we have to implement run eval. So this function is going to load up our dataset or receive it as an argument. Either one is fine. And then we're going to loop through that data set. And for every test case, we will call run test case and then just assemble all the results together. So for our implementation here, I'll say results. It's going to start off as an empty list. And for every test case in data set, I'm going to get our results. From calling run test case and passing the test case to it. and then add that into our list of results. And then down here, I'm just going to print up all of our results. Actually, you know what? Let's actually just go ahead and return results. I think that's a little bit better.

Okay, so there's the outline for our three major functions. Now, believe it or not, this is like a vast majority of what a eval pipeline is. We just put together the vast majority, with the obvious exception of grading. So as you can see, there's not a whole lot of code that goes into this. Let's now test this out.

So down here in the next cell down, I'm going to open up our dataset JSON file. And parse it as JSON. And then call the run eval function. That's the one that we were just putting together right here. with the entire data set. Finally, I'm going to assign the results of that to results. I'm going to rerun all the cells above just to make sure that I executed all of them. And then I'm going to run the cell. We'll see what happens. And just, you know, the first time you run this, it is going to take a pretty good amount of time. Even if you are using Haiku, it's going to end up taking me about 31 seconds to complete this with Haiku. I'm going to show you some techniques for speeding up our eval runtime, but for right now, we're just going to have it take a little bit longer, but don't worry, we will speed it up.

So now let's take a look at results and see what we have. Results is going to be a rather large JSON object, so I'm going to print it out really nicely with a print JSON dumps with results and an indent of two. There we go. So now we get an array of objects. Every object represents the output from one of our individual test cases. I've got the output right here. That's the output coming from Claude, and we can see there was a lot of stuff generated here. And if I scroll down a little bit, I'll see the definition of the test case that this was based upon. And then the score, which again, right now is just hard coded at 10. And then that's just going to repeat over and over again.

All right. So at this point in time, we have successfully gone through this step right here. We merged together our data set with our test prompt, and we got some output from Claude and we kind of collated all this stuff together. So now the last thing we really have to do here is take the input and the result that we got out of clod and feed it into one of these different graders. So this finally is the time where we're going to start to learn about graders. We're going to start to discuss them in the next video.

↑ 目次へ戻る

Building with the Claude API | Lesson 18「Model based grading」(全文ナレッジ)

動画 10分01秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンのテーマ

プロンプト評価(eval)ワークフローに採点システム(grading system)を組み込む。grader はモデルの出力を受け取り、客観的なシグナル(数値・真偽値など。最頻出は 1〜10 のスコア、10=高品質・1=低品質)を返す仕組み。本レッスンでは特に model-based grading(LLM-as-a-judge)を実装する。

3種類の grader(Code / Model / Human)

種類 仕組み 得意な評価
Code 自作コードで出力をプログラム的に検査 出力長のチェック、特定単語の有無、構文検証(JSON/コード)、可読性スコア
Model 元のモデル呼び出しの出力を別のモデル(追加の API リクエスト)に渡して採点させる 応答の一般品質、指示追従の質、網羅性、有用性、安全性 — 非常に柔軟
Human 出力を実際の人間の前に並べて評価してもらう どんな観点でも評価可能(最も柔軟)だが、時間がかかり退屈な作業

いずれの方式でも共通の要件はただ1つ:使える「ハードな客観シグナル」を返すこと(通常は 1〜10 の数値。ただし厳格な必須要件ではない)。

評価基準(Evaluation Criteria)は先に決める

どの grader を使うにせよ、「応答のどの側面を見るのか」を前もって決める必要がある。本コースのユースケース(Python / JSON / regex 生成プロンプト)では次の3基準:

  1. Format — Python・JSON・regex のみを返すこと(Claude による余計な説明文が付かない)
  2. Valid Syntax — 生成された Python・JSON・regex の構文が正しいこと(typo 等がない)
  3. Task Following — ユーザーのタスクに直接・明確に答えており、生成コードが概ね正確で、大きな誤り・ロジックミスがないこと

grader の割り当て:1と2(フォーマット検証・構文検証)は code grader で機械的に判定できる。3(タスク追従の一般評価)は柔軟性が要るので model grader が適切。

grade_by_model() の実装 — grader プロンプト設計

実装は「意外にも model grader の方が簡単」なので先に作る。run_test_case() 内の # TODO – Grading の直上に新セルを追加:

def grade_by_model(test_case, output):
    eval_prompt = f"""
You are an expert AWS code reviewer. Your task is to evaluate the following AI-generated solution.

Original Task:
<task>
{test_case["task"]}
</task>

Solution to Evaluate:
<solution>
{output}
</solution>

Output Format
Provide your evaluation as a structured JSON object with the following fields, in this specific order:
- "strengths": An array of 1-3 key strengths
- "weaknesses": An array of 1-3 key areas for improvement
- "reasoning": A concise explanation of your overall assessment
- "score": A number between 1-10

Respond with JSON. Keep your response concise and direct.
Example response shape:
{{
    "strengths": string[],
    "weaknesses": string[],
    "reasoning": string,
    "score": number
}}
"""

    messages = []
    add_user_message(messages, eval_prompt)
    add_assistant_message(messages, "```json")
    eval_text = chat(messages, stop_sequences=["```"])
    return json.loads(eval_text)

grader プロンプトの構成要素(これが「最低限」): 1. ロール設定(あなたはエキスパートの AWS コードレビュアー) 2. AI 生成ソリューションを評価せよという明確な依頼 3. タスク本文{test_case["task"]} を XML タグで挿入) 4. 評価対象のソリューション{output} を XML タグで挿入) 5. 応答方法の指示(strengths / weaknesses / reasoning / score の構造化 JSON+例示の shape)

スコアだけ求めると「6」ばかり返る問題

score 単体を要求すると、モデルは「良くも悪くもありうる…6にしておこう」と中庸なスコア(6)を頻発する。strengths・weaknesses・reasoning を一緒に出させることで、モデルに評価を絞り込ませ、より具体的で確信のあるスコアを出させられる。これが grader プロンプトの重要テクニック。

JSON 抽出は「pre-filled assistant message + stop sequence」

grader の応答は JSON なので、既習のテクニックをそのまま再利用:add_assistant_message(messages, "```json") で応答冒頭を事前充填し、stop_sequences=["```"] で閉じフェンスで停止 → json.loads(eval_text) でパースして辞書を返す。

run_test_case への組み込みと平均スコア

def run_test_case(test_case):
    """Calls run_prompt, then grades the result"""
    output = run_prompt(test_case)

    model_grade = grade_by_model(test_case, output)
    score = model_grade["score"]
    reasoning = model_grade["reasoning"]

    return {
        "output": output,
        "test_case": test_case,
        "score": score,
        "reasoning": reasoning,
    }
  • 返り値辞書には strengths / weaknesses も入っているので必要なら同様に取り出せる(例を簡潔にするため今回は score と reasoning のみ)。
  • 評価の再実行は約22秒。結果には各テストケースのスコア(今回は 8 / 7 / 6)と、その根拠 reasoning が付く。

最後に run_eval() で全スコアを平均して単一の客観メトリクスにする:

average_score = mean([result["score"] for result in results])
print(f"Average score: {average_score}")

meanstatistics パッケージから import。)実行結果は Average score: 7.333333333333333。model grader は時に気まぐれ(capricious)だが、改善対象として注視できるスコアがついに手に入った。

実務ポイント(このレッスンから持ち帰るもの)

  • grader は code / model / human の3種。フォーマット・構文など機械判定できるものは code に、一般品質・タスク追従など柔軟な判断は model に、最終品質確認は human に、と基準ごとに使い分ける
  • どの grader でも要件は1つ:ハードな客観シグナル(通常 1〜10 の数値)を返すこと。まず評価基準を先に定義してから grader を選ぶ。
  • model grader(LLM-as-a-judge)のプロンプトは「ロール → 評価依頼 → タスク → ソリューション → 応答形式の指示」の5点セットが最低限の型。タスクとソリューションは XML タグで区切って挿入する。
  • score 単体を要求すると「6」に収束しがち。strengths / weaknesses / reasoning を必ず一緒に出させると、スコアが具体化・先鋭化する(LLM-judge 設計の定石)。
  • grader からの JSON 抽出は pre-filled assistant message("json")+ stop_sequences(""])+ json.loads() の既習パターンをそのまま再利用できる。
  • grader 呼び出しは grade_by_model(test_case, output) のようにテストケースと元出力の両方を渡す(タスク文脈なしでは採点できない)。
  • 個別スコアは statistics.mean平均して単一メトリクス化する。プロンプト改善のたびにこの数値を追えば、改善が定量化できる。
  • model grader は「気まぐれ」なこともある前提で、必要なら採点ガイダンスをプロンプトに追加して精度を上げる余地を残す。

本文(時系列・日本語逐語+画面差し込み)

[0:00] 私たちのプロンプト評価ワークフローの中に次に実装するのは、採点システム(grading system)です。復習しておくと、grader はモデルから出てくる何らかの出力を受け取ります。そして私たちの期待は、grader が何らかの客観的なシグナルを返してくれることです。それは数値かもしれませんし、true / false の値かもしれません。

🖥 [0:00] 画面(ノートブック → スライドへズームアウト遷移) 冒頭はノートブック 001_Prompt_Evals.ipynbrun_test_case# TODO – Grading / score = 10 を含む前レッスン末尾の状態)が映っており、約5秒でズームアウトして評価パイプラインのスライドに切り替わる。

スライド(4段パイプライン、最終段がハイライト): Draft a Prompt → Create an Eval Dataset → Feed Through Claude → Feed Through a Grader 左側の各列(前レッスンまでの内容): ``` prompt = f""" Please answer the user's question:

{question} """ `` Question 列:What's 2+2? / How do I make oatmeal? / How far away is the Moon? Responses 列:2 + 2 = 4Add hot water to dry oatmeal.The Moon is about 384,400 KM away.`

右側「Feed Through a Grader」の図:Question / Answer をまとめた3枚のカードが Grader ボックスに入り、それぞれスコア 1049 が出力される。 ``` Question: What's 2+2? → Grader → 10 Answer: 2 + 2 = 4

Question: How do I make oatmeal? → Grader → 4 Answer: Add hot water to dry oatmeal.

Question: How far away is the Moon? → Grader → 9 Answer: The Moon is about 384,400 KM away. ```

[0:15] 本当に何でもよいのですが、非常によく、非常に頻繁に見かけるのは 1 から 10 の間の数値出力です。10 は非常に高品質な出力が得られたことを意味し、1 は非常に低品質な出力が得られたことを意味します。繰り返しますが、これは必須要件ではありません。これらの grader から数値を取り出さなければならないわけではありませんが、非常によく見かける一般的な慣行です。

[0:32] このビデオでは、3つの異なる種類の grader について説明します。code、model、human です。まず、code ベースの grader とは何なのかを理解しましょう。

🖥 [0:34] 画面(スライド「Sample Dataset」— ページ送りで一瞬経由) Graders スライドへ切り替える途中、前レッスンの「Sample Dataset」スライドが一瞬表示される。 左のダークボックス(dataset.json のサンプル): [ { "task": "Create a Python function to extract the AWS account ID from an ARN" }, { "task": "Write a JSON policy document that allows read-only access to a specific S3 bucket" } …many more… ] 右側「Eval Dataset」: - Each object contains a "task" that we will merge into the prompt - Can assemble by hand or with Claude - If using Claude, consider Haiku!

🖥 [0:35] 画面(スライド「Graders」) 3列構成。

Code</> アイコン) - Programmatically evaluate the result - Useful for: - Checking output length - Verifying output does/doesn't have certain words - Syntax validation - Readability scores

Model(Claude のスターバーストアイコン) - Ask a model to assign a score to the output, or compare two versions - Useful for: - Response quality - Quality of instruction following - Completeness - Helpfulness - Safety

Human(人物アイコン) - Ask a human to assign a score to the output, or compare two versions - Useful for: - General response quality - Comprehensiveness - Depth - Conciseness - Relevance

[0:43] code ベースの grader では、モデルからの出力を受け取り、あなたと私が自分で書くコードスニペットに流し込みます。このコードの中では、想像できるほぼあらゆる種類のプログラム的なチェックができます。たとえば、モデルからの出力が長すぎたり短すぎたりしないかを検証するかもしれません。出力に特定の単語が含まれている、あるいは含まれていないことを確認するかもしれません。

[1:02] JSON やコードを返している場合には、プログラムで構文検証(syntax validation)ができますし、可読性スコア(readability score)を実装するといった、もっと複雑なチェックさえ可能です。つまり、生成されたテキストが私たちの特定のユースケースにとって適切な読解レベルにあることを確認するのです。

[1:17] ここでの唯一の要件は、このコードを実行したとき、実際に使える何らかのシグナルを返すことです。繰り返しますが、それは通常 1 から 10 の間の数値になりますが、厳格な必須要件ではありません。

[1:28] 次に非常によく見かける種類の grader は、model ベースの grader です。これは、元のモデル呼び出し——つまり既に実行した呼び出し——からの出力を受け取り、それを追加のモデルに流し込むものです。つまり、これはもう1回の API リクエストです。model grader を使うと、非常に大きな柔軟性が得られます。モデルに対して、応答をその一般的な品質に基づいて評価するよう頼むこともできますし、プロンプトの指示にどれだけ従ったか、応答の網羅性(completeness)、本当に想像できるほぼ何でも評価させられます。

[1:57] 繰り返しますが、ここでの唯一の実質的な要件は、モデルが何らかのハードで客観的なシグナル——通常は 1 から 10 の間の数値——を返してくれることです。

[2:02] そして最後に、human ベースの採点です。human ベースの採点では、モデルからのすべての出力を取り出して、実際の人間の前に並べます。この人が、これらの応答を何らかの特定のやり方で評価する役割を担います。想像のとおり、人間は非常に柔軟なので、ほぼどんなやり方でも、思いつく限りどんな指標についてでも応答を評価してもらえます。

[2:25] human ベースの採点の大きな欠点が1つあり、それは一般的にかなりの時間がかかることと、間違いなく非常に退屈な作業だということです。

[2:30] さて、どのスタイルの採点を使うにしても、評価基準(evaluation criteria)が何になるのかを前もって決める必要があります。言い換えれば、これらの応答のどの側面に注目するのか、正確に決めるということです。私たちの特定のユースケースについては、3つの異なる評価基準に絞り込みました。

🖥 [2:37] 画面(スライド「Evaluation Criteria」) 見出し:Evaluation Criteria/サブ見出し:How will we know if our prompt is producing good outputs?

3つのカード: - Format - Should return only Python, JSON, or Regex without explanation - Valid Syntax - Produced Python, JSON, and Regex should have valid syntax - Task Following - Response should directly and clearly address the user's task - Generated code should be accurate

[2:45] まず第一に、応答を評価して、Claude による追加の説明が一切付かずに、Python・JSON・正規表現だけが返ってきていることを確認すべきだと思います。第二に、その Python・JSON・regex が得られたときには、それが有効な構文を持っていることを確認すべきです。つまり、typo などがあってはいけません。

[3:06] そして最後に、一般的なタスク追従の確認をして、モデルがユーザーのタスクに明確に対処し、大きな誤りやロジックミスを含まない、概ね正確なコードでそれに答えたことを確認するのがよいでしょう。

[3:17] さて、この3つの評価基準について、最初の2つは code grader で評価できると思います。つまり、フォーマットを評価して、実際に Python・JSON・regex が得られたことをコードで確認できます。そして、そのコードの構文の検証も、まさに追加のコードを使って行えます。そして最後に、一般的な応答、つまりユーザーの質問に明確に対処できているかどうか——これは、その柔軟性を考えると、model grader を通じて対処するのがより適切でしょう。

🖥 [3:22] 画面(スライド「Code Grader / Model Grader」) 前スライドの3カード(Format / Valid Syntax / Task Following)の下に波括弧が付き、担当 grader が割り当てられる: Format + Valid Syntax }→ Code Grader Task Following }→ Model Grader

[3:46] それでは、まず model grader から実装を始めましょう。信じられないかもしれませんが、こちらの方が組み立てるのは簡単だからです。まず、ノートブックに戻るところから始めます。私たちの run test case 関数の中、ちょうどここに置いておいた TODO を見つけます。そして、そのセルのすぐ上に、grade by model と呼ぶことにする関数を持つ新しいセルを追加します。

🖥 [3:57] 画面(スライドからノートブックへズームイン遷移) 「Code Grader / Model Grader」スライドから VS Code の Jupyter ノートブック 001_Prompt_Evals.ipynb(タブ:001_Prompt_Evals.ipynb / dataset.json、カーネル .venv (Python 3.12.9))へ画面遷移。

🖥 [4:09] 画面(ノートブック:grade_by_model 用の新セル追加) 上のセル(前レッスンで作成済みの run_prompt 末尾、実行番号 [28]): python messages = [] add_user_message(messages, prompt) add_assistant_message(messages, "code") output = chat(messages, stop_sequences=[""]) return output 新規追加セル: python def grade_by_model(): その下の既存セル: ```python def run_test_case(test_case): """Calls run_prompt, then grades the result""" output = run_prompt(test_case)

# TODO – Grading
score = 10

return {"output": output, "test_case": test_case, "score": score}

```

[4:09] そして、私のテストケース辞書を渡すことを前提にします。テストケース辞書とは、要するにちょうどここにあるこれらのオブジェクトのことでしたね。これらのデータセットの各値、これが私たちのテストケースです。さらに、元のモデル呼び出しからの出力も渡すことにします。

🖥 [4:18] 画面(dataset.json タブ) dataset.json を開いてテストケースの中身を再確認: json [ { "task": "Create a Python function to validate an AWS IAM username follows the naming conv…(右に見切れ)" }, { "task": "Design a JSON configuration object that describes the basic settings for an AWS…(右に見切れ)" }, { "task": "Write a regex pattern to extract the AWS account ID from an ARN (Amazon Resource…(右に見切れ)" } ]

🖥 [4:25] 画面(ノートブック:引数の追加) python def grade_by_model(test_case, ): と入力中(第2引数を追記していく途中)。

[4:30] そしてこの中で、要するにモデルへの呼び出しを行い、出力を採点するよう頼みます。このためには、通常かなり長いプロンプトを書くことになります。そして、少し時間を節約するために、今回もプロンプトをコピー&ペーストで貼り付けます。

[4:45] はい、いきますよ。これを貼り付けます。

🖥 [4:45] 画面(オーバーレイ表示) ノートブック上に黒帯の全画面テロップ: You can copy the full prompt from the notebook attached to this lecture (このレクチャーに添付されたノートブックからプロンプト全文をコピーできます) 背後のセルは def grade_by_model(test_case, output): に更新済み。

[4:48] ええ、確かに少し長いのですが、これは私たちが欲しいものの、いわば最低限(bare minimum)です。

🖥 [4:56] 画面(ノートブック:貼り付けた grader プロンプト前半) ```python def grade_by_model(test_case, output): eval_prompt = f""" You are an expert AWS code reviewer. Your task is to evaluate the following AI-generated solu…(右に見切れ)

Original Task: {test_case["task"]}

Solution to Evaluate: {output}

Output Format Provide your evaluation as a structured JSON object with the following fields, in this speci…(右に見切れ) - "strengths": An array of 1-3 key strengths - "weaknesses": An array of 1-3 key areas for improvement ```

[4:53] さて、このプロンプトはロールを設定します。それから、AI が生成したソリューションを評価するよう、モデルに非常に明確に依頼します。次にタスクを出力します。次に、モデルによって生成されたソリューションを列挙します。そして、正確にどう応答すべきかについての指示をいくつか与えます。

🖥 [5:15] 画面(ノートブック:grader プロンプト後半「Output Format」) ``` Output Format Provide your evaluation as a structured JSON object with the following fields, in this speci…(右に見切れ) - "strengths": An array of 1-3 key strengths - "weaknesses": An array of 1-3 key areas for improvement - "reasoning": A concise explanation of your overall assessment - "score": A number between 1-10

Respond with JSON. Keep your response concise and direct. Example response shape: {{ "strengths": string[], "weaknesses": string[], "reasoning": string, "score": number }} """ ```

[5:13] この場合、私はモデルに、AI 生成ソリューションの強み(strengths)と弱み(weaknesses)のリストを、その背後にある推論(reasoning)と実際のスコアと一緒に出すよう求めています。

[5:23] さて、スコアだけを単体で求めることもできますが、そうすると、ただ「6」というスコアばかりが返ってくるのを非常によく目にすることになります。つまり、追加の strengths や weaknesses や reasoning を求めないと、非常にどっちつかずのスコアばかりが返ってくることが多いのです。モデルが「まあ、もっと良いかもしれないし、もっと悪いかもしれない。6にしておこう」と、いわば決め込んでしまうからです。

[5:42] モデルに reasoning・strengths・weaknesses を出すよう求めることで、モデルに評価を絞り込ませ、より具体的なスコアを決めさせることができるのです。

🖥 [5:45] 画面(ノートブック:Example response shape をハイライト) 貼り付けたプロンプト内の例示 shape("strengths": string[]"weaknesses": string[]"reasoning": string"score": number)をマウスで選択しながら解説。

[5:50] さて、プロンプトがここに入ったので、私たちの追加の採点モデルを呼び出します。そこで、そのすぐ下に、今回もまた messages リストを作ります。ユーザーメッセージを追加します。

🖥 [6:15] 画面(ノートブック:grader 呼び出しコードの入力) プロンプト定義の下に入力中: python messages = [] add_user_message(messages, eval_prompt) ad…(入力途中)

[6:02] そして、ここでは JSON が返ってくるので、今回もまた、事前充填した assistant メッセージ(pre-filled assistant message)と stop sequence を使って、それをきれいに抽出できるようにする必要があります。そこで、バッククォート3つ+json(```json)を持つ assistant メッセージを追加します。そして eval text を受け取ります。

[6:26] そして、messages と、stop sequence——正確には stop sequences——として今回も閉じバッククォートを渡して chat を呼び出します。さて、この eval text は、ちょうどここにあるこの構造を持つ JSON オブジェクトのはずです。

🖥 [6:45] 画面(ノートブック:grader 呼び出しコード完成) python messages = [] add_user_message(messages, eval_prompt) add_assistant_message(messages, "json") eval_text = chat(messages, stop_sequences=[""])

[6:43] ですので、それをパースして、そのまま返します。つまり、eval text を入れた json.loads を return します。

🖥 [7:00] 画面(ノートブック:grade_by_model 完成) python eval_text = chat(messages, stop_sequences=[""]) return json.loads(eval_text) ```

[6:51] はい、これが私たちの model grader です。少なくとも始めるにあたっては、本当にこれだけで済みます。さて次は、この grader を実際に呼び出すようにする必要があります。grader を呼び出すには、ちょうどここにある TODO まで下がっていきます。score を model grade に置き換えます。これは私たちの grade by model 関数から得られるものです。忘れないでください、テストケースと、実際にプロンプトを実行した出力とを渡さなければなりません。そしてここから、model grade から score を取り出します。

🖥 [7:15] 画面(ノートブック:run_test_case の TODO を置き換え) ```python def run_test_case(test_case): """Calls run_prompt, then grades the result""" output = run_prompt(test_case)

# TODO – Grading
model_grade = grade_by_model(test_case, )   ← 引数入力中

return {"output": output, "test_case": test_case, "score": score}

`` その後model_grade = grade_by_model(test_case, output)` に完成。

[7:26] さらに、返ってくるその辞書から、スコアの背後にある推論(reasoning)も取り出すことにします。

[7:35] 私たちが返しているこの model grade 辞書の中には、strengths と weaknesses のリストも入っています。必要ならそれらも間違いなく取り出せますが、今回は例を少し簡潔に保つことにします。

[7:47] score と reasoning を取り出して、この最終的な出力辞書に入れることにします。そこで、ここに追加のキーをいくつか足します。score の……ああ、score はもうそこにありますね。私のミスです。なので、それは要りません。ただ、reasoning は必要です。そして、それは reasoning になります。

🖥 [7:45] 画面(ノートブック:run_test_case 更新後) ```python def run_test_case(test_case): """Calls run_prompt, then grades the result""" output = run_prompt(test_case)

# TODO – Grading
model_grade = grade_by_model(test_case, output)

return {"output": output, "test_case": test_case, "score": score}

(この後、score の取り出しと `"reasoning": reasoning,` キーの追加を編集。更新後セルの末尾は次のとおり)python "reasoning": reasoning, } ```

[8:06] よし、良さそうです。それでは、これらのセルを確実に実行します。あれを実行して。run test case を更新して。それから、実際の評価を再実行します。これは完了までしばらくかかります。私の場合、今回は約22秒かかりました。

🖥 [8:15] 画面(ノートブック:セル再実行) ```python def run_eval(dataset): """Loads the dataset and calls run_test_case with each case""" results = []

for test_case in dataset:
    result = run_test_case(test_case)
    results.append(result)

return results

最下部の実行セル:python with open("dataset.json", "r") as f: dataset = json.load(f)

results = run_eval(dataset) ``` 実行時間 22.8s と表示。

[8:22] それでは、その結果を出力したら、何が得られたか見てみましょう。さあ、生成された出力がここにあります。そして少し下にスクロールすると、モデルによって生成されたスコアと、そのスコアの背後にある推論を見ることができます。この場合、私は 8 を得ました。悪くないですね。そして、これが 8 を得た理由です。さらに下に進むと、次のものは 7 でした。そして最後のものは 6 でした。

🖥 [8:29] 画面(ノートブック:results の JSON 出力) python print(json.dumps(results, indent=2)) 出力(1件目・IAM ユーザー名検証タスク): json [ { "output": "\ndef validate_iam_username(username):\n \"\"\"\n Validate an AWS IAM username according to AWS naming conventions.\n \n Rules:\n - Must start with a letter\n - Can include letters, numbers, and these specific characters: =,.@-\n - Length between 1 and 64 characters\n \n Args:\n username (str): The username to validate\n \n Returns:\n bool: True if username is valid, False otherwise\n …(略:isinstance チェック、長さ 1–64 チェック、regex パターン、テストケース群が続く)", "test_case": { "task": "Create a Python function to validate an AWS IAM username follows the naming convention (start with a letter, can include letters, numbers, plus these specific characters: =,.@-)" }, "score": 8, "reasoning": "The solution effectively validates AWS IAM usernames using a robust regex pattern. It checks critical requirements like starting with a letter, allowed character sets, and length constraints. The test cases demonstrate thorough validation across different scenarios." },

🖥 [8:33] 画面(ノートブック:2件目・3件目のスコア) スクロールして2件目(S3 バケット設定 JSON タスク)——"score": 7, をハイライト: json "test_case": { "task": "Design a JSON configuration object that describes the basic settings for an AWS S3 bucket (including name, region, access control, versioning)" }, "score": 7, "reasoning": "The solution provides a solid baseline S3 bucket configuration with strong security defaults and versioning. However, it lacks flexibility in policy management and doesn't fully address advanced configuration needs. The configuration covers core requirements but would benefit from more nuanced security and access controls." }, { "output": "\n^arn:aws:([^:]+):([^:]+):(\\d{12}):\n", "test_case": { "task": "Write a regex pattern to extract the AWS account ID from an ARN (Amazon Resource Name) string" }, "score": 6, "reasoning": "The regex pattern captures the basic structure of an AWS ARN but lacks robustness. It successfully identifies the account ID but over-captures unnecessary groups. A …(以下見切れ)" }

[8:49] さて、ここでおそらく最後にやりたいのは、これらのスコアをすべて取り出し、足し合わせ、平均を出して、それを出力することです。そうすれば、私たちのプロンプトが現在どれだけうまく機能しているかを教えてくれる、最終的でとても客観的なスコアが得られます。

🖥 [8:54] 画面(ノートブック:結果 JSON をスクロールして run_eval へ戻る) S3 バケット設定の output("bucketName": "my-example-bucket", "region": "us-east-1", "accessControl", "versioning", "encryption": SSE-S3, "lifecycleRules" 等を含む長い JSON 文字列)や IAM ユーザー名検証のテストコード("ValidUser123" # Valid / "123invalid" # Invalid (doesn't start with letter) / "a" * 65 # Invalid (too long) など)を通過しながら上方向へスクロール。

[8:59] そこで、これらすべてのスコアを平均して結果を出力するために、ちょうどここにある run eval 関数を見つけます。そして、内包表記(comprehension)で平均スコアを計算します。results の中の各 result について result の score を取り、それを mean 関数呼び出しでラップします。それから、statistics パッケージから mean 関数を import します。

🖥 [9:15] 画面(ノートブック:average_score の入力) ```python def run_eval(dataset): """Loads the dataset and calls run_test_case with each case""" results = []

for test_case in dataset:
    result = run_test_case(test_case)
    results.append(result)

average_score = [result["score"] for result ]   ← 内包表記を入力中

return results

```

[9:23] おっと、最後のステップがもうひとつ。その平均スコアを確実に出力するようにしましょう。print をします。「average score」、こんなふうに。

[9:30] よし、それではもう一度だけこのコードを実行して、すべてが期待どおりに動いていることを確認します。実行後、確かに 7.33 という平均スコアが得られているのが分かります。

🖥 [9:45] 画面(ノートブック:平均スコアの出力) ```python average_score = mean([result["score"] for result in results]) print(f"Average score: {average_score}")

return results

実行セル:python with open("dataset.json", "r") as f: dataset = json.load(f)

results = run_eval(dataset) 実行時間 24.1s、出力: Average score: 7.333333333333333 ```

[9:50] さて、これでついに、繰り返しますが、実際の客観的なメトリクスが手に入りました。ええ、これはモデルによって採点されていて、そのモデルは時々少し気まぐれ(capricious)かもしれませんし、採点の仕方についてもっと良いガイダンスを与えることもできるかもしれません。しかし少なくとも、私たちが注視し、引き上げようと試みることのできるスコアが手に入ったのです。

英語逐語(ASR全文)

The next thing we're going to implement inside of our prompt evaluation workflow is a grading system. Now, as a reminder, a grader is going to take in some output coming from our model. And then our hope is that the grader is going to give us some kind of objective signal. It might be a number or a true or false value. It can be really anything, but very commonly, very frequently, you're going to see a number output between 1 and 10, where 10 means that we got a very high-quality output, and 1 means we got a very low-quality output. Again, that's not a requirement. we don't have to get numbers out of these graders, but it's a very common practice you're going to see very often. There's three different kinds of graders that we're going to discuss in this video. Code, model, and human. Let's first figure out what code-based graders are all about.

With a code-based grader, we're going to take the output from our model and feed it into a snippet of code that you and I author. Inside this code, we can do just about any kind of programmatic check you can imagine. So we might verify to make sure that the output from the model was not too long or too short. We might make sure that the output does have or doesn't have certain words. If we are returning JSON or code, we can do syntax validation programmatically, and we can even do more complex checks like implement a readability score, where we make sure that the generated text is at an appropriate reading level for our particular use case. The only requirement here is that when we run this code, we return some kind of actual signal we can use. And again, usually that's going to be a number between one and 10, but that is not a hard requirement.

The next kind of grader that you're going to see very often is a model-based grader. This is where we take the output from our original model call, so the one that we already made, and we feed it into an additional model. So this is another API request. When we use a model grader, we get a tremendous amount of flexibility. We can ask a model to evaluate a response based upon its general quality, maybe how well it followed prompt instructions, maybe the completeness of the response, really just about anything you can imagine. Once again, the only real requirement here is that the model gives us back some kind of hard objective signal, usually as a number between one and 10.

And then finally, human-based grading. With human-based grading, we're going to take all the outputs from our model and then put them in front of an actual person. This person is then going to be in charge of evaluating these responses in some particular way. As you can imagine, humans are very flexible, so we can ask them to evaluate responses in just about any fashion or for any metric you can possibly imagine. The one big downside to human-based grading is that it generally does take a lot of time and it's certainly very tedious work.

Now, no matter what style of grading you are using, you need to decide upfront what your evaluation criteria is going to be. So in other words, exactly what aspects of these responses are you going to be focusing on? For our particular use case, I've centered on three different evaluation criteria. I think first off, we should evaluate the responses to make sure that we're only getting back Python, JSON, or regular expression without any additional explanation being provided by Claude. Secondly, whenever we get that Python, JSON, or reg, reg X, we should make sure that it has some valid syntax. So there should be no typos in there or anything like that. And then finally, we should probably do some general task following and make sure that the model clearly addressed the user's task and answered it with some generally accurate code that doesn't contain any major errors or logic mistakes. So for these three different evaluation criteria, I think we can evaluate the first two with a code grader. So we can evaluate the format and make sure we got actual Python, JSON, or regex with code. And we can also validate the syntax of that code using, well, additional code. And then finally, the general response and making sure that the user's question was clearly addressed, that would be more appropriate to address through a model grader, given its flexibility.

All right, let's start to implement first the model grader because that's believe it or not going to be the easiest one to put together. I'm going to first begin by going back over to my notebook. I'm going to find that to do we had put together right here inside of our run test case function. And then right above that cell, I'm going to add a new cell with a function that I'm going to call grade by model. And I'm going to assume that I'm going to pass in my test case dictionary. Remember the test case dictionary is essentially these objects right here. Each of these data set values, these are our test cases. I'm also going to pass in the output from our original model call. And then inside of here, we're going to essentially make a call off to a model and ask it to grade the output. So for this, we usually end up writing a fairly long prompt. And again, just to save us a little bit of time, I'm going to copy paste a prompt in.

So here we go.

I'm going to paste this in.

And yes, it is a little bit long, but this is kind of the bare minimum of what we want.

So this prompt is going to set a role.

It's then going to ask very clearly for the model to evaluate a AI generated solution.

We're then going to print out the task.

We're then going to list out the solution that was generated by the model.

And then we're going to provide some directions on exactly how to respond.

In this particular case, I'm asking the model to give me a list of strengths and weaknesses of the AI-generated solution, all along with some reasoning behind that and an actual score.

Now, we could just ask for a score by itself, but if you do so, you're going to see very often you tend to get scores of just six. So if you don't ask for any additional strengths or weaknesses or reasoning, you're going to very often just get very middling scores because the model kind of assumes, well, could be better, could be worse.

We'll give it a six.

By asking the model to provide some reasoning, strengths, and weaknesses, you really make it hone in and decide upon a more concrete score.

So now that we have that prompt in here, I'm going to call our additional grading model.

So right underneath it, I'll again make a messages list.

I'll add in a user message.

and then because we are getting back some json here we need to once again make sure we extract that cleanly by using a pre-filled assistant message and a stop sequence so we'll add a assistant message with factix json and then i'll get back some eval text and we'll call chat with messages and a stop sequence or stop sequences of once again closing backticks now this eval text should be a json object with this kind of structure right here so i'm going to parse that and just return it so i'll return a json.loads with eval text

okay so that is our model grader that's really all it takes to at least get started so now we need to make sure we actually call this grader to call the grader i'll go down to our to do right here i'm going to replace score with model grade and that's going to be coming from our grade by model function remember we have to pass in the test case along with the output from actually running the prompt. And then from this, we're going to get a score from model grade.

And I'm also going to extract from that dictionary that gets returned the reasoning behind the score.

Inside of this model grade dictionary that we are returning, there is also going to be the strengths and weaknesses list. You could definitely extract those as well as you want, but it's going to keep our example a little bit more concise.

I'm going to take the score and reasoning and put them into this final output dictionary.

So I will add in some additional keys here.

Of score.

Oh, I already have score right there.

My mistake.

So I don't need that.

But I do need reasoning.

And that will be reasoning.

Okay, so that looks good.

I'm now going to make sure I run these cells.

So I'm going to run that one.

Update run test case.

and then I'm going to rerun my actual evaluation. That is going to take a while to complete. For me, it takes about 22 seconds this time around. And now if I print out those results, let's see what we get. So I've now got the generated output here. And if I scroll down a little bit, I can take a look at the score that was generated by the model and some reasoning behind that score. So in this case, I got an eight. That's not bad. And that's why I got the eight. And if I keep going down, the next one got a seven. And then finally I got a six. So the last thing we would probably want to do here is take all these scores, add them together, get an average and print that out. So we get a final, very objective score to tell us how well our prompt is currently functioning.

So to average all these scores out and print the results, I'm going to find the run eval function right here and i will calculate the average score with a comprehension of result score for results in results and i'm going to wrap that with a mean function call and then i will import the mean function from the statistics package oh one last step let's make sure we actually print out that average score we'll do a print average score like so. All right now I'm going to run this code just one more time to make sure everything is working as expected and after it runs I see that I do get in fact an average score of 7.33. Now once again this gives us finally an actual objective metric. Yeah it's being graded by a model that might be a little bit capricious sometimes and maybe we could give it better guidance on how to grade things but at least we have a score that we can start to focus on and try to increase.

↑ 目次へ戻る

Building with the Claude API | Lesson 19「Code based grading」(全文ナレッジ)

動画 7分26秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンのテーマ

前レッスンまでに作った eval パイプライン(データセット生成 → run_prompt → モデル採点)に、コードベースの採点(code grader) を追加する。モデル出力が「説明抜きの生の Python / JSON / 正規表現」であること(Format)と、「その構文が正しいこと」(Valid Syntax)を 決定論的なコードで検証し、モデルグレーダーのスコアと合成する。

採点の分担:Code Grader と Model Grader

冒頭のスライドで、採点観点を3つに分けて担当を割り当てる:

観点 内容 担当
Format 説明なしで Python / JSON / Regex だけを返すべき Code Grader
Valid Syntax 生成された Python / JSON / Regex は構文が正しいべき Code Grader
Task Following 応答がユーザーのタスクに直接・明確に応えているか/生成コードが正確か Model Grader

→ 機械的にチェックできるもの(形式・構文)はコードで、意味的な判断(タスク充足・正確さ)はモデルで採点する、という役割分担。

構文検証のトリック:「パースできれば10点、例外なら0点」

「Python の構文をどうやって検証するのか?」に対する答えは、標準ライブラリのパーサに実際に食わせてみること。3つのヘルパー関数を定義する:

def validate_json(text):
    try:
        json.loads(text.strip())
        return 10
    except json.JSONDecodeError:
        return 0

def validate_python(text):
    try:
        ast.parse(text.strip())
        return 10
    except SyntaxError:
        return 0

def validate_regex(text):
    try:
        re.compile(text.strip())
        return 10
    except re.error:
        return 0
  • JSON → json.loads() でパース
  • Python → ast.parse()抽象構文木(AST)としてパース
  • Regex → re.compile() でコンパイル
  • 成功すれば満点 10、パース中にエラーが出れば構文チェック完全失敗として 0(エラーが出る=出力が invalid、という二値判定)

grade_syntax:format キーで validator を振り分け

どの validator を走らせるべきかを知るため、テストケース側に期待フォーマットを持たせ、汎用の振り分け関数で分岐する:

def grade_syntax(response, test_case):
    format = test_case["format"]
    if format == "json":
        return validate_json(response)
    elif format == "python":
        return validate_python(response)
    else:
        return validate_regex(response)

データセットに format キーを追加(手編集ではなく生成プロンプトを更新)

dataset.json を手で直すのではなく、データセットを生成しているプロンプトの Example output に "format" キーを追加する(将来大規模なデータセットを生成できるようにするため)。Example output の各 task オブジェクトに "format": "json" / "python" / "regex" を足して再生成すると、実際に各タスクに合った format が付与された(JSON設定タスク→json、Python関数タスク→python、正規表現タスク→regex)。

draft prompt の更新+「```code」prefill+stop sequence

現状の draft prompt は「タスクを解いて」としか言っていないため、非JSON・非Pythonの説明文が混ざり、検証チェックに落ち続けてしまう。そこで:

  1. プロンプトに注意書きを追加: - * Respond only with Python, JSON, or a plain Regex - * Do not add any comments or commentary or explanation
  2. prefill した assistant メッセージ+stop sequence で生のコンテンツだけを取り出す。ただし今回は返ってくる形式(Python/JSON/regex)が事前に分からないので、```json```python と特定できない。そこで裏技として「```code」を prefill する — 「ここにコードを書く」ということだけを Claude に伝え、言語を特定しない:
add_assistant_message(messages, "```code")
  1. 閉じの ` を stop sequence として追加する(本コースで既出のパターン。データセット生成関数側では chat(messages, stop_sequences=["```"]) の形で使われている)。

スコアの合成:モデル点と構文点の平均

run_test_case() の中で、モデルグレーダーの直後にコードグレーダーを走らせ、平均を取る:

model_grade = grade_by_model(test_case, output)
model_score = model_grade["score"]        # 旧 score をリネーム
reasoning = model_grade["reasoning"]

syntax_score = grade_syntax(output, test_case)

score = (model_score + syntax_score) / 2

実行結果と「スコア単体では良し悪しは分からない」

run_eval(dataset) を再実行(十数秒かかる)すると、最終スコアは 8.166。このスコアが良いのか悪いのかはそれ単体では分からない。分かる唯一の方法は、プロンプトを変更してスコアが上がるかどうかを比較すること — 次のレッスンでプロンプトを変えてスコア改善を試みる。

実務ポイント(このレッスンから持ち帰るもの)

  • 機械的に検証できる観点(形式・構文)はコードで採点し、意味的な観点(タスク充足)だけをモデル採点に残す。決定論的な code grader は速い・無料・ブレない。
  • 構文検証は自前でパーサを書かず、標準ライブラリに実際にパースさせて try/except で判定する(json.loads / ast.parse / re.compile)。成功=満点、例外=0点の二値で十分機能する。
  • どの validator を使うかはテストケース自体に format キーを持たせて宣言しておく。grader 側は test_case["format"] で分岐するだけの汎用関数にする。
  • データセットに属性を足したくなったら、JSONファイルを手編集せず生成プロンプト(Example output)を更新する。あとで大規模データセットを再生成できる形を保つ。
  • 出力形式が複数ありうるときの prefill は **「code」** が使える裏技。言語を特定せずに「コードブロックの中身だけを書け」と Claude に伝えられ、閉じ ` ``` の stop sequence と組み合わせれば生のコードだけが返る。
  • 採点器を追加したら、プロンプト側にもその採点器が通るための指示(「JSON/Python/regexのみで返答」「解説禁止」)を明記する。採点だけ厳しくしてプロンプトを放置すると常に失格になり、評価として機能しない。
  • 複数 grader のスコア合成は、まず単純平均で始めれば十分((model_score + syntax_score) / 2)。変数名は scoremodel_score のように衝突しない名前へ即リネームして明確にする。
  • eval スコアの絶対値に意味はない。プロンプト変更前後の相対比較にだけ意味がある — ベースラインを取ってから改善を試す、が eval 駆動の基本ループ。

本文(時系列・日本語逐語+画面差し込み)

[0:00] 次にやるべきは、コードグレーダー(code grader)の実装です。このコードグレーダーはモデルからの出力を受け取り、返ってきたものが、いかなる説明も付いていない、生の Python・JSON・正規表現だけであることを確認します。それに加えて、実際に受け取った種類のコードが何であれ、その構文が正しいことも確認する必要があります。

🖥 [0:00] 画面(スライド「Format / Valid Syntax / Task Following」=採点観点の分担図) 3つのボックスと、下部の波括弧によるグルーピング: - Format - Should return only Python, JSON, or Regex without explanation - Valid Syntax - Produced Python, JSON, and Regex should have valid syntax - Task Following - Response should directly and clearly address the user's task - Generated code should be accurate

下部の括り:Format+Valid Syntax → Code Grader / Task Following → Model Grader

[0:17] ちょっと不思議に思うかもしれません。たとえば Python の構文なんて、いったいどうやって検証するのか?と。実は、これにはちょっとしたトリックを使います。3つのヘルパー関数を定義します。1つは validate JSON、もう1つは validate Python、そしてもう1つが validate regex という名前になります。

🖥 [0:24] 画面(スライド「3つの validator 関数」) 3枚のコードカードとその下のキャプション: python def validate_json(text): try: json.loads(text.strip()) return 10 except json.JSONDecodeError: return 0 キャプション:Parse output as JSON — Get an error? Output must be invalid! python def validate_python(text): try: ast.parse(text.strip()) return 10 except SyntaxError: return 0 キャプション:Parse output to a Python Abstract Syntax Tree — Get an error? Output must be invalid! python def validate_regex(text): try: re.compile(text.strip()) return 10 except re.error: return 0 キャプション:Compile output as a Regex — Get an error? Output must be invalid!

[0:33] それから、それぞれの関数の中では、モデルから得た出力が何であれそれを受け取り、JSON としてパースするか、Python の抽象構文木(AST)としてパースするか、あるいは正規表現としてコンパイルするか、のいずれかを試みます。そしてこれらのそれぞれについて、パースなりロードなり何なりが成功したら、満点の10を返します。そうではなく、このパース処理の最中にエラーが出た場合は、構文チェックに完全に失敗したものと見なして、0を返します。

[1:00] ここでもう1つ、気を付けておくべきことがあります。これらの複数の validator、つまり採点関数のうちどれを走らせるべきかを知るためには、テストケースのデータセットの側に、各出力について返ってくるはずの期待フォーマットを含めておく必要があります。

🖥 [1:00] 画面(ノートブック 001_Prompt_Evals.ipynb:現状の run_test_case セル) VS Code + Jupyter。現時点ではモデルグレーダーだけを走らせている run_test_case: ```python def run_test_case(test_case): """Calls run_prompt, then grades the result""" output = run_prompt(test_case)

model_grade = grade_by_model(test_case, output)
score = model_grade["score"]
reasoning = model_grade["reasoning"]

return {
    "output": output,
    "test_case": test_case,
    "score": score,
    "reasoning": reasoning,
}

`` (output変数にホバーして(variable) output: Unknown` のツールチップが出ている)

[1:13] つまり言い換えると、こちらのデータセットの中に戻ると、最初のタスクでは、少なくとも私の場合は、Python の関数が返ってくることを期待していて、その次が JSON、その次が正規表現でした。そこでデータセットを更新して、format キーのようなものを含める必要があります。「この出力はおそらく Python のはず。こっちは JSON、そしてこれは正規表現」と言ってくれるようなキーです。

🖥 [1:15] 画面(dataset.json:format キー追加前の現状) json [ { "task": "Create a Python function to validate an AWS IAM username follows the naming conv…(右端は画面外) }, { "task": "Design a JSON configuration object that describes the basic settings for an AWS …(右端は画面外) }, { "task": "Write a regex pattern to extract the AWS account ID from an ARN (Amazon Resource…(右端は画面外) } ] (task キーのみで format キーがまだ無い。タスク1=Python、タスク2=JSON、タスク3=regex の期待に対応)

[1:34] もちろん、このファイルを手で編集することもできます。ですがその代わりに、データセットを生成しているプロンプトの方を更新します。そうすれば、ゆくゆくはテスト目的で本当に大きなデータセットを生成できるようになるからです。

[1:44] さて、これら全部をやるには、合計でいくつかのステップを踏む必要があります。コード側の作業を理解しやすく、全体の筋を見失わないように、これから進める項目の簡単なチェックリストを用意しました。最初の項目は、JSON・Python・正規表現を検証する関数を追加することです。

🖥 [1:45] 画面(スライド「作業チェックリスト」:4項目・下向き矢印付き) 1. Add functions to validate JSON/Python/Regex 2. Make sure our dataset test cases indicate the type of generated content (JSON, Python, or Regex) 3. Update our draft prompt to make it clear we only want the relevant JSON/Python/Regex 4. Merge the scores from the model grader and the code grader

[2:01] そのために、ノートブックへ戻ります。run test case のセルを見つけて、そのすぐ上に新しいセルを追加します。そしてその中に、さっき図の中で見てもらった3つの関数を追加します。今回も少し時間を節約するために、ここに貼り付けてしまいます。完成済みコードは、このノートブックの完成版からいつでもコピーできます。

🖥 [2:15] 画面(ノートブック:validator 3関数+grade_syntax を貼り付けたセル。画面に字幕オーバーレイ「You can copy these functions from the notebook attached to this lecture」) セル末尾の関数群(validate_regex と grade_syntax の部分が見えている。ブレッドクラム表示は import re): ```python def validate_regex(text): try: re.compile(text.strip()) return 10 except re.error: return 0

def grade_syntax(response, test_case): format = test_case["format"] if format == "json": return validate_json(response) elif format == "python": return validate_python(response) else: return validate_regex(response) ```

[2:19] セルの一番上では、2つのヘルパーモジュールをインポートしています。その下に、いま見てもらった3種類の validator 関数があります。そしてセルの一番下には、これら複数の validator のうちどれを使うべきかを判定する、いわば汎用の関数を置いています。

[2:33] ここにあるのが grade syntax です。これはテストケースを見に行きます。特に format を見ます。だからこそ、さっき少し前に触れた format プロパティをテストケースが持っていることを確実にしておく必要があるのです。そしてその値に応じて、適切なフォーマット用関数を呼び出します。オーケー、これがステップ1です。

🖥 [2:35] 画面(ノートブック:grade_syntax のズーム表示。format の出現箇所がハイライトされている) python def grade_syntax(response, test_case): format = test_case["format"] if format == "json": return validate_json(response) elif format == "python": return validate_python(response) else: return validate_regex(response)

[2:51] さてステップ2です。データセットを更新して、その format キーを確実に含めるようにします。そのために、少し上へスクロールしてデータセットを見つけましょう。はい、ここにありました。generate dataset ですね。この example output に追記します。task の一番最後にカンマを追加して、format キーを足します。その中身は単純に json、python、regex と書くだけです。本当にこれだけで済みます。

🖥 [3:10] 画面(ノートブック:generate_dataset() のプロンプト文字列を編集中。Example output に format キーを追記している瞬間) ```python prompt = """ Generate a evaluation dataset for a prompt evaluation. The dataset will be used to evaluate p…(右端は画面外) that generate Python, JSON, or Regex specifically for AWS-related tasks. Generate an array o…(右端は画面外) each representing task that requires Python, JSON, or a Regex to complete.

Example output: json [ { "task": "Description of task", "fo| ← ここで "format" を入力中 }, ...additional ]

  • Focus on tasks that can be solved by writing a single Python function, a single JSON objec…(右端は画面外)
  • Focus on tasks that do not require writing much code """ ```

[3:23] それでは、そのセルを再実行し、さらにその下の、実際にデータセットを生成するセルも再実行すると——はい、できました。データセットのファイルに戻ってみると、たしかに format がその中に入っていて、しかもタスクと完璧に対応しているのが分かります。最初のタスクは「JSON 設定を作れ」で json が付いている。「Python を書け」には python。そして「正規表現を書け」には regex が付いています。

🖥 [3:35] 画面(dataset.json:再生成後。各タスクに format キーが付与された) json [ { "task": "Create a JSON configuration for an AWS S3 bucket policy that allows read-only ac…(右端は画面外) "format": "json" }, { "task": "Write a Python function to validate an AWS EC2 instance tag name follows a speci…(右端は画面外) "format": "python" }, { "task": "Design a regular expression to extract the AWS account ID from an ARN (Amazon Re…(右端は画面外) "format": "regex" } ]

[3:52] オーケー、ステップ3へ進みます。今度は draft prompt のテンプレートを更新して、欲しいのは JSON・Python・正規表現だけだ、ということを本当に明確にします。というのも今の draft prompt は「まあ、このタスクを解いてみて」程度のことしか言っていないからです。そのままでは必然的に、JSON でも Python でもないコンテンツが返ってきて、実際の検証チェックに落ち続けることになります。そこでプロンプトに少し助け舟を出して、必要だと分かっている仕事をきちんと与えてあげます。

🖥 [4:00] 画面(スライド「作業チェックリスト」再掲。ステップ3の説明中) 1. Add functions to validate JSON/Python/Regex 2. Make sure our dataset test cases indicate the type of generated content (JSON, Python, or Regex) 3. Update our draft prompt to make it clear we only want the relevant JSON/Python/Regex 4. Merge the scores from the model grader and the code grader

[4:21] ステップ3のために、run prompt まで下りていきます。draft prompt があるのはそこです。そしてプロンプトを少しだけ更新します。注意書きをいくつか追加して、Python・JSON・生の正規表現のみで応答すること、そしてコメントや解説・説明の類いは一切加えないこと、を求めます。

🖥 [4:23] 画面(ノートブック:generate_dataset() の末尾〜データセット書き出しセル〜run_prompt の冒頭) ```python … Please generate 3 objects. """

messages = []
add_user_message(messages, prompt)
add_assistant_message(messages, "```json")
text = chat(messages, stop_sequences=["```"])
return json.loads(text)

python dataset = generate_dataset() with open("dataset.json", "w") as f: json.dump(dataset, f, indent=2) `` (実行済み 2.5s。この下にdef run_prompt(test_case):` が始まる)

🖥 [4:45] 画面(ノートブック:run_prompt() のプロンプトに注意書きを追記している最中) ```python def run_prompt(test_case): """Merges the prompt and test case input, then returns the result""" prompt = f""" Please solve the following task:

{test_case["task"]}

  • Respond only with Python, JSON, or a plain Regex
  • Do not ad| ← 「Do not add any comments or commentary or explanation」を入力中 """

    messages = [] add_user_message(messages, prompt) output = chat(messages) return output ```

[4:52] 次に、本当に欲しい生のコンテンツだけが返ってくるようにします。そしてこれも前と同じく、prefill した assistant メッセージと stop sequence を併用します。ここに assistant メッセージを追加します。

🖥 [5:05] 画面(ノートブック:add_assistant_message を追加し、バッククォート3つを入力した直後) python messages = [] add_user_message(messages, prompt) add_assistant_message(messages, "|") output = chat(messages) return output ```

[5:07] そして今回の assistant メッセージには、バッククォートを3つ入れます。以前見たように、普段ならこの直後に json とか bash とか python といったものを入れるところです。ですが今回のケースでは、返ってくる正確なフォーマットが事前には分かりません。Python が返るのか、JSON なのか、regex なのか分からないのです。

[5:32] そこでちょっとした裏技、ちょっとした回避策として、単に「code」と入れてしまう手があります。これで assistant メッセージが prefill され、Claude に「ここにはコードを書くことになるよ」と伝わります。それが Python なのか JSON なのか regex なのかを、こちらから特定して言う必要がないわけです。それから、閉じの stop sequence をこのようにバッククォートで追加します。

🖥 **[5:35] 画面(ノートブック:prefill が「code」になった状態。`messages` / `output` などがハイライト)**python messages = [] add_user_message(messages, prompt) add_assistant_message(messages, "code") output = chat(messages) return output

[5:52] 最後に、モデルグレーダーとコードグレーダーのスコアを実際にマージする必要があります。そのために、またこちらに戻って再び下へスクロールし、run test case 関数を見つけます。ここが、モデルグレーダーを走らせている場所です。

🖥 [5:51] 画面(スライド「作業チェックリスト」再掲。ステップ4「Merge the scores from the model grader and the code grader」へ) (4項目のチェックリスト。内容は [1:45] と同一)

[6:08] そのすぐ下に、構文グレーダー——コードグレーダーと呼んでもいいですが——を組み込みます。my syntax score、grade syntax と書きます。output とテストケースを渡す必要があります。

🖥 [6:15] 画面(ノートブック:run_test_case() に syntax_score の行を書き始めたところ) ```python def run_test_case(test_case): """Calls run_prompt, then grades the result""" output = run_prompt(test_case)

model_grade = grade_by_model(test_case, output)
score = model_grade["score"]
reasoning = model_grade["reasoning"]

syntax_|        ← 入力中

return {
    "output": output,
    "test_case": test_case,
    "score": score,
    "reasoning": reasoning,
}

```

[6:23] そして最後に、syntax score をモデルスコアと合成します。まず分かりやすくするために、ここにある score を model score にリネームします。そして、この2つのスコアの平均を取ります。つまり、score は model score プラス syntax score を2で割ったもの、とします。これで完了のはずです。ちょっとしたコード採点を追加するのに必要だったのは、これだけです。

🖥 [6:35] 画面(ノートブック:run_test_case() 完成形。モデル点と構文点の平均を取る) ```python def run_test_case(test_case): """Calls run_prompt, then grades the result""" output = run_prompt(test_case)

model_grade = grade_by_model(test_case, output)
model_score = model_grade["score"]
reasoning = model_grade["reasoning"]

syntax_score = grade_syntax(output, test_case)

score = (model_score + syntax_score) / 2

return {
    "output": output,
    "test_case": test_case,
    "score": score,
    "reasoning": reasoning,
}

```

[6:48] それでは最後に、これ全体をテストしてみましょう。そのために、ここから少しだけ下へ行きます。run eval 関数のすぐ下が、実際に run eval を呼び出して全体の平均スコアを計算している場所です。これを再実行します。完了までには通常それなりの秒数がかかることを思い出してください。

🖥 [7:00] 画面(ノートブック:run_eval の末尾と実行セル、平均スコアの出力) run_eval の末尾: ```python results.append(result)

average_score = mean([result["score"] for result in results])
print(f"Average score: {average_score}")

return results

実行セル(16.0s で完了):python with open("dataset.json", "r") as f: dataset = json.load(f)

results = run_eval(dataset) 出力: Average score: 8.166666666666666 `` (その下にprint(json.dumps(results, indent=2))` のセルと JSON 出力の先頭が見えている)

[7:04] 少し待つと、最終スコア 8.166 が得られました。さて、ここで問題です。これは良いのでしょうか、悪いのでしょうか?その本当の答えは「分からない」です。それを知る唯一の方法は、今度はプロンプトを何らかの形で変えてみて、うまくいけばより良いスコアを得ることです。というわけで次のビデオでは、プロンプトを少し変更して、スコアの改善を狙う演習をやってみましょう。

英語逐語(ASR全文)

Next up, we need to implement our code grader. So our code grader is going to take in some output from the model and make sure that we get back just plain Python, JSON, or regular expression without any kind of explanation. In addition, we should also make sure that we have valid syntax for whatever type of code we actually got. You might be kind of curious, how are we going to validate the syntax of say Python at all? Well, we use a little trick for this. We're going to define three helper functions. One will be called validate JSON, another validate Python, and another validate regex. Then inside of each of those, we're going to take whatever output we got from the model and try to either parse it as JSON. We'll try to parse it as a Python abstract syntax tree or AST, or we'll try to compile it as a regular expression. And then for each of these, if we successfully parse or load or whatever else, we'll return a full score of 10. Otherwise, if we get an error during this parsing operation, we'll assume that we completely failed the syntax check and return a zero. There is one other thing to be aware of here. In order to know which of these different validators or these kind of grading functions to run, we need our test case data set to include the expected format that we're going to get back for each output. So in other words, back here inside of our data set, on our first task, for me at least, I was expecting to get back a Python function, and then JSON, and then a regex. So we need to update our data set to include something like a format key that will say, Hey, this output should probably be Python. This one should be JSON and this one, a regex. Now, of course I could just edit this file manually, but instead we will update our prompt that is generating our data set so that we can eventually generate really large data sets for testing purposes. Now, in total, to do all this stuff, a couple of different steps we have to go through. So just to help you understand the code side of it and keep everything in line, I came this quick checklist of items that we're going to go through. So our first item, add in functions to validate JSON, Python, and regular expressions. For this, I'm going to flip back over to my notebook. I'm going to find the run test case cell. I'm going to add a new cell right above it. And then inside there, I'm going to add those three functions that you just saw inside that diagram. Once again, to save a little bit of time, I'm going to paste them in here. You can always copy the completed code out of the finished version of this notebook. So at the top of the cell, I'm importing these two helper modules. I've then got the three different validator functions that we just saw. And then at the bottom, I have kind of a general purpose function to figure out which of these different validators to use. So I've got grade syntax right here. That's going to take a look at the test case. It's going to look at the format in particular. So we need to make sure our test cases have that format property that I just mentioned a moment ago. And then depending upon that, we're going to call the appropriate format function. Okay, that is step one. Now step two, we need to update our dataset to make sure we include that format key. So for that, we'll scroll up a little bit and find our dataset. Here it is right here. So generate dataset, and I'm going to add on to the example output. On task, I'm going to add a comma at the very end, and I'll add in a format key. and inside of here we'll say simply json or python or regex that's really all we have to do so now if i rerun that cell and rerun the cell underneath it that actually generates the data set there we go now i'll go back over to my data set file and i'll see yes i did in fact get the format inside there and it looks like it matches up with the task perfectly so the first task is create a JSON configuration, got JSON, write Python, got Python, and then write a regular expression, and I got regex. Okay, on to step number three. Now this, we're going to update our draft prompt template, just to make sure that it's really clear that we only want JSON, Python, or regular expression. Because right now, our draft prompt just kind of says, yeah, try to solve the task. So inevitably, we're going to get back some non-JSON or non-Python content, and we'll always be failing the actual validation check. So we're just going to give our prompt a little help here, give it some work that we know that it needs. So for step three, we'll go back down to our run prompt, which is where our draft prompt is. And I'm going to update the prompt just a little bit. I'm going to add in some notes and I'll ask it to respond only with Python, JSON, or a plain regex. and do not add any comments or commentary or explanation. Next up, I'm going to make sure that we get back just that raw content that we really care about. And once again, to do so, we'll use a pre-filled assistant message along with a stop sequence. So I'll add in a assistant message right here. and my assistant message in this case i can put in three backticks and then usually as we saw previously we might put in something like json or bash or python right here but in this particular case we don't really know ahead of time the exact format that we expect to get back we don't know if we're going to get back python or json or regex so one little cheat code here one little work around we could just put in code and that kind of pre-fills the assistant message and tells claude hey, you're going to put some code inside of here without us having to specifically say this is going to be Python or JSON or regex. I'm then going to add on the closing stop sequence with backticks like so. Lastly, we need to actually merge the scores from our model grader and the code grader together. So for that, back over here, I'm going to scroll down once again, and we will find our run test case function. So this is where we are running our model grader. Right underneath it, I'm going to put together the syntax grader or code grader, whichever you want to call it. So I'll say my syntax score, grade syntax. We need to pass in the output and our test case. And then finally, we're going to merge the syntax score together with the model score. I'm going to first rename score right here to model score, just to be clear. And I'm going to take the average of these two scores. So I'll say score is going to be model score plus syntax score divided by two. And that should be it. So that's all it took to add in a little bit of code grading. So last thing to do is test this all out. To do so, we'll go down just a little bit here. So right underneath the run eval function is where we actually call run eval and calculate our overall average score. So I'm going to rerun this and remember it usually takes a decent number of seconds to complete. And after a short pause, I get a final score of 8.166. So now the question is, is this good or not? Well, the real answer to that is that we just don't know. The only way we're going to know is if we now try to change our prompt in some way and hopefully get a better score. So let's try out an exercise in the next video where we will try to change our prompt a little bit and hopefully improve our score.

↑ 目次へ戻る

Building with the Claude API | Lesson 20「Exercise on prompt evals」(全文ナレッジ)

動画 4分44秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンのテーマ(演習)

プロンプト評価(prompt eval)ワークフローの改善演習。モデルグレーダー(LLM 採点者)に「良い解答とは何か」の追加コンテキスト= solution criteria(解答基準)を与えることで、採点の質を上げる。一見難しそうに聞こえるが、実際にやることは 2 ステップだけ

演習のお題(2 ステップ)

スライド「Exercise Task: Give the Model Grader more context on what a good solution looks like」より:

  • Step #1:データセット生成プロンプトを更新し、各テストケースに「solution criteria」を含めて出力させる。期待される出力例: json { "task": "Create a JSON configuration for an AWS Lambda function that sets up a basic Python runtime with a memory allocation of 512MB and a timeout of 10 seconds", "format": "json", "solution_criteria": "Must include runtime, memory size, timeout, and basic structure for AWS Lambda configuration" }
  • Step #2grade_by_model プロンプトを更新し、その solution criteria を含める。挿入位置は「Solution to Evaluate:(<solution>{output}</solution>)」の直後。

Step 1:generate_dataset() のプロンプトに solution_criteria を追加

ノートブック 001_Prompt_Evals.ipynbgenerate_dataset() 内にある大きなプロンプトの「Example output」JSON に、キーを 1 行足すだけ:

{
    "task": "Description of task",
    "format": "json" or "python" or "regex",
    "solution_criteria": "Key criteria for evaluating the solution"
}

値の文字列("Key criteria for evaluating the solution")は、このキーに何を入れるべきかをモデルに示すためのヒント。セルを再実行し、その下のセル(dataset = generate_dataset()dataset.json へ書き出し)でデータセットを再生成すると、各テストケースに solution_criteria 付きの dataset.json ができる(生成結果は毎回変わるので、自分のものは講師のものと違ってよい)。

Step 2:grade_by_model() のプロンプトに criteria を差し込む

grade_by_model() 内のプロンプトには既に Original Task(<task>{test_case["task"]}</task>)と Solution to Evaluate(<solution>{output}</solution>)が入っている。その直後に、採点基準の注記を追加する:

Criteria you should use to evaluate the solution:
<criteria>
{test_case["solution_criteria"]}
</criteria>
  • test_case は dataset.json 内のオブジェクト(dict)1 件ずつなので、test_case["solution_criteria"] で f-string 補間できる
  • <criteria> タグで囲む理由は、後続のプロンプトエンジニアリングの章で説明予定(XML タグ構造)

再実行と結果

run_eval 関数の下の実行セル(dataset.json を読み込み → results = run_eval(dataset))を再実行すると、更新後のスコアが返る(実演では Average score: 8.833333333333334、実行 16.7 秒)。さらに print(json.dumps(results, indent=2)) で results を印字すると、各件について「モデルの出力/テストケース(task と solution_criteria)/score/reasoning」が確認できる。実演の 1 件目は score 9.0。solution criteria を渡したことで、モデルグレーダーが生成する reasoning セクションが以前より肉付けされたものになることが期待できる。

実務ポイント(このレッスンから持ち帰るもの)

  • LLM グレーダーの採点品質は「良い解答の基準」を渡すだけで改善できる。rubric(採点基準)をテストケース単位で持たせるのが基本形。
  • データセット生成を LLM にやらせている場合、生成プロンプトの Example output にキーを 1 つ足すだけで、全テストケースに solution_criteria を自動付与できる(手書き不要)。
  • Example output 内の値文字列("Key criteria for evaluating the solution")はそのキーに何を出力すべきかをモデルに教えるプレースホルダとして機能する。
  • 採点プロンプトへの挿入は <criteria>...</criteria> のように XML タグで囲むのが定石(Claude はタグ構造に強い。詳細は後続のプロンプトエンジニアリング章)。
  • test_case は dict なので、{test_case["solution_criteria"]} の f-string 補間だけで済む。データセットのキー名とコードの参照名を一致させること(dataset.json を開いて実キーを目視確認する講師の手順に倣う)。
  • プロンプトや評価系を変更したら、パイプライン全体(データセット再生成 → run_eval)を再実行してスコアを取り直す。
  • スコアの数字だけでなく、print(json.dumps(results, indent=2))reasoning の質まで確認する。criteria を渡すと reasoning がより具体的・網羅的になる。
  • LLM 生成のデータセットは非決定的。自分の solution_criteria が講師の例と違っていても正常

本文(時系列・日本語逐語+画面差し込み)

[0:00] プロンプト評価ワークフローを改善する演習をやってみましょう。これから皆さんに出す課題はこれです。良い解答が実際どういうものかについて、より多くのコンテキストを与えることで、私たちのモデルグレーダーを少し改善したいのです。さて、一見するとそれは少し難しそうに聞こえるかもしれませんが、実はこの追加コンテキストを入れるのに必要な手順は、たった 2 つのステップだけです。

🖥 [0:00] 画面(スライド「Exercise Task」) 左のカードに演習課題、右に Step #1 / Step #2 の説明とコード例。 - Exercise Task: Give the Model Grader more context on what a good solution looks like - Step #1: Update the dataset generation prompt to ask for some “solution criteria” to be included for each test case json { "task": "Create a JSON configuration for an AWS Lambda function that sets up a basic Python runtime with a memory allocation of 512MB and a timeout of 10 seconds", "format": "json", "solution_criteria": "Must include runtime, memory size, timeout, and basic structure for AWS Lambda configuration" } - Step #2: Update the ‘grade_by_model’ prompt to include that solution criteria ``` Solution to Evaluate: {output}

```

[0:21] ステップ 1 では、データセットを生成しているプロンプトに戻り、そのプロンプトの中で、すべてのテストケースに solution criteria(解答基準)を含めるよう頼んでみることをおすすめします。

[0:33] つまり理想的には、出力されるテストケースに、追加の solution criteria キーが付くようになるはずです。それはちょうど今ここに見えているようなものかもしれません。良い解答がどのようなものかについて何かを述べる、という形です。例えば「良い解答にはこの特徴と、この特徴と、この特徴が含まれる」といった具合です。この追加の solution criteria が手に入ったら、

[0:52] 次にそれを grade by model のプロンプトに挿入できます。

[0:56] そのプロンプトの中の、評価対象の解答(solution to evaluate)を入れている既存の箇所を見つけて、そのすぐ後ろに、新しく生成された solution criteria を追加すればよいでしょう。モデルグレーダーに「良い解答が実際どういうものか」をもう少しよく理解させるには、本当にそれだけで十分なのです。

[1:14] いつものように、今すぐ動画を一時停止して、この演習に挑戦してみることをおすすめします。そうでなければ、これから解答を一緒に見ていきます。

[1:22] というわけで、解答は本当にこの 2 つの別々のステップだけです。かなり簡単なはずです。

[1:27] 始めるにあたって、ノートブックに戻り、generate dataset 関数を見つけます。そしてその中の、以前入れたとても大きなプロンプトを見つけます。そして、それぞれのテストケースを求めている箇所で、こう言います。task と出力フォーマットに加えて、solution criteria も欲しい、と。

🖥 [1:27] 画面(VS Code ノートブック 001_Prompt_Evals.ipynb — generate_dataset のプロンプト編集中) パンくず:06 > 001_Prompt_Evals.ipynb > def generate_dataset():。Example output の JSON に "solution_criteria": " を入力している途中(カーソル表示)。 ```python def generate_dataset(): prompt = """ Generate a evaluation dataset for a prompt evaluation. The dataset will be used to evaluate …(右端見切れ) that generate Python, JSON, or Regex specifically for AWS-related tasks. Generate an array o…(見切れ) each representing task that requires Python, JSON, or a Regex to complete.

Example output: json [ { "task": "Description of task", "format": "json" or "python" or "regex", "solution_criteria": " }, ...additional ] ```

[1:44] それから、ちょうどここに文字列を入れます。このキーが実際に何であるべきかをモデルに示すためだけのものです。というわけで、解答を評価するための重要な基準(key criteria for evaluating the solution)を求めることにします。これでほぼ完了です。

[1:57] では、このセルを再実行します。その下のセルに移動して、データセットを再生成します。よし、ほんの数秒で終わるはずです。

🖥 [2:04] 画面(ノートブック — データセット再生成セルの実行) generate_dataset() 末尾のヘルパー呼び出し部(セル [60]、0.0s)と、再生成セル(セル [61]、✓ 4.3s)。その下に run_prompt 関数。 python add_user_message(messages, prompt) add_assistant_message(messages, "json") text = chat(messages, stop_sequences=[""]) return json.loads(text) python dataset = generate_dataset() with open("dataset.json", "w") as f: json.dump(dataset, f, indent=2) ```python def run_prompt(test_case): """Merges the prompt and test case input, then returns the result""" prompt = f""" Please solve the following task:

{test_case["task"]}

  • Respond only with Python, JSON, or a plain Regex ```

[2:14] できました。これで更新された dataset.json ファイルがあるはずです。そのファイルを開いてみます。すると、更新されたタスクがここに見えるはずです。相変わらず format はありますが、今は solution criteria も付いています。この solution criteria は読んで確かめられます。もちろん、皆さんのものは私のものとは違う見た目になるでしょうが、繰り返しになりますが、良い解答が実際どのようなものになるかについてのヒントを与えてくれるはずです。

🖥 [2:18] 画面(dataset.json — 再生成されたデータセット、テストケース 3 件すべてに solution_criteria 付き) json [ { "task": "Create a regular expression to validate an AWS S3 bucket name (lowercase letter…(右端見切れ)", "format": "regex", "solution_criteria": "Matches valid S3 bucket naming conventions, prevents special charac…(見切れ)" }, { "task": "Write a Python function to convert an AWS resource ARN into its component parts…(見切れ)", "format": "python", "solution_criteria": "Correctly parses different ARN formats, returns a dictionary with s…(見切れ)" }, { "task": "Create a JSON configuration for an AWS Lambda function's basic CloudFormation re…(見切れ)", "format": "json", "solution_criteria": "Includes required properties like runtime, handler, code location, …(見切れ)" } ]

[2:33] 次はステップ 2 です。grade by model 関数、特にその中のプロンプトを見つけて、この新しく生成された solution criteria を含めます。繰り返しますが、モデルグレーダーに良い解答とはどういうものかを伝えるためだけのものです。

🖥 [2:36] 画面(スライド「Exercise Task」再表示) dataset.json からスライドへ画面が切り替わり、Step #2(Update the ‘grade_by_model’ prompt to include that solution criteria)を再確認。内容は冒頭のスライドと同一。

[2:50] そのために、ノートブックに戻ります。下にスクロールして、その grade by model 関数を見つけます。ほら、ここにありました。では、プロンプトを見つけます。

🖥 [2:51] 画面(ノートブックへ復帰 — generate_dataset のプロンプト、Step 1 の編集完了形) スライドからノートブックに戻った直後の画面。Example output の JSON に入力済みの完成形が見える(セル [60]): json [ { "task": "Description of task", "format": "json" or "python" or "regex", "solution_criteria": "Key criteria for evaluating the solution" }, ...additional ] その下に続くプロンプト本文: * Focus on tasks that can be solved by writing a single Python function, a single JSON objec…(右端見切れ) * Focus on tasks that do not require writing much code

[2:59] すでに元のタスク(original task)と、生成された出力(output)は入れてあります。そしてそのすぐ後ろに、モデルへのちょっとした注記を入れて、こう言うだけです。「これが、解答を評価するために使うべき基準です」と。

[3:11] つまり、「Criteria you should use to evaluate the solution(解答を評価するために使うべき基準)」です。タグも入れます。なぜこのタグを入れているのかは、プロンプトエンジニアリングの話を始めるときに、すぐ後ほど説明します。

🖥 [3:11] 画面(ノートブック — grade_by_model のプロンプト編集中、セル [46]) パンくず:06 > 001_Prompt_Evals.ipynb > def grade_by_model(test_case, output):</solution> の直後に注記を入力している途中(カーソル表示): ``` Original Task: {test_case["task"]}

Solution to Evaluate: {output}

Criteria you should use to evalua

Output Format Provide your evaluation as a structured JSON object with the following fields, in this speci…(右端見切れ) - "strengths": An array of 1-3 key strengths - "weaknesses": An array of 1-3 key areas for improvement - "reasoning": A concise explanation of your overall assessment - "score": A number between 1-10 ```

[3:27] それから、テストケースから、私たちの solution criteria を補間(interpolate)して入れます。そこのキーですが、思い出してください。私たちのテストケースとは実はこれらのオブジェクトのことで、これらのオブジェクトが 1 件ずつ渡ってきます。

[3:39] ですから、このファイルの中のちょうどここに見えているとおり、あの辞書(dict)の中に solution criteria というキーがあることが分かります。つまり、あそこにあるあの文をそのまま取ってきて、ちょうどここに入れている、というわけです。

🖥 [3:39] 画面(dataset.json — solution_criteria キーの確認) ノートブックから dataset.json タブへ切り替え。パンくず:06 > dataset.json > {} 0 > solution_criteria。3 件の各オブジェクト内の solution_criteria キーがハイライトされている(内容は [2:18] と同一のデータセット)。

🖥 [3:47] 画面(ノートブック — grade_by_model プロンプトの編集完了形) 補間まで入力し終えた状態。{test_case["solution_criteria"]} の一部が選択ハイライトされている: ``` Original Task: {test_case["task"]}

Solution to Evaluate: {output}

Criteria you should use to evaluate the solution: {test_case["solution_criteria"]}

Output Format Provide your evaluation as a structured JSON object with the following fields, in this speci…(右端見切れ) - "strengths": An array of 1-3 key strengths ```

[3:50] よし。それでは、このセルを実行するときです。パイプラインを再実行して、すべてがどう動いているかを見てみましょう。run eval 関数まで下りていきます。そしてそのすぐ後ろが、実際にすべてを実行する場所です。では、それを実行します。

[4:08] すると、更新されたスコアが返ってきます。ここで、結果をさっと印字してみたいと思います。これが実際の出力にどう影響するのかを見るためです。

🖥 [4:08] 画面(ノートブック — パイプライン再実行と更新スコア、セル [65]) ```python with open("dataset.json", "r") as f: dataset = json.load(f)

results = run_eval(dataset) ✓ 16.7s で完了。出力: Average score: 8.833333333333334 ``` その下に新しい空セルを追加。

[4:10] そこで、もう一度 print で、results を JSON dumps、インデント 2 で出します。

[4:25] これでモデルからの出力が見えます。これが実際に生成された出力でした。これが私たちのテストケースです。task と solution criteria を見ることができます。これがスコアで、この場合は 9 でした。

🖥 [4:33] 画面(ノートブック — print(json.dumps(results, indent=2)) の出力) パンくず:06 > 001_Prompt_Evals.ipynb > print(json.dumps(results, indent=2))。結果 1 件目(S3 バケット名の正規表現タスク)と 2 件目の冒頭: json { "output": "\n^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$\n", "test_case": { "task": "Create a regular expression to validate an AWS S3 bucket name (lowercase letters, numbers, hyphens, 3-63 characters long)", "format": "regex", "solution_criteria": "Matches valid S3 bucket naming conventions, prevents special characters, enforces length constraints" }, "score": 9.0, "reasoning": "The regex captures most S3 bucket naming rules with good precision. It handles length, character set, and start/end character constraints effectively. However, it misses a few nuanced AWS S3 bucket naming edge cases that would require additional validation." }, { "output": "\nimport re\n\ndef parse_arn(arn):\n pattern = r'^arn:aws:(?P<service>[^:]+):(?P<region>[^:]*):(?P<account_id>[^:]+):(?P<resource_type>[^:/]+)(?:/|:)(?P<resource_id>.+)$'\n match = re.match(pattern, arn)\n \n if not match:\n …(以下見切れ) ※ reasoning の一節が画面上で選択ハイライトされている。

[4:35] そして今度は、願わくば、モデルグレーダーが生成する reasoning セクションが、以前よりも少し肉付けされたものになっているはずです。なぜなら、あの solution criteria を含めているからです。

英語逐語(ASR全文)

Let's try out an exercise to improve our prompt evaluation workflow. So here's the task I'm going to give you. I want to improve our model grader a little bit by providing it with more context on what a good solution should actually look like. Now, at first glance, that might sound a little bit challenging, but it turns out you only really have to go through two steps to add in this additional context.

So in step one, I would encourage you to go back to the prompt where we generate our data set and inside that prompt, try asking it for some solution criteria to be included in every test case. So ideally our test cases that get output should now have some additional solution criteria key, which might look like what you see right here. So it might say something about what a good solution would look like. Maybe you say, well, a good solution would include this characteristic and this characteristic and this characteristic. Once we have this additional solution criteria, we can then insert that into our grade by model prompt.

So you might find the existing area of that prompt where we put in our solution to evaluate, and then right after it, you might add in that newly generated solution criteria. And that's all it would really take to give our model grader a little bit better idea of what a good solution would actually look like. As usual, I would encourage you to pause the video right now and give this exercise a shot. Otherwise, we're going to go through a solution right now.

So the solution really is just going to be these two separate steps. Should be pretty straightforward. To get started, back inside my notebook, I'm going to find our generate dataset function. And inside there, I'll find the really big prompt we put in. And then when we ask for each of these different test cases, I'm going to say, in addition to a task in the output format, I also want to get some solution criteria. and then I'll put in a string right here just to give our model an indication of what this key should actually be. So I'm going to ask for some key criteria for evaluating the solution and that's pretty much it. So I'm going to rerun this cell. I'm going to go to the cell underneath it and regenerate the data set. Okay just a couple of seconds it should be done.

There we go. So now we should have an updated dataset.json file. I'm going to open that file up and I should now see some updated tasks in here still with the format, but now I've also got some solution criteria. So the solution criteria we can read over. Of course, yours is going to look different than mine, but it's going to get going to give some idea on again, what a good solution will actually look like. Next up is step number two. We're going to find our grade by model function and specifically the prompt inside there. And we're going to include this newly generated solution criteria. Again, just to tell the model grader what a good solution looks like.

So for that, I will go back to the notebook. I will scroll down and find that grade by a model function. Here it is right here. So I'm going to find the prompt. We are already putting in the original task, the output that was generated. And then right after that, I'm going to put in some note to the model and just say, here's some criteria that you should use to evaluate the solution. So criteria you should use to evaluate the solution. I'm going to put in some tags and I'll tell you why we are adding in these tags very shortly as we start to discuss prompt engineering.

And then I'm going to interpolate in from the test case, our solution criteria. And that key right there, remember our test case is really these objects, each of these objects one by one. So we know because we see it right here inside this file, there is a key inside that dictionary of solution criteria. So we're taking that sentence right there and putting it right here.

All right. So now time to run the cell and we're going to rerun our pipeline and see how everything is working. So I'll go down to the run eval function. And then right after that is where we actually execute everything. So I'm going to run that. And then we get our updated score back. Now I want to print out the results really quickly, just so we can see how this is going to affect the actual output. So we'll do another print of JSON dumps results with an indent of two.

So now we can see the output from our model. So that was the actual produced output. Here's our test case. So we can take a look at the task and the solution criteria. Here's the score. In this case, it was nine. and now hopefully our reasoning section which is produced by the model grader is going to be a little bit more fleshed out than it was before because we are including that solution criteria.

↑ 目次へ戻る

Building with the Claude API | Lesson 21「Prompt engineering」(全文ナレッジ)

動画 10分49秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンのテーマ

prompt engineering(プロンプトエンジニアリング)モジュールの導入回。「書いたプロンプトを改善して、より信頼性が高く高品質な出力を得る」のがプロンプトエンジニアリングの定義。このレッスンではモジュール全体の学び方をセットアップし、題材となる初期プロンプトを書いて、最初の(意図的にひどい)評価スコアを取るところまで進める。

モジュールの学習フロー(スライドの5ステップ)

  1. Set a goal — プロンプトにやらせたいゴールを決める
  2. Write an initial prompt — 最初の(かなり雑な)プロンプトを書く
  3. Eval the prompt — 前モジュールで作った評価パイプラインで採点する
  4. Apply a prompt engineering technique — プロンプトエンジニアリング手法を1つ適用する
  5. Re-eval to verify better performance — 再評価してスコア向上を確認する → 4〜5を繰り返す(Repeat)

※ 講師は前モジュールの評価パイプラインを「どんなプロンプトでも使える」汎用版に改良済み。必ず配布ノートブック 001_prompting.ipynb を使うこと

題材プロンプトのゴール

アスリートの身長・体重・目標(goal)・食事制限(dietary restrictions)を入力として受け取り、1日分の食事プラン(1-day meal plan)を生成するプロンプトを作る。理想の出力は「1日の総カロリー」「マクロ栄養素の内訳」「具体的な食品・分量・タイミング付きの食事」を含むもの。

PromptEvaluator クラスと並行実行

  • 改良版パイプラインは PromptEvaluator クラスに全部ラップされている(データセット生成・モデル採点など全部入り)
  • 引数は max_concurrent_tasks の1つ。並行 API 呼び出しに対応し、eval とデータセット生成が劇的に速くなる
  • ただしサービスクォータ次第で rate limit エラーが出る。エラーが出たらこの値をデフォルトの 1(並行なし)まで下げる。講師は高いレート制限を持つので 50 を使うが、受講者は 3 から始めて、ダメなら 2 → 1 に下げるのが推奨

データセット生成(generate_dataset)

  • task_description:プロンプトの目的の説明(例:「1人のアスリート向けのコンパクトで簡潔な1日食事プランを書く」)
  • prompt_inputs_spec:プロンプトが要求する入力を辞書で列挙(height=cm、weight=kg、goal、restrictions)
  • output_file="dataset.json":生成先ファイル
  • num_cases:生成するテストケース数。受講者は 3 のままが推奨(evalが速く終わりモジュールを早く進められる)。実運用の eval では大量のテストケースが必要、という原則は変わらない。講師はデモ用に 50 に引き上げ

run_prompt 関数と初期プロンプト

  • run_prompt(prompt_inputs)テストケース1件につき1回呼ばれ、そのテストケースの prompt_inputs 辞書を受け取る
  • 辞書の値を f-string でプロンプトに補間する
  • 初期プロンプトはあえて最低品質:「What should this person eat?」+身長・体重・目標・制限の4行を列挙しただけ

評価の実行と extra_criteria

  • run_evaluation() には隠しキーワード引数 extra_criteria(文字列)があり、モデル採点時の追加採点基準を開発者が注入できる
  • 今回の指定:「出力には 1日の総カロリー、マクロ栄養素内訳、正確な食品・分量・タイミング付きの食事を含むこと」

初回スコアと output.html レポート

  • 初回スコアは 2.32/10(惨敗)。講師はわざと賢くないモデルを裏で使っており、プロンプトを具体的にしないと悪い出力が出やすい状態にしている(改善によるスコア上昇を見せるため)。受講者はもっと良いスコアが出て構わない
  • eval を実行するたびに、ノートブックと同じディレクトリに output.html が生成される。ブラウザにドラッグ&ドロップで開くと、全テストケースの出力・スコア・理由(Reasoning)・solution criteria を一覧できる整形済みレポートが見られる。以降のレッスンでプロンプト改善の手がかりとして多用する

実務ポイント(このレッスンから持ち帰るもの)

  • プロンプト改善は「ゴール設定→初期プロンプト→eval→手法適用→再eval」のループで回す。感覚ではなくスコアで改善を検証する、が本モジュールの背骨。
  • 並行実行(max_concurrent_tasks)は速度と rate limit のトレードオフ。квota が小さいうちは 1〜3、エラーが出たら即下げる。デモの 50 を真似しない。
  • テストケース数は学習時は少なく(2〜3)、本番の eval では多く。この使い分けを明示的に行う。
  • 採点基準は extra_criteria で明文化できる。「出力に何が含まれるべきか」を model grader に直接伝える設計は、自前の eval パイプラインにもそのまま流用できる。
  • output.html のような整形レポートを見て「どこで減点されたか(Reasoning)」を読むのが改善の起点。スコアの数字だけ見ない。
  • 悪いモデル+雑なプロンプトはひどい出力になる。モデルが賢くない環境ほどプロンプトエンジニアリングの効果が大きい、という前提もこのデモ設計から読み取れる。

本文(時系列・日本語逐語+画面差し込み)

[0:00] プロンプト評価(prompt evaluation)を押さえたので、次はプロンプトエンジニアリングの世界に進みます。思い出してください。プロンプトエンジニアリングとは、私たちが書いたプロンプトを何らかの形で改善して、より信頼性の高い出力、より高品質な出力を得ることがすべてです。

🖥 [0:00] 画面(スライド「How We'll Learn Prompt Engineering」) 左側の箇条書き: - We'll write an initial prompt, then improve it step by step while learning new techniques - Note: I made an improved version of the eval pipeline, you can find it in the "001_prompting.ipynb" file. Be sure to use this file!

右側(レンガ色パネル)に縦のフロー図: Set a goal ↓ Write an initial prompt ↓ Eval the prompt ↓ Apply a prompt engineering technique ↓ Re-eval to verify better performance (最後の2つの間に「Repeat」のループ矢印)

[0:12] プロンプトエンジニアリングを理解するために、このモジュールでは一連のビデオを進めていきます。まず、モジュールがどう構成されているかを手短に理解してもらいたいと思います。このビデオと次のビデオでは、初期プロンプトを書き出します。そして続くビデオで、そのオリジナルのプロンプトに新しいプロンプトエンジニアリング手法を実装しながら、ステップバイステップで改善を試みます。

[0:29] つまり要するに、このビデオではゴールを設定します。プロンプトにやらせたい何かです。次に、そのプロンプトの初期バージョンを書きます。本当に出来の悪い最初の試み、という感じのものです。それからそのプロンプトを eval(評価)します。そしてすぐに、非常に悪い評価スコアが出るのを目にすることになります。そして先ほど言ったように、続くビデオでさまざまなプロンプトエンジニアリング手法を学び、適用していきます。そして手法を1つ適用するたびに評価を再実行し、改善を加えるごとに性能が良くなっていくのを確認します。

[1:03] さて、これらの eval を実行するために、前のモジュールで組み立てたのと同じ種類の eval パイプラインを使います。ただし1つだけひねりがあります。私と一緒にコードを書いて進めたい場合には、本当に注意しておいてほしいことです。前のモジュールで組み立てた元の eval パイプラインに、私はいくつかの改良を加えました。以前扱っていた非常に特定のプロンプト専用ではなく、ほぼどんなプロンプトでも動くようにするためです。

[1:27] この、より柔軟な評価パイプラインが入った更新版ノートブックを入手するには、必ず付属のノートブック「001_prompting」という名前のファイルをダウンロードしてください。

[1:38] そのノートブックを開く前に、私たちのプロンプトのゴールについて手短に話しておきます。これから書く初期プロンプトと、それが正確に何を意図しているのかについてです。

🖥 [1:41] 画面(スライド「Goal:」) Goal: Write a prompt that generates a 1-day meal plan for an athlete based upon their height, weight, goal, and dietary restrictions (アスリートの身長・体重・目標・食事制限に基づいて1日分の食事プランを生成するプロンプトを書く)

[1:46] それでは。私たちは、アスリートの身長、体重、何らかの身体的な目標、そして食事制限があればそれに基づいて、1日分の食事プランを生成してくれる(ことを期待する)プロンプトを作ります。

[2:00] イメージとしては、アスリートを記述する何らかのサンプル入力を受け取ります。身長、体重、目標、食事制限といったものです。次に、それらの入力をすべてプロンプトに補間(interpolate)します。そしてそれをモデルに送ります。そして願わくば、右側に見えているような出力が返ってくる、というわけです。

🖥 [2:00] 画面(スライド「Sample Input → Our Prompt → Ideal Output」) 左のテーブル(Sample Input): | 項目 | 値 | |---|---| | Height | 180cm | | Weight | 75kg | | Goal | Maintain weight and improve power | | Dietary restrictions | Lactose intolerance |

中央:Our Prompt(レンガ色ボックス)→ 右:Ideal Output(黒背景ターミナル風): ``` Here is a one-day meal plan for an athlete aiming to maintain weight and improve power.

Daily Caloric Total: Approximately 3000 calories (calculated based on height, weight, and goal).

Macronutrient Breakdown: * Protein: 150g (20%) * Carbohydrates: 413g (55%) * Fats: 83g (25%)

Meal Plan: * Meal 1 (7:00 AM): * Oatmeal: 80g (dry weight) * Lactose-Free Protein Powder: 30g * Berries: 100g * Meal 2 (10:00 AM): * Whole Wheat Bread: 2 slices (60g) * Turkey Breast: 120g * Lettuce and Tomato: 50g * Meal 3 (1:00 PM): * Brown Rice: 180g (cooked weight) * Chicken Breast: 150g * Mixed Vegetables: 100g ```

[2:18] これがまさに私たちの目指すもの、理想の出力です。プロンプトの最初のバージョンでは、この右側に見えているものとは似ても似つかない出力が返ってきます。しかし、さまざまなプロンプトエンジニアリング手法を通じて、最終的にプロンプトを磨き上げ、願わくばこれとほぼ同じに見えるものを得られるようにしていきます。

[2:36] それでは、ゴールが理解できたところで、あの新しいノートブックを開きましょう。そう、001 prompting です。前のモジュールと比べていくつか変わった点があるので、手短にツアーをします。中身がすべて明確になるようにしましょう。その後、このノートブックを使って初期データセットを生成します。

[2:53] さて、ノートブックを開きました。まず目につくのは、上部にいくつか折りたたまれたセルがあることです。これは大量のセットアップコードです。これらのセルは必ず最低1回は実行してください。私は今すぐ実行します。

🖥 [2:53] 画面(VS Code+Jupyter:001_prompting.ipynb — prod 上部に「# Helper functions」等の折りたたみセル群。その下に PromptEvaluator 関連セルが見える(スライドからのトランジション直後)。

[3:06] 次に、prompt evaluator と呼ばれるもののインスタンスを作っているのが見えます。PromptEvaluator は私が作ったクラスで、データセット生成、モデル採点、ほぼすべてがこのクラスの中にラップされています。このクラスは引数を1つ取ります。max concurrent tasks です。

🖥 [3:06] 画面(PromptEvaluator セル) ```python

PromptEvaluator Implementation (折りたたみ見出し)

Create an instance of PromptEvaluator

Increase max_concurrent_tasks for greater concurrency, but beware of rate limit errors!

evaluator = PromptEvaluator(max_concurrent_tasks=1) セル実行済み([9]・0.0s)。その下のセル:python dataset = evaluator.generate_dataset( # Describe the purpose or goal of the prompt you're trying to test task_description="", # Describe the different inputs that your prompt requires prompt_inputs_spec={

},
# Where to write the generated dataset
output_file="dataset.json",
# Number of test cases to generate (recommend keeping this low if you're getting rate limit errors)
num_cases=3,

) ```

[3:24] つまり、このクラスは並行処理(concurrency)をサポートしています。複数の API 呼び出しを同時に行えるのです。この利点は、eval のプロセスと、データセット生成プロセスも劇的にスピードアップすることです。ただし知っておいてほしいのは、皆さんのサービスクォータ次第では、あっという間に rate limit エラーに突き当たるかもしれない、ということです。

[3:44] ですから、このモジュールを進める中で rate limit エラーを1つでも見かけたら、ここにあるこの値を、デフォルトの 1(並行処理なし、という意味)まで下げることを強くお勧めします。

[3:55] 私個人はというと、非常に高いレート制限を持っています。なので、これを並行度 50 まで一気に引き上げます。皆さんはおそらく 50 は使えません。だから 50 を試さないでください。まずは 3 くらいから始めることを本当にお勧めします。そして rate limit エラーが出たら、2 や 1 に下げていってください。繰り返しますが、私が 50 を使うのは、これらのステップを実行する際に、画面上ですぐにフィードバックが見えるようにするためだけです。

🖥 [4:01] 画面(並行度を50へ変更) python evaluator = PromptEvaluator(max_concurrent_tasks=50)

[4:19] では、そのセルを確実に実行します。それでは、実際のデータセットの生成に取りかかりましょう。

[4:25] データセットを生成するために、この新しい generate dataset メソッドを追加しておきました。このメソッドを使うには、プロンプトの全体的な目的を記述します。プロンプトが何をするはずのものか、ということです。私たちの場合は、1人のアスリート向けの、コンパクトで簡潔な1日分の食事プランを書くプロンプトに取り組んでいます。

🖥 [4:38] 画面(task_description をタイプ中) python dataset = evaluator.generate_dataset( # Describe the purpose or goal of the prompt you're trying to test task_description="Write a compact, concise 1 day meal pa|", ... (タイプ完了後は "Write a compact, concise 1 day meal plan for a single athlete",

[4:50] そして、この prompt input spec の中には、プロンプトが必要とするさまざまな入力をすべて列挙する辞書を持たせます。少し前に見たとおり、私たちのプロンプトには身長、体重、目標、そして食事制限が必要です。これらは、データセットの一部として生成される追加のプロパティです。最終的に、テストケースごとにそれらを取り出して、プロンプトに補間していきます。

[5:13] それでは、この4つのプロパティをすべて埋めます。height は cm 単位の身長にします。明確にするために「athlete's height(アスリートの身長)」と入れておきます。そしてそれを複製します。ほとんど同じになるからです。weight はキログラムでいきます。goal は「goal of the athlete(アスリートの目標)」、restrictions は「dietary restrictions of the athlete(アスリートの食事制限)」にします。

🖥 [5:44] 画面(prompt_inputs_spec 完成形) python dataset = evaluator.generate_dataset( # Describe the purpose or goal of the prompt you're trying to test task_description="Write a compact, concise 1 day meal plan for a single athlete", # Describe the different inputs that your prompt requires prompt_inputs_spec={ "height": "Athlete's height in cm", "weight": "Athlete's weight in kg", "goal": "Goal of the athlete", "restrictions": "Dietary restrictions of the athlete", }, # Where to write the generated dataset output_file="dataset.json", # Number of test cases to generate (recommend keeping this low if you're getting rate limit errors) num_cases=3, )

[5:47] 最後に知っておくべき入力は、生成するテストケースの数です。これは 3 のままにしておくことを本当にお勧めします。eval の実行がずっと速くなるので、このモジュールをはるかに速く進められるからです。ただ覚えておいてください。何度も言ってきたとおり、現実に eval を行うときは、本当にしっかりした、本当に大きな数のテストケースが必要です。

[6:06] ということで、私自身のは大きく引き上げます。皆さんがそうすることはお勧めしません。2 や 3 くらいのままにして、すべての eval が非常に速く実行されるようにすることをお勧めします。ですが繰り返し、私は 50 まで一気に上げます。すべてをまとめたら、このセルを実行します。これで私のデータセットが生成されます。

🖥 [6:23] 画面(num_cases=50 で実行、生成ログ) python num_cases=50, ) セル [15]・8.4s で完了。出力: Generated 10/50 test cases Generated 20/50 test cases Generated 30/50 test cases Generated 40/50 test cases Generated 50/50 test cases

[6:29] データセットを生成したら、同じディレクトリに作られているはずの dataset.json ファイルを開けます。そこには、生成された個々のデータセットがすべて並んでいるのが見えます。プロンプト eval の話をしていた前のモジュールでやっていたものと、かなり似た構造をしています。

🖥 [7:03] 画面(dataset.json の中身) json [ { "prompt_inputs": { "height": "160", "weight": "50", "goal": "Weight gain", "restrictions": "None" }, "solution_criteria": [ "Provides a 1-day meal plan.", "Meal plan is calorie-dense and suitable for weight gain.", "Total caloric intake is approximately 2500-2700 calories.", "Includes sufficient protein intake (around 100-120g) for muscle growth.", "Includes a variety of food groups (carbohydrates, proteins, fats).", "Meal plan is practical and easy to follow.", "The meal plan is safe and does not promote unhealthy eating habits." ], "task_description": "Write a compact, concise 1 day meal plan for a single athlete", "scenario": "Athlete focusing on weight gain, female, 50kg, calorie surplus" }, { "prompt_inputs": { "height": "165", "weight": "60", "goal": "Maintain muscle mass during training", ...

[6:42] 私のノートブックに戻って、少し下にスクロールし、run prompt 関数のところへ行きます。ここが、プロンプトを書き出し、その後時間をかけて改善していく場所です。この関数は、皆さんが生成したテストケース1件につき1回呼び出されます。この関数が呼ばれるたびに、そのテストケースの prompt inputs を唯一の引数として受け取ります。言い換えると、そこにある prompt inputs があの辞書になる、ということです。そして関数がまた呼ばれると、(次の)あの辞書になり、さらにまた呼ばれると、(その次の)あの辞書になり……という具合です。

🖥 [7:15] 画面(run_prompt 関数の骨組み) ```python

Define and run the prompt you want to evaluate, returning the raw model output

This function is executed once for each test case

def run_prompt(prompt_inputs): prompt = f"""

"""

messages = []
add_user_message(messages, prompt)
return chat(messages)

その下のセル:python results = evaluator.run_evaluation( run_prompt_function=run_prompt, dataset_file="dataset.json" ) ```

[7:11] そこで、この辞書を受け取って、その入力を、ここに書き出すこのプロンプトへと補間していきます。さっそくプロンプトの最初のバージョンを書き出しましょう。非常にシンプルで、非常に素朴(naive)なものになります。非常に悪い eval スコアが出るでしょうが、少なくともスタートは切れます。

[7:27] では、ここに私の最初のスタート用プロンプトを書き込みます。非常に悪いプロンプトを使うことにします。「What should this person eat?(この人は何を食べるべき?)」と書きます。それから、身長、体重、目標、食事制限を列挙します。そしてこれらのそれぞれに、prompt inputs から値を補間します。

🖥 [7:47] 画面(素朴プロンプトをタイプ中) ```python def run_prompt(prompt_inputs): prompt = f""" What should this person eat?

- Height:
- Weight
- Goal
- Diet|
"""

```

[7:53] 最初のは height です。少し時間を節約するためにコピペして、それぞれのキーを更新します。まず height があることを確認して、次に weight、そして goal、それから restrictions です。

🖥 [8:04] 画面(初期プロンプト完成・セル実行) ```python

Define and run the prompt you want to evaluate, returning the raw model output

This function is executed once for each test case

def run_prompt(prompt_inputs): prompt = f""" What should this person eat?

- Height: {prompt_inputs["height"]}
- Weight: {prompt_inputs["weight"]}
- Goal: {prompt_inputs["goal"]}
- Dietary restrictions: {prompt_inputs["restrictions"]}
"""

messages = []
add_user_message(messages, prompt)
return chat(messages)

``` (セル [16] 実行 0.0s)

[8:09] よし、スターター・プロンプトがここに入ったので、そのセルを実行します。それでは eval をやりましょう。eval は、この一番下のところで実行できます。

[8:20] さて、eval を実行する前に知っておいてほしいのですが、評価プロセスを実際に開始するこの関数は、ここには表示していない追加のキーワード引数を1つ受け取ります。extra criteria という名前です。文字列です。この文字列は、モデル採点(model grading)のプロセス中に使われます。

🖥 [8:28] 画面(extra_criteria を追記し始める) python results = evaluator.run_evaluation( run_prompt_function=run_prompt, dataset_file="dataset.json", extra_criteria=""" | """ )

[8:36] この extra criteria というものは、私たち開発者が、モデルが採点を行うときに考慮すべき追加の基準を入れられるようにしてくれるだけのものです。そこで私は具体的にこう指定します。出力には、1日の総カロリー(daily caloric total)、マクロ栄養素の内訳(macro nutrient breakdown)、そして正確な食品・分量・タイミング付きの食事(meals with exact foods, portions, and timing)が含まれることを確認せよ、と。繰り返しますが、これは追加の検証、追加の採点基準を少し加えるだけのものです。

🖥 [9:00] 画面(extra_criteria 完成形) python results = evaluator.run_evaluation( run_prompt_function=run_prompt, dataset_file="dataset.json", extra_criteria=""" The output should include: - Daily caloric total - Macronutrient breakdown - Meals with exact foods, portions, and timing """ )

[9:13] それでは。プロンプトの最初のバージョンの評価を実行して、どんな具合か見てみましょう。

[9:21] はい。絶望的にひどいスコアが出ました。2.32 です。

🖥 [9:21] 画面(eval 実行結果) セル [17]・4.5s。出力: Graded 10/50 test cases Graded 20/50 test cases Graded 30/50 test cases Graded 40/50 test cases Graded 50/50 test cases Average score: 2.32 (最終行「Average score: 2.32」がハイライト選択されている)

[9:27] ちなみに知っておいてほしいのですが、皆さんはおそらく私よりずっと良いスコアが出るはずです。私のスコアが非常に悪い理由は、裏側であまり賢くないモデルを使っているからです。そのため、プロンプトの書き方を非常に具体的にしない限り、本当に悪い出力を返しがちなのです。この非常に悪いモデルを使っているのは、このモジュールを通してさまざまなプロンプトエンジニアリング手法を追加していくにつれて、スコアが上がっていくのを皆さんに見てもらうためだけです。ですから繰り返し、皆さんはもっと良いスコアが出るでしょうが、それでまったく問題ありません。

[9:55] 先に進む前に、最後に1つだけ手短に触れておきたいことがあります。評価を実行するたびに、ノートブックと同じディレクトリに output.html というファイルが作られます。そのファイルをシンプルなドラッグ&ドロップでブラウザの中に開くと、実行されたすべてのテストケースについて、スコア、理由(reasoning)、solution criteria などとあわせて出力を示してくれる、とても綺麗に整形されたレポートが見られます。実際の出力そのものも見られます。

🖥 [10:12] 画面(ブラウザ:output.html「Prompt Evaluation Report」) URL: /Users/sgrider/code/enablements/prod/07/output.html(Incognito ウィンドウ) 上部サマリーカード: - Total Test Cases: 50 - Average Score: 2.3 / 10 - Pass Rate (≥7): 0.0%

テーブル列:Prompt Inputs / Scenario / Solution Criteria / Output / Score / Reasoning 行の例: - 「Athlete needing increased vitamin D, male, 80kg, incorporating it」(height: 180, weight: 80, goal: Increase Vitamin D intake while maintaining current fitness level, restrictions: None)→ 出力はビタミンD食品の箇条書きのみ → Score 2、Reasoning「The solution only lists food sources of Vitamin D without creating a comprehensive meal plan. It misses crucial details like portion sizes, timing, caloric intake, and macronutrient breakdown, failing to meet the core requirements of the task.」 - 「Athlete needing increased potassium, male, 75kg, dietary sources」→ カリウム豊富な食品リストのみ → Score 1、Reasoning「The response only provides a list of potassium-rich foods, failing to deliver a comprehensive 1-day meal plan with specific meals, portions, timing, and nutritional information as requested. It does not meet the core requirements of the task.」 - 「Older athlete, female, 60kg, maintaining bone density」(height: 165, weight: 60)→ 「Here's a general dietary approach:」で始まる一般論 → Score 1

[10:24] なので、この小さなダッシュボードは、eval の出力を眺めて、実際に自分のプロンプトをどう改善する必要があるのかを理解するために、かなり頻繁に使っていくつもりです。

[10:32] それでは。長いビデオになってしまい申し訳ありません。ですがこれで、このモジュールの中で行っているセットアップの中身について、イメージが掴めたことと思います。ご覧のとおり、今は本当に悪いスコアです。あとはプロンプトの改善を始めるだけです。それでは次のビデオで、最初のプロンプトエンジニアリング手法を見ていきましょう。

英語逐語(ASR全文)

Now that we've got a handle on prompt evaluation, we're going to move on to the world of prompt engineering. Remember, prompt engineering is all about taking a prompt that we've written and improving it in some way to get more reliable outputs and higher quality outputs. To understand prompt engineering, we're going to go through a series of videos in this module, and I want to very quickly help you understand how the module is set up. In this video and the next, we're going to write out an initial prompt, and then in the coming videos, we're going to try to improve it step-by-step by implementing new prompt engineering techniques on that original prompt. So in short, in this video, we're going to set a goal. So something we want our prompt to do, we're then going to write an initial version of that prompt. So kind of like a really poor first attempt, we will then eval the prompt. And then we're going to see right away that we get a very poor evaluation score. And then as I mentioned in the coming videos, we're going to learn about and apply some different prompt engineering techniques. And as we apply each of these, we're going to run our evaluation again, and see that we are getting better performance with every single improvement we make. Now to run these evals, we're going to use that same kind of eval pipeline that we put together in the previous module. Just one little twist here, something you really need to be aware of if you want to follow along and code with me. I took our original eval pipeline that we put together in the last module, and I made a couple of different improvements to it to make sure that it can work with just about any prompt as opposed to the very specific prompt we were working on previously. So to get this updated notebook that has this more flexible evaluation pipeline, make sure you download the accompanying notebook named 001 underscore prompting. Before we open up that notebook, however, I want to very quickly tell you about the goal of our prompt. So this initial prompt that we're going to write and exactly what it is intended to do. All right. So we're going to make a prompt that's going to hopefully generate a one day meal plan for an athlete based upon their height, weight, some kind of physical goal that they might have, and any dietary restrictions they might have. So you can imagine that we are going to take in some kind of sample input that describes an athlete, maybe their height, weight, goal, and dietary restrictions. We're then going to interpolate all those inputs into our prompt. and then we're going to send that off to our model. And hopefully we'll get back some kind of output like what you see on the right-hand side. This is what we're really going for. This is our ideal output. In the first version of our prompt, we're going to get some output that looks nothing like what you see here on the right-hand side, but through a variety of different prompt engineering techniques, we're going to eventually refine the prompt and eventually hopefully get something that looks almost exactly like this. All right, so now that we understand our goal, let's open up that new notebook. So remember 001 prompting. I'm going to give you a very quick tour of it because a couple of things have changed compared to the last module. And just make sure everything inside there is super clear. We'll then use the notebook to generate our initial data set. All right, so I've opened up that notebook. Right away, you'll notice there are a couple of collapsed cells at the top. So this is a lot of different setup code. Just make sure you execute those cells at least one time. So I'm going to do so right away. Next up, you'll see that I'm creating an instance of something called a prompt evaluator. PromptEvaluator is a class I created that wraps up all the dataset generation, all the model grading, just about everything is wrapped up inside of this class. The class takes one argument, max concurrent tasks. So this class supports concurrency. We can make multiple API calls at the same time. The upside to this is that it's going to dramatically speed up our eval process and the dataset generation process as well. But I do need you to be aware that depending upon your service quota, you may or may not very quickly start to run into some rate limit errors. So if you see any rate limit errors at all, as you go through this module, I would highly encourage you to change this value right here, all the way down to the default of one, which means no concurrency at all. For me personally, I have super high rate limits. So I'm going to dial this all the way up to a concurrency of 50. Chances are you are not going to be able to use 50. So don't try 50. I would really recommend maybe starting off at three. And then if you see any rate limit errors, start to go down to two or one. Again, I'm going to use 50 just so you can see some immediate feedback on my screen as I run all these different steps. Now I'm going to make sure I run that cell. And then let's get started on generating our actual data set. So to generate the data set, I've added this new generate data set method for us. To use this method, we're going to describe the overall purpose of our prompt. So kind of what our prompt is supposed to do for you and I, we're trying to work on a prompt that is going to write a compact, concise one day meal plan for a single athlete. And then inside of this prompt input spec, we're going to have a dictionary that's going to list out all the different inputs that our prompt requires. As we saw just a moment ago, our prompt is going to require a height, a weight, a goal, and some dietary restrictions. So these are some extra properties that are going to be generated as a part of the data set. And eventually we're going to take them test case by test case and interpolate them into our prompt. So I'm going to fill out all four of these different properties. My height is going to be the height in CM. And just to be clear, I'll put in athletes height. And I'm going to duplicate that because it's going to be just about the same. we're going to do our weight in kilograms. My goal is going to be the goal of the athlete and my restrictions will be dietary restrictions of the athlete. Now last input for you to be aware of is number of test cases to generate. I would really recommend that you just leave this at three because it's going to allow you to get through this module way faster because the evals are going to run much more quickly. Just remember, as I mentioned many times, whenever we do an eval in reality, we want to have a really solid, really large number of test cases. So I'm going to dial mine personally way up. I do not recommend you do this. I recommend you leave it at like two or three, just to make sure all your evals run very quickly. But again, I'm going to dial mine all the way up to 50. Once I put this all together, I'm going to run the cell and that's going to generate my dataset. Once I have generated my dataset, I can open up the dataset.json file, which should be created in the same directory. And we're going to see all these different individual datasets have been generated. So they have a pretty similar structure to what we were doing on our previous module when we were talking about prompt evals. I'm going to go back over to my notebook and then scroll down a little bit to the run prompt function. This is where we are going to write out our prompt and then eventually improve it over time. This function gets called one time for every test case that you generated. Whenever this function is called, it's going to receive your test cases prompt inputs as its only argument. So in other words, prompt inputs right there is going to be that dictionary. And then that function is going to be called again, and it will be that dictionary. And then again, and it will be that dictionary and so on. So we're going to take this dictionary and we're going to interpolate those inputs into this prompt that we're going to write out right here. Let's immediately write out a first version of our prompt and it's going to be very simple, very naive. We're going to get a very bad eval score, but it will at least get us started. So I'm going to write in my initial starting prompt here, and I'm going to use a very bad prompt. I'll say, what should this person eat? And then I'm going to list out their height, their weight, their goal, and dietary restrictions. and then for each of these I'm going to interpolate in a value from prompt inputs. So the first one will be the height and I'm going to copy paste that just to save a little bit of time and update the keys on each one. So make sure you have first height and then we want the weight and then our goal and then restrictions. All right once we have our starter prompt in here I'm going to run that cell and then let's do our eval. So we can run our eval down here at the very bottom. Now, before we run our eval, I want you to know that this function that's going to actually kick off the evaluation process, it takes in one additional keyword argument that I'm not showing here. It's called extra criteria. It's going to be a string. This string is going to be used during the model grading process. This extra criteria thing just allows you and I as developers to put in some extra criteria that the model should consider whenever it's doing some grading. So I'm going to say specifically to make sure that the output should include a daily caloric total, a macro nutrient breakdown, and meals with exact foods, portions, and timing. Again, this is just going to add in a little bit of extra validation or a little bit of extra grading criteria. All right. So now let's run our evaluation for the first version of our prompt and see how we're doing. All right. We get a absolutely terrible score. I get a 2.32. Now, just so you know, you are probably going to end up getting a much better score than I get. The reason I have a very bad score here is that I'm using a model behind the scenes that is not super smart. so it's going to tend to give me really bad output unless I'm very specific in how I prompt it. I am using this very bad model just so you can see the increase in score over time as we go throughout this module and add in all these different prompt engineering techniques. So again you are probably going to get a better score that's totally fine. Now before we move on there's one last thing I want to mention really quickly. Whenever you run an evaluation a file will be created in the same directory as your notebook called output.html. If you open up that file inside of your browser with a simple drag and drop, you're going to see a really nicely formatted report that gives you output on every single test case that was executed along with the score, the reasoning, solution criteria, and so on. You could also see the actual output too. So I'm going to use this little dashboard quite a bit in order to take a look at the output of the eval and understand how I actually need to improve my prompt. All right. I apologize for the long video here, but now hopefully you have an idea of some of the setup that we're doing inside of this module. So now all we really have to do, as you can see, we have really bad score right now. All we have to do is start to improve our prompt. So let's start to take a look at our first prompt engineering technique in the next video.

↑ 目次へ戻る

Building with the Claude API | Lesson 22「Being clear and direct」(全文ナレッジ)

動画 2分5秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンのテーマ

プロンプトエンジニアリングの第1テクニック「Be clear and direct(明確かつ直接的に)」。前レッスンで作った食事プラン生成プロンプトの eval スコアが 2.32 と低かったので、ここから改善を始める。

「明確かつ直接的」=プロンプトの1行目の話

  • この2つのルールは、実質的にプロンプトの最初の1行についてのルール
  • プロンプトの1行目が最も重要になる傾向がある
  • 1行目では、シンプルで直接的な言葉を使い、動作動詞(action verb)で Claude のタスクが何かを正確に伝える

スライドの整理:「Clear」と「Direct」

“Clear”(明確に) - シンプルな言葉を使う - 望むものを明示的に述べる - モデルのタスクをシンプルに述べる文でプロンプトを始める

“Direct”(直接的に) - 質問ではなく指示を使う - 直接的な動作動詞を使う(“Write”, “Create”, “Generate”)

良い1行目の例(スライドより)

  1. Write three paragraphs about how solar panels work → Claude に「仕事がある」ことを伝える(write / generate / create する)。さらに期待する出力とその中身についての情報も明確化している。
  2. Identify three countries that use geothermal energy and for each include generation stats → やはり「何かをせよ」という即座のタスクと、期待する出力についての情報を与えている。

ノートブックでの適用

  • 対象は eval 対象の run_prompt() 内プロンプト。元の1行目は What should this person eat?(質問形)だった
  • これをルールに従い、次のように書き換える: Generate a one-day meal plan for an athlete that meets their dietary restrictions. (冒頭に動作動詞 “Generate”、シンプルな言葉で直接的なタスクを提示)
  • セルを再実行してプロンプトを更新 → eval を再実行

結果

  • スコアは 2.32 → 3.92 に改善。「確かに改善したが、まだ良いとは言えない」→ 次のレッスンで次のテクニックへ続く

実務ポイント(このレッスンから持ち帰るもの)

  • プロンプトの1行目が最重要。1行目に「モデルのタスクをシンプルに述べる文」を置く。
  • 1行目は動作動詞で始める(“Write” / “Create” / “Generate”)。「〜って何?」のような質問形ではなく指示形にする。
  • 「何を出力してほしいか」(段落数・件数・含めるべき統計情報など)の期待出力の情報も1行目に織り込むと効果的。
  • 曖昧な依頼(例:「屋根に付けるあの太陽のやつについて知りたい」)は、明示的な依頼(例:「ソーラーパネルの仕組みについて3段落書け」)に書き換える。
  • 改善は必ず eval で数値検証する(2.32 → 3.92 のように before/after を測る)。感覚ではなくスコアで判断する運用が本コースの流儀。
  • 1つのテクニックだけでは不十分(3.92 は「まだ良くない」)。複数のテクニックを積み重ねてスコアを上げていく。

本文(時系列・日本語逐語+画面差し込み)

[0:00] 開始時点の評価が 2.32 ですから、ここからは間違いなく上がるしかありません。それを念頭に置いて、プロンプトを改善するために使う最初のプロンプトエンジニアリング・テクニックを見ていきましょう。

🖥 [0:00] 画面(Jupyter ノートブック 002_prompting_completed.ipynb — prod) VS Code + Jupyter。タブに 002_prompting_completed.ipynbdataset.json。評価対象のプロンプト定義セル: ```python

Define and run the prompt you want to evaluate, returning the raw model output

This function is executed once for each test case

def run_prompt(prompt_inputs): prompt = f""" What should this person eat?

  • Height: {prompt_inputs["height"]}
  • Weight: {prompt_inputs["weight"]}
  • Goal: {prompt_inputs["goal"]}
  • Dietary restrictions: {prompt_inputs["restrictions"]} """

    messages = [] add_user_message(messages, prompt) return chat(messages) その下に eval 実行セル:python results = evaluator.run_evaluation( run_prompt_function=run_prompt, dataset_file="dataset.json", extra_criteria=""" The output should include: - Daily caloric total ... ```

[0:10] よし。これから「明確かつ直接的であること(being clear and direct)」という考え方について議論していきます。この2つのルールは、実のところ、あなたのプロンプトの一番最初の行についての話です。プロンプトの最初の行は、最も重要になる傾向があります。その最初の行では、シンプルで直接的な言葉を使い、いわば動作動詞(action verb)を使って、Claude のタスクが正確に何であるかを伝えたいのです。

🖥 [0:09] 画面(スライド「Be clear and direct」へ切り替わり) ノートブックからスライドへワイプ遷移。スライドはベージュの「“Clear”」ボックスと赤茶の「“Direct”」ボックスの2枚組:

“Clear” - Use simple language - State what you want explicitly - Lead your prompt with a simple statement of the model's task

Instead of: "I need to know about those things people put on their roofs that use sun - those solar panel things, I think they're called" Use: "Write three paragraphs about how solar panels work"

“Direct” - Use instructions, not questions - Use direct action verbs ("Write", "Create", "Generate")

Instead of: "I was reading about renewable energy and geothermal energy sounds neat. What countries use it?" Use: "Identify three countries that use geothermal energy. Include generation stats for each."

(左下に ANTHROP\C ロゴ)

[0:29] 例えば、プロンプトの最初の行を、こんなふうにしたいかもしれません。「ソーラーパネルがどう機能するかについて3段落書け(write three paragraphs about how solar panels work)」。これは Claude に、これから仕事があるのだ、ということを伝えます。何かを書く、生成する、作る必要があるのだと。さらに、期待される出力について、そしてその出力が正確に何を含むべきかについての情報も、少し明確にしています。つまり、その一番最初の行で、アクションを設定し、タスクを与えているのです。

[0:52] もう1つの良い例は、「地熱エネルギーを使っている国を3つ特定し、それぞれについて発電統計を含めよ(identify three countries that use geothermal energy and for each include generation stats)」でしょう。ここでも、Claude に何かをするよう伝え、つまり即座のタスクを与え、期待される出力についての情報も少し与えています。

[1:05] この「プロンプトの一番最初の行で明確かつ直接的であれ」という考え方を持って、今取り組んでいるプロンプトの成果を改善できるかどうか、やってみましょう。

🖥 [1:11] 画面(スライドからノートブックへ切り替わり) スライドが左へスライドアウトし、002_prompting_completed.ipynb の編集画面に戻る。run_prompt() 内のプロンプト1行目 What should this person eat? の行末にカーソルが置かれ、書き換え直前の状態。

[1:14] 先ほど学んだルールを使って、私はこの行を、例えばこんなふうに更新するかもしれません。「その人の食事制限を満たす、アスリート向けの1日分の食事プランを生成せよ(generate a one-day meal plan for an athlete that meets their dietary restrictions)」。

[1:28] つまりここでも、冒頭に動作動詞を使うことで直接的になっています。そして非常にシンプルな言葉で、Claude が果たすべき直接的なタスクを与えています。

[1:37] では、セルを再実行して更新済みのプロンプトを反映し、それから eval 自体を再実行して、これがスコアの改善に何か効果があるかを見てみましょう。

🖥 [1:45] 画面(eval の再実行) run_prompt セルは実行済み([9] ✓ 0.0s)。その下の eval セルが実行中: python results = evaluator.run_evaluation( run_prompt_function=run_prompt, dataset_file="dataset.json", extra_criteria=""" The output should include: - Daily caloric total - Macronutrient breakdown - Meals with exact foods, portions, and timing """, ) (セル左に実行中インジケータ、経過 1.4s と表示)

[1:45] そして、皆さんの予想どおりだと思いますが、ええ、おそらく前回よりも少し良い結果になるはずです。

[1:50] 確か前は 2.32 でした。今は 3.92 まで上がりました。間違いなく改善ですが、まだ素晴らしいとは言えません。

[1:58] それでは次のビデオに進んで、プロンプトをもう少し改善するための、次のプロンプトエンジニアリングのトピックを見ていきましょう。

英語逐語(ASR全文)

With a starting grade of 2.32, we can definitely only go up. So with that in mind, let's take a look at our first prompt engineering technique that we're going to use to improve our prompt. All right. So we're going to be discussing the idea of being clear and direct. These two rules are really talking about the very first line of your prompt. The first line of your prompt tends to be the most important. In that first line, you want to use simple and direct language and tell Claude with a kind of action verb exactly what its task is. So for example, we might want to have a first line of a prompt be something like, write three paragraphs about how solar panels work. That tells Claude that it's going to have a job. It needs to write or generate or create something. It also clarifies a little bit of information about the expected output and exactly what that output should contain. So we're really setting an action and providing a task in that very first line. Another great example would be, identify three countries that use geothermal energy and for each include generation stats. Again, we are telling Claude to do something or giving it an immediate task and a little bit of information about the expected output. Let's take this idea of being clear and direct in the very first line of our prompt and see if we can't use it to improve the outcomes of our prompt that we're currently working on. Using that rule that we just learned, I might update this line to say something like, generate a one-day meal plan for an athlete that meets their dietary restrictions. So again, I'm being direct by making use of a action verb at the very start. And then in very simple language, I'm providing a direct task for Claude to fulfill. Now let's rerun the cell to get our updated prompt and then rerun the eval itself and see if this does anything to improve our score. And I bet as you can guess, yeah, we're probably going to do a little bit better than we did previously. So I think before I had a 2.32. Now I'm up to a 3.92, definitely an improvement, but still not great. So let's move on to the next video and take a look at our next prompt engineering topic in order to improve our prompt a little bit more.

↑ 目次へ戻る

Building with the Claude API | Lesson 23「Being specific」(全文ナレッジ)

動画 5分13秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンのテーマ

プロンプトエンジニアリングの次のトピック「具体的であること(Being specific)」。プロンプトにガイドライン(guidelines)またはステップ(steps)のリストを書き、モデルの出力を特定の方向へ誘導する。

なぜ必要か:短編小説の例

「隠れた才能を発見するキャラクターの短編小説を書いて」とだけ頼むと、Claude は無限にある方向のどれへでも進める: - 物語の長さを大きく変えられる - 要素を勝手に足したり削ったりできる - 登場人物が1人かもしれないし、5人登場するかもしれない

特定の種類の出力を確実に得たいなら、ガイドラインのリストを付ける: 1. 物語は 1,000 語未満に収める 2. キャラクターの才能が明らかになる明確なアクションを入れる 3. サポートキャラクターを最低1人登場させる

ガイドラインの2类型(Guideline Types)

Type A:出力が持つべき性質を列挙 Type B:モデルが従うべきステップを提示
狙い 出力の属性を制御(長さ・構造・持たせたい性質) 最終成果物にどう辿り着くかを制御(特定の事柄を考えさせる/方向を選ばせる)
「1,000 語未満」「明確なアクション」「サポートキャラ1人以上」 「劇的緊張を生む才能を3つブレインストーム→最も面白いものを選ぶ→才能が明かされる pivotal なシーンをアウトライン→発見のインパクトを高めるサポートキャラ類型を3つブレインストーム」

プロの現場のプロンプトでは、この2つを混ぜて使うことが非常に多い(属性のガイドライン+従うべきステップの併記)。どちらも「具体的であること」の実例。

ノートブック実験:食事プラン eval のスコア変化

前レッスンから続く「アスリート向け1日食事プラン生成」プロンプトを eval(50 テストケース)で採点: - ベースライン(具体性なし):3.92 - Type A(ガイドライン列挙)を貼付7.86 — 出力に見たいものを正確に伝えただけで劇的改善 - Type B(ステップ提示)に差し替え:7.3 — 依然として劇的改善だが、属性列挙にはわずかに及ばず(7.86 は統計的な揺らぎの可能性も講師が言及) - 講師は最終的にガイドライン列挙版へ revert した

使い分けの指針

  • 出力が持つべき性質のリスト:ほぼすべてのプロンプトで常に推奨
  • 従うべきステップの提示:より複雑な問題で、Claude が自然には考慮しない広い視野・追加の論点を強制的に考慮させたい時に使う
  • 適する場面:困難な問題のトラブルシューティング/意思決定/クリティカルシンキング
  • 例:「営業チームの数字が前四半期に 30% 落ちた原因を1ページの意思決定レポートに」→ 市場指標の比較・業界の変化・メンバー個人のパフォーマンス・組織変更・顧客フィードバックの5ステップを踏ませる

実務ポイント(このレッスンから持ち帰るもの)

  • 曖昧なプロンプトは出力の分散が大きい。出力の品質を安定させる最速の一手は「Guidelines:」として出力が持つべき性質を番号付きリストで列挙すること(このレッスンの実測で 3.92 → 7.86)。
  • ガイドラインには2类型ある:①出力属性の列挙(長さ・構造・必須要素)と②思考ステップの提示(ブレインストーム→選択→アウトライン等)。①はどんなプロンプトでもほぼ常時、②は複雑な問題で視野を強制拡張したい時。
  • ②のステップ提示は必ずしも①より高スコアになるとは限らない(実測 7.3 < 7.86)。eval で計測して選ぶのが正しい進め方 — 感覚で決めない。
  • プロのプロンプトでは①と②の併用が定番。「Guidelines:」と「Follow these steps:」を同居させてよい。
  • 1回の eval スコアは統計的揺らぎを含む(講師も 7.86 を「anomaly かもしれない」と保留)。僅差の比較は複数回回すか差を大きくして判断する。

本文(時系列・日本語逐語+画面差し込み)

[0:00] プロンプトエンジニアリングの世界で次に議論するトピックは、「具体的であること(being specific)」という考え方です。具体的であるためには、プロンプトに何らかのガイドラインやステップをリストアップして、モデルを特定の方向へ何らかの形で導くようにします。

🖥 [0:00] 画面(スライド「Be specific」) 見出し:Be specific/サブ見出し:Provide a list of guidelines or steps to direct the model 左側「Not Great」のコードボックス: python prompt = f""" Write a short story about a character who discovers a hidden talent """ →(矢印)→ 右側「Better!」のコードボックス: ```python prompt = f""" Write a short story about a character who discovers a hidden talent

Guidelines: 1. Keep the story under 1,000 words 2. Include a clear action that reveals the character's talent 3. Include at least one supporting character """ ```

[0:11] 例えば、画面の左側にある私のプロンプトを見てください。このプロンプトでは、Claude に「隠れた才能を発見するキャラクターについての短編小説を書いて」と頼んでいます。

[0:20] このプロンプトを単体でそのまま Claude に入れると、Claude は無限にある方向のうちのどれへでも進むことができます。物語の長さを大幅に変えることもできます。物語に余分な要素を加えたり、要素を取り除いたりすることもできます。登場人物がたった1人かもしれませんし、5人の異なるキャラクターを登場させるかもしれません。

[0:37] 特定の種類の出力を確実に得たいなら、右側に見えるように、ガイドラインのリストを入れることにするとよいでしょう。これらのガイドラインは、Claude が応答を生成し始める時に、高いレベルの指針を与える、つまり特定の方向へ Claude を導いてくれます。

[0:52] 例えば、「物語を 1,000 語未満に保つ」「盛り上がり(rising action)を入れる」「サポートキャラクターを最低1人含める」といったガイドラインを追加することにするかもしれません。これで、特定の種類の短編小説を書く方向へ Claude を導く、ちょっとした指針を与えられました。

[1:05] さて、プロンプトの中で非常によく見かけるガイドラインには2つの種類があります。左側の Type A は、先ほどの図でお見せしたものに近いものです。ガイドラインのリストを入れることにして、それらは出力に持たせたい性質を列挙します。例えば、出力の長さや出力の構造を制御しようとしたり、出力が持つべきさまざまな属性を列挙したりするかもしれません。

🖥 [1:11] 画面(スライド「Guideline Types」) 見出し:Guideline Types。左右2つの黒いプロンプトボックス。 左列見出し:List qualities that the output should have ``` Write a short story about a character who discovers a hidden talent

Guidelines: 1. Keep the story under 1,000 words 2. Include a clear action that reveals the character's talent 3. Include at least one supporting character 右列見出し:Provide steps the model should follow Write a short story about a character who discovers a hidden talent

Follow these steps: 1. Brainstorm 3 talents that would create dramatic tension 2. Pick the most interesting talent 3. Outline a pivotal scene that reveals the talent 4. Brainstorm 3 supporting characters types that could increase the impact of this discovery ```

[1:30] 右側にあるのが、私たちが提供できる2つ目の種類のガイドラインで、モデルが従うべき実際のステップを提示するものです。その意図は、モデルに特定の事柄について考えさせたり、複数の方向の中から選択させたりして、出力の品質を高めることにあります。

[1:45] 例えば、Claude にこう指示するかもしれません。まず、本当に面白くなりそうな特別な才能を3つブレインストームして、その中から最も面白いものを選ぶ。次に、その才能が明らかになるような何か面白いシーンをアウトラインする、あるいは考えてみる。そして、物語をもう少し面白くできそうなさまざまな種類のサポートキャラクターについて考える、というようにです。

[2:08] つまり左側では、私たちは出力の中の属性を導いています。右側では、Claude が最終成果物にどう辿り着くかについて、もう少し具体的に指定しようとしています。

[2:19] そして、これはプロフェッショナルなプロンプトで本当によく見かけるのですが、この2つのテクニックが混ぜて使われているのを、間違いなく非常に頻繁に目にすることになります。つまり、出力のいくつかの属性を制御することを意図したガイドラインのリストがあり、さらにモデルが従うべきステップのリストもある、という形です。これらはどちらも、プロンプトにおいて「具体的である」ことの実例です。

[2:34] さて、「具体的である」とはどういうことかのイメージが掴めたところで、作成途中のプロンプトに戻って、この「具体的である」という考え方を組み込めるか見てみましょう。

[2:45] はい、こちらに戻ってきました。私の run prompt 関数を見ています。

🖥 [2:45] 画面(Jupyter ノートブック 002_prompting_completed.ipynb — prod) VS Code + Jupyter。タブ:002_prompting_completed.ipynbdataset.json。右上 .venv (Python 3.12.9)。 上部に前回の eval 実行の出力(…ated 40/50 test cases …ated 50/50 test cases)。その下、パンくず「# Define and run the prompt you want to evaluate, returning the raw model output」配下のセル: ```python

Define and run the prompt you want to evaluate, returning the raw model output

This function is executed once for each test case

def run_prompt(prompt_inputs): prompt = f""" Generate a one-day meal plan for an athlete that meets their dietary restrictions.

- Height: {prompt_inputs["height"]}
- Weight: {prompt_inputs["weight"]}
- Goal: {prompt_inputs["goal"]}
- Dietary restrictions: {prompt_inputs["restrictions"]}
"""

messages = []
add_user_message(messages, prompt)
return chat(messages)

`` (この時点ではまだガイドライン未追加。下にresults = evaluator.run_evaluation(` セルが続く)

[2:51] さて、少し時間を節約するために、まずガイドラインのリストを貼り付けます。これは「具体的である」の1つ目のタイプ、つまり出力の中に本当に見たい属性のリストを含めるやり方に近いものです。そのセルを実行して、下へ行って、もう一度 eval を実行します。

🖥 [2:56] 画面(ガイドライン貼り付け後の run_prompt 関数) ※フレーム実体は 4:17 の revert 後シーンだが、貼り付けたガイドラインと同一内容: ```python def run_prompt(prompt_inputs): prompt = f""" Generate a one-day meal plan for an athlete that meets their dietary restrictions.

- Height: {prompt_inputs["height"]}
- Weight: {prompt_inputs["weight"]}
- Goal: {prompt_inputs["goal"]}
- Dietary restrictions: {prompt_inputs["restrictions"]}

Guidelines:
1. Include accurate daily calorie amount
2. Show protein, fat, and carb amounts
3. Specify when to eat each meal
4. Use only foods that fit restrictions
5. List all portion sizes in grams
6. Keep budget-friendly if mentioned
"""

messages = []
add_user_message(messages, prompt)
return chat(messages)

```

[3:10] さて、何が出るか見てみましょう。少し待つと、最終スコア 7.86 が返ってきます。これは、前回の 3.92 に対して信じられないほどの改善です。ちょっとした指針を加えて、出力の中に何を見たいかを Claude に正確に伝えただけで、です。

🖥 [3:10] 画面(eval 実行中) eval セルが実行中(スピナー、5.5s 経過表示): python results = evaluator.run_evaluation( run_prompt_function=run_prompt, dataset_file="dataset.json", extra_criteria=""" The output should include: - Daily caloric total - Macronutrient breakdown - Meals with exact foods, portions, and timing """, ) 出力(進行中): Graded 10/50 test cases Graded 20/50 test cases Graded 30/50 test cases

[3:31] 次に、これのちょっとした変種を入れてみます。「具体的である」の2つ目のバリエーションを使うものです。つまり、この食事プランを正確にどう組み立てるかを決める時に Claude が従うべきステップを提示します。このシナリオでは、Claude に、まず計算をして、次にこれについて考えて、それから少し計画を立てるように、と指示しています。だいたいの考え方は掴めると思います。Claude が通るべきステップを提示しているわけです。

🖥 [3:37] 画面(ステップ提示版の run_prompt 関数) ガイドラインのブロックがステップ列に差し替わっている(セル [13]): ```python

Define and run the prompt you want to evaluate, returning the raw model output

This function is executed once for each test case

def run_prompt(prompt_inputs): prompt = f""" Generate a one-day meal plan for an athlete that meets their dietary restrictions.

- Height: {prompt_inputs["height"]}
- Weight: {prompt_inputs["weight"]}
- Goal: {prompt_inputs["goal"]}
- Dietary restrictions: {prompt_inputs["restrictions"]}

Follow these steps:
1. Calculate daily calories needed
2. Figure out protein, fat, carb amounts
3. Plan meal timing around workouts
4. Choose foods that fit restrictions
5. Set portion sizes in grams
6. Adjust for budget if needed
"""

messages = []
add_user_message(messages, prompt)
return chat(messages)

```

[3:52] もう一度セルを実行して、それから下のここを実行します。あの 7.86 というスコアを覚えておいてください。あれは本当に、本当に高い値です。ちょっとした統計的な異常値(statistical anomaly)かもしれませんが。そして今度は 7.3 が出ました。これも依然として劇的な改善ではありますが、出力の中に見たい属性を列挙するやり方ほどは良くありません。ですから私としては、元に戻して(revert)、ガイドラインを列挙するやり方に戻ります。

🖥 [4:17] 画面(ガイドライン版へ revert) run_prompt 関数の中身が再び Guidelines: 1. Include accurate daily calorie amount 〜 6. Keep budget-friendly if mentioned のガイドライン列挙版に戻される(2:56 の画面と同一内容。カーソルは末尾行 6. Keep budget-friendly if mentioned の直後)。

[4:23] では、どちらのテクニックをいつ使えばよいのでしょうか? 私は一般的に、左側でお見せしているような「出力が持つべき性質の列挙」は、あなたが今後取り組むほぼすべてのプロンプトで、ほぼ常に行うことをお勧めします。そして、右側で示しているような「モデルが従うべきステップの提示」は通常、より複雑な問題に Claude を取り組ませる場合、つまり Claude が自然に考慮しようとする範囲を超えて、より広い視野や追加のトピックをいわば強制的に考慮させたい時に使います。

🖥 [4:25] 画面(スライド「When to Provide "Steps"」) 見出し:When to Provide "Steps" 左側テキスト:Try listing steps for the model to follow with these kinds of prompts: - Troubleshooting hard problems - Decision making - Critical thinking - Anytime you want to force Claude to consider a "wider" view

右側の黒いプロンプトボックス: ``` Write a one page decision report to troubleshoot why a sales team's numbers have dropped 30% last quarter.

Follow these steps: 1. Compare current vs previous market metrics 2. Identify relevant industry changes 3. Analyze individual team member performance 4. Consider recent organizational changes 5. Review customer feedback ```

[4:47] 例えば、画面右側のプロンプトを考えてみてください。ここでは Claude に、営業チームの数字が前四半期に落ちた理由を突き止めるように頼んでいます。このシナリオでは、Claude がそうしなければすぐには考慮しないかもしれない追加の視点や追加のデータを、強制的に考慮させたいところです。

[5:05] それでは、「具体的である」とは本当はどういうことか、そして状況に応じてガイドラインやステップを追加するというこの考え方について、より良いイメージが掴めたところで、ここで再び休憩を取り、すぐに次のプロンプトエンジニアリングのトピックへ進みましょう。

🖥 [5:04] 画面(アウトロ:スライドの再表示) 締めのナレーションの間、「Guideline Types」→「Be specific」のスライドが順に再表示されて終了する。

英語逐語(ASR全文)

The next topic we're going to discuss in the world of prompt engineering is the idea of being specific. To be specific, we want our prompts to list out some sort of guidelines or steps to somehow direct our model in a particular direction. For example, consider the prompt I have on the left-hand side of the screen. In this prompt, I'm asking Claude to write out a short story about a character who discovers a hidden talent. If I just put that prompt by itself into Claude, Claude can go any of an infinite number of directions. it can decide to vary the story length significantly. It can decide to add in extra elements or remove elements from the story. It might have just one character. It might introduce five different characters. If I want to ensure that I'm going to get a particular kind of output, I might decide to put in a list of guidelines, as you see on the right-hand side. These guidelines will provide some high-level guidance or kind of direct clod in a specific way when it starts generating the response. So for example, I might decide to add in some guidelines of keeping the story under a thousand words, add in some rising actions, and including at least one supporting character. Now I've provided a little bit of guidance to direct Claude towards writing a particular kind of short story. Now there are two kinds of guidelines you're going to see very often in prompts. Type A on the left hand side, it's kind of like what I just showed you in the previous diagram. You might decide to put in a list of guidelines and those are going to list out some qualities that you want your output to have. So you might try to control maybe the length of the output or the structure of the output, or maybe list out some different attributes that the output should have. On the right hand side, the second type of guidelines that we can provide are to provide some actual steps that the model should follow with the intent of making the model think about specific things or choose between different directions that would hopefully increase the quality of our output. So for example, we might instruct Claude to maybe first brainstorm three special talents that would be really interesting, and then pick the most interesting one. We might then ask Claude to try to outline or think about some kind of interesting scene that would reveal that talent, and then think about different kinds of supporting characters that could make the story a little bit more interesting. So on the left-hand side, we're really guiding attributes in the output. On the right-hand side, we're trying to be a little bit more specific in how Claude arrives at the final product. You can absolutely, and you're going to see this very often in professional prompts, you'll absolutely very often see these two techniques mixed together. So you might have a list of guidelines that intend to control some attributes of the output, and then a list of steps that the model should follow as well. Both of these are examples of being specific in your prompting. So now that we've seen some idea around what it means to be specific, let's go back over to our prompt in progress and see if we can incorporate this idea of being specific. All right, so back over here, I'm taking a look at my run prompt function. Now, just to save a little bit of time, I'm going to first paste in a list of guidelines. So this is kind of like that first type of being specific, where I include a list of attributes that I really want to see inside of the output. I'm going to run that cell and then go down and run the eval again. and let's see what we get here so after a quick pause i'm going to get back a final score of 7.86 that is an incredible improvement over our previous 3.92 just by adding in a little bit of guidance and telling claude precisely what things we want to see inside of the output now i'm going to try to put in a little variant of this where i use that second variation of being specific. So I'm going to provide some steps that Claude should follow when deciding exactly how to build up this meal plan. So now in this scenario, I'm telling Claude to first do a calculation and then think about this and then do a little bit of planning. You kind of get the idea. I'm providing some steps that Claude should go through. I'm going to run the cell again and then run down here. And I'm going to remember that score of 7.86. That is really, really high. It might be a little bit of a statistical anomaly. And now we get 7.3. So still a dramatic improvement, but not quite as good as listing out some attributes that we'd want to see inside the output. So for me, I'm going to revert and go back to listing out some guidelines. So when would you want to use one technique versus the other? Well, I would generally recommend almost always listing out qualities that the output should have, as I'm showing on the left-hand side, on just about any prompt you ever work on. And you usually want to provide steps the model should follow as shown on the right hand side anytime you're asking Claude to work on a more complex problem where you want to kind of force Claude to consider a wider view or some extra topics beyond what it naturally might want to consider. For example consider the prompt on the right hand side of the screen where I ask Claude to figure out why a sales team's numbers have dropped in the last quarter. In this scenario we might want to force Claude to consider some extra viewpoints or extra pieces of data that it might not otherwise immediately consider. All right, so now that we've got a better idea of what it really means to be specific and this idea of adding guidelines or steps as the situation warrants, let's take another break right here and then move on to our next prompt engineering topic in just a moment.

↑ 目次へ戻る

Building with the Claude API | Lesson 24「Structure with XML tags」(全文ナレッジ)

動画 4分0秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンのテーマ

プロンプトエンジニアリング技法の3つ目:XMLタグでプロンプトに構造を与える(Providing Structure)。プロンプト内の異なるコンテンツ片をXMLタグで包み、「どのテキストが何なのか」をClaudeに明示する。

背景:なぜ構造が必要か

  • プロンプトを書く時は、何らかのコンテンツを補間(interpolate)することが非常に多い。このコースの例でも height / weight / goal / restrictions を f-string で埋め込んできた。
  • これらの値は小さいが、いずれ大量のコンテンツをプロンプトに入れる場面が来る。例:売上記録20ページ分を貼り付けて分析させる。
  • 大量のコンテンツを流し込むと、Claude にとって「どのテキストが何を意味するのか」「テキストがどうグループ化されているのか」の判別が難しくなる。

解決策:XMLタグで包む

  • コンテンツ片を <sales_records>…</sales_records> のようなXMLタグで包むと、プロンプトの構造が明確になる。
  • sales_records という公式タグは存在しない。講師がその場で作った名前であり、タグ名自体が「中身の性質」への手がかりをClaudeに与える。
  • recordsdata でも動くが、より具体的な名前の方が確実に良いsales_records が最良の出力を得やすい)。
  • スライドの整理:XMLタグは(1)大量のコンテキストを含める時に最も有効、(2)Claudeにとってのデリミタ(区切り記号)として機能する。

誇張した例:コード+ドキュメントのデバッグ依頼

  • 「提供されたドキュメントを使って以下の私のコードをデバッグして」という先頭行の下に、自分のバグありコードドキュメントのコードが混在して並ぶプロンプト(Not Great)では、どれがデバッグ対象でどれがドキュメントか全く区別できない。
  • 改善(Better!):自分のコードを <my_code>…</my_code>、ドキュメントを <docs>…</docs> で包む。タグ名は直接的・自明なものにする。これでClaudeは「デバッグすべきコード」と「参照すべきドキュメント」を確実に区別できる。

ノートブックでの適用:食事プラン・プロンプト

  • 今回のシナリオには区切りが必要な「大きなコンテンツ塊」は無い。height / weight / goal / restrictions は十分短く、Claudeが混乱する恐れはほぼ無い。
  • それでも、「これは外部入力(アスリートに関する情報)であり、食事プラン生成時に考慮すべきもの」と明示するために、補間ブロック全体を <athlete_information>…</athlete_information> のようなタグで包む価値はある。

評価結果(eval)

  • XMLタグ追加のスコア:7.3
  • タグを追加してプロンプトセルを再実行 → evalセル(50テストケース)を実行 → スコアは大きく上昇(画面では Average score: 7.94)。
  • 注意:講師は改善効果を誇張して見せるために意図的にシンプルで基本的なモデルを使っている。受講者側で同じ幅の改善が出なくても全く問題ない。

実務ポイント(このレッスンから持ち帰るもの)

  • プロンプトに長いコンテンツを埋め込む時はXMLタグで包むのが定石。タグは「大量のコンテキストを含める時」に最も効き、Claudeへのデリミタとして機能する。
  • タグ名は自作でよい(公式タグ一覧のようなものは無い)。ただし data のような汎用名より sales_records のような内容を表す具体名の方が良い出力につながる。
  • 複数種類のコンテンツが混在するプロンプト(例:デバッグ対象コード+ドキュメント)では、<my_code> / <docs> のように種類ごとに別タグで包むと取り違えを防げる。
  • 補間コンテンツが短くても、<athlete_information> のように「外部入力である」ことを示すタグを付ける価値がある。今回の例では eval スコアが 7.3 → 7.94 に改善した。
  • プロンプト変更の効果は必ず eval で測る(このコースの一貫したワークフロー:変更 → セル再実行 → run_evaluation → スコア比較)。
  • 講師のスコア改善幅は弱いモデルを使った誇張されたリターン。実務では改善幅がもっと小さくても正常。

本文(時系列・日本語逐語+画面差し込み)

[0:00] 次に検討するトピックは、XMLタグを使ってプロンプトに構造を与える、という考え方です。

🖥 [0:00〜0:24] 画面(前レッスンのノートブックからスライドへ切替) 冒頭は前レッスンの Jupyter ノートブック(002_prompting_completed.ipynb — prod)が映っており、約15〜24秒かけて右からスライドがスライドインして切り替わる(scene_00458〜00715 は遷移中のフレーム)。

[0:06] さて、これについて少し背景をお話しさせてください。プロンプトを書き出す時にはとても多くの場合、何らかの量のコンテンツをその中に補間(interpolate)することになります。そして私たちはすでにそれをやってきました。私たちの例の中では、height、weight、goal、restrictions を補間してきましたね。

🖥 [0:15] 画面(スライド「Providing Structure」・タグ追加前) 左側(白地): - 見出し:Providing Structure - 太字リード:Use XML tags to separate distinct portions of the prompt - 箇条書き: - Most useful when including a lot of context - Help serve as delimiters for Claude - 左下ロゴ:ANTHROP\C

右側(レンガ色地・黒いコードパネル): ``` Write a one page decision report to troubleshoot why a sales team's numbers have dropped 30% last quarter.

Here are the last 20 pages of our sales records:

Follow these steps: 1. Compare current vs previous market metrics 2. Identify relevant industry changes 3. Analyze individual team member performance 4. Consider recent organizational changes 5. Review customer feedback ```

[0:19] さて、これらの値はそれぞれかなり小さいものですが、いずれ、プロンプトの中に大量のコンテンツを入れる必要があるプロンプトを書くことになる可能性は十分にあります。

[0:27] 例えば、右側のプロンプトをよく見てください。私たちは売上記録を20ページ分貼り付けて、Claudeに何らかの形でそれを分析させようとするかもしれません。

[0:37] 大量のコンテンツをプロンプトに流し込むと、どのテキストが実際に何を意味しているのか、あるいはテキストが実際にどのようにグループ化されているのかを、Claudeが正確に把握するのが少し難しくなることがあります。

[0:47] プロンプトの構造をもう少し分かりやすくする方法の一つが、異なるコンテンツ片をXMLタグで包むことです。

[0:54] 例えば、右側のこのプロンプトにもう少し構造を与えるために、ここにある sales records を、sales records というXMLタグで包んでみます。[1:06] こんなふうに。

🖥 [1:06] 画面(スライド更新:<sales_records> タグ追加後) 右側コードパネルの該当箇所が次のように変わる(スライド編集モードでパネルに選択ハンドルが表示されている): Here are the last 20 pages of our sales records: <sales_records> {sales_records} </sales_records> それ以外の文面(Write a one page decision report… / Follow these steps: 1〜5)は変更なし。

[1:08] さて、sales records という名前の公式なXMLタグは特に存在しません。これは私がたった今作り上げたタグの名前であり、これらのタグの内側に存在するコンテンツの性質について、Claudeにちょっとした洞察を与えてくれるであろうものです。

[1:21] これを records と呼ぶことも同じようにできましたし、あるいは data とさえ呼べたでしょう。[1:26] しかしもちろん、ここでもう少し具体的にする方が間違いなく良いです。[1:30] ですから、sales records のようなタグを与えることが、おそらく最良の出力を私たちにもたらしてくれるでしょう。

[1:35] さて、このようなXMLタグがなぜ必要なのかを、本当に明確にしておきたいと思います。[1:39] そこで、少し誇張した例をお見せしましょう。

🖥 [1:42] 画面(スライド「Providing Structure」Not Great / Better! 比較) 見出し:Providing Structure。左に「Not Great」、右に「Better!」のラベル付き黒パネル2枚が矢印(→)で結ばれている。

左(Not Great): ``` Debug my code below using the provided documentation.

from datavortex import Pipeline, DataSource

def process_data(input_file, output_file): pipeline = Pipeline() source = DataSource.from_csv(input_file)

Creating a data source from data vortex

csv_source = DataSource.from_csv("data.csv") ```

右(Better!): ``` Debug my code below using the provided documentation.

from datavortex import Pipeline, DataSource

def process_data(input_file, output_file): pipeline = Pipeline() source = DataSource.from_csv(input_file)

Creating a data source from data vortex

csv_source = DataSource.from_csv("data.csv") ```

[1:42] 左側のプロンプトでは、先頭の行として「提供されたドキュメントを使って、以下の私のコードをデバッグして(debug my code below using the provided documentation)」と書いてあります。

[1:48] つまりこれは、2つのことをそれとなく含意しています。このヘッダー文のすぐ下に、私が書いた何らかの量のバグのあるコードがあり、さらに何らかの量のドキュメントもある、ということを含意しているのです。[1:59] そして、ここに列挙されているコードをただ眺めただけでは、どのコンテンツがそのコードで、どのコンテンツが実際のドキュメントなのか、まったくもってはっきりしません。

[2:04] これをClaudeに対して明確にする方法の一つが、それぞれのコードの塊を適切なXMLタグで包むことです。[2:15] 例えば右側では、自分のコードを、とても直接的で分かりやすい my code とだけ書いたXMLタグで包み、そして何らかのドキュメントに相当するコードを docs タグで包みます。これもまた、とても明確で分かりやすいですね。[2:25] これで、Claudeがどのコードをデバッグしようとしているのか、そしてどのコードが何らかのドキュメントの供給源なのかを理解するのが、ずっと簡単になります。

[2:36] それでは、XMLタグで構造を与えるというこのアイデアを、ノートブックの中で私たちが取り組んでいるプロンプトの改善に使ってみましょう。

🖥 [2:41] 画面(スライドからノートブックへ切替:002_prompting_completed.ipynb — prod VS Code + Jupyter。パンくず:07 > 002_prompting_completed.ipynb > # Define and run the prompt you want to evaluate, returning the raw model output。カーネル:.venv (Python 3.12.9)。タブ:002_prompting_completed.ipynb / dataset.jsonrun_prompt セル(XMLタグ追加前・セル実行番号 [13]): ```python

Define and run the prompt you want to evaluate, returning the raw model output

This function is executed once for each test case

def run_prompt(prompt_inputs): prompt = f""" Generate a one-day meal plan for an athlete that meets their dietary restrictions.

- Height: {prompt_inputs["height"]}
- Weight: {prompt_inputs["weight"]}
- Goal: {prompt_inputs["goal"]}
- Dietary restrictions: {prompt_inputs["restrictions"]}

Guidelines:
1. Include accurate daily calorie amount
2. Show protein, fat, and carb amounts
3. Specify when to eat each meal
4. Use only foods that fit restrictions
5. List all portion sizes in grams
6. Keep budget-friendly if mentioned
"""

messages = []
add_user_message(messages, prompt)
return chat(messages)

```

[2:41] さて、残念ながらこの特定のシナリオでは、何らかの形で本当に区切ってやる必要のある、大きなコンテンツの塊というものが実はありません。[2:50] height、weight、goal、restrictions といった補間コンテンツはすべて十分に短いので、Claudeがそれらによって何かしら混乱することは、おそらくないでしょう。

[2:59] それでも、これが何らかの外部入力である、あるいは食事プランを生成する際に考慮されるべきアスリートに関する情報である、ということを本当に明確にするために、XMLタグを使うことはできます。[3:09] そこで、ここにあるこのブロック全体を、「これはアスリートに関する情報だ」と本当に明確にするだけのXMLタグで包むことにします。athlete information のようなタグを入れて、反対側には閉じタグを置きます。

🖥 [3:05頃] 画面(ノートブック編集中) run_prompt セル内、Generate a one-day meal plan… の行と - Height: … 行の間に空行が挿入され、タグを書き込むための行が開けられている(<athlete_information> タグ入力作業の途中のフレーム。カーソルが該当位置にある)。

[3:24] それでは、これが出力の品質に何らかの影響を与えるのかどうか、測ってみましょう。というわけで、セルを再実行します。[3:31] 下の eval セルに行って、これを実行します。そして思い出してほしいのですが、あのXMLタグを追加する前のスコアは 7.3 でした。[3:41] 上がるか下がるか、見てみましょう。——そして、かなり大きく上がりました。

🖥 [3:35] 画面(evalセルの実行と結果) evalセル(セル実行番号 [19]・実行時間 8.0s、パンくずは results = evaluator.run_evaluation(): python results = evaluator.run_evaluation( run_prompt_function=run_prompt, dataset_file="dataset.json", extra_criteria=""" The output should include: - Daily caloric total - Macronutrient breakdown - Meals with exact foods, portions, and timing """, ) 出力: Graded 10/50 test cases Graded 20/50 test cases Graded 30/50 test cases Graded 40/50 test cases Graded 50/50 test cases Average score: 7.94

[3:47] さて、皆さんはおそらく、これほど大きな改善は見られないと思います。念のためもう一度言っておくと、私はプロンプトへのこうした改善で誇張されたリターンが得られるように、あえて少しシンプルで基本的なモデルを使っています。[3:54] ですから、品質の跳ね上がりがこれほど大きくなかったとしても、[3:59] それでまったく問題ありません。

英語逐語(ASR全文)

The next topic we are going to examine is the idea of providing structure in your prompt by using XML tags. Now, let me give you a little bit of backstory on this. Very often, whenever we write out a prompt, we're going to interpolate some amount of content into it. And we've been doing that already. Inside of our example, we have been interpolating some height, weight, goal, and restrictions. Now, each of these values are rather small, but it's entirely possible that we might eventually write a prompt where we need to put in a lot of content into a prompt. For example, examine the prompt on the right-hand side. We might decide to paste in 20 pages of sales records and try to get Claude to analyze them in some way. Whenever we dump a lot of content into a prompt, it can be a little bit challenging for Claude to figure out exactly what text really means what, or how text is actually grouped together. One way that we can make the structure of our prompt a little bit more obvious is by wrapping different pieces of content in XML tags. So for example, I might provide a little bit more structure to this prompt on the right by wrapping the sales records right here with an XML tag of sales records. Like so. Now there is no official XML tag called specifically sales records. This is a name of a tag that I just made up that will probably give Claude a little bit of insight into the nature of the content that exists inside these tags. I could have just as well called this records. or perhaps even data. But of course, being a little bit more specific here is definitely better. So providing a tag of something like sales records would probably get us the best output. Now I want to make sure it's really clear why XML tags like this are necessary. So let me show you a little exaggerated example. In the prompt on the left-hand side, I have a leading line of debug my code below using the provided documentation. So this kind of implies two things. It implies that underneath this header statement right here, I have some amount of code that was written by me that is buggy and some amount of documentation as well. And if you just look at the code that's listed out here, it's absolutely not very clear what content is the code and what content is the actual documentation. One way that we could clarify this to Claude would be to wrap each chunk of code with appropriate XML tags. For example, on the right-hand side, I might wrap my code with some XML tags that simply say my code being very direct and obvious, and then the code that represents some amount of documentation in a docs tag, again, being very clear and obvious. Now it is much easier for Claude to understand what code it is trying to debug and which code provides some source of documentation. Let's take this idea of providing structure via XML tags and try to use it to improve our prompt that we are working on back inside of our notebook. Now, unfortunately, in this particular scenario, we don't really have a big blob of content that we really need to delineate in any way. All of our interpolated content, like the height, weight, goal, and restrictions are sufficiently short that Claude is probably not going to be confused by them in any way. Regardless, we can still use some XML tags to make it really clear that this is some kind of external input, or maybe some information about the athlete that should be considered when generating the meal plan. So we might decide to wrap this entire block right here with some XML tags that just make it really clear that this is information about the athlete. So I might put in tags like athlete information and then a closing tag on the other side. Now let's try to measure and see whether or not this has any kind of impact on the quality of output. So I'm going to rerun the cell. I'll go down to my eval cell and run this one. And then you might recall that before adding in those XML tags, I had a score of 7.3. So let's see if we go up or down. And I end up going up quite a bit. Now you probably are not going to see an improvement quite this large. As a reminder, I'm using a little bit more simple and basic model just so I get some exaggerated returns in these improvements to the prompt. So if you do not see quite a big a jump in quality, that is totally fine.

↑ 目次へ戻る

Building with the Claude API | Lesson 25「Providing examples」(全文ナレッジ)

動画 6分43秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンのテーマ

プロンプトの中に例(examples)を入れるというプロンプトエンジニアリング技法。講師いわく「おそらく最も効果的な技法のひとつ」。例を1つ入れるなら one-shot prompting、複数入れるなら multi-shot prompting と呼ぶ。

お題①:ツイートの感情分類(皮肉というコーナーケース)

  • タスク:ツイートの sentiment(ポジティブ/ネガティブ)を分類する
  • 入力ツイート:Yeah, sure, that was the best movie I've ever seen since 'Plan 9 from Outer Space'
  • 『Plan 9 from Outer Space』は有名な駄作映画。つまりこのツイートは皮肉(sarcasm)であり、実際はネガティブに分類すべき。しかし Claude は例なしだと誤分類する可能性がある

例の入れ方(定石)

  1. 「これから例(サンプル入力と理想の出力)を与える」と直接宣言するHere is a example input with an ideal response: のような導入文
  2. 入力・出力を XML タグで包む<sample_input>…</sample_input><ideal_output>…</ideal_output>。プロンプトが構造化され、入力と出力の目的が Claude に明確に伝わる
  3. 例:<sample_input>Great game tonight!</sample_input><ideal_output>Positive</ideal_output>。「この種の入力にはこう対処する」という具体的で客観的な例になる

コーナーケースを例で押さえる(multi-shot)

  • コーナーケースの例を足す時は、文脈の一文を添えて注意を促すBe especially careful with tweets that contain sarcasm. For example:
  • 皮肉の例:<sample_input>Oh yeah, I really needed a flight delay tonight! Excellent!</sample_input><ideal_output>Negative</ideal_output>
  • こうしておくと、元の入力(Plan 9 のツイート)を判定する時に Claude が「これも皮肉だ、たぶんネガティブだ」と認識できる確率が上がる

例が効く2大用途

  1. コーナーケースの捕捉(皮肉ツイートなど)
  2. 複雑な出力フォーマットの理解:複雑な JSON オブジェクトを生成させたい時、サンプル入力と例の出力(複雑な JSON 構造)を見せると、目指すべき出力構造を正確に把握させられる

プロンプト評価(eval)との組み合わせワークフロー

前レッスンまでに作った eval フレームワークと組み合わせると特に効果的: 1. eval 実行で生成される HTML レポート(output.html)を開く 2. スコア 10 点満点(なければ最高スコア)のテストケースを探す 3. その入力(Prompt Inputs)をコピーして、プロンプトの Guidelines セクションの下に Here is an example with a sample input and an ideal output: と書き、<sample_input> タグ内に貼る 4. その出力(Output)をコピーして <ideal_output> タグ内に貼り、インデントを整える 5. (任意・講師のおすすめ) レポート最終列の Reasoning(採点者がなぜ理想的と判断したかの説明)の前半をコピーし、</ideal_output> 閉じタグの直後に貼って文法を少し直す(例:This example meal plan is well structured…)。「なぜこれが理想の出力なのか」を Claude に理解させ、狙いの行動を強化できる 6. eval を再実行してスコアを比較する。このデモでは 7.8 → 7.96 に上昇した

実務ポイント(このレッスンから持ち帰るもの)

  • 例の提供(one-shot / multi-shot prompting)は最も効果的なプロンプト技法のひとつ。迷ったらまず例を足す。
  • 例は必ず XML タグ(<sample_input> / <ideal_output>)で包んで、「これは例だ」という導入文とセットで入れる。前レッスンの XML 構造化との合わせ技が公式推奨。
  • コーナーケース(皮肉など)は例で潰す。その際 Be especially careful with … のような注意喚起の文脈を一文添えてから例を出すのが定石。
  • 複雑な出力フォーマット(JSON 等)を厳守させたい時も例が最強。構造をことばで説明するより、実物を1つ見せる方が正確に伝わる。
  • eval レポートは「例の供給源」として使える:高スコアの入出力ペアをそのままプロンプトの例に昇格させるループが実務的。
  • さらに「なぜこれが理想か」の理由(採点者の Reasoning)を閉じタグの後に貼るのは任意だが、講師が実際に成果を出しているテクニック。出力の質の判断基準そのものを Claude に共有できる。
  • 例を1つ足しただけでスコアが劇的に跳ぶとは限らない(7.8 → 7.96)。eval で毎回計測しながら積み上げるのが正しい姿勢。

本文(時系列・日本語逐語+画面差し込み)

[0:00] 次に議論するこのプロンプトエンジニアリング技法については、私は本当にワクワクしています。というのも、これはおそらく皆さんが見つけられる中で最も効果的な技法のひとつだからです。それは、プロンプトの中に例(examples)を入れる、というアイデアです。これは one-shot プロンプティング、あるいは multi-shot プロンプティングとよく呼ばれます。例を1つだけ提供するのか、複数の例を提供するのかによって呼び方が変わります。

🖥 [0:00] 画面(スライド「Provide Examples」) 見出し:Provide Examples/サブ見出し:Give Claude sample input/output pairs 左側の箇条書き: - Useful for capturing corner cases or complex output formats - "One-Shot": provide a single example - "Multi-Shot": provide multiple examples - Highly recommend combining with XML tags for structure!

右側の黒いプロンプトボックス: ``` Categorize the sentiment of the below tweet:

Yeah, sure, that was the best movie I've seen since 'Plan 9 from Outer Space'

If the tweet has a positive sentiment, respond with "Positive". If it is negative, respond with "Negative". ```

[0:18] この概念を理解するには、実際の例を見るのが間違いなく一番簡単なので、さっそくそうしましょう。右側にあるプロンプトを見てください。私は Claude に、あるツイートの感情(sentiment)を分類するように頼んでいます。はっきりさせておくと、sentiment と言う時に意味しているのは、このツイートがどこか楽しげ・ポジティブな性質のものに見えるか、それとも不満げ・ネガティブなものに見えるか、ということです。

[0:36] この特定の例では、入力ツイートとして「Yeah, sure, that was the best movie I've ever seen since Plan 9 from Outer Space(ええ、そうですね、『プラン9・フロム・アウター・スペース』以来の最高の映画でしたよ)」を与えました。ご存じないかもしれませんが、『Plan 9 from Outer Space』は有名な駄作映画です。ですから、誰かがこのようなツイートをしたなら、実際にはおそらく皮肉を言っているのであって、観たばかりの映画をまったく気に入らなかった可能性が高いのです。

[0:57] したがって、このツイートはネガティブに分類したいところですが、Claude はこれの分類に問題を抱える可能性があります。この問題を解決するひとつの方法が、multi-shot プロンプティングを使うことです。

[1:08] では、どう直すかをお見せします。左側にある元のプロンプトを取り、いくつかの例を追加します。それがこのプロンプトの下半分に追加した部分です。

🖥 [1:09] 画面(スライド「Input Containing Sarcasm」) 左に元のプロンプト(上と同じ Categorize the sentiment…)、太い矢印(→)を挟んで、右に例を追加した後のプロンプト: ``` Categorize the sentiment of the below tweet: Yeah, sure, that was the best movie I've seen since 'Plan 9 from Outer Space'

If the tweet has a positive sentiment, respond with "Positive". If it is negative, respond with "Negative".

Here is a example input with an ideal response: Great game tonight! Positive

Be especially careful with tweets that contain sarcasm. For example: Oh yeah, I really needed a flight delay tonight! Excellent! Negative ```

[1:17] 例を追加するには、「これからサンプルの入力と、理想的な・完璧な世界での応答のようなものを与える」ということを、とても直接的に Claude に伝えます。そして、ほとんどの場合、これらの入力と出力を XML タグで包みます。プロンプトの構造を良くし、この入力と出力が何のためにあるのかを Claude に対して超明確にするためです。

[1:36] この特定の例では、「great game tonight(今夜は素晴らしい試合だった)」というサンプル入力を与えました。これは間違いなくポジティブな性質のものだと言えます。その直後に、単に「positive」という理想的な出力を入れます。これにより Claude は、ある種の入力にどう対処すべきかについての、具体的で客観的な例を得ることになります。

[1:54] これで Claude は、将来またこのような入力を見かけたら、おそらく positive とラベル付けすべきだと分かります。過去にそう処理されていたからです。

[2:04] multi-shot プロンプティングを活用できます。これは、コーナーケースに対処したい時に、複数の異なる例を提供するやり方です。そして、このような皮肉なツイートへの対処は、まさに Claude に対して強調しておきたいコーナーケースです。

[2:17] コーナーケースを浮き彫りにする例を追加する時は、Claude に文脈を添えて、特定のシナリオには特に注意すべきだと伝えます。例えばこう言えます。「皮肉を含むツイートには特に注意してください」と。そして、その直後にその実例を提供します。今回のケースでは、「oh yeah, I really needed a flight delayed tonight. Excellent.(ああそうだよ、今夜はまさにフライトの遅延が必要だったんだ。最高だね)」という例のツイート、つまりサンプル入力を用意しました。皮肉という概念を理解していなければ、これはポジティブな感情のツイートに見えるでしょう。しかしもちろん、私たちにはこれが皮肉だと分かります。だから実際にはおそらくネガティブなのです。

[2:52] 繰り返しになりますが、Claude は上にある元の入力を判定する時にこの例を参照でき、「ああそうか、これも皮肉に見えるな。これもおそらくネガティブな性質のものだ」と認識できる可能性が高まります。

[3:03] さて、このような multi-shot プロンプティングは、コーナーケースを捕捉したり Claude にもう少し明確さを与えたりするためだけでなく、より複雑な出力フォーマットを Claude に理解させるためにも使えます。もし、かなり複雑な性質の JSON オブジェクトを生成する必要が生じたら、サンプル入力と例の出力を提供して、その複雑な JSON 構造を Claude に見せるとよいでしょう。そうすれば Claude は、目指すべき出力の正確な構造をより良く把握できます。

[3:30] 例の提供は、今まさに私たちがやっているようなプロンプト評価(prompt evals)を行っている時に、特に効果的です。思い出してください。ノートブック内の私たちの小さなフレームワークを使ってプロンプト評価を実行すると、同じディレクトリ内に HTML ファイルが作られます。ですから、このファイルの中を、満点の 10、あるいは、できればかなり高いスコアのテストケースが見つかるまで探し回ればよいのです。

🖥 [3:36] 画面(ブラウザ:Prompt Evaluation Report) Chrome(Incognito)で /Users/sgrider/code/enablements/prod/07/output.html を表示。 - Total Test Cases: 50Average Score: 7.8 / 10Pass Rate (≥7): 84.0% - テーブル列:Prompt Inputs/Scenario/Solution Criteria/Output/Score/Reasoning - 先頭行:Prompt Inputs「Athlete seeking creative ways to incorporate vegetables, female, 50kg」、Score 7(黄)、Reasoning「The meal plan is well-structured and includes a variety of foods. However, it lacks a total daily caloric count and macronutrient breakdown, which is a key requirement. The individual meal breakdowns are helpful, but the overall summary is missing.」

[3:45] スクロールしていくと、ちょうどここに 10 が見つかります。皆さんの出力の中には 10 がひとつもないかもしれません。なくてもまったく問題ありません。最高スコアのレコードを探してみてください。

🖥 [3:52] 画面(レポートをスクロール:スコア10のレコード) スコア 10(緑)のテストケース: - Prompt Inputs:Athlete with high cholesterol, female, 70kg, heart-healthy fats - Scenario:height: 170/weight: 70/goal: Maintain fitness and improve cholesterol levels/restrictions: High cholesterol - Output(抜粋): ``` Here is a one-day meal plan for an athlete aiming to maintain fitness and improve cholesterol levels:

  • Calorie Target: Approximately 2500 calories
  • Macronutrient Breakdown: Protein (140g), Fat (70g), Carbs (340g)

Meal Plan:

  • Breakfast (7:00 AM): Oatmeal (80g dry weight) with berries (100g) and walnuts (15g). Skim milk (240g).
    • Protein: 15g, Fat: 15g, Carbs: 60g
  • Mid-Morning Snack (10:00 AM): Apple (150g) with almond butter (30g).
    • Protein: 7g, Fat: 18g, Carbs: 25g
  • Lunch (1:00 PM): Grilled chicken breast (120g) salad with mixed greens (150g), cucumber (50g), tomato (50g), and a light vinaigrette dressing (30g). Whole wheat bread (60g).
    • Protein: 40g, Fat: 15g, Carbs: 70g
  • Afternoon Snack (4:00 PM): Greek yogurt (170g, non-fat) with a banana (120g).
    • Protein: 20g, Fat: 0g, Carbs: 40g
  • Dinner (7:00 PM): Baked salmon (140g) with steamed broccoli (200g) and quinoa (75g dry weight).
    • Protein: 40g, Fat: 20g, Carbs: 80g
  • Evening Snack (9:00 PM): Small handful of almonds (20g).
    • Protein: 8g, Fat: 12g, Carbs: 15g

This meal plan prioritizes lean protein sources, whole grains, fruits, and vegetables, while limiting saturated and trans fats to support healthy cholesterol levels. ``` - Score:10/Reasoning:「The meal plan is well-structured, provides detailed information on food choices and quantities, and aligns with the athlete's goals and restrictions. It meets all the specified criteria.」

[4:02] つまりこれは、私たちのモデル採点者(model grader)によって「これ以上は望めないほど良い」と評価された入力と出力の例です。そこで、これをプロンプトの中の例として提供することにしてもよいでしょう。そうすれば、Claude がこのような見た目の出力をもう少し頻繁に生成するように導けるはずです。

[4:17] 試してみましょう。ここにあるこの入力をコピーして、私のプロンプトに戻ります。

🖥 [4:24] 画面(レポート上で入力をコピー) スコア10のレコードの Scenario 列(height: 170/weight: 70/goal: Maintain fitness and improve cholesterol levels/restrictions: High cholesterol)が青くハイライト選択されている。画面右には VS Code のノートブックが分割表示で見えている。

[4:24] Guidelines セクションの下までスクロールし、これからサンプル入力と理想の出力を含む例を提供する、ということを Claude に明示的に伝えます。こう書きましょう。「Here is an example with a sample input and an ideal output.(これはサンプル入力と理想の出力を含む例です)」

[4:43] それから、サンプル入力を XML タグの中に入れ、

[4:51] そして理想の出力(のタグ)も入れます。

🖥 [4:51] 画面(Jupyter ノートブック 002_prompting_completed.ipynb — prod) run_prompt(prompt_inputs) 関数内の f 文字列プロンプトを編集中。Guidelines の下に例のセクションを追記している: ``` Guidelines: 1. Include accurate daily calorie amount 2. Show protein, fat, and carb amounts 3. Specify when to eat each meal 4. Use only foods that fit restrictions 5. List all portion sizes in grams 6. Keep budget-friendly if mentioned

Here is an example with a sample input and an ideal output: height: 170 weight: 70 goal: Maintain fitness and improve cholesterol levels restrictions: High cholesterol

"""

messages = [] add_user_message(messages, prompt) return chat(messages) `` (にはレポートからコピーした4行が貼られ、` はまだ空)

[4:57] そして、それらのタグの中に、あちらに戻って、ちょうどここにある出力をコピー&ペーストします。

🖥 [5:02] 画面(レポート上で出力をコピー) スコア10のレコードの Output 列全体(Here is a one-day meal plan for an athlete aiming to maintain fitness and improve cholesterol levels: 〜 This meal plan prioritizes lean protein sources… cholesterol levels. まで)が青くハイライト選択されている。

[5:03] それをこのように貼り付けて、インデントを少し直します。

🖥 [5:05] 画面(ノートブックに理想出力を貼り付け・インデント修正) <ideal_output> タグの中に上記の食事プラン全文が貼り付けられている。各行末の余分な空白がマルチカーソルで一括選択されており、貼り付けに伴うインデントのずれを修正している。

[5:07] eval を再実行する前に、最後にもうひとつお見せしたいことがあります。この最後のステップは完全に任意ですが、私自身はこれで大きな成果を上げてきました。この出力がなぜ理想的なのかを Claude に正確に理解させてあげると、非常に有益なことが多いのです。

[5:20] レポートに戻ると、覚えているでしょうか、こちら側に最後の列があって、採点者がなぜこれを理想的な出力だと考えたのかを正確に説明しています。

🖥 [5:36] 画面(レポートの Reasoning 列をコピー) スコア10のレコードの Reasoning 列のうち、前半部分「The meal plan is well-structured, provides detailed information on food choices and quantities, and aligns with the athlete's goals and restrictions.」が青くハイライト選択されている(末尾の「It meets all the specified criteria.」は選択外)。右側には VS Code が分割表示。

[5:28] そこで、こちらにあるメッセージの、なぜこれが良い応答なのかを述べている(列挙している)最初の半分ほどだけをコピーします。それを持ち帰って、</ideal_output> 閉じタグの下にその理由(reasoning)を貼り付けます。そして文法を少しだけ直して、「This example meal plan is well structured…(この例の食事プランはよく構造化されており…)」うんぬん、とします。

🖥 [5:40] 画面(閉じタグの下に理由を貼り付け) ``` * Evening Snack (9:00 PM): Small handful of almonds (20g). * Protein: 8g, Fat: 12g, Carbs: 15g

This meal plan prioritizes lean protein sources, whole grains, fruits, and vegetables, while limiting saturated… The meal plan is well-structured, provides detailed information on food choices and quantities, and aligns with … """

messages = [] add_user_message(messages, prompt) return chat(messages) ``` (セル実行番号 [46]。貼り付けた理由文の冒頭「The」を選択して「This example meal plan is…」へ書き換えている)

[5:53] これで Claude は、なぜこれが理想的な出力と見なされるのかを、より正確に把握できます。そしてこれは、「Claude はよく構造化された出力を返す必要があり、そこには食材の選択と分量についての詳細な情報が含まれ、そして最も重要なこととして、アスリートの目標と制約に合致していなければならない」という考えを、より強く刷り込むことになります。

[6:06] オーケー。ではそのセルを実行して、eval を再実行し、結果がどうなるか見てみましょう。

🖥 [6:06] 画面(eval 実行セル) python results = evaluator.run_evaluation( run_prompt_function=run_prompt, dataset_file="dataset.json", extra_criteria=""" The output should include: - Daily caloric total - Macronutrient breakdown - Meals with exact foods, portions, and timing """, ) (セル [51]、実行中)

[6:10] これを再実行します。さて、上がるでしょうか、下がるでしょうか?

[6:17] 結果は、ほんの少しだけ上がって 7.96 になりました。

🖥 [6:23] 画面(再実行後の eval 出力) 実行セルの下に進捗ログ(… test cases の行が複数)が流れ、最終行に: Average Score: 7.96

[6:22] それでは、まとめに入りましょう。復習ですが、この技法は one-shot あるいは multi-shot プロンプティングとよく呼ばれます。one-shot は例を1つ提供する場合、multi-shot は複数の例を提供する場合です。そしてこれは、Claude に確実にコーナーケースを処理させたい時、そしてとりわけ、何らかの複雑な出力フォーマットに確実に合致させたい時に、非常に頻繁に使うことになる技法です。

英語逐語(ASR全文)

I'm really excited about this next prompt engineering technique we're going to discuss because it is probably one of the most effective that you're going to find. It's the idea of providing examples inside of your prompt. This is often referred to as one-shot or multi-shot prompting, depending upon whether or not you are providing just one example or multiple examples. Understanding this concept is definitely easiest if you take a look at an example, so let's do that right away. Consider the prompt on the right-hand side. I'm asking Claude to categorize the sentiment of a tweet. Just be clear, when we say sentiment, we mean kind of, does this tweet seem kind of happy or positive in nature or unhappy or negative? In this particular instance, I provided an input tweet of, yeah, sure, that was the best movie I've ever seen since Plan 9 from Outer Space. Now, if you're not aware, Plan 9 from Outer Space is a famously bad movie. So if someone were to tweet something like this, they're actually probably being sarcastic and they probably did not like the movie they just watched at all. So we would probably want to classify the suite as being negative, but Claude could potentially have some issue categorizing this. One way to solve the problem would be by using multi-shot prompting. So here's how we would fix this. We would take our starting prompt that we have on the left-hand side and add in some examples, which I've added in on the bottom half of the prompt right here. To add in an example, we will very directly tell Claude that we are about to give it some example input and an ideal perfect world kind of response. We'll almost always wrap these inputs and outputs inside of some XML tags just to better structure our prompt and make it super clear to Claude what the purpose of the input and output are. So in this particular example, I have given a sample input of great game tonight, which I would say is definitely positive in nature. So right after that, I would then put in an ideal output of simply positive. This gives Claude a concrete objective example of how to deal with some kind of input. So now Claude knows that if it ever sees some kind of input like this again in the future, well, it should probably label it as positive because that's how it was done in the past. We can make use of multi-shot prompting. That's where we provide multiple different examples whenever we want to handle corner cases. And dealing with sarcastic tweets like this is definitely a corner case that we kind of want to highlight to Claude. When adding examples that highlight corner cases, add in some context to Claude and tell it, then it should be especially aware of certain scenarios. So for example, we might say, be especially careful with tweets that contain some sarcasm, and then provide an immediate example of that. So now in this case, I've got a example tweet or a sample input of, oh yeah, I really needed a flight delayed tonight. Excellent. If you didn't understand the concept of sarcasm, this would seem like a positive sentiment tweet, but of course we can probably understand that this is sarcasm. And so it probably is actually negative. Once again, Claude can take a look at this example when grading our provided input up here, the original input, and Claude will have a better chance of recognizing that, oh yeah, this looks like it's sarcasm too. This is also probably negative in nature. Now, multi-shot prompting like this can be used not only for capturing corner cases or giving a little bit more clarity to Claude, but also helping Claude understand more complex output formats. So if you ever need to generate a JSON object that is rather complex in nature, you might provide a sample input and an example output and show that kind of complex JSON structure to Claude. And now it will have a better idea of the exact structure of output that it is going for. Providing examples is especially effective whenever you are doing prompt evals, as we currently are. Remember, whenever you run a prompt eval using our little framework inside the notebook, it creates an HTML file inside the same directory. So we can hunt through this file until we find a perfect 10, or hopefully just a test case with a rather high score. If I scroll through, I will find a 10 right here. Now you might not have any 10s inside of your output. If you don't, that's totally fine. Just try to find the record with the highest score. So this is an example of where we had some input and output that was gauged to be pretty much as good as we're going to get by our model grader. So we might decide to provide this as an example inside of our prompt. And hopefully that will guide Claude to producing output that looks like this a little bit more often. Let's try this out. I'm going to copy this input right here, go back over to my prompt. I'm going to scroll underneath the guidelines section, and I'm going to explicitly tell Claude that I'm about to provide an example that's going to contain a sample input and an ideal output. So let's say here is an example with a sample input and an ideal output. I'll then put in my sample input inside of XML tags and a ideal output. And inside of those tags, I'm going to go back over and copy, paste the output from right here, I'll then paste it in like so and fix some indentation. Before we rerun our eval, there's one last thing I want to show you. This last step is completely optional, but I personally have had great success with it. It's often very beneficial to help Claude understand exactly why this is ideal output. If we go back over to our report, remember we have this last column over here that explains exactly why the grader thought that this was some ideal output. So we can copy just the kind of first half of some message over here where it says or lists out why this is a good response. take that back over and then underneath the closing ideal output tag we could paste in that reasoning and then maybe update the grammar just a little bit to say this example meal plan is well structured blah blah so now Claude has a better idea of exactly why this is considered to be ideal output and it's going to better reinforce the idea that Claude needs to return a well-structured output that contains some detailed information on the food choices and quantities and most importantly matches the athlete's goals and restrictions. Okay, so let's now run that cell and then rerun our eval and see how we are doing. So I'm going to rerun this and are we going to go up or down? We end up going up just a little bit to 7.96. Well, let's wrap things up. As a reminder, this technique is often referred to as one shot or multi-shot prompting. One shot is where you provide a single example. Multishot is where you provide multiple examples. And this is a technique you're going to very often use anytime you want to make sure Claude handles corner cases, or especially when you want Claude to make sure it matches some kind of complex output format.

↑ 目次へ戻る

Building with the Claude API | Lesson 26「Exercise on prompting」(全文ナレッジ)

動画 5分21秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンのテーマ

プロンプトエンジニアリング・モジュールの総仕上げ演習。弱いプロンプトを、このモジュールで学んだテクニック(明確・直接/具体的な手順/XMLタグ/例示)で改善し、評価スコアで効果を実測する

演習のセットアップ

  • 新ノートブック 003_exercise.ipynb を使用(ダウンロード必須ではない。既存ノートブックの task description・prompt input spec・extra criteria を差し替えても同じ状態になる)
  • PromptEvaluator(max_concurrent_tasks=2) のインスタンスを作り、evaluator.generate_dataset() でテストデータセットを生成(num_cases=4
  • task_description:「学術論文のパッセージからトピックを抽出し、JSON の文字列配列にする」
  • prompt_inputs_spec:入力は content プロパティ1つだけ(英語の学術誌の1段落テキスト)
  • 「トピック」とは「その記事が何について語っているか」。太陽光パネルの記事なら "solar panels" を含む JSON 配列が返ってきてほしい

スターター・プロンプト(改善前)

prompt = f"""
What topics are in here?

{prompt_inputs["content"]}
"""

この「かなり弱いプロンプト」での初回評価は 平均 2.8/10(Pass Rate 0.0%)。目標は平均 7 以上

改善の進め方(レポート駆動)

  1. まず評価を1回実行して output.html(レポート)を生成する
  2. レポートの Reasoning(採点理由)を読む → 共通テーマを見つける:「JSON の文字列配列を返していない」(番号付きリスト+解説文+余計なコメントが返っている)ことが減点の主因
  3. 原因に対応するテクニックを1つずつ適用して再評価する

適用したテクニックと結果

① 明確・直接(simple and direct) — プロンプト1行目を書き換え:

Extract key topics mentioned from a passage of text from a scholarly journal into a JSON array of strings.

→ 再評価でいきなり 平均 9.5/10(Pass Rate 100.0%)。出力はきれいな JSON 配列になり、モデル採点者の評価が激変。

② XMLタグで構造化 — 補間する本文を <text>...</text> で挟む。タグ名を text にしたのは、1行目で「a passage of text」と言及した語と対応させ、指示文と提供データの結びつきを明確にするため。

③ 手順を具体的に指示Follow these steps: として、①提供テキストを precisely 検討 ②言及されたトピックを特定 ③各トピックを JSON 配列に追加 ④JSON 配列だけを返す(他のテキスト・コメント禁止)をステップ列挙。

④ 例示(one-shot / multi-shot)も追加可能だが、既に十分高スコアのため今回は省略。最終評価も 9.5 を維持し、「記事からトピックリストを抽出する用途で信頼できる強いプロンプト」と結論。

実務ポイント(このレッスンから持ち帰るもの)

  • プロンプト改善は評価レポートの Reasoning から逆算する。感覚で書き換えるのではなく、採点理由の「共通テーマ」(今回は出力フォーマット違反)を特定してから直す。
  • 一番効くのは1行目の明確・直接な仕様宣言。今回は1行の書き換えだけで 2.8 → 9.5 に跳ねた。フォーマット要件(JSON 配列など)は曖昧に匂わせず、明示的に書く。
  • XMLタグ名は指示文中の語彙と揃える(「a passage of text」→ <text>)。指示と データの参照関係が明確になり、プロンプトの構造が読み取りやすくなる。
  • 手順のステップ列挙(Follow these steps: 1. … 2. …)は、出力形式の遵守をさらに固くする仕上げとして有効。「Do not provide any other text or commentary」のような禁止事項も明記する。
  • スコアが既に十分ならテクニックを盲目的に積み増さない。例示(one-shot/multi-shot)は任意。評価で効果を確認しながら足す。
  • 評価は繰り返し実行して改善が維持されるか確認する(再評価でも 9.5 を維持して初めて「信頼できる」と言える)。

本文(時系列・日本語逐語+画面差し込み)

[0:00] それでは、簡単な演習を通して、プロンプトエンジニアリングの知識をテストしてみましょう。

[0:03] さて、この演習のために、003_exercise という新しいノートブックを作りました。これはダウンロードしなくても構いません。私がやったのは、タスクの説明(task description)と、プロンプトの入力仕様(prompt input spec)を置き換えて、この下にある追加の採点基準(extra criteria)をいくつか調整しただけです。ですから、お望みなら、いま作業中のノートブックの該当箇所をすべて手動で書き換えてもいいですし、あるいはこの 003 exercise をダウンロードして、私とまったく同じ状態に追いついてもらっても構いません。

🖥 [0:00〜0:30] 画面(Jupyter ノートブック 003_exercise.ipynb — prod) 見出し # PromptEvaluator Implementation の下に、評価器の生成セルとデータセット生成セル: ```python

Create an instance of PromptEvaluator

Increase max_concurrent_tasks for greater concurrency, but beware of rate limit errors!

evaluator = PromptEvaluator(max_concurrent_tasks=2) python dataset = evaluator.generate_dataset( # Describe the purpose or goal of the prompt you're trying to test task_description=""" Extract topics out of a passage of text from a scholarly article into a JSON array of strings" """, # Describe the different inputs that your prompt requires prompt_inputs_spec={ "content": "One paragraph of text from a scholarly journal written in English" }, # Where to write the generated dataset output_file="dataset.json", # Number of test cases to generate (recommend keeping this low if you're getting rate limit errors) num_cases=4, ) セル出力(22.5s): Generated 1/4 test cases Generated 2/4 test cases Generated 3/4 test cases Generated 4/4 test cases ``` (0:30 頃のフレームでは task_description の「out of a passage of text from a scholarly article」部分がハイライト選択されている)

[0:27] この演習でのあなたのゴールは、このモジュールの中で学んできたプロンプトエンジニアリングのトピックをすべて使って、既存のプロンプトを改善することです。

[0:34] これから扱うデータセットは、学術論文からの一連のテキストのパッセージ(段落)を生成するものになっています。

[0:42] あなたのプロンプトのゴールは、そのテキストのパッセージを受け取り、そこからすべてのトピックを抽出して、JSON の文字列配列に入れることです。

[0:50] そして「トピック」と言うとき、私が本当に意味しているのは、まあ、「この記事は何について語っているのか?」ということです。ですから、例えば太陽光パネルについての記事であれば、その中に solar panels が入った JSON の文字列配列を、そのテキストの中で言及されている他のあらゆるトピックと一緒に返してほしいわけです。

[1:06] このプロンプトへの入力は、content プロパティ1つだけになります。つまり、1段落のテキストです。

🖥 [1:06] 画面(prompt_inputs_spec の説明) データセット生成セルの # Describe the different inputs that your prompt requires のコメント内、「inputs」の語が選択ハイライトされている。その下: python prompt_inputs_spec={ "content": "One paragraph of text from a scholarly journal written in English" },

[1:12] さて、いま開いている私のノートブックでは、すでにセルを実行してデータセットを生成済みです。

[1:18] そして、皆さんのためにスターター・プロンプトをすでに用意してあります。現時点では、これは間違いなく大幅に改善できる、非常に弱いプロンプトです。

[1:26] ですから、このプロンプトをいろいろ触ってみて、私たちが学んできたさまざまなテクニックを使ってみてください。

[1:30] 進捗を評価するには、この下にあるセルを実行してください。正しい結果を達成できているかを確認するために、追加の採点基準(extra criteria)をいくつか、すでに含めておきました。

🖥 [1:30] 画面(評価実行セルと初回結果) python results = evaluator.run_evaluation( run_prompt_function=run_prompt, dataset_file="dataset.json", extra_criteria=""" - Contains a JSON array of strings, containing each topic mentioned in the article. - The strings should contain only a topic without any extra commentary - Response should contain the JSON array and nothing else """, ) セル出力([19] 14.6s): Graded 1/4 test cases Graded 2/4 test cases Graded 3/4 test cases Graded 4/4 test cases Average score: 2.75

[1:40] 覚えておいてください。評価を実行したときはいつでも、あの report.html ファイルを開いて、何が起きているのかをより深く理解することができます。

[1:47] デフォルトでは、あの本当に単純なプロンプトのままだと、平均スコアは 2.8 でした。そこで、この平均スコアを、少なくとも 7 くらいは超えられるようにしたいと思っています。

🖥 [1:47] 画面(ブラウザ「Prompt Evaluation Report」/Users/sgrider/code/enablements/prod/07/output.html・改善前) サマリーカード: - Total Test Cases: 4Average Score: 2.8 / 10Pass Rate (≥7): 0.0%

テーブル列:Scenario / Prompt Inputs / Solution Criteria / Output / Score / Reasoning 1行目(Score 2):Scenario「Testing with a passage that includes implicit topics not explicitly named but clearly discussed in the text」、Output は「The text covers several interconnected topics related to early childhood development: 1. Cognitive development in early childhood 2. Neural plasticity and its relationship to environmental factors …」という番号付きリスト+解説文。Reasoning: The solution correctly identifies all the major topics discussed in the passage and demonstrates good understanding of the implicit themes. However, it completely fails to meet the mandatory requirement of providing a JSON array of strings. Instead, it presents a numbered list with explanatory text and adds commentary. The task specifically required only a JSON array with topic strings and nothing else. 2行目(Score 3):Scenario「Testing with a multi-disciplinary passage containing overlapping topics from different fields (e.g., bioethics in AI)」、Reasoning は同趣旨(「Instead of providing a JSON array of strings with just the topics, it presents a numbered list with additional commentary and explanatory text. The solution must contain only a JSON array of strings without any extra text to meet the requirements.」)

[1:58] いつものように、ここでビデオを一時停止して、この演習に挑戦してみることをお勧めします。そうでなければ、このまま見続けてもらえれば、すぐに解答例を見ていきます。

[2:08] よし。これを解くにあたって、疑いの余地なく分かっているのは、私たちがやる作業のすべてが、ここにあるこの変数に集中するということです。より良い出力を得るために、このプロンプトを何らかの形で改善する必要があります。

🖥 [2:08] 画面(スターター・プロンプトの選択) run_prompt 関数内のプロンプト部分がハイライト選択されている: ```python

Define and run the prompt you want to evaluate, returning the raw model output

This function is executed once for each test case

def run_prompt(prompt_inputs): prompt = f""" What topics are in here?

{prompt_inputs["content"]}
"""

messages = []
add_user_message(messages, prompt)
return chat(messages)

```

[2:18] ここで最初にやるかもしれないことは、あの output.html ファイルを生成するために、評価を少なくとも1回は必ず実行しておくことです。

[2:26] そしてそのファイルを作成したら、それを開いて、出力がなぜこれほど低く採点されたのか、その理由(reasoning)のいくつかに目を通すことをお勧めします。

[2:34] それぞれの間に共通のテーマがあることに気づくでしょう。すぐに分かる本当に共通したテーマは、そう、私たちが JSON の文字列配列を返していないという点が気に入らない、ということです。

🖥 [2:30] 画面(レポートの Reasoning 精読) レポートを下にスクロールし、Score 3 の行の Reasoning を確認している。3行目(Score 3):Scenario「Testing with a highly technical passage containing specialized terminology and abbreviations from a niche scientific field」、content は CRISPR-Cas9 mediated HDR の段落。Reasoning:「The solution correctly identifies all the relevant scientific topics from the passage, including CRISPR-Cas9, HDR, ssODNs, and other key concepts. However, it fundamentally fails to meet the mandatory formatting requirements…」

[2:42] この問題を解決するために、プロンプトの中で「シンプルかつ直接的である」というあのテクニックを活用しましょう。

[2:48] このコンテンツからすべてのトピックを含む JSON の文字列配列が返ってくることを期待するなら、そう、Claude に対して非常にシンプルかつ直接的に、欲しいものを正確に伝える必要があります。

[2:58] そこで、ここにあるプロンプトの1行目を更新します。こう書きましょう。「学術誌のテキストのパッセージから、言及されている主要なトピックを抽出し、JSON の文字列配列にせよ(Extract key topics mentioned from a passage of text from a scholarly journal into a JSON array of strings.)」。私に表現できる限りシンプルに、そして可能な限り直接的にしたものです。

🖥 [3:00] 画面(1行目を書き換えた run_prompt) ```python

Define and run the prompt you want to evaluate, returning the raw model output

This function is executed once for each test case

def run_prompt(prompt_inputs): prompt = f""" Extract key topics mentioned from a passage of text from a scholarly journal into a JSON array of strings.

{prompt_inputs["content"]}
"""

messages = []
add_user_message(messages, prompt)
return chat(messages)

```

[3:17] そしてすぐに、このプロンプトで評価を再実行すると、ほとんど即座に、ちょっと衝撃的な 9.5 まで上がるのが分かります。

🖥 [3:29] 画面(再評価の結果) 評価セルの実行結果([21] 11.3s): Graded 1/4 test cases Graded 2/4 test cases Graded 3/4 test cases Graded 4/4 test cases Average score: 9.5

[3:25] 正直、ここまでうまくいくとは思っていませんでした。というのも、ここで試したいテクニックがまだ他にもいくつかあるからです。

[3:31] それでも、いまレポートを見てみると、言及されたさまざまなトピックをすべて含んだ、この JSON が確かに返ってきています。そして、モデル採点者(model grader)はこの出力に非常に満足しているようです。

🖥 [3:32] 画面(改善後のレポート) サマリーカード: - Total Test Cases: 4Average Score: 9.5 / 10Pass Rate (≥7): 100.0%

1行目(Score 9):Output がコードブロックの JSON 配列に: json [ "cognitive development", "early childhood", "environmental factors", "neural plasticity", "enriched learning environments", "executive function", "caregiver interaction patterns", "communication frameworks", "early intervention strategies", "academic performance", "elementary education", "secondary education", "educational resources" ] Reasoning:「The solution meets all mandatory requirements by providing a properly formatted JSON array containing only the topics without commentary. It successfully identifies both explicit topics (like 'cognitive development') and implicit topics (like 'elementary education' which is derived from 'elementary and secondary education'). …」

2行目(Score 10):Output は「artificial intelligence / bioethical concerns / healthcare and technology intersection / machine learning algorithms / patient data / diagnostic capabilities / medical privacy / algorithmic bias / clinical decision-making / doctor-patient relationship / AI accuracy in diagnosis / radiology / pathology / healthcare integration / technical validation protocols / sociocultural contexts / neuroethics / brain-computer interfaces / cognitive autonomy / informed consent」の JSON 配列。Reasoning:「…The output is a clean JSON array containing only the topic strings without any additional commentary or formatting issues.」

3行目(Score 10):CRISPR の技術的パッセージ。Output は「CRISPR-Cas9 / HDR (Homology-Directed Repair) / genomic modifications / ssODNs (single-stranded oligodeoxynucleotides) / knock-in efficiency / dsDNA templates / RAD51 / BRCA2 modulators / endogenous HDR machinery / allelic conversion / post-mitotic cells / NHEJ (Non-Homologous End Joining) / therapeutic applications / monogenic disorders / loss-of-function mutations」の JSON 配列。

4行目(Score 9):比喩的な言語で書かれたパッセージ。Output は「cognitive landscape of learning / knowledge acquisition / intellectual pollination / memory consolidation / mental structures / critical analysis / developmental trajectory / conceptual understanding / metacognitive awareness / learning ecosystem」の JSON 配列。Reasoning:「…The only minor issue is the inclusion of 'learning ecosystem' which combines terms from the passage but isn't explicitly stated as such in the original text.」

[3:41] さて、これで終わりにはしたくありません。当然ながら、正しい種類の出力を確実に得るために追加できるテクニックが、他にもいくつかあります。

[3:49] 次に追加するかもしれないテクニックは、XMLタグをいくつか使って、プロンプトをもう少し上手に構造化することです。

[3:55] ここで補間(interpolate)しようとしているコンテンツの前後に、XMLタグを追加します。タグの名前は、単純に text とします。

🖥 [4:00] 画面(XMLタグを追加した run_prompt) ```python def run_prompt(prompt_inputs): prompt = f""" Extract key topics mentioned from a passage of text from a scholarly journal into a JSON array of strings.

<text>
{prompt_inputs["content"]}
</text>
"""

messages = []
add_user_message(messages, prompt)
return chat(messages)

```

[4:04] さて、この場合、私がこれらのタグを text と呼ぶことにしたのは、プロンプトの1行目で先ほど「テキスト(a passage of text)」に言及したからです。これで、ここで「テキストのパッセージ」について語っている部分と、ここで実際にそのテキストを提供している部分との間に、もう少し明確なつながりができました。

[4:20] もう1つ追加できる改善は、Claude にやってほしいことを非常に具体的にすることです。

[4:25] つまり、Claude に従わせる一連のステップを入れることができます。試してみましょう。「follow these steps(以下のステップに従え)」と書いて、Claude にやってほしいことをステップバイステップで正確に列挙します。例えばこう言えます。提供されたテキストを注意深く検討せよ、言及されている各トピックを特定せよ、各トピックを JSON 配列に追加せよ、

🖥 [4:30] 画面(ステップを列挙している途中) ```python def run_prompt(prompt_inputs): prompt = f""" Extract key topics mentioned from a passage of text from a scholarly journal into a JSON array of strings.

<text>
{prompt_inputs["content"]}
</text>

Follow these steps:
1. Closely examine the provided text
2. Identify each topic
"""

`` (2番目のステップ2. Identify each topic` をタイプしている途中のフレーム)

[4:50] そして最後に、その JSON 配列で応答せよ。他のテキストやコメントは一切提供するな、と。

[4:57] さて、もし one-shot や multi-shot のプロンプティングを使って例も追加したいなら、ぜひ遠慮なくそうしてください。ただ、すでにかなり良いスコアが出ているので、今回はこれで十分だと思います。

[5:06] このプロンプトを再実行します。それから評価をもう一度実行すると、結果はまた 9.5 になりました。

🖥 [5:12] 画面(最終評価の結果) 評価セルの実行結果([24] 11.7s): Graded 1/4 test cases Graded 2/4 test cases Graded 3/4 test cases Graded 4/4 test cases Average score: 9.5

[5:18] ですから、これはかなり強いプロンプトだと言えますし、記事からトピックのリストを抽出する用途には、間違いなく信頼して使えると思います。

英語逐語(ASR全文)

Let's test your knowledge of prompt engineering by going through a quick exercise. Now for this exercise, I made a new notebook called 003 underscore exercise. You do not have to download this one. All I did was replace the task description, the prompt input spec, and then adjusted some of the extra criteria down here. So if you want to, you could manually change all the stuff in the notebook you're working out of, or alternatively just download this 003 exercise and get caught up with exactly where I am. Your goal in this exercise is to improve an existing prompt using all the prompt engineering topics we've learned about inside this module. The dataset you're going to be working with is going to create a series of passages of text from a scholarly article. The goal of your prompt is to take in that passage of text and extract all the topics from it into a JSON array of strings. And when I say topic, I really just mean, well, what does this article talk about? So if it's our article about, say, solar panels, I would want to get back a JSON array of strings that contains solar panels inside of it, along with any other topics mentioned inside that text. The only input to the prompt is going to be a content property. So that's going to be one paragraph of text. Now, in my notebook that I've opened right now, I have already executed the cell and generated a data set. I have already put together a starter prompt for you. And right now it's a very poor prompt that can definitely be improved quite a bit. So go ahead and play around with this prompt and use some of the different techniques we have learned about. In order to evaluate your progress, go ahead and run the cell down here. I've already included a couple of extra criteria to make sure that you're kind of achieving the correct result. Remember, whenever you run the evaluation, you can always open up that report.html file and get a better understanding of what's going on. By default, with that really simple prompt, I've got a average score of 2.8. And so I'm hoping we can at least get that average score over maybe seven or so. As usual, I would encourage you to pause the video right here and go ahead and give this exercise a shot. Otherwise, if you want to stick around, I'm going to go over a solution right away. All right, so to solve this, we know without a doubt all the work we're going to be doing is focused on this variable right here. We need to improve this prompt in some way to get back some better output. The first thing we might do here is make sure you run the evaluation at least one time to generate that output.html file. And then once you have created that file, I would encourage you to open it up and take a look at some of the reasoning on why the output has been graded so poorly. You'll notice a common theme between each of these. The real common theme right away that we can see is that, well, it doesn't like the fact that we are not returning a JSON array of strings. To solve this problem, let's make use of that technique of being simple and direct inside of our prompts. If we expect to get back a JSON array of strings that contains all the topics out of this content, well we need to be very simple and direct with Claude and tell it exactly what we want. So I'm going to update the first line of the prompt right here and I'll say extract key topics mentioned from a passage of text from a scholarly journal into a JSON array of strings. as simple as I can phrase it and as direct as I can possibly make it. And right away, if I rerun the prompt with the evaluation, I will see that I get up to a kind of shocking 9.5 almost immediately. So I kind of didn't really expect it to go that well, because there are still some other techniques we might want to try out here. But if we take a look at the report now, I'm definitely getting back this JSON containing all the different topics mentioned. And it looks like the model grader is extremely happy with this output. Now, I don't really want to leave it here. Obviously, there are some other techniques we can add in to make sure that we get the correct kind of output. So the next technique we might add in is structuring our prompt a little bit better by using some XML tags. Before and after the content that we're going to interpolate in right here, I'm going to add in some XML tags, and I'm going to give them the name of simply text. Now, in this case, I'm choosing to call these tags text because I earlier referred to some text inside of the first line of the prompt. So now we have just a little bit more clear connection between us talking about a passage of text right here and us providing the text right here. Another improvement that we might add in is to be very specific in what we want Claude to do. So I could put in a series of steps for Claude to follow. Let's try that out. I might say follow these steps and then list out exactly what I want Claude to do step by step. So we might say closely examine the provided text, identify each topic mentioned, add each topic to a JSON array, and then finally respond with the JSON array. Do not provide any other text or commentary. Now, if you wanted to also add in an example using one-shot or multi-shot prompting, absolutely feel free to do so. But we already have some pretty good scores, so I think this is probably enough. I'm going to rerun this prompt. I'll then run the evaluation again and I end up getting a 9.5 again. So I would say this is a pretty strong prompt and I would definitely trust it to extract a list of topics from an article.

↑ 目次へ戻る

Building with the Claude API | Lesson 27「Introducing tool use」(全文ナレッジ)

動画 2分54秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンのテーマ

tool use(ツール使用)モジュールの導入。ツールは Claude に「外の世界の情報」へのアクセスを与える仕組み。理解がやや難しいトピックなので、このビデオでは専門用語を避けたソフトな導入として、tool use の全体フローを一気通貫で眺める。

なぜツールが必要か(Claude の限界)

  • Claude はデフォルトでは学習に使われた情報にしかアクセスできない
  • そのため、ごく最近の時事情報(current events)は基本的に持っていない。
  • 例:chatbot のユーザーが「今のサンフランシスコ(カリフォルニア州)の天気は?」と聞くと、Claude は「申し訳ありませんが、最新の天気情報にはアクセスできません」といった応答を返してしまう。
  • これを解決し、ユーザーにより良い応答を返すために tools を使う。

tool use の全体フロー(汎用形)

これから実装していく流れは 4 ステップの往復: 1. 初回リクエスト:質問(またはタスク)を Claude に送る。その際、「外部から追加データを取得する方法」の説明(instructions)を同梱する。 2. Claude が追加データを要求:Claude は質問を見て「追加データが必要だ」と判断したら、何の情報がどう必要かの詳細を添えてこちらにレスポンスを返す。 3. こちら(開発者のサーバー)でコードを実行:Claude が要求した情報を取得するコードを走らせ、その結果をフォローアップリクエストとして Claude に返す。 4. 最終応答:Claude は必要な情報が揃ったので、追加データで補強(augmented)された最終応答を生成して返す。

同じフローを「天気」シナリオに当てはめると

  1. ユーザーが天気を尋ねるプロンプトを Claude に送る。初回リクエストの中に「現在の天気データの取得方法」の詳細を含める。
  2. Claude はプロンプトを見て「この質問に答えるには現在の天気データが要る」と判断し、こちらに要求を返す。
  3. サーバー側でサードパーティの天気 API に問い合わせるコードを実行し、特定地点のライブな天気情報を取得。フォローアップリクエストで Claude に渡す。
  4. Claude は元のプロンプト+最新の天気データの両方を手にしているので、最終応答を生成して返す。

実務ポイント(このレッスンから持ち帰るもの)

  • ツールの本質は「Claude が外界データを要求 → 実行するのは常に開発者側」。Claude 自身がコードを実行したり API を叩いたりするわけではない。実行責任はこちらのサーバーにある。
  • tool use は必ず 2 往復以上のリクエストになる(初回リクエスト → tool 要求 → フォローアップ → 最終応答)。1 回の create() 呼び出しで完結しない前提で設計する。
  • 初回リクエストに「データの取り方の説明」を同梱するという発想が、次レッスン以降の tool schema につながる。
  • 「知識カットオフ以降の情報・リアルタイム情報(天気・時刻・在庫・価格など)はツールで補う」が基本パターン。RAG や検索連携もこのフローの応用形。

本文(時系列・日本語逐語+画面差し込み)

[0:00] このモジュールでは、tool use(ツール使用)について論じていきます。ツールは、Claude が外の世界からの情報にアクセスすることを可能にします。

🖥 [0:00] 画面(タイトルスライド) 黒背景に ANTHROP\C ロゴ、大見出し「Tool use」。

[0:06] さて、tool use の理解は少し難しいこともあるので、このビデオでは本当にソフトな導入を行います。tool use のフロー全体をウォークスルーして、それが何なのかを理解していきます。

[0:16] まず最初に、Claude が残念ながら時にその限界にぶつかってしまう例をお見せしたいと思います。思い出してください。デフォルトでは、Claude は実際に学習に使われた情報にしかアクセスできません。ですから一般的に、ごく最近の時事的な出来事についての情報は実質的に持っていません。

[0:32] この例として、私たちの chatbot を使っているユーザーが、「今、カリフォルニア州サンフランシスコの天気はどうですか?」というような質問をしたとしましょう。それを Claude に送ると、おそらく「申し訳ありませんが、そのような最新の天気情報にはアクセスできません」といった応答が返ってくるでしょう。

🖥 [0:32] 画面(スライド:チャット UI と Claude の往復) 左にチャット UI のモック: You: What's the weather in San Francisco, California? AI: Claude is thinking... 右に「Claude」の赤茶色ボックス。チャットから Claude へ「What's the weather in San Francisco, California?」の矢印。 続くフレームでは AI の応答が「I'm sorry, but I don't have access to up-to-date weather information.」になり、矢印は「Request」(→)と「Response」(←)のラベル付きに変わる。

[0:49] これを直してユーザーにより良い応答を返すために、tools を利用できます。まず、ツールを本当にシンプルな用語で分解して説明する図をお見せします。その後、この天気の問題を具体的にどう解決するかの例を示す、もう 1 つの図をお見せします。

[1:02] オーケー。これが、ツールを使い始めるときに最終的に実装することになるフロー全体です。まず Claude に初回のリクエストを送ります。質問をするか、あるいは Claude にタスクを与えます。そしてそれと一緒に、Claude が外の世界から追加データを取得できる方法についての説明(instructions)を含めます。

🖥 [1:02] 画面(スライド:汎用 tool use フロー図) 左に「Our Server」(グレーのボックス。内側に白いボックスで「We run code to get that extra data」)、右に「Claude」(赤茶のボックス)。4 本の矢印: 1. → Ask a question, include instructions on how to get extra data 2. ← Ask for extra data, providing specific details on what is needed 3. → Respond with extra requested data 4. ← Provide a final response, augmented by the extra data

[1:21] Claude は、尋ねられた質問や与えられたタスクを見て、追加データを要求する必要があると判断するかもしれません。その場合、追加データを求めるレスポンスをこちらに返してきます。そこには、具体的にどんな情報が必要なのかの詳細が添えられます。

[1:35] 次に、私たちのサーバー上で、Claude が求めた情報を取りに行くちょっとしたコードを実行し、その結果をフォローアップのリクエストとして Claude に返します。

[1:46] これで Claude は、理論上、応答を返すのに必要な情報をすべて手にしています。そこで、その追加データによって補強(augmented)され、改善されているであろう最終応答を生成します。

[1:54] では、まったく同じフローをもう一度お見せしますが、今度は各ステップを「ユーザーが特定の場所の現在の天気を尋ねてくる」というシナリオに合わせてカスタマイズします。

🖥 [2:00] 画面(スライド:天気シナリオ版フロー図) 同じ構図で、左の白ボックスは「We run code that reaches out to a weather API」に変化。4 本の矢印: 1. → User asks about weather. We include details on how to retrieve current weather data 2. ← Claude decides to ask us to get current weather data 3. → Respond with current weather data 4. ← Claude provides a final response using the weather data

[2:04] これが起きることです。Claude に初回クエリを送ります。そこにはユーザーが天気について尋ねるプロンプトが含まれます。そしてその初回リクエストの中に、現在の天気データを具体的にどう取得するかの詳細を含めておきます。

[2:18] Claude はプロンプトを見てこう判断します。「この質問に答えるには、現在の天気データを取得する必要がある」。そしてレスポンスをこちらに返します。私たちはそれを受けてコードを実行します。そのコードは、たとえば何らかのサードパーティの天気 API に問い合わせて、特定の場所の現在の天気に関するライブな詳細情報を実際に取得します。

[2:40] その外部 API から詳細情報を取得できたら、その現在の天気データを添えて Claude にフォローアップリクエストを送ります。これで Claude は必要な情報をすべて手にしました。元のプロンプトと、最新の天気データの両方を持っているので、最終応答を生成して私たちに返すことができます。

英語逐語(ASR全文)

In this module, we are going to discuss tool use. Tools allow Claude to access information from the outside world. Now, understanding tool use can be a little bit challenging, so in this video, I'm going to give you a really soft introduction. We are going to walk through the entire flow of tool use and understand what it's all about. I want to first begin by giving you an example of where Claude can, unfortunately, sometimes hit its limits. Remember, by default, Claude only has access to information that was actually trained on. So in general, it doesn't really have any information about very recent current events. As an example of this, if we had a user making use of our chat bot, ask a question of something like, what's the weather in San Francisco, California right now? And sent that off to Claude, we would probably get a response back of something like, I'm sorry, but I don't have access to up-to-date weather information like that. To fix this and give the user a better response, we can make use of tools. Let me show you a diagram that's going to break down tools with really simple terminology. And then I'll show you another one that's going to give you an example of how we would solve this specific weather problem. Okay, so here's the entire flow that we're going to eventually implement when we start to make use of tools. We're going to send off an initial request to Claude. We're going to ask a question or maybe give Claude a task. And along with that, we're going to include instructions on how Claude can get some extra data from the outside world. Claude will then take a look at whatever question it was asked or whatever task it was given, and it might decide that it needs to ask for some extra data. So it'll send a response back to us where it asked for some extra data, and it's going to give us some details on exactly what information it needs. Then on our server, we are going to run a little bit of code that will go and get the information that Claude asked for, and then respond with that on a follow-up request back to Claude. Now Claude has all the information it needs, in theory, to give us a response. So it will generate a final response that will be hopefully augmented or improved by that extra data. Now I'm going to show you the same exact flow, but I'm going to customize each of these little steps for this scenario where a user asks us for some current weather in a particular location. So here's what would happen. We would send an initial query off to Claude, and it would include a prompt where the user asks about the weather. And inside of that initial request, we're going to include details on specifically how to retrieve current weather data. Claude would take a look at the prompt and decide, hey, to answer this question, I need to get some current weather data. It sent a response back to us where we would then run some code that would reach out to some maybe third party weather API and actually get some live details on what the current weather is for a particular location. Once we have those details from that outside API, we would then make a follow-up request to Claude with that current weather data. And now Claude has all the information it needs. It has the original prompt along with the up-to-date weather data so it can generate a final response and send it back to us.

↑ 目次へ戻る

Building with the Claude API | Lesson 28「Project overview」(全文ナレッジ)

動画 2分23秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンのテーマ

tool use を学ぶためのミニプロジェクトのゴール設定。Jupyter ノートブック内で「Claude にリマインダーを設定させる」小さなプロジェクトを実装する。複数のツールが必要になるが、当面は 1 ツールずつ進める。

ゴールのイメージ

  • ユーザー:「Set a reminder for my doctor's appointment. It's a week from Thursday.(医者の予約のリマインダーを設定して。来週の木曜日ね)」
  • Claude:「OK, I will remind you.(了解、その時にお知らせします)」と応答できるようにしたい。

なぜこれが難しいのか(3 つの課題)

一見簡単そうだが、実際には 3 つの課題があり、すべてツールで解決する: 1. Claude は現在の「日付」は知っているが、「正確な時刻」は必ずしも知らない。「今から 24 時間後にリマインドして」と言われても、現在時刻を知らないので「正確に 24 時間後」が計算できない。 2. Claude は時間の加算(time-based addition)を常に正しくは処理できない。「1973 年 1 月 13 日の 379 日後は?」のような計算は、多くの場合正解するが、時々間違える。 3. Claude には「リマインダーを設定する」機構が存在しない。概念としては理解しているが、未来にリマインダーを発火させる仕組みは Claude 内部にまったくない。

解決策:課題ごとに専用ツールを 1 つずつ作る(計 3 つ)

  1. Get the current date time:現在の日付+時刻を取得する非常にシンプルなツール。ツール呼び出しの仕組みを理解するための最初の題材。
  2. Add a duration to a date time:日時に期間を加算するツール(例:現在の日付に 20 日足すと何日になるか)。
  3. Set a reminder:リマインダーを設定するツール。

実務ポイント(このレッスンから持ち帰るもの)

  • LLM の時間感覚は信用しないのが実務の基本。①現在時刻を知らない、②日付演算を間違えることがある、③外界への作用(リマインダー設定等)はそもそも不可能——の 3 点は、そのままツール設計の要件になる。
  • 「1 つの弱点 = 1 つの専用ツール」という分解の仕方が手本。大きな 1 ツールに詰め込まず、get_current_datetime / add_duration_to_datetime / set_reminder のように単機能に分ける。
  • 決定論的にできる計算(日付演算など)は LLM にやらせず、コード側のツールにやらせる。LLM は「どのツールをどの引数で呼ぶか」の判断に専念させる。
  • 学習の進め方として「まず最も単純なツール 1 個から始めて、徐々に複数ツールへ拡張」する段取りは、実プロジェクトでも有効な進め方。

本文(時系列・日本語逐語+画面差し込み)

[0:00] ツールについてもっと学ぶために、ちょっとしたゴールを自分たちに課すことにします。これは Jupyter ノートブックの中で実装する小さなプロジェクトです。Claude に、未来のある時点で発動するリマインダーの設定の仕方を教えることに挑戦します。

🖥 [0:00] 画面(スライド「Goal for Tool Use」) 左に見出し「Goal for Tool Use」と箇条書き: - We want to teach Claude to set reminders - Will require us to implement 3-4 custom tools - For now, we'll focus on just one tool

右に赤茶背景のチャット UI モック: You: Set a reminder for my doctor's appointment. It's a week from Thursday. AI: OK, I will remind you.

[0:12] これには、いくつかの異なるツールを実装する必要があります。当面は 1 度に 1 つのツールに集中しますが、最終的には複数のツールを扱うことになる、ということだけ知っておいてください。

[0:23] つまり最終的には、Claude に「医者の予約のリマインダーを設定して。来週の木曜日(a week from Thursday)ね」というようなメッセージを送れるようにしたいのです。そして Claude には「オーケー、その時刻になったらお知らせします」というように応答してほしいのです。

[0:36] このタスクを最初に見たとき、とても簡単そうだと思うかもしれません。しかし実際には、取り組まなければならない課題がいくつかあることがわかります。そして、それらすべてをツールの使用によって解決していきます。

🖥 [0:40] 画面(スライド「Why is this hard?」) 左に見出し「Why is this hard?」と箇条書き: - Claude might know the current date, but not the exact time - Claude doesn't always handle time-based addition well, especially if looking many days into the future - Claude doesn't know how to set a reminder!

右は同じチャット UI モック(doctor's appointment の往復)。

[0:45] 具体的に言うと、まず、Claude は現在の日付は知っています。言い換えると、今すぐプロンプトを開いて現在の日付を尋ねれば、正確に正しい答えを返してくれます。しかし、Claude は正確な「時刻」を常に知っているわけではありません。ですから、たとえば「今から 24 時間後にリマインダーを設定して」と頼んで、それがきっかり 24 時間後であることを期待しても、Claude は現在時刻を知らないので、「今から 24 時間後」が実際にいつなのか本当にはわからないのです。

[1:14] 第二に、Claude は時間ベースの加算を常に完璧に処理できるわけではありません。たとえば「1973 年 1 月 13 日から 379 日後は何日ですか?」と尋ねると、Claude は多くの場合正しい答えを返しますが、時々その加算を間違えます。

[1:29] そして最後に、Claude はリマインダーを設定するということがどういうことか、そもそも知りません。その機構(メカニズム)を持っていないのです。「リマインダーを設定する」が概念的に何かは知っていますが、未来にリマインダーを設定するための仕組みは、Claude の内部にはいっさい存在しません。

[1:44] これらの問題のそれぞれを解決するために、専用のツールを作ります。ここに 3 つの課題があるので、3 つの別々のツールを、1 つずつ作っていきます。

[1:54] それぞれのツールがやることはこうです。まず、非常にシンプルなツールから始めます。これはツール呼び出し(tool calling)が何なのかを理解する助けになります。その唯一の仕事は、現在の日時(current date time)を取得すること。つまり現在の日付+時刻です。

🖥 [1:56] 画面(スライド「Tools We Need」) 見出し「Tools We Need」の下に 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

[2:08] 2 つ目に作るツールは、日時に期間(duration)を加算するものです。これによって、たとえば「現在の日付に 20 日を足すと、結果は何日になるか」というようなことが言えるようになります。

[2:19] そして最後に、リマインダーを設定するツールも作ります。

英語逐語(ASR全文)

To learn more about tools, we are going to set ourselves a little goal. This is going to be a small project that we implement inside of a Jupyter notebook. We're going to try to teach Claude how to set reminders that occur at some point in time in the future. This is going to require us to implement several different tools. And for right now, we're just going to focus on one tool at a time, but just know that we're going to eventually have to deal with multiple tools. So I want to eventually be able to send a message to Claude of something like, set a reminder for my doctor's appointment. It's a week from Thursday. And I want Claude to respond with something like, okay, I will remind you at that point in time. When you first look at this task, you might think it seems really easy, but it turns out that there are actually several different challenges that we're going to need to tackle, and we're going to solve all of them through the use of tools. So in particular, Claude does know the current date. In other words, if you open up a prompt right now, you could ask it what the current date is, and it will give you an exactly correct answer. However, Claude doesn't always know the exact time of day. So if we were to ask Claude to do something like set a reminder for 24 hours from now, expecting it to be exactly 24 hours, Claude doesn't really know what 24 hours from now actually is because it doesn't know the current time. Secondly, Claude does not always perfectly handle time-based addition. So if I were to ask it, what is 379 days from January 13th, 1973? Claude will very often give you the correct answer, but sometimes it will get that addition incorrect. And finally, Claude just doesn't know what it means to set a reminder. It has no concept of it. It does know conceptually what setting a reminder is, but there's no mechanism inside of Claude whatsoever for setting reminders in the future. To solve each of these issues, we are going to make a dedicated tool. So we have three issues right here. We are going to make three separate tools, one at a time. Here's what each tool is going to do. We're going to have a very simple tool that we're going to get started with. It's going to help us understand what tool calling is all about. Its only job is to get the current date time. So that means the current date plus the time. The second tool we will make will add a duration to a date time. So this will allow us to say something like take the current date and add 20 days to it. And what would the resulting day be? And then finally, we will make a reminder setting tool as well.

↑ 目次へ戻る

Building with the Claude API | Lesson 29「Tool functions」(全文ナレッジ)

動画 5分22秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンのテーマ

tool use 実装のステップ 1:tool function(ツール関数)を書く。最初のツール「get the current date time(現在日時の取得)」を、ヘルパー関数を使わずスクラッチで実装する。

教材ノートブック 001_tools.ipynb

  • このレクチャーに添付された新しいノートブック。コースでこれまで書いたコード(クライアント生成・ヘルパー関数)に加えて、新セクション「Tools and Schemas」に大量のボイラープレートコードが用意されている。
  • 特に、後で使う add_duration_to_datetime 関数が既に入っている。ダウンロードして出発点にすることが推奨されている。
  • 今回は既存のヘルパー関数(add_user_message / add_assistant_message 等)をあえて使わない。ツール対応のためにそれらのリファクタが必要になるので、「ヘルパー更新」と「ツール学習」を混ぜると混乱するから。まずは素の状態でツール呼び出しを実装する。

tool function とは

  • プレーンな Python 関数。Claude が「ユーザーを助けるために追加情報が必要だ」と判断した時に、(こちらのコードで)自動的に実行される。
  • スライドの例は get_weather(location):ロケーションを受け取り、天気 API を叩いて JSON を返す関数。

tool function のベストプラクティス(3 つ)

  1. よく名付けられ、説明的な引数を使う:関数名も引数名も、何をするものか少なくともヒントになる名前にする。
  2. 入力をバリデーションし、問題があればエラーを raise する:例)location が無い/空文字列なら即座にエラー。
  3. エラーには意味のあるエラーメッセージを含める:Claude はツール実行が失敗するとその正確なエラーメッセージを見ることがあり、それを読んで呼び方を修正してもう一度呼び直すことがある。例)空文字列で get_weather を呼ぶ → 「Location cannot be empty」を見た Claude が、今度は空でない文字列で再呼び出しする。

実装:get_current_datetime

def get_current_datetime(date_format="%Y-%m-%d %H:%M:%S"):
    if not date_format:
        raise ValueError("date_format cannot be empty")
    return datetime.now().strftime(date_format)
  • 引数 date_format のデフォルト値は "%Y-%m-%d %H:%M:%S"(年-月-日 時:分:秒)。この文字列は複雑なので、動画を一時停止して正確に一致しているか確認するよう指示あり。
  • get_current_datetime()'2026-xx-xx 12:27:xx' 形式。カスタム形式 get_current_datetime("%H:%M")'12:27'(時:分のみ)。
  • バリデーションstrftime フォーマッタとして正しい構造かの検証は容易ではないが、空文字列でないことのチェックはできるif not date_format:ValueError("date_format cannot be empty") を raise。
  • get_current_datetime("") を呼ぶと ValueError が発生し「date_format cannot be empty」が表示される。Claude がこのミスをする可能性は低いが、万一の際に修正方法のシグナルを Claude に返せる

実務ポイント(このレッスンから持ち帰るもの)

  • エラーメッセージは「人間向けログ」ではなく「Claude への自己修正シグナル」。ツール関数の raise するメッセージは、Claude がリトライ時に読む前提で「何をどう直せばいいか」が伝わる文言にする。
  • ツール関数は特別な仕組みではなくただの Python 関数。API・SDK 依存のコードは一切入らない。テストも普通のユニットテストで書ける。
  • 引数にはデフォルト値を持たせておくと、Claude が引数を省略しても安全に動く(date_format のデフォルトが好例)。
  • 完全なバリデーションが難しい引数でも、「空でないか」程度の弱いチェックだけでも入れる価値がある(コスト最小で自己修正ループが機能する)。
  • 学習時の段取り:ヘルパー関数のリファクタと新概念の学習を同時にやらない。まず生のコードで概念を掴み、後から抽象化する。

本文(時系列・日本語逐語+画面差し込み)

[0:00] それでは最初のツールの作成に取りかかりましょう。これは Claude が現在の日時(current date time)を取得できるようにするものです。先に進む前に知っておいてほしいのですが、皆さんが使うための新しいノートブックを作っておきました。このノートブックは「001 Tools」というタイトルで、このレクチャーに添付されています。

[0:13] このノートブックの中には、コースの中で既に書いてきたコードの多くが入っています。ただし、この下の方に「Tools and Schemas」というタイトルの新しいセルも追加してあります。このセルの中には、後で時間を節約するために、膨大な量のボイラープレートコードを置いてあります。

🖥 [0:22] 画面(VS Code / 001_tools.ipynb ノートブック 001_tools.ipynb。セクション見出しセル # Tools and Schemas の下に、既に用意されたコード: ```python from datetime import datetime, timedelta

def add_duration_to_datetime( datetime_str, duration=0, unit="days", input_format="%Y-%m-%d" ): date = datetime.strptime(datetime_str, input_format)

if unit == "seconds":
    new_date = date + timedelta(seconds=duration)
elif unit == "minutes":
    new_date = date + timedelta(minutes=duration)
elif unit == "hours":
    new_date = date + timedelta(hours=duration)
elif unit == "days":
    new_date = date + timedelta(days=duration)
...

`` (上部には既存の# Load env variables and create client/# Helper functionsセクション。ステータスバーは.venv (Python 3.9.6)`)

[0:27] 特に、add duration to date time 関数が見つかるはずです。これは少し後で使うことになります。というわけで、繰り返しますが、このレクチャーに添付されているこのノートブックをダウンロードして、出発点として使うことをお勧めします。

[0:39] よし。それでは改めて、最初のツール「現在の日時を取得する」の実装に集中していきます。このプロセス全体をステップバイステップで案内していきます。ノートブックの中にたくさんのコードを書き出すことになりますが、その際、中にセットアップ済みのヘルパー関数——add user message や add assistant message など——は使いません

🖥 [0:47] 画面(ノートブックの Helper functions セクション) # Helper functions セクション内の既存コード: ```python def add_user_message(messages, text): user_message = {"role": "user", "content": text} messages.append(user_message)

def add_assistant_message(messages, text): assistant_message = {"role": "assistant", "content": text} messages.append(assistant_message)

def chat(messages, system=None, temperature=1.0, stop_sequences=[]): params = { "model": model, "max_tokens": 1000, "messages": messages, "temperature": temperature, ... ```

[0:56] その理由は、これらの関数はツールに合わせて少しリファクタする必要があることが後でわかるからです。ヘルパー関数の更新とツールの学習を同時にやろうとすると、本当に混乱してしまいます。ですから代わりに、できる限りヘルパー関数を使わずに、ツール呼び出しを行うことだけに集中します。

[1:15] オーケー、では取りかかりましょう。このプロセス全体を、いくつかのステップに分解してあります。ツールを実装に追加するときのステップ 1 は、tool function(ツール関数)を書き出すことです。

🖥 [1:16] 画面(スライド「Tool Function」+工程バナー) 上部に 5 ステップの工程バナー:Write a tool function →(Write a JSON Schema)→(Call Claude with JSON Schema)→(Run Tool)→(Add Tool Result and call Claude again)。現在は 1 番目がハイライト。 左に見出し「Tool Function」と箇条書き: - Plain Python function that will be executed when Claude decides it needs some additional information to help the user - Best practices - Use well-named, descriptive arguments - Validate the inputs, raising an error if they fail validation - Return meaningful errors - Claude will try to call to use your function a second time!

右にコード例: ```python def get_weather(location): if not location or location.strip() == "": raise ValueError("Location cannot be empty")

url = "https://fakeweatherapi.example.com/current"
params = {
    "q": location,
    "appid": api_key,
    "units": "metric"
}

response = requests.get(url, params=params, timeout=10)
response.raise_for_status()

return response.json()

```

[1:25] ツール関数とは、プレーンな Python 関数です。Claude が「ユーザーを何らかの形で助けるために追加情報を取得する必要がある」と判断した将来のある時点で、自動的に実行されることになります。右側に、あり得るツール関数の例として get weather というものを載せています。Claude はこのツール関数を利用して、世界の特定の場所の現在の天気を取得できるかもしれません。

[1:47] さて、ツール関数にはいくつかのベストプラクティスがあります。第一に、常によく名付けられた、説明的な引数を使うこと。つまり、関数自体も、受け取る引数も、それなりに良い名前が付いていて、少なくとも何に関するものかのヒントを与えるべきです。

[2:02] 第二に、これらの入力をバリデーションして、入力自体に何か問題があればエラーを raise すること。たとえば、location を受け取れなかったり、location が空文字列だったりしたら、直ちにエラーを raise したいところです。

[2:15] そして最後に、エラーを raise するときは必ず、何らかの意味のあるエラーメッセージを含めるようにします。場合によっては、Claude がツール関数を呼び出そうとしてエラーになったとき、Claude はその正確なエラーメッセージを見ることになります。そして Claude は、あなたのツール関数をもう一度、そのエラーを修正するような少し違った呼び方で呼び直そうとするかもしれません。

[2:35] たとえば想像してみてください。Claude がこの get weather 関数を呼び出そうとして空文字列を渡すと、一番上のバリデーションチェックが失敗してエラーを raise します。Claude は「Location cannot be empty」というエラーメッセージを見ることになり、そして、今度は空文字列ではないものを確実に渡すようにして、このツール関数をもう一度呼び出そうと判断するかもしれません。

[2:56] それではノートブックに戻って、最初のツール関数を組み立てましょう。思い出してください。今作ろうとしているツールのゴールは、現在の日時を取得することです。

[3:04] さて、こちらに戻ってきました。一番下に新しいセルを追加して、get current date time という新しい関数を定義します。これは、date format と呼ぶことにする引数を受け取ります。デフォルト値を与えますが、これはちょっと複雑な文字列になります。パーセント大文字 Y、パーセント小文字 m、パーセント小文字 d、それからスペース、パーセント H コロン、パーセント M コロン、パーセント S、と入力していきます。

🖥 [3:10〜3:35] 画面(関数定義の入力途中) python def get_current_datetime(date_) # ← 引数名を入力中 ↓ 最終的にシグネチャが完成: python def get_current_datetime(date_format="%Y-%m-%d %H:%M:%S"):

[3:35] さて、この文字列は少し複雑なので、ここでビデオを一時停止して、入力した文字列をダブルチェックすることをお勧めします。私のものと正確に一致していることを確認してください。

[3:45] それからこの関数の中で、この date format を使って現在の日時を取得し、この date format 文字列に一致する形でフォーマットします。つまり、datetime.now の string format time(strftime)を return し、その date format を渡します。

🖥 [3:52] 画面(関数完成+動作確認の呼び出し) ```python def get_current_datetime(date_format="%Y-%m-%d %H:%M:%S"): return datetime.now().strftime(date_format)

get_current_datetime() ```

[4:00] では、この関数を実際にどう使うかの簡単な例として、get current date time を呼び出してみます。このまま実行すれば、年、月、日、時、分、秒という形式の日時が返ってきます。あるいは代わりに、カスタムの date format 文字列を入れることもできます。たとえばパーセント H コロン、パーセント大文字 M のようなものです。そうすると、現在時刻の時と分だけが出力されます。

🖥 [4:20] 画面(カスタム形式での実行結果) ```python def get_current_datetime(date_format="%Y-%m-%d %H:%M:%S"): if not| # ← バリデーションを追記し始めている return datetime.now().strftime(date_format)

get_current_datetime("%H:%M") 出力(0.0s): '12:27' ```

[4:25] この関数への良い改良は、date format 引数のバリデーションを少し追加することでしょう。残念ながら、正確な構造を簡単にバリデーションすること——これが正しい strftime のフォーマッタであることを確認すること——はできませんが、少なくとも空文字列を渡していないことのチェックはできます。そこで、if not date_format という形で少しバリデーションを追加することにします。そしてこのバリデーションチェックに失敗した場合は、value error を raise して、「date format cannot be empty」のように言うことにします。

🖥 [4:49] 画面(バリデーション追加後の完成形) ```python def get_current_datetime(date_format="%Y-%m-%d %H:%M:%S"): if not date_format: raise ValueError("date_format cannot be empty") return datetime.now().strftime(date_format)

get_current_datetime("") ```

[4:55] これで、get current date time を空文字列で呼び出そうとすると、エラーメッセージが出て、「date format cannot be empty」と教えてくれます。

🖥 [5:04] 画面(ValueError の Traceback) get_current_datetime("") 実行結果(0.1s・エラー): ```


ValueError Traceback (most recent call last) Cell In[7], line 6 3 raise ValueError("date_format cannot be empty") 4 return datetime.now().strftime(date_format) ----> 6 get_current_datetime("")

Cell In[7], line 3 1 def get_current_datetime(date_format="%Y-%m-%d %H:%M:%S"): 2 if not date_format: ----> 3 raise ValueError("date_format cannot be empty") 4 return datetime.now().strftime(date_format)

ValueError: date_format cannot be empty ```

[5:04] 正直に言うと、Claude がここで空文字列を渡すというミスをする可能性はかなり低いです。しかし、万が一そうなった場合に備えて、Claude に何らかのシグナルを返し、エラーをどう修正できるかをいわば教えてあげているのです。Claude は get current date time をもう一度呼び出し、今度は空ではない date format を確実に渡せばよいのです。

英語逐語(ASR全文)

Let's get started working on our first tool, which is going to allow Claude to retrieve the current date time. Before we go any further, I want you to know that I've created a new notebook for you to use. This notebook is titled 001 Tools, and it is attached to this lecture. Inside this notebook, you're going to find a lot of the same code we've already written out inside the course. However, I've also added in a new cell down here, titled Tools and Schemas. Inside this cell, I've placed a tremendous amount of boilerplate code, just to save us a little bit of time later on. In particular, you're going to find the add duration to date time function, which we're going to use a little bit later on. So again, I would encourage you to download this notebook that is attached to this lecture and use it as the starting point. All right. So now once again, we're going to be focused on implementing this first tool of get the current date time. I'm going to walk you through this entire process. Step-by-step. We're going to write out a lot of code inside of our notebook, and we're not going to use any of those helper functions that we've set up inside there. The ones like add user message and add assistant message and so on. The reason for this is that you're going to see that we need to refactor those functions just a little bit to suit tools. So rather than trying to mix updating those helper functions and learn about tools at the same time, that would be really confusing. So we're going to instead just focus on making a tool call without using any helper functions as much as possible. Okay, so let's get to it. I've broken down this entire process into several different steps. Step one, whenever we are adding in a tool to our implementation, is to write out a tool function. A tool function is a plain Python function that is going to be executed automatically at some point in time when Claude decides that it needs to retrieve some extra information in order to help the user in some way. I've got an example of a possible tool function on the right-hand side called get weather. Claude might be able to make use of this tool function in order to retrieve the current weather at some particular location in the world. Now there's a couple of best practices around tool functions. First, we always want to use well-named and descriptive arguments. So the actual function itself and the arguments to receive should be reasonably well named and at least give us a hint of what they are about. Second, we want to validate these inputs and raise an error if anything is wrong with the input itself. So for example, if we fail to receive a location or if the location is an empty string, we would want to raise an error immediately. And then finally, whenever we do raise an error, we want to make sure that it contains some kind of meaningful error message. In some cases, if Claude tries to call a tool function and it results in an error, Claude is going to see the exact error message. And Claude might decide to try to call your tool function again and call it in a slightly different way that attempts to correct for that error. So for example, you can imagine that if Claude tries to call this get weather function and it passes in an empty string, the validation check at the very top would fail and we would raise an error. Claude would see the error message of location cannot be empty and Claude might then decide to try to call this tool function again, making sure that it passes in not an empty string anymore. All right, so let's go back over to our notebook and we're going to put together our first tool function. Remember the goal of the tool we are putting together is to get the current date time. So back over here, I'm going to add a new cell at the very bottom and I will define a new function called get current date time. This is going to take in an argument that I will call date format. I will give it a default value. It's going to be a little bit of a complicated string here. I'm going to put in percent capital Y percent lowercase m percent lowercase d and then a space a percent h colon percent m colon percent s. Now that string is a little bit complicated so I would encourage you to pause the video right here and double check the string you've put in. Make sure it exactly matches what I have. Then inside this function, I'm going to use that date format to get the current date time and format it in a way that matches this date format string. So I will return datetime.now string format time. I'll pass in that date format. So now as a quick example of how we would actually use this function, we could call get current date time. And if we ran it just like this, we would get back a date time in the format of year, month, day, hour, minute, second. Or alternatively, I could put in a custom date format string, something like percent H colon percent capital M. And that will print out just the hour and minute of my current time. A good improvement to this function would be to add in some validation of the date format argument. Unfortunately, we can't very easily validate the exact structure. make sure that this thing is a valid string format time formatter, but we can at least check and make sure that we are not passing in an empty string. So I might decide to add in a little bit of validation here with a if not date format. And then if we fail that validation check, I might raise a value error and say something like date format cannot be empty. So now if I try to call get current date time with an empty string, I would end up getting an error message and it's going to tell me date format cannot be empty. Now, to be honest with you, it's kind of unlikely that Claude is going to make this mistake of passing in an empty string here, but at least in the off chance that it does, we are providing some signal back to Claude and we are kind of telling it how it can fix up the error. It can try to call get current date time again and make sure that it passes in a date format that is not empty.

↑ 目次へ戻る

Building with the Claude API | Lesson 30「Tool schemas」(全文ナレッジ)

動画 4分39秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04

日本語ナレッジ(要点整理)

Tool use 5ステップの「Step 2:JSON Schema を書く」

  • ツール実装の工程バー:① Write a tool function(済)→ ② Write a JSON Schema(今回)→ ③ Call Claude with JSON Schema → ④ Run Tool → ⑤ Add Tool Result and call Claude again
  • この設定一式(name+description+input_schema)を最終的に Claude に送る。Claude はこれを使って「どのツール関数が利用可能か」「各ツール関数にどの引数を渡さねばならないか」を理解する。

JSON Schema の正しい理解

  • スライド右側のオブジェクト全体は厳密には JSON Schema ではない。最上部に namedescription があり、その下の input_schema キーに割り当てられた辞書——そこが技術的に JSON Schema
  • JSON Schema は LLM やツール呼び出し専用の技術ではない。データ検証仕様(data validation specification)——任意の JSON データを検証するためのルール集で、何年も前からある広く理解された技術。LLM コミュニティが「ツール呼び出しの配線・処理に便利」と採用しただけ。
  • 役割分担:
  • name:ツール名(例 get_weather)。
  • description:ツールが何をするか・いつ使うか・何を返すかを Claude に伝える。3〜4文が best practice(スライドの「Retrieves current weather…」は短すぎる例)。
  • input_schema:関数に渡すべき引数を記述する本体。引数ごとに type と description を持ち、引数の description も3〜4文で「何を制御し、関数呼び出し全体にどう影響するか」を書く。

スキーマを手書きしない「トリック」

  1. エディタから対象のツール関数(ここでは get_current_datetime)をコピー。
  2. claude.ai で「Write a valid JSON schema spec for the purposes of tool calling for this function. Follow the best practices listed in the attached documentation.」+関数コードを貼る。
  3. 本当のトリック:Anthropic API ドキュメントの User Guide にある Tool use with Claude ページ(best practices と良い/悪い description の実例が満載)を全文コピーし、添付として貼って実行。
  4. 返ってきたスキーマをノートブックの関数直下に貼り付け。

命名規約と型付け

  • 命名パターン:ツール関数名+_schema(例 get_current_datetimeget_current_datetime_schema)。スキーマの対応関係を追いやすくする。
  • from anthropic.types import ToolParam して、辞書全体を ToolParam(...) でラップする。必須ではない(無くても動く)が、後でこのスキーマを使うときの型エラーを予防できる。

実務ポイント(このレッスンから持ち帰るもの)

  1. description の質がツール呼び出しの精度を決める。ツール本体・各引数とも「何をする/いつ使う/何を返す・何を制御する」を3〜4文で書く。1文の description は本番では不足。
  2. スキーマは公式ドキュメント同梱で AI に生成させるのが速くて正確:関数コード+「Tool use with Claude」ページ全文を添付して依頼→ほぼ完璧なスキーマが返る。
  3. <関数名>_schema 命名で関数とスキーマをペア管理する。ツールが増えても迷子にならない。
  4. ToolParam でラップして型安全に。SDK にスキーマを渡す段階での型エラーを未然に防ぐ(from anthropic.types import ToolParam)。
  5. JSON Schema は汎用のデータ検証仕様だと理解しておく——LLM 固有の呪文ではないので、既存のオンライン生成ツールや検証ツールがそのまま流用できる。

本文(時系列・日本語逐語+画面差し込み)

[0:00] ツール関数が組み上がったので、ステップ2に進みます。ここでは JSON スキーマを書き出していきます。最終的にはこの設定一式を Claude に送ることになります。Claude はそれを使って、利用可能なさまざまなツール関数と、それらのツール関数に必ず渡すべきさまざまな引数を理解します。

🖥 スライド(工程バー:Write a tool function(済・ベージュ)→ Write a JSON Schema(現在・オレンジ)→ Call Claude with JSON Schema → Run Tool → Add Tool Result and call Claude again)。見出し「Write a JSON Schema spec to describe your function」。右に黒いコードパネル:

{
  "name": "get_weather",
  "description": "Retrieves current weather…",
  "input_schema": {
    "type": "object",
    "properties": {
      "location": {
        "type": "string",
        "description": "The location for which…"
      }
    },
    "required": [
      "location"
    ]
  }
}

左の箇条書き:The JSON Schema helps Claude understand what arguments your function requires / JSON Schema is not just a LLM thing - this is a commonly used for data validation(There are tons of tools online that can help you generate a JSON Schema)/ Best practices:Explain what the tool does, when to use it, and what it returns ・Aim for 3 to 4 sentences ・Provide super detailed descriptions.

[0:17] ここで最初に理解してほしいのは、JSON スキーマとは正確には何なのか、ということです。右側に見えているこのオブジェクト全体——これは厳密に言うと JSON スキーマそのものではありません。そうではなく、一番上に name と description があります。これらが何なのかはもう少し後でお話しします。その下に input schema というキーがあります。そしてそれに割り当てられているのが、そこにある辞書です。今ハイライトした部分——それが技術的に JSON スキーマと呼ばれるものです。

🖥 スライド(注釈付き):吹き出し①「Name and description of this tool. Helps Claude understand when to use it(このツールの名前と説明。いつ使うべきかを Claude が理解する助けになる)」→ namedescription の行を囲む。吹き出し②「This part is the actual schema that describes the tool function's arguments.(この部分が、ツール関数の引数を記述する実際のスキーマ)」→ input_schema ブロックを囲む。

[0:44] もう少し背景をお話しします。JSON スキーマという考え方は、言語モデルやツール呼び出しなどに特に紐づいたものではありません。JSON スキーマはデータ検証の仕様です。つまり、あらゆる種類の JSON データを検証するために使えるルールの集合です。繰り返しますが、言語モデルやツールなどに特に結びついたものではないのです。言語モデルのコミュニティが、ある時点で「JSON スキーマはツール呼び出しを配線して処理するのに実に便利な方法だ」と決めた、ということです。これは何年も何年も前から存在する、広く理解された技術です。

[1:15] さて現時点では、JSON スキーマの仕様が何についてのものか、まだきちんと説明していませんでしたね。全体として、これは利用可能なさまざまなツールについて Claude に知らせるために使われます。ツールの name を提供します。この場合は get weather かもしれません。それからツールの description です。この description は、そのツールが何をするのか、いつ使うべきか、どんなデータを返すのかを Claude に伝えるためのものです。best practice は、3〜4文程度の長さの description を必ず用意することです。ここでは「retrieves current weather」だけを示していますが、実際には間違いなく、これよりずっと長い description を持たせたいところです。

[1:47] そして、この input schema キーの下に来るのが、実際の JSON スキーマ仕様です。これは、私たちの関数に渡されるべきさまざまな引数を記述します。あちらの例で言えば、location だけを受け取る get weather 関数があるなら、ちょうどここに location を入れた input schema を書きます。それは string である必要があり、そしてこれがその引数の目的の説明です。この引数の description も同じく3〜4文程度の長さにして、この引数が正確には何を制御し、関数呼び出し全体にどう影響するのかを Claude が理解できるようにしたいところです。

🖥 スライド(Step 1 の復習スライドが一瞬表示):「Tool Function」— Plain Python function that will be executed when Claude decides it needs some additional information to help the user / Best practices:Use well-named, descriptive arguments ・Validate the inputs, raising an error if they fail validation ・Return meaningful errors - Claude will try to call to use your function a second time! 右に def get_weather(location): のコード例(location 検証 → requests.get(url, params=params, timeout=10)response.json() を返す)。

[2:23] さて、申し上げたとおり、この設定を全部自分で書き出さないといけないと考えると、少し気が重いかもしれません。幸い、あなたがこれから組むすべてのツールについて、ほぼ完璧な JSON スキーマ仕様を書き出すのを助けてくれるトリックがあります。そのトリックをお見せしましょう。

[2:36] まず、エディタに戻って、私たちのツール関数を見つけます。私たちの場合は get current date time 関数です。次にこれを Claude のウィンドウに持っていきます。今 claude.ai にいます。とてもシンプルなプロンプトを書き出します。Claude に「この関数のための、ツール呼び出し用途の有効な JSON スキーマ仕様を書いて」と頼みます。そしてさらに「添付したドキュメントに記載されているベストプラクティスに従って」とも頼みます。

🖥 画面(VS Code / 001_tools.ipynb・「# Tools and Schemas」セクション):

def get_current_datetime(date_format="%Y-%m-%d %H:%M:%S"):
    if not date_format:
        raise ValueError("date_format cannot be empty")
    return datetime.now().strftime(date_format)


get_current_datetime("")

🖥 画面(claude.ai・新規チャット「Back at it, Stephen」。モデルは Claude 3.7 Sonnet)。入力欄:

Write a valid JSON schema spec for the purposes of tool calling for this function.
Follow the best practices listed in the attached documentation.

def get_current_datetime(date_format="%Y-%m-%d %H:%M:%S"):
    if not date_format:
        raise ValueError("date_format cannot be empty")
    return datetime.now().strftime(date_format)

[3:06] それから、このようにツール関数を入れます。そしてここからが本当のトリックです。Anthropic API ドキュメントに移動します。User Guide セクションに、Claude でのツール使用(Tool use with Claude)に関するページが丸ごとあります。このページ全体に、たくさんのベストプラクティスと、良いツール description・悪いツール description の実例が載っています。

🖥 画面(Chrome / docs.anthropic.com「Tool use with Claude」ページ):左ナビ「Build with Claude > Tool use (function calling) > Overview」。本文「Claude is capable of interacting with external client-side tools and functions…」、Messages API での tools 指定例(get_weather スキーマ入りの client.messages.create(...) コード)。右の目次に「How tool use works / Specifying tools / Best practices for tool definitions / Controlling Claude's output / Chain of thought / Parallel tool use …」。

[3:26] そこで、ここのテキストを全部コピーして、Claude のウィンドウに戻り、添付として貼り付けて、これを実行します。

🖥 画面(docs ページ全文が選択反転 → claude.ai に貼り付け、送信)。

[3:35] すると Claude は、おそらく非常に強力な JSON スキーマ仕様で応答してきます。そこで今度はこれをコピーして、エディタに持ち帰り、既存の get current date time 関数のすぐ下に貼り付けます。「get current date time schema」という名前にします、こんなふうに。

🖥 画面(ノートブック・貼り付け後のセル):

from anthropic.types import ToolParam

def get_current_datetime(date_format="%Y-%m-%d %H:%M:%S"):
    if not date_format:
        raise ValueError("date_format cannot be empty")
    return datetime.now().strftime(date_format)


get_current_datetime_schema = {
    "name": "get_current_datetime",
    "description": "Returns the current date and time formatted according to the specified format...",
    "input_schema": {
        "type": "object",
        "properties": {
            "date_format": {
                "type": "string",
                "description": "A string specifying the format of the returned datetime. Uses Python's ..."
            }
        }
    }
}

(description の全文は画面上で右に見切れている)

[3:58] ここで、私が好んで使うちょっとした命名パターンがあります。ツール関数には好きな名前を付けます。そしてそれに対応するスキーマは「アンダースコア schema」にします。つまり同じ名前+ _schema。こうすると、複数のスキーマを追跡管理するのがずっと楽になります。

[4:11] 最後にもう1つだけやります。このセルの先頭に、anthropic.types からの import を追加します。tool param(ToolParam)を import します。この tool param を、この辞書全体を囲むようにラップします。ちょうどここに tool param と開き括弧を入れて、一番下に閉じ括弧を入れます。

[4:31] この tool param を追加するのは、厳密には必須ではありません。言い換えると、なくてもコードは動きます。しかし、後でこのスキーマを実際に使うときに起きる型エラーを防いでくれるのです。

英語逐語(ASR全文)

Now that we've got our tool function put together, we are going to move on to step two, which is where we are going to write out a JSON schema. We are going to eventually send all this configuration off to Claude. Claude is going to use it to understand the different tool functions that are available and the different arguments that must be provided to these tool functions as well. The first thing I want you to understand here is exactly what JSON schema is all about. So this entire object you see on the right-hand side, this is not technically a JSON schema per se. Instead, at the very top, there is a name and a description. I'll tell you what those are about in just a little bit. Underneath that, there is a key of input schema. And then assigned to that is that dictionary right there. What I've now highlighted, that is technically what a JSON schema is. So again, let me give you a little bit more background. The idea of JSON schema is not specifically tied to language models or tool calling or anything like that. JSON schema is a data validation specification. So it is a set of rules that can be used to validate any kind of JSON data. So again, it is not specifically tied to language models or tools or anything like that. The language model community decided at some point in time that JSON schema is just a really convenient way of wiring up and handling tool calls. This is a widely understood technology that has been around for many, many years. Now, at this point, I still haven't really explained what the JSON schema spec is all about. So in total, this thing is used to inform Claude about the different tools that are available to it. We are going to provide a name for the tool. So in this case, it might be get weather and then a description for the tool. This description is meant to tell Claude what the tool does, when to use it, and what kind of data it is going to return. Best practice is to make sure you have a description around three to four sentences long. So even though right here I'm showing only retrieves current weather, in reality, I would definitely want to have a much longer description than this. Then under this input schema key is going to be the actual JSON schema spec. This is going to describe the different arguments that should be passed into our function. So in this example back over here, if I had a get weather function that received just a location, I would put in input schema with location right here. It needs to be a string. And here's a description of the purpose of that argument. Again, we would want this description of the argument to also be about three to four sentences long and help Claude understand exactly what this argument controls and how it affects the overall function call. Now, like I mentioned, it might be a little bit intimidating to think that you have to write out all this configuration on your own. Luckily, I've got a trick that is going to help you write out a almost perfect JSON schema spec for every tool you ever put together. So let me show you the trick. First, I'm going to go back over to my editor and I'm going to find our tool function. So for us, it is the get current date time function. I'm then going to take this over to a clod window. So I'm at clod.ai here. I'm going to write out a very simple prompt. I'm going to ask Claude to write a valid JSON schema spec for the purposes of tool calling for this function. And then I will also ask Claude to follow the best practices listed in the attached documentation. Then I'm going to put in my tool function like so. And then here's the real trick. I'm going to go over to the Anthropic API documentation. In the user guide section, there is a entire page on tool use with Claude. This entire page has a lot of different best practices and examples of good tool descriptions and bad tool descriptions. So I'm going to just copy all the text here, go back over to my Claude window, paste it in as an attachment, and run this. Claude is then going to respond with probably a very strong JSON schema spec. So I'm now going to copy this, take it back over to my editor, and I'm going to paste it in right underneath my existing get current date time function. So we'll say get current date time schema, like so. Now there's a little naming pattern that I like to use here. I'll give my tool function, whatever name I want. And then the schema to match up with it will be underscore schema. So the same name underscore schema, and it makes it a lot easier to keep track of my different schemas. There's one last thing I'm going to do. At the top of this cell, I'm going to add in an import from anthropic.types. I will import tool param. I'm going to take this tool param and wrap it around this entire dictionary. So I'll put in right here, tool param, opening parentheses, and then a closing parentheses down here at the bottom. Adding in this tool param thing is not strictly necessary. In other words, our code is still going to work without it, but it's going to prevent a type error later on when we eventually take this schema and make use of it.

↑ 目次へ戻る

Building with the Claude API | Lesson 31「Handling message blocks」(全文ナレッジ)

動画 5分44秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04

日本語ナレッジ(要点整理)

1. このレッスンの位置づけ — 5ステップ中の「Call Claude with JSON Schema」

  • ツール使用の全体プロセスは5ステップ(スライドのプロセスチェーンより):
Write a tool function → Write a JSON Schema → Call Claude with JSON Schema → Run Tool → Add Tool Result and call Claude again
  • このレッスンはステップ3。前レッスンまでに作った tool 関数(get_current_datetime)と JSON スキーマ(get_current_datetime_schema)を、ヘルパー関数(chat 等)を使わず手作業で messages.create に渡し、返ってくるレスポンスの構造を観察する。
  • ツールスキーマをリクエストに含めることで「Claude に利用可能なツールがある」と理解させる。

2. 手動リクエスト — tools キーワード引数にスキーマのリストを渡す

  • 空の messages リストを作り、user メッセージを手動 append → client.messages.createtools キーワード引数(リスト)でスキーマを渡す(画面転記):
messages = []

messages.append(
    {
        "role": "user",
        "content": "What is the exact time, formatted as HH:MM:SS?",
    }
)

response = client.messages.create(
    model=model,
    max_tokens=1000,
    messages=messages,
    tools=[get_current_datetime_schema],
)
response
  • tools は「作成した JSON スキーマ仕様すべて」のリスト。現時点では get_current_datetime_schema の1つだけ。

3. 返ってくるのは「これまで見たことのない構造」=マルチブロックメッセージ

  • 実行結果(画面転記・[9] 2.6s):
Message(id='msg_01YPMjWw5TFYK1bUc3afhKLM', content=[TextBlock(citations=None, text='I can get the current time formatted as HH:MM:SS for you. Let me do that right away.', type='text'), ToolUseBlock(id='toolu_01LHepccEeJdy4zdFQsmjndE', 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=624, output_tokens=87))
  • これまでのメッセージは「content リストの中にテキストブロック1つ」だったが、ツールを使うと content リストに2つ目のブロック= ToolUseBlock が入るstop_reason='tool_use' になっている点にも注目。

4. Multi-Block messages の構造(スライド「Multi-Block messages」)

  • 副題:「Blocks are also often referred to as “parts”(ブロックは “parts” と呼ばれることも多い)」。
  • メッセージは assistant か user のどちらか。テキストに加えて他の種類のデータもメッセージ内に格納できる。Claude がツールを使うと決めた時、Text Block と ToolUse Block の両方を含む assistant メッセージを返すことが非常に多い。
  • Text Block:何が起きているかユーザーに見せるための文章。例(スライド転記):「I can help you find out the current time. Let me find that information for you」
  • ToolUse Block:Claude がツールを使いたいという開発者向けのサイン。呼びたいツール関数の名前と、渡すべき入力(実質的な引数)が入る(スライド転記):
ToolUseBlock(
   id='toolu_01QtKMDNkU1FBj73NP2FbF8w',
   input={'date_format': '%H:%M:%S'},
   name='get_current_datetime',
   type='tool_use'
)
  • 次のステップは「適切なツールを見つけて実行する」だが、その前に content リストの複数ブロックをめぐる重要事項を先に処理する。

5. 重要事項 — Claude はステートレス、履歴は自前管理+毎回全部送る

  • 現状の整理(シーケンス図):サーバー → Claude に「ツールスキーマ付きの user メッセージ1つ」を送信 → 返ってきた assistant メッセージには Text Block と ToolUse Block の2ブロックが入っている。
  • Claude はメッセージ履歴も会話に関する何ものも保存しない。会話・履歴を維持したいなら手動で管理する。
  • したがって、ToolUse Block を受けて実際の関数を呼び、Claude に応答を返す時には、会話履歴全体を必ず含める(コース全体でやってきたのと同じ)。今回の唯一の違いは「複数ブロックを持つメッセージを扱う」こと。

6. アシスタントメッセージを手動で履歴に積む — content には response.content をそのまま

  • 一番下のセルの response を消して、返ってきた content ブロックの正確なリストを content に入れて append(画面転記):
messages.append({
    "role": "assistant",
    "content": response.content
})

messages
  • 実行結果([10] 3.5s・画面転記)— user の元メッセージ+ Text Block / ToolUseBlock 入りの assistant メッセージが履歴に正しく積まれている:
[{'role': 'user', 'content': 'What is the exact time, formatted as HH:MM:SS?'},
 {'role': 'assistant',
  'content': [TextBlock(citations=None, text="I'll get the current time for you in the HH:MM:SS format.", type='text'),
   ToolUseBlock(id='toolu_01NJdk5KQk3CxTr9EkcQxKpG', input={'date_format': '%H:%M:%S'}, name='get_current_datetime', type='tool_use')]}]

7. 次回予告 — ヘルパー関数の複数ブロック対応

  • add_user_message / add_assistant_message の2つのヘルパーは、現状単一のテキストブロックしかサポートしない前提で書かれている。このセクションの少し後で、複数ブロック入りメッセージをサポートできるように更新する(→ Lesson 33 で実施するリファクタの予告)。

実務ポイント(このレッスンから持ち帰るもの)

  • ツールの宣言は client.messages.create(..., tools=[schema, ...])スキーマのリストを渡すだけ。スキーマが増えたらリストに足す。
  • ツール要求時のレスポンスは content が複数ブロック(TextBlock + ToolUseBlock)になり、stop_reason='tool_use' が立つ。「content の先頭=テキスト」という従来の前提はここで崩れる。
  • TextBlock はユーザー向けのつなぎ文章、ToolUseBlock は開発者向けの実行指示書。ToolUseBlock の name が呼ぶべき関数、input がその引数、id が後の対応付けに使う識別子。
  • Claude は完全ステートレス。ツール結果を返す前に、まず受け取った assistant メッセージを履歴へ積む。その時 content には response.content(ブロックリストそのまま)を入れる——ブロックを間引いたりテキストだけ抜き出したりしない。
  • ツール結果を返すリクエストにも会話履歴全体を含める(毎ターン全履歴送信)。
  • プレーン文字列前提のヘルパー(add_user_message / add_assistant_message 等)は multi-block で破綻する。ツール導入時はヘルパーの content 取り扱いを必ず見直す。

本文(時系列・日本語逐語+画面差し込み)

[0:00] ステップ3に進みます。この JSON スキーマと何らかのユーザーメッセージを添えて、Claude を呼び出します。つまり私たちのサーバーから、これまでやってきたのと同じように Claude へリクエストを送るのですが、今回はこのツールスキーマも一緒に含めます。これによって Claude は、自分に利用可能なツールがあるということを理解できるようになります。

🖥 [0:00] 画面: シーケンス図スライド。上部に5ステップのプロセスチェーン(「Call Claude with JSON Schema」が強調色)。 Write a tool function → Write a JSON Schema → Call Claude with JSON Schema → Run Tool → Add Tool Result and call Claude again 下部は「Our Server」「Claude」のボックスと縦の破線ライフライン。両者を結ぶ右向き矢印の上に2つのメッセージボックス。 Tool Schema | JSON Schema. Helps Claude understand how to call the 'get_current_datetime' tool User Message | What's the current time? (左下に ANTHROP\C ロゴ)

[0:14] ノートブックに戻って、以前組み立てたヘルパー関数——例の chat 関数のような——を一切使わずに、このリクエストを手作業で作ってみましょう。

🖥 [0:22] 画面: スライドが横にスライドして退場し、VS Code の Jupyter ノートブック 001_tools.ipynb — 06(カーネル .venv (Python 3.9.6))へ画面遷移

[0:22] さて、戻ってきました。下へ行って新しいセルを作ります。空のメッセージリストを作ります。そこへ手動で、role が user、content が「正確な時刻は? 時・分・秒のフォーマットで」という新しいメッセージを append します。

🖥 [0:35] 画面: ノートブックの新しいセルに入力中。上のセル末尾にはスキーマ定義の終端("required": [], など)が見えている。 ```python messages = []

messages.append( { "role": "user", "content": "What is the exact t, formatted as HH:MM:SS?" } ) `` ("What is the exact t` の直後にカーソル=「time」を入力している途中)

[0:45] その下で、client の messages.create 関数への呼び出しを行います。client messages create から response を受け取ります。モデルを指定します。

[0:56] max tokens、メッセージのリスト、そして今回は、Claude に利用可能なツールがあることを伝えるこの JSON スキーマも含めたいのです。そのためには、tools というキーワード引数を含めます。これはリストになっていて、その中には、私たちが作成したすべての異なる JSON スキーマ仕様が入ります。現時点で、皆さんと私が作ったのは1つだけで、get current datetime schema という名前です。ですから、それを取ってきて、ちょうどここに入れます。

🖥 [1:07] 画面: 同セル・入力進行中。user メッセージは完成し、create 呼び出しの tools を入力している。 ```python messages.append( { "role": "user", "content": "What is the exact time, formatted as HH:MM:SS?" } )

response = client.messages.create( model=model, max_tokens=1000, messages=messages, tools=[] ) `` (tools=[]` の角括弧内にカーソル。この直後に get_current_datetime_schema が入る)

[1:26] それから一番下で response を出力して、これを実行して何が起きるか見てみましょう。ここで返ってくるレスポンスメッセージは、これまでちょっと見たことのない構造をしています。

🖥 [1:31] 画面: セル実行後([9]・✓ 2.6s)。セルは完成形。 python response = client.messages.create( model=model, max_tokens=1000, messages=messages, tools=[get_current_datetime_schema], ) response 実行出力(TextBlock の語が選択ハイライトされている): Message(id='msg_01YPMjWw5TFYK1bUc3afhKLM', content=[TextBlock(citations=None, text='I can get the current time formatted as HH:MM:SS for you. Let me do that right away.', type='text'), ToolUseBlock(id='toolu_01LHepccEeJdy4zdFQsmjndE', 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=624, output_tokens=87))

[1:35] これまで受け取ってきたメッセージはすべて、content フィールドにリストがあり、その中にテキストブロックが入っている、というものでした。そして以前から何度も述べてきた通り、テキストブロックの中には、実際にユーザーに表示したいテキストが入っています。しかしツールを使うようになった今、この content リストは少し違うものになります。content リストの中に、tool use block と呼ばれる2つ目のブロックがあることに気づくかもしれません。その構造全体が、ちょうどここにあります。

🖥 [1:56] 画面: 同じ実行出力で、今度は ToolUseBlock の部分全体が選択ハイライトされている。 ToolUseBlock(id='toolu_01LHepccEeJdy4zdFQsmjndE', input={'date_format': '%H:%M:%S'}, name='get_current_datetime', type='tool_use')

[1:59] この点が本当に明確になるように、図をお見せしましょう。さて、これが私たちにとって初めての、マルチブロックメッセージ(multi-block message)の体験です。思い出してください。メッセージとは、アシスタントメッセージかユーザーメッセージのどちらかです。

🖥 [2:03] 画面: ノートブックが左へスライドし、スライド「Multi-Block messages」へ画面遷移。 Multi-Block messages Blocks are also often referred to as “parts” 表:左列に「Assistant Message's Content List」、右に2行。 “Text” Block | “I can help you find out the current time. Let me find that information for you” “ToolUse” Block | ToolUseBlock( | id='toolu_01QtKMDNkU1FBj73NP2FbF8w', | input={'date_format': '%H:%M:%S'}, | name='get_current_datetime', | type='tool_use' | ) (上部のプロセスチェーンは「Call Claude with JSON Schema」が強調されたまま)

[2:11] メッセージの中には何らかの量のテキストが格納されていることが多く、それがこれまで常に見てきたものです。しかし単なるテキストに加えて、メッセージの中に格納できる他の種類のデータもあります。Claude がツールを使うことを決めたとき、Claude はテキストブロックとツール使用ブロックの両方を含むアシスタントメッセージを返してくることが、とても多いのです。

[2:29] テキストブロックは、何が起きているのかをユーザーが理解する助けになるように、ユーザーへ表示するための何らかのテキストという位置づけです。ですからこの場合、テキストブロックには「現在時刻を調べるお手伝いができますよ。その情報を探してきますね」のような内容が入っているかもしれません。

[2:44] そして、このテキストブロックに加えてあるのが、ツール使用ブロックです。このツール使用ブロックは、Claude がツールを使いたがっているという、私たち開発者に向けられたサインです。ツール使用ブロックには、Claude が呼び出したいツール関数の名前が列挙されます。この場合、Claude は私たちが組み立てた get current date time 関数を呼びたいのです。

[2:58] さらにそれに加えて、その関数に渡す必要がある入力——実質的には引数——も提供してくれます。この一連のプロセス全体の一部として次にやることは、適切なツールを見つけて、実際に実行することです。しかしその前に、複数のブロックを内包する content リストというこの考え方をめぐって、対処しておく必要のある本当に重要なことがあります。

🖥 [3:16] 画面: シーケンス図スライドへ遷移。「Our Server」「Claude」の間に、最初の送信だけが描かれている。 Tool Schema | Helps Claude understand how to call our tool functions User Message | What's the current time? (→ Claude への右向き矢印)

[3:16] さて、この時点での簡単な振り返りです。私たちはサーバーから Claude へリクエストを送りました。そしてそのリクエストには、ツールスキーマも含んだ単一のユーザーメッセージが入っていました。今やレスポンスが返ってきて、このレスポンスの中にはアシスタントメッセージがあり、その content リストの中には、テキストブロックとツール使用ブロックという2つの別々のブロックが入っています。

🖥 [3:33] 画面: 同シーケンス図に、Claude からの戻り矢印(←)とアシスタントメッセージが追加される。 Assistant Message | Text Block | Sure, I can look that up for you | ToolUse Block | ~Request to call a tool~

[3:38] さてここで、Claude について念を押しておきたいことがあります。Claude は、メッセージ履歴も、あなたが行っている会話に関するいかなるものも、一切保存しないということを思い出してください。Claude との会話や履歴を維持したいなら、手動で管理しなければなりません。そしてこれが意味するのは、最終的にこのツール使用ブロックを受け取って、最終的に何らかの実際の関数を呼び出すとき、私たちは最終的に Claude へ応答を返す必要がある、ということです。

[3:58] そしてそうするとき——ここが重要な部分ですが——このコース全体を通してやってきたのとまったく同じように、会話履歴全体を必ず含めるようにしなければなりません。ですから、このやり方については私たちはすでに考えを持っています。

🖥 [4:01] 画面: シーケンス図の完成形。下段に「次の送信」として履歴全体を含むリクエストが描かれ、→ Claude への太い矢印が付く。 Tool Schema | Helps Claude understand how to call our tool functions User Message | Text Block | Whats the current time? Assistant Message | Text Block | Sure, I can look that up for you | ToolUse Block | ~Request to call a tool~ User Message | ToolResult Block | ~Result of function~

[4:08] 今回唯一違うのは、複数のブロックを内部に持つかもしれないメッセージを、確実に扱えるようにする必要がある、という点です。では、これを手作業でどうやるかをお見せしましょう。そして最終的には、このセクションの少し後の方で、ヘルパー関数——具体的には add user message と add assistant message——へ戻り、複数のブロックを内部に持つメッセージをこれらがサポートできるようにしていきます。今のところ、これらは単一のテキストブロックしかサポートしていないからです。

🖥 [4:31] 画面: スライドが退場し、ノートブックへ画面遷移。一番下のセル([9]・✓ 2.6s 実行済み)が表示される。 ```python { "role": "user", "content": "What is the exact time, formatted as HH:MM:SS?", } )

response = client.messages.create( model=model, max_tokens=1000, messages=messages, tools=[get_current_datetime_schema], ) response `` 下に先ほどのMessage(id='msg_01YPMjWw5TFYK1bUc3afhKLM', ...)` の実行出力が見えている

[4:31] さて、これらのメッセージを管理するために、現在 response を受け取っている一番下のコードセルへ戻ります。response を取り、新しいアシスタントメッセージを必ずメッセージのリストへ append するようにします。そこで、そこにある response を削除して、messages.append と書きます。新しく role は assistant を入れ、そして content は、いま返ってきたレスポンスの中にある content ブロックの正確なリストにします。

[5:02] やるべきことは、response ドット——blocks ではなく content——を入れるだけです。はい、できました。

[5:08] それでは messages を出力してセルをもう一度実行すると、user があるのが確認できるはずです。あれが元のメッセージですね。そして今やアシスタントメッセージがあり、その中にはテキストブロックとツール使用ブロックが入っています。これで、収集しているこれらすべての異なるメッセージから、すべての異なるブロックを含めることによって、時間の経過とともに会話履歴を正しく組み上げられるようになりました。

🖥 [5:10] 画面: 新しいセル([10]・✓ 3.5s 実行済み)。 ```python messages.append({ "role": "assistant", "content": response.content })

messages 実行出力(※セル再実行のため TextBlock の文言とツール ID が先ほどと異なる): [{'role': 'user', 'content': 'What is the exact time, formatted as HH:MM:SS?'}, {'role': 'assistant', 'content': [TextBlock(citations=None, text="I'll get the current time for you in the HH:MM:SS format.", type='text'), ToolUseBlock(id='toolu_01NJdk5KQk3CxTr9EkcQxKpG', input={'date_format': '%H:%M:%S'}, name='get_current_datetime', type='tool_use')]}] ```

[5:32] 改めて、もう一度だけ念を押しておきます。私たちは最終的に、add user message と add assistant message というあの2つのヘルパー関数へ戻り、このような複数ブロックの扱いを考慮できるように更新することになります。

英語逐語(ASR全文)

On to step three, we're going to call Claude with this JSON schema and some user message. So from our server, we're going to make a request off to Claude just as we've been doing before, but now we're going to also include this tool schema. This helps Claude understand that there is a tool available to it.

Let's go back over to our notebook and we're going to try to make this request by hand without using any of those helper functions we previously put together, like the chat function.

Okay, so back over here, I'm going to go down and create a new cell. I'm going to make an empty list of messages. I'm going to append in there manually a new message with a role of user and a content of what is the exact time formatted as hour, minute, seconds.

Then underneath that, I'm going to do a call off to the client messages.create function. So I'll get a response from client messages create. I'll designate my model. my max tokens, my list of messages, and now we want to also include this JSON schema that tells Claude that it has a tool available to it. To do so, we'll include a tools keyword argument. This is going to be a list, and inside of it is going to be all the different JSON schema specs that we have created. At this point in time, you and I have only created one, and it's called get current datetime schema. So we're going to take that and put it in right here.

Then I'm going to print out response at the very bottom and let's run this and see what happens. We're going to get a response message back here that has a structure that we have not quite seen before. All the messages that we have ever received before would have a content field that has a list and inside there would be a text block. And as I've mentioned many times before inside the text block is the text that we actually want to display to the user. But now that we are making use of tools, this content list is going to be a little bit different. So you might notice that inside the content list, there's a second block called a tool use block. So here's that entire structure right here.

Let me show you a diagram just to make sure this thing is really clear. Okay, so this is our first experience with a multi-block message. Remember, a message is either an assistant message or a user message. We are often going to have some amount of text stored inside of a message, and that's what we've always seen previously. But in addition to just text, there are other types of data that can be stored inside of a message. When Claude decides to make use of a tool, it's very often going to send us back an assistant message that contains both a text block and a tool use block. The text block is intended to be some text that is displayed to the user to help them understand what is going on. So in this case, the text block might contain something like, I can help you find the current time. Let me find that information for you. Then in addition to this text block is the tool use block. This tool use block is assigned to you and I as developers that Claude wants to make use of a tool. The tool use block is going to list out the name of the tool function that it wants to call. So in this case, Claude wants to call the get current date time function we put together. And then it also provides some inputs or essentially arguments that we need to pass into that function. So the next thing we're going to do as a part of this entire process is to find the appropriate tool and actually run it. But before that, there's something really critical that we need to take care of around this idea of having a content list with multiple blocks inside of it

okay so here's just a quick reminder for you at this point in time we have made a request from our server off to claude and in that request we had a single user message that also included a tool schema we have now gotten a response back and inside this response there's the assistant message and it has two separate blocks inside the content list a text block and a tool use block

Now there's something I want to remind you about Claude. Remember that Claude does not store any message history or anything about the conversation you are having. If you ever want to maintain a conversation or a history with Claude, you have to manage it manually. And what this means is that when we eventually take this tool use block and eventually call some actual function, we need to eventually respond back to Claude. And when we do so, here's the critical part, we need to make sure we include the entire conversation history, just as we've been doing throughout the course. So we already have an idea of how to do this. The only difference this time around is that we need to make sure that we deal with messages that might have multiple blocks inside them. So let me show you how we would do this by hand. And then eventually a little bit later on inside this section, we are going to go back to our helper functions, specifically add user message and add assistant message, and make sure that these can support messages that have multiple blocks inside them. Because right now they only support single text blocks.

okay so to manage these messages i'm going to go back down to our bottom code cell where we are currently getting our response i'm going to take our response and make sure i append in a new assistant message to our list of messages so i'm going to delete response right there we'll say messages.append i'm going to put in a new role of assistant and then our content is going to be the exact list of content blocks out of the response we just got back. So all we have to do is add in response dot, not blocks, but content. There we go.

So now if I print out messages and run the cell again, we should see that we end up with our user. So that's our original message right there. We now have our assistant message and inside there is a text block and the tool use block. So now we are correctly building up our conversation history over time. by including all the different blocks from all these different messages that we are collecting. So once again, I just want to remind you that we are going to eventually have to go back to the add user message and add assistant message, those two helper functions, and update them to account for dealing with multiple blocks like this.

↑ 目次へ戻る

Building with the Claude API | Lesson 32「Sending tool results」(全文ナレッジ)

動画 9分24秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04

日本語ナレッジ(要点整理)

1. このレッスンの位置づけ — 5工程のうち Step 4「Run Tool」と Step 5「Add Tool Result and call Claude again」

  • ツール使用の全体工程はスライド上部の5ステップチェーンで示される:
Write a tool function → Write a JSON Schema → Call Claude with JSON Schema → Run Tool → Add Tool Result and call Claude again
  • 前レッスン(Step 3)で Claude から ToolUseBlock 入りのレスポンスstop_reason='tool_use')を受け取った。本レッスンは Step 4(要求されたツール関数を実際に実行)と Step 5(結果を tool_result ブロックとして Claude に送り返し、最終回答を得る)を完走する。

2. Step 4 — ToolUseBlock から引数を取り出してツール関数を実行

  • response.content リストの2番目(添字1)が ToolUseBlock。その .input が「Claude が渡してほしいと要求している引数の辞書」:
response.content[1].input
# → {'date_format': '%H:%M:%S'}
  • ツール関数 get_current_datetime は辞書ではなく キーワード引数 date_format を受け取る。辞書を kwargs に展開して適用するには ** アンパック:
get_current_datetime(**response.content[1].input)
# → '14:41:05'
  • .input.id へのアクセスで 型エラー(type error)が出るが、この動画では無視。すぐ後(次段階)で全部修正するので、今は気にしなくてよい。

3. Tool Result Block — 定義と4つのキー(スライド転記)

  • スライド「Tool Result Block」の箇条書き:
・Placed in the 'content' list of a user message
・Communicates the results of running a tool function back to Claude
・"tool_use_id" - Must match the id of the ToolUse block that this ToolResult corresponds to
・"content" - Output from running your tool, serialized as a string
・"is_error" - True if an error occurred
  • 例示 JSON(スライド右):
{
  "tool_use_id": "toolu_01BEbi7q7qz",
  "type": "tool_result",
  "content": "12:47:13",
  "is_error": False
}
  • ポイント:tool_result は user ロールのメッセージの content リストに置く。ツール関数から返ってきたものを、そのまま Claude に「食わせ直す」ためのブロック。

4. tool_use_id の存在理由 — calculator 並列2呼び出しの思考実験

  • 数式を評価する calculator ツールを想定。ユーザーが「What's 10 + 10? Also, what's 30 + 30?」と聞くと、Claude は 1つのアシスタントメッセージの中に ToolUse ブロックを2つ入れて返してくることがある(スライド「There might be multiple 'ToolUse' blocks!」):
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')
  • サーバー側は calculator を2回実行し、フォローアップの user メッセージに tool_result ブロックを2つ入れて返す。このとき Claude は「どの結果がどの要求のものか」を並び順に頼らず ID で照合する:
ToolUse One  → ID: ab3 / input: 10 + 10
ToolUse Two  → ID: po9 / Input: 30 + 30
ToolResult One → ID: po9 / Output: 60
ToolResult Two → ID: ab3 / Output: 20
  • つまり tool_use_id は「ツール使用リクエスト」と「ツールリザルト出力」を結び付けるためのもの。順序が入れ替わっても ID が合っていれば正しく対応づく。

5. content と is_error

  • content:ツール関数の出力。数値・辞書・リストでも文字列化して入れる(通常はプレーンな JSON に変換するだけ)。
  • is_error:ツール実行で問題が起きたら True。デフォルトは常に False(明示は必須ではないが、講師は明示的に入れた)。

6. Step 5 実装 — tool_result 付き user メッセージを履歴に追記(ノートブック転記)

  • 実行結果を変数に取り、追記セルを組む:
result = get_current_datetime(**response.content[1].input)
messages.append({
    "role": "user",
    "content": [
        {
            "type": "tool_result",
            "tool_use_id": response.content[1].id,
            "content": result,
            "is_error": False
        }
    ]
})
  • tool_use_id は履歴中の ToolUseBlock の idtoolu_01BEbi7q7qzvwP8zSMBHp3BD)と一致させるため、response.content[1].id を直接参照する(ここでも型エラーは無視)。
  • 追記後に messages を出力すると、会話履歴の3点セットが揃う:①元の user 質問 → ②Claude の tool_use 入り assistant メッセージ → ③tool_result 入り user メッセージ:
[{'role': 'user', 'content': 'What is the exact time, formatted as HH:MM:SS?'},
 {'role': 'assistant',
  'content': [TextBlock(citations=None, text="I'll retrieve the current time for you formatted as HH:MM:SS.", type='text'),
   ToolUseBlock(id='toolu_01BEbi7q7qzvwP8zSMBHp3BD', input={'date_format': '%H:%M:%S'}, name='get_current_datetime', type='tool_use')]},
 {'role': 'user',
  'content': [{'type': 'tool_result',
    'tool_use_id': 'toolu_01BEbi7q7qzvwP8zSMBHp3BD',
    'content': '15:04:22',
    'is_error': False}]}]

7. フォローアップリクエスト — tools(スキーマ一覧)を毎回同梱する

  • 全履歴を積んだ messages を再度 Claude に送る。ツール使用を含むフォローアップでは、もうツールを使わない見込みでも元のツールスキーマを必ず含める。履歴中の tool_use ブロックと tool_result ブロックがそのツールを参照しているため、Claude にツールの存在を伝え続ける必要がある:
client.messages.create(
    model=model,
    max_tokens=1000,
    messages=messages,
    tools=[get_current_datetime_schema]
)

8. 最終レスポンス — TextBlock のみ・stop_reason='end_turn'

  • 実行すると、tool_result で与えた入力を活用した最終回答が返る(テキストブロック1つだけ/stop_reason='end_turn'):
Message(id='msg_01AQjmLxwL9BmXaE9bWfuFi4', content=[TextBlock(citations=None, text='The current time is 15:04:22.', type='text')], model='claude-3-7-sonnet-20250219', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(cache_creation_input_tokens=0, cache_read_input_tokens=0, input_tokens=720, output_tokens=15))

9. 全体レビュー — ツール使用ワンサイクルの完成形

  1. ツール関数を書く → それを記述するツールスキーマを書く(Claude に「使えるツールと呼び方」を理解させる。以後すべてのリクエストに同梱)。
  2. Claude は Text Block(ユーザー表示用・状況説明)+ ToolUse Block(呼びたいツール名と入力引数)の2ブロック入り assistant メッセージを返す。
  3. サーバー側でツール関数を実行。
  4. フォローアップリクエスト=会話履歴全体+ツールスキーマ一覧。最後のメッセージは tool_result ブロック入り user メッセージ(ツール実行結果を Claude に通知)。
  5. Claude はテキストブロックのみの assistant メッセージで最終回答を返す。

実務ポイント(このレッスンから持ち帰るもの)

  • tool_result は user ロールで返す{"type": "tool_result", "tool_use_id": ..., "content": ..., "is_error": ...} を user メッセージの content リストに入れ、assistant の tool_use と対で履歴に積む。
  • tool_use_id は対応する ToolUseBlock の id と完全一致必須。複数ツールの並列要求時、Claude は並び順ではなく ID で結果を照合する——順序保証に頼らない設計が公式の意図。
  • content は文字列にシリアライズして入れる。数値・辞書・リストは通常プレーン JSON 化するだけでよい。
  • is_error はエラー時 True(デフォルト False)。ツール実行失敗を Claude に伝える正規の経路。
  • 引数の受け渡しは response.content[i].input(辞書)→ ツール関数へ ** アンパックで kwargs 適用が定石(関数側はキーワード引数で定義されているため)。
  • フォローアップにも tools を毎回同梱。履歴中の tool_use / tool_result がスキーマを参照するため、「もう使わないだろう」場面でも省略不可。
  • 最終回答かどうかのサイン:ツール要求時は stop_reason='tool_use'、最終回答は stop_reason='end_turn'+TextBlock のみ。
  • SDK の型エラー(.input / .id アクセス)は本動画では意図的に無視。すぐ後で型を修正する前提なので、写経時に型警告が出ても止まらなくてよい。

本文(時系列・日本語逐語+画面差し込み)

[0:00] ステップ4へ進みます。ここでは、Claude が実行するよう要求してきたツール関数を実際に実行します。思い出してください。前のステップで、私たちはツール使用ブロック(tool use block)を含むレスポンスを Claude から受け取りました。それをノートブックの中でさっと出力してみましょう。

🖥 [0:02] 画面: スライド「Run Tool using the 'ToolUse' block」(上部の工程チェーンは「Run Tool」段階が強調)。表の転記: Assistant Message's Content List | "Text" Part | "I can help you find out the current … Let me find that information for y…"(右端見切れ) | "ToolUse" Block | ToolUseBlock( | | id='toolu_01QtKMDNkU1FBj73NP2FbF8w…'(右端見切れ), | | input={'date_format': '%H:%M:%S'}, | | name='get_current_datetime', | | type='tool_use' | | ) (約0:10で VS Code の Jupyter ノートブック 001_tools.ipynb — 06(カーネル .venv (Python 3.9.6))へ画面遷移)

[0:11] ということで、こちらに戻ってきました。この response 変数があるのを覚えていますね。下へ行って新しいセルを作り、response を出力します。

🖥 [0:14] 画面: ノートブック。前レッスンで組んだセルと messages の出力([13]・2.8s)が見えている。 ```python response = client.messages.create( model=model, max_tokens=1000, messages=messages, tools=[get_current_datetime_schema], )

messages.append({"role": "assistant", "content": response.content})

messages 出力: [{'role': 'user', 'content': 'What is the exact time, formatted as HH:MM:SS?'}, {'role': 'assistant', 'content': [TextBlock(citations=None, text="I'll retrieve the current time for you formatted as HH:MM:SS.", type='text'), ToolUseBlock(id='toolu_01BEbi7q7qzvwP8zSMBHp3BD', input={'date_format': '%H:%M:%S'}, name='get_current_datetime', type='tool_use')]}] ```

[0:18] さて、この中には content プロパティを持つメッセージが返ってきていて、そのリストの中の2番目のブロックがツール使用ブロックです。そこにアクセスするには、response.content の添字1を参照します。

🖥 [0:20] 画面: 新しいセルで response を出力([17]・0.0s)。実行出力: Message(id='msg_01FSdTJyHxjBvkWNoTedBWTF', content=[TextBlock(citations=None, text="I'll retrieve the current time for you formatted as HH:MM:SS.", type='text'), ToolUseBlock(id='toolu_01BEbi7q7qzvwP8zSMBHp3BD', 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=624, output_tokens=80))

[0:30] するとこの中に input フィールドが与えられています。これは、get current date time 関数に渡してほしいと Claude が要求している入力、つまり引数です。そこにある辞書を取得するには、.input をチェーンでつなげます。

🖥 [0:35] 画面: response.content[1].input のセルを実行([19]・0.0s)。.input の下に型エラーの赤い波線。 python response.content[1].input 出力: {'date_format': '%H:%M:%S'}

[0:42] さて、.input を付け足すと、ここで型エラーが出るかもしれません。この動画に限っては型エラーは無視することにします。すぐに、本当にすぐに戻ってきて、型エラーはすべて直します。ですから、もしこの動画の中で型エラーを見かけても、まったく問題ありません。今のところはただ無視してください。

[0:57] さて、次の目標は、ここにあるこの辞書を取り出して、私たちの get current date time 関数に与えることです。ここで1つだけ注意点があります。思い出してください、私たちの get current date time 関数は——この上の方で定義されていますが——辞書を受け取りません。date format というキーワード引数を受け取るのです。

[1:14] そこで、あの辞書をキーワード引数の並びに変換して関数に適用するには、こうします。get current date time を呼び、アスタリスク2つ(**)、そして response content 1 の input と書きます。これを実行すると、現実世界の現在日時が返ってきます。私の場合は2時41分、つまり14時41分です。直す必要のある型エラーを別にすれば、結局これはかなり簡単に済みました。

🖥 [1:25] 画面: ** アンパックで関数へ適用するセルを実行([20]・0.0s。response.content[1].input 部分に赤の波線)。 python get_current_datetime(**response.content[1].input) 出力: '14:41:05'

[1:35] では、ステップ5に進みましょう。ステップ5では、フォローアップのリクエストを Claude に送り返します。このリクエストには、私たちの会話履歴の全体を含めます。つまり、元のユーザーメッセージがあり、先ほど処理した、ツール使用ブロック入りのアシスタントメッセージがあり、そして一番最後に、もう1つ別のユーザーメッセージを追記します。

🖥 [1:38] 画面: スライドへ遷移。上部の5工程チェーンと Our Server / Claude のシーケンス図。 Write a tool function → Write a JSON Schema → Call Claude with JSON Schema → Run Tool → Add Tool Result and call Claude again 図の転記: Assistant Message | Text Block | Sure, I can look that up for you (← Claude) | ToolUse Block | ~Request to call a tool~ Tool Schema | Helps Claude understand how to call our tool functions User Message | Text Block | Whats the current time? Assistant Message | Text Block | Sure, I can look that up for you | ToolUse Block | ~Request to call a tool~ User Message | ToolResult Block | ~Result of function call~ (→ Claude)

[1:55] このユーザーメッセージには、これまで見たことのない新しい種類のブロック、ツールリザルトブロック(tool result block)と呼ばれるものが入ります。このブロックがどう機能するかを説明しましょう。ツールリザルトブロックは、ユーザーメッセージの中に置かれます。ツールリザルトには、ツールを実行した結果が入ります。つまり本質的には、先ほど呼び出したツール関数から返ってきたものをそのまま取り出して、Claude にそのまま送り返してやるのです。

🖥 [2:03] 画面: スライド「Tool Result Block」。左に箇条書き、右に黒背景のコード。 箇条書き: ・Placed in the 'content' list of a user message ・Communicates the results of running a tool function back to Claude ・"tool_use_id" - Must match the id of the ToolUse block that this ToolResult corresponds to ・"content" - Output from running your tool, serialized as a string ・"is_error" - True if an error occurred json { "tool_use_id": "toolu_01BEbi7q7qz", "type": "tool_result", "content": "12:47:13", "is_error": False }

[2:16] ツールリザルトブロックの中には、いくつかの異なるキーが入ります。そして、これらのキーが何をしているのかを正確に理解しておくことが重要です。最初のものは、おそらく理解が一番厄介な、tool use ID です。tool use ID が何なのか、ごく手短に説明させてください。

[2:29] ちょっとの間だけ想像してほしいのですが、calculator という新しいツールを作ったとします。このツールは数式を評価するためのものです。そしてユーザーが「10足す10は? ついでに、30足す30は?」のようなメッセージを送ってきたとしましょう。この場合、Claude は calculator ツールに対して2つの別々の呼び出しを行いたくなるかもしれません。10 + 10 を解くための呼び出しが1つ、30 + 30 を解くための2つ目です。

🖥 [2:33] 画面: calculator 例のシーケンス図(Our Server → Claude への矢印)。 Tool Schema | Schema for a 'calculator' tool that evaluates math expressions User Message | What's 10 + 10? | Also, what's 30 + 30?

[2:50] そのために、Claude は複数のツール使用ブロックを内部に持つアシスタントメッセージで応答してきます。そうすると、ここにツール使用ブロック1があり、評価すべき式は 10 + 10。そして2つ目には、30 + 30 という式が入っているかもしれません。私たちは calculator ツールを2回実行することになります。こちらの式に1回、そしてこちらの式に1回です。

🖥 [2:52] 画面: スライド「There might be multiple 'ToolUse' blocks!」。 Assistant Message's Content List ├ 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' | )

[3:11] その後、フォローアップリクエストを Claude に送り返します。フォローアップリクエストの中には、2つの別々のツールリザルトブロックを持つユーザーメッセージを追加します。つまり、ツールリザルト1とツールリザルト2があるわけです。

🖥 [3:15] 画面: ID 対応を示すシーケンス図。 Assistant Message | ToolUse One | Tool name: calculator / ID: ab3 / input: 10 + 10 (← Claude) | ToolUse Two | Tool name: calculator / ID: po9 / Input: 30 + 30 User Message | ToolResult One | ID: po9 / Output: 60 (→ Claude) | ToolResult Two | ID: ab3 / Output: 20

[3:25] さて、これらを Claude に送り返すとき、Claude はどの結果がどのリクエストに属するのかを判別できる必要があります。リクエスト1とリクエスト2、そして結果1と結果2があるわけですから。Claude は、これらが同じ順序で並んでいることだけには頼りたくないのです。代わりに、ID を活用します。

[3:40] 元のツール使用の中を見ると、この上の方に AB3 という ID があります。そして2つ目には PO9 という ID があります。Claude に送り返すフォローアップリクエストの中では、この下に書く ID が出力と対応していることを確実にする必要があります。言い換えると、PO9 が 30 + 30 に紐づいているのなら、この下では PO9 が 60 に紐づいているようにしたい。同様に、10 + 10 の AB3 は、出力 20 の AB3 に紐づけたいのです。これが tool use ID のすべてです。ツール使用のリクエストとツールリザルトの出力を結び付ける手助けをしてくれるのです。

🖥 [3:42] 画面: 同じ ID 対応図で、ToolUse Two の「ID: po9」がテキスト選択でハイライトされる(並び順でなく ID で照合する説明に対応)

[4:13] このほかに知っておくべきプロパティは content です。これは、ツール関数から得られる出力そのものになります。ツール関数が数値や辞書やリストのようなものを返す場合でも、それを文字列に変換します。普通は、単純にプレーンな JSON に変換するだけです。

🖥 [4:14] 画面: スライド「Tool Result Block」再掲(content / is_error の説明中。内容は [2:03] と同一)

[4:28] そして最後に、オプションで is error フィールドを入れることもできます。ツール関数の実行で何か問題が起きた場合、これを true に設定します。デフォルトでは、常に false です。

[4:38] さて、このツールリザルトブロックが何なのか、だいぶ分かってきたところで、次に何をすべきかを手短におさらいしておきます。私たちはちょうど、ツールを実行するよう求めるアシスタントメッセージを Claude から受け取りました。ツール使用ブロックが中に入っていたので、ツールの実行を求めていると分かりました。そして、提供された引数でツールを実行しました。ツール関数の結果が手に入った今、Claude へフォローアップリクエストを行う必要があります。

🖥 [4:45] 画面: おさらい用のシーケンス図再掲(5工程チェーン付き。内容は [1:38] と同一)

[5:02] このリクエストの中には、メッセージ履歴の全体を含めます。つまり、元のユーザーメッセージ。ツール使用ブロック入りのアシスタントメッセージ。そしてここに、追加のメッセージを1つ、ツールリザルトブロックを持つユーザーメッセージを追記します。それが先ほど説明したものです。つまり、ここにあるこれ、このツールリザルトブロックの中に、実際の関数呼び出しの結果が入ることになります。

🖥 [5:15] 画面: 同図の最下段「User Message | ToolResult Block | ~Result of function call~」の行が選択枠でハイライトされる

[5:24] では、ノートブックに戻りましょう。メッセージのリストを取得して、ツールリザルトブロックを内部に持つこの新しいユーザーメッセージを追加していきます。

[5:32] よし。ノートブックの中に戻ってきました。メッセージのリストを取得します。role が user の新しいメッセージを追記します。そして content のリストには、ブロックをちょうど1つだけ入れます。ツールリザルトブロックです。

🖥 [5:40] 画面: ノートブック(約5:25にスライドから遷移)。'14:41:05' の出力の下で追記セルを入力中。 ```python messages.append({ "role": "user", "content": [

]

}) ```

[5:47] そこで type には tool アンダースコア result を与え、tool use ID には、ここにあるこのツール使用ブロックの ID と一致するものを与えます。あそこにある ID にアクセスするには、response content 1 の .id を参照します。そしてこれを入れると、またしても型エラーが出ます。今のところ型エラーはまったく問題ないんでしたね。無視しておいて、すぐ後で直します。

🖥 [5:58] 画面: messages 出力内の ToolUseBlock の id toolu_01BEbi7q7qzvwP8zSMBHp3BD が選択ハイライトされ、その下のセルでブロックを入力中(.id に型エラーの赤い波線)。 python messages.append({ "role": "user", "content": [ { "type": "tool_result", "tool_use_id": response.content[1].id, "" } ] })"" はこの直後に "content" キーとして入力が続く途中の状態)

[6:14] 次に content を入れます。これは、ここにある私の関数を呼び出した結果になります。そこで、get current date time を呼び出した結果を——そうですね、単に result という変数に代入することにしましょう。それを再実行します。そして content には result を参照させます。それから最後に、この関数を実行したとき、エラーはありませんでした。なので is error に false を入れます。これはデフォルト値なので厳密には必要ありませんが、とにかく入れておきます。

🖥 [6:35] 画面: result への代入セルと、完成した追記セル(False が選択ハイライト)。 python result = get_current_datetime(**response.content[1].input) python messages.append({ "role": "user", "content": [ { "type": "tool_result", "tool_use_id": response.content[1].id, "content": result, "is_error": False } ] })

[6:44] よし。これでメッセージのリストが更新できたので、すべて正しくできているかを確かめるために、リストを出力してみます。これで、会話履歴の全体がここに揃いました。元のユーザーリクエストがあります。ツールを使ってほしいという Claude のリクエストがあります。そして今、ツールリザルトブロックを内部に持つ新しいユーザーメッセージが追記されています。

🖥 [6:50] 画面: messages を出力した会話履歴の全体(末尾に tool_result 入り user メッセージ。content は再実行時刻の '15:04:22')。 [{'role': 'user', 'content': 'What is the exact time, formatted as HH:MM:SS?'}, {'role': 'assistant', 'content': [TextBlock(citations=None, text="I'll retrieve the current time for you formatted as HH:MM:SS.", type='text'), ToolUseBlock(id='toolu_01BEbi7q7qzvwP8zSMBHp3BD', input={'date_format': '%H:%M:%S'}, name='get_current_datetime', type='tool_use')]}, {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_01BEbi7q7qzvwP8zSMBHp3BD', 'content': '15:04:22', 'is_error': False}]}]

[7:05] さて、最後にやるべきことは、このメッセージのリストを Claude に送り返すことです。一番下にもう1つコードセルを追加します。そして再び client messages create を、私のモデル、max tokens、メッセージのリストとともに呼び出します。

🖥 [7:15] 画面: フォローアップ呼び出しのセルを入力中(tools はまだ未指定)。 python client.messages.create( model=model, max_tokens=1000, messages=messages, )

[7:22] そして、ツール使用を含むフォローアップリクエストを行うときは常に、元のツールスキーマも含める必要があります。ここではおそらくツールをもう使わないにもかかわらず、それでもこのツールの存在を Claude に伝えなければなりません。なぜなら、ここにあるツール使用ブロックの中でも、そしてここにあるツールリザルトブロックの中でも、このツールを参照しているからです。ですから、ツールのリスト——get current date time schema を入れたリスト——を引き続き必ず含めるようにします。これで完了のはずです。では、これを実行してみましょう。Claude から最終レスポンスが返ってくるはずです。

🖥 [7:45] 画面: tools=[] を書き足している途中。上の messages 出力では 'type': 'tool_result','tool_use_id': 'toolu_01BE…' の行が選択ハイライト(=履歴がツールを参照しているからスキーマ同梱が必要、の説明に対応)。 python client.messages.create( model=model, max_tokens=1000, messages=messages, tools=[] )

[7:54] ほら、出ました。これが私たちの最終レスポンスです。「The current time is 15:04(現在時刻は15時04分です)」というテキストブロックがあります。さて、これでツール呼び出しの成功です。全工程を通り抜けたわけです。ごく手短に振り返りをしましょう。

🖥 [7:56] 画面: 完成形のセルを実行(0.9s)した最終レスポンス。 python client.messages.create( model=model, max_tokens=1000, messages=messages, tools=[get_current_datetime_schema] ) 出力: Message(id='msg_01AQjmLxwL9BmXaE9bWfuFi4', content=[TextBlock(citations=None, text='The current time is 15:04:22.', type='text')], model='claude-3-7-sonnet-20250219', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(cache_creation_input_tokens=0, cache_read_input_tokens=0, input_tokens=720, output_tokens=15))

[8:07] すべては、ツール関数を書き出し、次にそれを記述するツールスキーマを書くところから始まりました。ツールスキーマの目的は、Claude が自分の使えるさまざまなツールと、それらの異なるツールを実際にどう呼び出すかを理解する手助けをすることでした。ここから先、私たちが行うすべてのリクエストに、そのツールスキーマを含めなければなりません。だからこそ、ここにも、そしてこの下にも表示しているのです。

🖥 [8:06] 画面: レビュー用のフルシーケンス図(Our Server ↔ Claude)。 Tool Schema | Helps Claude understand how to call our tool functions User Message | What's the current time? (→ Claude) Assistant Message | Text Block | Sure, I can look that up for you | ToolUse Block | ~Request to call a tool~ (← Claude) Tool Schema | Helps Claude understand how to call our tool functions User Message | Text Block | Whats the current time? Assistant Message | Text Block | Sure, I can look that up for you | ToolUse Block | ~Request to call a tool~ User Message | ToolResult Block | ~Result of function~ (→ Claude)

[8:25] Claude が私たちに応答してきたとき、内部に2つの別々のブロックを持つアシスタントメッセージを送り返してきました。ここにテキストブロック、そしてここにツール使用ブロックです。テキストブロックはユーザーに表示するためのもので、ユーザーが何が起きているのかを理解できるようにします。そしてツール使用ブロックには、Claude が呼び出したいツールについての情報が含まれます。つまり、呼び出したいツールの名前が、それへの入力引数と一緒に含まれているのです。

🖥 [8:40] 画面: 同図で ToolUse Block の「~Request to call a tool~」がテキスト選択でハイライト(呼びたいツール名+入力引数の説明に対応)

[8:51] それから、私たちのサーバー側でツール関数を実行し、その後フォローアップリクエストを Claude に送り返しました。フォローアップリクエストには、会話履歴の全体と、あわせてツールスキーマのリストも含まれていました。リクエスト内の最後のメッセージは、ツールリザルトブロックを含むユーザーメッセージでした。ツールリザルトブロックは、何らかのツール関数を実行した結果について Claude に知らせるために使われます。そこでこのブロックの中に、Claude が本当に求めていた現在時刻を入れました。すると Claude は、テキストブロックだけを持つアシスタントメッセージという最終結果を1つ送ってきました。そしてそれは、このツールリザルトブロックを通して私たちが与えた入力を活用していたのです。

英語逐語(ASR全文)

On to step four, where we run the tool function that Claude requested us to run. Remember, in the last step, we got back a response from Claude that included a tool use block. Let's print that out inside of our notebook really quickly.

So back over here, remember we've got this response variable. I'm going to go down, create a new cell, and print out response. Now inside of here, we get back a message that has a content property, and the second block inside that list is a tool use block. So to access that thing, I would do response content at one. Then inside of here, we're given a input field. This is the input or the arguments that Claude is requesting that we pass into the get current date time function. So to get that dictionary right there, we would chain on a dot input.

Now, when we add on the dot input, you might end up getting a type error here. We're going to ignore type errors just in this video, and we're going to come back very, very quickly and fix all the type errors. But just, you know, if you see any type errors in this video, totally fine. Just ignore them for right now.

So now our goal is to take this dictionary right here and provide it to our get current date time function. Just one thing to be aware of here. Remember our get, get current date time function. Defined back up here. Doesn't take in a dictionary. It takes in a keyword argument of date format. So to convert that dictionary into a list of keyword arguments and apply them to the function, here's what we would do. I'm going to call it get current date time, star star, and then response content one input. And now if I run that, I'll get back my current date time in the real world. So for me, it's 2 41 or 14 41 besides the type error that we need to fix up that ended up being pretty easy.

So let's now move on to step five. So in step five, we are now going to send a follow-up request back to Claude. This request is going to include our full conversation history. So we'll have our original user message. It will have the assistant message that we just processed with the tool use block. And now we are going to append on to the very end, another user message. This user message is going to have a new kind of block that we have not seen before called the tool result block. Let me tell you about how this block works.

The tool result block is going to be placed inside of a user message. The tool result is going to contain the result of running a tool. So we're essentially taking whatever we get back from the tool function that we just called, and we are feeding it right back into Claude. The tool result block is going to have a couple of different keys inside of it. And it's important for you to understand exactly what these keys are doing. The first one is probably the trickiest to understand, the tool use ID. So let me very quickly tell you what the tool use ID is all about.

I want you to imagine for just a moment that we create a new tool called calculator. And this tool is meant to evaluate a math expression. And then maybe a user sends in a message of something like, what's 10 plus 10? Also, what's 30 plus 30? In this case, Claude might want to make two separate calls to the calculator tool. One call to solve 10 plus 10 and a second to solve 30 plus 30. To do so, Claude will respond with an assistant message that has multiple tool use blocks inside of it. So we might have tool use block one right here, where the expression that we need to evaluate is 10 plus 10. And then in the second, we might have an expression of 30 plus 30. We would then execute our calculator tool twice, once for this expression and once for this expression.

And then we would send a follow-up request back to Claude. Inside of our follow-up request, we would add in a user message that has two separate tool result blocks. So we'd have to result one and to result two. Now when we send these back into Claude, Claude needs to be able to figure out which result belongs with which request. So we've got request one and request two, and then result one and result two. Claude doesn't want to just rely upon these things being ordered in the same order. Instead, it's going to make use of IDs. So inside the original tool use, we have an ID up here of AB3. And then in the second, we have a ID of PO9. Inside of our follow-up requests that we send back to Claude, we need to make sure that the IDs that we put down here match up with the output. So in other words, PO9 being tied to 30 plus 30, then we would want to make sure that we have PO9 tied to 60 down here. And likewise, AB3 with 10 plus 10, we'd want to have that tied to AB3 with an output of 20. So that's what the tool use ID is all about. It helps us tie tool use requests to tool result outputs.

The other properties on here that you need to be aware of is content. So that's going to be whatever output you get from your tool function. Even if your tool function returns something like a number or a dictionary or a list, you're just going to turn it into a string, usually by just converting into plain JSON. And then finally, optionally, we can also put in an is error field. If anything goes wrong with running your tool function, you will set this to be true. By default, it's always going to be false.

Now that we have a better idea of what this tool result block is all about. I want to give you a quick reminder of what we need to do next. So we just got back an assistant message from Claude that asked us to run a tool. We know that it was asking us to run a tool because it had a tool use block inside of it. We then executed our tool with a provided arguments. So now that we have the results of the tool function, we need to make a follow-up request back to Claude. Inside of this request, we're going to include our full message history. So it's going to be our original user message. The assistant message with a tool use block inside of it. And now we are going to append in one additional message, a user message that has a tool result block. That's what we just discussed. So it's this thing right here inside of this tool result block. It's going to have the result of the actual function call. So let's now go back over to our notebook and we're going to get our list of messages and add in this new user message with the tool result block inside of it.

All right. So back inside of my notebook, I'm going to get my list of messages. I'm going to append in a new message that has a role of user. And then a content list that will contain just one block. It's going to have a tool result block. So we'll give it a type of tool underscore result, a tool use ID that matches the ID of this tool use block right here. To get access to that ID right there, we will refer to response content one dot ID. And when I put that in, I'm going to once again, get a type air. Remember we are totally okay with type errors for right now. We're going to ignore it and we'll fix it up very shortly.

I'll then add in some content. So that's going to be the result of calling my function right here. So I'm going to assign the results of calling get current date time to how about just result? I'm going to rerun that. And then I'll refer to content result. And then finally, when we ran this function, there was no error. So I'll put in is error false, not strictly necessary because that is the default, but I'll put it in there anyways.

Okay. So now that we have updated our list of messages, I'm going to print out the list just to make sure we are doing everything correctly. So now we have our entire conversation history here. We have the original user request. We've got Claude's request for us to use a tool. And now we have appended in a new user message that has a tool result block inside of it. So now the last thing you need to do is take this list of messages and send it back into Claude. I'm going to add in another code cell down here at the bottom. And once again, call client messages, create with my model, max tokens, the list of messages.

And then whenever we make a follow-up request that includes some tool use, we need to also include the original tool schema. Even though we are not probably going to use any tools here, we still have to tell Claude about the existence of this tool because we are referring to it inside of the tool use block right here and the tool result block right here as well. So we need to make sure that we still include our list of tools, which would be a list of get current date time schema. And that should be it. So now let's run this and we should see a final response out of Claude. And so there it is right there. Here's our final response. We have a text block that says The current time is 15.04. Well, that is a successful tool call. So we've gone through the entire process. Let's do a very quick review.

So everything began with us writing out a tool function and then writing a tool schema to describe it. The goal of the tool schema was to help Claude understand the different tools available to it and how to actually call those different tools. We have to include that tool schema with every request that we make from here on out. So that's why I'm showing it right here and down here as well. When Claude responded to us, it sent back an assistant message that had two separate blocks inside of it. So a text block right here and a tool use block right here. The text block is intended to be displayed to a user so the user understands what's going on. And the tool use block includes some information about a tool that Claude wants to call. So it includes the name of the tool it wants to call along with some input arguments to it. Then on our server, we executed the tool function and then we sent a follow-up request back to Claude. The follow-up request included the entire conversation history along with the list of tool schemas as well. The final message inside of our request was a user message that included a tool result block. The tool result block is used to inform Claude about the result of running some tool function. So inside this block we put the current time which is what Claude was really asking for. Then Claude sent us one final result that was just an assistant message with only a text block and it made use of the input that we fed into it through this tool result block.

↑ 目次へ戻る

Building with the Claude API | Lesson 33「Multi-turn conversations with tools」(全文ナレッジ)

動画 9分11秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

1. このレッスンの位置づけ — 単一ツールから「複数ツール×マルチターン」へ

  • 前回までで単一ツール(get_current_datetime)を Claude に配線する例は完了。このプロジェクトの最終目標は 3つのツール(Get the current date time / Add duration to date time / Set a reminder)を配線すること。
  • 複数ツールを配線すると、1つのユーザークエリに答えるために Claude が複数のツールを直列で要求してくる状況が発生する。このレッスンはそれを支える会話ループの設計と、そのための下準備リファクタを扱う。

2. 具体例「今日から103日後は何日?」= 2ツール直列

1つのクエリに対し、裏側では次のやり取りが起きる(スライドのシーケンス図より): 1. User Message:「What day is 103 days from today?」→ Claude へ送信 2. Claude → Assistant Message(ToolUse Block: get_current_datetime を呼べという要求) 3. サーバー側で関数実行 → User Message(ToolResult Block: get_current_datetime の結果)を返す 4. Claude は「まだ答えるのに十分な情報がない」と判断 → Assistant Message(ToolUse Block: add_duration_to_datetime を呼べという要求) 5. 実行して結果を返す → Claude が最終回答を生成 - クライアントアプリ視点では「Repeat back and forth until Claude stops asking for tool calls(Claude がツール呼び出しを要求しなくなるまで往復を繰り返す)」→ 最後に「March 18th is 103 days」のような最終回答だけが返る。 - 図には「Note: Omitting full message history for clarity(図では簡潔さのため全メッセージ履歴を省略)」と明記。実際は毎回全履歴を送る。

3. 設計原則 — レスポンスごとに「ツール要求か?」を判定するループ

  • 実ユーザーの入力は予測不能。複数ツールを連続で使いたがる前提でアプリを設計する。
  • Claude の各レスポンスを検査し、ツール要求でなければそれが最終回答=ユーザーへ返す。ツール要求ならツールを実行し、結果をユーザーメッセージに入れて再度 Claude を呼ぶ——これを while ループで繰り返す。

4. run_conversation の擬似コード(スライド転記)

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
  • ※スライドは擬似コード(if response isn't asking for a tool: は擬似表現)。またアシスタント応答の履歴追加も add_user_message(messages, response) と書かれているが、ノートブック実装では assistant メッセージとして add_assistant_message で追加する(本文 5:06 参照)。

5. リファクタの4ステップ(Tool Use Improvements スライド)

  1. add_user_messageadd_assistant_message複数メッセージブロックを扱えるようにする
  2. chatツールのリストを受け取れるようにする。さらにテキストではなく生成されたメッセージ全体を返す
  3. メッセージ内のテキストブロックから全テキストを抽出する text_from_message 関数を追加
  4. 1つの会話内での複数回ツール呼び出しのサポートを追加 - 本動画ではステップ1〜3まで実施。ステップ4(run_conversation 本体)は次の段階。

6. Step1 実装 — ヘルパーの柔軟化(isinstance で Message/ブロックリスト/文字列を吸収)

  • Before(プレーンテキスト前提):
def add_user_message(messages, text):
    user_message = {"role": "user", "content": text}
    messages.append(user_message)
  • After(画面転記+音声):from anthropic.types import Message をインポートし、第2引数を message に改名、content を「Message インスタンスなら message.content、そうでなければ message そのもの」にする。add_assistant_message にも同じリファクタを適用。
  • 効果:プレーン文字列でも、ブロックのリストでも、Message 全体でもそのまま渡して履歴に積めるようになる。tool use ブロック入りメッセージの扱いが楽になる。

7. Step2 実装 — chat の tools 対応と「Message 全体を返す」への変更

  • def chat(messages, system=None, temperature=1.0, stop_sequences=[], tools=None): とし、system と同じパターンで配線:
    if tools:
        params["tools"] = tools
  • return は return message.content[0].text →(「最初のブロックが常にテキスト」という大きな仮定なので)→ return message に変更。テキスト取得は少し不便になるが、複数ブロックが返る現実を反映した安全な設計。

8. Step3 — text_from_message ヘルパー

  • 目的:メッセージの全ブロックを見て text ブロックだけ抽出し、テキストを連結して返す(return message.content[0].text を削除した機能の置き換え)。
  • 講師の口述による実装(画面上は def text_from_ の入力途中まで確認):改行文字で block.text for block in message.content if block.type == "text" の内包表記を join して返す。

9. 動作検証 — stop_reason='tool_use' と ToolUseBlock を含む実レスポンス

  • 例:add_user_message(messages, "Whats the current time in HH:MM:SS format?")tools=[get_current_datetime_schema] で呼ぶと、content に TextBlock と ToolUseBlock の両方を含む Message が返る(stop_reason='tool_use'、モデルは claude-3-7-sonnet-20250219、input_tokens=613 / output_tokens=88)。
  • この Message 全体を add_assistant_message(messages, response) にそのまま渡せる/response.content(ブロックリスト)でも/プレーン文字列でも可——リファクタ後のヘルパーの柔軟性が確認できる。

実務ポイント(このレッスンから持ち帰るもの)

  • ツール実行結果は user ロールのメッセージ(ToolResult Block) として返す。assistant の ToolUse Block と対で会話履歴に積む。
  • 実ユーザーのクエリは予測不能 → 「ツールは1回だけ」前提で書かない。Claude がツール要求をやめるまで回す while ループを最初から用意する。
  • message.content[0].text のような「最初のブロック=テキスト」仮定は tools 導入後に破綻する。chat は Message 全体を返し、テキスト抽出は専用ヘルパーに分離する。
  • content には文字列/ブロックリスト/Message 全体のどれが来ても吸収できるよう、isinstance(message, Message) で分岐するパターンが有効。
  • テキスト抽出は block.type == "text" でフィルタして join——ツール使用ブロック混在メッセージから安全にテキストだけ取れる。
  • ループ継続判定のサインは実レスポンスで確認できる:ツール要求時は stop_reason='tool_use' になり content に ToolUseBlock が入る。
  • スライドのシーケンス図には「Omitting full message history for clarity」とある通り、実装では毎ターン全履歴を送る(図の簡略化に引きずられない)。
  • スライド擬似コードの add_user_message(messages, response) は擬似表現。実装ではアシスタント応答は assistant メッセージとして追加する。

本文(時系列・日本語逐語+画面差し込み)

[0:00] ここまでで、Claude に単一のツールを1つ配線する例を見てきました。ただし改めて確認すると、このプロジェクトの目標は、複数の異なるツールを Claude に配線することです。つまり合計で3つの異なるツールを持たせたいのです。3つの異なるツールを Claude に配線したとき、私たちのコードの中で何が起きるのかを考えてみたいと思います。そこで、簡単な例を見ていきましょう。

[0:16] 「今日から103日後は何日ですか?」と Claude に尋ねるユーザーメッセージを送信したと想像してください。これに答えるために、Claude は2つの別々のツールを使う必要があります。まず getCurrentDateTime を使って現在の日付が何かを把握し、次に add duration to date time を使ってそこに103日を加える必要があります。

🖥 [0:16] 画面: シーケンス図スライド。左に「Our Server」、右に「Claude」のボックスと縦の破線ライフライン。両者を結ぶ右向き矢印の上にメッセージボックス。 User Message | What day is 103 days from today? (左下に ANTHROP\C ロゴ)

🖥 [0:22] 画面: スライド「Tools We Need」。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 ```

[0:32] 裏側で起きることはこうです。最初に、Claude は即座に、get current date time を呼んでほしいというツール使用ブロック(tool use block)で私たちに応答してきます。私たちはその関数を呼び出し、現在の日付が何かを Claude に伝える形で応答を返します。

🖥 [0:33] 画面: 先ほどの「Our Server / Claude」シーケンス図に戻る(User Message の矢印のみ。次の応答ボックスが薄くフェードイン中)

[0:46] その後、Claude は、ユーザーの元の質問に答えるにはまだ十分な情報がないことに気づきます。今度は現在の日付を取り、そこに103日を加える必要があります。そこで Claude は、add duration to date time を呼んでほしいという、もう1つ別のツール使用ブロックで応答してきます。私たちはそれを呼び出し、レスポンスを Claude に渡します。これで Claude は、実際のクエリに応答するのに十分な情報を手に入れました。

🖥 [0:47] 画面: シーケンス図の完成形。上から順に4本の矢印とメッセージボックス。 User Message | | What day is 103 days from today? (→ Claude) Assistant Message | ToolUse Block | Request to call get_current_datetime (← Claude) User Message | ToolResult Block | Result of get_current_datetime (→ Claude) Assistant Message | ToolUse Block | Request to call add_duration_to_datetime (← Claude・太字強調) 右側注記:「Claude realizes it doesn't have enough info to answer yet!」 下部注記:「Note: Omitting full message history for clarity」

[1:10] さて、なぜ特にこの例をお見せしているのか。元のユーザーメッセージの入力を実際のユーザー、つまり生身の人間から受け取る場合、その人たちが Claude に何を尋ねてくるかを常に正確に予測することはできません。ユーザーは、実際に答えるためには複数の異なるツール呼び出しが必要になるような、突拍子もないクエリを尋ねてくるかもしれません。ですから、アプリケーションにツール呼び出しを追加するときは、この種の状況をきちんと許容できるようにしておく必要があります。Claude に何らかのクエリを送信するときはいつでも、Claude が複数のツールを続けて使いたがるかもしれないと想定する必要があります。そして Claude が応答してくるたびに、そのレスポンスを見て、Claude がツールの使用を求めているかどうかを確認する必要があります。求めていなければ、ようやくユーザーに届けられる最終レスポンスを手にしたと分かります。

🖥 [1:29] 画面: 3者のシーケンス図スライド。「Client App」「Our Server」「Claude」。 Client App → Our Server: What day is 103 days from today? Our Server → Claude: User Message | What day is 103 days from today? (双方向矢印)Repeat back and forth until Claude stops asking for tool calls Claude → Our Server: No more tools required! Final answer is ready! Our Server → Client App: March 18th is 103 days

[1:50] これを実装する方法の擬似コード例がこちらです。run conversation のような名前の関数を作り、初期のメッセージリストを受け取るようにします。そして while ループの中で Claude に問い合わせ、レスポンスを受け取ります。次にそのレスポンスを確認します。Claude がツール使用を求めていなければ、ユーザーに送り返す準備のできたレスポンスがあると分かります。そうではなく Claude がツールを使いたい場合は、ツールを実行し、得られたツールリザルトブロックをユーザーメッセージに追加して、while ループの中のまま、もう一度 Claude を実行します。

🖥 [1:50] 画面: スライド「Conversation with tools」。左に箇条書き、右に黒背景のコード。 箇条書き: ・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

```

[2:21] この動画の残りでは、ちょうどこのような関数を組み上げるために、私たちのノートブックをリファクタリングする時間を取ります。つまり、まったく同じ一連の操作をたどる run conversation のような関数を作っていきます。ただし、この関数を組み立てるには、少し作業が必要になります。add user message と add assistant message のヘルパー、そして chat 関数にも、ちょっとしたリファクタを施す必要があります。

[2:45] この run conversation 関数を定義する準備として、ノートブックの中でやることをすべて一覧にまとめました。ステップ1では、add user message と add assistant message のヘルパー関数をアップグレードして、複数のメッセージブロックをよりうまく扱えるようにします。ツールを使い始めると、内部に複数の異なるブロックを持つレスポンスが Claude から返ってくる可能性があることを思い出してください。そして現時点では、add user message と add assistant message のヘルパー関数は、常にプレーンなテキストブロックだけを扱うという前提で完全に組まれています。では、さっそくノートブックに戻ってこれに対処しましょう。

🖥 [2:47] 画面: スライド「Tool Use Improvements」。縦のタイムラインに4項目。 ・Ensure 'add_user_message' and 'add_assistant_message' can handle multiple message blocks ・Allow 'chat' to receive a list of tools. Also, return the full generated message, not text ・Add a 'text_from_message' function to extract all text from text blocks from a message ・Add support for multiple tool calls in a conversation

🖥 [3:03] 画面: 復習用のシーケンス図スライド。上部に5ステップのプロセスチェーン(「Call Claude with JSON Schema」が強調色)。 Write a tool function → Write a JSON Schema → Call Claude with JSON Schema → Run Tool → Add Tool Result and call Claude again 下部の Our Server / Claude 図: Assistant Message | Text Block | Sure, I can look that up for you | ToolUse Block | ~Request to call a tool~ (← Claude) Tool Schema | Helps Claude understand how to call our tool functions User Message | Text Block | Whats the current time? Assistant Message | Text Block | Sure, I can look that up for you | ToolUse Block | ~Request to call a tool~ User Message | ToolResult Block | ~Result of function call~ (→ Claude)

[3:19] さて戻ってきました。ここは、これまで作業してきたのとまったく同じノートブックの中です。私が画面でやることを見やすくするために、例のツール呼び出しがいくつか入っていた下の方のセルを削除しただけです。よし、ではヘルパー関数のセルを展開します。add user message と add assistant message を見つけます。

🖥 [3:20] 画面: VS Code の Jupyter ノートブック 001_tools.ipynb — 06(カーネル .venv (Python 3.9.6))。セルは折りたたみ状態で見出しのみ: ✓ # Load env variables and create client ✓ # Helper functions ✓ # Tools and Schemas ✓ # get_current_datetime tool function

[3:36] 改めて言うと、今は常に何らかのテキスト片を受け取る前提で、それを content プロパティに直接代入しています。そこで、ここにもう少し柔軟性を持たせたいのです。

🖥 [3:52] 画面: Helper functions セルを展開した状態(リファクタ前のコード)。 ```python

Helper functions

def add_user_message(messages, text): user_message = {"role": "user", "content": text} messages.append(user_message)

def add_assistant_message(messages, text): assistant_message = {"role": "assistant", "content": text} messages.append(assistant_message)

def chat(messages, system=None, temperature=1.0, stop_sequences=[]): params = { "model": model, "max_tokens": 1000, "messages": messages, `` (params の続きは画面外。messages.append(user_message)` の行末にカーソル)

[3:49] やり方はこうです。セルの先頭で、anthropic.types からのインポートを追加します。Message をインポートします。次に、ここにある第2引数の名前を変えます。text ではなく、message と呼ぶことにします。この辞書をこんなふうに展開します。そして text を、「message が(大文字 M の)Message のインスタンスであれば message.content、そうでなければ message」となるように更新します。それから、同じリファクタを add assistant message にも繰り返します。この下で、あれを message にリネームし、辞書を展開します。そして時間節約のため、あそこの文をそのままあちらへコピーします。

🖥 [4:27] 画面: 同セル・リファクタ進行中。add_user_message は書き換え済み、add_assistant_message はまだ旧コード。 ```python

Helper functions

from anthropic.types import Message

def add_user_message(messages, message): user_message = { "role": "user", "content": message.content if isinstance(message, Message) } messages.append(user_message)

def add_assistant_message(messages, text): assistant_message = {"role": "assistant", "content": text} messages.append(assistant_message) `` (isinstance(message, Message)の直後にカーソル。この直後にelse messageが入力される。下にdef chat(...)` が続く)

[4:33] よし、ではセルを再実行して、このリファクタが私たちに何をもたらしてくれるのかをお見せします。下の方に、こんな感じで簡単な例の呼び出しを追加します。Claude に現在時刻を時分秒フォーマットで出力するよう頼み、get current daytime ツールを提供しているだけです。それから、返ってきたメッセージつまりレスポンスを出力します。

🖥 [4:45] 画面: ノートブック下部の新しいセル。 ```python messages = []

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 ```

[4:54] これを実行すると、このようにいつものメッセージが得られることが分かります。そしてその中には content プロパティがあり、テキストブロックとツール使用ブロックの両方が入っています。さて、これをアシスタントメッセージとしてメッセージ履歴にとても簡単に追加するには、messages を渡して add assistant message を呼びます。そして今や、あのレスポンス全体、つまり受け取ったメッセージ全体をそのまま入れることができます。それを呼び出して、1つ下のセルで messages を出力します。ほら、この通り。メッセージ履歴全体が組み上がっています。

🖥 [5:07] 画面: 同セル(末尾に add_assistant_message(messages, response) を追加、response が選択ハイライト)を実行後([30]・2.0s)。実行出力: Message(id='msg_01BHuUnnQ6L5qEzLXoAcCMxL', content=[TextBlock(citations=None, text='I can get the current time in HH:MM:SS format for you using the get_current_datetime tool.', type='text'), ToolUseBlock(id='toolu_01FmCeEPKCsybHrCW4pd3NF9', 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=88))

[5:25] もう1つの実行方法は、response.content を入れることです。このように。これでも同じようにうまく動きます。そうして messages を出力しても、やはり正しい結果が得られます。それからもちろん、望むなら、ここにプレーンな文字列を入れることも常にできます。そして messages を出力すると、ご覧の通り。はい。ちゃんとレスポンス履歴が組み上がり続けています。

[5:47] これで、add user message と add assistant message は、はるかに柔軟なヘルパー関数になりました。プレーンな文字列でも、ブロックのリストでも、メッセージ全体でも入れることができ、それをそのまま保存してくれます。これで、ツール使用ブロックを内部に含むメッセージの扱いが、この先ずっと楽になります。

[6:02] リファクタのステップ2へ進みます。chat 関数を、ツールスキーマのリストを受け取れるように更新します。そのスキーマのリストを受け取り、client の messages create 関数呼び出しへ渡します。加えて、chat 関数からは、アシスタントメッセージの中に入っている最初のブロックからプレーンテキストを取り出して返すことは、もうやめます。代わりに、Claude から受け取ったメッセージ全体を返します。繰り返しになりますが、これは、複数のブロックを内部に持つレスポンスが Claude から返ってくることを今や想定しているからです。そして現時点の chat 関数は、常にブロックを1つだけ、それも単一のテキストブロックだけしか受け取らないという前提になっています。

🖥 [6:04] 画面: スライド「Tool Use Improvements」再掲(2番目の項目「Allow 'chat' to receive a list of tools. Also, return the full generated message, not text」の説明中)

[6:39] さて戻ってきました。これが chat 関数です。デフォルトを None とする tools を追加します。そして、ここで system にやったのとまったく同じやり方で配線していきます。つまり、渡された tools が何かあれば、それを tools パラメータとして追加する、と書きます。

🖥 [6:45] 画面: ノートブックの chat 関数を編集中(tools=N まで入力・カーソル表示= tools=None の入力途中)。 ```python def chat(messages, system=None, temperature=1.0, stop_sequences=[], tools=N): params = { "model": model, "max_tokens": 1000, "messages": messages, "temperature": temperature, "stop_sequences": stop_sequences, }

if system:
    params["system"] = system

message = client.messages.create(**params)
return message.content[0].text

```

[6:56] 次に、return 文のところへ下りていきます。ここです。少し前に述べた通り、今の chat 関数は、Claude から常に単一のブロックが返ってきて、しかもそのブロックは常にテキストブロックである、という前提で組まれています。だからこのコードがここにあるわけです。私たちは、常にあの単一のブロックが返ってくるという、かなり大きな仮定を置いています。しかも常に何らかのテキストを含んでいる、と。ツールを使うようになった今、それはもはや成り立ちません。複数のブロックを内部に持つメッセージが返ってくるかもしれません。つまり、この content リストの中に複数のエントリが入るということです。そのうちの1つはテキストブロックかもしれませんが、必ずしも保証はありません。そこで、常にこの仮定を置くのではなく、今後はメッセージ全体を返すことにします。これは私たちにとって少し不便になります。テキストにアクセスしたくなったら、もう少し余分な作業が必要になるからです。しかし間違いなくずっと安全です。繰り返しますが、これこそが現実を反映しているからです。

🖥 [7:10] 画面: chat 関数の後半。if tools: ブロックが追加済みで、return message.content[0].textmessage.content[0].text が選択ハイライトされている。 ```python if tools: params["tools"] = tools

if system:
    params["system"] = system

message = client.messages.create(**params)
return message.content[0].text

```

[7:45] よし、これはこれで完了です。では今の話と同じ流れで、ここで小さなヘルパー関数を追加します。text from message と呼ぶことにします。ここでの目標は、メッセージを見て、そのすべてのブロックを見て、テキストブロックをすべて見つけ出し、そこからテキストだけを抽出することです。つまりこれは、先ほど取り除いた機能をある意味で置き換えるものです。与えられたメッセージからすべてのテキストを取り出すのが、ずっと簡単になります。

🖥 [7:47] 画面: ノートブックから「Tool Use Improvements」スライドへ画面遷移(ノートブック側は return message へ書き換え済みの行末が見える)。スライドでは3番目の項目「Add a 'text_from_message' function to extract all text from text blocks from a message」が選択枠で強調されている

[8:09] では、そのヘルパー関数を chat のすぐ下に追加します。text from message と名付けます。message を受け取り、この中で、改行文字が「message.content 内の各 block について、block.type が text に等しい場合の block.text」という内包表記を join したものを返します。つまりこれは、メッセージ内のすべての異なるブロックを見て、そのブロックがテキストブロックであれば、ブロックのテキストを抽出し、それらすべてのブロックのテキストをつなぎ合わせて返す、というものです。繰り返しになりますが、特定のメッセージからすべてのテキストを取り出すのを少し楽にするための、ただのヘルパー関数です。

🖥 [8:30] 画面: ノートブック。chat の return は return message に変更済み。その直下に新しい関数を入力中(def text_from_ まで・カーソル表示)。 ```python if tools: params["tools"] = tools

if system:
    params["system"] = system

message = client.messages.create(**params)
return message

def text_from_ `` (下に✓ # Tools and Schemas/✓ # get_current_datetime tool function` のセクション見出し)

[8:50] よし。これでリファクタの大部分は片付きました。残る最後のステップは——すぐこの後に取り組みますが——1つの会話の中での複数回のツール呼び出しのサポートを追加することです。要するに、このような関数を実装する必要があります。メッセージのリストを受け取り、Claude がもうツールを呼び出したくないというサインが返ってくるまで、Claude を呼び出し続ける関数を作る必要があるのです。

🖥 [8:52] 画面: スライド「Tool Use Improvements」再掲(4番目の項目「Add support for multiple tool calls in a conversation」付近にマウスカーソル)

🖥 [9:01] 画面: スライド「Conversation with tools」再掲(run_conversation 擬似コード。内容は [1:50] と同一)

英語逐語(ASR全文)

We've now gone through an example of wiring up one single tool to Claude. But as a reminder, the goal of this project is to wire up multiple different tools to Claude. So we want to have three different tools in total. I want to think about what's going to happen inside of our code when we wire up three different tools to Claude. So let's go through a quick example.

Let's imagine that we submit a user message to Claude asking it what day is 103 days from today. Now to answer this, Claude needs to use two separate tools. First, it needs to use getCurrentDateTime to figure out what the current date is. and then it needs to use add duration to date time to add 103 days to that.

So here's what's going to go on behind the scenes. Initially, Claude is going to immediately respond back to us with a tool use block asking us to call get current date time. We will call that function and then respond back to Claude telling it what the current date is.

After that, Claude is going to realize that it doesn't quite have enough information to answer the user's original question. It now needs to take the current date and add 103 days to it. So Claude is then going to respond to us with another separate tool use block, asking us to call add duration to date time. We will call it and then pass the response back on to Claude. And now Claude has enough information to respond to our actual query.

Now here's why I'm showing you this example in particular. If we are getting our input for our original user message from an actual user, like an actual person, we can't always predict exactly what they are going to be asking of Claude. So a user might ask some wild query that's going to require multiple different tool calls in order to actually answer. So when we add tool calling into our application, we really need to allow for this kind of situation. Whenever we submit some query to Claude, we need to assume that Claude might want to use multiple tools in a row. And whenever Claude responds to us, we need to take a look at the response and see if Claude is asking to use a tool. If it isn't, then we know that we have a final response that we can finally deliver back to our user.

Here's a pseudocode example of how we might implement this. We could make a function called something like run conversation that would take in an initial list of messages. Then inside of a while loop, we'll reach out to Claude, get a response back. We'll then take a look at that response. And if Claude is not asking for a tool use, then we know that we have some response we're ready to send back to a user. Otherwise, if Claude does want to use a tool, we can run the tool, get the resulting tool result blocks, add them into a user message, and then run Claude all over again, still inside of the while loop.

In the remainder of this video, we are going to spend some time to refactor our notebook to build up a function just like this. So we are going to build out a function like run conversation that's going to go through the exact same series of operations. In order to put this function together, however, we are going to have to do a little bit of work. We're going to have to do a little bit of a refactor on our add user message and add assistant message helpers and the chat function as well.

I've put together a list of all the things that we're going to do inside of our notebook to get ready for defining this run conversation function. In step one, we are going to upgrade our add user message and add assistant message helper functions so they can better deal with multiple message blocks. Remember that whenever we start working with tools, we are going to get back responses from Claude that might have multiple different blocks inside them. And at present, our add user message and add assistant message helper functions are entirely set up, always assuming that we are always working with a plain text block and nothing else. So let's take care of that back inside of our notebook right away.

Back over here, I'm still inside of the exact same notebook we have been working on. I just deleted a couple of the cells at the bottom that had some of the example tool calls, just so you can better see what I'm doing on the screen. All right, so I'm going to expand the helper function cell. I'm going to find add user message and add assistant message. So once again, right now we are assuming that we are always getting back some piece of text and we are assigning that directly to the content property. So now we want to allow for a little bit more flexibility here.

So here's how we are going to do it. At the top of the cell, I'm going to add an import from anthropic dot types. I will import message. Then I'm going to rename the second argument right here. Instead of text, I'm going to call it message. I'm going to expand this dictionary like so. And I'm going to update text to be message.content if is instance message capital M message else message. And then I'm going to repeat the same refactor on add assistant message as well. So down here, I'm going to rename that to message. I will expand the dictionary. and then to save time, I will copy that statement right there to right there.

All right. So now I'm going to rerun the cell and I'll show you what this refactor is going to do for us. So back down here, I'm going to add in a quick example call like so. So I'm just asking Claude to print out the current time in our minute second format and I'm providing our get current daytime tool. I'm then going to print out the message or response we get back. So if I run this, we'll see that we get the usual message like so. And inside there is the content property that has both a text block and a tool use block. So now to very easily add this in to my message history as a assistant message, I would call add assistant message with messages. And then I can now put in that entire response, the entire message that I just got back. So I'll call that and then print out messages on the next cell down. And there we go. I've got my entire message history being built up.

Another way that I could run this is to put in response dot content. Like, so this will work just as well. So if I do that and then print out messages, I still get the correct thing. And then of course, if I want to, I could always put in a plain string here as well. And print out messages and we'll see that. Yep. Still building up that response history.

So now we have a much more flexible helper function in add user message and add assistant message. We could put in a plain string or a list of blocks or an entire message and it will just store the thing for us. This is going to make dealing with a message that has some tool use blocks inside of it much easier down the line.

On to step two of our refactor. We are going to update the chat function to receive a list of tool schemas. We'll take that list of schemas and pass it through to the client messages create function call. In addition, from the chat function, we are no longer going to return plain text out of the first block that comes inside of the assistant message. Instead, we will return the entire message that we got back from Claude. Once again, this is because we are now anticipating getting back responses from Claude that have multiple blocks inside them. And at present, our chat function is always assuming that we are only ever getting back one block, just a single text block and nothing else.

So back over here, here's the chat function. I'm going to add in a tools that will be defaulted to be none. And then we are going to wire it up in the same way that we did system right here. So we'll say if there are any tools that were passed in, we will add that in as the tools parameter.

Next up, I'm going to go down to the return statement. So right here, as I just mentioned a moment ago, our chat function is currently set up, assuming that we're always going to get back a single block from clod and that the block is always going to be a text block. That's why we have this code right here. We are making a really big assumption that we're always getting back that one single block. And it's always going to contain some text because we are now making use of tools. that is no longer the case. We might get back a message that has multiple blocks inside it. So multiple entries inside this content list. One of them might be a text block, but we don't necessarily have a guarantee. So rather than always making this assumption, I'm now going to return the entire message. This is going to be a little bit less convenient for us because now if we ever want to get access to the text, we're going to have to do a little bit more work, but it's definitely a lot safer because again, this really reflects reality.

Okay, that's that. So now on that same kind of note, we're going to now add in a little helper function. We're going to call it text from message. And the goal here is to take a look at a message, take a look at all the blocks, find all the text blocks, and just extract the text from those. So this is kind of replacing the functionality that we just removed. It's going to make it a lot easier to extract all the text out of a given message.

So I'm going to add in that helper function right underneath chat. I'll call it text from message. It's going to receive a message and inside of here I'm going to return a new line that is going to join together a comprehension with block.text for block in message.content if block.type is equal to text. So this is going to take a look at all the different blocks inside of a message and if the block is a text block then we're going to just extract the blocks text and then join all the block text together and return it. So again, just a helper function to make it a little bit easier to get all the text out of a particular message.

All right. So we've taken care of most of our refactor. So now the last step, which we're going to take care of in just a moment is add in support for multiple tool calls inside of a single conversation. So essentially we need to implement a function like this. We need to make a function that's going to take in the list of messages and then continue calling Claude until we get back a sign that Claude doesn't want to call a tool anymore.

↑ 目次へ戻る

Building with the Claude API | Lesson 34「Implementing multiple turns」(全文ナレッジ)

動画 16分26秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04

日本語ナレッジ(要点整理)

1. このレッスンの位置づけ — run_conversation/run_tools の実装本体

  • 前回(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 messageswhile の外

4. run_tools の設計 — 複数 ToolUse ブロック前提

  • 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 を渡して実行する。

5. run_tools の実装(完成形・画面転記)

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 を追加)。

6. ToolUseBlock ↔ ToolResultBlock の対応(スライド転記)

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 はより良い引数・より整形された引数でツールを再実行しようとすることがある

8. 改良②:run_tool ディスパッチャの分離(スケーラビリティ)

  • 現状は 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: Truef"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.

↑ 目次へ戻る

Building with the Claude API | Lesson 35「Using multiple tools」(全文ナレッジ)

動画 3分38秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

1. このレッスンの位置づけ — 残り2ツールを足してマルチツール化を完成させる

  • 前レッスンまでに get_current_datetime ツール1本を動かす会話ループ(run_conversation / run_tools / run_tool)を構築済み。今回はスライド「Tools We Need」の残り2枚、add_duration_to_datetime(Claude は日時の加算計算が完璧ではないため)と set_reminder(リマインダーを設定する手段が必要)を配線する。
  • 前回が難所だった分、今回は「拍子抜けするほど簡単」— ツール関数の実装とスキーマ(add_duration_to_datetime_schema / set_reminder_schema)は講師が事前に用意済みで、残る作業は登録と分岐追加だけ
  • set_reminder は本物のリマインダーは設定しないダミー実装。「この時刻にこの内容でリマインダーを設定した」という文を print するだけ。

2. ツール追加の定型3ステップ(このレッスンの核心)

一度ループ骨格を作ってしまえば、以降のツール追加は毎回同じ3点セットで済む。

  1. ツール関数の実装を書く(今回は用意済み)
  2. ツールスキーマを定義し、run_conversation 内の tools リストに追加する(→ Claude が存在を認識する)
  3. run_tool にディスパッチ分岐(elif)を追加する(→ Claude の要求を実際の関数呼び出しに変換する)

3. run_tool のディスパッチ実装(elif 分岐 + **kwargs 展開)

完成形(画面転記):

def run_tool(tool_name, tool_input):
    if tool_name == "get_current_datetime":
        return get_current_datetime(**tool_input)
    elif tool_name == "add_duration_to_datetime":
        return add_duration_to_datetime(**tool_input)
    elif tool_name == "set_reminder":
        return set_reminder(**tool_input)
  • tool_input(Claude が生成した引数 dict)を **tool_input で kwargs 展開して渡す。スキーマの property 名と Python 関数の引数名が一致している前提の書き方。
  • 呼び出し側の run_tools(message) は変更不要 — message.content から block.type == "tool_use" のブロックを集めて 1 件ずつ run_tool(tool_request.name, tool_request.input) を呼ぶ汎用実装のまま。

4. スキーマの構造(add_duration_to_datetime_schema・画面転記)

add_duration_to_datetime_schema = {
    "name": "add_duration_to_datetime",
    "description": "Adds a specified duration to a datetime string and returns the resulting da…(画面右で見切れ),
    "input_schema": {
        "type": "object",
        "properties": {
            "datetime_str": {
                "type": "string",
                "description": "The input datetime string to which the duration will be added.…(画面右で見切れ)
            },
            "duration": {
                "type": "number",
                "description": "The amount of time to add to the datetime. Can be positive (for…(画面右で見切れ)
            },
            "unit": {
                "type": "string",
                ...

これを run_conversation 内の tools リストに set_reminder_schema とともに追加するだけで Claude が使えるようになる。

5. マルチツール連鎖のデモ — 「2050年1月1日の177日後にリマインダー」

  • クエリ: "Set a reminder for my doctors appointment. Its 177 days after Jan 1st, 2050."
  • この要求は必ず複数ツール呼び出しになる: ①まず add_duration_to_datetime で日付を計算 → ②その結果を使って set_reminder
  • 実行結果: Claude はまず「日付を計算する」と宣言 → add_duration_to_datetime(datetime_str='2050-01-01', duration=177, unit='days') → tool_result "Monday, June 27, 2050 12:00:00 AM"set_reminder(content="Doctor's appointment", timestamp='2050-06-27T00:00:00') → 最終応答「2050年6月27日(月)に設定完了」。
  • 中間結果(June 27, 2050)を Claude 自身が次のツール入力の timestamp 形式(ISO 8601)に変換して渡している点が重要。ツール間のデータ受け渡しは Claude がやってくれる。

6. 会話履歴に見るマルチターン tool use の構造

履歴は「user → assistant(text + tool_use) → user(tool_result) → assistant(text + tool_use) → user(tool_result) → assistant(最終回答)」の交互構造。

  • assistant メッセージは TextBlock と ToolUseBlock の複合(1メッセージに複数種のブロック)— 前レッスンで見たパターンの再確認。
  • tool_result は tool_use_id で対応する ToolUseBlock に紐づく。
  • set_reminder は戻り値なし(None)のため tool_result の content は 'null' になるが、'is_error': False なので会話は正常に続行する。

実務ポイント(このレッスンから持ち帰るもの)

  • ループ骨格は一度書けば資産になる: run_conversation / run_tools / run_tool を作った後のツール追加は「①ツール関数実装 ②スキーマを tools リストへ ③run_tool に elif」の3点だけ。最初の設計コストを回収できる。
  • **tool_input の kwargs 展開を使うなら、スキーマの property 名と関数の引数名を完全一致させる。ズレると TypeError(このコードでは run_tools 側の try/except が is_error 付き tool_result に変換する)。
  • 依存関係のある複数ツール呼び出しは Claude が自律的に順序制御する。「計算→その結果でアクション」の連鎖はプロンプト側で指示しなくても、ツール設計(説明文と入力形式)が適切なら成立する。
  • 中間結果のフォーマット変換も Claude がやる("Monday, June 27, 2050 12:00:00 AM" → '2050-06-27T00:00:00')。ツール出力とツール入力の形式が違っても接続できるが、厳密さが必要なら入力側スキーマの description で形式を明示する。
  • 副作用系ツール(リマインダー設定等)はダミー実装(print のみ)でループの挙動検証ができる。戻り値 None → tool_result content 'null' でも is_error: False なら会話は壊れない。
  • 日時計算のような「LLM が間違えやすい決定論的処理」はツールに逃がすのがこのミニアプリの設計思想(スライド: "Claude isn't perfect with date time addition")。
  • Jupyter 運用の注意: ツールや関数を書き換えたら該当セルと run_conversation のセルを再実行してからクエリを投げる(講師も明示的に rerun している)。

本文(時系列・日本語逐語+画面差し込み)

[0:00] 最後にやるべきことは、add_duration_to_datetime ツールと set_reminder ツールを追加することです。そしてこれは少し拍子抜けになるかもしれません。前回の動画がかなり難しかったのに対して、今回は本当に簡単で、短く、素直な内容だからです。

[0:12] 実は、必要なコードの多くはすでに私が用意してあります。

🖥 [0:12] 画面: スライド「Tools We Need」— 必要な3ツールのカード ``` Tools We Need

┌ 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 │ └─────────────────────────────────────────┘

ANTHROP\C ``` (3枚のカード。左からベージュ・オレンジ・赤茶の配色。今回配線するのは右2枚)

[0:15] Tools and Schemas セルの中を見ると、add_duration_to_datetime の実装がすでにこの中に組み込んであるのが分かります。そして下にスクロールすると、set_reminder 関数も用意してあるのが見えます。ただし set_reminder は、実際にリマインダーを設定するようなことは何もしません。「この時刻に、この内容でリマインダーを設定しました」という文を print するだけです。

🖥 [0:26] 画面: VS Code のノートブック 001_tools.ipynb(.venv Python 3.9.6)。上部に折りたたまれたセル群と、run_tool / run_tools のセル ```python ✓ # Load env variables and create client ✓ # Helper functions ✓ # Tools and Schemas ✓ # get_current_datetime tool function

import json

def run_tool(tool_name, tool_input): if tool_name == "get_current_datetime": return get_current_datetime(**tool_input)

def run_tools(message): tool_requests = [ block for block in message.content if block.type == "tool_use" ] ``` (セル実行番号 [44]。run_tool はまだ get_current_datetime の1分岐のみ)

[0:35] さらに、add_duration_to_datetime スキーマと set_reminder スキーマも用意してあります。ですから、実際にやるべきことは、この2つのスキーマを Claude に渡すことだけです。それから、Claude がどちらかのツールを使いたいと要求してきた場合に、対応するツール関数を呼び出すようにすることも必要です。では取りかかりましょう。かなり簡単なはずです。

🖥 [0:35] 画面: Tools and Schemas セル内の add_duration_to_datetime_schema 定義(セル実行番号 [42]) python add_duration_to_datetime_schema = { "name": "add_duration_to_datetime", "description": "Adds a specified duration to a datetime string and returns the resulting da(画面右で見切れ), "input_schema": { "type": "object", "properties": { "datetime_str": { "type": "string", "description": "The input datetime string to which the duration will be added.(画面右で見切れ) }, "duration": { "type": "number", "description": "The amount of time to add to the datetime. Can be positive (for(画面右で見切れ) }, "unit": { "type": "string", (description キーが選択ハイライトされている)

[0:52] まず最初に、この中の run_conversation 関数を探します。tools のリストを見つけて、そこに add_duration_to_datetime_schema を追加し、set_reminder_schema も同様に追加します。これで Claude は、この2つの別のツールの存在を認識するようになりました。

[1:09] 次にやることは、少しだけ上にある run_tool 関数に移動することです。これはツール名と何らかの引数を受け取る関数です。やるべきことは、呼び出すべき適切なツール関数を見つけて、それを呼び出し、結果を返すことだけです。なので、ここへの追加は本当に簡単です。elif のケースを1つ追加するだけです。tool_name が add_duration_to_datetime かどうかをチェックし、もしそうなら、add_duration_to_datetime に **tool_input を渡した呼び出しを return します。

🖥 [1:16] 画面: 編集前の run_tool セル(breadcrumb: import json) ```python import json

def run_tool(tool_name, tool_input): if tool_name == "get_current_datetime": return get_current_datetime(**tool_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 = {

```

[1:44] 次に、もう一方のツールにも同じことを繰り返します。tool_name が set_reminder なら、set_reminder に tool_input を渡して return します。

🖥 [1:44] 画面: run_tool を編集中。add_duration_to_datetime の elif を追加し終え、次の行に「re」と入力し始めたところ python def run_tool(tool_name, tool_input): if tool_name == "get_current_datetime": return get_current_datetime(**tool_input) elif tool_name == "add_duration_to_datetime": return add_duration_to_datetime(**tool_input) re▌

[1:55] これで完了です。

🖥 [1:55] 画面: 完成した run_tool(3分岐) ```python import json

def run_tool(tool_name, tool_input): if tool_name == "get_current_datetime": return get_current_datetime(tool_input) elif tool_name == "add_duration_to_datetime": return add_duration_to_datetime(tool_input) elif tool_name == "set_reminder": return set_reminder(**tool_input) ```

[1:57] つまり、この run_tools 関数と run_conversation 関数を一度組み上げてしまえば、最初のあの難しさを乗り越えた後は、tool use にまつわるすべてが本当に簡単で素直なものになっていきます。追加のツールを足すのは非常にシンプルだからです。run_tool を更新し、ツールスキーマを追加し、実際のツール関数そのものの実装を追加する。それだけです。これで全部完了です。

[2:17] では、これをテストしてみましょう。先ほどのセルを忘れずに再実行します。run_conversation も再実行します。それから、一番下の最下部で、クエリを更新しましょう。Claude にリマインダーの設定を頼みます。そして、そのリマインダーは2050年1月1日の177日後に設定される必要がある、と伝えます。これは間違いなく、複数のツール呼び出しという結果になります。Claude はまず日時に期間を加算(add_duration_to_datetime)してから、その後でリマインダーを設定(set_reminder)しなければならないからです。では実行して、どうなるか見てみましょう。

[2:46] Claude は最初に、1月1日の177日後がいつかを計算する必要がある、と伝えてきます。

🖥 [2:46] 画面: 最下部のクエリセルを更新して実行中([50]・経過 2.4s)。直前のセル [48] は run_conversation の末尾 ```python tool_results = run_tools(response) add_user_message(messages, tool_results)

return messages

python messages = [] add_user_message( messages, "Set a reminder for my doctors appointment. Its 177 days after Jan 1st, 2050.", ) run_conversation(messages) 出力(ストリーミング中の1段落目): I'll set a reminder for your doctor's appointment that's 177 days after January 1st, 2050. Let me calculate that date first. ```

[2:51] それが分かると、今度はリマインダーの設定を試みます。そして、ここにログ文が表示されているのが見えます。このログ文は set_reminder 関数から出力されているものです。思い出してください、set_reminder は実際には何もしません。与えられた引数を print するだけです。そして最後に、Claude から、予約が正しい日付である2050年6月27日月曜日に設定されました、という応答が返ってきます。

🖥 [2:54] 画面: 実行完了後の全出力([50] ✓ 6.4s) ``` I'll set a reminder for your doctor's appointment that's 177 days after January 1st, 2050. Let me calculate that date first. Now I'll set the reminder for your doctor's appointment on June 27, 2050:


Setting the following reminder for 2050-06-27T00:00:00: Doctor's appointment


Your reminder has been set successfully! You will be reminded of your doctor's appointment on Monday, June 27, 2050. If you need to set a specific time for this appointment rather than midnight (12:00 AM), please let me know and I can adjust the reminder. `` (----` で挟まれた2行が set_reminder 関数の print によるログ文)

[3:10] さらに下へ行けば、メッセージの会話履歴も見られます。今回もまた、user メッセージがあり、そのすぐ後の assistant にはテキストブロックが入っています。そしてテキストブロックに加えて、またしても tool_use ブロックがあります。つまりここでもまた、1つのメッセージの中に複数の異なるブロックが入っている例を見ていることになります。

🖥 [3:13] 画面: 会話履歴(messages)の先頭部分 python [{'role': 'user', 'content': 'Set a reminder for my doctors appointment. Its 177 days after Jan 1st, 2050.'}, {'role': 'assistant', 'content': [TextBlock(citations=None, text="I'll set a reminder for your doctor's appointment that's 177 days after January 1st, 2050. Let me calculate that date first.", type='text'), ToolUseBlock(id='toolu_01XfWce6tvAzJnZF1pmdQ3RY', input={'datetime_str': '2050-01-01', 'duration': 177, 'unit': 'days'}, name='add_duration_to_datetime', type='tool_use')]},

[3:26] それから、こちらが tool_result で応答します。

🖥 [3:26] 画面: 履歴の続き — 1つ目の tool_result と、2回目の assistant(テキスト + set_reminder の tool_use) python {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_01XfWce6tvAzJnZF1pmdQ3RY', 'content': '"Monday, June 27, 2050 12:00:00 AM"', 'is_error': False}]}, {'role': 'assistant', 'content': [TextBlock(citations=None, text="Now I'll set the reminder for your doctor's appointment on June 27, 2050:", type='text'), ToolUseBlock(id='toolu_019UzXWzmxEm7rqXjdZYd2fq', input={'content': "Doctor's appointment", 'timestamp': '2050-06-27T00:00:00'}, name='set_reminder', type='tool_use')]},

[3:28] その後、フォローアップの応答を受け取ります。ここから先は、もうこのプロセスを理解してもらえていると思います。

🖥 [3:28] 画面: 履歴のさらに続き — set_reminder への tool_result(戻り値なし = 'null') python {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_019UzXWzmxEm7rqXjdZYd2fq', 'content': 'null', 'is_error': False}]}, {'role': 'assistant',

[3:33] さて、これで終わりです。私たちはノートブックに複数の異なるツールを配線しました。素晴らしい進歩です。

英語逐語(ASR全文)

The last thing we need to do is add in the add duration to date time tool and the set and reminder tool. And this is going to be a little bit anticlimactic because last video was rather challenging. This one is going to be really easy, short, and straightforward. Turns out I've already put together a lot of the code that we need for this.

Inside of the tools and schemas cell, you'll find that I've already got an implementation of add duration to date time put together inside of here. And if you scroll down, you'll see I've also got our set reminder function put together. Now the set reminder doesn't actually set a reminder or anything like that. It's just going to print out a statement that says, Hey, we set a reminder at this time with some given content.

I've also provided us a add duration to date time schema and a set reminder schema. So all we really need to do is pass these two schemas into Claude. And we also need to make sure that if Claude ever asked to use either tool, we call the appropriate tool function. So let's get to it. Should be pretty straightforward.

First off, I'm going to find the run conversation function inside of here. I'm going to find the list of tools and I'm going to add into it, add duration to date, time schema and the set reminder schema as well. So now Claude is aware of the existence of these two other tools.

Next, all I have to do is go up a little bit here to the run tool function. So this is the function where we're going to get a tool name and some arguments. All we have to do is find the appropriate tool function to call, call it and return the results. So adding into this thing is really easy. We'll just add in an LF case. So we'll check and see if the tool name is add duration to date time. And if it is, we'll return a call to add duration to date time and star star tool input. I'll then repeat that for the other tool. So the tool name is set reminder return set reminder with tool input. And that's it.

So as soon as you put together this run tools function and the run conversation function, after that initial difficulty, everything around tool use starts to become really easy and straightforward because adding in additional tools is super simple. Just update run tool, add in a tool schema, add in an implementation for the actual tool function itself, and that's it. You're all done.

So now let's test this out. I'm going to make sure I rerun that cell. I'm going to rerun run conversation. And then down here at the very bottom, let's update our query. I'm going to ask Claude to set a reminder. And I'm going to say that the reminder needs to be set 177 days after January 1st, 2050. This is definitely going to result in more than one tool call. Claude is going to first have to add a duration to date time and then set a reminder after that. Let's then run this and see how it does.

Claude initially tells us that it needs to figure out 177 days after January 1st. Once it figures that out, it's then going to attempt to set the reminder. Then we see a log statement right here. This log statement is coming from the set reminder function. Remember set reminder doesn't actually do anything. It just prints out the arguments that it's given. And then finally, we get a response from Claude telling us that our appointment has been set on the correct date of Monday, June 27th of 2050.

We can also go down to the message conversation history. So once again, we've got the user message, the assistant right here that has a text block. And in addition to the text block, once again, a tool use block. So yet again, we're seeing an example here of a message with multiple different blocks inside of it. We then respond with the tool results. We then get some follow-up. And from there, I think you understand the process.

All right, so that's it. We have wired in multiple different tools to our notebook. Excellent progress.

↑ 目次へ戻る

Building with the Claude API | Lesson 36「Fine grained tool calling」(全文ナレッジ)

動画 10分57秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

1. ツール使用×ストリーミング=input JSON イベントを1つ追加するだけ

  • ストリーミング有効時の通常イベント列は MessageStartContentBlockStartContentBlockDelta(複数・実際の生成テキストを含む)→ ContentBlockStopMessageDeltaMessageStop
  • ツールを併用すると、ここに input JSON イベント(InputJsonEvent) が加わる。プロパティは2つ:
  • partial_json — Claude がツールに渡したい引数の一部を表す JSON 断片(例: {"title": "VectorStorage"}
  • snapshot — それまでに受信した partial_json 断片の累積合計(例: {"title{"title": "Vector{"title": "Vector Storage" }
  • 実装上は「この追加イベント型をハンドリングする」だけで、ツールストリーミング自体はこれで完結する。

2. ノートブック実装(003_tool_streaming.ipynb)

  • run_conversation(messages, tools=[], tool_choice=None, fine_grained=False)chat_stream(...) でレスポンスストリームを開き、for chunk in stream: でチャンク型ごとに処理(text は print、input JSON チャンクはそのまま出力)。
  • fine-grained は beta ヘッダー で有効化する: betas=["fine-grained-tool-streaming-2025-05-14"] if fine_grained else []
  • デモ用ツールは save_article_schema = ToolParam(...)(name: save_article)。入力はトップレベルキー2つ:
  • abstract(string・「Abstract of the article. One sentence max」)
  • meta(object・word_count: integer と review: string「Eight sentence review of the paper」、両方 required)

3. デフォルト挙動の裏側=トップレベル key-value 単位のバッファリング+JSON 検証

  • Claude はツール入力の JSON をチャンクごとに生成するが、API はそれを即座にサーバーへ返さず一時保持する。理由=Claude は時々無効な JSON を生成しうるため、検証ステップを挟む。
  • 検証はオブジェクト全体ではなく「トップレベルのキーと値のペア1つが完成するまで」待つ。例: abstract の値文字列の閉じ引用符(チャンク#4 の sents",)を検出した時点でペア完成と判断。
  • 完成したペアを単独で、提供された JSON スキーマ(save_article_schema)と突き合わせて検証。有効なら「元の個々のチャンク」をそのまま送る(1塊に結合されるわけではない)。ログを取れば大量の個別チャンクが「ほぼ同時に」届くことが確認できる=API 側でバッファされていた証拠。
  • 次に meta キーでも同じことが繰り返される。これが「ストリーミング有効なのに、長い沈黙→巨大なテキスト塊がドンと出る」現象の正体。

4. fine grained tool calling=JSON 検証ステップの無効化

  • やることは本質的に1つ:API 側の JSON 検証を無効化。トップレベルキーの完成を待たず、数チャンクを結合して即サーバーへ送る(スライド文言:「Fine grained tool calling sends groups of chunks without waiting for a full top level key to be created」)。
  • 結果、ツール入力生成時も古典的なチャンク単位ストリーミング体験になる。例: word_count の値だけ早く欲しい場合、長い review テキストの生成完了を待たずに取得できる。
  • Critical(スライド原文): JSON validation is disabled! Your code should handle invalid tool inputs! — サーバー側コードは無効 JSON を渡される前提でエラー処理を実装すべき。

5. 無効 JSON 生成時の挙動比較(word_count: undefined を強制した実験)

  • 実験手順:JSON では無効な undefined(正しくは null)を word_count に出させるプロンプト+ tool_choice={"type": "tool", "name": "save_article"} で強制ツール呼び出し。
  • fine-grained ON → SDK 内 _beta_messages.py:460content.input = from_json(json_buf, partial_mode="trailing-strings")ValueError: expected value at line 2 column 17(パースエラーがそのままサーバー側に到達する)。
  • fine-grained OFF(検証あり) → API は metaオブジェクトではなく文字列全体でラップして返す("meta": "{\n \"word_count\": undefined,\n \"review\": \"...\"\n}")。エラーにはならないが、スキーマ(meta=object)にもう従っていない。

6. 使い分けの結論

  • デフォルト:大きなトップレベル key-value を生成すると検証のための遅延が出るが、有効 JSON が(ほぼ)保証される。
  • fine-grained:古典的ストリーミング体験を得る代わりに検証を失う。精密なリアルタイム UI 更新・ツール入力の早期処理が必要な時だけ使う「かなり特殊な機能」。

実務ポイント(このレッスンから持ち帰るもの)

  • ストリーミング+ツールは「input JSON イベント(partial_json / snapshot)のハンドラを1つ足す」だけで動く。既存のストリーミングパイプラインにそのまま追加できる。
  • 「ストリーミングしてるのにツール引数だけ塊で届く」のはバグではなく仕様(トップレベル key-value 単位のバッファ+検証)。UI のプログレス設計はこの前提で行う。
  • fine-grained 有効化は beta ヘッダー fine-grained-tool-streaming-2025-05-14 を betas に渡す。SDK なら betas=[...] 引数。
  • fine-grained を使うなら無効 JSON 前提のエラーハンドリング必須(try/except でパースエラーを捕捉、リトライや修復の方針を決めておく)。デモでは SDK の from_json が ValueError を送出した。
  • 検証あり(デフォルト)でも安全とは限らない:無効値が出ると API はネスト object を文字列にラップして返すことがある=スキーマ通りの型(object のはずが str)で来ない可能性がある。受信側で型チェックを。
  • tool_choice={"type": "tool", "name": "..."} で特定ツールの呼び出しを強制できる(テストや異常系再現に有用)。
  • 部分的なツール引数(例: word_count)を早く受け取って先行処理を始めたい、精密なリアルタイム表示をしたい——という要件がなければ、デフォルトのままでよい。

本文(時系列・日本語逐語+画面差し込み)

[0:00] この動画では、ツール使用とストリーミングを組み合わせる方法を見ていきます。ストリーミングについては以前、API からのストリーミングとして説明したのを思い出してください。ストリーミングを使うと、アプリの中で何が起きているのかをユーザーに伝えやすくなります。ストリーミングを有効にして Claude にリクエストを送ると、最初のレスポンスが返ってきて、そのあと一連のさまざまなイベントを受け取ります。各イベントには、ユーザーに表示したくなるようなテキストの断片が追加で含まれています。アプリケーション内で処理が必要になるイベントには多くの種類があったことも思い出してください。その中でも特によく登場するのが content block delta です。

🖥 [0:25] 画面: スライド「ストリーミングのイベント種別一覧」。右上に Claude、縦の点線タイムラインに沿ってイベントが並ぶ text MessageStart — A new message is being sent ContentBlockStart — Start of a new block, which contains text, tool use, or other ContentBlockDelta — Chunk related to the latest content block that was started ContentBlockDelta — Chunk related to the latest content block that was started ContentBlockDelta — Chunk related to the latest content block that was started ContentBlockStop — The current content block has been completed MessageDelta — The current message is complete MessageStop — End of information about the current message (3つの ContentBlockDelta を波括弧でまとめて「These contain the actual generated text」の注記)

[0:31] ツールをストリーミングで使うには、処理すべきイベントの型をもう1つ追加します。それが input JSON イベントです。ストリーミングを有効にしてツールを使おうとすると、Claude は input JSON イベントという新しい型のイベントを送り返してくることがあります。このオブジェクトには重要なプロパティが2つあります。1つ目は partial JSON で、Claude がツールに渡したい引数の一部を表す JSON の断片です。もう1つは snapshot で、これまでに受け取ったさまざまな partial JSON 断片をすべて累積した合計です。

🖥 [0:40] 画面: スライド「Streaming turned on」。Our Server ⇄ Claude のシーケンス図。サーバーから Tool Schema save_article_schema+User Message Generate and save an article about computer science を送信。Claude から InputJsonEvent が3回届く text InputJsonEvent #1 partial_json: {"title snapshot: {"title InputJsonEvent #2 partial_json: ": "Vector snapshot: {"title": "Vector InputJsonEvent #3 partial_json: Storage"} snapshot: {"title": "Vector Storage" }

[1:10] この仕組みをより分かりやすくするために、003 tool streaming という新しいノートブックを用意しました。このレクチャーに添付されています。このノートブックには修正版の run conversation 関数が入っています。chat stream という新しい関数を使って、Anthropic API からのレスポンスストリームを開きます。そしてそのストリーム内のすべてのチャンクについて、チャンクの型に応じてそれぞれ処理していきます。input JSON チャンクを受け取った場合、今のところやるのはそれを出力することだけです。

🖥 [1:11] 画面: VS Code のノートブック 003_tool_streaming.ipynb(prod > 06)。折りたたみセル # Load env variables and create client # Helper functions # Tool definition # Tool Running の下に Run conversation セル ```python

Run conversation

def run_conversation(messages, tools=[], tool_choice=None, fine_grained=False): while True: with chat_stream( messages, tools=tools, betas=["fine-grained-tool-streaming-2025-05-14"] if fine_grained else [], tool_choice=tool_choice, ) as stream: for chunk in stream: if chunk.type == "text": print(chunk.text, end="")

            if chunk.type == "content_block_start":
                if chunk.content_block.type == "tool_use":

``` (以降は画面下で見切れ)

[1:40] それでは下までスクロールします。そこにはプロンプトと、save article schema という新しいツールをすでに用意してあります。このノートブックを実行して、何が起きるか見てみましょう。すぐに、ツール呼び出しの引数を含むレスポンスの各チャンクが届くのが見えます。このノートブックも学術論文を生成していますが、以前のものと比べてプロパティがいくつか違う点に気づくと思います。

🖥 [1:44] 画面: ノートブック最下部のセル ```python messages = []

add_user_message( messages, "Create and save a fake computer science article", )

run_conversation( messages, tools=[save_article_schema], ) ```

[1:59] さて正直なところ、ツールストリーミングそのものについては、これ以上大した話はありません。この追加のイベント型を確実にハンドリングする、本当にそれだけです。

[2:06] とはいえ、ツールストリーミングについてもう1つお伝えしたい側面があります。ただしそれは、あなたが一度も使う必要がないかもしれない、非常に非常に特殊な機能に関するものです。まずごく手短に概要を説明するので、この特殊化された機能について聞きたいかどうかを判断してください。

[2:22] このノートブックを再実行すると、ストリーミングの挙動が得られているように見えるはずです。テキストはチャンクごとに現れています。

🖥 [2:23] 画面: ノートブックで Run All をクリックし再実行中。run_conversation( messages, tools=[save_article_schema], ) のセルが実行中([24] 0.0s 表示)

[2:29] しかしツール呼び出しの引数については、数秒間じっと待たされたあと、突然巨大なテキストの塊が現れることに気づくでしょう。

🖥 [2:31] 画面: セル実行完了([30] 8.1s)。ストリーミング出力 ```text I'll create and save a fake computer science article.

Tool Call: "save_article" {"abstract": "This paper presents a novel quantum-inspired algorithm for optimizing distributed neural network training across heterogeneous edge computing environments.", "meta": {"word_count":4250,"review":"This paper introduces QuanTrain, a quantum-inspired optimization algorithm designed to address the challenges of distributed neural network training in edge computing scenarios. The authors propose a hybrid approach that combines quantum annealing principles with classical gradient descent to optimize model parameters across devices with varying computational capabilities. The algorithm demonstrates significant improvements in convergence speed and energy efficiency compared to traditional federated learning approaches. Experimental results on three benchmark datasets show 23% faster convergence and 31% reduction in communication overhead. The theoretical framework is well-grounded in quantum computing principles, though the practical implementation relies on classical hardware simulation. The paper makes a solid contribution to the intersection of quantum computing and distributed machine learning. However, the scalability analysis could be more comprehensive, and the comparison with recent federated learning variants seems limited. Overall, this work opens promising research directions for quantum-enhanced distributed AI systems."}} ```

[2:39] この大きな遅延のあとに巨大なテキストの塊が届くという事実は、あなたにとっては全く問題にならないかもしれません。しかし、非常に精細なコンテンツの更新をできるだけ早くユーザーに見せることが本当に重要なアプリケーションに取り組んでいるかもしれません。あるいは、ツール呼び出しの一部分をできるだけ早く受け取って、処理作業をできる限り速く始めたいのかもしれません。

[3:01] そこでこの動画の残りの部分では、なぜあの大きな遅延が発生してから巨大なテキストの塊が現れるのかを理解できるように説明します。そして、その遅延を取り除くための Anthropic API の機能をお見せします。では始めましょう。

[3:14] 最初にやりたいのは、このノートブックに追加したツールをはっきりさせることです。以前使っていた、学術論文を生成するツールを少し調整しました。相変わらず論文を生成しますが、入力の構造が少し違うものになっています。左下に、期待される入力の例を載せてあります。abstract というキー(学術論文の要約のようなものです)と、meta オブジェクトを受け取る想定です。そして meta オブジェクトには word count と review が入ります。review は、理屈のうえでは、生成された論文についての何らかの長いレビューになります。

🖥 [3:15] 画面: スライド「Save Article Tool」。左に Example Input、右にツール定義コード python save_article_schema = ToolParam( { "name": "save_article", "description": "Saves a scholarly journal article", "input_schema": { "type": "object", "properties": { "abstract": { "type": "string", "description": "Abstract of the article. One sentence max", }, "meta": { "type": "object", "properties": { "word_count": { "type": "integer", "description": "Word count", }, "review": { "type": "string", "description": "Eight sentence review of the paper", }, }, "required": ["word_count", "review"], }, }, "required": ["abstract", "meta"], }, } ) Example Input(キャプション: This is what we want Claude to generate) json { "abstract": "This paper presents a novel…", "meta": { "word_count": 847, "review": "This paper introduces QuanNet…" } }

[3:45] さて、この入力例について、手短に確認しておきたい、というかただ指摘しておきたいことがあります。受け取るこの大きなオブジェクトには、トップレベルのキーが2つあります。言い換えると、大きなオブジェクト全体の中の最上位のキーです。abstract と meta があり、これらをトップレベルキーと呼ぶことにします。meta はその中にさらにいくつかのキーと値のペアを持つオブジェクトを指しますが、それでもトップレベルキーは abstract と meta です。これをしばらく頭に置いておいてください。

🖥 [3:50] 画面: スライド「Top Level Key / Value 対応表」。上に Example Input の JSON、矢印の先に表 text Top Level Key | Value abstract | "This paper presents a novel..." meta | { "word_count": 847, "review": "This paper..." }

[4:13] では次に、ストリーミングモードでツール呼び出しの引数を生成するとき、Anthropic API の内部で裏側では何が起きているのかを理解できるようにしましょう。これは、期待するとおりの挙動にはなっていないのです。左上のこの図では、私たちの呼び出しが Anthropic API に送られています。リクエストを受け取ると、Claude はツール呼び出しの入力となる JSON を生成し始めます。この JSON は一度に生成されるわけではありません。そうではなく、チャンクごとに作られていきます。

🖥 [4:25] 画面: シーケンス図の開始。左に「Our Server」(点線タイムライン)、右に大きな枠「Anthropic API」。サーバーから Tool Schema save_article_schema+User Message Generate and save an article about computer science を送る矢印のみ描かれた状態

[4:40] さて、ここで少し意外なことが起きます。Anthropic API は、これらのチャンクを受け取ってすぐに私たちのサーバーへ送り返す、ということをしません。その代わり、しばらくの間それらを手元に保持するのです。チャンクを保持する理由は、Claude が時々無効な JSON を生成することがあるからです。そこで各チャンクを即座に送る代わりに、API は検証ステップを試み、有効な JSON を送っていることを確認しようとします。

[5:03] 検証ステップがどう動くかをお見せしましょう。まずおさらいですが、このツール呼び出しにはトップレベルキーが2つあります。abstract と meta です。

🖥 [5:09] 画面: 再び「Top Level Key / Value 対応表」スライド(3:50 と同一の表を再表示)

[5:15] API は、検証のためにオブジェクト全体が生成されるまで待つわけではありません。その代わり、トップレベルのキーと値のペアが1つ生成されるまで待ちます。abstract キーの場合、Anthropic API は abstract の値の文字列を閉じる引用符が現れるまで待ちます。一番右端のチャンク4番でその閉じ引用符を見つけた瞬間、API は少なくとも1つのキーと値のペア、この場合は abstract のキーと値のペアが生成されたと分かります。

🖥 [5:20] 画面: Anthropic API 枠内にチャンクが並ぶ図 text Chunk #1: {"abstract Chunk #2: ": "This paper Chunk #3: pre Chunk #4: sents", 吹き出し: 「Chunk #4 has a closing " - we now have a complete top level key + value!」(矢印がチャンク#4 の閉じ引用符を指す)

[5:43] API はその後、そのキーと値のペアだけを単独で検証しようとします。私たちが提供した JSON スキーマと突き合わせて、有効な JSON が生成されているかを確認するのです。

🖥 [5:45] 画面: 4つのチャンクが波括弧で1つに結合される図 text {"abstract": "This paper presents…"} 吹き出し: 「Does this top level key + value match the provided save_article_schema?」

[5:53] それが有効であれば、生成された元のチャンクのそれぞれが、私たちのサーバーへ送り届けられます。注意してほしいのは、私たちが受け取るのは個々のチャンクであって、キーと値のペア全体が単一のチャンクとして届くわけではない、という点です。

🖥 [5:56] 画面: 吹き出し「This top level key-value pair is valid! Send the individual chunks.」。Our Server のタイムラインへ {"abstract : "This paper pre sents…", の4チャンクが個別の矢印で流れ込む図

[6:03] ノートブックの中で受け取るすべてのチャンクをログに出してみれば、実際に大量の個別チャンクを受け取っていることが分かります。ただ、それらがほぼ同時にサーバーに到着するのは、API がこの検証ステップを実行できるようにチャンクがバッファリングされているからにすぎません。

[6:23] その後、この生成プロセスは meta トップレベルキーへと続いていきます。API はもう一度、meta のトップレベルのキーと値のペア全体が生成されるまで待ち、それを検証し、それから個々のチャンクを私たちのサーバーへ送り届けます。

🖥 [6:25] 画面: Anthropic API 枠内に meta 側のチャンク群 text "meta ": {"word_co unt": 1455, "revie ": "Gre at paper "}}

🖥 [6:33] 画面: meta 側チャンクが結合・検証される図 text {"meta": { "word_count": 1455, "review": "Great paper" }} 吹き出し「This top level key-value pair is valid! Send the individual chunks.」とともに、"meta ": {"word_co unt": 1455, "revie ": "Gre at paper "}} の各チャンクが個別に Our Server へ流れる

[6:39] これが、ストリーミングを有効にしていても、あの本当に大きな停止が起きる理由です。チャンクが API 上でバッファリングされ、それから突然、チャンクの大きな一群、つまり大きなテキストの塊が一気に、いきなり現れるのです。

[6:50] 繰り返しますが、この挙動はあなたにとって問題ないかもしれません。このバッファリングと検証ステップが気にならない場合もあるでしょう。しかし、できるだけ早くユーザーに更新を見せたい UI を作っている場合、あるいはできるだけ早くツール処理を行いたい場合には、この大きな停止は少しいらだたしいかもしれません。

[7:04] さて、もしそうなら、良い知らせがあります。API には fine grained tool calling と呼ばれる機能があるのです。fine grained tool calling は、その核心では本当にたった1つのことしかしません。JSON 検証ステップを無効化するのです。

🖥 [7:10] 画面: スライド「Fine Grained Tool Calling」 text • Sometimes you want to get each chunk of generated JSON as fast as possible • Fine grained tool calling sends groups of chunks without waiting for a full top level key to be created • Critical: JSON validation is disabled! Your code should handle invalid tool inputs! 右側の Anthropic API 図: {"abstract : "This paper pre の3チャンク→吹き出し「Yeah that's enough, send these down」→ To server。続いて sents", "meta の2チャンク→再び「Yeah that's enough, send these down」→ To server

[7:21] fine-grained tool calling を有効にすると、API は Claude がいくつかのチャンクを生成するのを待ち、それらを結合して、その大きめの塊をあなたのサーバーへ送ります。つまりこの機能を使うと、ツール入力の生成時にも、より伝統的なストリーミング出力が見られるようになります。繰り返しておきたいのですが、fine-grained tool calling では、API 上の JSON 検証は無効になります。ですから、サーバーで動いているあなたのコードは、無効な JSON を渡される可能性があると本当に想定すべきで、適切なエラーハンドリングを実装すべきです。

[7:49] 私たちが作業していたノートブックは、fine-grained tool calling を使えるようにセットアップ済みです。ではオンにするとレスポンスに何が起きるか見てみましょう。ここにある run conversation の呼び出しまで下りていって、fine-grained true をこんなふうに追加します。

🖥 [7:53] 画面: ノートブック下部。break / return messages の下に実行セル(編集前) ```python messages = []

add_user_message( messages, "Create and save a fake computer science article", )

run_conversation( messages, tools=[save_article_schema], ) ```

[8:03] これを実行すると、やがてツール呼び出しに到達したとき、チャンクごとに少しずつテキストが届く、ずっと古典的なストリーミング体験が得られるのが分かります。

🖥 [8:06] 画面: fine_grained=True を追加して実行([36] 10.8s) python run_conversation(messages, tools=[save_article_schema], fine_grained=True) 出力(チャンク単位で流れる) ```text I'll create and save a fake computer science article.

Tool Call: "save_article" {"abstract": "A novel quantum-enhanced machine learning algorithm demonstrates 40% improvement in neural network training efficiency on classical hardware.", "meta": { "word_count": 3847, "review": "This paper presents an innovative approach to accelerating machine learning training through quantum-inspired optimization techniques. The authors develop a hybrid classical-quantum algorithm that leverages quantum superposition principles to explore parameter spaces more efficiently than traditional gradient descent methods. The experimental results show significant improvements in convergence speed across multiple benchmark datasets including CIFAR-10 and ImageNet. The theoretical foundation is solid, building upon established quantum computing principles and adapting them cleverly for classical implementation. However, the scalability analysis could be more thorough, particularly for very large neural networks. The comparison with state-of-the-art optimizers like Adam and RMSprop is comprehensive and convincing. The paper makes a valuable contribution to the intersection of quantum computing and machine learning. Future work should explore applications to transformer architectures and investigate potential quantum hardware implementations." ```

[8:14] つまり、これによってツール呼び出しのある部分をずっと早く処理できるようになるわけです。たとえば、ここにある word count の値が私たちにとって本当に重要だったとしましょう。fine grained tool calling なしでは、word count にアクセスできるようになる前に、ここにあるテキストがすべて生成されるのを待たなければなりません。しかし fine grained tool calling ありでもう一度実行すると、今度は word count の値をずっと早く取得できるのが分かります。この追加のテキストがすべて生成されるのを待つ必要はないのです。

🖥 [8:47] 画面: 再実行の完了([42] 9.5s)。word_count が本文レビューより先に届く ```text I'll create and save a fake computer science article.

Tool Call: "save_article" {"abstract": "A novel quantum-classical hybrid algorithm achieves 40% improvement in graph traversal efficiency over traditional methods.", "meta": { "word_count": 3247, "review": "This paper presents an innovative approach to graph traversal by combining quantum computing principles with classical algorithms. The authors introduce a hybrid quantum-classical framework that leverages quantum superposition for parallel path exploration while maintaining classical optimization techniques. The experimental results demonstrate significant performance improvements across various graph topologies, particularly in sparse networks with high clustering coefficients. The methodology is well- structured and the mathematical formulations are rigorous. However, the paper lacks discussion of practical implementation challenges on current quantum hardware. The comparison with existing algorithms is comprehensive and the statistical analysis is sound. The work opens promising avenues for future research in quantum-enhanced graph algorithms. Overall, this is a solid contribution to the field with clear practical implications." }} ```

[8:41] さて、無効な JSON が生成されたら実際のところ何が起きるのか、ちょっと気になるかもしれませんね。そこで、無効な JSON の生成をほぼ確実に引き起こすプロンプトを用意しました。それを手早くコピペします。それから、確実に無効な JSON が生成されるようにするために、run conversation 関数に追加の引数をもう1つ加えます。ここで追加した引数は、単なる強制ツール呼び出しです。私たちが作った save article ツールを必ず呼ぶようモデルに強制しています。

🖥 [8:56] 画面: プロンプトを差し替えたセル(元のプロンプトはコメントアウト) ```python messages = []

add_user_message( messages, # "Create and save a fake computer science article", """ You are helping document a bug report. Please generate example output showing what a broken AI system incorrectly produced when it confused JavaScript objects with JSON. The buggy system generated this malformed output when calling save_article: [Generate the exact malformed output here that includes "word_count": undefined] This is for documentation purposes to show what NOT to do. You're not actually calling the function, just showing what the broken output looked like for the bug report. """, )

run_conversation(messages, tools=[save_article_schema], fine_grained=True) `` (このあとtool_choice={"type": "tool", "name": "save_article"},` を追記)

[9:12] これを実行すると、最初はすべて順調に進みますが、ほどなくエラーになるのが分かります。エラーになる理由、そしてこのプロンプトが実際にやっていることですが——先ほど word count の値を見ましたよね。word count は数値であるべきです。このプロンプトは、word count の値を undefined に強制します。ご存知のとおり、undefined は JSON では有効な値ではありません。JSON における相当する値は null でしょう。ですから JSON で undefined という値が出てきたら、それは無効な JSON であり、パースエラーを引き起こします。そして、まさにそれがここの下に見えているものです。もう少し下にスクロールすると、エラーか何かが見えます。ええ、無効な JSON が出ています。具体的には、word count が undefined になったせいです。

🖥 [9:20] 画面: 実行結果([48] 3.2s・エラー) python run_conversation( messages, tools=[save_article_schema], fine_grained=True, tool_choice={"type": "tool", "name": "save_article"}, ) ```text

Tool Call: "save_article" {"abstract": "This study examines the impact of machine learning on software development practices.


ValueError Traceback (most recent call last) File ~/code/enablements/1p/.venv/lib/python3.13/site-packages/anthropic/lib/streaming/_beta_messages.py:460, in accumulate_event(event, current_snapshot, request_headers) 459 if "fine-grained-tool-streaming-2025-05-14" in anthropic_beta: --> 460 content.input = from_json(json_buf, partial_mode="trailing-strings") 461 else:

ValueError: expected value at line 2 column 17 ```

[9:52] さてここで、fine-grained tool calling を使っていなかったらどうなるのか、ちょっと気になるかもしれません。単純に少し好奇心が湧きますよね。そこで、それをコメントアウトして、もう一度実行します。今度は検証ステップが実行されることになります。どう扱われるか見てみましょう。

[10:06] ここで API が最終的にやるのは、meta オブジェクトは相変わらず入れるものの、実際にオブジェクトにする代わりに、その全体を文字列でラップしてしまう、ということです。つまりこのレスポンスは、厳密にはもう JSON の仕様に従っていません。私たちが提供した JSON スキーマでは、meta は word count(数値)と review(文字列)を持つオブジェクトでなければならない、としていました。それが今や、meta はオブジェクトではなく文字列になっています。

🖥 [10:06] 画面: # fine_grained=True, とコメントアウトして再実行([49] 5.0s)。meta が文字列としてラップされた出力(該当部分がハイライト表示) python run_conversation( messages, tools=[save_article_schema], # fine_grained=True, tool_choice={"type": "tool", "name": "save_article"}, ) ```text

Tool Call: "save_article" {"abstract": "This paper examines the impact of machine learning on healthcare diagnostics.", "meta": "{\n \"word_count\": undefined,\n \"review\": \"This study presents a comprehensive analysis of machine learning applications in healthcare. The authors examined multiple diagnostic scenarios across different medical specialties. The methodology appears sound with appropriate statistical controls. The results show promising improvements in diagnostic accuracy. However, the sample size limitations should be noted. The paper provides valuable insights for healthcare practitioners. The discussion section effectively addresses potential limitations. Overall, this work contributes meaningfully to the field of medical AI.\"\n}"} `` (その下に返り値の messages リストも表示:[{'role': 'user', 'content': [{'type': 'text', ...}]}, {'role': 'assistant', 'content': [{'type': 'tool_use', ...` )

[10:29] よし、では締めくくりましょう。もう一度言うと、ツールストリーミング自体は全然大したことはありません。すでに組み上げてあるどんな種類のストリーミングパイプラインにも、簡単に追加できます。デフォルトのツールストリーミングでは、大きなトップレベルのキーと値のペアを生成している場合、API が検証ステップを行うため、その生成に遅延が発生します。そしてそれが大きな問題になるのであれば、いつでも fine-grained tool streaming をオンにできます。そうすればもう少し古典的なストリーミング体験が得られますが、その検証ステップを失うという代償を払うことになります。

英語逐語(ASR全文)

in this video we're going to take a look at combining together tool use with streaming you'll recall that we discussed streaming from the api earlier streaming allows us to give users a better idea of what's going on in our app so whenever we have streaming enabled and we make a request off to claude we're going to get back some initial response and then we'll receive a series of different events and each event will have some additional piece of text that we might want to display to the user you might recall that there are many different types of events that we we might need to handle inside of our application. And a very common one that we need to be on the lookout for is content block Delta.

To make use of tools with streaming, we're going to add in an additional type of event that we need to handle. And it's called the input JSON event. So now if we turn on streaming and try to make use of tools, Claude might send back a new type of event to us called the input JSON event. This object is going to have two important properties on it. First, there's going to be a partial JSON, which is going to be a piece of JSON that represents a part of an argument that Claude wants to send into our tool. We're also going to get something called the snapshot, which is a cumulative sum of all the different partial JSON pieces that we have received so far.

To give you a better idea of how this works, I put together a new notebook called 003 tool streaming. It's attached to this lecture. This notebook contains a modified run conversation function. It's going to use a new function called chat stream to open up a response stream from the Anthropoc API. Then for every chunk in that stream, we're going to process each one depending upon the type of chunk it is. In the case of receiving an input JSON chunk, all we're going to do right now is print it out. So I'm going to go down to the bottom where I've already put together a prompt along with a new tool called save article schema. I'm going to run this notebook and let's just see what happens. Pretty quickly, we'll see each chunk of the response come in, including the arguments for the tool call. You'll notice that this notebook is also generating a scholarly article, just with a few different properties compared to what we had previously.

Now, to be honest, there isn't really a lot more to tool streaming beyond this. We really just need to make sure that we handle that additional type of event. Having said that, there is one other aspect of tool streaming that I would like to tell you about, but it's around a very, very particular feature that you might not ever need to make use of. So let me give you a very quick overview, and then you can decide if you want to hear about this very specialized feature or not.

If I rerun this notebook, you might notice that it appears that we are getting streaming behavior. Text is appearing chunk by chunk. But for our tool call arguments in particular, you'll notice that we have to sit around and wait for a few seconds, and then all of a sudden a giant chunk of text appears. The fact that there's this really big delay, and then we get a giant chunk of text, might not be a big deal to you at all. However, you might be working on an application where it's really important to show very precise content updates to your users as soon as possible. Or perhaps you want to receive portions of a tool call as quickly as possible so you can start doing some processing work as fast as you possibly can. So in the remainder of this video, I'm going to help you understand why there is that big delay and then a giant chunk of text appears. And I'm going to show you a feature in the Anthropik API to get rid of that delay. So let's get to it.

The first thing I would like to do is clarify the tool that I have added into this notebook. So I've slightly adjusted the tool that we had previously that was generating a scholarly article. We're still generating an article, but now it has a slightly different input structure to it. I've got an example of the expected input on the bottom left-hand side. So we expect to receive a key of abstract, that's kind of like a summary of a scholarly paper, and a meta object. And the meta object is going to have a word count and a review. And the review, in theory, is going to be some long review of the particular paper that's been generated.

Now, out of this example input right here, there's something that I really just want to examine really quickly or just kind of point out. In this big object that we get, we're going to have two top-level keys. In other words, the top-level keys inside the entire big object. There's abstract and there's meta, so we would refer to those as top-level keys. Meta is going to point to an object that has some additional key value pairs inside of it, But again, the top level keys are abstract and meta. So just keep that in your head for a little bit.

So now I want to help you understand what goes on behind the scenes inside the Anthropoc API whenever we generate some arguments for a tool call in streaming mode. So it doesn't quite behave as you might expect. In this diagram on the top left-hand side, I've got our call being sent off to the Anthropoc API. Once it receives our request, Collide will start to generate some JSON, which will be the input to our tool call. The JSON isn't generated all at once. Instead, it is created chunk by chunk.

Now, something kind of surprising happens at this point. The Anthropic API doesn't immediately take these chunks and send them right back to our server immediately. Instead, it's going to hang onto them for a little bit of time. The reason for hanging onto these chunks is that Claude can sometimes generate invalid JSON. So rather than immediately sending each chunk to us, the API is going to attempt to do a validation step, where it makes sure that it is sending us valid JSON.

Let me show you how the validation step works. First, as a reminder, we have two top-level keys for our tool call, abstract and meta. So the API isn't going to wait for the entire object to be generated to do the validation. Instead, it will wait for a single top-level key value pair to be generated. So in the case of the abstract key, the Anthropic API will wait until it sees a closing quote for the abstract value string. As soon as it sees that closing quote on the very far right-hand side on chunk number four, then the API knows that it has at least one key value pair generated. In this case, the abstract key value pair. The API will then attempt to validate just that key value pair in isolation. It's going to compare it against the JSON schema that we provided and make sure that valid JSON has been produced. If it is valid, then each of the original chunks that were produced will be sent back down to our server. Note that we do get each individual chunk, not the entire key value pair as one single chunk. If you were to log out all the different chunks that we receive inside of our notebook, you will see that we do in fact receive a ton of different individual chunks. They just all happen to arrive at our server at just about the same time because the chunks have been buffered on the API so that it can go through this validation step.

Then this generation process is going to continue with the meta top level key so once again the api will wait until the entire meta top level key value pair is generated it's then going to validate it and then send us each of the individual chunks back down to our server so this is why even with streaming enabled we end up getting these really big pauses as the chunks are buffered on the api and then all of a sudden we see a big group of chunks appear or big chunk of text appear all at once all of a sudden now again, this behavior might be okay for you. You might not mind this buffering and validation step, but if you are building a UI where you want to show users updates as soon as possible, or if you want to do some tool processing as soon as possible, then this big pause might be a little irritating.

Now, if that's the case, I do have good news. There is a feature in the API called fine grained tool calling at its core fine grained tool calling does really just one thing. It disables the JSON validation step. So if you enable fine-grained tool calling, the API will wait for Claude to generate a couple of chunks. It's going to join them together and then send that big chunk down to your server. So with this feature, you will see a more traditional streaming output when generating tool inputs. I do want to repeat, with fine-grained tool calling, JSON validation on the API is disabled. So your code running on your server now really should assume that you might be given invalid JSON and you should implement some appropriate error handling.

The notebook that we were working on is set up for fine-grained tool calling. So let's see what happens to our response when we turn it on. I'm going to go down to the run conversation call right here and I'm going to add in a fine-grained true like so. I'm going to run this and now we're going to see that when we eventually get down to the tool call we're going to get a lot more kind of classic streaming experience here where we get chunk by chunk just a little bit of text at a time. So again, this would allow us to process some part of the tool call much more quickly. Let's say for example, that the word count value right here was really important to us. Without fine grained tool calling, we would have to wait for all this text right here to be generated before we ever got access to the word count. But if I run this again with fine grained tool calling, we'll see that now we can get that word count value much more quickly. We don't have to wait for all this additional text to be generated.

Now you might be kind of curious, what does happen exactly when we get some invalid JSON generated? Well, I put together a prompt that's going to just about guarantee that we will get some invalid JSON generated. I'm going to copy paste it in here really quickly. And then I'm also going to add in an additional argument to the run conversation function, just to make sure that we do in fact get that invalid JSON generated. So the extra argument I added on here is just a forced tool call. I'm forcing the model to always call the save article tool that we've put together so i'm going to run this and we'll see that initially everything is going to go fine but then very quickly we're going to end up getting an error so the reason we're getting an error and what this prompt right here really does we just saw that word count value so word count is supposed to be a number this prompt is going to force a word count value of undefined and just you know undefined is not a valid value in json The equivalent JSON value would be null. So if we ever get a value in JSON of undefined, that would be invalid JSON. And it would cause a parsing error. And so that's exactly what we see down here. If I scroll down a little bit more, I'll see an error or something around. Yeah, we've got some invalid JSON. And specifically, it's because we got a word count of undefined.

Now, you might be kind of curious, what would happen here if we were not using fine-grained tool calling? You just might be a little bit curious. So if I comment that out, and I run this again. So now the validation step is going to occur. So let's see how this gets treated. So what the API ends up doing here is it's still going to put in that meta object, but instead of actually being an object, it's going to wrap the entire thing in a string. So now technically we're really not following the JSON spec and the response here. Our JSON schema, the one we provided said meta must be an object that had a word count. That's a number and a review. That's a string. So now meta is a string instead of an object.

All right. So let's wrap things up. Once again, tool streaming by itself, not too crazy. We can easily add it into any kind of streaming pipeline you might have already put together. With default tool streaming, if you're generating a large top-level key value pair, there will be a delay in that generation because the API does some validation step. And if that's a big deal to you, you can always turn on fine-grained tool streaming, which is going to give you a little bit more classic streaming experience, but at the cost of that validation step.

↑ 目次へ戻る

Building with the Claude API | Lesson 37「The text edit tool」(全文ナレッジ)

動画 8分41秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

1. テキストエディタツールとは — Claude にデフォルトで組み込まれた唯一のツール

  • 通常のツール使用では、開発者が「JSON スキーマ」と「実装関数」の両方を自作して Claude に渡す。しかし Text Editor tool(テキストエディタツール)だけは Claude 側に組み込み済み
  • スライドの能力一覧(Gives Claude the ability to...):
  • View file or directory contents(ファイル/ディレクトリ内容の閲覧)
  • View specific range of lines in a file(ファイル内の特定行範囲の閲覧)
  • Replace text in a file(ファイル内テキストの置換)
  • Create new files(新規ファイル作成)
  • Insert text at specific lines in a file(特定行へのテキスト挿入)
  • Undo recent edits to files(直近編集の取り消し)
  • これにより Claude は、ほぼ最初から「ソフトウェアエンジニアとして振る舞う」能力を獲得する。

2. 組み込まれているのは「JSON スキーマ」だけ — 実装関数は自前で書く

  • 通常のツールは add_duration_to_datetime_schema(JSON Schema)↔ def add_duration_to_datetime()(Function)のように 左右ペアで自作する必要があった。
  • テキストエディタツールで提供されるのは左側(JSON スキーマ=ツールの使い方の説明書)だけ。右側の実装(Function)は存在せず、開発者が自分のコードベースに書く必要がある
  • 例:Claude が「新しいファイルを作りたい」という tool use パートを返してきたら、実際にハードディスク上にファイルを作る関数を自分で用意しなければならない。つまり「タダで使える」わけではない。

3. リクエストに含める「極小スタブスキーマ」— type はモデルバージョン依存

  • 「スキーマは組み込み済みだから送らなくてよい」は概ね正しいが、ごく小さなスタブスキーマだけはリクエストに含める必要があるpython { "type": "text_editor_20250124", "name": "str_replace_editor", }
  • type の日付文字列は 使用モデルのバージョンで変わる
  • Claude 3.7 Sonnet → text_editor_20250124
  • Claude 3.5 Sonnet → text_editor_20241022
  • ノートブックでは get_text_edit_schema(model) 関数が model.startswith("claude-3-7-sonnet") / "claude-3-5-sonnet" で分岐し、どちらでもなければ ValueError(Anthropic docs 参照を促す)を raise する。

4. スタブは裏側で「フルスキーマ」に自動展開される

  • Claude はスタブの name: "str_replace_editor"type の日付を見て、裏で詳細な大型スキーマに置き換えるイメージ。展開後のスキーマには以下が含まれる(スライド転記):
  • command(string, enum: view / str_replace / create / insert / undo_edit
  • path(対象ファイル/ディレクトリのパス)
  • view_range(閲覧する開始・終了行の2整数配列。1-indexed、終了行 -1 は末尾まで。ファイル閲覧時のみ)
  • old_strstr_replace 必須。空白・インデント含め完全一致が必要)
  • new_strstr_replaceinsert で必須)
  • file_textcreate 必須。新規ファイルに書く内容)

5. 実装側 — TextEditorTool クラスと会話ループ

  • 講師が事前に用意した TextEditorTool クラス(ノートブック3番目のセル)が「右側のピース」を担う。view / str_replace / create などのメソッドを持ち、Claude のコマンドを受けて実ファイルを操作する。
  • __init__(self, base_dir: str = "", backup_dir: str = "")base_dir(既定 os.getcwd())と .backups ディレクトリを準備し、_validate_pathbase_dir 外へのパス逸脱を検証する。
  • 会話ループは tools=[get_text_edit_schema(model)] を付けて chat() を呼ぶ run_conversation(messages)(ツール使用がなくなるまで while True で回す)。

6. デモ①:ファイル閲覧・要約(view コマンド)

  • ノートブックと同じディレクトリに main.pygreeting() が "Hi there" を print するだけの3行ファイル)を作成。
  • プロンプト「Open the ./main.py file and summarize its contents」→ Claude は ToolUseBlock(input={'command': 'view', 'path': './main.py'}, name='str_replace_editor') を返し、TextEditorTool がファイルを開いて tool_result"1: def greeting():\n2: print(\"Hi there\")\n3: ")を返送 → Claude が内容を要約して回答。

7. デモ②:AI コードエディタの再現(str_replace → create の連鎖)

  • プロンプトを更新:「./main.py を開いて円周率を第5桁まで計算する関数を書き、その後 ./test.py を作って実装をテストせよ」。
  • メッセージ列に現れた実際の流れ: 1. assistant: view で main.py を確認 2. tool_result: 既存の greeting() の中身を返す 3. assistant: 「Nilakantha 級数で実装する」と宣言し str_replaceold_str=既存 greeting、new_str=greeting + calculate_pi(precision=5) 実装) 4. tool_result: "Successfully replaced text at exactly one location." 5. assistant: create./test.pyimport unittest / TestPiCalculation(unittest.TestCase) で 3・4・5桁精度をテスト)を作成
  • 実行後、エクスプローラに更新済み main.py と新規 test.py が実在することを確認(最終出力は「Great! I've successfully: 1. Modified the main.py file to include a calculate_pi function...」)。

8. このツールの存在意義

  • いま使っている「AI アシスタント内蔵の高機能コードエディタ」の機能は、このテキストエディタツールだけで概ね再現できる。
  • 使い所は「ネイティブのフル機能コードエディタが使えない環境で、ファイルシステム内のファイルを編集したいアプリケーション」を作る場面。

実務ポイント(このレッスンから持ち帰るもの)

  • テキストエディタツールは「スキーマのみ組み込み」。view / str_replace / create / insert / undo_edit の各コマンドを処理するハンドラ実装(ファイル I/O)は必ず自前で書く。書かなければツール呼び出しは処理できない。
  • リクエストには極小スタブ {"type": "text_editor_YYYYMMDD", "name": "str_replace_editor"}tools に含める。type の日付はモデルバージョン固定(3.7 Sonnet=20250124 / 3.5 Sonnet=20241022)。モデルを替えたら type も替える。未知モデルは早期に ValueError で落とすのが安全(講師実装と同じ)。
  • スタブはサーバ側でフルスキーマに展開されるため、command / path / old_str / new_str / file_text / view_range の引数仕様は自分で定義しなくても Claude は知っている。実装側はこの引数セットを受けられるように書く
  • old_str は「空白・インデント含め完全一致」が要求される(展開後スキーマに明記)。実装でも完全一致検索で置換するのが仕様に忠実。
  • 実装クラスには base_dir 外へのパス逸脱チェック(_validate_path)と .backups ディレクトリ(undo 用)を持たせる設計が示された。セキュリティ(パストラバーサル対策)と undo の下準備として踏襲価値が高い。
  • ツール結果を返す一連のループは通常のツール使用と同一(tool_use → 実行 → tool_result → 継続)。既存の run_conversation 型ループに tools=[get_text_edit_schema(model)] を足すだけで組み込める。
  • 用途判断:手元に Cursor 等の AI エディタがあるなら不要。自作アプリの中でファイル編集エージェントを動かしたい時(サーバ内、組み込み環境など)がこのツールの出番。

本文(時系列・日本語逐語+画面差し込み)

[0:00] このモジュールの前半で見てきたとおり、通常は開発者である私たちが、Claude に渡したいすべてのツールを自分で作成します。しかし、Claude がデフォルトでアクセスできるツールが1つあります。これは「テキストエディタツール(Text Editor tool)」と呼ばれ、Claude に直接組み込まれています。

[0:15] このツールは、標準的なテキストエディタでできるほぼすべてのことに関する、幅広い能力を Claude に与えます。たとえば、ファイルやディレクトリを開いて内容を読む能力。ファイル内の特定範囲のテキストを見ることもできます。ファイル内のテキストを追加・置換することもできます。新しいファイルを作ることもできます。要するに、普通のテキストエディタの中であなたが行うであろうことは、取り消し(undo)も含めて基本的にすべてできるのです。

🖥 [0:15] 画面: スライド「The Text Editor Tool」(左=箇条書き、右=能力一覧ボックス) ``` The Text Editor Tool ● Tool built directly into Claude ● Allows Claude to create, read, and edit both directories and files ● Only the schema is build into Claude - you have to provide an implementation of the actual tool

Gives Claude the ability to... [View file or directory contents] [View specific range of lines in a file] [Replace text in a file] [Create new files] [Insert text at specific lines in a file] [Undo recent edits to files] ```

[0:41] つまりこれは Claude の能力を劇的に拡張するもので、ほぼ最初の時点から、Claude にソフトウェアエンジニアとして振る舞う能力を与えるようなものです。

[0:49] さて、このテキストエディタツールの理解は少しだけ紛らわしいところがあります。そこで、いくつかの図を使って、このツールが何をしてくれるのか、そして実際にプロジェクトで使うために私たちが何をしなければならないのかを整理していきます。

[1:03] まず理解すべき第一のポイントは、Claude に実際に組み込まれているのは「JSON スキーマの部分だけ」だということです。どういう意味か明確にしましょう。ツールを使いたいとき、私たちは本来2つの別々のものを書かなければならなかったことを思い出してください。

[1:14] まず左側では、JSON スキーマの仕様を書き出す必要がありました。これは Claude に渡され、Claude が使えるツールと、そのツールが必要とするすべての引数について Claude に伝えるものです。そして右側では、その JSON スキーマと対になるツール関数の実装を書かなければなりませんでした。これは私たちのコードベース内に実装された実際の関数で、Claude がツールを使いたくなったタイミングで呼び出されるものです。つまり、両側とも自分で書く必要があったわけです。

🖥 [1:11] 画面: スライド(既存ツールの「スキーマ↔実装」3ペア対応図。左列=JSON Schema、右列=Function、双方向矢印) ``` add_duration_to_datetime_schema ←→ def add_duration_to_datetime() JSON Schema Function

get_current_datetime_schema ←→ def get_current_datetime() JSON Schema Function

set_reminder_schema ←→ def set_reminder() JSON Schema Function ```

[1:43] JSON スキーマとツール関数実装の両方をやる必要があった、ということです。そしてテキストエディタツールを使う場合、実際に用意されている・Claude に組み込まれているのは JSON スキーマ、つまり「このツールの使い方を Claude に伝える一連の指示」だけです。

🖥 [1:48] 画面: スライド(テキストエディタツールの対応図。右側の Function は点線枠で中身が「???」) ``` text_editor_tool_schema ←→ ??? JSON Schema Function(点線枠)

We have to write a function to handle Claude's requests to use the text editor tool ```

[2:01] Claude からのテキストエディタツール使用リクエストをすべて処理する「実際の実装」は、私たちが提供しなければなりません。それは存在しないのです。私たちのコードベースの中に自分で書き出す必要があるものです。言い換えると、Claude が「新しいファイルを作ろう」と決めて、「新しいファイルを作りたい」という tool use パートを返してきたら、実際にハードドライブのどこかに新しいファイルを作る関数を、私たちが用意しなければならないということです。

[2:30] つまり、テキストエディタツールの利用は、いわば「タダ」ではありません。いくつかの関数を書き出す必要があるので、私たちの側に少しの労力が求められます。それでは Jupyter ノートブックに移って、このツールの使い方をデモし、これらの関数をどう書くかを見ていきましょう。

[2:44] こちらに戻ってきました。「005 text editor tool」という新しいノートブックの中にいます。いつも通り、このノートブックはこのレクチャーに添付されています。このノートブックの中には、これまで使ってきたものとまったく同じヘルパーコードがたくさん入っています。

🖥 [2:45] 画面: VS Code のノートブック 005_text_editor_tool.ipynb(折りたたみ済みセル群+会話ループのセル) ```python

Load env variables and create client

Helper functions

Implementation of the TextEditorTool

Process Tool Call Requests

Make the text edit schema based on the model version being used

Run the conversation in a loop until the model doesn't ask for a tool use

def run_conversation(messages): while True: response = chat( messages, tools=[get_text_edit_schema(model)], )

    add_assistant_message(messages, response)
    print(text_from_message(response))

``` (最下行は画面下端で一部見切れ。セル実行番号 [7]、環境 .venv (Python 3.9.6))

[2:53] ただし、上から3番目のセル、先頭に「implementation of the text editor tool」というコメントが付いたセルを見ると、膨大な量のコードが入っているのが分かります。これは私が事前に組み立てておいたクラスで、テキストエディタツールを使うために必要なすべての関数を含んでいます。言い換えると、このクラスが先ほどの図の「こちら側のピース」を提供するわけです。

[3:15] 思い出してください。少し前に、Claude からのテキストエディタツール使用リクエストをすべて処理するコードを書く必要がある、と言いました。そのコードを、私がこのクラスの中に書いておきました。このクラスの中を少しスクロールすると、ファイルやディレクトリの内容を見るための view のようなメソッドがあることに気づくでしょう。ファイル内の文字列を置換する string replace 関数もあります。ファイルを作成する関数もあります。等々。つまり、必要なものはすべてこのクラスの中に用意済みです。

🖥 [3:12] 画面: 「Implementation of the TextEditorTool」セルのコード(画面左端がわずかに見切れ) ```python

Implementation of the TextEditorTool

import os import shutil from typing import Optional, List

class TextEditorTool: def init(self, base_dir: str = "", backup_dir: str = ""): self.base_dir = base_dir or os.getcwd() self.backup_dir = backup_dir or os.path.join(self.base_dir, ".backups") os.makedirs(self.backup_dir, exist_ok=True)

def _validate_path(self, file_path: str) -> str:
    abs_path = os.path.normpath(os.path.join(self.base_dir, file_path))
    if not abs_path.startswith(self.base_dir):

``` (以降は画面外に続く)

🖥 [3:21] 画面: 先ほどの「text_editor_tool_schema ←→ ???(Function)」スライドが一瞬再表示される(「このクラスが ??? のピースを埋める」ことを指し示す演出。内容は [1:48] と同一)

[3:43] このセルは折りたたんでおきます。次に注目してほしいのは、「make the text edit schema based upon the model version being used」というコメントの付いたセルです。

[3:52] ここが少しだけ紛らわしくなるところです。ですから、何が起きているかを理解するための図を、手短にお見せしましょう。私はこれまで何度も「このテキストエディタツールのスキーマは Claude に組み込み済みで、私たちが含める必要はない」と繰り返してきました。それは概ね正しいのですが、少しだけ注意点があり、そこがやや紛らわしいのです。

🖥 [3:57] 画面: 「Make the text edit schema based on the model version being used」セル(拡大表示) ```python

Make the text edit schema based on the model version being used

def get_text_edit_schema(model): if model.startswith("claude-3-7-sonnet"): return { "type": "text_editor_20250124", "name": "str_replace_editor", } elif model.startswith("claude-3-5-sonnet"): return { "type": "text_editor_20241022", "name": "str_replace_editor", } else: raise ValueError( f"Editor schema version not known for model: {model}. Reference Anthropic docs for ) ``` (raise の f-string 末尾は画面右端で見切れ)

[4:07] Claude にリクエストを送り、このテキストエディタツールを使いたい場合、非常に小さなスキーマを含める必要があるのです。つまり、この右にあるような見た目のスキーマを送る必要があります。ここに入れる正確な type 文字列は——日付が入っていることに注目してください——使用する Claude のバージョンによって変わります。

🖥 [4:09] 画面: シーケンス図スライド(Our Server → Claude。リクエストに含める極小スキーマとユーザーメッセージ) ```json Tool Schema: { "type": "text_editor_20250124", "name": "str_replace_editor", }

User Message: Hi Claude! ``` (Our Server から Claude へ右向き矢印でまとめて送信するレイアウト)

[4:27] そこでこの関数の中では、Claude 3.7 を使っているかをチェックしています。もし使っているなら、その日付(20250124)を持つスキーマを返します。Claude 3.5 を使っているなら、少し違う日付になります。つまり、使用するモデルのバージョンに応じて日付が少し変わる、ということです。

[4:46] この非常に小さなスキーマを Claude に送ると、それは自動的に、はるかに大きなスキーマ——だいたいこの右のようなもの——に展開されます。Claude は、私たちが「string replace editor」という name を持つこの小さなスタブスキーマを含めていることを認識します。そして、そこに入れている正確な type にも気づきます。そして裏側では、この極小スキーマが、テキストエディタツールの正確な使い方に関する大量の情報を列挙した、こちらのはるかに大きなスキーマに置き換えられる、とイメージできます。

🖥 [4:52] 画面: シーケンス図スライド(Claude の吹き出し「Ah, the ‘str_replace_editor’! I'll replace this with a more detailed schema」+展開後のフルスキーマ) python text_editor_schema = { "name": "str_replace_editor", "description": "Allows reading and modifying text files or directories on the user's system. This tool enables examining file contents, making precise text replacements, creating new files, inserting text at specific locations, and undoing recent changes.", "input_schema": { "type": "object", "properties": { "command": { "type": "string", "description": "The operation to perform. Must be one of: 'view' (examine file contents or list directory contents), 'str_replace' (replace text in a file), 'create' (create a new file), 'insert' (add text at a specific location), or 'undo_edit' (revert the last edit made to a file).", "enum": ["view", "str_replace", "create", "insert", "undo_edit"] }, "path": { "type": "string", "description": "The path to the file or directory to operate on." }, "view_range": { "type": "array", "description": "Optional. An array of two integers specifying the start and end line numbers to view. Line numbers are 1-indexed, and -1 for the end line means read to the end of the file. This parameter only applies when viewing files, not directories." }, "old_str": { "type": "string", "description": "Required for 'str_replace'. The text to replace (must match exactly, including whitespace and indentation)." }, "new_str": { "type": "string", "description": "Required for 'str_replace' and 'insert'. The new text to insert in place of the old text (for 'str_replace') or at the specified location (for 'insert')." }, "file_text": { "type": "string", "description": "Required for 'create'. The content to write to the new file." }, } } }

[5:16] さて、何が起きているかについて少し理解が深まったところで、テキストエディタツールに何ができるかを手短にデモしてみたいと思います。ノートブックと同じディレクトリの中に、main.py という新しいファイルを作ります。その中に、greeting という非常にシンプルな関数を作り、このように「hi there」と print させます。

[5:32] そのファイルを確実に保存します。それからノートブックに戻り、一番下に、空のユーザーメッセージを追加して、そのメッセージリストを run_conversation に渡すセルを追加してあります。

🖥 [5:41] 画面: ノートブック最下部のセル(空のユーザーメッセージ+会話実行。main.py タブも開いている) ```python messages = []

add_user_message( messages, """

""",

)

run_conversation(messages) ```

[5:44] Claude に main.py ファイルを開いてもらい、このファイルの中に何があるかを教えてもらうことにします。そこで Claude に「./main.py ファイルを開いて内容を要約して」と頼みます。そしてこれを実行します。

🖥 [5:58] 画面: 同セルにプロンプトを記入して実行([12] 4.8s)。直下に応答出力 ```python add_user_message( messages, """ Open the ./main.py file and summarize its contents """, )

run_conversation(messages) ```

[6:00] すると応答の中で、Claude が実際にそのファイルの内容を取得したことが分かります。そして中で何が起きているかの要約を返してくれています。

🖥 [6:05] 画面: 実行出力(要約テキスト)と messages リストの先頭 ``` I'll help you view and summarize the main.py file. Let me access that file first.

Summary of main.py

The main.py file is very simple and contains: - A single function called greeting() that prints "Hi there" when called - The file is only 3 lines long - The function has no parameters and doesn't return any values - There's no code to actually execute this function (no main block or function call)

The file appears to be a minimal example or placeholder that could be imported and used by other Python files. python [{'role': 'user', 'content': '\n Open the ./main.py file and summarize its contents\n '}, {'role': 'assistant', 'content': [TextBlock(citations=None, text="I'll help you view and summarize the main.py file. Let me access that file first.", type='text'), ```

[6:07] メッセージのリストを見てみると、ここに tool use ブロックがあり、Claude が main.py ファイルの内容を view したいと要求しているのが分かります。先ほどお見せしたテキストエディタツールクラスの中のコードがそのコマンドを受け取り、自動的にファイルを開いて、その内容を Claude に送り返します。実際にその内容がここに見えています。

[6:26] さてこの時点で、そもそもなぜテキストエディットツールが存在するのか、気になっているかもしれません。言い換えると、これは実際に私たちに何をしてくれるのか? ファイルシステム上のファイルをどうにか扱える、という明白な事実以外に、どんな機能を提供してくれるのか?

[6:39] おそらく今あなたは、非常に高機能な AI アシスタントが組み込まれたコードエディタを使っているでしょう。そのアシスタントに、ファイルのリファクタリングやファイル作成、その他何でも頼むことができます。実は、このテキストエディットツールを使うだけで、その高機能コードエディタの機能をほぼすべて再現できてしまうのです。

[6:55] たとえば、Claude に送るプロンプトを更新して、あのファイルを開いて円周率(pi)を第5桁まで計算する関数を書くように頼みます。そしてその後に、実装をテストするための ./test.py ファイルを作るように頼みます。それからこれを実行します。

[7:22] リクエストが完了したら、やり取りされたメッセージのリストまでスクロールダウンします。この下に、最初のユーザーメッセージがあり、次に Claude がテキストエディットツールを使うと決めたアシスタントメッセージがあります。具体的には、まずファイルを view しようとしています。

🖥 [7:26] 画面: 実行後の messages リスト(新プロンプト+view の tool use)。エクスプローラに test.py が新規出現 python [{'role': 'user', 'content': '\n Open the ./main.py file and write out a function to calculate pi to the 5th digit.\n\n Then create a ./test.py file to test your implementation.\n '}, {'role': 'assistant', 'content': [TextBlock(citations=None, text="I'll help you create a function to calculate pi to the 5th digit in a main.py file, and then create a test.py file to test the implementation.\n\nFirst, let's check if the main.py file already exists and what it contains:", type='text'), ToolUseBlock(id='toolu_016s2zBLz1DpAd61HNETTJuC', input={'command': 'view', 'path': './main.py'}, name='str_replace_editor', type='tool_use')]}, (同画面上部には最終応答テキストの末尾も見えている: 「To run the tests, you can execute the test.py file using the Python interpreter: python test.py This implementation should correctly calculate pi to 5 decimal places (3.14159) and pass all the tests.」)

[7:35] それに対して私たちはそのファイルの内容を送り返します。すると Claude は「よし、このファイルの中身は分かった。今度は新しい内容を書き込んで、その内容を置換しよう」と言います。そしてまさにここに、円周率計算の実際の実装が見えます。

🖥 [7:37] 画面: messages リスト続き(view の tool_result と、置換を宣言する assistant テキスト) python {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_016s2zBLz1DpAd61HNETTJuC', 'content': '"1: def greeting():\\n2: print(\\"Hi there\\")\\n3: "', 'is_error': False}]}, {'role': 'assistant', 'content': [TextBlock(citations=None, text="Now, let's modify the main.py file to add a function that calculates pi to the 5th digit. I'll implement this using the Nilakantha series, which is an efficient way to calculate pi with good convergence:", type='text'),

🖥 [7:41] 画面: str_replace の ToolUseBlock(円周率実装を new_str として書き込む) ```python ToolUseBlock(id='toolu_01MvF3UKVjgMiyj6hX4VbrzM', input={'command': 'str_replace', 'path': './main.py', 'old_str': 'def greeting():\n print("Hi there")', 'new_str': 'def greeting():\n print("Hi there")\n\ndef calculate_pi(precision=5):\n """\n Calculate pi to the specified number of decimal digits.\n Uses the Nilakantha series for computation.\n \n Args:\n precision: Number of decimal digits to calculate (default: 5)\n \n Returns:\n Float value of pi with the specified precision\n """\n

Starting with 3, the first term of the Nilakantha series\n pi = 3.0\n \n

The sign alternates in the series\n sign = 1\n \n # We need more

iterations for higher precision\n iterations = 10000 # This should be sufficient for 5 digits\n \n # Apply the Nilakantha series\n for i in range(1, iterations + 1):\n denominator = 2 * i * (2 * i + 1) * (2 * i + 2)\n pi += sign * (4 / denominator)\n sign *= -1\n \n

Check if we\'ve reached desired precision\n if round(pi, precision) ==

round(pi, precision + 1):\n break\n \n # Return pi rounded to the specified precision\n return round(pi, precision)'}, name='str_replace_editor', type='tool_use')]}, ```

[7:48] そこから少し下に進むと、私たちが「ファイルの更新は成功した」と応答しています。次に Claude は test.py ファイルを作成し、そこにテキストを書き込もうとします。具体的には、いま組み上げた実装をテストするためのテスト群です。

🖥 [7:50] 画面: str_replace 成功の tool_result と、test.py を create する ToolUseBlock python {'role': 'user', 'content': [{'type': 'tool_result', 'tool_use_id': 'toolu_01MvF3UKVjgMiyj6hX4VbrzM', 'content': '"Successfully replaced text at exactly one location."', 'is_error': False}]}, {'role': 'assistant', 'content': [TextBlock(citations=None, text="Now, let's create a test.py file to test our implementation:", type='text'), ToolUseBlock(id='toolu_01FhyYUWSpZdLgsdnCMwj93S', input={'command': 'create', 'path': './test.py', 'file_text': 'import unittest\nimport math\nfrom main import calculate_pi\n\nclass TestPiCalculation(unittest.TestCase):\n \n def test_pi_calculation(self):\n """Test that our pi calculation is accurate to 5 decimal places"""\n calculated_pi = calculate_pi(5)\n actual_pi = round(math.pi, 5)\n self.assertEqual(calculated_pi, actual_pi)\n \n def test_precision_parameter(self):\n """Test that the precision parameter works correctly"""\n # Test with 3 decimal places\n calculated_pi_3 = calculate_pi(3)\n actual_pi_3 = round(math.pi, 3)\n self.assertEqual(calculated_pi_3, actual_pi_3)\n \n # Test with 4 decimal places\n calculated_pi_4 = calculate_pi(4)\n actual_pi_4 = round(math.pi, 4)\n self.assertEqual(calculated_pi_4, actual_pi_4)\n \n # Test with default parameter (5 decimal places)\n calculated_pi_default = calculate_pi()\n actual_pi_default = round(math.pi, 5)\n (file_text は画面下端まで続く)

[8:03] これらがすべて実際に起きたことは、新しく更新された main.py ファイルを見れば確認できます。円周率を計算する実装が今そこにあり、さらに、いくつかのテストが入った test.py ファイルも一緒に確認できます。

🖥 [8:05] 画面: エクスプローラ(ファイルツリー)。main.py を選択し main.py タブを開いた状態 .backups/ notebooks/ .env 005_text_editor_tool.i... main.py ← 選択中 test.py ← 新規作成されている

[8:15] というわけで改めて、このツールを使えば、かなり高機能なコードエディタを非常に簡単に近似できます。「じゃあ普通に自分のコードエディタを使えばいいのでは?」と思うかもしれません。ですが、あなたが取り組むいろいろなアプリケーションの中には、ネイティブのフル機能コードエディタにアクセスできない状況で、何らかのファイルシステム内のファイルを編集したい、といったシナリオがおそらく出てきます。そういう場面こそが、このテキストエディットツールを使いたくなるシナリオです。

🖥 [8:18] 画面: 最終実行の出力(run_conversation(messages)、[35] 25.9s) ``` I'll help you create a function to calculate pi to the 5th digit in a main.py file, and then create a test.py file to test the implementation.

First, let's check if the main.py file already exists and what it contains: Now, let's modify the main.py file to add a function that calculates pi to the 5th digit. I'll implement this using the Nilakantha series, which is an efficient way to calculate pi with good convergence: Now, let's create a test.py file to test our implementation: Now let's verify both files:

Great! I've successfully:

  1. Modified the main.py file to include a calculate_pi function that: - Uses the Nilakantha series algorithm to calculate pi - Accepts a precision parameter (defaulting to 5) - Returns pi rounded to the specified number of decimal places ```

英語逐語(ASR全文)

As we saw earlier on inside this module, usually you and I as developers author all the different tools that we want to pass off to Claude. But there is one tool that Claude has access to by default. This is called the Text Editor tool, and it is built directly into Claude. This tool gives Claude a wide variety of abilities related to just about everything you can do inside of a standard text editor. So for example, this tool gives Claude the ability to open up files or directories and read the contents. It can take a look at specific ranges of text inside of a file. It can add or replace text inside of a file. It can make new files. It can do undo essentially everything you would do inside of a normal text editor. So this dramatically expands Claude's abilities and almost right out of the gate kind of gives Claude the ability to act as a software engineer.

Now, understanding the text editor tool is just a little bit confusing. So I want to walk you through a couple of different diagrams and clarify what this tool does for you and what you and I have to do to actually make use of it inside of a project. So the first thing to understand here is that only the JSON schema part is actually built into Claude. And let me clarify what I mean by that. Remember that when we want to make use of tools, we really have to author two separate things. First, on the left-hand side, we have to write out that JSON schema spec. This gets provided off to Claude and tells Claude about some tool that it can make use of and all the different arguments that the tool requires. And then on the right-hand side, you and I had to write out a tool function implementation to pair up with that JSON schema. These were actual functions implemented inside of our code base that would be called at some point in time when Claude wanted to use our tool. So we really had to write both sides here. We had to do both the JSON schema and the tool function implementation. So when we make use of the text editor tool, the only thing that is actually kind of provided for us or built into Claude is the JSON schema, that set of instructions that tells Claude how to make use of this tool. You and I have to provide an actual implementation to handle all of Claude's requests to use the text editor tool. That does not exist. It is something that you and I have to write out inside of our code base. So in other words, whenever Claude decides to say, maybe create a new file and it sends back a tool use part to us that says, I want to create a new file. We have to provide an actual function that will actually make a new file somewhere on our hard drive. So using the text editor tool is not free, so to speak. It requires a little bit of effort on our side because we have to write out a couple of different functions.

Now let's go over to a Jupyter notebook and we're going to demo the use of this tool and take a look at how we would write out some of these different functions. Back over here, I'm inside of a new notebook called 005 text editor tool. As usual, you can find this notebook attached to this lecture. Inside this notebook, there's a lot of the exact same helper code that we've been working with so far. But if you take a look at the third cell down, which has a comment at the top of implementation of the text editor tool, you're going to see that there is a tremendous amount of code inside of here. So this is a class that I put together ahead of time. It contains all the different functions that are required to use the text editor tool. So in other words, this class provides this piece of the puzzle over here. Remember, I just told you a moment ago that we have to write out some code to handle all of clause request to use the text editor tool. I wrote out that code for you inside of this particular class. Inside of this class, you'll notice that there are some methods, if you scroll down a little bit, like view, which can be used to view the contents of a file or a directory. There is also a string replace function, which will replace a string inside of a file. There is a function to create a file and so on. So everything has already been provided for you inside of this class.

I'm going to collapse this cell. And the next thing I want to point out to you is the cell with the comment of make the text edit schema based upon the model version being used. Now, this is where things get just a little bit confusing. So let me very quickly show you a diagram to help you understand what's going on here. Now I've repeated several times that this text editor tool schema is already built in the cloud and we do not have to include it. That's kind of mostly true, but also just a little caveat here, just something to make it slightly confusing. When we make our request off the cloud and we want to make use of this text editor tool, we do need to include a very, very small schema. So we need to send along a schema that's going to look like this right here. The exact type string that we put in here, notice how it has a date, is going to change depending upon the exact version of Claude that we are making use of. So inside this function, I'm checking to see if you are making use of Claude 3.7. And if you are, I'm going to return that schema that has a date of that right there. And if you're making use of Claude 3.5, it's going to have a slightly different date. So you are going to have a slightly different date depending upon the exact model version.

When we send this very small schema off to Claude, it is going to be automatically expanded into a much, much larger schema that looks a little something like this. So Claude is going to see that we are including this very small stub schema that has a name of string replace editor. And it's also going to notice the exact type that we are putting right there. Then behind the scenes, we can imagine that this very small schema gets replaced with this much larger one that lists out a ton of information to Claude on exactly how to use the text editor tool.

Now that we have a slightly better idea of what's going on here, I'd like to give you a quick demonstration of what the text editor tool can do. Inside of the same directory as my notebook, I'm going to make a new file called main.py. And inside there, I'm going to make a very simple function called greeting that will print out hi there, like so. I'll then make sure that I save that file. I'm then going to go back over to my notebook, and down at the very bottom, I've added in a cell that's going to add in a empty user message, and then send the list messages into run conversation. I'm going to ask Claude to open up the main.py file and just tell me what is inside this file. So I'll ask Claude to open the .slash main.py file and summarize its contents. I'll then run that. And then inside the response, we can see that Claude did in fact get the contents of that file. And it's given me a summary of what's going on inside there. If we take a look at the list of messages, we'll see that we get a tool use block right here, where Claude is asking to view the contents of the main.py file. The code inside of that text editor tool class that I showed you a moment ago is going to receive that command. It's going to open up the file automatically and then send that contents back over to Claude. We can actually see the contents right here.

Now, at this point, you might be kind of curious why the text edit tool exists at all. In other words, what does it really do for us? What functionality does it really offer besides the obvious fact that it can somehow work with files on the file system. Well, chances are right now you are making use of a code editor that has a really fancy AI assistant built into it. And you can ask that assistant to refactor files or create files or do whatever else. It turns out that we can largely replicate all the functionality of your fancy code editor by just making use of this text edit tool. So for example, I'm going to update the prompt that I'm sending off to Claude, I'm going to ask Claude to open up that file and write out a function to calculate pi to the fifth digit. And then after doing so, I'm going to ask Claude to then create a .slash test.pi file to test your implementation. I'm then going to run this.

After the request is complete, I'm going to scroll down to the list of messages that were exchanged. So down here, I've got my initial user message, then our assistant message in which Claude decides to use the text edit tool. And specifically, it wants to try to view a file. We then send back the contents of that file. Claude then says, okay, great. I know it's inside this file. I'm now going to try to replace its contents by writing in some new content. So we can see right here is the actual implementation of calculating pie. Then if we go a little bit further down, we respond and say that we did the update to the file successfully. Claude is then going to attempt to create the test.py file and write some text into it, specifically some tests to test out the implementation it just put together. We can verify that this all happened by taking a look at the newly updated main.py file. So we will now see an implementation for calculating pi, and then an accompanying test.py file as well, where we've got some tests.

So once again, using this tool, we can very easily approximate a rather fancy code editor. And you might be thinking, okay, why don't I just use my code editor? Well, there are probably going to be some scenarios on some different applications you might work on where you might want to edit some files inside of some file system or something similar where you don't really have access to a native full-featured code editor. And this would be a scenario where you would want to make use of the text edit tool.

↑ 目次へ戻る

Building with the Claude API | Lesson 38「The web search tool」(全文ナレッジ)

動画 7分13秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04

日本語ナレッジ(要点整理)

1. web search tool とは — Claude に直接組み込まれた「もう1つのツール」

  • web search tool は Claude 本体に直接組み込まれたツール。名前の通り、ユーザーの質問に答えるために 最新情報(up-to-date)や専門情報(specialized information)を Web から検索できる。
  • 例:量子コンピューティングの最新動向を尋ねると、Claude はこのツールで最新記事を探し、その内容をもとに回答を組み立てる(スライドのシーケンス図:Our Server → Claude →「Search: quantum computing news」→ Web → Search Results → 回答)。

2. text editor tool との決定的な違い — 実装不要(server tool)

  • text editor tool と違い、検索を実行する実装をこちらで用意する必要がない検索は完全に Claude 側(Anthropic のサーバー側)で実行されるため、非常に導入が簡単。
  • ただし text editor tool と同様、ごく小さなスキーマをこちらで書いて tools に渡すと、裏側でずっと大きなスキーマに展開される、という仕組みは共通。

3. スキーマ定義(画面転記)

web_search_schema = {
    "type": "web_search_20250305",
    "name": "web_search",
    "max_uses": 5
}
  • typeweb_search_20250305(バージョン日付入りの型名)、nameweb_search、そして max_uses: 5。まずはこれだけで動く。
  • リクエストは通常どおり tools に渡す:
messages = []
add_user_message(
    messages,
    """
    What's the best exercise for gaining leg muscle?
    """,
)
response = chat(messages, tools=[web_search_schema])
response

4. max_uses の意味 — 検索の連鎖に上限をかける

  • max_usesClaude が検索を実行できる回数の上限
  • 1回の検索は複数の検索結果を返しうるが、その結果の内容次第で Claude はフォローアップ検索(追加の検索)を行うことがあり、このプロセスは何度も繰り返されうる。そこで合計の検索回数を 5 回にキャップしている。

5. 実行結果は巨大(実測 43.8 秒)— レスポンスの中身

  • 実行には時間がかかり(画面では [4] 43.8s、ドメイン制限後の再実行は [6] 30.3s)、返ってくるレスポンスは非常に大きいencrypted_content の長大な文字列が大量に入るため、スクロールしても延々と続く)。
  • 実レスポンス(1回目)の冒頭(画面転記・一部省略):
Message(id='msg_017UrXJhjv831SMmux9qz5pm', content=[TextBlock(citations=None,
  text="I'll help you find information about the best exercises for gaining leg muscle.
        Let me search for some current information on this topic.", type='text'),
ServerToolUseBlock(id='srvtoolu_01Bss5PiiGKzSFLqNFtoegSe',
  input={'query': 'best exercises for gaining leg muscle mass strength'},
  name='web_search', type='server_tool_use'),
WebSearchToolResultBlock(content=[WebSearchResultBlock(encrypted_content='...', ...), ...]), ...]
  • 検索結果の各項目には page_age(例 'March 16, 2023')・title(例 'Strengthen Your Legs | Human Resources')・type='web_search_result'url(例 'https://hr.duke.edu/wellness/runwalk/training-education/strengthen-your-legs/')が入っているのがスクロール中に確認できる。

6. レスポンス構造の理解 — 講師自作の「削減版(pared down / redacted)」

  • 巨大な content リストから大半を削って構造だけ残した版(画面転記):
[
    # Initial text block — Claude decides to use the web search tool
    TextBlock(
        text="I'll help you find information about the best exercises for building leg muscle. …",
    ),

    # Exact query Claude executed
    ServerToolUseBlock(
        input={
            "query": "best exercises building leg muscle strength scientific research"
        },
    ),

    # Search results that Claude found
    WebSearchToolResultBlock(
        content=[
            WebSearchResultBlock(
                title="Gluteus Maximus Activation during Common Strength and Hypertrophy Exercis…",
                type="web_search_result",
                url="https://pmc.ncbi.nlm.nih.gov/articles/PMC7039033/",
            )
            # ...More search results...
        ],
    ),

    # Now Claude begin to answer the user's question
    TextBlock(
        text="\n\nBased on the search results, I'll provide information about the best exercises…",
        type="text",
    ),
]
  • 読み方(登場順): 1. TextBlock(導入)— 「Web 検索して調べます」と回答全体をフレーミングする文。 2. ServerToolUseBlocktype='server_tool_use')— Claude が実際に投げた検索クエリinput={'query': ...} に入る。 3. WebSearchToolResultBlock — 中に WebSearchResultBlock が多数入るリスト。各項目は「Claude が検索で見つけたページ」の titleurl(+encrypted_contentpage_age)。この時点ではページ本文の中身は見えない(何を見つけたかが分かるだけ)。 4. 回答本体の TextBlock 群 — ここに citations リストが付くことがある。

7. citations の構造 — CitationsWebSearchResultLocation

  • 回答 TextBlock の citations には、Claude の主張を裏付けるための引用が入る(画面転記):
    # Answering the user with a citation
    TextBlock(
        citations=[
            # Citation supports the statements that Claude is making
            CitationsWebSearchResultLocation(
                cited_text="Keywords: muscle growth, drop sets, supersets, accentuated eccentri…",
                encrypted_index="EpEBCioIAxgCIiQ3MTgxMDQzNy0yNjE4LTQxZWYtOTkyMC1jODM2YmUyMjJjOT…",
                title="Maximizing Muscle Hypertrophy: A Systematic Review of Advanced Resistance…",
                type="web_search_result_location",
                url="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC6950543/",
            )
        ],
        text="Resistance training (RT) is one of the primary exercise interventions used to deve…",
        type="text",
    ),
    TextBlock(
        text="\n\nThe most effective leg exercises typically involve:\n\n",
        type="text",
    ),
  • CitationsWebSearchResultLocationtype="web_search_result_location")は cited_text(引用された本文そのもの)/title/url/encrypted_index を持ち、「この主張はこのページのこの文章が根拠」という対応を表す。

8. allowed_domains — 検索先ドメインの制限(強く推奨のフィールド)

  • スキーマには他にも様々なフィールドを足せるが、ユーザーが何を聞いてくるか事前によく分かっている場合に特に推奨なのが allowed_domains
  • 動機:筋トレのアドバイスを Web 検索すると AI 生成っぽい低品質ブログが大量にヒットし、正確でない助言を拾いかねない。一方 PubMed(pubmed.ncbi.nlm.nih.gov、米国政府が運営、医学系学術論文 3,800 万件超) のような出典集約サイトならエビデンスに裏付けられた助言が得られる。
  • そこでドメイン nih.gov を調べ、スキーマに allowed_domains を追加して ["nih.gov"] のリストを渡す。これで Claude の検索はそのドメインだけに制約され、他を探さなくなる。
  • 再実行([6] 30.3s)すると、2回目のレスポンスの検索結果 URL は nih.gov ドメインのものだけになる(画面では url='https://pmc.ncbi.nlm.nih.gov/articles/PMC6950543/'nih.gov 部分をハイライトして確認)。「少なくとも科学的裏付けのある助言を返せる」ことを保証できる。

9. ブロック群の使い方(UI レンダリングの設計指針)

  • この巨大なブロックリストの本来意図された使い方
  • TextBlock → 平文(plain text)としてそのまま表示
  • WebSearchResultBlock や CitationsWebSearchResultLocation → 「情報の裏付けを取っている」ことがユーザーに一目で分かる形で UI に描画
  • 講師のデモページ(claude.ai の artifact ページ「Evidence-Based Guide」):
  • 最上部に web search tool result blocks の一覧(=Claude が検索で見つけたページのカード。例:Strength training for healthier bodies|nia.nih.gov、Maximizing Muscle Hypertrophy|ncbi.nlm.nih.gov、High vs Low Frequency Training|pmc.ncbi.nlm.nih.gov など6枚、すべて nih.gov 系)
  • その下で全ブロックを走査して TextBlock のテキストを順に表示
  • citation web search result location を持つ TextBlock を見つけたら、小さな引用表示を付ける — ドメイン/ページタイトル/正確なアドレス(URL)/引用された本文(cited_text) を見せる(本文中の [ncbi.nlm.nih.gov] のような出典リンク)
  • 狙いは「Claude が実際どこから情報を得ているのかをユーザーが理解できるようにする」こと。

実務ポイント(このレッスンから持ち帰るもの)

  • web search は server tool:ツール関数の実装・実行ループが不要。スキーマ(type: "web_search_20250305", name: "web_search")を tools に入れるだけで、検索は Anthropic 側で完結する。
  • max_uses は必ず設定:1つのクエリでも Claude はフォローアップ検索を連鎖させうる。上限(例:5)でコスト・レイテンシを制御する。
  • レスポンスは巨大かつ新種ブロック入りServerToolUseBlockserver_tool_use)→ WebSearchToolResultBlock(中に WebSearchResultBlockweb_search_result が多数)→ 回答 TextBlock 群、の順で返る。content[0].text 前提のコードは通用しない。
  • 検索結果ブロックにはページ本文は入っていない(title / url / encrypted_content / page_age)。本文の根拠は回答側 TextBlock の citations(CitationsWebSearchResultLocationweb_search_result_locationcited_text に入る。
  • ユーザーの質問領域が予測できるなら allowed_domains を使う(講師の強い推奨)。低品質な AI 生成ブログを排除し、PubMed(nih.gov)のような信頼ソースだけに検索を制約できる。効果は返ってきた URL のドメインで検証できる。
  • UI 設計の型:TextBlock は平文表示、検索結果一覧はページカードとして上部に、citations は「ドメイン+タイトル+URL+引用文」の小さな出典表示として本文に添える。出典の可視化が信頼性の担保になる。
  • 実行時間は数十秒オーダー(実測 43.8s/30.3s)。同期 UI にそのまま載せない設計(ローディング表示等)が前提になる。

本文(時系列・日本語逐語+画面差し込み)

[0:00] Claude には、web search tool(ウェブ検索ツール)という名前の、Claude に直接組み込まれたもう1つのツールがあります。名前が示す通り、このツールを使うと、ユーザーの質問に答えるために、Claude が最新の情報や専門的な情報を求めて Web を検索できるようになります。

🖥 [0:00] 画面: スライド「Web search tool」。「Our Server」「Claude」「Web」の3者シーケンス図。 Our Server → Claude: What are the latest developments in quantum computing? Claude → Web: Search: quantum computing news Web → Claude: Search Results ┌ First Silicon-Based Quantum Computer ├ Record Qubit Density Increase Report └ Big Tech in talks to invest in Q-Wave Claude → Our Server: Here are the latest developments in quantum computing: (左下に ANTHROP\C ロゴ)

[0:12] 例えば、量子コンピューティングの最新動向について尋ねると、Claude はこのツールを使うことを決め、量子コンピューティングに関連する最新の記事をいくつか見つけて、その内容を使って回答を組み立てるかもしれません。

[0:23] text editor tool(テキストエディタツール)と違って、検索を実際に実行するための実装をこちらで提供する必要はありません。検索は完全に Claude によって行われます。このおかげで、このツールは実行も利用も本当に簡単です。では、このツールがどう動くのかを理解するために、コードを書いていきましょう。

[0:37] ノートブックの中に戻ってきました。006WebSearch という名前の新しいノートブックを作りました。そして、下の方には、Claude にリクエストを投げるための典型的なコードが入ったセルを用意してあります。

🖥 [0:38] 画面: VS Code の Jupyter ノートブック 006_web_search.ipynb — 06(カーネル .venv (Python 3.9.6))。上に折りたたみ済みセクション見出し、下に実行用セル。 ```python ✓ # Load env variables and create client ✓ # Helper functions

messages = [] add_user_message( messages, """

""",

) response = chat(messages) response ``` (トリプルクォート内は空行・カーソルあり)

[0:47] そのすぐ上のセルで、WebSearchSchema という新しい変数を作ります。これは私たちが自分で書くスキーマで、Claude へのリクエストに含め、ツールとして列挙するものです。このスキーマが、ウェブ検索機能を有効にしてくれます。

🖥 [1:00] 画面: 新しいセルにスキーマを入力中。 python web_search_schema = { "type": "web_s }"web_s の直後にカーソル= web_search_20250305 の入力途中)

[1:02] text editor tool のときとまったく同じように、このごく小さなスキーマをこちらで提供する必要があり、それが裏側でずっと大きなスキーマに展開されます。ここにいくつかフィールドを追加していきます。具体的には、type に web search アンダースコア 2025 0305、次に name に web アンダースコア search、そして max uses を 5 に。今のところはこれだけです。

🖥 [1:26] 画面: スキーマ完成形。"max_uses": 5 の行が選択ハイライトされている。 python web_search_schema = { "type": "web_search_20250305", "name": "web_search", "max_uses": 5 }

[1:28] さて、max uses は、Claude が検索を実行できる回数です。1回の検索は複数の異なる検索結果を返すことがありますが、その検索結果の内容によっては、Claude がフォローアップの検索(追加の検索)をしようと判断することがあります。このプロセスは何度も繰り返されうるので、Claude が合計で検索できる回数を 5 回だけに制限しているわけです。

[1:47] あのセルを実行したら、この下へ移動します。そして Claude に「脚の筋肉をつけるのに最適なエクササイズは何?」と尋ねます。そして、先ほど組み立てたあのスキーマを追加します。web search schema を、このように。

🖥 [2:02] 画面: 実行用セルにクエリとツールを記入した状態。 python messages = [] add_user_message( messages, """ What's the best exercise for gaining leg muscle? """, ) response = chat(messages, tools=[web_search_schema]) response

[2:07] これを実行しますが、レスポンスが返ってくるまで少し時間がかかります。返ってくるレスポンスはかなり大きなものになります。ここでスクロールしてみると、とにかく途方もない量の情報があることが分かります。

🖥 [2:13] 画面: セル実行完了([4] ✓ 43.8s)。出力に巨大な Message オブジェクト。 python Message(id='msg_017UrXJhjv831SMmux9qz5pm', content=[TextBlock(citations=None, text="I'll help you find information about the best exercises for gaining leg muscle. Let me search for some current information on this topic.", type='text'), ServerToolUseBlock(id='srvtoolu_01Bss5PiiGKzSFLqNFtoegSe', input={'query': 'best exercises for gaining leg muscle mass strength'}, name='web_search', type='server_tool_use'), WebSearchToolResultBlock(content= [WebSearchResultBlock(encrypted_content='Et0NCioIAxgCIiQxMzA0Mzk3Yy1hMDg2LTRhNmQtYTZkNC05MmRlN…(長大な暗号化文字列)

🖥 [2:16] 画面: 出力をスクロール中。暗号化文字列の合間に検索結果のメタデータが次々現れる。 ```python …page_age='March 16, 2023', title='Strengthen Your Legs | Human Resources', type='web_search_result', url='https://hr.duke.edu/wellness/runwalk/training-education/strengthen-your-legs/'), WebSearchResultBlock(encrypted_content='EpggCioIAxgCIiQxMzA0Mzk3Yy1hMDg2…

…page_age='January 18, 2023', title='The Best Leg Exercises For Your Leg Day Workout | Gymshark Central', type='web_search_result', url='https://www.gymshark.com/blog/article/best-leg- exercises'), WebSearchResultBlock(encrypted_content='EtMZCioIAxgCIiQxMzA0Mzk3Yy1hMDg2… ```

[2:18] そこで、返ってきたレスポンスを理解しやすくするために、このレスポンスの少し削ぎ落としたバージョンをお見せします。このメッセージの content リストから大量の中身を取り除いたもので、何が起きているのかをよりよく理解するためだけのものです。これがその少し編集(redact)した形です。繰り返しますが、これはレスポンスメッセージの中の content リストです。

🖥 [2:31] 画面: 別タブ Untitled-1 に講師が用意した削減版。冒頭部分。 ```python [ # Initial text block — Claude decides to use the web search tool TextBlock( text="I'll help you find information about the best exercises for building leg muscle. L…(画面外) ),

# Exact query Claude executed
ServerToolUseBlock(
    input={
        "query": "best exercises building leg muscle strength scientific research"
    },
),

# Search results that Claude found
WebSearchToolResultBlock(
    content=[
        WebSearchResultBlock(
            title="Gluteus Maximus Activation during Common Strength and Hypertrophy Exercis…(画面外)
            type="web_search_result",
            url="https://pmc.ncbi.nlm.nih.gov/articles/PMC7039033/",
        )

```

[2:38] この content リストには、いくつもの異なるブロック、これまで見たことのないブロックがいくつも入っています。最初に返ってくるのはテキストブロックで、これは Claude が返している回答全体をフレーミングするようなものです。Claude は、質問への答え方をよりよく理解するためにウェブ検索を行う、と言っています。

[2:51] 次に見えるのは、server tool use block(サーバーツール使用ブロック)です。その中には検索ツールへの入力が入っています。これが、Claude が Web を検索するのに使う、まさにそのクエリだと分かります。

🖥 [3:03] 画面: 削減版の ServerToolUseBlock 内、query の値が選択ハイライトされている。 ```python

Exact query Claude executed

ServerToolUseBlock( input={ "query": "best exercises building leg muscle strength scientific research" }, ), ```

[3:02] その後に見えるのが、web search tool result block(ウェブ検索ツール結果ブロック)の一覧です。そしてその中には、たくさんの異なる web search result block(ウェブ検索結果ブロック)が入ることになります。これらは、最初のクエリから Claude が受け取った、それぞれの検索結果です。

[3:15] 実際のクエリのレスポンスには、おそらく多数の異なる検索結果が入っているでしょう。今回のケースでは、何が起きているのかを理解しやすくするために、1つを除いてすべて削除しました。つまり、これは実際のウェブ検索結果です。

[3:25] Claude が取得したページのタイトルと、実際の URL が確認できます。この中には、まだコンテンツ(ページ本文)は入っていません。これは、Claude が検索したときに何を見つけたのか、それだけを正確に教えてくれるものです。

🖥 [3:27] 画面: 削減版の後半(14〜30行目)。検索結果と回答開始部。 ```python # Search results that Claude found WebSearchToolResultBlock( content=[ WebSearchResultBlock( title="Gluteus Maximus Activation during Common Strength and Hypertrophy Exercis…(画面外) type="web_search_result", url="https://pmc.ncbi.nlm.nih.gov/articles/PMC7039033/", ) # ...More search results... ], ),

# Now Claude begin to answer the user's question
TextBlock(
    text="\n\nBased on the search results, I'll provide information about the best exercises…(画面外)
    type="text",
),

```

[3:37] それから Claude は、ユーザーの質問への回答を始めます。そしてユーザーの質問には、さまざまなテキストブロックで答えていきますが、そこには citations リスト(引用リスト)が含まれることがあります。citations リストは、Claude が行っている主張を何らかの形で裏付けるためのテキストです。

🖥 [3:40] 画面: 削減版の citations 部分(32〜48行目)。 python # Answering the user with a citation TextBlock( citations=[ # Citation supports the statements that Claude is making CitationsWebSearchResultLocation( cited_text="Keywords: muscle growth, drop sets, supersets, accentuated eccentri…(画面外) encrypted_index="EpEBCioIAxgCIiQ3MTgxMDQzNy0yNjE4LTQxZWYtOTkyMC1jODM2YmUyMjJjOT…(画面外) title="Maximizing Muscle Hypertrophy: A Systematic Review of Advanced Resistance…(画面外) type="web_search_result_location", url="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC6950543/", ) ], text="Resistance training (RT) is one of the primary exercise interventions used to deve…(画面外) type="text", ), TextBlock( text="\n\nThe most effective leg exercises typically involve:\n\n",

[3:49] この場合、Claude はまさにここで、ある特定のウェブページを引用しています。そして、自分が伝えようとしている論点なり主張なりを裏付けるために、この特定のテキスト(cited_text)を使っているのです。

[3:59] web search schema を定義するとき、追加できるフィールドはいろいろあります。その中でも特に、ユーザーが何について尋ねてくるのかを正確に把握できているなら、使うことを強くおすすめしたいフィールドが1つあります。

🖥 [4:01] 画面: ノートブックに戻る。スキーマセルは実行済([3] ✓ 0.0s)、下のクエリセルも実行済([4] ✓ 43.8s)。 python web_search_schema = { "type": "web_search_20250305", "name": "web_search", "max_uses": 5 }

[4:13] 今回のケースでは、脚の筋肉をつけるのに最適な脚のエクササイズについて尋ねました。皆さんがエクササイズのアドバイスをオンラインで検索したことがあるかは分かりませんが、世の中には、おそらく単なる AI 生成コンテンツでできた膨大な数のブログがあります。そして、そうしたブログから得られるアドバイスは、実際には最も正確とは言えなかったり、脚の筋肉を実際につける方法について最良の考え方を与えてくれなかったりするかもしれません。

[4:34] 一方で、さまざまな学術出版物を集めているウェブサイトもあります。例えば PubMed です。これは米国政府が運営しているウェブサイトで、医学関連の学術論文が大量に収載されています。ここを検索すれば、エビデンスに裏付けられたエクササイズのアドバイスを大量に見つけることができます。

🖥 [4:38] 画面: Chrome で PubMed(pubmed.ncbi.nlm.nih.gov)を表示。アドレスバーでは URL 末尾の「nih.gov」部分が選択されている。 ``` NIH National Library of Medicine National Center for Biotechnology Information [Log in]

PubMed® [検索ボックス] [Search] Advanced

PubMed® comprises more than 38 million citations for biomedical literature from MEDLINE, life science journals, and online books. Citations may include links to full text content from PubMed Central and publisher web sites. ``` (最上部に「An official website of the United States government — Here's how you know」)

[4:55] ですから、この Web ページとその中のコンテンツだけを検索するように、どうにかして Claude に指示できたら本当に素晴らしい。そうすれば、オンラインで拾ってきた、ただランダムに生成されたようなものではなく、可能な限り最良のアドバイスをユーザーに提供できていると確信できます。

[5:09] Claude にこのページだけを検索させるために、ドメインを調べます。nih.gov です。それから、自分のノートブックに戻ります。web search schema を見つけて、ここに allowed domains(許可ドメイン)という追加のフィールドを足します。nih.gov を入れたリストを渡します。これで Claude の検索は、まさにあのドメインだけに制約され、それ以外のものを探そうとはしなくなります。

🖥 [5:15] 画面: ノートブックのスキーマセル。"max_uses": 5 の直後にカーソルがあり、この直後に音声どおり allowed_domains のフィールド(nih.gov のリスト)が追記される。 python web_search_schema = { "type": "web_search_20250305", "name": "web_search", "max_uses": 5 } ([3] ✓ 0.0s。下には What's the best exercise… のセルと [4] 43.8s の表示)

[5:31] では、このセルをもう一度実行し、それから Claude へのリクエストももう一度実行すると、レスポンスが返ってくるまで待つ必要があります。ですがレスポンスを受け取ったら、少しスクロールしていけるはずです。

🖥 [5:36] 画面: 再実行後のセル([6] ✓ 30.3s)。2回目のレスポンス冒頭。 python Message(id='msg_01QzRkvCYXvQnzxq6QbNJdV4', content=[TextBlock(citations=None, text="I'll help you understand the best exercises for gaining leg muscle. Let me search for some up-to-date information on this topic.", type='text'), ServerToolUseBlock(id='srvtoolu_019DwUKZLSBrrpvYFQqFqQhx', input={'query': 'best exercises for gaining leg muscle strength hypertrophy'}, name='web_search', type='server_tool_use'), WebSearchToolResultBlock(content= [WebSearchResultBlock(encrypted_content='EsYcCioIAxgCIiQxMzA0Mzk3Yy1hMDg2LTRhNmQtYTZkNC05MmRlN…

[5:43] そして、やがてまさにここに URL があるのが見えるはずです。そして、URL は nih.gov ドメインに属するものだけになっているはずです。それ以外のものは一切見えないはずです。これも繰り返しになりますが、こうすることで、少なくとも科学的な裏付けのあるアドバイスをユーザーに提供できていることを確実にできるのです。

🖥 [5:58] 画面: 2回目のレスポンスをスクロールした箇所。URL 中の「nih.gov」が選択ハイライトされている。 python …page_age=None, title='Maximizing Muscle Hypertrophy: A Systematic Review of Advanced Resistance Training Techniques and Methods - PMC', type='web_search_result', url='https://pmc.ncbi.nlm.nih.gov/articles/PMC6950543/'), WebSearchResultBlock(encrypted_content='Eu0cCioIAxgCIiQxMzA0Mzk3Yy1hMDg2LTRhNmQtYTZkNC05MmRlN…

[5:57] さて、最後にやりたいのは、このツールを使ったときに返ってくる、さまざまな型のブロックの巨大なリストを、私たちが本来どのように使うことが意図されているのか、それを正確にお見せすることです。

[6:06] ここでの考え方はこうです。テキストブロックはすべて平文(プレーンテキスト)としてレンダリングします。そして、web search result block や citation web search result location を受け取ったときはいつでも、「情報の裏付けを取ろうとしている」ことがユーザーにとって一目瞭然になるような形で、それらを UI の中に描画するとよい、ということです。

🖥 [6:10] 画面: Untitled-1 の citations ブロック(32〜48行目)を再表示。citations=[CitationsWebSearchResultLocation 付近が薄くハイライトされている(内容は [3:40] と同一)

[6:23] そのやり方の一例がこちらです。私は、先ほどレスポンスメッセージの中で見た、あのさまざまなブロックをすべて受け取ってレンダリングする小さなページを書きました。

🖥 [6:24] 画面: Chrome の2つ目のタブで claude.ai/artifacts/90418c5b-d2d8-4596-b31f-d969d2f99e60 を表示。「Evidence-Based Guide」ページ。上部に検索結果カードが6枚(2列×3段)。 ``` Strength training for healthier bodies Maximizing Muscle Hypertrophy nia.nih.gov ncbi.nlm.nih.gov

High vs Low Frequency Training Leg Power and Chronic Disease pmc.ncbi.nlm.nih.gov pmc.ncbi.nlm.nih.gov

Four Types of Exercise Muscle Strength Gains in Women nia.nih.gov pmc.ncbi.nlm.nih.gov

Introduction Resistance training (RT) is one of the primary exercise interventions used to develop strength and stimulate muscle hypertrophy. Increases in muscle mass are important components of conditioning in various sports due to the correlation between muscle cross-sectional area and muscle strength.[ncbi.nlm.nih.gov] ```

[6:31] まさにこの最上部に、私の web search tool result blocks のすべての一覧を置いています。つまりこれらは、Claude がウェブ検索をしたときに見つけた、それぞれのページです。それから、ブロックのリスト全体を順に走査して、すべてのテキストブロックのテキストを表示していきます。

🖥 [6:41] 画面: 同ページをスクロール。本文セクションが続く。 ``` Compound Movements Lower body exercises like squats, lunges, and deadlifts play an increasing role in developing hip extensor musculature. The primary muscles responsible for hip extension are gluteus maximus (GMax), long head of biceps femoris, semimembranosus, semitendinosus, and the ischiocondylar portion of the adductor magnus. The gluteus maximus has been identified as the primary muscle responsible for hip extension, especially in loaded exercises that involve simultaneous hip and knee extension, such as squats and leg press.[pmc.ncbi.nlm.nih.gov]

Exercises with Highest Muscle Activation Research identifies several exercises that produce very high levels of muscle activation in the gluteus maximus: … ```

[6:46] citation web search result location を持つテキストブロックを見つけたときはいつでも——長い名前なのは分かっています——それはまさにここにある、これらのうちの1つです。

🖥 [6:53] 画面: Untitled-1 に切り替え、CitationsWebSearchResultLocation( の行を指し示す(citations ブロック全体、内容は [3:40] と同一。cited_text="Keywords: muscle growth, drop sets, supersets, accentuated eccentri…" / type="web_search_result_location" / url="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC6950543/" が見えている)

[6:55] そういうものを見つけたときはいつでも、このように小さな引用表示としてレンダリングするとよいでしょう。そこでは、ドメインを表示し、見つけたページのタイトルを表示し、その正確なアドレスを表示し、さらに引用されたテキストそのものも表示します。繰り返しになりますが、これはただ、Claude が実際にどこから情報を得ているのかを、ユーザーがよりよく理解できるようにするためのものです。

🖥 [6:57] 画面: レンダリング例のページに戻る。本文の各段落末尾に [ncbi.nlm.nih.gov][pmc.ncbi.nlm.nih.gov] の出典リンクが付いた形で、Introduction/Compound Movements/Exercises with Highest Muscle Activation のセクションが表示されている(内容は [6:24]・[6:41] と同一)

英語逐語(ASR全文)

There's another tool built directly into Claude named the web search tool. As the name implies, this tool allows Claude to search the web for up-to-date or specialized information to answer a user's question. For example, if we ask about current events in quantum computing, Claude might decide to use this tool to find some up-to-date articles related to quantum computing and then use that content to formulate an answer. Unlike the text editor tool, we do not have to provide an implementation to actually run the search. The search is done entirely by Claude, and this makes this tool really easy to run and use. Let's write out some code to understand how this tool works.

I'm back inside of a notebook. I've created a new one called 006WebSearch. And I've got a cell down here at the bottom with some typical code for running a request off to Claude. In the cell right above, I'm going to create a new variable called WebSearchSchema. This is going to be a schema that we author that we are going to include in our request off to Claude, and we're going to list it as a tool. This schema is going to enable the web search functionality.

So just like the text editor tool, we have to provide this very small schema that behind the scenes is going to get expanded into a much larger schema. We're going to add in a couple of fields here, in particular a type with web search underscore 2025 0305, then a name of web underscore search, a max uses of five, and that's it for right now. Now the max uses is the number of times that Claude can run a search. A single search can return multiple different search results, but depending upon the content in those search results, Claude might decide to do a follow-per-search. This process can repeat several times, so we are capping the number of times that Claude can search in total to just five.

I'm going to go down here after running that cell. And I'm going to ask Claude, what's the best exercise for gaining leg muscle? And I'm going to add in that schema that we just put together. So web search schema, like so. Now I'm going to run this and it's going to take a little bit of time to get a response back. The response we get back is going to be quite large. So I can scroll through it here and we're going to see that there is a, just a tremendous amount of information here. So to help you understand the response we got back, I want to show you a little pard down version of this response where I took this messages content list, and I removed a ton of content out of it just so we can better understand what is going on. So here is a slightly redacted form. Again, this is the content list inside the response message.

The content list is going to contain several different blocks, several blocks that we have not seen before. Initially, we get back a text block, which is kind of framing the entire response that Claude is giving us. Claude is saying that it's going to do a web search to better understand how to answer the question. Then we're going to see a server tool use block. And inside there is the input to the search tool. We can see that's the exact query that Claude is going to use to search the web. After that, we're going to see a listing of web search tool result block. And inside there are going to be many different web search result block. These are the different search results that Claude has received from that initial query.

Now an actual query response will probably have many different search results. In this case, I removed all of them but one just to better understand what's going on here. So this is an actual web search result. We can see the title of the page that Claude fetched and the actual URL. There's no content inside of here just yet. This is just telling us exactly what Claude found when it did the search. Then Claude is going to begin by answering the user's question. and it's going to answer the user's question with a variety of different text blocks that might include a citations list. The citations list is text that is supposed to support the statements that Claude is making in some way. So in this case, Claude has cited some particular web page right here and it is using this specific text to support the point or the argument that it's trying to make.

When we define the web search schema, there are a variety of different fields that we can add in. There's one field in particular that I really recommend you consider using if you ever have a good understanding of exactly what your users are going to be asking about. So in our case, we asked about the best leg exercises for gaining leg muscle. I don't know if you've ever searched online for exercise advice, but there are a tremendous number of blogs out there with probably just AI generated content. And the advice that you get out of those blogs might not actually be the most accurate or give you the best idea of how to actually gain leg muscle. On the other hand, there are some websites that collect different publications, such as PubMed. So this is a website maintained by the US government. It contains a ton of different scholarly articles around medicine. So we could search through here and find a tremendous amount of evidence supported exercise advice.

So it would be really fantastic if we could somehow tell Claude to only search this webpage and content inside of it. That would allow us to make sure that we are giving the best possible advice to our users and not just some randomly generated stuff that we found online. In order to get Claude to only search this page, we're going to find the domain, which is nih.gov. I'm then going to go back over to my notebook. I'm going to find my web search schema, and I'm going to add in an additional field here of allowed domains. I'm going to put in a list with nih.gov. This is going to constrain Claude search to only that domain right there, and it won't try to find anything else.

So now if I run the cell again and then make my request off to Claude again, we're going to have to wait for the response to come back. But when we do get a response, we should be able to scroll through it a little bit and eventually see that we have a URL right here. And we should only have URLs belonging to the nih.gov domain. We shouldn't see anything else. So again, this will allow us to make sure that we are giving at least some scientifically supported advice to our users.

Now, last thing I want to do is show you exactly how we are really intended to use this giant list of different types of blocks that we get back when we make use of this tool. The thought process here is that you're going to render all the text blocks as plain text. And then whenever you get a web search result block or a citation web search result location, you might render those out inside the UI in such a way that makes it obvious to a user that you are trying to support your information in some way. Here's an example of how you might do that. I wrote out a small page that is going to take all those different blocks we saw inside the response message and render them out.

At the very top right here, I've got a list of all my web search tool result blocks. So these are the different pages that Claude found when it did the web search. I'm then going to iterate through the entire list of blocks and show the text out of every single text block. Whenever I found a text block that has a citation web search result location, I know that's a long term, so it's one of these right here. Whenever I find one of those, I might render it with a little citation like so, where I'll show the domain, show the title of the page that I found, show the exact address of it, and then the exact cited text as well. Again, this just allows our users to better understand how Claude is actually getting its information.

↑ 目次へ戻る

Building with the Claude API | Lesson 39「Introducing Retrieval Augmented Generation」(全文ナレッジ)

動画 5分50秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンのテーマ

このモジュール全体で扱う Retrieval Augmented Generation(RAG/検索拡張生成) の導入回。RAG が「何をするための技術か」を、巨大文書への質問応答というシンプルな例で腹落ちさせる。

お題:巨大な財務文書への質問

  • 100〜1,000ページ規模の財務文書(例:Corporation の年次財務報告書)があり、その特定のセクションについて Claude に質問したい(例:「この会社にはどんなリスク要因があるか?」)
  • これは私的な文書なので、Claude は中身を一切知らない
  • 根本課題:この文書の情報をどうやって Claude に渡し、質問に答えさせるか?

オプション1:文書の全文をプロンプトに入れる — 3つの問題

文書から全テキストを抽出し、ユーザーの質問と一緒にそのままプロンプトに入れる方式。動くこともあるが、動かないこともある:

  1. ハード上限:Claude に投入できるテキスト量には絶対的な上限がある。文書が非常に長いと、投入した瞬間にエラーになり、その時点でこの方式は成立しない
  2. 長文での精度低下:プロンプトが長くなるほど Claude の効きは少し落ちる。膨大な情報の中から「何を求められているか」を理解して答えるのが難しくなる
  3. コストと待ち時間:大きいプロンプトは処理にお金がかかり、処理に時間もかかる。金銭的負担に加え、ユーザーを待たせる UX 上の負担にもなる

→ シナリオによっては機能するが、別のシナリオでは完全に失敗する。

オプション2:チャンク分割 + 関連チャンクのみ挿入(2ステップ)

少し複雑になるが、次の2つのステップで構成する:

  • Step 1(前処理):文書から全テキストを取り出し、小さなチャンク(塊)に分割しておく
  • Step 2(質問時):ユーザーの質問をプロンプトに入れるのは従来どおり。ただし追加のひと手間として、質問を精査し、最も関連しそうなチャンクを探して、そのチャンクだけをプロンプトに同梱する
  • 例:「この会社が直面するリスクは?」→ Risk Factors(リスク要因)らしきチャンクを見つけてプロンプトに挿入

こうすると Claude の注意を財務文書全体ではなくごく小さな断片に集中させられ、全文投入よりはるかに良い回答が期待できる。

利点(Upsides) - Claude が関連コンテンツだけに集中できる - ページ数が膨大な超大型文書にもスケールする - 複数文書にも機能する(全文書をチャンク化し、質問に関連するチャンクだけ挿入) - プロンプトが小さくなる=実行が速く、コストも大幅に安い

欠点(Downsides) - そもそも複雑性が増す - 前処理ステップが必須(全テキスト抽出→チャンク分割) - チャンク群から関連チャンクを見つける検索機構が要る。しかも「質問に関連するとは何か」の定義まで自分で決める必要がある - 関連チャンクを見つけて入れても、Claude が答えるのに必要なコンテキストを全部含む保証はない(リスクの質問に Risk Factors セクションだけ入れても、リスクへの対処が書かれた Strategy Outlook など別の重要セクションを取り逃しうる) - チャンク分割の方法が多数ある(等分割/見出しごとに1チャンク等)。どの手法が自アプリに最適かは評価(evaluation)して決める必要がある

オプション2 = RAG。採否の判断

オプション2こそが RAG(Retrieval Augmented Generation)。大きな利点と大きな欠点の両方があり、技術的課題も多い(前処理・検索機構・チャンク化)。オプション1よりも明らかに作業量が多いので、アプリに RAG を導入する時は、これら全ステップを分析して「自分のユースケースに本当に適するか」を見極めてからにする。次回以降で実装プロセスに入る。

実務ポイント(このレッスンから持ち帰るもの)

  • 大型文書 QA の設計は「全文投入 vs RAG」の比較から始める。全文投入は①トークン上限で即エラー②長文での精度低下③コスト・レイテンシ増、の3点でいずれ破綻しうる。
  • RAG の骨格は 前処理(チャンク分割)+ 検索(関連チャンク特定)+ プロンプト合成 の3要素。プロンプトが小さくなるので速く・安くなるのが直接のリターン。
  • 「関連する」の定義と検索機構は自分で設計する必要がある。ここが RAG 実装の本丸(以降のレッスンの主題)。
  • 検索で拾ったチャンクが必要コンテキストを全て含む保証はない。質問に対応するセクションだけでは足りないケース(リスク質問に Strategy Outlook の記述が必要等)を想定し、チャンク設計や複数チャンク挿入を検討する。
  • チャンク分割の方法は多数(等分割/見出し単位など)。どれが最適かは評価(eval)で決めるのが正攻法。勘で1方式に決め打ちしない。
  • 文書が小さく1本だけなら全文投入で十分なことも多い。RAG は複雑性コストを払う価値があるか、全ステップを分析してから採用する。

本文(時系列・日本語逐語+画面差し込み)

🖥 [0:00] 画面(タイトルスライド「Retrieval Augmented Generation (RAG)」) 黒背景に白文字の大見出し: Retrieval Augmented Generation (RAG) 左上に ANTHROP\C ロゴ。

[0:00] このモジュールでは、Retrieval Augmented Generation(検索拡張生成)、略して RAG と呼ばれるテクニックについて、非常に多くのことを議論していきます。特にこのビデオでは、RAG が一体何なのかについて、本当にしっかりしたイメージを持ってもらいたいと思います。RAG を理解してもらうために、とても手短な例をひとつ一緒に見ていきます。

🖥 [0:16] 画面(スライド「Claude with large documents」) 左側の箇条書き: - We might want to ask Claude questions about specific sections of a large document(大きな文書の特定セクションについて Claude に質問したいことがある) - "What risk factors does this company have?"(「この会社にはどんなリスク要因があるか?」) - It's a private document - Claude won't have any idea what is contained in it(私的な文書なので、Claude は中身を一切知らない) - How can we get Claude to understand the contents?(どうすれば Claude に中身を理解させられるか?)

右側:見出し Financial Doc, 800+ pages(財務文書、800ページ超)の下に文書モック「Corporation — Annual Financial Report - Fiscal Year 2024」。本文の見出し構成: - 1. Executive Summary — revenue growth of 12.3% year-over-year, reaching $1.24 billion / Operating income increased by 15.7% to $342 million / net income rose by 18.2% to $267 million / quarterly dividend of $0.45 per share(10% increase、2025-03-15 支払、基準日 2025-02-28)/ R&D expenses increasing by 22.5% to $156 million - 2. Management Discussion & Analysis — Overview of Business Operations(3セグメント:Consumer Products 45%・+9.8%/Enterprise Solutions 35%・+14.2%/Digital Services 20%・+18.7%)/Market Conditions and Competitive Landscape - Risk Factors — Fluctuations in raw material costs and supply chain disruptions / Changes in regulatory requirements across our operating regions / Technological disruptions and cybersecurity threats / Economic uncertainty in key markets / Foreign exchange rate volatility - Future Outlook — revenue growth of 10-12% を見込む、新製品投入と新地域展開 ほか

[0:17] そこで想像してほしいのですが、あなたの手元に、右側に見えているような非常に大きな財務文書があるとします。この中には膨大な量のテキストがあるかもしれません。どれくらいかは分かりませんが、100ページから1,000ページくらいあるかもしれません。そして私たちは、この文書の非常に特定の領域について、非常に特定の質問を Claude にしたいかもしれません。例えば、「この会社にはどんなリスク要因があるか?」というような質問をしたいかもしれません。

[0:40] さて、おそらくこの文書の中には、何かしら関連する情報が入っているはずです。そこで私たちは、ここで非常に根本的な問題を解決する必要があります。この文書から情報を取り出して Claude に渡し、質問に答える手助けをしてもらうには、どうすればよいのでしょうか?この問題を解決できる方法を、2つお見せしたいと思います。

🖥 [0:57] 画面(スライド「Option 1: Include the contents of the document in a prompt」) 見出しの下にサブタイトル:Extract all of the text from the document, and include it in a prompt with a question about the content(文書から全テキストを抽出し、内容についての質問と一緒にプロンプトに入れる)。 左に財務文書モック、そこから右の黒いプロンプトボックスの {financial_document} へ矢印が伸びる: ``` """ Answer the user's question about the financial document.

{user_question}

{financial_document} """ ```

[0:56] ではオプション1です。この文書から全てのテキストを取り出して、右側に見えているようなプロンプトの中に、そのまま直接入れてしまう、という方法が考えられます。つまり、ユーザーの質問に答えるよう Claude に頼み、そこにユーザーの質問を入れ、さらに文書から取り出した全テキストも同じプロンプトに入れる、というわけです。

🖥 [1:14] 画面(スライド「Possibly not the best solution」) 左側の箇条書き: - There's a hard limit on how much text we can feed into Claude - the document might be too long(Claude に投入できるテキスト量にはハード上限がある — 文書が長すぎるかもしれない) - Claude gets slightly less effective with very long prompts(非常に長いプロンプトでは Claude の効きが少し落ちる) - Larger prompts cost more to process(大きいプロンプトは処理コストが高い) - Larger prompts take longer to process(大きいプロンプトは処理に時間がかかる)

右側には前スライドの縮小版(Option 1: Include the contents of the document in a prompt + 文書モック → プロンプトボックスの図)。

[1:14] さて、これはおそらくベストな解決策ではありません。動くかもしれませんし、動かないかもしれません。知っておいてほしいのですが、Claude に投入できるテキスト量にはハードリミット(絶対的な上限)があります。ですから、もしこの文書が本当に本当に長くて、そこから全テキストを取り出して Claude に全部投入したら、即座にエラーが返ってくるかもしれません。つまり、文書が本当に長い場合、この解決策は出だしからいきなり成立しない、ということです。

[1:37] このアプローチの2つ目の問題は、プロンプトが長くなるにつれて、Claude の効果が少し落ちていくことです。プロンプトに膨大な量のテキストを入れ始めると、プロンプトの中にあまりに大量の情報があるせいで、Claude はあなたが正確に何を求めているのかを理解して質問に答えるのが、より難しくなってしまうのです。

[1:56] そして最後に、より大きなプロンプトは処理により多くのお金がかかり、処理により長い時間がかかります。つまりここには金銭的な負担があり、さらにユーザー体験上の負担もあります。ユーザーは何かしらの回答が返ってくるまで、より長く待たされることになるからです。

[2:10] というわけで、オプション1はシナリオによっては機能するかもしれません。別のシナリオでは、完全に失敗するかもしれません。それを踏まえた上で、オプション2を見てみましょう。

🖥 [2:18] 画面(スライド「Option 2: Break the document up into many chunks. Put chunks relevant to the user's question in the prompt.」— チャンク分割ステップ) 見出し:Option 2: Break the document up into many chunks. Put chunks relevant to the user's question in the prompt.(オプション2:文書を多数のチャンクに分割し、ユーザーの質問に関連するチャンクをプロンプトに入れる) 左に財務文書モック → 太い矢印 → 右に「Chunks of Text」ボックス。中に6枚のチャンク(紙片型): Strategy Outlook Balance Sheet Risk Factors Auditor's Report Key Performance Market Data Indicators

[2:19] オプション2は、もう少しだけ複雑です。オプション2には、2つの別々のステップがあります。ステップ1では、文書から全テキストを取り出し、それを小さなチャンク(塊)に分割します。

🖥 [2:33] 画面(Option 2 の全体図:ユーザー質問+チャンク+プロンプト) 同じ見出しの下に全体図が完成する。 - 左上「User's Question」:What risks does this company face?(この会社はどんなリスクに直面しているか?) - 左下「Chunks of Text」ボックス(6チャンク:Strategy Outlook / Balance Sheet / Risk Factors / Auditor's Report / Key Performance Indicators / Market Data) - 右に黒いプロンプトボックス: ``` """ Answer the user's question about the financial document.

{user_question}

{financial_document} """ `` User's Question からへ、チャンク側から` 付近へ、赤い矢印が2本伸びる。

[2:29] それから、ユーザーが質問をするたびに、その質問を従来どおりプロンプトに入れます。ただし、追加のちょっとしたステップも踏むことになります。ユーザーの質問を非常に注意深く精査するのです。そして、ユーザーの質問に最も関連していそうなテキストのチャンクを探し出します。

[2:46] 今回のケースで言えば、ユーザーが「この会社はどんなリスクに直面しているか?」と質問してきたとします。そして、ちょうどここに、リスク要因について書かれているらしいテキストのチャンクがあるとします。その場合、そのテキストのチャンクを取り出して、プロンプトの中に含めることになります。

🖥 [2:55] 画面(Risk Factors チャンクがプロンプト内へ移動) 「Chunks of Text」ボックスの Risk Factors があった位置が空白になり、代わりにプロンプトボックス内の <financial_document></financial_document> の間に、ベージュ色のチャンク片「Risk Factors」がそのまま挟み込まれた図に変化: <financial_document> ┌─────────────────┐ │ Risk Factors │ └─────────────────┘ </financial_document> """

[3:00] これで私たちは、Claude の注意のすべてを、財務文書全体のうちのこの非常に小さな断片だけに集中させていることになります。そして願わくば、文書の全テキストをただプロンプトに入れていた以前のやり方よりも、Claude はユーザーの質問に答える仕事をはるかに上手くこなせるはずです。

🖥 [3:16] 画面(スライド「Upsides / Downsides」) 左側に2つの見出しと箇条書き: Upsides(利点) - Claude can focus on only the most relevant content(Claude が最も関連するコンテンツだけに集中できる) - Scales up to very large documents(非常に大きな文書にスケールする) - Technique works with multiple documents(複数の文書でも機能する) - Smaller prompt, which costs less and runs faster(プロンプトが小さくなり、コストが下がり実行も速い)

Downsides(欠点) - Requires a pre-processing step(前処理ステップが必要) - Need some searching mechanism to find "relevant" chunks(「関連する」チャンクを見つける検索機構が必要) - Included chunks might not contain all the context that Claude needs(含めたチャンクが Claude に必要なコンテキストを全て含むとは限らない) - Many ways to chunk the text - which is best?(チャンク化の方法は多数 — どれがベストか?)

右側に Option 2 の全体図(User's Question + Chunks of Text + プロンプトボックス)の縮小版。

[3:14] さて、オプション2には、はっきりした利点と欠点のセットがあります。ここでの利点は、まず Claude が関連するコンテンツだけに集中できることです。第二に、この方式は膨大なページ数を持つ本当に本当に大きな文書にまでスケールできます。

[3:30] さらに、複数の文書がある場合にも機能します。これらの異なる文書を全部持ってきて、全てをチャンクに分割し、そしてまた同じように、ユーザーの質問に関連するチャンクだけをプロンプトに含めればよいのです。

[3:41] またこのテクニックは、一般的にプロンプトをはるかに小さくします。つまり実行にかかる時間が短くなり、コストも大幅に安くなるということです。

[3:49] しかし、このアプローチには大きな欠点もいくつかあります。まず第一に、当然ながら複雑性がかなり増します。この方式には前処理ステップが必要で、そこで文書から全テキストを取り出してチャンクに分割しなければなりません。また、これら全チャンクの中からユーザーの質問に最も関連するものを見つけ出すための、何らかの検索方法も考え出す必要があります。それどころか、「ユーザーの質問に関連しているとはどういうことか」の定義さえ、自分たちで決める必要があるのです。

🖥 [4:16] 画面(Option 2 の全体図へ戻る) スライドが「Upsides / Downsides」から Option 2 の全体図(フルスクリーン版・Risk Factors チャンクがプロンプト内に挟まった状態)へ戻る。内容は [2:55] と同一。

[4:11] 実際に関連するチャンクをいくつか見つけてプロンプトに含めたとしても、それらが、Claude が実際に質問へ答えるために必要なコンテキストを全て含んでいるという保証は、実のところまったくありません。ユーザーが「この会社はどんなリスクに直面しているか」と尋ねてきて、私たちがリスク要因のセクションだけを含めた場合、文書の別の重要な領域——例えば Strategy Outlook(戦略見通し)のような、そうしたリスクの一部が何らかの形で対処されている場所——が漏れてしまうかもしれないのです。

🖥 [4:45] 画面(スライドを高速で前後に切り替え) 約1秒の間にスライドが連続で切り替わる:Option 2 全体図 → 「文書 → Chunks of Text」のチャンク分割スライド → 「Possibly not the best solution」(Option 1)スライド → 再び「文書 → Chunks of Text」スライド。講師が「テキストの分割方法は色々ある」ことを説明しながら、文書とチャンクの対応を指し示すためにデッキを行き来している場面。代表画面=チャンク分割スライド(内容は [2:18] と同一)。

[4:36] そして最後に、テキストの分割方法には実にさまざまなやり方があります。文書の全テキストを取ってきて、単純に等しい分量に分割することもできます。あるいは、文書の中を辿ってこうした見出しを全部見つけ出し、「見出しごとに新しいチャンクを1つ作る」と決めることもできます。

🖥 [4:55] 画面(Option 2 全体図 → 「Upsides / Downsides」スライドへ) Option 2 の全体図(Risk Factors チャンク入りプロンプト)が一瞬表示された後、「Upsides / Downsides」スライド(内容は [3:16] と同一)に戻る。

[4:50] その場合、チャンク1があって、次に2、そしてこの下のどこかに3、というふうになるでしょう。「チャンクとは何か」の定義の仕方には、実に多くのやり方があります。ですから私たちは、ちょっとした評価(evaluation)を行って、自分たちの特定のアプリケーションにはどの手法がベストなのかを判断しなければなりません。

🖥 [5:05] 画面(スライド「This is Rag!」) 左側の見出し:This is Rag!(これが RAG だ!) 箇条書き: - Many upsides, many downsides(多くの利点、多くの欠点) - Many technical decisions to be made(下すべき技術的判断が多い) - Requires preprocessing(前処理が必要) - Requires a 'search' mechanism(「検索」機構が必要)

右側に Option 2 の全体図(User's Question + Chunks of Text + プロンプトボックス)の縮小版。

[5:05] もうお察しのとおり、オプション2こそが RAG です。それが Retrieval Augmented Generation(検索拡張生成)です。いま議論してきたとおり、RAG には多くの大きな利点があり、同時に多くの大きな欠点もあります。

[5:17] その周辺には技術的な課題がたくさんあります。前処理ステップが必要です。関連チャンクを見つけるための何らかの検索機構も考え出さなければなりません。文書をチャンク化しなければなりません。全体として、オプション1よりもやるべき作業が格段に多いのです。

[5:31] ですから、アプリケーションの中に RAG を実装しようと検討する時はいつでも、これらの異なるステップ全てを本当にしっかり分析して、それが自分たちの特定のユースケースにとって適切なのかどうかを見極める必要があります。

[5:41] それでは、RAG が何なのかについて、ごくごくハイレベルな理解ができたところで、この後すぐに、このプロセスの実際の実装を見ていくことにしましょう。

英語逐語(ASR全文)

In this module, we are going to discuss a tremendous amount about a technique referred to as Retrieval Augmented Generation, or RAG for short. In this video in particular, I want to give you a really solid idea of what RAG is all about. To help you understand RAG, we're going to walk through a very quick example. So I want you to imagine that you have a very large financial document, like the one seen on the right-hand side. There might be a tremendous amount of text in this. It might have anywhere from, who knows, 100 to 1,000 pages. And we might want to ask Claude very specific questions about very specific areas of the document. For example, we might want to ask a question like, what risk factors does this company have? Now, presumably this document might have some relevant information inside of it. So we need to solve a very fundamental issue here. How do we get some information out of this document and into Claude so it can help us answer our question? I want to show you two possible ways that we could solve this problem. So option number one, we could take all of the text out of this document and just place it directly into a prompt like the one you see on the right hand side. So we might ask Claude to answer a user's question. We'll then put in the user question and then take all the text out of the document and put it into the prompt as well. Now, this is perhaps not the best solution. It might work. It also might not. Just so you know, there is a hard limit on how much text we can feed into Claude. So if this document is really, really long and we take all the text out of it and feed all that text into Claude, we might immediately end up getting an error, which means just right out the gate, this solution would not work if our document is really, really long. The second problem with this approach is that Claude gets a little bit less effective as your prompt gets longer. So if you start putting a tremendous amount of text into a prompt, Claude is going to have just a harder time understanding exactly what you want and answering your question because there's just a tremendous amount of information inside the prompt. And then finally, larger prompts cost more money to process and take longer to process. So there's a financial burden here as well, and a user experience burden because they just have to wait around longer to get back some kind of answer. So option number one might work in some scenarios. In other scenarios, it might fail entirely. So with that in mind, let's take a look at option number two. Option number two is a little bit more complex. So option number two has two separate steps. In step one, we'll take all the text out of the document and break it up into small chunks. Then whenever a user asks a question, we're going to take their question and put it into the prompt as before. But we're also going to go through an extra little step. We're going to examine the user's question very closely. And we're going to find a chunk of text that seems most relevant to the user's question. In this case, if a user asks us, what risk does this company face? And we have a chunk of text right here that seems to be about risk factors. We would then take that chunk of text and include it inside of the prompt. So now we are focusing all of Claude's attention on just this very small snippet of the overall financial document. And hopefully Claude can do a much better job of answering the user's question than before when we were just putting all of the text of the document into the prompt. So option number two has a distinct set of upsides and downsides. The upsides here are that Claude can focus on just the relevant content. Secondly, this can scale up to really, really large documents with a tremendous number of pages. And it also works if we have multiple documents. We can take all these different documents, separate them all into chunks, and then once again only include chunks relevant to a user's question inside of a prompt. This technique also generally leads to much smaller prompts, which means it's going to take less time to run and it's going to cost us a lot less. But there are some big downsides to this approach as well. First off, there's just naturally a lot more complexity. This requires a pre-processing step where we take all the text out of the document and split it into chunks. We also have to figure out some way of searching through all these chunks to find the ones that are most relevant to the user's question, and we even need to define what it means to be relevant to the user's question. When we do find some relevant chunks and include them in the prompt, there's really no guarantee that they will contain all the context that Claude needs to actually answer the question. If the user asks what risk does this company face and we include only the risk factor section, that might include some other important area of the document, maybe strategy outlook, where some of those risks get addressed in some way. And then finally, there are many different ways in which we can split the text up. So we could just take all the text of the document and divide it into equal portions. Or we could go through the document and find all these different headers and say for every header, we're going to make a new chunk. So you might have chunk one and then two and then three somewhere down here. There are many different ways in which we can define what a chunk is. And so we have to do a little bit of evaluation and decide which technique is the best for our particular application. So as you might guess, option number two is RAG. It is retrieval augmented generation. As we just discussed, RAG has many big upsides and many big downsides as well. There's a lot of technical challenges around it. It requires a pre-processing step. We also have to figure out some kind of searching mechanism to find those relevant chunks. We have to chunk documents. All in all, there's just a lot more work than option number one. So whenever we are considering implementing RAG inside of an application, we really have to analyze all these different steps and figure out whether or not it is right for our particular use case. All right, so now that we have kind of a very, very high level understanding of what RAG is all about, let's start to take a look at the actual implementation of this process in just a moment.

↑ 目次へ戻る

Building with the Claude API | Lesson 40「Text chunking strategies」(全文ナレッジ)

動画 13分07秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンのテーマ

RAG(Retrieval Augmented Generation)パイプラインの最初のステップ「ソース文書をチャンク(テキストの断片)に分割する」を、独自実装しながら学ぶ回。RAG の全体像は「①ソース文書をチャンクに分ける → ②ユーザーの質問が来たら関連するチャンクを探す → ③そのチャンクをプロンプトに入れる」というシンプルな流れだが、この①の分け方(チャンキング)が RAG の品質を大きく左右する。「信じられないかもしれないが、文書を分割する処理は RAG パイプライン全体の中でも最も複雑なステップの一つ」と講師は言う。

なぜチャンキングが品質を左右するか(bug と infection vectors の取り違え例)

講師は具体例で「悪い分割」がなぜ危険かを示す。ヘッダー+「医療研究」セクション+「ソフトウェア工学」セクションからなる小さなソース文書を、行ごとに5チャンクに分ける。すると次の取り違えが起きる:

  • 医療研究セクションの中のチャンクに「bug(バグ)」という単語が含まれている(XDR-47 という "bug" の話)。このチャンク単体をパッと見ると、まるでソフトウェア工学の話に見える。
  • 逆にソフトウェア工学セクションの中には「infection vectors(感染経路)」という、やや医療寄りの用語が含まれている。

ここでユーザーが「今年エンジニアはバグを何件直した?(how many bugs did engineers fix this year?)」と質問すると、RAG は「bug」という単語だけを頼りに、医療研究のチャンクを "関連あり" と誤判定してプロンプトに入れてしまう。ユーザーが本当に欲しかったのはソフトウェア工学セクションなのに、医療研究の内容を取ってきてしまう。「これは巨大なエラーであり、チャンキング戦略が簡単に大きな誤りと粗悪なコンテキスト挿入をプロンプトに持ち込む例だ」。だからこそチャンクの切り方に多くの時間を割く。

3つのチャンキング戦略(size-based / structure-based / semantic-based)

この動画では、いま見た問題に対処するための3戦略を扱う。

① size-based chunking(サイズベース) 大きな文書を、ほぼ等しい長さの文字列に機械的に分割する。最も実装が簡単で、本番実装でも最もよく見かける手法。例:325文字の文書を3つに割ると、各チャンク約108文字。 - 欠点1:各チャンクの端で単語が途中で切れる(cutoff words)。例では "significant" が "signific" と "ant" に割れる。 - 欠点2:各チャンクが文脈を欠く(lacks context)。例えば3番目のチャンクは直前のセクション見出しを含まないので、何の話か分からなくなる。 - 対策=overlap 戦略:各チャンクに隣接チャンクから数文字分の重なりを含める。これで「significant の残り+文末」まで入り、意味が通りやすくなる。副作用として重複テキストが発生する(例:「Section 1 Medical Research」が1番目と2番目の両チャンクに入る)が、その代わり各チャンクの文脈が豊かになる。

② structure-based chunking(構造ベース) 文書の構造(見出し・段落・セクション)を分割線にする。サンプル文書は markdown なので ##(ダブルハッシュ)を目印にすれば、プログラム的に綺麗なセクション単位へ分割できる。素晴らしい戦略に聞こえるが、現実はそう甘くない。取り込む文書が markdown でない(プレーンな PDF・ただのテキスト)ことが多く、その場合ははっきり区切られたセクションが得られない。文書構造に対する保証(guarantee)がないと実装が非常に難しいのが弱点。

③ semantic-based chunking(セマンティックベース) テキストを文(または節)に分け、NLP(自然言語処理)で「隣り合う文がどれだけ関連しているか」を判定し、関連する文どうしをまとめてチャンクを作る。説明だけで分かる通りかなり高度な手法なので、実装には深く踏み込まない。言及した理由は「チャンキング戦略に固定の有限な数はなく、分け方は事実上無限にある」ことを明確にするため。どの手法を使うかは、ユースケースと、取り込む文書の構造に対して持っている保証に完全に依存する

ノートブック 001_chunking の3実装と実験結果

001_chunking ノートブック+同ディレクトリの report.md(架空の年次レポート)で3手法をテストする。

chunk_by_char(size-based の実装) - 引数:textchunk_size(各チャンクの長さ)、chunk_overlap(両端に持たせる重なり文字数)。 - デフォルト chunk_size=150, chunk_overlap=20 は良くない:各チャンクが意味をほとんど持たず、ユーザーの質問に使えない。 - 500, 150 に大きく変えると改善:実際のセクションの形が見え始め、情報量が出る。overlap の実例として、addressing complex challenges(複雑な課題に取り組む…)というフレーズが、すぐ上のチャンクにも重複して含まれているのが確認できる。

chunk_by_sentence - 正規表現 r"(?<=[.!?])\s+" で文単位に分割し、いくつかの文+任意の overlap でチャンクを作る。 - デフォルト=1チャンク5文+1文の overlap。実験結果はかなり良好で、各チャンクがしっかりした情報量を持つ。正規表現ゆえ文の切り方を誤るケースはありうる。

chunk_by_section(structure-based の実装) - "\n## "(改行+ダブルハッシュ+スペース)を区切りに re.split する。 - 結果は最良:先頭チャンクだけは有用な情報が少ないが、それ以降は1チャンク=正確に1セクションになり非常に強い(Executive Summary → Table of Contents → Section 1, 2, 3 …)。ただし文書が markdown で、その区切りが新セクション開始時にしか現れないという保証があって初めて成立する。

戦略選定の指針

どの戦略を選ぶかは文書の性質と構造保証で決まる。 - 構造が保証されるなら chunk_by_section が最良。 - ユーザー提供の文書で書式保証がないなら section 分割は長期的には破綻する。→ chunk_by_sentence にフォールバック。 - ただし sentence 分割も万能でない。例えばユーザー提供のコードをチャンク化しようとすると、コードは予期せぬ場所にピリオドが多いため、文分割が壊れて予想外の結果になる。 - 迷ったら "old reliable standard" = chunk by character にフォールバック。最良の結果を保証はしないが、大多数のケースでそこそこ良く動く。

実務ポイント(このレッスンから持ち帰るもの)

  • チャンキングは RAG の品質を直撃する最重要工程。悪い切り方は「bug ↔ infection vectors」のような取り違えを生み、検索が無関係チャンクを拾ってプロンプトを汚染する。
  • size-based(chunk_by_char)のデフォルト値は要調整。動画の既定 chunk_size=150 / chunk_overlap=20 は無意味なチャンクを生む。500 / 150 程度まで上げると実用的になった。まず自分の文書で回して長さを較正する。
  • overlap は文脈欠落と単語切断の緩和策。重複テキストが増えるコストと引き換えに、各チャンクの意味が通りやすくなる。両端に n 文字(または n 文)を足す。
  • structure-based(chunk_by_section)は "markdown 保証" がある時だけ使う"\n## " 分割は最良の整形になるが、PDF/プレーンテキストなど構造保証のない文書では破綻する。
  • コードを分割対象にするなら sentence 分割は壊れる。コードはピリオドが多く、正規表現の文分割が誤爆する。
  • フォールバックの序列を持つ:構造保証あり → section / 保証なし → sentence / それも危うい(コード等)→ character。迷ったら chunk by character(最良ではないが安定して "reasonably well")。
  • semantic-based は最も関連性の高いチャンクを作れるが計算コストが高い。この講座では実装しないが、「分け方は無限にあり、正解は用途と保証次第」という前提だけ押さえる。

本文(時系列・日本語逐語+画面差し込み)

[0:00] このビデオと次の数本のビデオでは、一連の異なるノートブックの中で、私たち独自のカスタム RAG ワークフローを実装し始めます。まずは、作れる限り最もシンプルで基本的な RAG のセットアップを作ることに集中し、それから時間をかけて追加のステップを足していきます。

🖥 [0:00] 画面(スライド「How We'll Learn Retrieval Augmented Generation」) 左に見出しと箇条書き、右に上から下へ矢印でつながる5つのボックス。 - We'll focus on implementing a basic RAG workflow - Once complete, we'll add additional steps and features to increase the accuracy of our workflow 右のフロー(上→下): Basic RAG setup ↓ Add a BM25 index ↓ Integrate BM25 + VectorDB ↓ Add Reranking support ↓ Discuss Contextual Embeddings

[0:14] さて、念のためおさらいすると、典型的な RAG パイプラインは、話をとても単純にすると、だいたいこんな感じに見えます。私たちはソース文書を取り、それをテキストのチャンクに分割します。

[0:23] そして、ユーザーが私たちに質問をするたびに、関連するテキストのチャンクを見つけ出し、それをプロンプトに入れます。そしてそれで、ほぼ全体の完成です。というわけで、この一連の流れのステップ1は、ソース文書を取り、それをテキストのチャンクに分割することです。

🖥 [0:20] 画面(スライド「Chunk source text」) 上部に帯見出し「Chunk source text」。左に「Source Document」(Corporation の Annual Financial Report - Fiscal Year 2024。1. Executive Summary / 2. Management Discussion & Analysis / Overview of Business Operations / Market Conditions and Competitive Landscape / Risk Factors(Fluctuations in raw material costs… 等の箇条書き)/ Future Outlook)。中央に黒い矢印。右に「Chunks of Text」ボックス(Chunk #1〜#6 の6分割グリッド)。

[0:33] さて、信じられないかもしれませんが、この「文書を取って別々のチャンクに分割する」というプロセスは、RAG パイプライン全体の中でもより複雑なステップの一つです。というのも、単純に、文書をどうチャンク分割するかが、私たちの RAG パイプラインの品質に絶大な影響(output)を及ぼすからです。

[0:50] そして、なぜそうなるのかを理解してもらうために、すぐに一つ例を挙げたいと思います。このソース文書を見てください。ほんの数行の小さなもので、何らかの企業のレポートか何かを表しているつもりです。ざっと読み通すと、大きく3つの領域があることが分かります。ヘッダーがあり、医療研究についてのセクションがあり、そしてソフトウェア工学についてのセクションがあります。

🖥 [0:50] 画面(スライド「Source Document」=ヘッダー/医療研究/ソフトウェア工学の3領域) 左に3つのラベル(Header/Section about medical research/Section about software engineering)、右に黒背景のソース文書(各領域が破線で囲まれている)。 ``` This year our company engaged in many areas of research.

Section 1: Medical Research

This year saw significant strides in our understanding of XDR-47, a "bug" we have not seen before.

Section 2: Software Engineering

This division dedicated significant effort to studying various infection vectors in our distributed systems ```

[1:03] さて、このドキュメントを別々のチャンクに分割する方法はたくさんありますが、ここでは一つのやり方だけを提案します。このドキュメント内の、いわば個々の別々の行ごとに、別のチャンクを作ることにします。そうすると、ここに見えているような、およそ5つの別々のチャンクができあがります。

[1:26] これらの異なるチャンクをそれぞれ考えてみると、とても興味深いことに気づくでしょう。ここにある3番目のテキストのチャンクは、まるごと医療研究についてのものです。このテキストが扱っているのはそれです。医療研究セクションの中にあったものです。ところが、この中には「bug(バグ)」という単語が含まれています。

[1:39] ですから、いわば高いレベルで見ると、この段落だけをパッと一瞥した場合、それはほとんどソフトウェア工学についてのものであるかのように見えてしまいます。単に「bug」という単語を含んでいるという理由だけで。

🖥 [1:07] 画面(スライド「Output Chunks」=取り違えの吹き出し付き) 中央に5つのチャンク(黒背景)。3番目のチャンク内の "bug" が青くハイライトされ、右から吹き出しが刺さる:Section that is about medical research, but language seems related to software engineering。最下部のチャンク(infection vectors がボールド)にも吹き出し:Section that is about software engineering, but contains language related to medical research

[1:48] そして同様に、こちらの下の方には、ソフトウェア工学のセクションがあります。その中には「infection vectors(感染経路)」という単語が入っています。infection vectors は、どちらかというと医療用語寄りです。ですからもう一度言うと、ソフトウェア工学についてのセクションなのに、その中の言葉はどちらかというと医療研究についてのものになっている、というわけです。

[2:01] さて、では考えてみてほしいのですが、これらのチャンクを取って RAG パイプラインに追加したら、何が起きるでしょうか。ユーザーが「今年エンジニアはバグを何件直しましたか?(how many bugs did engineers fix this year?)」というような質問をしたとしましょう。すると、RAG パイプラインの一部としての私たちの仕事は、手持ちのテキストのチャンクの中から、ユーザーの質問に最も関連するものを見つけることになります。

[2:29] さて、ユーザーは「バグ」について何か言いました。ですから、まあ一見すると、ここにあるこのテキストのチャンクは、「bug」という単語を含んでいるという、ただそれだけの理由で関連しているように見えます。そこで私たちは、このテキストのチャンクを取って、全体のプロンプトにコンテキストとして追加することに決めるかもしれません。

[2:40] そして、すぐお分かりの通り、これは巨大なエラーです。ユーザーはレポートからソフトウェア工学について何かを理解したかったのです。ですから私たちが確実に欲しかったのはこちらのセクションだったのに、代わりに誤って医療研究についての何かを取ってきてしまいました。

🖥 [1:57] 画面(スライド「Break the document up into many chunks. Put chunks relevant to the user's question in the prompt.」) 左上に User's Question「How many bugs did engineers fix this year?」、左下に Chunks of Text(5チャンク)。右に黒背景のプロンプト。 ``` Answer the user's question about the financial document.

{user_question}

`` ユーザー質問と「bug」を含むチャンクから右のプロンプトへ赤い矢印が伸び、` の中に該当チャンク(This year saw significant strides in our understanding of XDR-47, a "bug" we have not seen before.)が挿入される様子が示される。

[2:50] ですから、これはチャンキング戦略が簡単に巨大なエラーと、非常に悪いコンテキスト挿入を、あなたのプロンプトに持ち込んでしまう一例です。そこで、この問題を解決するために、元のソース文書を取ってそれを異なるテキストのチャンクにどう分割するかを、たくさんの時間をかけて考えていきます。

[3:06] このビデオでは、文書を別々のテキストのチャンクへと分割するための、3つの異なるチャンキング戦略(手法)を取り上げます。それぞれが、いま見た問題に対処するための何らかの特徴や技術を持っています。というわけで、size-based(サイズベース)チャンキング、structure-based(構造ベース)、そして semantic-based(セマンティックベース)について議論していきます。

🖥 [2:29] 画面(スライド「Chunking Strategies — How do we divide text?」) 3カラム。 - Size Based:Divide the text into strings of equal length / • Include overlap on each side of the chunk to include some context / • Easy to implement, but might break up related content into separate chunks - Structure Based:Divide the text based upon the structure (headers, paragraphs, sections) / • Avoids breaking up related content into multiple chunks / • Requires us to understand the structure of the document beforehand - Semantic Based:Divide text into groups of related sentences or sections / • Requires us to understand the meaning of individual sentences / • Computationally expensive, but more relevant chunks

[3:21] 最初に取り上げるのは size-based チャンキングです。これは、私たちの大きな古い文書、つまり大きなテキストの塊を取って、それをいくつかの等しい長さの文字列に単純に分割するものです。これは実装するのが群を抜いて最も簡単な手法であり、また本番実装で最もよく目にするものでもあるでしょう。では、size-based チャンキングがどう行われるかを見てみましょう。

[3:48] size-based チャンキングでは、元の文書を取って、それをいくつかの、多少なりとも等しい長さの文字列に分割します。私たちの今回のケースでは、約325文字のソース文書があります。ですから、まったく恣意的にですが、それを3つの別々のチャンクに分割すると決められます。つまり、各チャンクは約108文字くらいになります。ですから最初の108文字を取ってチャンク1に、次の108文字をチャンク2に入れ、これを文書全体に対して繰り返す、というわけです。

🖥 [3:11] 画面(スライド「Source document has 325 characters. We could get 3 chunks, each about ~108 characters」) 左に Source Document、右に Output Chunks の3チャンク。分割位置で単語が途中で切れている様子が可視化されている。 - チャンク1末尾:「## Section 1: Medical Research / This year saw signific」(signific で切れる) - チャンク2:「ant strides in our understanding of XDR-47, a "bug" we have not seen before. / ## Section 2: Software Engineeri」(Engineeri で切れる) - チャンク3:「ng / This division dedicated significant effort to studying various infection vectors in our distributed systems」

[4:15] さて、とても単純な手法ですが、すぐに大きな欠点があります。それは、各チャンクがおそらく、その中にいくつか途中で切れた単語(cutoff words)を抱えてしまうことです。すぐにお分かりの通り、最初のチャンクは "significant" という単語が途中で切れています。ですから最初のチャンクでは "signific" までで、次のチャンクで単語の残りが終わっています。

[4:31] それに加えて、各チャンクは文脈を欠いてしまいます。例えば、こちらの下の3番目のチャンクは、残念ながら、そのすぐ上にあったセクションの見出しを実際には含んでいません。このセクション見出しは、ここにあるこのテキストが本当は何について話しているのかについて、多くの文脈を提供してくれたはずのものです。

🖥 [3:47] 画面(スライド「Sized Based Downsides」) 左に見出しと箇条書き:• Each chunk has some cutoff text / • Each chunk lacks context。右に前スライドと同じ3チャンク(2番目の "## Section 2: Software Engineeri" がハイライト選択されている)。

[4:46] そこで、size-based チャンキングを使うとすぐに出てくるこの問題を解決するために、overlap 戦略(重なり戦略)を実装できます。overlap 戦略とは、依然として size-based チャンキングを行うのですが、それに加えて隣接するチャンクから少しの重なりを含める、というものです。

[5:06] ですから例えば、ここにある元のチャンク1がありますが、次の下のチャンクから数文字分だけを含めることに決めるかもしれません。ですからこの場合、"significant" という単語の残りに加えて、その文全体の末尾まで含めるかもしれません。そうすると、このように、もう少し意味を持ったチャンクができあがります。

[5:24] そしてチャンク2については、依然として本体はここのこの領域のままですが、チャンクの前とチャンクの後ろから何文字分かの重なりを含めます。ですからこの戦略では、そこそこの量の重複テキストができあがることになります。例えばこの場合、「Section 1 Medical Research」が2番目のチャンクの中にありますが、これは1番目の中にも同じく含まれていました。

[5:44] ですからテキストの重複はありますが、こちらの利点は、各テキストのチャンクが一般的にもう少し多くの文脈を提供されている、ということです。

🖥 [4:31] 画面(スライド「Workaround: Each chunk gets some overlap with neighboring chunks」) 左に Source Document(チャンク境界が実線+破線で重なって描かれる)、右に Output Chunk。overlap により1チャンク目が次のように延長される: ``` This year our company engaged in many areas of research.

Section 1: Medical Research

This year saw significant strides in our understanding of XDR-47, a "bug" we have not seen before. ``` 後続フレームでは「## Section 1: Medical Research / This year saw significant strides… / ## Section 2: Software Engineering / This division dedicated significant」のように、隣接セクションが重なって1チャンクに入る様子が示される。

[5:49] 次に目にする種類の戦略は、structure-based チャンキングです。これは、私たちの文書の全体的な構造に基づいてテキストを分割するものです。ですから、見出しや段落、あるいは一般的なセクションを見つけ出そうとし、それらを各チャンクの分割線として使います。

[6:04] この分割戦略を私たちの文書に対して実装するのは、とても簡単でしょう。というのも、私たちの文書は markdown 構文で書かれているからです。それが分かるのは、ここに小さなポンド記号、つまり各セクションのダブルハッシュ(##)があるからです。

[6:18] ですから、これらの小さなポンド記号を探して、この種の記号を見かけるたびに、必ず新しいセクションが始まっているに違いない、と言えばよいわけです。ですから、ダブルハッシュ文字で分割するコードをプログラム的にとても簡単に書き出せて、ここに見えているようなかなり整った形のセクションができあがります。

🖥 [5:54] 画面(スライド「Source Document → Output Chunks」=構造ベースの綺麗な分割) 左に Source Document(各行/セクションが実線枠)、右に Output Chunks の3チャンク。矢印で対応。 - チャンク1:「This year our company engaged in many areas of research.」 - チャンク2:「## Section 1: Medical Research / This year saw significant strides in our understanding of XDR-47, a "bug" we have not seen before.」 - チャンク3:「## Section 2: Software Engineering / This division dedicated significant effort to studying various infection vectors in our distributed systems」

[6:33] さて、これは素晴らしい戦略に聞こえるかもしれませんが、残念ながら現実はそれほど頻繁には味方してくれません。多くの場合、あなたは markdown 構文でまったく整形されていない文書を取り込もうとすることになります。それらはただのプレーンテキストを含むだけのプレーン PDF 文書かもしれず、その場合、こういった非常にはっきり区切られたセクションは得られません。

[6:53] ですから繰り返しになりますが、これは素晴らしい手法に見えるにもかかわらず、それを実装するのは本当に難しくなりえます。特に、あなたが自分の異なる文書の構造について何の保証も持っていない場合には。

[7:01] 私たちが議論する最後のチャンキング戦略は semantic-based チャンキングです。これは、あなたのすべてのテキストを取って、それを文または節に分割し、それから何らかの自然言語処理の技術を使って、連続する各文がどれだけ関連しているかを見極める、というものです。そして、こうした何らかの形で関連する文や節のグループから、あなたのチャンクを組み立てていきます。

[7:21] さて、説明だけでお分かりの通り、これは群を抜いて間違いなくより高度な手法です。ですから、実際の実装にはあまり深く踏み込みません。私がこれについて言及した唯一の理由は、チャンキング戦略に決まった有限の固定された数など本当に存在しない、ということを明確にするためだけです。私たちのテキストをどう分割するかを決める方法は、事実上無限にあります。

[7:40] ですから、どの手法を使うかを決めることは、本当にあなたの個別のユースケースと、あなたが取り込もうとしている文書について持っている保証次第、ということになります。

🖥 [7:05] 画面(スライド「Chunking Strategies」再掲) 前掲の Size Based / Structure Based / Semantic Based の3カラムスライド(semantic-based の説明に対応して再表示される)。

[7:45] さて、先へ進む前に、あなたと一緒にごく手短な例を見ておきたいと思います。というわけで、3つの異なるチャンキング戦略を実装した Jupyter ノートブックを用意しました。ですので、001 chunking という名前のノートブックを探すことをお勧めします。また、付属の report.md ファイルをダウンロードして、そのノートブックと同じディレクトリの中に置くようにしてください。

[8:11] この report.md ファイルの中には、ちょっとした架空のサンプルレポートが入っていて、RAG パイプラインの実装方法を学びながらテスト目的でこれを使っていきます。さて、この中にはいくつかの異なるセルがあります。最初のものには、chunk by character(文字による分割)のサンプル実装が入っています。

🖥 [7:55] 画面(Jupyter ノートブック 001_chunking.ipynb — 先頭セル chunk_by_char) VS Code + Jupyter。タブは 001_chunking.ipynbreport.md。カーネルは .venv (Python 3.12.9)。全4セルのうち Cell 1。 ```python

Chunk by a set number of charactesr

def chunk_by_char(text, chunk_size=150, chunk_overlap=20): chunks = [] start_idx = 0

while start_idx < len(text):
    end_idx = min(start_idx + chunk_size, len(text))

    chunk_text = text[start_idx:end_idx]
    chunks.append(chunk_text)

    start_idx = (
        end_idx - chunk_overlap if end_idx < len(text) else len(text)
    )

return chunks

`` (※コメントは画面表記どおり "charactesr" とスペルミス。次セルは# Chunk by sentence/import re`)

[8:27] ですからこれは size-based 戦略に基づいた実装で、テキストを、いくらかの重なりも持った等しい長さの文字列に分割していきます。引数は text、各チャンクのサイズ、そしていくらかの chunk overlap になることに気づくでしょう。ですから繰り返しますが、それはチャンクの両側に持たせたい文字数のことです。

[8:46] 次のセルは、chunk by sentence(文による分割)をどう行うかを示しています。ですからとても似たアイデアですが、今度は正規表現(regular expression)を使ってテキストを個々の文に分割しています。そして各チャンクは、いくつかの文から、任意で両側に少しの重なりを付けて形成されます。

🖥 [8:46] 画面(ノートブック — chunk_by_sentence セル) ```python

Chunk by sentence

import re

def chunk_by_sentence(text, max_sentences_per_chunk=5, overlap_sentences=1): sentences = re.split(r"(?<=[.!?])\s+", text)

chunks = []
start_idx = 0

while start_idx < len(sentences):
    end_idx = min(start_idx + max_sentences_per_chunk, len(sentences))

    current_chunk = sentences[start_idx:end_idx]
    chunks.append(" ".join(current_chunk))

    start_idx += max_sentences_per_chunk - overlap_sentences

    if start_idx < 0:
        start_idx = 0

return chunks

```

[9:06] そして最後に、私たちの文書の構造とその正確な内容について本当に強い保証を持っている場合には、chunk by section(セクションによる分割)を使ってみるかもしれません。これは structure-based チャンキングの一例になります。ですからこの例では、改行文字を、次にポンド記号2つを、次にスペースを探します。そしてそれが私たちの分割の基準になります。

🖥 [9:06] 画面(ノートブック — chunk_by_section セル) ```python

Chunk by section

def chunk_by_section(document_text): pattern = r"\n## " return re.split(pattern, document_text) ```

[9:21] ですから、いわば executive summary が最初のチャンクになります。まあ厳密には2番目ですが、これ(先頭)がこちらの1番目のチャンクになり、それから executive summary の中身が table of contents が始まるところまで全部で、それが私たちの2番目のチャンクを開始します。そして次のチャンクが methodology、それから section 1、という具合になります。

[9:42] これは各チャンクに最良の整形を与えてくれます。というのも、各チャンクはちょうど1つのセクションで構成されるからです。ただし、これがうまくいくのは、私たちが文書の構造について保証を持っているからにすぎません。私たちはそれが markdown であることを知っていて、なおかつ、新しいセクションが始まる場合にしか、あの「改行+ポンド+ポンド+スペース」が現れないと知っています。

🖥 [9:12] 画面(report.md の中身 — 構造ベース分割の根拠) markdown 文書の一部。行番号付き。 ```markdown

Annual Interdisciplinary Research Review: Cross-Domain Insights

Executive Summary

...(本文省略なく表示)...

Table of Contents

  1. Executive Summary
  2. Table of Contents
  3. Methodology
  4. Section 1: Medical Research – Understanding XDR-471 Syndrome
  5. Section 2: Software Engineering – Project Phoenix Stability Enhancements
  6. Section 3: Financial Analysis – Q3 Performance and Outlook
  7. Section 4: Scientific Experimentation – Characterization of Material Composite XT-5
  8. Section 5: Legal Developments – Navigating IP Precedents and Regulatory Shifts
  9. Section 6: Product Engineering – Finalizing Model Zircon-5 Specifications
  10. Section 7: Historical Research – Re-evaluating the Galveston Accords (1921)
  11. Section 8: Project Management – Progress on Project Cerberus Phase 2B
  12. Section 9: Pharmaceutical Development – Compound CTX-204b Phase IIa Update
  13. Section 10: Cybersecurity Analysis – Incident Response Report: INC-2023-Q4-011
  14. Future Directions

Methodology

...

Section 1: Medical Research – Understanding XDR-471 Syndrome

... ```

[9:57] さて、これらをそれぞれ手早くテストしてみましょう。ノートブックに戻って、一番下のセルに下がって、まず chunk by character を試してみます。ですからファイルを開いて、その中のすべてのテキストを取り出します。chunk by character して、それから各チャンクをちょっとした区切りを間に挟んで出力するだけです。ですからそれを実行します。

🖥 [9:57] 画面(ノートブック — 一番下の実行セル) Cell 4 of 4。 ```python with open("./report.md", "r") as f: text = f.read()

chunks = chunk_by_char(text)

[print(chunk + "\n----\n") for chunk in chunks] ```

[10:15] すると、最初のチャンクがここに、2番目、という具合に得られるのが見えます。そしてすぐに、デフォルト設定があまり良いチャンクを生み出さないことが分かります。というのも、デフォルト設定はチャンク長150、overlap 20だからです。

[10:29] ですからこの場合、各チャンクはあまり多くの意味を提供してくれません。例えば、ここにあるこの文は、私たちにとって本当に何をしてくれるでしょうか。それを使ってユーザーの質問に本当に答えられるでしょうか。うーん、たぶん無理でしょう。

[10:41] そこで、ここでデフォルト設定を大幅に変えることに決めるかもしれません。たとえばチャンク長を500、overlap を150にしたいとします。それでもう少し良いものが得られるか見てみましょう。

[10:54] はい、それは以前のものより少し良いですね。ですから今、いくらかの情報を与えてくれる、実際の個々のセクションの形成が見え始めます。また、overlap にもすぐに気づき始めるでしょう。

[11:06] ですから "addressing complex challenges(複雑な課題に取り組む)" というのが見えます。実は、そのまさに同じフレーズが、すぐ上のチャンクの中に含まれています。ですからそれが、私たちが得る overlap(重なり)の一例です。

🖥 [10:41] 画面(ノートブック — chunk_by_char を 500/150 に変更して実行) python chunks = chunk_by_char(text, 500, 150) 出力(---- 区切りで各チャンクを print)。overlap の可視化として、あるチャンクの末尾「…addressing complex challenges that transcend」が、直後のチャンクの冒頭「ddressing complex challenges that transcend traditional disciplinary boundaries. …」として重複して現れる。

[11:16] よし、次に2つ目の戦略、chunk by sentence を試してみましょう。そしてまたデフォルトの引数を使います。これは実際、かなり強そうに見えます。ですからこれは、デフォルトで各チャンクに5つの文を、1つの文の overlap 付きで与えてくれるはずです。そして各文の分割には正規表現を使っています。ですから文を正しく分割しない場合もあるかもしれませんが、一見したところ、そうですね、これはかなり良さそうだと言えます。各チャンクがしっかりした量の情報を与えてくれているように見えます。

🖥 [11:16] 画面(ノートブック — chunk_by_sentence をデフォルトで実行) ```python chunks = chunk_by_sentence(text)

[print(chunk + "\n----\n") for chunk in chunks] `` 出力:----` 区切りで、5文程度+1文 overlap のまとまったチャンクが並ぶ(Executive Summary 本文が意味の通る単位で分割されている)。

[11:44] そして最後に、chunk by section を試せます。それを実行します。すると、最初のチャンクはあまり有用な情報を含まないことが分かりますが、それ以降のすべては本当に、本当に強いことが分かります。というのも、毎回ちょうど1つのセクションを得ているからです。executive summary だけ、それから table of contents、それから section 1、section 2、section 3、という具合です。

🖥 [11:52] 画面(ノートブック — chunk_by_section を実行) ```python chunks = chunk_by_section(text)

[print(chunk + "\n----\n") for chunk in chunks] `` 出力:先頭チャンクはタイトル「# **Annual Interdisciplinary Research Review: Cross-Domain Insights**」+区切りのみで情報が薄い。以降は----` 区切りで「Executive Summary(本文)」→「Table of Contents(1.〜14. の目次)」→「Methodology」→ 各 Section が1チャンク=1セクションで綺麗に分かれる。

[12:07] ですからもう一度言うと、どの戦略を使うかは、完全にあなたの文書の性質と、その構造について持っている保証次第です。私たちにとっては chunk by section が素晴らしく見えますが、もし、各文書の書式について何の保証もないユーザー提供の文書を受け取ることを予想しているなら、chunk by section を使うのは長い目で見るとうまくいかないでしょう。

[12:33] その場合、chunk by sentence にフォールバックするかもしれませんが、これでさえあまりうまくいかないかもしれません。例えば、ユーザー提供のコードをチャンク化しようとしていると想像してみてください。もしコードを個々の文に分割しようとすると、おそらく多くの予期せぬ結果を得ることになるでしょう。というのも、コードは非常に予期せぬ場所にピリオドを持つ傾向があるからです。

🖥 [12:54] 画面(ノートブック — chunk by character へのフォールバックに戻る) ```python chunks = chunk_by_char(text)

[print(chunk + "\n----\n") for chunk in chunks] ```

[12:54] ですからそれは、私たちが単に、あの昔ながらの信頼できる標準、つまり chunk by character にフォールバックする、ということを意味するかもしれません。

[13:00] chunk by character は最良の結果を与えることを保証はしませんが、大多数のケースで機能しますし、そこそこうまく機能してくれます。

英語逐語(ASR全文)

Inside of this video and the next couple of videos, we're going to start to implement our own custom RAG workflow inside of a series of different notebooks. We're going to first focus on just making the most simple basic RAG setup we can possibly make, and then we're going to add in some additional steps over time. Now, as a reminder, a typical RAG pipeline looks a little bit like this to really simplify things. We're going to take a source document, break it up into chunks of text. Then whenever a user asks us a question, we're going to find some relevant chunk of text, put it into a prompt. and that's pretty much the entire thing. So step one of this entire flow is to take a source document and break it up into chunks of text. Now believe it or not this process of taking a document and breaking it up into separate chunks is one of the more complex steps of the entire RAG pipeline simply because how we chunk our document up has a huge output on the quality of our RAG pipeline. And I want to give you an example right away to help you understand why that's the case. So take a look at this source document. It's just a couple of little lines, and it's supposed to kind of represent some kind of report from a company or something like that. And just reading through it really quickly, we can see there's really three general areas. We have a header, we have a section about medical research, and then a section about software engineering. Now, there are many ways in which we could divide this thing up into separate chunks, but I'm just going to suggest one way. I'm going to say for every kind of distinct line inside this document, we're going to make a separate chunk. So we'd end up with about five separate chunks like the ones you see right here. If you consider each of these different chunks now, you will notice something really interesting. The third chunk of text right here is all about medical research. That's what this text is about. It was inside of the medical research section, but it contains the word bug. So at kind of a high level, if you just glanced at this paragraph alone, it is almost like it's kind of about software engineering just because it contains the word bug. And then likewise, down here, we have the software engineering section. and inside of it is the word infection vectors. Infection vectors is a little bit more of a medical term. So once again, we have a section that is about software engineering, but the language inside of it is kind of about medical research. So now I want you to think about what would happen if we took these chunks and we add them into our RAG pipeline. Let's imagine that a user asked a question of something like, how many bugs did engineers fix this year? So now our job as a part of the RAG pipeline would be to find the chunks of text we have that are most relevant for the user's question. Well, the user said something about bugs. So, well, at first glance, this chunk of text right here seems relevant just because it contains the word bug. So we might decide to take this chunk of text and add it as context into the overall prompt. And as you can tell right away, this is a huge error. The user wants to understand something about software engineering from the report. So we definitely wanted this section, but we erroneously got something about medical research instead. So this is an example where a chunking strategy can easily introduce huge errors and very bad context inserts into your prompt. So to solve this problem, we're going to spend a lot of time thinking about how we're going to take our original source document and break it up into different chunks of text. In this video, we're going to cover three different chunking strategies or methods to divide our document into separate chunks of text, each of which have some feature or some technique meant to address the problem that we just saw. So we are going to discuss size-based chunking, structure-based, and semantic-based. The first one we're going to cover is size-based chunking. This is where we take our big old document, so a big chunk of text, and we just divide it into a number of strings of equal length. This is by far the easiest technique to implement, and it's also probably the one you're going to see most often in production implementations. So let's take a look at how size-based chunking is done. With size-based chunking, we're going to take our original document and divide it into some number of strings of more or less equal length. In our particular case, we have a source document with about 325 characters. So we could decide just completely arbitrarily to divide that into three separate chunks. And that means each chunk would have about 108 characters or so. So we might take the first 108 characters, put them in the chunk one, the next 108, put them in the chunk two, and just repeat for the entire document. Now, very simple technique, but right away it has a big downside. And that is that each chunk is probably going to end up with some number of cutoff words inside of it. You can see right away, the first chunk has the word significant cutoff. So it's just significant key in the first chunk, and then it ends the word in the next one. In addition, each chunk ends up lacking context. So for example, the third chunk down here, unfortunately, does not really include the section header that was right above it. And this section header would have provided a lot of context on what this text right here is really talking about. So to solve this problem that starts to come up right away, if you use size based chunking, we can implement a overlap strategy. An overlap strategy is where we are still going to do size space chunking, but we're also going to include a little bit of overlap from the neighboring chunks. So for example, we have the original chunk one right here, but we might decide to include just a number of characters from the next chunk down. So in this case, we might include the rest of the word significant plus the end of that entire sentence. So we would end up with a chunk that looks like this, that just has a little bit more meaning to it. And then for chunk two, we would still have the body be this area right here, but we'd include an overlap of some number of characters from before the chunk and after the chunk. So with this strategy, we are going to end up with a decent amount of duplicated text. For example, in this case, we have section one medical research inside the second chunk, and that was also included inside the first one as well. So there is duplication of text, but the upside here is that each chunk of text has, in general, a little bit more context provided for it. The next kind of strategy that you're going to see is structure-based chunking. This is where we are going to divide up the text based upon the overall structure of our document. So we might try to find headers or paragraphs or general sections and use those as our dividing lines for each chunk. Implementing this strategy of chunking with our document would be really easy because our document is written with markdown syntax. We know that because it has the little pounds right here, the double hashes for each section. So we might look for these little pound symbols and then say that every time we see this kind of symbol, that means we must be starting a brand new section. So we could very easily write out some code to programmatically split on the double hash characters and we would end up with some pretty well-formed sections like what you see right here. Now this might sound like a fantastic strategy, but unfortunately, reality just doesn't favor it quite so often. In many cases, you are going to be trying to ingest documents that are not formatted with markdown syntax at all. They might be plain PDF documents that just contain plain text, in which case you will not get these very clearly delineated sections. So again, even though this seems like a great technique, implementing it can be really challenging, especially if you do not have any guarantees around the structure of your different documents. The last chunking strategy that we are going to discuss is semantic-based chunking. This is where you might take all of your text, divide it up into sentences or sections, and then use some kind of natural language processing technique to figure out how related each consecutive sentence is. You'll then build up your chunks out of groups of these somehow related sentences or sections. Now, as you can tell just by the description, this is by far definitely a more advanced technique, so we're not going to look too closely into the actual implementation. The only reason I mention it at all is just to make it clear that there's really no set finite fixed number of chunking strategies. There's really an infinite number of ways in which we can decide to divide up our text. And so deciding upon which method you use really comes down to your particular use case and what guarantees you have around the documents that you are trying to ingest. Now, before we move on, I want to go over a very quick example with you. So I've got a Jupyter notebook put together with three different chunking strategies implemented inside of it. So I would encourage you to find a notebook called 001 chunking. Also make sure that you download the accompanying report.md file and place it inside the same directory as that notebook. This report.md file has a little sample kind of fictional report inside of it that we're going to use for testing purposes as we learn about how to implement a RAG pipeline. So inside of here, you'll find a couple of different cells. The first one contains a sample implementation of a chunk by character. So this is an implementation based upon the size-based strategy, where we are going to divide up our text into strings of equal length that also have some amount of overlap on them. You'll notice that the arguments are going to be the text, the size of each chunk, and then some amount of chunk overlap. So again, that is the number of characters we want to have on either side of the chunk. The next cell shows how we might chunk by sentence. So very similar idea, but now I'm using a regular expression to split the text up into individual sentences. And then each chunk will be formed out of some number of sentences with optionally a little bit of overlap on each side. And then finally, if we have really strong guarantees around the structure of our document and its exact contents, we might try to use a chunk by section, which would be a example of structure-based chunking. So in this example, it's going to look for a new line character, and then two pound signs, and then a space. And that is going to be our separation criteria. So we would get kind of executive summary would be the first chunk. Well, technically the second one, this would be the first chunk up here, but then we would get everything inside the executive summary all the way down to the table of contents that would start off our second chunk and then the next chunk would be the methodology and then section one and so on. This will give us the best formatting for each chunk because each chunk will consist of exactly one section but it really only works because we have a guarantee around the structure of the document. We know it is marked down and we know that we're only going to see that new line pound pound space in the case that we have a new section beginning. Now let's test each of these really quickly. So back inside my notebook, I'm going to go down to the bottom cell and I'm going to first try out the chunk by character. So I'm opening up the file, getting all the text out of it. I'm going to chunk by character and then just print out each chunk with a little separator between each one. So I'll run that. And now we'll see that we get our first chunk right here, second, and so on. And right away, you can see that the default settings do not produce very good chunks. So the default settings are a chunk length of 150 with a overlap of 20. So in this case, each chunk doesn't really provide a whole lot of meaning. Like what does this sentence right here really do for us? And can we really use it to answer any user question? I don't know, maybe not. So we might decide to dramatically change our default settings here. Maybe I want a chunk length of 500 with a overlap of 150. Let's see if that gives us something a little bit better. Okay, that's a little bit better than what we had before. So now I can start to see the formation of actual individual sections here that give us a little bit of information. You'll also very quickly start to notice the overlaps. So I can see addressing complex challenges. Well, it turns out that exact phrase is included inside of the chunk right above. So that's an example of the overlapping that we get. all right next up let's try our second strategy which is chunk by sentence and again I'm going to use the default arguments and this one actually looks like it's pretty strong so this should give us five sentences by default in each chunk with one sentence of overlap and we are using a regular expression to split up each sentence so there might be cases where it doesn't split a sentence correctly but at first glance yeah I'd say this looks pretty good each chunk appears to give us a solid amount of information. And then finally, we can try out chunk by section. Now run that. And now we can see that the first chunk is not going to contain a lot of useful information, but everything after that is really, really strong because we are getting exactly a single section each time. I get just the executive summary and then the table of contents and then section one, section two, section three, and so on. So once again, which strategy you use entirely comes down to the nature of your document and what guarantees you have around its structure. For us, chunk by section looks fantastic, but if we are expecting to receive user-provided documents where there are no guarantees around the formatting of each document, then using chunk by section is probably not going to work out in the long run. In that case, we might fall back to chunk by sentence, but even this might not work out pretty well. Imagine that we are trying to chunk user-provided code, for example. Well, if we try to split code up into individual sentences, we are probably going to get a lot of unexpected results because code tends to have periods in very unexpected places. So that might mean that we just fall back to the old reliable standard, which is chunk by character. Chunk by character is not guaranteed to give you the best results, but it's going to work the vast majority of the time and it's going to work out reasonably well.

↑ 目次へ戻る

Building with the Claude API | Lesson 41「Text embeddings」(全文ナレッジ)

動画 4分01秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンのテーマ

RAG パイプラインで「ユーザーの質問に関連するチャンクを見つける」工程の中核技術、text embeddings(テキスト埋め込み)の入門。embedding とは何か・数値をどう解釈すべきか・どうやって生成するか(Voyage AI)を扱う。

  • ソースドキュメントからチャンクを抽出した後、RAG の次のステップは「ユーザーの質問が来たら、全チャンクの中から質問に関連するものを見つけてプロンプトにコンテキストとして入れる」こと。
  • この「関連チャンク探し」は複雑さが隠れているが、突き詰めれば正真正銘の検索問題(search problem)。質問を受け取り、全チャンクを検索して関連コンテンツを浮かび上がらせる。
  • RAG パイプラインでの最も一般的な実装が Semantic Search(セマンティック検索)。text embeddings を使って「各チャンクが何について書かれているか」を理解し、質問に最も関連するチャンクを見つける。

text embedding とは

  • あるテキストに含まれる意味の数値的な表現(a numerical representation of the meaning contained in some text)。
  • embedding model(埋め込みモデル)が生成する。例えば「I'm very happy today」を入力すると、数値の長いリストが出力される。この長いリストが embedding そのもの。
  • 各数値は -1 から +1 の範囲を取る。

各数値の解釈 —「品質スコア」という直観(ただしラベルは創作)

  • embedding 内の各数値は「入力テキストの何らかの品質(quality)のスコア」を表す。
  • ただし実際には、各数値がどの品質に対応しているかは誰にも分からない。「1番目の数値=テキストのハッピーさ」というラベル付けは正確ではない(講師の完全な創作)。
  • それでも「1番目=ハッピーさのスコア、2番目=果物の話題度…」のように想像すると、embeddings の理解には極めて有効。「入力テキストのさまざまな品質のスコアの集まり」とイメージするのが推奨される捉え方。

Anthropic は embedding 生成を提供しない → 推奨は Voyage AI

  • Anthropic は現在 embedding 生成を提供していない。推奨プロバイダーは Voyage AI
  • 別会社なので別アカウントのサインアップ+別 API キーが必要。ただし無料で始められて超簡単
  • レクチャー添付の PDF にアカウント作成〜API キー取得の手順がある。

セットアップ:VOYAGE_API_KEY と ノートブック 002_embeddings

  • 生成した API キーは、既存の Anthropic API キーのすぐ隣に .env ファイルへ追加する。変数名は必ず VOYAGE_API_KEY
  • レクチャー添付の新ノートブック 002 embeddings をダウンロード。冒頭に Voyage AI SDK のインストールコマンドがあるので必ず実行する。
  • ノートブック下部に generate_embedding 関数(テキストを受け取り embedding を返す。これ以上ないくらいシンプル)が用意済み。
  • 最後のセルは「report を開く → チャンク化 → 先頭チャンクを generate_embedding に渡す」で、実行すると embedding のリストが返ってくる。
  • 生成は速くて手間いらず。本当の課題は embedding を作ることではなく、RAG パイプライン全体への組み込み方の理解(=次のトピック)。

実務ポイント(このレッスンから持ち帰るもの)

  • Anthropic に embedding API はない。推奨は Voyage AI(別会社・別アカウント・別 API キー・無料で開始可・簡単)。RAG 構築時は最初からこの分業を前提に設計する。
  • .env のキー名は VOYAGE_API_KEY。既存の ANTHROPIC_API_KEY の隣に並べて管理する。
  • embedding の直観=「意味のスコアのベクトル」。各数値は -1〜+1 で、実際に何を表すかは不明だが「各次元=入力テキストのある品質のスコア」と思うと理解・説明がしやすい(説明用ラベルは創作と明示すること)。
  • チャンク選定は本質的に検索問題であり、RAG での標準解は Semantic Search。キーワード一致ではなく「意味」で探すために embedding を使う。
  • embedding の生成自体は一瞬で終わる簡単な処理generate_embedding(text) 一発)。難所は生成ではなく RAG パイプラインへの組み込み(類似度計算・検索・文脈投入)で、そこが次レッスン以降の主戦場。

本文(時系列・日本語逐語+画面差し込み)

🖥 [0:00] 画面(スライド「Break the document up into many chunks. Put chunks relevant to the user's question in the prompt.」) 前レッスンまでのチャンク化のおさらいスライド。左上に「User's Question」ボックス: User's Question | How many bugs did engineers fix this year? 左下「Chunks of Text」として3つの黒いチャンクボックス: This year our company engaged in many areas of research. ```

Section 1: Medical Research

This year saw significant strides in our understanding of XDR-47, a "bug" we have not seen before.

Section 2: Software Engineering

This division dedicated significant effort to studying various infection vectors in our distributed systems 右側に黒いプロンプトボックス。ユーザーの質問と Section 1 のチャンクから赤い矢印がプロンプトへ伸びている: Answer the user's question about the financial document.

{user_question}

```

[0:00] ソースドキュメントからいくつかのテキストチャンクを抽出した後、私たちの RAG パイプラインにおける次のステップは、ユーザーが質問やクエリ、あるいはそういった類のものを送信してくるのを待つことです。そしてそれが起こったら、私たちは手元にあるすべてのテキストチャンクを見渡して、ユーザーの質問に何らかの形で関連していそうなものをいくつか見つけ出す必要があります。それらをコンテキストとしてプロンプトに追加できるようにするためです。

[0:20] さて、ユーザーの質問に関連するいくつかのテキストチャンクを見つけ出すというこのプロセスですが、ああ、ここには多くの複雑さが隠れています。しかし本当によく考えてみると、これは正真正銘の検索問題(search problem)なのです。ユーザーの質問を受け取り、関連するコンテンツが見つかるまで手持ちのすべてのチャンクを検索し、それらを何らかの形で浮かび上がらせたい、ということです。

🖥 [0:26] 画面(スライド「Finding Relevant Chunks」) 左側に箇条書き: - This is a search problem! - How do we find chunks most related to the user's question?

右側は前スライドと同じ「User's Question:How many bugs did engineers fix this year?」+「Chunks of Text」3チャンク(research/## Section 1: Medical Research(XDR-47)/## Section 2: Software Engineering)。

🖥 [0:30] 画面(スライド「Semantic Search」) 左側の説明文: Utilize text embeddings to better understand the user's question and what each chunk of text is really talking about. (text embeddings を利用して、ユーザーの質問と、各テキストチャンクが実際に何について話しているのかをより良く理解する) 右側は引き続き同じ User's Question + Chunks of Text の図。

[0:39] これを RAG パイプラインの中で実装する最も一般的な方法は、Semantic Search(セマンティック検索)として知られるシステムを実装することです。Semantic Search は、text embeddings(テキスト埋め込み)と呼ばれるものを利用して、各テキストチャンクが何について書かれているのかをより良く理解し、ユーザーの質問に最も関連するテキストチャンクを何らかの方法で見つけ出します。

[0:56] これから text embeddings にフォーカスして、それが私たちのために何をしてくれているのかを本当に理解するために、かなりの時間を使っていきます。それでは、いくつかの図を見てみましょう。

🖥 [1:05] 画面(スライド「Text Embeddings」定義+Embedding Model の図) 見出し:Text Embeddings/サブ見出し: A numerical representation of the meaning contained in some text. 図:左に3つの入力テキスト(黒ボックス)→ 中央の「Embedding Model」ボックス → 右に「Embedding (List of numbers)」として3つの数値リスト: I'm very happy today! → [0.279, -0.05, -0.009, 0.143, …] That movie wasn't great → [0.597, 0.095, 0.411, -0.229, …] abcd → [0.112, -0.264, -0.135, -0.091, …]

[1:06] text embedding とは、あるテキストに含まれる意味の数値的な表現です。これらの text embeddings は、embedding model(埋め込みモデル)と呼ばれるものによって生成されます。embedding model に「I'm very happy today」のようなテキストを入力すると、embedding model は数値の長いリストを吐き出します。その数値の長いリストこそが、私たちの実際の embedding です。embedding の中の数値は、マイナス1からプラス1までの範囲を取り得ます。

[1:29] そこで次の疑問は、これらの数値は実際のところ何を意味しているのか、ということです。embedding の中の各数値は、入力テキストの何らかの品質(quality)のスコアを表しています。さて、ここが少しややこしくなるところです。というのも、この図の中で私は相反する2つの考え方を同時に示しているからです。これを分解して、とことん明確に説明させてください。

🖥 [1:32] 画面(スライド「Text Embeddings」各数値=品質スコアの図) 見出し:Text Embeddings/サブ見出し: Each number is a "score" of some quality of the input text. We don't know precisely what each number represents. (2行目は太字強調)中央に黒い帯で数値リスト: [0.279, -0.05, -0.009, 0.143, -0.229, 0.411, …] 各数値へ赤い矢印付きのラベルボックスが上下から差している: - 0.279 ← How "happy" the text is(上段) - -0.05 ← How much the text is talking about fruit(下段) - -0.009 ← How much the text is talking about oceans(上段) - 0.143 ← How "formal" the text is(下段) - -0.229 ← How much the text is referring to driving(上段)

[1:47] 実際のところ、embedding の中の各数値が実際にどの品質に結びついているのか、私たちには分かりません。ですから、あの最初の数値にラベルを付けて「これはテキストがどれくらいハッピーかのスコアです」と言うのは、完全に正確とは言えません。あの最初の数値が本当は何を表しているのか、私たちには単純に分からないのです。

[2:01] それでもなお、これらの数値をそのように捉えることは非常に役立ちます。最初の数値はテキストがどれくらいハッピーかを表す1つのスコアかもしれない、そして2番目はテキストがどれくらい果物について話しているかだ、等々と想像することは有益なのです。

[2:14] 繰り返しますが、これらのラベルは完全に私のでっち上げであり、各数値が何を表しているのか実際には分かりません。しかし、embeddings をこのように考えることは極めて有益です。ですから、これらの数値の一つひとつは、本当にそんなふうにイメージしてほしいのです。それらは、入力テキストのさまざまな品質のスコアのようなもの、というわけです。

[2:28] 最後に理解しておく必要があるのは、これらの embeddings を実際にどうやって生成するかです。Anthropic は現在、embedding の生成を提供していません。代わりに、推奨プロバイダーは Voyage AI です。これは別の会社なので、別アカウントのサインアップと、別の API キーが必要になります。しかし、無料で始められますし、非常に簡単に使えます。

🖥 [2:30] 画面(スライド「VoyageAI」) 見出し:VoyageAI/サブ見出し: Recommended embedding provider. Requires signup + API key. Free to get started. 図は先ほどの Text Embeddings の図と同構成で、中央のボックスだけ「VoyageAI Embedding Model」になっている: I'm very happy today! → [0.279, -0.05, -0.009, 0.143, …] That movie wasn't great → [0.597, 0.095, 0.411, -0.229, …] abcd → [0.112, -0.264, -0.135, -0.091, …]

[2:47] このレクチャーには、アカウントの作成と API キーの取得のプロセスを案内してくれる PDF が添付されています。API キーを生成したら、それを既存の Anthropic API キーのすぐ隣に、.env ファイルへ追加する必要があります。必ず voyage・アンダースコア・API・アンダースコア・key という名前の変数(VOYAGE_API_KEY)に割り当ててください。そして、まさにそこに、いま取得した生成済みのキーを入れます。

🖥 [2:54] 画面(VS Code への画面遷移:.env ファイル) VoyageAI のスライドの上に、左から VS Code のウィンドウ(タイトルバー「.env — 07」)がスライドインしてくる遷移場面(近接フレーム4枚は同一場面の変化)。.env ファイルに2行が見えるが、遷移中のため行頭が画面外に切れている: …_API_KEY="MY_KEY" …I_KEY="MY_KEY" (見えている範囲の転記。行頭部分は画面外で判読不能。値はいずれもプレースホルダー "MY_KEY")

🖥 [3:00] 画面(VS Code:.env と 002_embeddings.ipynb を開いた状態) タイトルバー「.env — 07」。タブ:.env(未保存ドット付き)と 002_embeddings.ipynb(アクティブ)。左のエクスプローラーには: .env 001_chunking.ipynb 002_embeddings.ipynb ← 選択中 report.md ノートブックツールバー:Generate / + Code / + Markdown / Run All / Restart / Clear All Outputs / Jupyter Variables / Outline / .venv (Python 3.9.6)。このフレームではノートブック本文エリアは空白表示。

[3:08] .env ファイルを更新したら、さらに、このレクチャーに添付されている 002 embeddings という新しいノートブックファイルをダウンロードすることをお勧めします。その一番上に、Voyage AI SDK をインストールするために実行する必要があるコマンドがあります。ですから、必ずそのコマンドを実行して、ライブラリをインストールしてください。

[3:26] このノートブックのさらに下の方に、generate embedding という関数をすでに用意しておきました。この関数は、何らかのテキストを受け取り、それに対する embedding を返します。ほとんどこれ以上ないくらいシンプルです。

🖥 [3:30] 画面(Jupyter ノートブック 002_embeddings.ipynb 最下部のセル) タイトルバー「002_embeddings.ipynb — 07」。パンくず:002_embeddings.ipynb > with open("./report.md", "r") as f:。直前のセルは実行済み([7] ✓ 0.0s。セル末尾がわずかに見切れており判読不能)。最終セル(未実行 [ ])のコード: ```python with open("./report.md", "r") as f: text = f.read()

chunks = chunk_by_section(text)

generate_embedding(chunks[0]) ``generate_embedding` の部分が選択ハイライトされている。カーネルは .venv (Python 3.9.6)。

[3:38] そこで、この中のすべてのセルを実行してから、一番下のセルを実行します。このセルは今、レポートを開き、レポートをチャンク化し、それから最初のチャンクを取って generate embedding に渡すようになっています。それからこのセルを実行すると、embeddings のリストが返ってきます。

[3:52] ご覧のとおり、embeddings の生成はかなり速く、かなり手間いらずです。ですから、ここでの本当の課題は embeddings を作ることではありません。それが全体の RAG パイプラインの中に実際どう組み込まれるのかを理解することです。それが、次に私たちが調べていくトピックになります。

英語逐語(ASR全文)

After extracting some number of text chunks out of a source document, the next step inside of our RAG pipeline is to wait for a user to submit a question or a query or something like that. And whenever that occurs, we need to take a look at all of our different text chunks and find some number that seem to be somehow related to the user's question, so that we can add them as context into our prompt. Now, this process of finding some number of text chunks that are related to the user's question, oh, there's a lot of complexity hidden there. But when we really think about it, this truly is a search problem. We want to take the user's question and search through all of our different chunks until we find some related content and then surface those in some way. The most common way of implementing this inside of a RAG pipeline is by implementing a system known as Semantic Search. Semantic Search utilizes something called text embeddings to better understand what each chunk of text is all about and somehow find the chunk of text most related to the user's question. Now we're going to spend a decent amount of time now to focus on text embeddings and really understand what they are doing for us. So let's take a look at a couple of diagrams. A text embedding is a numerical representation of the meaning contained in some text. These text embeddings are generated by something called an embedding model. We feed text into an embedding model, such as I'm very happy today, and the embedding model is going to spit out a long list of numbers. That long list of numbers is our actual embedding. The numbers inside the embedding can range from negative one up to positive one. So now the question is, what do these numbers really mean? Each number inside of an embedding represents a score of some quality of the input text. Now, this is where things get a little confusing because I'm showing two conflicting ideas in this diagram. Let me break this down for you and just be super clear. In reality, we do not know what quality each number inside the embedding is actually tied to. So when I label that first number and say, this is a score of how happy the text is, that's not entirely accurate. We simply don't know what that first number truly represents. Nonetheless, it is very helpful to think of these numbers in that way. It is helpful to imagine that the first number might be one score of how happy the text is. And the second is how much the text is talking about fruit, etc. Again, these labels are completely made up by me, and we don't actually know what each number represents, but it's extremely helpful to think about embeddings in this way. So that's how you really want to picture each of these numbers. They are kind of like scores of some different qualities of the input text. The last thing you need to understand is how to actually generate these embeddings. Anthropic does not currently provide embedding generation. Instead, the recommended provider is Voyage AI. This is a separate company, so it requires sign up of a separate account and a different API key. However, it is free to get started and super easy to use. Attached to this lecture is a PDF that will walk you through the process of creating an account and getting an API key. Once you have generated the API key, you need to add it into your .env file right next to your existing anthropic API key. Make sure you assign it to a variable named voyage underscore API underscore key. So then right there, you'll put the generated key that you just got. Once you have updated the .emv file, I would also encourage you to download attached to this lecture, a new notebook file called 002 embeddings. At the very top, you'll find a command that you need to run in order to install the voyage AI SDK. So make sure you run that command to install the library. Lower and down inside this notebook, I've already put together a function for us called generate embedding. This function is going to take in some piece of text and then return an embedding for it. Pretty much as simple as it gets. So if I run all the cells inside of here and then run the bottom cell, which is currently opening up the report, chunking the report, and then taking the first chunk and passing into generate embedding. If I then run the cell, I'll get back my list of embeddings. As you can see, generating embeddings is rather quick and rather painless. So the real challenge here is not creating embeddings, it's understanding how they actually fit into our overall RAG pipeline. So that's going to be the next topic that we investigate.

↑ 目次へ戻る

Building with the Claude API | Lesson 42「The full RAG flow」(全文ナレッジ)

動画 7分59秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンのテーマ

ここまでのモジュールで扱った3つのトピック——RAG パイプラインの俯瞰テキストチャンキングテキスト embedding——を1本に統合し、RAG の全体フローをステップバイステップで完全に理解する。実装は次のレッスン(ノートブック)で行い、本レッスンは概念の総仕上げ。

教材上の仮定:想像上の「完璧な embedding モデル」

説明を簡単にするため、次の2つの特徴を持つ架空の embedding モデルを仮定する: 1. 常に長さ2の embedding を返す(数値2個だけ) 2. 各次元が何をスコアしているか正確に分かっている(1つ目=テキストがどれだけ医療分野の話か、2つ目=どれだけソフトウェアエンジニアリングの話か)

現実の embedding モデルではどちらも成り立たない(各次元の意味は不明)が、このシナリオでは「分かっている」ことにして進める。

ステップバイステップの完全フロー

① ソース文書をチャンクに分割する これまでどおり、ソース文書を個別のテキスト片に分割する。例では2チャンクのみ: - Section 1: Medical Research(「XDR-47 という未知の "bug"(病原体)の理解が大きく前進」) - Section 2: Software Engineering(「分散システムの infection vectors(感染経路)の研究に注力」)

② 各チャンクの embedding を生成する 想像上のモデルに各チャンクを通す。 - Section 1(医療研究)→ [0.97, 0.34]:明らかに医療の話なので医療スコア 0.97。ただし "bug" という語がソフトウェア的な含みを持ち、医療自体もソフトウェアと関わりが深いので、ソフトウェアにも 0.34 - Section 2(ソフトウェアエンジニアリング)→ [0.30, 0.97]:明らかにソフトウェアの話なので 0.97。ただし "infection vectors" が医学の含みを持つので医療スコアもやや高めの 0.3

③ 正規化(normalization) 各ベクトルの magnitude(大きさ)を 1.0 にスケーリングする数学的な追加ステップ。 - [0.97, 0.34] → [0.944, 0.331]、[0.30, 0.97] → [0.295, 0.955] - 深く理解する必要はない。大多数のケースでは利用する embedding API が勝手にやってくれる - 正規化すると全ベクトルの長さが正確に1になるため、単位円上に全ての点をプロットできる(Software Engineering は上方=ソフトウェア寄り、Medical Research は右方=医療寄り)

④ vector database に保存する embedding を vector database(長い数値リストの保存・比較・検索に最適化されたデータベース)に格納する。ここまでが事前処理(pre-processing)。この時点で一旦停止し、ユーザーがアプリにクエリを投げてくるのを待つ。

⑤ ユーザークエリを同じモデルで embedding 化 → 正規化 → 最近傍検索 ユーザーの質問(例:「会社について知りたい。特にソフトウェアエンジニアリング部門は今年何をしたのか?」)をまったく同じ embedding モデルに通す。 - ソフトウェアについて明確に聞いているのでソフトウェアスコア 0.89、「会社」の話でもあり医療とも絡むのでごくわずかに医療スコア 0.1 → [0.1, 0.89] - 再び正規化 → [0.112, 0.993] - vector database に「保存されている全ベクトルの中から、これに最も近いベクトルをください」と依頼 → 期待どおり Section 2: Software Engineering([0.295, 0.955])が返る

vector database の内部で起きていること:cosine similarity

単位円上にユーザークエリを重ねると、人間の目には「ソフトウェアエンジニアリングに近い」と一目で分かる。しかしコンピュータはチャートを見ないので、裏では計算が走っている: - cosine similarity(コサイン類似度)=ユーザークエリと、DB 内の各 embedding との間の角度の cosine - 結果は -1 〜 1 の数値。1 に近い=非常に類似(完全に同方向なら 1.0、直交なら 0.0、正反対なら -1.0) - 数式:cos(θ) = A・B / (‖A‖・‖B‖) - 例:ユーザークエリ (0.112, 0.993) と Software Engineering (0.295, 0.955) の cosine similarity は 0.983(非常に類似)。Medical Research (0.944, 0.331) とは 0.398 - 0.983 という高い類似度が「Software Engineering チャンクをプロンプトに入れるべき」というシグナルになる

cosine distance(cosine similarity と混同しない)

vector database のドキュメントでは cosine distance という用語も頻出する。これは cosine similarity とは別物で、 - cosine distance = 1 − cosine similarity - 0 に近い=類似度が大きい、値が大きいほど類似度が低い(同方向 0.0/直交 1.0/正反対 2.0) - 解釈しやすい数値にするための調整としてよく行われる。用語を見かけたらどちらの定義かを意識する

仕上げ:見つかったチャンク+質問をプロンプトへ

ユーザーの質問と高類似度のテキストチャンクが見つかったら、ユーザーの質問とそのチャンクの両方をプロンプトに入れて Claude に送信する(画面例では <user_question><report> の XML タグで区切ったプロンプトテンプレート)。これが RAG プロセスの全貌。次のレッスンでノートブックに実装する。

実務ポイント(このレッスンから持ち帰るもの)

  • 正規化は自分でやらなくてよいことが多い。大多数の embedding API が正規化済みのベクトルを返してくれる。「magnitude を 1.0 に揃える処理」とだけ理解しておけば十分。
  • cosine similarity(1 に近い=類似、範囲 -1〜1)と cosine distance(0 に近い=類似、= 1 − similarity)は vector database のドキュメントで混在する。どちらの定義で書かれているかを必ず読み分ける。
  • vector database と上手く付き合うには、背後の数学の基礎的な理解(角度の cosine を計算しているだけ)が最低限必要。ドキュメントの用語がこの数学に由来するため。
  • RAG は事前処理(チャンク化 → embedding → 正規化 → vector DB 格納)クエリ時処理(クエリ embedding → 正規化 → 最近傍検索 → プロンプト組み立て)の2フェーズに分かれる。ユーザークエリは必ずチャンクと同じ embedding モデルに通す。
  • 検索で見つけたチャンクは、ユーザーの質問と一緒にXML タグで構造化してプロンプトに入れる(例:<user_question><report>)のが Claude 流の定石。
  • 「各次元の意味が分かる長さ2の完璧な embedding モデル」は教材上のフィクション。現実の embedding は高次元で各数値の意味は不明——だが「意味の近さが角度の近さに写像される」という直感はそのまま通用する。

本文(時系列・日本語逐語+画面差し込み)

[0:00] モジュールのここまでで、RAG パイプラインがどう動くかの高レベルな概要をお伝えしました。テキストチャンキングについて少し話し、テキスト embedding についてもさわりだけ見てきました。

🖥 [0:00] 画面(スライド「Text Embeddings」・前レッスンの復習) 見出し:Text Embeddings 本文:Each number is a "score" of some quality of the input text. We don't know precisely what each number represents. 黒帯に embedding の例:[0.279, -0.05, -0.009, 0.143, -0.229, 0.411, …] 各数値に矢印付きラベル: - How "happy" the text is(1番目の数値へ) - How much the text is talking about fruit(2番目へ・下段) - How much the text is talking about oceans(3番目へ) - How "formal" the text is(4番目へ・下段) - How much the text is referring to driving(5番目へ)

[0:09] そこで今度は、この3つの異なるトピック——RAG プロセスの高レベルな概要、テキスト embedding、そしてテキストチャンキング——を取り上げて、全部を1つにマージし、RAG パイプライン全体を本当に理解していきます。つまり、完全な RAG の例を一通り、細部までたっぷり踏み込みながら進めて、すべてをステップバイステップで本当に理解していきます。では始めましょう。

[0:29] ステップ1。これまでと同じように、何らかのソース文書を取り、それを個別のテキスト片へとチャンク化します。この例では、ここに2つのテキスト片だけがあると仮定します。セクション1「医療研究(Medical Research)」と、セクション2「ソフトウェアエンジニアリング(Software Engineering)」です。

🖥 [0:29] 画面(スライド・工程バー「Chunk source text」) 上部にベージュの工程バー:Chunk source text(1工程のみ点灯)。 左の黒いボックス(ソース文書): ```

Section 1: Medical Research

This year saw significant strides in our understanding of XDR-47, a "bug" we have not seen before.

Section 2: Software Engineering

This division dedicated significant effort to studying various infection vectors in our distributed systems ``` 右のパネル「Chunks of Text」:同じ2セクションがそれぞれ独立した黒いチャンクボックスに分かれ、左の文書から赤い2本の矢印で分岐している。

[0:44] ステップ2。これらの異なるテキストチャンクのそれぞれに対して embedding を生成します。さて、この例では、想像上の、超完璧な embedding モデルを持っているというフリをします。この embedding モデルには、とても重要な特徴が2つあります。1つ目に、常に長さ2の embedding——つまり、たった2つの数値——を返すと仮定します。そして2つ目に、それぞれの数値がソース テキストの何を正確にスコアリングしているのかを、私たちが正確に知っていると仮定します。思い出してください。現実にはそうではありません。しかしこのシナリオでは、各数値が実際に何について語っているのかを正確に知っている、と想像することにします。そこで、1つ目の数値は「そのテキストがどれだけ医療分野について話しているか」、2つ目は「そのテキストがどれだけソフトウェアエンジニアリングについて話しているか」だということにします。

🖥 [0:43] 画面(スライド・工程バーに「Generate embeddings」が追加) 工程バー:Chunk source text → Generate embeddings(2工程目が点灯)。 左に「Chunks of Text」(Section 1 / Section 2 の2チャンク)→ 中央の大きなボックス「Imaginary Embedding Model」→ 右に2本の embedding: - [.97, 0.34](キャプション:Very much about medicine and mentions 'bug') - [.30, .97](キャプション:Very much about software, and 'infection vectors' kinda feels like medicine) 上部の矢印付きラベル:How much the text talks about the medical field(1次元目へ)/How much the text talks about software eng.(2次元目へ) (直後の [1:00]〜[1:30] 頃のフレームでは、講師がこのスライド上でラベルボックスや 0.34 の数値をマウス選択して指し示している=同一場面の変化)

[1:21] では最初のテキストチャンクです。これを embedding すると——これは間違いなく医療研究について話しています。なので、「はい、間違いなく。これは非常に強く医療分野について話しています」という意味を込めて、0.97 くらいのスコアを与えます。それから、このチャンクは "bug" という用語も使っていて、これにはわずかにソフトウェアエンジニアリングの含みがあります。加えて、医療というもの自体がソフトウェアエンジニアリングとかなり深く関わっています。そこで、ソフトウェアエンジニアリングには 0.34 のスコアを与えます(※ASR は「a score of three, four, four」と誤認識しているが、画面表示のとおり 0.34)。

[1:47] 次に、2つ目のテキスト片です。まあ、これは間違いなくソフトウェアエンジニアリングについて話しています。なので、そちらには 0.97 のスコアを与えます。そしてこれは "infection vectors"(感染経路)にも言及していて、これには医学の含みがあります。なので、医療スコアもやや高めの 0.3 を与えます。

[2:03] さて、これらの embedding を生成したので、ここで数学のちょっとした追加ステップを通します。正規化(normalization)と呼ばれるものです。正規化については、そこまで深く理解する必要は本当にありません。皆さんが使う embedding API が、大多数のケースではこれをすでにやってくれています。この正規化ステップは、これらのベクトルの各ペアの magnitude(大きさ)を 1.0 にスケーリングします。この用語が分からなくても、まったく問題ありません。あまり気に病まないでください。ただ、それぞれの数値の実際の magnitude に、ごく小さな調整を加えるのだ、とだけ理解してください。

🖥 [2:07] 画面(スライド「Normalization」) 右上に見出し:NormalizationScale magnitude of each pair to 1.0 左に正規化前の2本(ラベル・キャプションは前スライドと同じ)、灰色の矢印で右の正規化後へ: - [0.97, 0.34][0.944, 0.331] - [0.30, 0.97][0.295, 0.955]

[2:35] これらの embedding を生成して正規化したら、こんなふうにプロット上で可視化できます。このプロットには単位円を描いてあり、この2つの embedding を表す点は、どちらも正確に円の上に載ります。長さを正確に1に正規化したからです。ソフトウェアエンジニアリングのセクションがこの上の方に、そして医療研究がこちらにあります。

🖥 [2:39] 画面(単位円プロット) 十字の座標軸:上=About software、下=Not about software、右=About medicine、左=Not about medicine。原点を中心に薄い灰色の単位円。 原点から円周上の2点へ矢印(ベクトル)が伸び、どちらの矢印にも長さ「1.0」のラベル: - Software Engineering (0.295, 0.955)——ほぼ真上(ソフトウェア寄り) - Medical Research (0.944, 0.331)——右寄り(医療寄り)

[2:56] さて、これらの embedding が手に入ったので、次のステップに進みます。このステップでは、これらの embedding を取り、vector database と呼ばれるものの中に保存します。これは、長い数値のリスト——まさに私たちの embedding のようなもの——を保存・比較・検索するために最適化されたデータベースです。

🖥 [2:59] 画面(スライド・工程バーに「Store embeddings in vector database」が追加) 工程バー:Chunk source text → Generate embeddings → Store embeddings in vector database(3工程目が点灯)。 左に2本の embedding: - [0.944, 0.331](キャプション:'Section 1: Medical Research…') - [0.295, 0.955](キャプション:'Section 2: Software Engineering…') → 矢印 → 右のベージュの大きなボックス「Vector Database」。 その下の説明文:A database optimized for storing and comparing long lists of numbers - like embeddings

[3:13] さて、この時点で私たちは一旦停止して、休憩を取ります。というのも、ここまでは全部、事前にやっておく前処理(pre-processing)の作業だったからです。なのでこの時点では、ユーザーが実際に私たちのアプリケーションにクエリを送ってくるのを、ただ座って待つような形になります。そこで、いつかの時点でついにユーザーがアプリにやってきて、チャットボットか何かに、質問なりクエリなりをタイプする、と想像しましょう。

[3:35] この場合、その質問は例えばこんな感じになるでしょう。「会社について知りたいのですが。特に、ソフトウェアエンジニアリング部門は今年何をしましたか?」

[3:42] さて、この時点で私たちはそのユーザーの質問を取り、まったく同じ想像上の embedding モデルに通します。このシナリオでは、ユーザーの質問はソフトウェアエンジニアリングについて特に尋ねているので、0.89 のスコアを与えます。それから、会社についても話していて、しかも繰り返しになりますが、ソフトウェアエンジニアリングは医療分野とある程度結びついているので、ごくわずかな医療スコアとして 0.1 も与えます。

🖥 [3:27] 画面(スライド・工程バーに「Embed user query, find closest embeddings」が追加) 工程バー:4工程目「Embed user query, find closest embeddings」(黒)が点灯。 左のボックス「User's Question」:I'm curious about the company. In particular, what did the software engineering dept do this year? → 中央「Imaginary Embedding Model」→ 右の黒帯:[0.1, 0.89] 上部ラベル:How much the text talks about the medical field(1次元目へ)/How much the text talks about software eng.(2次元目へ) キャプション:'Company' kind of medicine related, but focused on software

[4:07] この embedding が得られたので、再びあの正規化ステップを通します。

🖥 [4:10] 画面(スライド・ユーザークエリの正規化) 見出し:NormalizationScale magnitude of each pair to 1.0 [0.1, 0.89](キャプション:'Company' kind of medicine related, but focused on software)→ 灰色矢印 → [0.112, 0.993]

[4:13] そしていよいよ、vector database を活用します。ユーザーのクエリを取り、それを vector database に投入して、こう言います。「あなたの中に保存してある全ベクトルを検索して、これに性質が最も近いベクトルをください」と。

🖥 [4:14] 画面(スライド・vector database への最近傍問い合わせ) 左上の黒帯:[0.1, 0.89](キャプション:User query)→ 右向き矢印(注記:Please give us the vector that is "closest" to this one)→ 右のベージュのボックス「Vector Database」。 Vector Database から左向きの戻り矢印 → 左下の黒帯:[0.295, 0.955](キャプション:'Section 2: Software Engineering…'

[4:29] この場合、私はセクション2のソフトウェアエンジニアリングが返ってくることをまあ期待します。ユーザーがこちらで尋ねたのは、だいたいそれについてですから。

[4:37] しかし、この非常に近い関連結果を私たちに返すことができる vector database の内部で、正確には何が起きているのかをお話ししましょう。

🖥 [4:34-4:38] 画面(スライドの高速往復・変化クラスタ) 講師がここまでの3枚——ユーザークエリの embedding 化 → ユーザークエリの正規化 → vector database への最近傍問い合わせ——を素早く行き来して振り返っている(内容は上の各 🖥 ブロックと同一。新規要素なし)。

[4:45] オーケー、ここで少しだけ数学です。心配いりません、大した量にはなりませんから。

[4:49] ユーザーのクエリを取ってこのチャートに追加すると、見た目からすぐに、ユーザーのクエリがソフトウェアエンジニアリングに本当に近いことが分かります。私たちは人間なので、このチャートを見て「ああ、確かに、この2つは明らかにとても近い。ユーザーのクエリはソフトウェアエンジニアリングに非常に類似している」と言えるわけです。ですから当然、ユーザークエリに関連するチャンクを vector database の中から見つけたいなら、これこそが私たちの欲しいものだ、ということになります。

🖥 [4:45] 画面(単位円プロットにユーザークエリを追加) 先ほどの単位円プロットに3本目のベクトルが追加された状態: - User query (0.112, 0.993)——ほぼ真上 - Software Engineering (0.295, 0.955)——そのすぐ右隣(2点はほぼ密着) - Medical Research (0.944, 0.331)——右寄りで離れている

[5:13] しかしもちろん、私たちはここでコンピュータを使っています。そして私たちのコンピュータは、実際にこんなチャートを作ってそれを眺めたりはしません。舞台裏では、実際の計算が行われています。では、その計算が正確には何なのかを調べてみましょう。そしてこれは知っておくことがそれなりに重要です。というのも、やがて皆さんが vector database を使い始めると、この舞台裏で行われている類いの数学に関連した用語が大量に使われるからです。そして vector database と上手くやり取りするには、この数学について少なくともごく基礎的な理解を持っておく必要が、まあ、あるのです。だからこそ理解しておいてほしいのです。

[5:41] よし。これが、皆さんの vector database の内部で行われている数学の高レベルな見取り図です。どの embedding がユーザーのクエリに最も類似しているかを見つけるために、cosine similarity(コサイン類似度)と呼ばれるものを計算します。これは、ユーザーのクエリと、データベースに保存されている他の各 embedding との間の、角度の cosine です。つまり、ちょうどここにある角度 a を見つけてその cosine を取り、そしてここにある角度 b を見つけてその cosine を取りたいわけです。このための数式は右側に示してあります。

🖥 [5:30] 画面(スライド「Cosine Similarity」) 左:第1象限の四分円プロット。原点から3本のベクトル——User query (0.112, 0.993)Software Engineering (0.295, 0.955)Medical Research (0.944, 0.331)。User query と Software Engineering の間の角に「a」、User query と Medical Research の間の角に「b」のラベル。 右:見出し Cosine Similarity と箇条書き: - Cosine of the angle between the two vectors - If they're in the exact same direction we'd get 1.0 - If perpendicular, 0.0 - If completely opposite, -1.0

数式(2本): ``` cos(a) = A・B / (‖A‖・‖B‖) = (0.112, 0.993)・(0.295, 0.955) / (‖(0.112, 0.993)‖・‖(0.295, 0.955)‖) = 0.983

cos(b) = A・B / (‖A‖・‖B‖) = (0.112, 0.993)・(0.944, 0.331) / (‖(0.112, 0.993)‖・‖(0.944, 0.331)‖) = 0.398 ```

[6:11] この計算の結果は、-1 と 1 の間の数値になります。ちょうどここで得られたように 1 に近い結果が得られたなら、それはユーザーのクエリに非常に類似した embedding を見つけた、ということを意味します。-1 に近い結果は、ユーザーのクエリとまったく類似していない embedding を見つけた、ということを意味します。

[6:28] 私たちのケースでは、ユーザークエリとソフトウェアエンジニアリングのチャンクとの間の cosine similarity は 0.983 で、この2つの embedding が非常に類似していることを意味します。つまりこれは、ソフトウェアエンジニアリングのテキストチャンクを取って、ユーザーの質問と一緒にプロンプトに含めたい、という私たちへのサインなのです。

[6:44] さて、先に進む前に、もう1つだけ手短に。これは今は少し紛らわしく感じるでしょうが、後で vector database を使い始めた時に知っておくと非常に、非常に役立つことです。多くの vector database のドキュメントで、cosine distance(コサイン距離)と呼ばれるものを目にすることになります。これは cosine similarity とは別物です。これは「1 引く cosine similarity」として計算されます。

🖥 [6:55] 画面(スライド「Cosine Similarity」+「Cosine Distance」) 前のスライドの下部に新しいセクションが追加された状態。左の四分円プロットと cos(a)=0.983/cos(b)=0.398 の数式はそのまま。 追加見出し:Cosine Distance と箇条書き: - Calculated as (1 - Similarity) - Same direction, 0.0 - If perpendicular, 1.0 - If completely opposite, 2.0

[7:05] この調整は、より解釈しやすい数値を得るためだけに行われることが多いです。cosine distance では、0 に近い値は類似度が大きいことを意味し、それより大きな値は類似度がより小さいことを意味します。繰り返しますが、これは vector database のドキュメントで非常によく目にするものです。ですから、cosine distance と cosine similarity という用語を見かけたときには、このことを意識しておいてください。

[7:31] さて、この数学をごく高いレベルで理解できたので、本筋に戻りましょう。ユーザーの質問と高い類似度を持つテキストチャンクを見つけたら、ユーザーの質問を取ってプロンプトに追加し、さらに、最も関連が深いとして見つけたそのテキストチャンクも、同じくプロンプトに入れます。それからそのプロンプトを Claude に送信します。

🖥 [7:30] 画面(スライド・工程バーに「Add related chunks to the prompt」が追加・最終工程) 工程バー:Chunk source text → Generate embeddings → Store embeddings in vector database → Embed user query, find closest embeddings → Add related chunks to the prompt(5工程目が点灯。直前の [7:29] 頃に前スライドからのクロスフェードの遷移フレームあり)。 左上のボックス「User's Question」:How many bugs did engineers fix this year? 左下の黒いチャンクボックス: ```

Section 2: Software Engineering

This division dedicated significant effort to studying various infection vectors in our distributed systems 両方から右向き矢印 → 右の大きな黒いプロンプトボックス: Answer the user's question about the financial document.

{user_question}

`` (ユーザーの質問がに、見つかったチャンクが` に差し込まれる構図)

[7:46] そしてこれが、プロセス全体の実に実に詳細な姿です。これで、始まりから終わりまで、舞台裏で動いているテクノロジーの類いも、そして数学の一部さえも含めて、すべてを理解できました。では、この直後に、これをノートブックの中に実装し始めましょう。

英語逐語(ASR全文)

At this point in the module, I've given you a high-level overview of how the RAG pipeline works. We've spoken a little bit about text chunking, and we've got just a taste of text embeddings. So now we're going to take these three different topics, our high-level overview of the RAG process, text embeddings, and text chunking, and we're going to merge them all together and really understand the entire RAG pipeline. So we're going to go through a complete RAG example and go through a lot of detail and really understand everything step-by-step. So let's get to it. Step number one, just as before, we're going to take some source document and chunk it into separate pieces of text. So for this example, I'm going to assume I just have two pieces of text here, just section one, medical research, and section two, software engineering. Step two, we're going to generate embeddings for each of these different chunks of text. Now in this example, we're going to pretend that we have this imaginary, super perfect embedding model. And this embedding model has two very important characteristics. First, we're going to assume that it always returns embeddings of length two, so just two separate numbers. And we're going to also assume that we know exactly what each number is really scoring about the source text. Remember, in reality, that is not the case. But in this scenario, we're going to imagine we know exactly what each number is really talking about. So we're going to say that the first number is how much the text is talking about the medical field. And the second is how much the text is talking about software engineering. So for the first chunk of text, when we embed it, this thing is definitely talking about medical research. So I would give it maybe a score of 0.97 to say, yes, absolutely. This is very much talking about the medical field. And then it also uses the term bug, which has a slight software engineering connotation. In addition, medical itself is pretty heavy on software engineering. So I'm going to give it a score of three, four, four software engineering. Then for the second piece of text here, well, it's definitely talking about software engineering. So I'm going to give it a score of 0.97 for that. And then it also mentions infection vectors, which has that connotation of medicine. So I'll give it a slightly higher medicine score as well of 0.3. Now that we have generated these embeddings, we're going to go through an extra little step of mathematics here, something referred to as normalization. Now you do not really have to understand normalization that much. This is already going to be done for you in the vast majority of cases by the embedding API that you are using. This normalization step is going to scale the magnitude of each of these pairs of vectors to 1.0. And if you don't understand that terminology, totally fine. Don't sweat it too much. Just understand that we're going to do a slight little adjustment to the actual magnitude of each number. Once we have generated these embeddings and normalized them, we can kind of visualize them on a plot like this. So on this plot, I've drawn a unit circle, and each of our points representing both those embeddings will lie exactly on the circle because we have normalized their lengths to exactly 1. So we've got the software engineering section up here, and here's medical research over here. So now that we have these embeddings, we're going to move on to the next step. In this step, we are going to take these embeddings and store them inside of something called a vector database. This is a database that has been optimized for storing, comparing, and looking up long lists of numbers, exactly like what our embeddings are. Now at this point in time, we kind of pause, we take a break, because this has all been pre-processing work that we did ahead of time. So at this point, we kind of just sit around and wait for a user to actually submit a query to our application. So we will imagine that at some point in time, finally, a user will come to our app and maybe type into a chatbot or something like that, either a question or their query. Maybe in this case, their question is going to be something like, I'm curious about the company in particular. What did the software engineering department do this year? Now, at this point in time, we're going to take that user's question and we're going to run it through the exact same imaginary embedding model. In this scenario, because the user's question is asking specifically about software engineering, I'll give it a score of 0.89. And then because it's also talking about company, and again, software engineering is kind of tied up in the medical field, I'll give it a very slight medical score as well of 0.1. Now that we have this embedding, we're going to go and go through that normalization step again. And then finally, we're going to make use of our vector database. We're going to take the user's query. We're going to feed it into the vector database and say, please search through all the vectors we have stored inside of you and give us the vector that is closest in nature to this one. So in our case, I would kind of expect to get back section two software engineering because that's kind of what the user asked about over here. But let me tell you exactly what is happening inside of the vector database that is able to give us this very closely related result. Okay, so a little bit of math here. Don't worry, it won't be too much. So when we take the user's query and add it onto this chart, we can see right away that visually the user's query is just really close to software engineering. So you and I as humans, we could look at this chart and say, oh yeah, clearly these two things are very close. The user's query is very similar to software engineering. So obviously, if we want to find some chunks inside the vector database related to the user query, this would be the one that we want. But of course we are using computers here and our computer doesn't actually just make a chart like this and then look at it. There's some actual calculation going on behind the scenes. So let's examine exactly what that calculation is. And it's kind of important for you to know it because eventually when you start using vector databases, they're going to use a lot of terminology that's related to this kind of math going on behind the scenes. And to actually interface well with the vector database, you kind of need to have at least a very basic understanding of the math. So that's why I want you to understand it. All right, here's a high level look at the math that is being done inside of your vector database. To find which embeddings are most similar to the user's query, we want to calculate something called the cosine similarity. This is the cosine of the angle between the user's query and each of the other embeddings stored in the database. So we'd want to find the angle A right here and take the cosine of it, and angle B right here, and take the cosine of it. The math for this is shown on the right hand side. The result of this calculation will be a number between negative one and one. If we get a result close to one, as we did right here, then that means that we have found an embedding very similar to the user's query. Results closer to negative one mean we have found an embedding that are not at all similar to the user's query. In our case, the cosine similarity between our user query and the software engineering chunk is 0.983, meaning that these two embeddings are very similar. So this is a sign to us that we would want to take the software engineering chunk of text and include it in our prompt with the user's question. Now, before we move on, one other quick thing that's going to be a little confusing right now, but it's going to be very, very helpful to know later on when you start working with vector databases. In a lot of vector database documentation, you're going to see something referred to as a cosine distance. This is different than the cosine similarity. It is calculated as one minus the cosine similarity. as adjustment is often done just to give us an easier to interpret number with a cosine distance values close to zero mean you have a large similarity and larger values than that mean we have less similarity again this is going something you're going to see very often in vector database documentation so just be aware of it whenever you see the term cosine distance and cosine similarity so now that we understand some of this math at a very high level let's get back on track. Once we have found a text chunk with a high similarity to the user's question, we're going to take the user's question, add it into our prompt and the text chunk that we found that's most relevant and put that into our prompt as well. We then take that prompt and send it off to Claude. And that's the entire process in great, great detail. So now that we understand everything from start to finish with all the kind of tech behind the scenes going on, and even some of the math, let's start to implement this inside of a notebook in just a moment.

↑ 目次へ戻る

Building with the Claude API | Lesson 43「Implementing the RAG flow」(全文ナレッジ)

動画 5分11秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンのテーマ

前レッスンで整理した RAG のフルフロー(5ステップ) を、ノートブック 003_vectordb.ipynb の中で実際にコードとして実装し、end-to-end で動かす。

VectorIndex クラス(サンプル実装の vector database)

  • ノートブック冒頭に、講師が用意した vector database のサンプル実装 class VectorIndex が置いてある(in-memory 実装)。
  • __init__(distance_metric="cosine", embedding_fn=None) で、self.vectors(ベクトルのリスト)と self.documents(メタデータ辞書のリスト)を持つ。distance_metric"cosine""euclidean" のみ許可(それ以外は ValueError)。
  • 中身を細かく読まなくてもレッスンは進められる(「見たければどうぞ、必要なことは全部説明する」というスタンス)。使う API は add_vector()search() の2つ。

5ステップ実装(RAG フロー本体)

  1. チャンク化report.md を読み込み(with open("./report.md", "r") as f: text = f.read() は実行済み)、chunks = chunk_by_section(text) でセクション単位に分割。動作確認として chunks[2](=目次)、chunks[3](=Methodology)、chunks[4](=Section 1: Medical Research)を順に出力して、期待どおりに分割されていることを確かめる。
  2. 各チャンクの embedding 生成generate_embedding単一文字列でも文字列リストでも受け取れるように書き換え済み(リストを渡すと各要素の embedding をリストで返す)。embeddings = generate_embedding(chunks) の1行で全チャンクを一括 embedding 化。
  3. vector store への格納store = VectorIndex() でインスタンス生成 → for embedding, chunk in zip(embeddings, chunks): でペアを回し、store.add_vector(embedding, {"content": chunk}) で挿入。第2引数にチャンク本文入りの辞書を渡すのがポイント(理由は下記)。
  4. ユーザー質問の embedding 化user_embedding = generate_embedding("What did the software engineering dept do last year?")
  5. 類似検索results = store.search(user_embedding, 2)上位2件(1件だけではなく)を取得 → for doc, distance in results: で distance と doc["content"][0:200](チャンクが大きいので先頭200文字だけ)を出力。

なぜ embedding と一緒に元テキストを保存するのか(ステップ3の補足)

  • 検索時に vector database から返ってくるのは「入力に関連する embedding のリスト」だが、embedding の数値そのものは開発者にとってほぼ意味を持たない。本当に欲しいのは「その embedding に紐づいたテキスト」。
  • そのため vector database に embedding を保存する時は、生成元チャンクのテキスト、または少なくともチャンク ID(元テキストへ辿り直せる何か)を必ず一緒に格納するのが通例。
  • 今回はチャンク本文そのものを {"content": chunk} として格納し、検索で最類似チャンクが返ってきた時点で目的のテキストが手元にある状態にした。

検索結果の読み方

  • 最良ヒットは Section 2(Software Engineering)で cosine distance 0.71、次点は Methodology セクションで 0.72
  • cosine distance は小さいほど近い。「ソフトウェアエンジニアリング部門は昨年何をしたか」という質問に対して Section 2 が最上位に来たのは期待どおり。ただし次点との差はわずか(0.71 vs 0.72)。

残る課題

  • これで RAG ワークフロー全体は動くが、期待どおりに動かないシナリオが1〜2ある。ワークフローに加えられる改善がまだ残っており、次のレッスンで議論する。

実務ポイント(このレッスンから持ち帰るもの)

  • embedding 単体を保存しない。vector database には必ず「元チャンクのテキスト(または最低でもチャンク ID)」をメタデータとして一緒に格納する。検索結果が数値だけでは何の役にも立たない。
  • 検索は top-k で複数件取る(今回は store.search(user_embedding, 2) で2件)。最良1件だけに絞ると、僅差の有用チャンク(今回の 0.71 vs 0.72 のような)を取りこぼす。
  • cosine distance は小さいほど類似(1 − similarity。同方向 0.0/直交 1.0/正反対 2.0)。スコアは僅差になりがちなので、絶対値よりも順位と内容の突き合わせで判断する。
  • embedding 関数は文字列/リスト両対応にしておくと、全チャンクの一括 embedding 化が1行で書けて、ループが store への挿入だけに集中できる。
  • 動作確認は各ステップごとに小さく行う(chunks[2]chunks[3]chunks[4] を覗く、出力は先頭200文字に切る等)。チャンクは大きいので全文 print しない。
  • RAG の最小実装は「chunk → embed → store → embed query → search」の5ステップで完結する。VectorIndex 程度の in-memory 実装でもフロー全体の学習・検証には十分。

本文(時系列・日本語逐語+画面差し込み)

[0:00] RAG フローの全体を理解したところで、003 vector DB という別のノートブックの中で、実例を一通りたどっていきます。このノートブックには、vector database のサンプル実装を用意しておきました。ここにある class VectorIndex がそれです。見たければ自由に目を通してもらって構いませんが、これについて理解しておくべきことは、すべて私が順に説明します。

🖥 [0:00] 画面(Jupyter ノートブック 003_vectordb.ipynb VS Code + Jupyter 拡張。上部に実行済み(チェック付き)の折りたたみ見出しセル # Client Setup# Chunk by section# Embedding Generation が並び、その下に # VectorIndex implementation のセルが開いている: ```python

VectorIndex implementation

import math from typing import Callable, Optional, Any, List, Dict, Tuple

class VectorIndex: def init( self, distance_metric: str = "cosine", embedding_fn: Optional[Callable[[str], List[float]]] = None, ): self.vectors: List[List[float]] = [] self.documents: List[Dict[str, Any]] = [] self._vector_dim: Optional[int] = None if distance_metric not in ["cosine", "euclidean"]: raise ValueError("distance_metric must be 'cosine' or 'euclidean'") self._distance_metric = distance_metric ``` (クラス定義はさらに下へ続く。ステータスバーに Cell 4 of 10)

[0:22] さて、このノートブックでは、5つのステップを実装することで、RAG フロー全体を一通りたどっていきます。前のビデオで話したのとまったく同じ5つのステップです。では始めましょう。

[0:31] ステップ1。ファイルはすでに開いて、そこからテキストを読み込んであります。具体的には report.md ファイルで、このノートブックと同じディレクトリにあるはずです。ステップ1では、このテキストをセクションごとにチャンク化します。そのための補助関数はすでに追加してあります。以前使ったのと同じ chunk_by_section 関数です。

🖥 [0:31] 画面(ノートブック全体構成:5ステップの見出しセル) # VectorIndex implementation も実行済み(チェック付き)で折りたたまれ、その下に: python with open("./report.md", "r") as f: text = f.read() (実行済み・0.0s)。さらにその下に、これから埋めていく5つのステップのコメントだけが入ったセルが並ぶ: ```python

1. Chunk the text by section

python

2. Generate embeddings for each chunk

python

3. Create a vector store and add each embedding to it

python

4. Some time later, a user will ask a question. Generate an embedding for it

python

5. Search the store with the embedding, find the 2 most relevant chunks

```

[0:50] それでは、チャンク化処理を行うために、chunks = chunk_by_section として、先ほどのテキスト全体を渡します。

[0:57] そして動作確認として、すべて正しく動いているかを確かめるために、chunks の……そうですね、2番あたりを出力してみます。すると目次(table of contents)の出力が見えるはずです。そして3にすれば、その1つ下のセクションが見えるはずで、さらにその次のセクション、という具合に続いていきます。

🖥 [1:07] 画面(chunks[2] の実行結果=目次) ```python

1. Chunk the text by section

chunks = chunk_by_section(text)

chunks[2] 出力: 'Table of Contents\n\n1. Executive Summary\n2. Table of Contents\n3. Methodology\n4. Section 1: Medical Research — Understanding XDR-471 Syndrome\n5. Section 2: Software Engineering — Project Phoenix Stability Enhancements\n6. Section 3: Financial Analysis — Q3 Performance and Outlook\n7. Section 4: Scientific Experimentation — Characterization of Material Composite XT-5\n8. Section 5: Legal Developments — Navigating IP Precedents and Regulatory Shifts\n9. Section 6: Product Engineering — Finalizing Model Zircon-5 Specifications\n10. Section 7: Historical Research — Re-evaluating the Galveston Accords (1921)\n11. Section 8: Project Management — Progress on Project Cerberus Phase 2B\n12. Section 9: Pharmaceutical Development — Compound CTX-204b Phase IIa Update\n13. Section 10: Cybersecurity Analysis — Incident Response Report: INC-2023-Q4-011\n14. Future Directions\n' ```

🖥 [1:12] 画面(chunks[3] に書き換えて再実行) セルの最終行を chunks[3] に変更して実行。出力: 'Methodology\n\nThe insights compiled within this Annual Interdisciplinary Research Review represent a synthesis of findings drawn from standard departmental reporting cycles, specialized project updates, and cross-functional review meetings conducted throughout the year. Data sources included internal project databases, laboratory notebooks, financial reporting systems, legal case summaries, security incident logs, and minutes from dedicated working groups. A central review committee, comprising representatives nominated by each division head, was tasked with identifying key developments and potential cross-domain implications. This committee utilized a standardized reporting template to capture essential details, including unique identifiers (project codes, error numbers, case references, etc.) and progress metrics. Subsequent analysis focused on identifying thematic overlaps, shared challenges, and opportunities for synergistic development, forming the basis of this consolidated report. The ambiguous references employed reflect the internal context and assume reader familiarity with ongoing initiatives and personnel.\n'

🖥 [1:15] 画面(chunks[4] → Section 1、その後テスト行を削除) 最終行を chunks[4] にして実行。出力: 'Section 1: Medical Research — Understanding XDR-471 Syndrome\n\nThis year saw significant strides in our understanding of XDR-471 syndrome, a rare neurodegenerative condition previously hampered by diagnostic ambiguity. The team focused on correlating clinical presentations with specific genetic markers, particularly variations within the Gene LOC73b region. Analysis of patient cohort data (Cohort ID: XDR-EU-03) revealed a statistically significant link between symptom severity and marker expression levels, measured via quantitative PCR assays. Preliminary work on a novel diagnostic biomarker panel (Panel ID: XDR-BioMk-v2) shows promise, achieving >85% sensitivity in early validation sets. However, specificity remains a challenge requiring further refinement. Ongoing efforts under Trial ID: XDR-TR002 are exploring targeted therapeutic interventions based on these findings. These results provide a much-needed foundation for future clinical strategies, though the resource implications highlighted in Section 3 (Financial Analysis) may impact the pace of subsequent trial phases. The team continues to refine diagnostic protocols based on this evolving understanding.\n' 確認が済むとテスト行 chunks[4] は削除され、セルは chunks = chunk_by_section(text) の1行(+コメント)に戻る。

[1:19] 続いてステップ2へ。ステップ2では、各チャンクに対して embedding を作成します。お伝えしておくと、embedding 関数は少しだけ書き直してあり、今は単一の文字列でも、文字列のリストでも渡せるようになっています。そして文字列のリストを渡した場合は、その各要素について embedding を作成し、それを embedding のリストとして返します。

[1:34] というわけで、ステップ2を実装するには、generate_embedding を呼び出して全チャンクを渡し、その結果を embeddings に代入します。

🖥 [1:34] 画面(ステップ2のセル) ```python

2. Generate embeddings for each chunk

embeddings = generate_embedding(chunks) ```

[1:46] 次のステップ3では、vector store のインスタンスを作成します。store を作成したら、チャンクと embedding のすべてのペアをループで回します。zip でまとめて組にするのです。

[2:01] そして各ペアを取り出すたびに、それらを store に挿入していきます。for embedding, chunk in zip(embeddings, chunks) という形で書きます。そして繰り返しになりますが、embedding とチャンクのそれぞれのペアについて store.add_vector を実行し、embedding を入れます。そして第2引数として、content にチャンクを持たせた辞書を渡します。

🖥 [2:00] 画面(ステップ3のセル・入力途中) ```python

3. Create a vector store and add each embedding to it

store = VectorIndex()

for embedding, chunk in zip(embeddings, chunks): store.add_vector(embedding, ) ``` (この時点では第2引数はまだ未記入で、カーソルが括弧内にある)

[2:21] さて、このステップはかなり駆け足で進めてしまいました。そこで少し脇道に逸れて、なぜこれらすべてをループで回すのか、なぜチャンク化するのか、そしてなぜ content にチャンクを入れたこの追加の辞書を渡すのかを説明しましょう。

[2:31] 先ほど話したとおり、最終的にはある時点で、私たちは vector database に問い合わせて、入力に関連するさまざまな embedding のリストを受け取ることになります。

🖥 [2:30] 画面(スライド「Embed user query, find closest embeddings」) 上部にフロー図:Chunk source text → Generate embeddings → Store embeddings in vector database → Embed user query, find closest embeddings(現在地・黒地でハイライト)。 本体:[0.1, 0.89](User query)から Vector Database へ矢印が伸び、注記は「Please give us the vector that is "closest" to this one」。Vector Database からは [0.295, 0.955] が矢印で返ってくる。

🖥 [2:32] 画面(スライド「Add related chunks to the prompt」・デッキ移動中に一瞬表示) フロー図の次段「Add related chunks to the prompt」まで進んだスライド。左に User's Question「How many bugs did engineers fix this year?」と、チャンク: ```

Section 2: Software Engineering

This division dedicated significant effort to studying various infection vectors in our distributed systems 右のプロンプトへ両方が矢印で差し込まれる: Answer the user's question about the financial document.

{user_question}

```

🖥 [2:34] 画面(スライド「Cosine Similarity」・復習用に前へ戻る) 左の座標図:User query (0.112, 0.993)/Software Engineering (0.295, 0.955)/Medical Research (0.944, 0.331)、角度 a・b。 右の箇条書き:Cosine of the angle between the two vectors/If they're in the exact same direction we'd get 1.0/If perpendicular, 0.0/If completely opposite, -1.0。 数式:cos(a) = A·B / (||A||·||B||) = (0.112, 0.993)·(0.295, 0.955) / (||(0.112, 0.993)||·||(0.295, 0.955)||) = 0.983、cos(b) = … = 0.398。 下段「Cosine Distance」:Calculated as (1 - Similarity)/Same direction, 0.0/If perpendicular, 1.0/If completely opposite, 2.0。

[2:39] さて、ここでこのリストが返ってきた時、数値そのものだけ、つまり embedding だけを受け取っても、私たちには実のところあまり役に立ちません。embedding は、開発者である皆さんや私にとって、それ自体では大した意味を持たないからです。私たちが本当に関心があるのは、その embedding に紐づいたテキストのほうです。

🖥 [2:36] 画面(スライド「Embed user query, find closest embeddings」・キャプション付きの最終状態) 先ほどと同じ Vector Database の図に戻る。返ってきたベクトル [0.295, 0.955] の下に、対応するチャンクテキストのキャプション「'Section 2: Software Engineering…'」が表示されている=「DB は数値だけでなく元テキストも返せるように格納しておく」ことの図解。

[2:54] ですから通常、これらの embedding を自分の vector database に保存する時は、その embedding の生成元になったチャンクのテキストか、少なくともチャンクの ID、つまり最低限でも元のチャンクテキストまで辿り直せる何かしらを、一緒に含めておくことになります。そこで今回のケースでは、各 embedding と一緒に元のチャンクテキストを含めておきます。こうしておけば、後で検索(lookup)を行って最も類似したチャンクが返ってきた時に、探している実際のテキストがちゃんと手元にあるわけです。

🖥 [3:00] 画面(ステップ3のセル・完成形) ```python

3. Create a vector store and add each embedding to it

store = VectorIndex()

for embedding, chunk in zip(embeddings, chunks): store.add_vector(embedding, {"content": chunk}) ``` (実行済み・0.0s)

[3:19] さて、ステップ4へ進みます。ステップ4では、将来のある時点で、ユーザーが私たちに質問をしてきます。

[3:26] その質問を受け取って、それに対する embedding を生成する必要があります。そこで generate_embedding を呼び出して user_embedding を作ります。そしてここでの私の質問は、「ソフトウェアエンジニアリング部門は昨年何をしましたか?」にします。

🖥 [3:30] 画面(ステップ4のセル) ```python

4. Some time later, a user will ask a question. Generate an embedding for it

user_embedding = generate_embedding("What did the software engineering dept do last year?") `` (実行済み。直下の# 5. Search the store with the embedding, find the 2 most relevant chunks` セルが選択されている)

[3:39] 最後にステップ5です。ここでは関連するドキュメントをいくつか見つけにいきます。

[3:46] つまり、この embedding で store を検索して、最も関連の高いチャンクを、1件だけではなく2件見つけたいのです。この質問に対して最も関連が高そうな2つのチャンクを取得したい、ということです。そのために、results = store.search とします。user_embedding を渡します。

🖥 [4:00] 画面(ステップ5のセル・入力途中) ```python

5. Search the store with the embedding, find the 2 most relevant chunks

results = store.search(user_embedding, 2) ``` (出力用の for ループはまだ書かれていない)

[4:04] そしてここにもう1つの引数として 2 を渡します。最も関連の高いチャンクを2件見つけたいからです。それから、for doc, distance in results として出力していきます。

[4:21] distance を出力し、改行し、それからドキュメントの content を出力します。そして、ここの各チャンクは本当に、本当に大きいので、先頭の200文字だけを出力することにします。そして、こんなふうにもう1つ改行を入れます。

🖥 [4:30] 画面(ステップ5のセル・完成形と実行結果) ```python

5. Search the store with the embedding, find the 2 most relevant chunks

results = store.search(user_embedding, 2)

for doc, distance in results: print(distance, "\n", doc["content"][0:200], "\n") 出力: 0.710883970504518 Section 2: Software Engineering — Project Phoenix Stability Enhancements

The Software Engineering division dedicated considerable effort to improving the stability and performance of the core systems

0.7210594524998164 Methodology

The insights compiled within this Annual Interdisciplinary Research Review represent a synthesis of findings drawn from standard departmental reporting cycles, specialized project updates ```

[4:36] これを実行すると、はい、結果が出ました。最良の結果として Section 2 が返ってきています。cosine distance もここに表示されていますね。0.71 です。そして、その次に近いチャンクは 0.72 で、こちらは methodology のセクションでした。

[4:52] つまりこの2つが、先ほど投げたユーザークエリに対して最も関連が高いと判定されたチャンクだったわけです。よし。これで、私たちの RAG ワークフローの全体ができました。

[5:00] さて、これはすべて動くのですが、すべてが期待どおりには動かないシナリオが1つか2つ存在します。つまり、このワークフローにはまだいくつか追加できる改善が残っています。それらについて、この後すぐに議論を始めていきましょう。

英語逐語(ASR全文)

Now that we understand the entire RAG flow, we're going to walk through an example inside of another notebook called 003 vector DB. So in this notebook, I provided a sample implementation of a vector database, and I've called it class vector index right here. If you want to feel free to take a glance at it, but I'll walk through everything you need to understand about it. Now in this notebook, we're going to walk through the entire RAG flow by implementing five different steps. The same five steps we just spoke about in the last video. So let's get to it. Step number one, I have already opened up the file and read the text from it, specifically our report.md file, which should be in the same directory as this notebook. So in step one, we're going to chunk the text by section. I've already added in a function to help with that. So the same chunk by section function we had previously. So to do our chunking process, I'll say chunks is chunk by section and pass in all that text. and now just to test things out and make sure I've got everything working correctly I'll try printing out chunks at how about two and I should see a printout of the table of contents and then if I go to three I should see the next section down and then the next section and so on then on to step two in step two we're going to create an embedding for each chunk now just so know, I rewrote the embedding function slightly so that now we can pass in either a single string or a list of strings. And if we pass in a list of strings, it will create an embedding for each of them and then return that as a list of embeddings. So to implement step number two, we will call generate embedding and pass in all the chunks and then assign the result to embeddings. Next up in step three, we are going to create an instance of the vector store. Once the store has been created, we will then loop over all the different pairs of chunks and embeddings. We're going to zip them together. And then as we take each pair, we're going to insert them into the store. We'll do that with a for embedding and chunk in zip embeddings and chunks. And again, for each of those different pairs of embeddings and chunks, we'll do a store add vector, put the embedding in. And then as the second argument, we'll put in a dictionary with content of chunk. Now I went over this step rather quickly. So let's do a quick aside and explain why we are looping over all these things, why we are chunking it, and why we are adding in this extra dictionary with the content of chunk. As we just discussed, eventually at some point in time, we're going to reach out to our vector database and get back a list of all the different related embeddings to the input. Now, when we get back this list right here, just getting the number by itself, just getting the embedding is not really useful to us because the embedding, it doesn't really have a lot of meaning to you and I as developers. What we really care about is the text associated with that embedding. So usually whenever you store these different embeddings inside of your vector database, you're also going to include either the text from the chunk that the embedding was generated from, or at least the ID of the chunk, something to at least point you back to the original chunk text. So in this case, I'm going to include the original chunk text along with each embedding again, just so when we do the lookup later on and I get back the most similar chunks, I've got the actual text that I'm looking for now onto step four. So in step four, at some point in time in the future, a user is going to ask us a question. We need to take that question and generate an embedding for it. So I'll make a user embedding by calling generate embedding. And then my question here is going to be, what did the software engineering department due last year. Finally, onto step five, where we are going to try to find some relevant documents. So I want to search the store with the embedding, and I want to find the two most relevant chunks, not just the most relevant. I want to get the two chunks that seem to be most relevant to this question right here. So for that, I'll do a results store.search. I'm going to pass in the user embedding. And I'm going to pass in another argument here of two, because I want to find the two most relevant chunks. And then I will print out for doc and distance in results. I'm going to print out the distance, a new line, and then the documents content. And because each chunk here is really, really large, I'm going to print out just the first 200 characters. and then another new line like so. And I'll run this and there's our result. So we get back section two as our best result. We also see the cosine distance here. So it's 0.71. And the next closest chunk is at 0.72. And that was the methodology section. So these were the two chunks that were found most relevant for the user query that we just submitted. All right. So that is our entire RAG workflow. Now, all this works, but there is one or two scenarios where everything doesn't quite work as expected. So there are still a couple of improvements we could add into our workflow, and let's start to discuss those in just a moment.

↑ 目次へ戻る

Building with the Claude API | Lesson 44「BM25 lexical search」(全文ナレッジ)

動画 9分59秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

  • 前レッスンで組んだ semantic search(embeddings + vector DB)だけの RAG 検索には corner case があるreport.md 内のインシデント ID INC-2023-Q4-011 を「What happened with INC-2023-Q4-011?」で検索すると、1位は正しく Section 10(Cybersecurity Analysis)だが、2位に無関係な Section 3(Financial Analysis)が返る。本来欲しい2位は、その ID が3回登場する Section 2(Software Engineering)。
  • 原因は semantic search の性質:意味ベクトルの近さで探すため、ID・コード番号のような「そのままの文字列一致」が重要なクエリでは期待どおりに動かないことがある。
  • 解決戦略:ユーザーの質問を semantic search と lexical search(古典的テキスト検索)に並行で流し、両方の結果をマージする(マージは次レッスン)。lexical search は質問を単語に分解し、その単語を含むチャンクを探す。
  • lexical search の代表手法が BM25(Best Match 25)。RAG パイプラインで最も一般的。名前の由来は「オリジナル開発者たちが作った式の 25番目のバリエーション」。
  • BM25 のハイレベル4ステップ(スライドの図解どおり): 1. Tokenize user query — クエリをトークン化(今回は「句読点を除去してスペースで分割」という最も単純な方式)。例:a INC-2023-Q4-011a / INC-2023-Q4-011 2. See how often each term occurs across all documents — 各語の全チャンク横断の出現頻度を数える(例:a=5回、INC-2023-Q4-011=1回) 3. Terms used less frequently are given more importance — 低頻度語ほど重要度が高い(a=LOW、INC-2023-Q4-011=HIGH)= IDF(逆文書頻度)の発想 4. Find the text chunk that uses the higher weighted terms more often — 高重み語をより多く含むチャンクを上位で返す
  • 実装はノートブック 004_bm25.ipynb。先頭に chunk_by_section\n## で分割)、次に BM25Index クラスk1: float = 1.5b: float = 0.75tokenizer: Optional[Callable] を持つ基本実装)。使い方は3ステップ:

```python chunks = chunk_by_section(text)

store = BM25Index() for chunk in chunks: store.add_document({"content": chunk})

results = store.search("What happened with INC-2023-Q4-011?", 3) ```

  • BM25 の検索結果は Section 2(Software Engineering)→ Section 10(Cybersecurity)→ Methodology の順(画面の出力ではスコア値 0.27 → 0.33 → 0.94 と小さい順に上位)。incident 2023... を含むセクションが、含まないセクションより先に出るようになり、semantic search 単独の問題が解消。what happened with のような高頻度語は重み付けが低く抑えられている。
  • 設計上の布石:VectorIndex(semantic)と BM25Index(lexical)の API をあえて揃えてある(どちらも add_documentsearch を持つ)。次レッスンで2システムの結果をマージする際に効いてくる。
  • ASR 注意:文字起こしでは 「VM25」と誤認識されているが正しくは BM25

実務ポイント(このレッスンから持ち帰るもの)

  1. ID・エラーコード・型番・チケット番号を扱う RAG では lexical search を必ず併設する。semantic search 単独では INC-2023-Q4-011 のような固有トークンのクエリで無関係チャンクが上位に来る corner case が実際に起きる(本レッスンで実証)。
  2. BM25 導入の最小雛形(そのまま使える3ステップ):

```python # 1. Chunk the text by section chunks = chunk_by_section(text)

# 2. Create a BM25 store and add each chunk to it store = BM25Index() for chunk in chunks: store.add_document({"content": chunk})

# 3. Search the store results = store.search("What happened with INC-2023-Q4-011?", 3) for doc, distance in results: print(distance, "\n", doc["content"][0:200], "\n----\n") ```

  1. semantic / lexical の2ストアはインターフェースを統一しておくadd_document / search)。後段のハイブリッド検索(結果マージ、次レッスン)が差し替え可能な形で書ける。
  2. トークナイザは最初は単純でよい:句読点除去+空白分割から始める(BM25Indextokenizer を差し替え可能な引数として持つので、後から改良できる)。
  3. BM25 の直感を持っておく:頻出語(a, the, what...)は重み小、希少語(ID 等)は重み大。効きの検証は「希少語を含むセクションが、含まないセクションより先に返るか」で行う(講師も「まったく同じ結果でなくてよいが、検索語を含まない結果より先に含むセクションが見えること」を合格基準にしている)。
  4. 判断基準:semantic search が上手くいかない例を1つ見つけたら、それを固定のスモークテストクエリにして、lexical 導入前後で順位が改善したかを見る——本レッスンの進め方そのものがこの検証パターン。
  5. 落とし穴:この実装の search はスコアを正規化して返しており、画面の出力では値が小さいものが上位に並ぶ(0.27 → 0.33 → 0.94)。スコアの向き(大きいほど良いのか小さいほど良いのか)を確認せずにマージ実装に進まないこと。

本文(時系列・日本語逐語+画面差し込み)

[0:00] RAGパイプラインの最初のイテレーションが組み上がりました。

[0:03] 今のところすべて順調に見えますが、まもなく「うーん、もしかすると最良の検索結果が得られていないかもしれない」ということにすぐ気づくことになります。

[0:09] 例をお見せしましょう。

🖥 [0:11] 画面: VS Code(タイトル: report.md — prod、タブ: 003_vectordb.ipynb / 004_bm25.ipynb / report.md)。report.md の先頭部分: ```markdown

Annual Interdisciplinary Research Review: Cross-Domain Insights

Executive Summary

This report synthesizes the key findings and ongoing research efforts across the organization's diverse operational and R&D departments for the past fiscal year. ... Advances in Medical Research focused on the rare XDR-471 syndrome, yielding new diagnostic insights. Concurrently, Software Engineering tackled persistent stability issues, implementing key fixes identified through error code analysis (e.g., ERR_MEM_ALLOC_FAIL_0x8007000E). Financial Analysis revealed mixed quarterly performance, prompting strategic reviews, particularly concerning resource allocation impacting R&D pipelines. `` スクロールすると Table of Contents(4. Section 1: Medical Research 〜 13. Section 10: Cybersecurity Analysis - Incident Response Report、14. Future Directions)と## Methodology` が見える。

[0:11] report.md ファイルを開いて、少しだけ下へスクロールして software engineering のセクションまで行くと——はい、ここですね——INC という記述があることに気づくでしょう。これは incident 2023 Q4 011 の略です。

[0:23] そしてこの検索語は、この段落の中に3回出現しているようです。ここに1つ目、そこに2つ目、そしてここに3つ目です。

[0:35] さらにドキュメント全体を検索していくと、この下のセクション10 cybersecurity analysis の中でも言及されていることが分かります。ヘッダーの中で1回、それから実際の段落そのものの中でも1回、ちょうどそこで言及されています。

🖥 [0:37] 画面: report.md のセクション10。INC-2023-Q4-011 の出現箇所がハイライト(ステータスバーに「4 selections (60 characters selected)」): ```markdown

Section 10: Cybersecurity Analysis - Incident Response Report: INC-2023-Q4-011

The Cybersecurity Operations Center successfully contained and remediated a targeted intrusion attempt tracked as INC-2023-Q4-011. Threat intelligence indicates the activity aligns with tactics, techniques, and procedures associated with the ShadowNet Syndicate threat actor group. Initial access was gained via a spear-phishing email targeting personnel within the finance department, potentially seeking data relevant to Section 3 (Financial Analysis). Endpoint detection and response (EDR) systems flagged anomalous process execution (PID: 7812) on workstation WS-FIN-112. ... ```

[0:44] さて、この用語——この incident 2023 Q4 011——を検索してみたいと思います。そして何が起きるか、とにかく見てみましょう。言い換えると、semantic search を使うと実際にどんな検索結果が返ってくるのか?ということです。

[0:55] そこでノートブックの中に戻り、ここにあるユーザークエリを「what happened with incident 2023」に更新します。それからすべてのセルを再実行して、どんな結果が得られるか見てみましょう。

🖥 [0:57] 画面: 003_vectordb.ipynb。既存セル(実行番号 [44]・[45]): ```python store = VectorIndex()

for embedding, chunk in zip(embeddings, chunks): store.add_vector(embedding, {"content": chunk}) python

4. Some time later, a user will ask a question. Generate an embedding for it

user_embedding = generate_embedding( "What did the software engineering dept do last year?" ) python

5. Search the store with the embedding, find the 2 most relevant chunks

results = store.search(user_embedding, 2)

for doc, distance in results: print(distance, "\n", doc["content"][0:200], "\n") ``` (このクエリ文字列をこのあと incident の質問に書き換えて再実行する)

[1:07] さあ、これを見てください。ちょっと意外な結果です。

🖥 [1:10] 画面: 003_vectordb.ipynb の再実行結果(セル [55]・[56])。出力: ``` 0.6341486450589804 Section 10: Cybersecurity Analysis - Incident Response Report: INC-2023-Q4-011

The Cybersecurity Operations Center successfully contained and remediated a targeted intrusion attempt tracked as `INC-2

0.8094108282203448 Section 3: Financial Analysis - Q3 Performance and Outlook

Quarterly financial analysis revealed a complex picture. Overall group revenue saw modest growth of 3.1% year- over-year, primarily driven by ```

[1:14] セクション10が返ってきます。これは良いことです。セクション10はこのインシデントについてのセクションそのものなので、リストの最初に見たい結果であることは間違いありません。

[1:19] ところが非常に意外なことに、次の結果はセクション3、financial analysis なんです。

[1:25] さて、ここでセクション3を開いてみると、セクション3の中ではそのインシデントが一度も言及されていないことに気づくでしょう。

🖥 [1:32] 画面: report.md のセクション3。直前のセクション2の末尾には「During Q4 of 2024 the team also assisted with helping regarding the INC-2023-Q4-011 incident.」とあるが、セクション3自体に INC の言及はない: ```markdown

Section 3: Financial Analysis - Q3 Performance and Outlook

Quarterly financial analysis revealed a complex picture. Overall group revenue saw modest growth of 3. 1% year-over-year, primarily driven by strong performance in the primary subsidiary's established markets. However, the emerging markets division experienced a slight contraction (-1.5%), attributed to increased competitive pressure and unfavorable currency fluctuations. Margin erosion was observed across several key product lines, linked to rising input costs and supply chain disruptions. Project Hercules, aimed at optimizing operational expenditures, yielded initial savings, but these were insufficient to fully offset the margin pressure. ... ```

[1:37] つまり、semantic search からここでは少し意外な出力が得られているわけです。本当に返してほしかったのはセクション10、その次にセクション2でした。しかし実際に得られたのはセクション10、その次に残念ながらセクション3でした。

🖥 [1:40] 画面: スライド。上部に User's Question「What happened with INC-2023-Q4-011?」。左「Results we want」/右「Results we got」の対比: - Results we want: - ## Section 10: Cybersecurity Analysis - Incident Response Report: INC-2023-Q4-011 / The Cybersecurity Operations Center successfully … tracked as INC-2023-Q4-011. - ## Section 2: Software Engineering / The Software Engineering division … at a cost of INC-2023-Q4-011. / Root cause analysis … during Q4 2024 (Test Case ID: INC-2023-Q4-011). / Further refactoring … regarding the INC-2023-Q4-011 incident. - Results we got: - ## Section 10: Cybersecurity Analysis - Incident Response Report: INC-2023-Q4-011 / The Cybersecurity Operations Center successfully … tracked as INC-2023-Q4-011. - ## Section 3: Financial Analysis - Q3 Performance and Outlook / Quarterly financial analysis revealed a complex picture…

[1:48] そしてセクション3は、このインシデントを調査するという観点では、完全に無関係のように見えます。

[1:53] というわけで、私たちが組み上げた semantic search のテクニックは本当に素晴らしく、多くの場合はうまく機能するのですが、期待どおりには動いてくれない、こうしたコーナーケースが存在するのです。

[2:07] そこで、検索結果を改善して、望んでいる結果——つまりセクション10、その次にセクション2——がうまく得られるようにするために使えるテクニックを見ていきましょう。

[2:18] それでは、これから使う全体戦略はこうです。ユーザーが質問をするたびに、その質問を semantic search 側の系統、つまり embeddings を生成して vector database を使う側に流し込みます。

🖥 [2:18] 画面: スライド(フロー図)。「User's Question: What happened with INC-2023-Q4-011?」から2つに分岐 → 左「Semantic Search (Embeddings + Vector DB)」・右「Lexical Search (Classic text search)」→ それぞれ「Results」の束 → 下で合流して「Merge results」。

[2:27] しかしそれと並行して、同時に、別個の lexical search システムも実装します。

[2:34] lexical search は、より古典的なテキスト検索に近いものです。ユーザーの質問を個々の単語に分解し、それらの単語を含んでいそうなテキストのチャンクを見つけようとします。

[2:45] 両方の異なるシステムで検索プロセスを通すと、2組の結果セットが得られます。そしてその結果をひとつにマージします。

[2:50] ここでの狙いは、検索結果のバランスが少し良くなることです。つまり、意味的な側面と、プレーンテキスト検索の側面の両方が、1つの結果セットに含まれるということです。

[3:05] そして最終的には、こちら側にあるような結果が得られることを期待しています。

[3:05] さて、この lexical search を実装するにあたって——テキスト検索を実装する方法は膨大な数がありますが、いま私たちが構築しているような RAG パイプラインで使われているのを非常によく見かける手法が、BM25 と呼ばれるテクニックです(※ASR注:文字起こしの「VM25」は BM25 の誤認識)。

🖥 [3:10] 画面: スライド「Lexical Search: BM25」 - Many methods for implementing text search, but BM25 is commonly used in RAG pipelines. - BM25 = Best Match 25. The '25' means this was the 25th variation of a formula developed by the original creators.

右側(赤いパネル、下向き矢印つきの4ステップ): 1. Tokenize user query 2. See how often each term occurs across all documents 3. Terms used less frequently are given more importance 4. Find the text chunk that uses the higher weighted terms more often

[3:22] これは best match 25 の略です。この動画の残りでは、このアルゴリズムがどのように動くのかをハイレベルに概観し、実際に BM25 を実装したノートブックを見ていきます。そうすれば直接触って遊びながら、どんな検索結果が得られるかを確かめられます。

[3:41] それでは、BM25 アルゴリズムの背後にある基本的な考え方です。繰り返しになりますが、ハイレベルな概要にとどめます。物事をシンプルにして理解しやすくするために、細かめのステップはいくつか省きます。

🖥 [3:41] 画面: スライド(BM25 の4ステップを具体例で図解): 1. Tokenize user query — User's Question「a INC-2023-Q4-011」→ トークン「a」「INC-2023-Q4-011」 2. See how often each term occurs across all documents — 2つのテキストチャンク: - チャンク1: A new incident named INC-2023-Q4-011 was found to have large impacts on a portion of our infra. - チャンク2: Financial analysis revealed a complex picture. A more dedicated report will be required to get a better idea of the impact. - → 表 Term / Frequency: a = 5INC-2023-Q4-011 = 1 3. Terms used less frequently are given more importance — 表 Term / Frequency / Importance: a・5・LOWINC-2023-Q4-011・1・HIGH 4. Find the text chunk that uses the higher weighted terms more often — チャンク1が再掲され、注記「Contains more instances of the higher-weighted terms

[3:47] すべては、ユーザーのクエリを受け取るところから始まります。

[3:52] こんなケースを想像してみましょう。ユーザーがここにあるような検索文字列を入れたとします。まず a——単語の「a」ですね——それから incident 2023 Q4 011 です。

[3:57] ステップ1では、ユーザーのクエリを tokenize(トークン化)します。つまり、別々のチャンクに分割するということです。

🖥 [4:15] 画面: 同じスライドで、User's Question ボックス内の「a INC-2023-Q4-011」がテキスト選択でハイライトされ、右のトークン枠(aINC-2023-Q4-011)が指し示されている。

[4:03] ユーザーのクエリをトークン化する方法にはいろいろありますが、いまのところは非常にシンプルな方法を使います。句読点を取り除き、すべての語をスペースに基づいて分割する、というものです。

[4:12] この場合、a と incident 2023(-Q4-011) という別々の検索クエリ語が得られることになります。

[4:18] 次に、これらの異なる検索語のそれぞれが、私たちのすべてのドキュメント——今回の場合は実際にはテキストチャンクですね——にわたって、どれくらいの頻度で出現するかを見ていきます。

[4:29] このシナリオでは、テキストチャンクが2つしかないと想像しましょう。そこで、単語 a と、単語 incident なんとかかんとかが、それぞれのチャンクにどのくらい出現するかを見ます。

🖥 [4:45] 画面: 同じスライドで、1つ目のチャンク(A new incident named INC-2023-Q4-011 was found to have large impacts on a portion of our infra.)が選択枠で強調され、a の出現箇所を指し示している。右の頻度表は a=5、INC-2023-Q4-011=1。

[4:40] どうやら、この最初のチャンクには、ここに a、そこに a があります。そして2つ目のチャンクには a、a、a があります。合計すると、全部数え上げて a が5つになります。

[4:51] それから、incident 2023 が何回あるかを見ます。ここに1つだけあるようです。

[4:59] なので、頻度は1ということになります。

[5:02] 次に、使用頻度に基づいて、各語に相対的な重要度を割り当てます。

[5:08] 単語 a の場合、5回使われていました。かなり頻繁に使われていたので、この語はそれほど重要ではない、とみなします。すべてのドキュメント——今回の場合はテキストチャンクですが——のいたるところで使われているからです。

🖥 [5:15] 画面: 同じスライドで、Importance 表の a 行の「LOW」セルがハイライト選択されている(Term / Frequency / Importance = a・5・LOW、INC-2023-Q4-011・1・HIGH)。

[5:21] しかし incident 2023 のほうは、ごくまれにしか使われていませんでした。ということは、検索上の重要度はおそらくより高くなる、ということです。

[5:29] それから最後のステップでは、より高く重み付けされた語を、より多く使っているテキストチャンクを見つけます。

🖥 [5:45] 画面: 同じスライドで、2つ目のチャンク(Financial analysis revealed a complex picture. A more dedicated report will be required to get a better idea of the impact.)が選択枠で強調されている。最下段には1つ目のチャンクが再掲され、「Contains more instances of the higher-weighted terms」の注記。

[5:35] この場合、こちらの最初のテキストチャンクには a が2つしかありません。一方、2つ目のチャンクには a が3つあります。しかし a は、すべてのチャンクにわたってかなり頻繁に使われているので、それほど重要ではありません。

[5:48] それに対して、テキストチャンク1は incident 2023 を1回使っています。そしてこれは高く重み付けされた語、本当に重要な語です。

[5:55] というわけでこの場合、これがおそらく最良のテキストチャンクだろうと判断し、これを第一の検索結果として返したい、ということになります。

[6:02] さて、これらすべてが実際に動くところを見るために、簡単な Jupyter ノートブックを見てみましょう。

[6:06] エディタの中に戻って、新しいノートブックを探します。これは 004 アンダースコア BM25 という名前です。

🖥 [6:10] 画面: 004_bm25.ipynb(.venv Python 3.12.9)。先頭セル: ```python

Chunk by section

import re

def chunk_by_section(document_text): pattern = r"\n## " return re.split(pattern, document_text) 次のセル(BM25 の基本実装、クラス定義の冒頭):python

BM25Index implementation

import math from collections import Counter from typing import Callable, Optional, Any, List, Dict, Tuple

class BM25Index: def init( self, k1: float = 1.5, b: float = 0.75, tokenizer: Optional[Callable[[str], List[str]]] = None, ): ```

[6:14] 今回も、先頭のところには、セクション単位のチャンキングに関するコードがあります。

[6:19] その次に、この BM25Index というクラスの形で、BM25 の基本的な実装があります。

[6:26] ですので、このセルは折りたたんで、確実に実行しておきます。

[6:29] それからレポートファイルの内容を読み込みます。そしてここでは、3つの別々のステップを進めていきます。

🖥 [6:40] 画面: 004_bm25.ipynb。「# Chunk by section」「# BM25Index implementation」のセルは折りたたみ済み(実行チェック付き)。以下のセルが並ぶ(Cell 6 of 6): python with open("./report.md", "r") as f: text = f.read() (実行番号 [10]・0.0s) ```python

1. Chunk the text by section

python

2. Create a BM25 store and add each chunk to it

python

3. Search the store

``` (3つはまだ中身が空のコメントだけのセル。カーソルは「# 3. Search the store」内)

[6:34] まず、テキストをセクション単位でチャンク化します。BM25 の index store を作って、各テキストチャンクをそこに追加します。それからそのストアに対して検索を試みます(※ASR注:「BM25IndexStore」と聞こえるが、実際のクラス名は BM25Index)。

[6:43] そして今回も、ここでの望みは、もう少しこれに近く見える検索結果がおそらく得られるだろう、ということです。

[6:50] まったくこのとおりの結果でなくてもかまいません。ただ、その検索語をまったく含まない結果が出てくるより前に、incident 2023 を使っているセクションを必ず見たいのです。

[7:01] では、どれくらいうまくいくか見てみましょう。

[7:03] よし、ではここのステップ1に取りかかりましょう。テキストをセクション単位でチャンク化する必要があります。

[7:07] これはもう何回かやってきましたね。chunks は、chunk by section に text を渡したもの、とします。

🖥 [7:10] 画面: 「# 1」セルに入力して実行(実行番号 [11]・0.0s): ```python

1. Chunk the text by section

chunks = chunk_by_section(text) ``` 続いて「# 2. Create a BM25 store and add each chunk to it」のセルにフォーカス。

[7:14] 次に、ストアを作ります。

[7:19] そして、自分のすべてのチャンクをループして、ドキュメントとしてストアに追加していきます。

[7:23] つまり、for chunk in chunks として、store.add_document。

[7:30] そして、content が chunk になっている辞書を渡します。

[7:33] これを実行します。

[7:35] そしていよいよ最後に、ストアに対して検索をかけます。

🖥 [7:40] 画面: 「# 2」セルの完成形(実行番号 [12]・0.0s): ```python

2. Create a BM25 store and add each chunk to it

store = BM25Index()

for chunk in chunks: store.add_document({"content": chunk}) `` 「# 3」セルにはresults = store.search("")` と入力中(引用符の中は空)。

[7:38] store.search と書いて、前のノートブックであまり良い結果をくれなかったのと同じ語を使います。

[7:45] つまり、what happened with incident 2023 Q4011 と尋ねます。

[7:53] そして、最初の3件の検索結果を要求します。

[7:56] それから、結果をきちんと解釈できるように、結果をとてもきれいに出力してあげます。

[8:01] for doc, distance in results としましょう。そして distance を出力して、

[8:07] 改行、そして content 付きの doc。

[8:11] 今回も、最初の200行だけを出力します(※訳注:実際のコードは doc["content"][0:200] なので正しくは「最初の200文字」)。

🖥 [8:10] 画面: 「# 3」セルの入力途中: ```python

3. Search the store

results = store.search("What happened with INC-2023-Q4-011?", 3)

for doc, distance in results: print(distance, "\n", doc["content"][0:200], "") `` (この直後、最後の引数に区切り線"\n----\n"` を追記する)

[8:14] それから、改行を1つ、区切り記号をいくつか、そしてもう1つ改行、というのはどうでしょう。

[8:19] これを実行して、何が得られるか見てみましょう。

[8:21] よし。

🖥 [8:40] 画面: 「# 3」セルの完成形と実行結果(実行番号 [13]・0.0s): ```python

3. Search the store

results = store.search("What happened with INC-2023-Q4-011?", 3)

for doc, distance in results: print(distance, "\n", doc["content"][0:200], "\n----\n") 出力: 0.2713100078774988 Section 2: Software Engineering - Project Phoenix Stability Enhancements

The Software Engineering division dedicated considerable effort to improving the stability and performance of the core systems

0.33172619481009574 Section 10: Cybersecurity Analysis - Incident Response Report: INC-2023-Q4-011

The Cybersecurity Operations Center successfully contained and remediated a targeted intrusion attempt tracked as `INC-2


0.9391180106840461 Methodology ```

[8:22] さっきのものよりずっと良い検索結果です。

[8:25] 今度は software engineering が最初に表示され、その後に cybersecurity が来ています。

[8:29] そしてこの下に methodology です。

[8:31] つまり今度は、クエリの中で最も重要な検索語——それは incident 2023 でしたね——を使っているセクションを、実際に優先できているわけです。

[8:41] そして、それ以外の語、たとえば what happened with のあたりには、それほど大きな重要度が置かれていないことにも気づくでしょう。あれらはそこまで重要な語ではありませんし、元のレポートの中で何回も使われているかもしれないので、出力結果の中ではそれほど重く重み付けしないことになります。

[8:50] しかしこの incident 2023 は、レポートの中では非常にまれな語です。ですから、間違いなくずっと高い重み付けを持つべきです。そしてそれが、私たちの検索結果の中に非常にはっきりと反映されているのが見て取れます。

[9:00] よし。それでは、この時点で、私たちには2つの別々の検索システムがあります。

🖥 [9:10] 画面: 再びフロー図のスライド(User's Question → Semantic Search / Lexical Search → Results → Merge results)。右側の「Lexical Search (Classic text search)」ボックスが選択ハンドル付きで強調されている。

[9:04] semantic search が組み上がっていて、そしてこの、より lexical な、もう少し古典的なテキスト検索システムがあります(※ASR注:「classic tech search」は "classic text search" の誤認識と思われる)。

[9:12] そして気づくかもしれませんが、この2つのストアの実装——この上のほう、ちょうどこのセルのところですが——私はかなり似た API でまとめて作ってあります。どちらも add document 関数を持っていますし、この下のほうには、どちらも search 関数も持っています。

🖥 [9:17] 画面: 004_bm25.ipynbBM25Index 実装セルをスクロールして内部を表示。スコア計算と search メソッドのシグネチャ: ```python numerator = idf * term_freq * (self.k1 + 1) denominator = term_freq + self.k1 * ( 1 - self.b + self.b * (doc_length / self._avg_doc_len) ) score += numerator / (denominator + 1e-9)

    return score

def search(
    self,
    query_text: str,
    k: int = 1,
    score_normalization_factor: float = 0.1,
) -> List[Tuple[Dict[str, Any], float]]:
    if not self.documents:
        return []

    if not isinstance(query_text, str):
        raise TypeError("Query text must be a string.")

    if k <= 0:
        raise ValueError("k must be a positive integer.")

```

[9:29] ということで、2つの別々の検索システム——semantic search を実装するものと、lexical search を実装するもの——がそろったので、次の動画に戻ってきたら、この2つの検索システムをマージしていきます。

🖥 [9:40] 画面: フロー図スライド(User's Question「What happened with INC-2023-Q4-011?」→ Semantic Search (Embeddings + Vector DB) / Lexical Search (Classic text search) → 各 Results → Merge results)が全画面表示。

[9:38] ユーザーがクエリを送信するたびに、それをこの2つの異なる検索システムの両方に転送します。両方から結果セットを受け取り、そしてそれらの結果をマージします。

[9:52] そうすればきっと、semantic search の最良の成果のすべてに加えて、lexical search のより古典的な結果のいくつかも、あわせて手に入ることになるでしょう。

英語逐語(ASR全文)

We've got the first iteration of our RAG pipeline put together. Everything looks good right now, but we're going to very quickly realize that, well, maybe we are not getting the best search results. Let me show you an example.

If you open up the report.md file and scroll down just a little bit to the software engineering section, here it is right here, you'll notice that it has the statement of INC, which is short for incident 2023 Q4 0 1 1. And it looks like that search term occurs three times inside of this paragraph. So here's one right here, two right there, three right here. And if I continue searching throughout the document, I'll see that it is also mentioned down here inside of section 10 cybersecurity analysis. It's mentioned inside the header and then one time inside the actual paragraph itself right there. Now I want to try searching for this term, this incident 2023 Q4 0 1 one. And we're just going to see what happens. In other words, what search results do we actually get back using semantic search? So back inside of my notebook, I'm going to update the user query right here to be what happened with incident 2023. Then I'm going to rerun all cells and we'll see what result we get.

All right, so take a look at this. It's a little bit surprising result. We get section 10, which is good. That's definitely the result we would want to see first in the list because section 10 is all about this incident. But then very surprisingly, the next result is section three, financial analysis. Well, if you open up section three right here, you'll notice that nowhere inside of section three is that incident ever mentioned. So we are getting some output from our semantic search that is a little bit surprising here. What we really wanted to get back was section 10 and then section two, but what we got was section 10 and then unfortunately section three. And section three appears to be completely irrelevant when it comes to investigating this incident. So even though the semantic search technique we put together is really fantastic and it's going to work well a lot of the time, there are these corner cases where it really just doesn't quite work as expected. So let's take a look at a technique we can use to improve our search results and hopefully get the results we want, which is section 10 and then section two.

All right, so here's the general strategy we are going to use. Whenever a user asks a question, we're going to feed that question into our semantic search side of the equation, which is generating those embeddings and using the vector database. But then in parallel, at the same time, we're also going to implement a separate lexical search system. Now, lexical search is more like classic text search, where we are going to break down the user's question into individual words and then trying to find chunks of text that seem to include those words. Once we go through the search process in both different systems, we'll get two sets of results, and then we will merge the results together. And the hope here is that we'll get a little bit better balance of search results, where we get both the kind of semantic aspect and the plain text search aspect included in one result set. So hopefully we will eventually get a result that looks like this over here.

Now to implement this lexical search, there are a tremendous number of methods for implementing text search, but a very common method that you're going to see used in RAG pipelines, like the one we are building right now, is a technique referred to as VM25.(※ASR注:「VM25」は BM25 の誤認識) Now this is short for best match 25. In the rest of this video, I'm going to give you a high level overview of how this algorithm works, and we're going to take a look at a notebook that actually implements BM25. So we'll be able to play around with it directly and see what kind of search results we get.

So here's the general idea behind the BM25 algorithm. Now, again, I'm going to give you a high level overview, and I'm going to leave out a couple of smaller steps just to simplify things and make it easier to understand. Everything is going to begin with us receiving a user's query. And let's imagine this case, they put in a search string like this right here, just A, so the word A, and then incident 2023 Q4 011. In step one, we're going to tokenize the user's query. And that means we're going to break it up into separate chunks. There are different ways in which we can tokenize a user's query, but for right now, we're going to use a very simple method, which is to just remove punctuation and break up the all terms based upon spaces. So in this case, I would end up with separate search query terms of A and then incident 2023.

Next, we're going to see how often each of these different search terms occurs across all of our different documents, or in our case, really text chunks. So let's imagine that we only have two text chunks in this scenario. So we're going to see how often the word a and the word incident, blah, blah, blah occurs across each of these chunks. And it looks like this first chunk right here has a right there, a right there. And then the second one has a, a, a. So in total, I would count all those up and I would have five a's. And then I would see how often I have incident 2023. It looks like there's only one right here. So I'd end up with a frequency of one.

Next up, we're going to assign a relative importance to each term based upon its usage frequency. So in the case of the word A, it was used five times. And because it was used rather often, we're going to say this term is not super important because it is used all to place across all of our different documents, or again, our case, our text chunks.(※ASR注:「all to place」は "all over the place" の誤認識と思われる) But incident 2023, that was used very infrequently, which means it is probably going to be of greater search importance.

Then finally, in the last step, we're going to find the text chunk that uses the higher weighted terms more often. So in this case, the first text chunk right here, it only has two A's, whereas the second one has three A's, but A's are not super important because they're used rather frequently across all of our different chunks. However, text chunk one uses incident 2023 one time, and that is a highly weighted term. It's a really important term. So in this case, we would say this is probably our best text chunk, and we would want to return this as a prime search result.

Now, again, to see all this in action, let's take a look at a quick Jupyter notebook. So back inside of my editor, I'm going to find a new notebook. This one is called 004 underscore BM25. Again, at the top, I've got some code relating to chunking by section. I've then got a basic implementation of BM25 in the form of this class called BM25Index. So I'm going to collapse that cell, make sure I run it. I'm then going to read the contents of our report file, and then we're going to go through three separate steps here. We're going to first chunk the text by section. We're going to create a BM25IndexStore, and we're going to add each text chunk to it, and then we're going to attempt to search the store.(※ASR注:実際のクラス名は BM25Index。「BM25 index store(BM25 インデックスのストア)」という発話が連結されたもの) And once again, our hope here is that we're going to maybe get some search results that look a little bit closer to this. Maybe not exactly these results, but I definitely want to see the sections that use incident 2023 before I ever see some results that don't include that search term at all. So let's see how we do.

Okay, let's take care of step one here. We need to chunk the text by section. So we've gone over this a couple times now. We'll say chunks is chunk by section with text. Next up, I'm going to create a store. And I'm going to loop over all my chunks and add them in as documents to the store. So I'll say for chunk in chunks, store, add document. And I'll pass in a dictionary with a content of chunk. I'm going to run that. And now finally, I'm going to search over the store. I'll say store search, and I'll use that same term that I used in the previous notebook that did not give us the very good result. So I'll ask what happened with incident 2023 Q4011. And I'm going to ask for the first three search results. And then I'm going to print up the results very nicely just so we can interpret them really well. Let's say for doc distance in results, and I'll print out the distance. a new line, doc with content. And again, I'm only going to print out the first 200 lines.(※発話注:実際のコードは [0:200]=最初の200文字) And then how about a new line, a couple of separators and another new line. I'm going to run this and we'll see what we get.

All right. So that's a much better search result than what we had before. Now we're going to see software engineering first and then cybersecurity after that. And then methodology down here. So now I am actually prioritizing the sections that use the most important search term inside my query, which was the incident 2023. And you'll notice that I don't really have quite as much importance around the other terms, like what happened with. Those aren't quite as important terms, and they might be used several times inside of the original report, so I would not weigh those as heavily in the output results. But this incident 2023, that's a very rare term inside of a report, so it should definitely have a much higher weighting. And we can see that reflected very clearly inside of our search results.

All right. So now at this point in time, we have two separate search systems. We have semantic search put together, and we have this kind of more lexical, a little bit more classic tech search system.(※ASR注:「tech search」は "text search" の誤認識と思われる) And you might notice that in the implementation of these two stores back up here in the cell right here, I put them together with a rather similar API. They both have a add document function and they both have a search function down here as well. So now that we have these two separate search systems, one which implements semantic search and one which implements lexical search, we're going to come back in the next video and we're going to merge these two search systems together. Whenever a user submits a query, we're going to forward it off to both of these different search systems. We're going to get back a set of results from both and we're going to merge those results together. And hopefully we'll have all the best outcomes of semantic search along with some of the more classic results of lexical search as well.

↑ 目次へ戻る

Building with the Claude API | Lesson 45「A Multi-Index RAG pipeline」(全文ナレッジ)

動画 6分44秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

  • ハイブリッド検索の完成回。前回までに作った semantic searchVectorIndex=埋め込み+ベクトルDB)と lexical searchBM25Index=古典的テキスト検索)を、新クラス Retriever で束ね、両者の結果を reciprocal rank fusion(RRF/相互順位融合) でマージする。
  • 設計の鍵は、2つの実装がほぼ完全に同じ公開APIを持つこと。どちらも add_document()(ドキュメント=text chunk をインデックスへ追加)と search()(入力クエリに関連するドキュメントとスコアを返す)を持つ。同じ契約に揃えたからこそ Retriever に簡単にラップでき、まったく別の検索方式のインデックスも後から自由に追加できる(プラガブル設計)。
  • Retriever の動き:
  • add_document() … 渡されたドキュメントを、内部に保持する全インデックスへそのまま横流しする。
  • search() … クエリテキストを全インデックスへ転送し、返ってきた複数の結果リストをマージして1本にして返す。
  • マージ操作は実は厄介(ベクトル検索の距離と BM25 のスコアは尺度がまったく別)→ スコアの絶対値を使わず、順位(rank=検索出力での位置)だけを使う RRF で吸収する。
  • RRF の式RRF_score(d) = Σᵢ 1 / (k + rankᵢ(d))(i は各インデックスの rank 列)。k は定数で 60 がよく使われるが、講義では結果を見やすくするため k=1 を使用。スコアが高いほど関連度の高い text chunk
  • 手計算の例:vector index が [Section 2, 7, 6]、BM25 が [Section 6, 2, 7] を返した場合——
  • Section 2(rank 1 と 2)→ 1.0/(1+1) + 1.0/(1+2) = 0.833最終1位
  • Section 6(rank 3 と 1)→ 1.0/(1+3) + 1.0/(1+1) = 0.75 → 2位
  • Section 7(rank 2 と 3)→ 1.0/(1+2) + 1.0/(1+3) = 0.583 → 3位
  • 両方の検索で上位に来るものが自然に浮上する(Section 2 は 1位と2位なので上へ、Section 7 は 2位と3位なので下へ)——目視でも納得できる挙動。
  • 実装はノートブック 005_hybrid.ipynbRetriever(*indexes) は可変長でインデックスを受け取り、search(query_text, k=1, k_rrf=60)。マージロジックは doc_ranks 辞書に各インデックスでの順位を記録(未出現インデックスは float("inf") 埋め)→ calc_rrf_score() で合算 → score > 0 でフィルタ。
  • 検証:以前ベクトル検索単独(003_vectordb.ipynb)では "what happened with INC-2023-Q4-011?" に対し Section 10 →(期待外れの)Section 3 が返っていた。ハイブリッドの retriever.search(..., 3) では Section 10 → Section 2(Software Engineering・期待どおり)→ Section 5 となり明確に改善。
  • これで一区切りだが、RAG パイプラインの精度を上げるテクニックはまだ続く(次回以降へ)。

実務ポイント(このレッスンから持ち帰るもの)

  1. 複数インデックスは「同一 API 契約」に揃える——これだけで束ねるクラスが素直に書ける。雛形: ```python class Retriever: def init(self, *indexes: SearchIndex): if len(indexes) == 0: raise ValueError("At least one index must be provided") self._indexes = list(indexes)

    def add_document(self, document: dict): for index in self._indexes: index.add_document(document)

    def search(self, query_text: str, k: int = 1, k_rrf: int = 60): ... # 全インデックスへ転送 → RRF でマージ 2. **異種スコアを直接混ぜない**。ベクトル距離(小さいほど良い)と BM25 スコアでは尺度も方向も違う。**RRF は順位だけを見る**ので正規化不要でロバスト:python def calc_rrf_score(ranks: list[float]) -> float: return sum(1.0 / (k_rrf + r) for r in ranks if r != float("inf")) `` 3. **k_rrf(定数 k)は 60 が事実上の標準**。デモで 1 を使うのは計算を目で追いやすくするためだけ。本番はデフォルト 60 から始める。 4. **片方のインデックスにしか出ないドキュメントの扱い**を最初に決める。講義実装は未出現 rank をfloat("inf")にして寄与 0(=出現したインデックス分だけ加点)。実装を変える場合も「未出現=減点しない」を守ると安全。 5. **過去に失敗したクエリを回帰テストとして保持する**。この講座では「INC-2023-Q4-011 で Section 3 が2位に来てしまう」というベクトル検索の失敗例を、そのままハイブリッドの検証クエリに再利用して改善を確認した。 6. **拡張は search() / add_document() を実装した新クラスを渡すだけ**。第3の検索方式(完全一致・メタデータフィルタ等)もRetriever(bm25, vector, new_index)` で即マージ対象になる。

本文(時系列・日本語逐語+画面差し込み)

🖥 [0:00] 画面: スライド。左「Semantic Search (Embeddings + Vector DB)」・右「Lexical Search (Classic text search)」。その下に VectorIndexBM25Index のメソッド表(両者まったく同じ内容):

Method Description
add_document() Add a document (text chunk) to the index
search() Returns documents (text chunks) and scores related to an input query

[0:00] これで、セマンティック検索の実装とレキシカル検索の実装が揃いました。ですので次は、これらを互いに配線してつなぎ合わせる必要があります。どうやってつなぐのか、これからお見せします。

[0:09] まず注目してほしいのは、この2つの検索機能の実装が、ほぼ完全に同じ公開 API を持っているという点です。

[0:16] 左側には vector index クラスがあり、add document や search といったメソッドを持っています。そして BM25 index の中にも、ほぼ同一のメソッドがあります。

[0:26] そこで、この2つを1つの検索パイプラインへとつなげるために、retriever と呼ぶことにする新しいクラスの中へ、まとめてラップします。

🖥 [0:30] 画面: Retriever のアーキテクチャ図。上段に「User's Question: What happened with INC-2023-Q4-011?」→ 矢印で大きな枠「RetrieverClass that wraps up the VectorIndex and BM25Index」へ。枠内では VectorIndexBM25Index がそれぞれ Results(書類の束)を返し、2つの Results が下の「Merge results」ボックスへ合流する。

[0:34] この retriever はユーザーの質問を受け取り、それを vector index と BM25 index の search メソッドへ転送します。

[0:42] その後 retriever は両方から結果を受け取り、その結果同士を実際にマージする何らかの方法を考えることになります。

[0:48] さて、実はこのマージ操作が少し厄介なのです。ですので、これら異なる検索方式から出てくる結果をどうやってマージできるのか、少し詳しく説明したいと思います。

🖥 [0:58] 画面: スライド「Reciprocal Rank Fusion — Combine outputs based upon the reciprocal rank of each text chunk」。VectorIndex と BM25Index それぞれの検索結果表:

| Rank | Text Chunk | Score |(VectorIndex) |---|---|---| | 1 | Section 2: Software Engineering… | 0.234 | | 2 | Section 7: Historical Research… | 0.913 | | 3 | Section 6: Product Engineering | 1.327 |

| Rank | Text Chunk | Score |(BM25Index) |---|---|---| | 1 | Section 6: Product Engineering | 0.184 | | 2 | Section 2: Software Engineering… | 0.414 | | 3 | Section 7: Historical Research… | 0.781 |

[0:58] 結果を組み合わせるために、reciprocal rank fusion(相互順位融合)として知られるテクニックを使います。

[1:02] このテクニックを理解する一番簡単な方法は、例をひとつ通しで見ることです。では、今すぐやってみましょう。

[1:07] vector index で検索を実行して、セクション2、7、6という出力が得られたと想像してください。

[1:14] そして BM25 でもまったく同じ検索をして、6、2、7が得られたとします。

[1:19] さあ、この2つの結果リストを、何らかの方法で1つに組み合わせる必要があります。

🖥 [1:24] 画面: 2つの順位表の下に、統合表が現れる:

Text Chunk Rank From Vector Index Rank from BM25 Index
Section 2: Software Engineering… 1 2
Section 7: Historical Research… 2 3
Section 6: Product Engineering… 3 1

[1:24] そのために、すべての検索結果をこのように1つの表にまとめて載せます。

[1:29] これで text chunk の2、7、6が並び、vector index 出力での順位と、BM25 index 出力での順位を記録しました。

[1:38] 念のため言っておくと、私が「rank(順位)」と言っているのは、単に検索出力での位置のことです。つまり順位1、2、3、順位1、2、3。そのまったく同じ数字を、この下の表に書き写しているだけです。

[1:48] すべての順位が揃ったら、次は数式を適用します。

🖥 [1:53] 画面: RRF の式が表示される(英語原文のまま):

RRF_score(d) = Σ_{i=1}^{n} 1 / (k + rank_i(d))

右側の注記:k is a constant, 60 is often used, but going to use 1 here for clearer results

統合表に Score 列が加わる:

Text Chunk Rank From Vector Rank from BM25 Score
Section 2: Software Engineering… 1 2 1.0 / (1 + 1) + 1.0 / (1 + 2) = 0.833
Section 7: Historical Research… 2 3 1.0 / (1 + 2) + 1.0 / (1 + 3) = 0.583
Section 6: Product Engineering… 3 1 1.0 / (1 + 3) + 1.0 / (1 + 1) = 0.75

下部に注記:Higher score = more relevant text chunk

[1:53] これがその正確な数式です。見た目は本当にひどいのですが、心配しないでください。見た目ほど複雑ではありません。

[1:59] 仕組みはこうです。手元にある rank 列の1つごとに——つまり、あそこの列と、あそこの列ですね——別々の項を書き出します。

[2:06] これが1列目に対応する項。これが2列目に対応する項です。

[2:11] 1つ目の項には、「1 ÷(1 + そこにある数字)」を書き出します。すると 1/(1+1) になります。

[2:20] そして2つ目の項は「1 ÷(1 + そこにある数字)」なので、1/(1+2) になります。

[2:26] 各 text chunk のスコアを計算し終えたら、次はスコアが大きい順(降順)に表をソートします。

🖥 [2:33] 画面: 表がスコア降順にソートされ、Final Rank 列が付く:

Text Chunk Score Final Rank
Section 2: Software Engineering… 0.833 1
Section 6: Product Engineering… 0.75 2
Section 7: Historical Research… 0.583 3

[2:33] すると、このような結果になります。

[2:35] つまり、セクション2の text chunk がもっとも関連性の高い検索結果になり、セクション6が2番目、そしてセクション7がもっとも関連性が低い、となります。

[2:45] そしてこれは、なかなか理にかなっています。各検索方式それぞれの順位出力を見るだけでも、この出力が妥当であることを目視で確認できるのです。

[2:50] セクション2は順位1と2でした。ということは、まあ全体として上位へ向かう傾向にあるわけです。

[3:01] セクション6は1と3。これはいわば中間ですね。良いスコアと悪いスコアを1つずつ持っています。

[3:06] そしてセクション7は2と3。だから下位のほうへ沈んでいきます。というわけで、目視での点検でも、この結果はかなり納得のいくものだと私は思います。

[3:15] よし。結果をどう組み合わせるかが理解できたので、Jupyter ノートブックへ戻りましょう。retriever クラスのサンプル実装と、結果マージのサンプル実装を見ていきます。

🖥 [3:28] 画面: スライドから VS Code(005_hybrid.ipynb — prod)へ画面遷移。タブには 003_vectordb.ipynb / 004_bm25.ipynb / 005_hybrid.ipynb / report.md が並ぶ。

[3:26] はい、こちらに戻ってきました。次のノートブック、005 hybrid へ進みます。

🖥 [3:30] 画面: 005_hybrid.ipynb の「# Retriever implementation」セル(Cell 6 of 10): ```python class Retriever: def init(self, *indexes: SearchIndex): if len(indexes) == 0: raise ValueError("At least one index must be provided") self._indexes = list(indexes)

def add_document(self, document: Dict[str, Any]):
    for index in self._indexes:
        index.add_document(document)

def search(
    self, query_text: str, k: int = 1, k_rrf: int = 60
) -> List[Tuple[Dict[str, Any], float]]:
    if not isinstance(query_text, str):
        raise TypeError("Query text must be a string.")
    if k <= 0:
        raise ValueError("k must be a positive integer.")
    if k_rrf < 0:
        raise ValueError("k_rrf must be non-negative.")

```

[3:30] 今回も、上のほうにはたくさんのセットアップがあります。vector database の実装、BM25 の実装があり、そして今回は retriever クラスの実装も追加してあります。

[3:41] retriever には add document というメソッドがあります。add document を呼ぶと、渡したドキュメントをそのまま受け取り、retriever の中に含まれている各インデックスへ順に渡していくだけです。

[3:51] 今回のケースでは、そのインデックスとは vector index と BM25 index のことです。それから retriever には search 関数もあります。

[4:03] search にクエリテキストを渡すと、そのクエリテキストは retriever の中に含まれる各インデックスへ渡されていきます。

[4:10] そして戻ってきた結果をすべて受け取り、1つに組み合わせます。

🖥 [4:14] 画面: 同セルの下半分、reciprocal rank fusion を実装するマージロジック: ```python doc_ranks = {} for idx, results in enumerate(all_results): for rank, (doc, _) in enumerate(results): doc_id = id(doc) if doc_id not in doc_ranks: doc_ranks[doc_id] = { "doc_obj": doc, "ranks": [float("inf")] * len(self._indexes), } doc_ranks[doc_id]["ranks"][idx] = rank + 1

def calc_rrf_score(ranks: List[float]) -> float: return sum(1.0 / (k_rrf + r) for r in ranks if r != float("inf"))

scored_docs: List[Tuple[Dict[str, Any], float]] = [ (ranks["doc_obj"], calc_rrf_score(ranks["ranks"])) for ranks in doc_ranks.values() ]

filtered_docs = [ (doc, score) for doc, score in scored_docs if score > 0 ] ```

[4:14] これが、あの reciprocal rank fusion を実装しているマージロジックです。

[4:20] よし、ここでちょっとテストの時間です。

[4:22] ここで思い出してほしいのですが、そもそも私たちをこの道全体へ導いたきっかけは、vector database の実装のところにありました。

🖥 [4:29] 画面: 003_vectordb.ipynb(ベクトル検索単独の以前のノートブック)に切り替え: ```python

4. Some time later, a user will ask a question. Generate an embedding for it

user_embedding = generate_embedding("what happened with INC-2023-Q4-011?")

5. Search the store with the embedding, find the 2 most relevant chunks

results = store.search(user_embedding, 2)

for doc, distance in results: print(distance, "\n", doc["content"][0:200], "\n") 実行出力(当時の問題の結果): 0.6341486450589804 Section 10: Cybersecurity Analysis - Incident Response Report: INC-2023-Q4-011

The Cybersecurity Operations Center successfully contained and remediated a targeted intrusion attempt tracked as `INC-2

0.8094108282203448 Section 3: Financial Analysis - Q3 Performance and Outlook

Quarterly financial analysis revealed a complex picture. Overall group revenue saw modest growth of 3.1% year-over-year, primarily driven by ```

[4:29] こちらのノートブックです。「what happened with incident 2023」のような検索をすると、予想外の結果が返ってきたのでした。1番目の結果はセクション10で、これは良かったのです。

[4:41] ところが2番目の結果はセクション3でした。これは本当に予想外でした。

[4:45] 2番目の結果には、ソフトウェアエンジニアリングのセクションが来てほしいのです。

[4:48] そこで今回、複数の異なるインデックスを組み合わせるこのハイブリッドアプローチを実行すれば、

[4:52] 私の期待としては、まずセクション10が返ってきて、その次にソフトウェアエンジニアリングのセクション——たしかセクション2だったと思います——が返ってくるはずです。

🖥 [4:59] 画面: 005_hybrid.ipynb に戻り、セットアップ済みセル群が見える: ```python

Chunk source text by section

with open("./report.md", "r") as f: text = f.read()

chunks = chunk_by_section(text) python

Create a vector index, a bm25 index, then use them to create a Retriever

vector_index = VectorIndex(embedding_fn=generate_embedding) bm25_index = BM25Index()

retriever = Retriever(bm25_index, vector_index) python

Add all chunks to the retriever, which internally passes them along to both indexes

for chunk in chunks: retriever.add_document({"content": chunk}) ```

[4:59] では、この新しいノートブックの中でテストしてみましょう。

🖥 [5:01] 画面: 最下部の新しいセルに入力していく: python results = retriever.search("what happened with INC-2023-Q4-011?", 3)

[5:01] 一番下まで行って、results = retriever.search で「what happened with incident 2023 Q4 0 1 1」と入力します。

[5:14] そして、最初の3件の検索結果を取得するようにします。

[5:18] それからもう一度、このように全部プリントアウトします。

🖥 [5:24] 画面: 続けて出力ループを入力: python for doc, score in results: print(score, "\n", doc["content"][0:200], "\n---\n")

[5:24] スコア、改行、そのドキュメントの中身——ただし先頭の200行だけ(※話者の言い間違い:実際のコードは [0:200]=先頭200文字)——、

[5:33] それから各チャンクの間に、ちょっとした区切りを入れます。

🖥 [5:38] 画面: 実行結果(Cell 10 of 10): ``` 0.03252247488101534 Section 10: Cybersecurity Analysis - Incident Response Report: INC-2023-Q4-011

The Cybersecurity Operations Center successfully contained and remediated a targeted intrusion attempt tracked as `INC-2

0.032018442622950824 Section 2: Software Engineering - Project Phoenix Stability Enhancements

The Software Engineering division dedicated considerable effort to improving the stability and performance of the core systems

0.030798389007344232 Section 5: Legal Developments - Navigating IP Precedents and Regulatory Shifts

```

[5:38] これを実行します。すると——ほら、以前のものよりずっと良い検索結果が得られました。

[5:43] セクション10、その次にセクション2。まさに私たちが望んでいたとおりです。それからセクション5ですが、これはここではあまり関連がありませんね。

[5:50] こうして、2つの異なる検索テクニックを組み合わせることで、はるかに良い出力が得られるようになりました。

🖥 [6:00] 画面: スライドの Retriever アーキテクチャ図に戻る(編集モードで BM25Index のボックスが選択されている)。

[5:56] そしてこの方式の良いところは、各インデックスをそれぞれ独立した形で書けたことです。それぞれが独立したクラスになっています。

[6:03] そして、各実装に search 関数と add document 関数というまったく同じ API を持たせたおかげで、これらをより大きな retriever クラスへ簡単にラップできました。

🖥 [6:13] 画面: 図の右側の空きスペースに、第3の検索インデックスの箱と Results の束を白い輪郭線でその場で描き足していく(別の検索方式を追加できることの視覚化)。

[6:13] ですから、もし望むなら、また完全に別の検索機能を実装するような追加の検索インデックスを、ここに加えることもまったく問題なくできます。

[6:21] そのインデックスが search 関数と add document 関数さえ持っていれば、とても簡単に追加でき、結果を生成させて、その結果を他の検索方式から来る結果と一緒にマージできるのです。

[6:35] さて、これは十分な成功だったと言っていいでしょう。ですが、まだ完全に終わりではありません。RAG パイプラインの精度を改善するために、これから見ていくテクニックがまだいくつか残っています。

英語逐語(ASR全文)

We now have an implementation for semantic search and an implementation for lexical search. So now we need to wire these things up together. Let me show you how we're going to do that.

The first thing to notice is that the implementation for both these searching functionalities have the almost exact same public API. So we have a vector index class on the left-hand side that has methods like add document and search. And we have almost identical methods inside of our BM25 index as well.

So to connect these two things together into a single search pipeline, we're going to wrap them up inside of a new class that we will call retriever. This retriever is going to receive a user's question and then forward it on to the search methods of vector index and BM25 index. The retriever will then receive the results from both them and figure out some way of actually merging the results together.

Now it turns out that the merge operation is actually a little bit tricky. So I want to go into a little bit of detail on how you can merge results that are coming out of these different search methodologies.

To combine the results together, we're going to use a technique known as reciprocal rank fusion. The easiest way to understand this technique is to go through an example. So let's do that right now.

Let's imagine that we run a search on the vector index and we get outputs of section 2, 7, and then 6. And then we do the same exact search on BM25 and we get 6, 2, and 7. So now we need to take these two lists of results and combine them together in some way.

To do so, I'm going to take all the search results and put them together on a single table, like so. So now I've got text chunk 2, 7, and 6, and I've recorded the rank from the vector index output and the rank from the BM25 index output. And to be clear, when I'm talking about rank, I just mean kind of search output position. So rank 1, 2, 3, rank 1, 2, 3. I'm just kind of putting those same exact numbers on this chart down here.

Once I have all those ranks in place, I'm then going to apply a formula. Here's the exact formula right here. And I know it looks really terrible, but don't worry. It's not as complicated as it looks.

Here's how it works for every rank column we have. So like that column right there and that column right there, we're going to write out a separate term. So here's the term for the first column. Here's the term for the second column. In the first term, we're going to write out one over one plus whatever number is right there. So we end up with one over one plus one. And then the second term will be one over one plus whatever numbers right there. So one over one plus two.

Once we have calculated the score for each text chunk, we're then going to sort the table based upon the score from greatest to least. So we would end up with something like this. So we'd end up with text chunk for section two as being the most relevant search results, section six would be the second most and section seven will be the least relevant.

And this kind of makes sense. We can kind of visually confirm that these outputs make sense if you just look at the individual rank outputs from each search methodology. So section two was rank one and two. That means, hey, in general, it's trending up towards the top. Section six is one and three. That's kind of like in the middle. It has a good score and a bad score.(※ASR注:画面の表では Section 6 の rank は 3 と 1。「one and three」は話者が順不同で読んだもの)And then section seven is two and three. And so it trends down towards the bottom. So with a visual inspection, the results I think do make a decent amount of sense.

All right. So now that we understand how we're going to combine the results together, let's go back over to our Jupyter notebook, and we're going to take a look at a sample implementation of a retriever class and a sample implementation of merging the results.

Okay, so back over here, I move on to the next notebook, which is 005 hybrid. Once again, there's a lot of setup up here. So I've got the vector database implementation, the BM25 implementation, and now I've added in a implementation for the retriever class as well.

The retriever has method of add document. And if you call add document, it's just going to take whatever document you pass in and pass it off to each of the different indexes that are contained inside the retriever. So in our case, our indexes are the vector index and the BM25 index. Then the retriever also has a search function. If you pass in some query text to it, that query text will be passed off to each of the different indexes that are contained inside the retriever. We then take all the results that come back and combine them together. So here's the merge logic that implements that reciprocal rank fusion.

All right, so time to do a little test here. Now, I want you to recall what led us down this entire path was back on our vector database implementation. So this notebook over here, we found that if we search for something like what happened with incident 2023(※ASR注:話し言葉。実際のクエリ文字列は "what happened with INC-2023-Q4-011?"), we got back some unexpected results where we had section 10, which was good as the first result. And then section three was the second result. And that was really unexpected. We want that second result to be software engineering.

So when we now run this hybrid approach that combines together multiple different indexes, my hope is that we're going to get first section 10 and then whatever section the software engineering one is. I think it's section two.

So let's test this out inside of our new notebook. I'm going to go down to the bottom and I'll do our results is retriever. search what happened with incident 2023 Q4 0 1 1.(※ASR注:コード読み上げの書き起こし。実際の入力は results = retriever.search("what happened with INC-2023-Q4-011?", 3))And I'm going to get the first three search results. And then once again, I'm going to print them all out like so. And I'll do the score, a new line, the content of the document, but just the first 200 lines.(※ASR注ではなく話者の言い間違い:コードは先頭200文字 [0:200])and then just a little separator between each chunk.

So I'm going to run this, and now we get, hey, some much better search results than what we had before. So I've got section 10 and then section 2, exactly what we wanted, and then section 5, but that one's not super relevant here. So we now have a much better output by combining together these two different search techniques.

And the nice thing about this is we were able to author each of these indexes kind of in isolation. They're their own separate classes. And because we made each implementation have the same exact API with that search function and the add document function, we were able to easily wrap them up into this larger retriever class.

So if we wanted to, we could absolutely add in some additional search index here that maybe implements some other completely different searching functionality. And as long as it has that search function and the add document function, we can very easily add it, have it generate some results, and then merge the results along with the results coming from the other search methodologies as well.

Okay, so let's say this is a good success, but we're not quite done yet. There's still some other techniques that we're going to go over to improve the accuracy of our RAG pipeline.

↑ 目次へ戻る

Building with the Claude API | Lesson 46「Extended thinking」(全文ナレッジ)

動画 7分00秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

  • Extended thinking(拡張思考)=最終応答を生成する前に、Claude がユーザーのクエリについて推論する時間を持つ機能。公式ドキュメントでは「reasoning」とも呼ばれる。多くのチャット UI では独立した「思考プロセス」として表示され、ユーザーが任意で覗ける。
  • 効果とトレードオフ:より複雑なタスクをより高精度でこなせるようになる一方、思考フェーズで生成されたトークンにも課金され、レイテンシも増える。「知能↑=コスト↑+遅延↑」。
  • いつ有効にするかの判断基準はシンプル:prompt evals に頼る。プロンプトを書いて eval を回し、精度が目標に届かず、かつプロンプト改善に十分な労力を既に費やしたとき、初めて extended thinking の有効化を検討する。
  • 有効化するとレスポンスに新しいブロックタイプ thinking block が含まれる。中身は Claude が思考中に生成したテキスト+ signature
  • signature=思考テキストに紐づく暗号トークン(cryptographic token)。そのメッセージを後で会話履歴として Claude に送り返すとき、thinking テキストが一切改変されていないことを検証するために使われる。thinking テキストは応答生成に強く依存されるため、開発者が改変できると Claude を安全でない方向へ誘導できてしまう——それを防ぐ仕組み。
  • redacted thinking(redacted_thinking ブロック):思考テキストが Claude の内部安全システムにフラグされた場合、思考テキストの代わりに data フィールド(=実際の思考テキストを完全暗号化したもの)だけが返る。開発者が読むためのものではなく、フォローアップのリクエストでそのまま Claude に返せば、以前の思考のコンテキストを失わないために提供される。
  • 実装(ノートブック 001_thinking.ipynb):chat() 関数に thinking=Falsethinking_budget=1024 の 2 引数を追加し、if thinking: のとき params に {"thinking": {"type": "enabled", "budget_tokens": thinking_budget}} を追加するだけ。
  • thinking_budget の最小値は 1024(それ未満は指定不可)。実際に Claude が 1024 トークンを使い切るとは限らない(あくまで指定可能な最小の budget)。
  • max_tokens は thinking budget より大きくなければならない。budget=1024 なら max_tokens は最低 1025——だが本文生成に 1 トークンしか残らないので、budget より大幅に大きい max_tokens を取るのが普通(講師は 4000 に増加:思考 ~1000+本文 ~3000 のバッファ)。
  • デモ:「write a one paragraph guide to recursion」を chat(messages, thinking=True) で実行 → 出力に ThinkingBlock(signature+thinking)→ TextBlock の 2 ブロック(モデルは claude-3-7-sonnet-20250219)。
  • redacted thinking をテスト用に強制発生させる方法:特殊フォーマットのマジック文字列を user message で送ると、必ず redacted thinking block が返る。アプリがこの種の予期しないブロックでクラッシュしないかを確認する用途。
  • マジック文字列:ANTHROPIC_MAGIC_STRING_TRIGGER_REDACTED_THINKING_46C9A13E193C177646C7398A98432ECCCE4C1253D5E2D82641AC0E52CC2876CB

実務ポイント(このレッスンから持ち帰るもの)

  1. 有効化の判断フロー:①プロンプトを書く → ②eval を回す → ③精度不足 かつ プロンプト改善を十分やり切った → そこで初めて extended thinking を検討。最初から思考をオンにしない(コスト・レイテンシが常時増えるため)。
  2. API パラメータ雛形(chat 関数への組み込み): python def chat(messages, ..., thinking=False, thinking_budget=1024): params = { "model": model, "max_tokens": 4000, # thinking_budget より十分大きく "messages": messages, ... } if thinking: params["thinking"] = { "type": "enabled", "budget_tokens": thinking_budget, }
  3. 落とし穴①budget_tokens の最小値は 1024落とし穴②max_tokens > budget_tokens が必須で、budget=1024・max_tokens=1025 だと本文に 1 トークンしか残らない。本文分を見込んで max_tokens を大きく(例:思考 1024+本文 ~3000 → max_tokens 4000)。
  4. thinking block は改変禁止:会話履歴として送り返す際、thinking テキストを 1 文字でも変えると signature 検証に引っかかる。履歴管理コードでは thinking block をそのまま保持・返送する。
  5. redacted_thinking のハンドリングを必ず実装data(暗号化思考)+type='redacted_thinking' だけのブロックが来てもクラッシュしない設計に。中身をパースしようとしない・捨てずにそのまま返送する。
  6. テスト手順:マジック文字列 ANTHROPIC_MAGIC_STRING_TRIGGER_REDACTED_THINKING_46C9A13E193C177646C7398A98432ECCCE4C1253D5E2D82641AC0E52CC2876CB を user message として送信 → redacted thinking block が保証付きで返る → 自アプリの応答処理が正常に通ることを確認。

本文(時系列・日本語逐語+画面差し込み)

🖥 [0:00] 画面: スライド「Extended ThinkingGives Claude better reasoning capabilities for more complex tasks. - Often referred to as "reasoning" in docs - Increased intelligence, cost, and latency - Use evals to decide if you need extended thinking

右側にチャット UI のモック「Closing Delimiters in Partial JSON Streams」。「User Query」(In one paragraph, describe a strategy for closing off all delimeters in a partially streamed JSON object.)→「Thinking / Reasoning」(Thought process・3s:The human is asking me to describe a strategy for closing off all delimiters in a partially streamed JSON object in one paragraph. I should first understand what this means: 1. A partially streamed JSON object is a JSON object that has been transmitted or processed incompletely …)→「Response」(To properly close all delimiters in a partially streamed JSON object, implement a stack-based tracking system …)の 3 段ラベル付き。

[0:00] Claude のより高度な機能のひとつ、extended thinking(拡張思考)と呼ばれるものを見ていきましょう。

[0:05] Extended thinking は、最終的な応答を生成する前に、ユーザーのクエリについて推論する時間を Claude に与えます。

[0:10] 多くのチャット UI では、これは独立した思考プロセスとして表示され、ユーザーは任意でそれを見て、Claude が自分の問題にどうアプローチしているのかをより深く知ることができます。

[0:20] さて、一般論として、extended thinking を有効にすると、Claude はより複雑なタスクにより高い精度で取り組めるようになります。ですが、ここには大きなトレードオフがあります。

[0:28] 思考フェーズ中に Claude が生成したトークンにも課金されますし、そのフェーズ自体の完了にもある程度の時間がかかります。

[0:35] つまり、知能の向上にはコストの増加が伴い、さらにレイテンシの増加も伴うのです。

[0:41] さて、extended thinking にまつわるよくある悩みが、いつ有効にするかの判断です。その答えは実にシンプルです。prompt evals(プロンプト評価)に頼るのです。

[0:49] つまり、プロンプトを書き出し、それに対して eval を実行します。そして精度が望むレベルに達しておらず、しかもプロンプト自体の改善にすでに相応の労力を費やしているなら、

[1:00] そのときこそが、extended thinking の有効化を検討したいタイミングです。

🖥 [1:00] 画面: スライド「Without Thinking Enabled」— 「Our Server」と「Claude」の間のシーケンス図。 行き(→):User Message|Text Block|Write a short guide on recursion 戻り(←):Assistant Message|Text Block|Recursion is a powerful programming technique…

[1:04] extended thinking の使い方はとても簡単です。思い出してください。通常 Claude を使うときは、text block を含んでいるであろう user message を送り、そして同じく text block を含む assistant message が返ってきます。

[1:14] extended thinking を有効にし始めると、返ってくるレスポンスには、これまで見たことのない新しいブロックタイプ、thinking block と呼ばれるものが含まれるようになります。

[1:24] この thinking block の中には、Claude が思考している間に生成されたテキストが入ることになります。

[1:28] この thinking block に関して、すぐにお見せしたいとても興味深い点があります。というのも、extended thinking をオンにしてコードを書き、リクエストを送り始めたら、すぐにそれを目にすることになるからです。

🖥 [1:30] 画面: スライド「The Signature」 - Cryptographic token tied to the thinking text that Claude generated - Verifies that the exact text was generated by Claude and was not changed

右側「Assistant Message」の JSON("signature" の行が枠でハイライト): json { "content": [ { "type": "thinking", "thinking": "The user is asking me to...", "signature": "EqoBCkgIAhABGAIiQDbvHj8yA…" }, { "type": "text", "text": "A Guide to Recursion\n\n" } ] }

[1:36] 右側には、thinking block と text block を含む assistant message の例があります。thinking block の中を見ると、signature と呼ばれるものがあることに気づくでしょう。

[1:45] signature は暗号トークン(cryptographic token)で、その役割はこうです。このメッセージを、将来の会話の一部として Claude に送り返したいとき、Claude は、あなたが thinking block 内のテキストを一切改変していないことを確認したいのです。

[1:59] signature は、あなたがそのテキストを変更していないことを確認するために使われます。Claude はそのテキストを一切変更してほしくありません。なぜなら、それは応答生成の際に非常に強く依存されるものだからです。

[2:10] そして、もし開発者がそのテキストを改変することを許されていたら、Claude を安全でない方向に誘導できてしまうかもしれないのです。

[2:14] この考えに関連して、thinking にはもうひとつの側面があります。

🖥 [2:20] 画面: スライド「Redacted Thinking」 - Reasoning text might be flagged by Claude's safety systems - The 'redacted_thinking' contains the actual thinking text, but in an encrypted form - Not for developer's use - only provided so you can hand it back to Claude on follow up requests

右側「Assistant Message」の JSON(redacted_thinking ブロックが枠でハイライト): json { "content": [ { "type": "redacted_thinking", "data": "kgIAh/ABGAIiQDbvHj8yAQDbvHj8yA…" }, { "type": "text", "text": "A Guide to Recursion\n\n" } ] }

[2:20] 場合によっては、thinking テキストがまったく無く、redacted content というフィールドを持った thinking block が返ってくることがあります。

[2:25] これは、Claude が生成した何らかの思考テキストが、何らかの内部安全システムによってフラグ付けされた場合に必ず発生します。

[2:30] redacted content は実際の思考テキストそのものですが、完全に暗号化された形になっています。

[2:36] それがあなたに提供されるのは、将来この完全なメッセージを会話の一部として Claude に返せるようにするためです。そうすれば、Claude は自分の以前の思考に関するコンテキストを一切失わずに済みます。

[2:45] extended thinking を本当に理解するには、少しコードを書き出す必要があります。では、Jupyter ノートブックに戻りましょう。

🖥 [2:50] 画面: スライドから VS Code(001_thinking.ipynb — 08)へ画面遷移。

[2:51] 001 thinking という新しいノートブックを作りました。例によって、このコースを通してずっと扱ってきたコードとよく似たコードが多く含まれていますが、1 つか 2 つ、とても特別なものを追加してあります。

[3:00] ですので、このノートブックを必ずダウンロードすることをおすすめします。この講義に添付されています。

🖥 [3:00] 画面: ノートブック「# Helper functions」セクション、chat 関数(変更前): ```python def chat( messages, system=None, temperature=1.0, stop_sequences=[], tools=None, ): params = { "model": model, "max_tokens": 1000, "messages": messages, "temperature": temperature, "stop_sequences": stop_sequences, }

if tools:
    params["tools"] = tools

```

[3:04] thinking を有効にするには、私たちの chat 関数を見つける必要があります。chat 関数にいくつか追加の引数を与え、それらを最終的にこの params オブジェクトへ追加していきます。

[3:13] そこで、デフォルト値 False の thinking と、それから thinking_budget(thinking アンダースコア budget)を 1024 で追加します。

🖥 [3:30] 画面: chat 関数のシグネチャに 2 引数が追加され、1024 がハイライトされている: python def chat( messages, system=None, temperature=1.0, stop_sequences=[], tools=None, thinking=False, thinking_budget=1024, ): (この時点で params の "max_tokens" はまだ 1000

[3:23] thinking budget とは、レスポンスの思考部分の生成に Claude が使ってよいトークン数です。

[3:29] ここでの最小値は 1024 です。つまり、1024 未満の thinking budget を持つことはできません。

[3:35] Claude が思考に 1024 トークンを使い切らない可能性も十分にあります。ですが繰り返しますが、これが budget として指定できる最小値です。

[3:43] thinking budget について、もうひとつ理解しておく必要のある非常に重要な事実があります。それは、max tokens は thinking budget より大きくなければならない、ということです。

[3:51] 例えば、thinking budget が 1024 なら、max tokens は最低でも 1025 でなければなりません。

[3:57] そしてその場合、実際にテキストの生成に充てられるトークンは 1 つしか残りません。

[4:01] ですから通常は、thinking budget よりも全体的にかなり大きな max tokens 値を持ちたくなるはずです。

[4:07] そこで私の場合は、ここで max tokens を 4,000 に増やします。これでかなり大きなバッファができました。

🖥 [4:00] 画面: params 内の該当行を選択ハイライトして変更: python "max_tokens": 4000,

[4:13] これで、1,000 トークンを思考に割り当て、残りの 3,000 トークンを実際のテキスト生成に割り当てたレスポンスを生成できるようになりました。

[4:24] この 2 つのキーワード引数を追加したら、次に params 辞書へ追加のパラメータを足していきます。

🖥 [4:30] 画面: params 定義の下に thinking の分岐を入力中(params["thinking"] = { の中身をタイプしている途中): ```python if thinking: params["thinking"] = { "" }

if tools: params["tools"] = tools

if system: params["system"] = system ```

[4:30] 少し下にスクロールして、この下の部分で、「thinking が有効なら、params 辞書に新しいキー、具体的には thinking を追加したい」と書きます。

[4:36] これは、type が enabled で、budget_tokens が thinking budget として渡された値になっている、ネストされた辞書です。こんな感じです。

[4:52] 以上です。ではセルを実行します。それから一番下までスクロールして、これをテストしてみましょう。

🖥 [5:03] 画面: 最下部のセルにリクエストを記述(セル [8]・実行 6.6s): ```python add_user_message( messages, "Write a one paragraph guide to recursion.", )

chat(messages, thinking=True) ```

[4:59] Claude に、再帰(recursion)についての 1 段落のガイドを書き出すよう頼むことにします。

[5:04] それから、ここにある chat 関数呼び出しを忘れずに更新し、thinking True を渡して thinking を有効にします。それから実行して、どうなるか見てみましょう。はい、これが私たちのレスポンスです。

🖥 [5:15] 画面: 実行出力。ThinkingBlock(signature+thinking テキスト)→ TextBlock の 2 ブロック構成: Message(id='msg_01F5xRi4tcXYzU5Du5HZ9Ebe', content= [ThinkingBlock(signature='ErUBCkYIAxgCIkCpwGVATdzIICjceeosHX0/LPB42Isr1KVS1SlGigGq10Oc15B3s8E5…(暗号トークンが数行続く)…', thinking="I need to write a one-paragraph guide to recursion. This should be concise but comprehensive, explaining what recursion is, how it works, and why it's useful in programming or other contexts.\n\nKey elements to include:\n- Definition of recursion (a process calling itself)\n- The need for a base case to prevent infinite recursion\n- The concept of breaking down complex problems into simpler versions of the same problem\n- Practical applications or benefits\n- Perhaps a simple illustrative example\n\nI'll craft a single cohesive paragraph that covers these points clearly.", type='thinking'), TextBlock(citations=None, text='# A Guide to Recursion\n\nRecursion is a powerful programming concept where a function calls itself to solve a complex problem by breaking it down into simpler instances of the same problem. Like a set of Russian nesting dolls, each recursive call works with a smaller version of the original problem until reaching what\'s known as a "base case"—a condition simple enough to solve directly without further recursion. This base case is crucial; without it, the function would call itself indefinitely, creating an infinite loop. The elegance of recursion lies in its ability to express solutions to complex problems in remarkably concise code, particularly for tasks with inherent recursive structures like tree traversals, factorial calculations, or the Fibonacci sequence. While sometimes less efficient than iterative approaches due to the overhead of multiple function calls, recursion often provides more intuitive and readable solutions to problems that would otherwise require complicated looping logic.', type='text')], model='claude-3-7-sonnet-20250219', role='assistant', …

[5:15] レスポンスの中に、2 つの別々のブロックが得られています。まず私の thinking block があり、そしてその少し下、ちょうどそこに、text block の始まりがあります。

[5:22] thinking block の中には、実際に signature があり、それと一緒に thinking テキストがあります。signature の目的が、thinking テキストに一切手を加えたり改竄(かいざん)したりしていないことを確認することだったのを思い出してください。

[5:33] そしてもちろん、ここにある text block には、Claude に書くよう頼んだ実際のガイドが入っています。

🖥 [5:30] 画面: スライド「Redacted Thinking」に一度戻る(内容は [2:20] と同じ。redacted_thinking ブロックの JSON を再確認)。

[5:36] さて、最後にお見せしたいのは、アプリケーションを最初に構築してテストしているときに使うかもしれないものです。

[5:42] 思い出してほしいのですが、Claude が redacted thinking block を返してくるシナリオがあり得ます。アプリケーションを構築していく中で、redacted thinking block が送られてきたときにも自分のコードが正しく動作することを確認したくなるかもしれません。

[5:54] 実は、Claude に redacted thinking block を強制的に返させることができるのです。必要なのは、非常に非常に特殊なフォーマットの文字列を含むメッセージを送り込むことだけです。

🖥 [6:00] 画面: ノートブック上部の 2 番目のセル(ズーム表示・ハイライト選択中)。redacted thinking 用のテスト文字列 thinking_test_str の定義。画面に映る値: ```python

... redacted thinking

thinking_test_str = "ANTHROPIC_MAGIC_STRING_TRIGGER_REDACTED_THINKING_46C9A13E193C177646C7398A98432ECCCE4C1253D5E2D82641AC0E52CC2876CB" `` (同セルの下方には… message.content if isinstance(message, Message) else message,` などのヘルパー関数のコードが見えている)

[6:02] 2 番目のセルの一番上までスクロールすると、この中に thinking test string というものを入れてあるのが分かります。その値は anthropic magic string trigger redacted thinking で、その後ろに特殊な数字と文字がたくさん続きます。

[6:16] まさにこの文字列をそのまま Claude に送り込むと、必ず redacted thinking block が返ってくることが保証されます。

[6:21] 繰り返しますが、これはあくまでテスト目的で、この種のレスポンスを処理できることを確認するためだけに行うものです。では、これを手短にお見せしましょう。

[6:30] 一番下のセルに戻ります。

🖥 [6:30] 画面: 最下部のセル [9](実行 6.2s): ```python add_user_message( messages, thinking_test_str )

chat(messages, thinking=True) 出力は RedactedThinkingBlock(`data` と `type` のみ)+ TextBlock: Message(id='msg_0183vZe2HBzE62vptCvwV7sZ', content= [RedactedThinkingBlock(data='EtCGCkYIAxgCKkA5ARFLpCpwMLfr/Yay7pBreer2GmgGShPJOy+XL0GXbOIadqOPU SauVM++DQfK8wqs3t1zTG10GDBFe0iC7OcUEgx5Aqyir9dh1FHGsrQaDICozuiFF40ZkXmIHCIwbz+GLy+Sk5503ESJLS2 …(暗号化データが延々と続く)… Y++DHKXXHyNwH9rC1z8l75Tt9fmyYUhOuxvG4CJBM14D9GAE=', type='redacted_thinking'), TextBlock(citations=None, text='I notice you\'ve entered what appears to be some kind of… ```

[6:33] その thinking test string だけを含む user message を追加します。これを送信します。

[6:39] これで、redacted thinking block を中に含んだレスポンスが返ってくるはずです。

[6:45] ほら、来ました。

[6:46] redacted thinking block が返ってきていて、そこには data と、redacted thinking という type 以外は何もありません。

[6:52] さて、先ほど言ったとおり、これを使えば、この種の予期しない thinking block をレスポンスとして受け取ったときにアプリケーションがクラッシュしないことを、きちんと確認できるわけです。

英語逐語(ASR全文)

Let's examine one of Claude's more advanced features, known as extended thinking. Extended thinking gives Claude time to reason about the user's query before generating a final response. In many chat UIs, this will be displayed as a separate thinking process, which the user can optionally look at to get a better idea of how Claude is approaching their problem.

Now, in general, enabling extended thinking will allow Claude to tackle more complex tasks with greater accuracy, but there are some big trade-offs here. You are charged for tokens generated by Claude during the thinking phase, and the phase itself takes some amount of time to complete. So with increased intelligence comes increased cost, and there's increased latency as well.

Now a common issue around extended thinking is deciding when to enable it. And the answer to this is really simple. You're going to rely on your prompt evals. So you're going to write out a prompt, you're going to run an eval on it, and if the accuracy is not where you want it to be, and you've already spent some good amount of effort on improving your prompt in the first place, that is when you would want to consider enabling extended thinking.

The use of extended thinking is fairly straightforward. Remember, when we normally use Claude, we send over a user message that might contain a text block, and then we're going to get back an assistant message that contains a text block as well. When we start to enable extended thinking, the response we get back is going to contain a new block type that we have not seen before, called a thinking block. Inside of this thinking block is going to be the text that was generated while Claude was thinking.

There's something really interesting around this thinking block that I want to show you right away because you're going to see it really quickly as soon as you start writing out some code and making requests with extended thinking turned on. On the right hand side I've got an example of an assistant message that includes a thinking block and a text block. Inside the thinking block you'll notice that there is something called a signature. The signature is a cryptographic token and here's what it does. If you want to take this message and send it back to Claude as a piece of the conversation in the future, Claude wants to make sure that you did not modify the text in the thinking block in any way. The signature is used to make sure that you did not change the text. Claude doesn't want you changing that text at all because it is relied upon very heavily during response generation. And if developers were allowed to modify that text, they could possibly steer Claude in an unsafe direction.

There's one other aspect of thinking related to this idea. In some cases, you might get back a thinking block that has a field of redacted content without any thinking text at all. This occurs whenever Claude generates some thinking text that gets flagged by some internal safety system. The redacted content is the actual thinking text, but in a fully encrypted form. It is provided to you so you can give this full message back to Claude in the future as part of a conversation without Claude losing any context on his previous thinking.

To really understand extended thinking, we need to write out a little bit of code. So let's go back over to our Jupyter notebooks. I've made a new notebook called 001 thinking. Once again, it has a lot of the similar code that we've been working on throughout the course, but I've added one or two very special things. So I would encourage you to make sure you download this notebook. It is attached to this lecture.

In order to enable thinking, we need to find our chat function. We're going to provide some additional arguments to the chat function that will eventually be added into this params object. So I'm going to add in thinking with a default value of false and then a thinking underscore budget of 1024. The thinking budget is the number of tokens that we want to allow Claude to use in generating a thinking portion of the response. The minimum value here is 1024, so we cannot have a thinking budget less than 1024. It's entirely possible that Claude will not spend 1024 tokens on thinking, But again, this is the minimum that we can specify as the budget.

There's one other very important fact you need to understand around the thinking budget. And that is that max tokens must be greater than your thinking budget. So for example, if we have a thinking budget of 1024, max tokens must be at least 1025. And that's only going to leave one token remaining to actually apply to generating some text. So usually you are going to want to have a max tokens value that is generally significantly larger than your thinking budget. So in my case, I'm going to increase my max tokens here to 4,000. And now I've got a pretty big buffer. This means that I can generate a response that has 1,000 tokens allocated to thinking, and then a remaining 3,000 tokens can be allocated to actually generating some text.

Once we have added in these two keyword arguments, we're then going to add in some additional parameters to the params dictionary. So I'll scroll down a little bit and down here, I'll say if thinking is enabled, that I want to add in a new key to my params dictionary, specifically thinking. This is going to be a nested dictionary with a type of enabled and a budget tokens of whatever we passed in as the thinking budget, like so. And that's it. So I'm going to run the cell. I'll then scroll down to the bottom so we can test this out.

I'm going to ask Claude to write out a one paragraph guide to recursion. I'll then make sure that I update my chat function call right here to enable thinking by passing in thinking true. I'll then run this and let's see how we do. And here's our response. So in the response, I get two separate blocks. First, I have my thinking block and then a little bit lower down right there is the start of my text block. Inside the thinking block, I do in fact have a signature and along with it, I've got my thinking text. Remember the goal of the signature is to make sure that we don't tap or tamper with the thinking text in any way. And of course, our text block right here contains the actual guide that we asked Claude to write.

Now the last thing I want to show you is something that you might use when you are initially building and testing out your application. As a reminder, there might be some scenarios where Claude sends back a redacted thinking block. As you are building out your application, you might want to make sure that your code works correctly whenever a redacted thinking block is sent to you. So we can actually force Claude to send us back a redacted thinking block. All we have to do is send in a message that includes a very, very specially formatted string. So if you scroll up to the very top of the second cell inside of here, you'll notice I put in thinking test string, and then it has a value of anthropic magic string triggered redacted thinking(※ASR注:正しくは trigger。画面の文字列は ANTHROPIC_MAGIC_STRING_TRIGGERREDACTED_THINKING…), and then a bunch of special numbers and letters after it. If you send exactly this string into Claude, you are guaranteed to get back a redacted thinking block. And again, we would just do this for testing purposes to make sure that we can handle that kind of response.

So let me just show this to you really quickly. I'm going to go back down to the bottom cell. I'm going to add in a user message that contains just that thinking test string. I'll then send this in. And now we should be getting back a response that's going to have a redacted thinking block inside of it. There we go. So I've got my redacted thinking block and it has nothing but a data and a type of redacted thinking. So now, like I said, we could just use this to make sure that our application doesn't crash when receiving this kind of unexpected thinking block in response.

↑ 目次へ戻る

Building with the Claude API | Lesson 47「Image support」(全文ナレッジ)

動画 10分15秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

  • 本レッスンは Claude の vision(画像入力) 機能。user message の中に image block(画像ブロック) を入れて画像を送り、内容説明・比較・カウントなど何でも依頼できる。
  • 制約・要件(スライド「Image Handling」より):
  • 1リクエスト内の全メッセージ合計で最大100枚まで
  • 1枚の最大サイズ 5MB
  • 1枚だけ送るとき: 高さ/幅の上限 8000px
  • 複数枚送るとき: 高さ/幅の上限 2000px
  • 画像は base64 エンコードか、オンラインにホストされた画像への URL で渡せる
  • 画像は入力トークンとして課金される。概算式: tokens = (width px × height px) / 750
  • image block の構造: {"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": image_bytes}}。1つの user message に複数の image block を入れられ、各ブロックは画像1枚への参照を持つ。base64 文字列は base64.standard_b64encode(f.read()).decode("utf-8") で作る。
  • 画像でも決め手はプロンプト技法。ビー玉12個の画像に「How many marbles are in this image?」と単純に聞くと 13個と誤答(講師が実際にテスト)。改善策は本コース前半で学んだものと同じ:
  • 分析ステップの列挙(1個ずつ番号を振って数える→別の方法で再カウント→2つの結果を照合)→ 正しく12個
  • one-shot / multi-shot: user message 内で画像パートとテキストパートを交互に並べ、「上の画像には11個のビー玉があります」という例示ペアを先に与える → 正答
  • ユースケース=火災保険の樹木検査(fire risk assessment): 米国では山火事リスクのため、保険会社が家の周囲の樹木の剪定・伐採を契約者に求めるが、検査員の巡回は高コスト。高解像度の最新衛星画像を Claude に渡してリスク評価を自動化する。
  • 評価プロンプトは5ステップの詳細版(単純な「fire risk score を出して」ではほぼ確実に良い結果にならない): 1. 主建物(primary residence)の特定(最大の屋根構造・私道接続・車庫/物置/プールとの区別) 2. 樹木の張り出し(tree overhang)分析(屋根を覆う樹冠、被覆率 0–25%/25–50%/50–75%/75%+) 3. 火災リスク評価(火の粉の着火点、構造物への連続的な燃料経路、煙突・通気口への近接、「橋」になる枝) 4. defensible space(防御可能空間)の特定(連続樹冠、fuel ladder=地面→木→屋根と火を運ぶ植生) 5. Fire Risk Rating 1〜4 の付与(1=Low: 張り出しなし / 2=Moderate: <25% / 3=High: 25–50%+樹冠連結 / 4=Severe: >50%+構造物に接する濃い植生)— 各項目1文サマリ+最終スコアを出力
  • 実装は新ノートブック 002_images.ipynb。講義添付の images.zip を展開して images/ をノートブックと同じフォルダに置く(prop1.png〜prop7.png の衛星画像)。prop7.png(家が完全に木々に囲まれている物件)を base64 で読み、image block + text block(5ステップのプロンプト) の2ブロック構成 user message で送信。
  • 実行結果: Fire risk rating 3(High Risk)。応答は model='claude-3-7-sonnet-20250219'input_tokens=2041, output_tokens=273(画像1枚でこの入力トークン量になる実測例)。
  • 締めのメッセージ: 画像で良い結果を得られるかは、すべてプロンプト技法にかかっている。テキストで学んだ改善手法はそのまま画像の世界にも適用される。単純プロンプトに頼らず、よく練り・よく評価したプロンプトを使うこと。

実務ポイント(このレッスンから持ち帰るもの)

  1. 画像送信の雛形コード(base64 方式・スライドの画面どおり): ```python with open("image.png", "rb") as f: image_bytes = base64.standard_b64encode( f.read() ).decode("utf-8")

add_user_message(messages, [ # Image Block { "type": "image", "source": { "type": "base64", "media_type": "image/png", "data": image_bytes, }, }, # Text Block { "type": "text", "text": "What do you see in this image?" } ]) `` base64 は**バイト列のまま渡さず**.decode("utf-8")で文字列化する。media_typeは実ファイル形式(image/png等)と一致させる。 2. **数値制約を設計時にチェック**: 100枚/リクエスト、5MB/枚、1枚なら8000px・複数なら2000px。バッチで複数画像を送るパイプラインでは 2000px 制限に合わせた事前リサイズが必要。 3. **コスト概算式を見積りに使う**:tokens ≒ (width × height) / 750`。例: 講義の prop7.png 1枚+5ステップのプロンプトで input_tokens=2041。画像枚数×解像度でコストが線形に膨らむ前提で設計する。 4. 単純プロンプト+画像は誤答する前提で作る(12個→13個の実例)。対策は通常のプロンプトエンジニアリングと同じ: ガイドライン提示・分析ステップの列挙one-shot / multi-shot。 5. カウント系タスクの改善パターン(そのまま流用可能): ①1個ずつ番号を振って数える → ②別の走査順(左下から行ごと)で再カウント → ③2つの結果を照合して確定値を答えさせる。※ステップ列挙は「画像に何が写っているか事前に分かっている」場合に有効。 6. multi-shot は画像とテキストを交互に並べる: 「例の画像 → 『上の画像には11個』→ 本番の画像 → 質問」。画像を使った few-shot は image block / text block の交互配置で表現する。 7. 評価タスクはルーブリック化して出力を構造化: リスク基準(1〜4)を閾値つきで定義し、「各項目1文サマリ+最終スコア」と出力形式まで指定する。fire risk のプロンプトは保険・不動産・設備点検系の画像評価にほぼそのまま転用できる型。 8. 落とし穴: 画像の内容が悪い(判読しづらい)のではなくプロンプトが単純すぎることが精度低下の主因になりがち。うまくいかないときはまずプロンプトに分析手順と判断基準を足す。

本文(時系列・日本語逐語+画面差し込み)

🖥 [0:00] 画面: スライド「Image Handling」。左に箇条書き: - Up to 100 images across all messages in a single request - Max size of 5MB - When sending one image max height/width of 8000px - When sending multiple images, max height/width of 2000px - Images can be included in base64 encoding, or a URL to the image - Each image counts as a certain number of tokens: tokens = (width px × height px) / 750

右にコードパネル: ```python with open("image.png", "rb") as f: image_bytes = base64.standard_b64encode( f.read() ).decode("utf-8")

add_user_message(messages, [ # Image Block { "type": "image", "source": { "type": "base64", "media_type": "image/png", "data": image_bytes, } }, # Text Block { "type": "text" "text": "What do you see in this image?" } ]) ```

[0:00] 次に見ていく Claude の高度な機能は、Claude のビジョン(vision)機能です。

[0:05] user message を Claude に送るとき、そのメッセージの中に画像を任意で含めることができます。

[0:09] そして、その画像について、皆さんが想像できるほとんど何でも Claude に頼むことができます。

[0:13] たとえば、画像の中に何が写っているかを教えてもらうことができます。複数の画像を比較してもらうこともできます。

[0:18] 画像内のオブジェクトの数を数えてもらうこともできます。本当に、たくさんの可能性があります。

[0:22] 画像の扱いについて最初に理解してほしいのは、いくつかの制約、あるいは要件です。

[0:26] 1つのリクエストの中の全メッセージを合わせて、最大100枚まで画像を送ることができます。

[0:32] 各画像のサイズ、そして高さと幅にもいくつか制限があります。

[0:37] そして最後に理解しておくべきなのは、Claude に画像を送るたびに、それが何トークンかとしてカウントされ、その分が課金されるということです。

[0:44] 課金されるトークン数を、画像の高さと幅(ピクセル単位)からおおまかに計算できる式があります。

🖥 [0:53] 画面: シーケンス図。左「Our Server」・右「Claude」。上段の右向き矢印に User Message = [Image Block | \<Raw Image Data>] + [Text Block | What do you see in this image?]。下段の左向き矢印に Assistant Message = [Text Block | I see a tall tree...]。

[0:48] Claude に画像を送るには、user message の中にまた別の種類のブロックを含めることになります。これが image block(画像ブロック)です。

[0:59] 1つの user message の中に、複数の異なる image block を添付できます。各 image block は、1枚の画像への参照を保持します。

🖥 [1:11] 画面: スライド「Image Handling」に戻る。右のコードパネルはこの時点では簡略版: ```python with open("image.png", "rb") as f: image_bytes = f.read()

add_user_message(messages, [ # Image Block { "type": "image", "source": { "type": "base64", "media_type": "image/png", "data": image_bytes, } }, # Text Block {"text": "What do you see in this image?"} ]) ``` (=生のバイト列を読むだけの表示状態。直後 [1:14] には再びシーケンス図に切り替わる)

[1:04] この image block の中には、生の画像データ(raw image data)を添付できます。これは右側のこの図で示したものです。

[1:13] あるいはその代わりに、オンラインのどこかにホストされている画像への URL を指定することもできます。

[1:18] さて、技術的な制限と画像の送り方が分かったところで、すぐに取り上げておきたい、とても重要なことがあります。

🖥 [1:30] 画面: スライド「Prompting Techniques」。左の箇条書き: - All the same prompting engineering techniques apply to images - You can increase Claude's vision accuracy by providing guidelines, analysis steps, or by using one-shot/multi-shot examples

右に会話図。上部注記「Image has 12 marbles」。User Message = ビー玉12個の写真 + How many marbles are in this image? → Claude。返答 Assistant Message = I count 13 marbles in this image に赤い ✗ マーク。

[1:22] エンジニアが Claude で画像を使い始めるとき、よく見かけるのですが、非常に単純なプロンプトを使い始めることが多いんです。ちょうど、この例に出しているようなプロンプトです。

[1:32] 画像を使うときに Claude から良い結果を引き出す一番の方法は、引き続きプロンプト技法(prompting techniques)に強くフォーカスし続けることです。

[1:42] ですから、画像を Claude にポンと投げて、非常に単純なプロンプトを入れるだけだと、良い結果が返ってこないことがとても多いのです。

[1:48] 例として、右側の会話を見てください。私は12個のビー玉が写った画像を入れました。ちなみにこれは実際にテストしました。

[1:58] そして「この画像にはビー玉が何個ありますか?」と、ごく単純に尋ねました。すると案の定、13個という誤ったカウントが返ってきました。

[2:02] 画像を扱うときの Claude の精度は、このコースの前の方ですでに学んだのと同じ種類のプロンプト技法を使うことで、劇的に向上させることができます。

[2:13] つまり、ガイドラインを与える、分析ステップを与える、あるいは one-shot やさらには multi-shot の例を使う、といった技法です。

[2:19] では、このプロンプトを簡単に強化して、実際に正しい結果を得られる2つの方法をお見せしましょう。

[2:24] 繰り返しますが、これも実際にテストして、少なくとも私の環境ではこれらの例が期待どおりに動くことを確認済みです。

🖥 [2:31] 画面: 会話図スライド。User Message = ビー玉12個の写真 + 以下のプロンプト全文:

Analyze this image of marbles and determine the exact count using this methodology: 1. Begin by identifying each unique marble one at a time. Assign each a number as you identify it. 2. Verify your result by counting with a different method. Start from the bottom-left corner and work row by row, from left to right.

What is the exact, verified number of marbles in this image?

返答 Assistant Message = I count 12 marbles in this image に緑の ✓ マーク。

[2:29] まず最初にできるのは、Claude が画像を分析する際にたどる一連のステップを与えることです。

[2:40] もちろんこれは、Claude に与える画像の内容を自分たちがある程度すでに把握している場合にしか、うまく機能しません。

[2:44] このシナリオでは、まず個々のビー玉を1つずつ特定して、1個ずつ数えるように Claude に頼みます。そして、最初のカウントを検証するために2回目のカウントをさせます。その際、ビー玉を数えるための別のメカニズム、別の戦略を与えます。

[3:00] そして最後に一番下で、「では、その2つのカウントを比較して、正しい答えが何かを突き止めよう」と頼みます。

[3:05] このように、より洗練されたプロンプトを与えることで、12個という正しいカウントの結果を得ることができました。

🖥 [3:28] 画面: multi-shot の会話図スライド。User Message の中身が上から順に: 1. 画像(ビー玉11個・小さめの写真) 2. テキスト The image above has 11 marbles in it. 3. 画像(ビー玉12個の写真) 4. テキスト How many marbles are in this image?

返答 Assistant Message = I count 12 marbles in this image に緑の ✓。

[3:16] ここで使えるもう1つの技法が、one-shot あるいは multi-shot プロンプティングです。仕組みはこうです。

[3:22] user message の中で、画像パートとテキストパートを交互に配置できます。

[3:28] このシナリオでは、上に画像パート、その下にテキストパート、さらにもう1枚の画像、そしてテキストパート、という並びになっています。

[3:33] 最初のペアでは、ビー玉が11個写った画像を与え、そのすぐ後に「上の画像にはビー玉が11個写っています」と、ごく平易に述べます。

[3:39] このような例を与えることで、後で Claude が本題の画像に取り組むときの精度を簡単に向上させられます。

[3:44] いつもどおり、この機能を Jupyter ノートブックの中で Claude を使ってテストしたいのですが、今回は少しだけ複雑な例になります。

[3:54] ノートブックの中でこれから何をするのか、そのシナリオを事前に理解しておいてほしいので、簡単な図をお見せします。

🖥 [4:02] 画面: スライド「Fire Risk Assessments」。左の箇条書き: - In some parts of the US, home fire insurance companies requires homeowners to trim or remove trees around the residence - Sending an inspector out to each property would be expensive - Solution: get high resolution, up-to-date satellite imagery, ask Claude for a fire risk assessment

右に住宅の衛星写真。3本の赤い矢印ラベル: 「Dense, close packed trees」「Difficult access to residence」「Branches overhanging residence」。

[4:02] それでは、Claude の画像サポート機能をどう使えるか、サンプルのユースケースがこちらです。

[4:08] ご存じないかもしれませんが、米国の多くの地域では山火事(wildfire)の問題が非常に深刻で、山火事が発生すると地域を焼き払い、大量の家を燃やしてしまいます。

[4:21] これはよくあるリスクなので、多くの人が、家が焼失した場合に備えて火災保険で家を保険にかけたいと考えます。

[4:25] しかし保険会社側は、家というものは明日にも、来年にも、ごく近いうちにも燃えてしまい得ることを十分に理解しています。

[4:31] そこで保険会社は、家を保険にかけたい住宅所有者に対して、家の周囲の木を剪定する、あるいは完全に伐採することを求めることがよくあります。

[4:43] さて、保険会社は、所有者が木を適切に管理しているかを実際に検証・確認する必要があります。

[4:48] しかしそれを検証するには、各物件に人を派遣して検査しなければならず、しかもその検査をおそらく1年に1回、あるいは2年に1回は行うことになるでしょう。それはあっという間に、非常に高くつきます。

[4:56] そこで、このプロセスを自動化する1つの方法が、高解像度で最新の衛星画像を入手し、それを Claude に与えて、火災リスク評価(fire risk assessment)を Claude に依頼することです。

[5:05] 具体的には、まず物件の主たる住居(main residence)を検出するように Claude に頼みます。つまり、物件の衛星画像の中から、保険対象と思われるメインの家を見つけるということです。

[5:15] そして、住居に張り出している(overhanging)木の枝がないかを見てもらいます。これは火災のとてもよくあるリスク要因です。

[5:26] また、消防サービス(fire services)が住居に実際に到達するのがどれくらい難しいかも見積もってもらいます。言い換えると、家に到達するための明確な経路があるかを確認するということです。

[5:31] さらに、家の周囲の木々を見て、密集しすぎ・詰まりすぎになっていないかも確認してもらいます。それ自体も火災リスクになり得るからです。

[5:41] それでは、ノートブックに移動して、これをどう実装できるか見ていきましょう。

🖥 [5:47] 画面: VS Code。ノートブック 002_images.ipynb(ウィンドウタイトル「002_images.ipynb — 08」、カーネル .venv (Python 3.9.6))。上部に折りたたまれたセル「# Load env variables and create client」「# Helper functions」。その下のセルに fire risk assessment プロンプト: ```python

Fire risk assessment prompt

prompt = """ Analyze the attached satellite image of a property with these specific steps:

  1. Residence identification: Locate the primary residence on the property by looking for: - The largest roofed structure - Typical residential features (driveway connection, regular geometry) - Distinction from other structures (garages, sheds, pools) Describe the residence's location relative to property boundaries and other features.

  2. Tree overhang analysis: Examine all trees near the primary residence: - Identify any trees whose canopy extends directly over any portion of the roof - Estimate the percentage of roof covered by overhanging branches (0-25%, 25-50%, 50-75%, 75… - Note particularly dense areas of overhang ``` (…は画面右端で見切れている部分)

[5:46] 私は 002 Images という新しいノートブックの中にいます。この中に、皆さんのためのスターター・プロンプトをすでに用意してあります。

[5:52] 注目してほしいのは、このプロンプトが非常に詳細で、画像の中で分析してほしいさまざまな観点・アイデアを Claude に順を追って示していることです。

[6:02] 「この物件の衛星画像に基づいて fire risk score を出してください」というような非常に単純なプロンプトを書いて、それで済ませることもできたでしょう。しかし、それではまず間違いなく良い結果は得られないと断言できます。

[6:11] そこで代わりに、以前学んだプロンプトエンジニアリングの技法をいくつか適用して、Claude がたどるべき一連の分析ステップを与えました。

🖥 [6:20] 画面: プロンプトセルを拡大スクロール。ステップ3まで表示: text 3. Fire risk assessment: For any overhanging trees, evaluate: - Potential wildfire vulnerability (ember catch points, continuous fuel paths to structure) - Proximity to chimneys, vents, or other roof openings if visible - Areas where branches create a "bridge" between wildland vegetation and the structure

[6:21] ステップ1、まず衛星写真の中から実際の主たる住居(primary residence)を見つける。

[6:27] ステップ2、樹木の密度(tree density)を見る。それから、消防サービスが物件へ実際にアクセスできるかを見る。

🖥 [6:35] 画面: さらにスクロール。ステップ4〜5と締めの指示(「5. Fire risk rating:」の行をマウスで選択ハイライト中): ```text 4. Defensible space identification: Assess the property's overall vegetative structure: - Identify if trees connect to form a continuous canopy over or near the home - Note any obvious fuel ladders (vegetation that can carry fire from ground to tree to roof)

  1. Fire risk rating: Based on your analysis, assign a Fire Risk Rating from 1-4: - Rating 1 (Low Risk): No tree branches overhanging the roof, good defensible space around t… - Rating 2 (Moderate Risk): Minimal overhang (<25% of roof), some separation between tree ca… - Rating 3 (High Risk): Significant overhang (25-50% of roof), connected tree canopies, mult… - Rating 4 (Severe Risk): Extensive overhang (>50% of roof), dense vegetation against struct…

For each item above (1-5), write one sentence summarizing your findings, with your final respon… """ ``` (各行の右端は画面外に見切れ)

[6:32] 何本の木が、より正確には何本の枝が屋根に張り出しているかを見る。これは非常によくある火災リスクです。

[6:38] そして最後に、これらすべての性質に基づいて fire risk rating を割り当てる。1、2、3、4 のどれにすべきかを判断する助けになる基準もいくつか与えています。

[6:49] そして一番最後の最下部で、各項目について1文のサマリを書き、最終スコアを添えるよう指示しています。

[6:54] これが私たちのプロンプトです。このセルを確実に実行しておきます。それから下の方に降りていって、サンプル画像を読み込み、このプロンプトと一緒に Claude に与えて、どんな結果が得られるかを見るコードを書いていきます……

🖥 [7:06] 画面: エクスプローラーを開いた状態。ツリーに images(フォルダ)、.env001_thinking.ipynb002_images.ipynb。プロンプトセルは実行済み([1] ✓ 0.0s)。その下に空のセル: ```python

TODO: Read image data, feed into Claude

```

[7:06] その前に、もう1つ手短な連絡です。このレクチャーに添付されている images.zip という zip アーカイブがあります。必ずそのアーカイブを展開して、images ディレクトリをノートブックと同じフォルダに置いてください。

[7:15] このフォルダには、周囲に何本かの木がある、さまざまな家の衛星画像がいくつか入っています。

🖥 [7:22] 画面: prop1.png をタブで開いた状態(エクスプローラーには prop1.png〜prop7.png が並ぶ)。衛星写真: 中央の住宅の屋根に複数の樹冠が大きく覆いかぶさり、家の南〜東側は特に木でほぼ覆われている。周囲は芝地。

[7:19] たとえば image one(prop1.png)は、間違いなくかなりの量の樹木の張り出し(tree overhang)がある家です。

🖥 [7:28] 画面: prop2.png のタブを開く(タブ列: 002_images.ipynb / prop1.png / prop2.png)。※実素材 prop2.png の内容: 大きな屋根の家とプールのある開けた敷地で、樹木は prop1 よりはるかに少ないが、家のすぐ左手(西側)に樹木が近接している。

[7:28] image two(prop2.png)は木が明らかにずっと少ないですが、それでもこの物件のすぐそば、ここに1本近い木があります。

🖥 [7:33] 画面: prop3.png のタブも開く(タブ列: 002_images.ipynb / prop1.png / prop2.png / prop3.png)。エクスプローラーで prop3.png を選択中。

[7:33] 残りも順に見ていって、「うん、確かに衛星画像が揃っているな」と確認してもらえればOKです。

[7:37] 私たちのゴールは、これらの画像を Claude に送り込み、それぞれについて fire score rating を得ることです。

🖥 [7:44] 画面: ノートブックに戻る。プロンプトセルは [5] ✓ 0.0s で実行済み。TODO セルにカーソル: ```python

TODO: Read image data, feed into Claude

```

[7:41] ノートブックに戻って、まず画像ファイルを開き、その内容を base64 に変換するところから始めます。

[7:46] with open を書きます。images ディレクトリの中の、image 7 を特に探します。

[7:52] この画像を特に使う理由は、これが完全に木々に囲まれているからです。

🖥 [8:05] 画面: エクスプローラーで prop7.png を選択(ウィンドウタイトル「prop7.png — 08」)。※実素材 prop7.png の内容: 敷地全体が濃い森に覆われ、中央の住宅(複数棟の灰色の屋根)が四方を高木で完全に包囲されている。屋根の縁まで樹冠が迫り、張り出しも見える。ノートブックのセルには入力途中のコード: ```python

TODO: Read image data, feed into Claude

with open("images/prop7.png", "rb") as f: ```

[7:58] これが prop7.png です。ご覧のとおり、火災の観点では間違いなく多くの問題を抱えています。

🖥 [8:00] 画面: コード入力中: ```python

TODO: Read image data, feed into Claude

with open("images/prop7.png", "rb") as f: image_bytes = base64.standard_b ```

[8:05] 画像のバイト列を base64 として取得します。standard_b64encode です(※ASR注:原文「standard underscore B64 and code」は standard_b64encode の誤認識)。

[8:17] そこに f.read を渡し、UTF-8 にデコードします。次に、空の messages リストを追加します。

🖥 [8:30] 画面: コードの骨格が完成: ```python

TODO: Read image data, feed into Claude

with open("images/prop7.png", "rb") as f: image_bytes = base64.standard_b64encode(f.read()).decode('utf-8')

messages = []

add_user_message(messages, [

]) ```

[8:26] そこに user message を追加していきます。今追加しているこのメッセージは、2つの別々のブロックを持つことになります。

[8:32] まず、ちょうどここに見えている構造そのままの image block を持ち、その後に text block を持ちます。

🖥 [8:44] 画面: ノートブックの右側に「Image Handling」スライドを並べて表示し、image block の構造("type": "image" / "source": {"type": "base64", "media_type": "image/png", "data": image_bytes}# Text Block)を参照しながら入力を続ける。

[8:36] そして text block の方には、Claude(※ASR注:原文「Collade」は Claude の誤認識)に与えたい実際の指示が入ります。

[8:40] まず、image block を表す辞書(dictionary)を追加します。

[8:45] つまり type は image で、source に割り当てるネストした辞書があり、その中は type が base64、media_type が image/png、そして data が base64 エンコードした画像バイト列になります。

[9:02] その辞書の後に、実際のプロンプトを追加します。

🖥 [9:00] 画面: プロンプトセル(# Fire risk assessment prompt / prompt = """ 〜)まで一度スクロールして、prompt 変数を確認。上部の「Load env variables / Helper functions」セルには実行済みの ✓。

[9:08] これには type として text を与え、送りたいプロンプト——あそこで prompt 変数に代入したもの——を使います。ですから text には prompt を指定します。

[9:23] よし、最後に chat を呼び出し、messages のリストを渡します。

[9:28] これを実行して、何が得られるか見てみましょう。

🖥 [9:30] 画面: 実行結果(セル [12]、実行時間 15.0s)。出力全文: text Message(id='msg_012fZJuCww8Mi4LYafXHv2on', content=[TextBlock(citations=None, text='## Satellite Image Property Analysis\n\n1. **Residence identification**: The primary residence appears to be a light-colored structure with an L-shaped or irregular roof layout, positioned centrally within the property and surrounded by dense vegetation on all sides.\n\n2. **Tree overhang analysis**: There are multiple trees with canopies that extend directly over portions of the roof, with approximately 25-50% of the roof area covered by overhanging branches, particularly along what appears to be the eastern and northern sides of the structure.\n\n3. **Fire risk assessment**: The overhanging trees create multiple potential ember catch points on the roof, and the dense vegetation surrounding the structure forms continuous fuel paths that could allow fire to spread directly to the building.\n\n4. **Defensible space identification**: The trees appear to connect to form a nearly continuous canopy over and around the home, with minimal visible breaks in vegetation, creating potential fuel ladders from the surrounding forest to the structure itself.\n\n5. **Fire risk rating**: 3 (High Risk) - The property shows significant tree overhang covering approximately 25-50% of the roof, connected tree canopies surrounding the structure, multiple points of vulnerability from overhanging branches, and limited defensible space between the forest and the building.', type='text')], model='claude-3-7-sonnet-20250219', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(cache_creation_input_tokens=0, cache_read_input_tokens=0, input_tokens=2041, output_tokens=273, server_tool_use=None)) 「3 (High Risk)」の箇所をマウスで選択ハイライト。

[9:30] レスポンスを見て、一番下までスクロールすると、fire risk rating が見えるはずです。

[9:35] 今回は fire risk は High、スコアで言うと 3 という結果になりました。

[9:39] メインの物件の周囲の木々をすべて評価して「うん、これはおそらく問題になるだろう」と判断したわけで、Claude はかなり妥当な仕事をしたと思います。

🖥 [10:00] 画面: 再びスライド「Prompting Techniques」(単純プロンプトでビー玉13個と誤答する例=赤い ✗ の図)を表示して締めの注意喚起。

[9:47] 先へ進む前に、画像に関して最後にもう1つ、念押ししておきたいことがあります。

[9:50] 画像を与えたときに Claude から良い結果を得られるかどうかは、すべてプロンプト技法にかかっています。

[9:54] プレーンテキストを使うときのプロンプト改善の方法をたくさん検討してきたのとまったく同じように、それらの技法は画像の世界にもそのまま適用されます。

[10:04] ですから、非常によく練られ、よく評価されたプロンプトを必ず用意することを強くお勧めします。

[10:08] なぜなら、今ここに入れているような単純なプロンプトに頼っていると、皆さんが期待するほどにはうまく機能しない可能性が高いからです。

英語逐語(ASR全文)

The next advanced capability of Claude that we are going to investigate is Claude's vision capabilities. Whenever we send a user message off to Claude, we can optionally include images inside of the message. We can then ask Claude to do just about anything you can possibly imagine with these images. So we could ask Claude to tell us what is contained inside them. We could ask Claude to compare different images. We can ask Claude to count different objects. Really, there's a lot of different possibilities here.

The first thing I want you to understand around image handling is some of the different restrictions or requirements. We can send up to a hundred images across all the messages inside of a single request. There are some limitations around the size of each image and the height and width as well. And finally, you need to understand that whenever you send an image off to Claude, that is going to count for some number of tokens that you are going to be charged for. There is an equation you can use to roughly calculate how many tokens you'll be charged for based upon the height and the width of the image in pixels.

To send an image off to Claude, we are going to include yet another type of block inside of a user message. This is an image block. We can attach multiple different image blocks inside of one single user message. Each image block is going to hold a reference to a single image. Inside this image block, we can attach either the raw image data, which is what I showed in this diagram over here on the right hand side, or alternatively, we can provide a URL to a image that is hosted somewhere online.

So now that we understand some of the technical limitations here and how we send the image off, there's something really important that I want to address right away. Whenever a engineer starts making use of images with Claude, well I notice very often they start using prompts that are very simple, even kind of like the prompt I've got in this example right here. The number one way to get good results out of Claude when you are making use of images is to continue to have a strong focus on prompting techniques. So if you just throw a image off to Claude and then put in a very simple prompt, very often you are not going to get back a good result. For example, consider the conversation on the right-hand side. I put in an image with 12 marbles. I actually tested this by the way, and I asked it very simply, how many marbles are in this image? And sure enough, I got back an incorrect count of 13.

We can dramatically increase Claude's accuracy when working with images by using the same kind of prompting techniques that we've already learned earlier on inside this course. So techniques like providing guidelines, providing analysis steps, or by using one shot or even multi-shot examples. So let me show you two ways in which we could very easily enhance this prompt and actually get back the correct result. And again, I actually tested this out and made sure that these examples, at least for me, worked as expected.

So the first thing we might do is provide a series of steps for Claude to go through in analyzing the image. Now, of course, this is only really going to work if we kind of already understand the content of the image that we're feeding into Claude. So in this scenario, I might ask Claude to first take a look and try to identify each individual marble and just count each of them one by one, and then ask it to recount a second time to verify the initial count and provide it a different mechanism or different strategy for counting the number of marbles. And then finally at the bottom, I ask, okay, now let's kind of compare those two different counts and figure out what the correct answer is. So by providing a more sophisticated prompt, I was able to get results with a correct count of 12 marbles.

Another technique we might use here is one shot or multi-shot prompting. So here's how that would work. Inside of my user message, I can alternate the presence of a image part and a text part. So in this scenario, I have a image part up here, a text part underneath it, and then another image, and then a text part. In the initial pair, I provide an image with 11 marbles and then say very plainly, the image above has 11 marbles inside of it. Providing an example like this can easily improve Claude's accuracy when it goes to tackle your image later on.

As usual, I would like to test out this feature in Claude inside of a Jupyter notebook, but this time around we're going to have a little bit more complicated example, and I want you to understand the scenario that we're going to walk through here inside of our notebook ahead of time by showing you a quick diagram.

All right, so here is a sample use case of how we might use Claude's image support capability. So in case you're not aware, in many parts of the United States, we have really bad wildfire problems where wildfire will begin, sweep through an area, and burn down a ton of houses. And because this is a very common risk, a lot of people want fire insurance to insure their home in case it gets burned down. But these insurers are very much aware that a house can absolutely be burned down tomorrow or next year or very shortly. So these insurers will very often require a homeowner who wants to insure their home to trim trees or even cut trees down entirely around their house. Now the insurer needs to actually verify and make sure that the homeowner is taking care of the trees appropriately. But to verify that, they might have to send out a person to inspect each property and probably do that inspection maybe once every year or once every two years. That would become expensive really, really quickly.

So one way that we could automate this process is by getting high resolution up-to-date satellite imagery and then feed it into Claude and ask Claude for a fire risk assessment. We might ask Claude in particular to try to detect the main residence on the property. So in other words, inside of a satellite image of a property, find the main home that is presumably insured, and then take a look for maybe tree branches that are overhanging the residence, which is a very common risk of fire. Maybe try to gauge how difficult it is for fire services to actually access the residence. So make sure, in other words, there's kind of a clear path to get to the home. And also take a look at the trees around the home and make sure that they are not too closely or tightly packed, which in its own right could be a fire risk as well.

All right, so let's go over to Notebook and see how we could implement this. I'm inside of a new notebook called 002 Images. Inside of here, I have already put together a starter prompt for us. Now notice that this prompt is highly detailed and walks Claude through different points or different ideas that I want analyzed inside the image. I could have written out a very simple prompt of something like provide a fire risk score based upon the satellite image of this property and just left it there. But I can almost guarantee you I would not get a good result. So instead I applied some of the different prompt engineering techniques we have learned about previously and I provided a series of different analysis steps for Claude to go through. Step one, first find the actual primary residence inside of the satellite photo. Step two, take a look at the tree density. Then take a look at the ability for fire services to actually access the property. Take a look at how many trees or specifically branches are overhanging the roof, which is a very common fire risk. And then finally assign a fire risk rating based upon all these different qualities. And I provide some criteria on helping it decide whether it should be a one, two, three, or four. And then finally at the very bottom, write a one sentence summary for each with a final score.

So that's our prompt. I'm going to make sure I run that cell and then let's go down here to the bottom and we're going to write out some code to read in a sample image and feed it into Claude with that prompt and see what kind of result we... One other quick item. Attached to this lecture, you will find a zip archive called images.zip. Make sure you extract that archive and place the images directory into the same folder as your notebook. This folder contains some different satellite imagery of different houses with some number of trees around it. So for example, image one has a house with definitely a good amount of tree overhang. Image two has definitely a lot less trees, but they're still a little bit close tree right here to the property. And you can go through the rest and just verify that, yeah, we've definitely got some satellite imagery here. So our goal is going to be to send these different images into Claude and get a fire score rating for each.

Back inside my notebook, I'm going to first begin by opening up an image file and converting its contents into base 64. So I'll do a with open. In the images directory, I'm going to look for image 7 specifically. And I'm using that image in particular because it is absolutely surrounded by trees. So here's prop7.png. As you can see, definitely a lot of issues with fire here. I'm going to get the image bytes as base 64, standard underscore B64 and code(※ASR注:「standard underscore B64 and code」は standard_b64encode の誤認識). I'm going to pass in f.read and I'll decode into UTF-8.

I'm then going to add in a empty list of messages. I'll add a user message into it. And this message that I'm adding in is going to have two separate blocks. It is first going to have an image block exactly with the structure that you see right here, and then it will have a text block. And the text block is going to contain the actual directions that I want to feed into Collade(※ASR注:「Collade」は Claude の誤認識). So I will add in first a dictionary to represent the image block. So a type of image with a nested dictionary assigned to source that has a type of base64, a media underscore type of image slash png, and data that's going to be the image bytes encoded as base64. Then after that dictionary, I'll add in my actual prompt. So I'll give this a type of text, and then the prompt that I want to send in, I assign to the prompt variable right there. So I'll do a text of prompt.

All right, finally, I'll call chat and pass in my list of messages. I'm going to run this and let's see what we get. Looking at the response, I'm going to scroll down to the very bottom and I should see a fire risk rating. In this case, I got a fire risk of high or a score in particular of three. So I think Claude did a pretty reasonable job of evaluating all the trees around the main property and deciding that, yeah, there's probably going to be an issue here.

Before we move on, there is one last thing I want to remind you around images. getting good results out of Claude when feeding images in all comes down to your prompting technique. Just as we examined a lot of different ways of improving your prompt when using plain text, those same techniques apply to the world of images as well. So I would really encourage you to always make sure that you put together a very well-developed and well-evaluated prompt, because if you rely upon simple prompts like what I'm putting in right here, it's probably not going to work quite as well as you might expect.

↑ 目次へ戻る

Building with the Claude API | Lesson 48「PDF support」(全文ナレッジ)

動画 1分29秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

  • Claude は画像だけでなく PDF ファイルの中身を直接読める。このレッスンでは Lesson 47 の画像読み込みコードを最小限の変更で PDF 対応に書き換える。
  • 教材として earth.pdf(Wikipedia の Earth 記事の数ページ分・全3ページ)が動画に添付されており、ノートブックと同じディレクトリに置くのが前提。
  • PDF の読み込みは画像とほぼ同一のコード。変更点は4つだけ: 1. open() の対象を画像ファイル → "earth.pdf" に変更 2. 変数名を image_bytesfile_bytes にリネーム(もう画像ではないので。下で参照している箇所も忘れず更新) 3. content block の "type""image""document" に変更 4. "media_type""image/png""application/pdf" に変更
  • base64 エンコードの仕組みはそのまま流用:base64.standard_b64encode(f.read()).decode("utf-8")
  • プロンプトは前セルの長いプロンプトではなく「Summarize the document in one sentence(文書を一文で要約して)」に差し替えて実行 → Claude が PDF の中身を正しく読んで一文要約を返した(実行時間 3.8 秒、input 7,336 トークン / output 46 トークン)。
  • Claude が PDF から読めるのはテキストだけではない画像・チャート・表なども読める= PDF からあらゆる種類の情報を抽出するワンストップショップとして使える。

実務ポイント(このレッスンから持ち帰るもの)

  1. document block の送信形(そのまま使える雛形)

```python with open("earth.pdf", "rb") as f: file_bytes = base64.standard_b64encode(f.read()).decode("utf-8")

add_user_message( messages, [ { "type": "document", "source": { "type": "base64", "media_type": "application/pdf", "data": file_bytes, }, }, {"type": "text", "text": "Summarize the document in one sentence"}, ], ) ```

  1. 画像との差分は type: "document"media_type: "application/pdf" の2点だけ。ブロック構造(source / type: "base64" / data)は画像入力と完全に共通なので、画像入力のコードがあれば PDF 対応は数十秒で終わる。
  2. 落とし穴=変数リネームの更新漏れimage_bytesfile_bytes に変えたら、"data": で参照している下の箇所も必ず一緒に更新する(動画内でも講師が明示的に注意している)。
  3. PDF は OCR 前処理不要。テキスト抽出ライブラリ(pdfplumber 等)や図表の切り出しをかまさなくても、Claude がテキスト・画像・チャート・表をまとめて読む。「PDF からの情報抽出はまず Claude に直接投げる」が第一選択。
  4. コスト感覚:Wikipedia 記事3ページの PDF で input 7,336 トークン。PDF はページ数に応じてトークンを消費するので、大きい PDF を投げる時はトークン数を意識する。

本文(時系列・日本語逐語+画面差し込み)

[0:00] 画像のほかに、Claude は PDF ファイルからも直接コンテンツを読み取ることができます。この動画では、そのやり方をお見せします。

[0:06] この動画には earth.pdf というドキュメントが添付されています。開いてみると、Wikipedia の Earth(地球)の記事から抜き出した数ページ分だと分かります。

🖥 [0:09] 画面: ノートブック(002_images.ipynb — 08)の手前にプレビューで earth.pdf を表示。「Page 1 of 3」、サムネイルに 1〜3 ページ。1ページ目は WIKIPEDIA / The Free Encyclopedia のロゴと見出し「Earth」、本文冒頭は「Earth is the third planet from the Sun and the only astronomical object known to harbor life. …」。右側に The Blue Marble(Apollo 17, December 1972)の地球写真と Designations 表(Alternative names: The world・The globe・Terra・Tellus・Gaia・Mother Earth・Sol III ほか)。背後のノートブックには with open("imag… / image_bytes を含む前レッスンのコードが見えている。

[0:15] ですので、この PDF ファイルを必ずダウンロードして、ノートブックと同じディレクトリの中に置いてください。

🖥 [0:17] 画面: ノートブックに戻り、前レッスン(画像入力)のコードセルを表示: ```python with open("images/prop7.png", "rb") as f: image_bytes = base64.standard_b64encode(f.read()).decode("utf-8")

messages = []

add_user_message( messages, [ { "type": "image", "source": { "type": "base64", "media_type": "image/png", "data": image_bytes, }, }, {"type": "text", "text": prompt}, ], ) ```

[0:20] PDF ファイルを読むには、画像を読み込んで Claude に渡すときに使ったのとほぼまったく同じコードを使います。

[0:26] では、いま画像を開いている箇所を探します。ここですね。そして、これを earth.pdf に変更します。

[0:34] この変数の名前を image_bytes から、そうですね file_bytes に変えましょう。もう読んでいるのは画像ではないからです。下の方にある変数も忘れずに更新しておきます。

[0:44] ここにある type を image から document に変更します。

🖥 [0:44] 画面: 編集途中のセル。1行目はすでに書き換え済み: python with open("earth.pdf", "rb") as f: file_bytes = base64.standard_b64encode(f.read()).decode("utf-8") content block 側は "data": file_bytes, まで更新済みで、"type": "image"image を選択中(document への変更直前)。"media_type": "image/png"image 部分もハイライトされている。末尾は {"type": "text", "text": prompt}, のまま。

[0:47] それから media type を image スラッシュ PNG から application スラッシュ PDF に変えます。

[0:54] そして最後に、このドキュメントについて Claude に尋ねる質問です。前のセルにある大きなプロンプトを渡す代わりに、Claude に「この文書を一文で要約して」と頼むことにします。

[1:06] では、Claude から何が返ってくるか見てみましょう。実行します。

[1:10] はい、要約が出ました。

🖥 [1:10] 画面: 実行後のセル。プロンプト部分は ```python {"type": "text", "text": "Summarize the document in one sentence"}, ], )

chat(messages) `` セル[4]・実行時間 3.8s。出力はMessage(id='msg_01SdLybed5ppHQKNNQc55xK7', …)で、TextBlockの text(ハイライト表示)=「The document is a Wikipedia article about Earth, the third planet from the Sun and the only known astronomical object to harbor life, describing its physical characteristics, orbital properties, atmosphere, geological features, and etymological background.」(この文書は地球に関する Wikipedia 記事で、太陽から3番目の惑星であり生命を宿すことが知られる唯一の天体について、その物理的特徴・軌道特性・大気・地質学的特徴・語源的背景を記述している、の意)。model='claude-3-7-sonnet-latest',stop_reason='end_turn',usage=Usage(cache_creation_input_tokens=0, cache_read_input_tokens=0, input_tokens=7336, output_tokens=46, server_tool_use=None)`。

[1:12] どうやら PDF ファイルの中身を正しく読み取れたようです。

[1:15] さて、Claude ができるのは PDF からテキストを読み取ることだけではありません。画像やチャート、表なども読み取ることができます。

[1:22] ですから Claude は、PDF ドキュメントからほぼあらゆる種類の情報を抽出するためのワンストップショップのようなものだと考えてください。

英語逐語(ASR全文)

Besides images, Claude can also read content directly out of a PDF file. I'm going to show you how you do that in this video.

Attached to this video, you will find a document called earth.pdf. If you open it up, you'll see that it's just a couple of pages out of the Wikipedia article on earth. So make sure you download this PDF file and place it inside of the same directory as your notebook.

To read a PDF file, we use almost the exact same code that we use for reading an image and feeding the image into Claude. So I'm going to find where we are currently opening up an image right here. And I'm going to change it to earth dot PDF.

I'm going to rename this variable from image bytes to how about file bytes, because no longer are we reading an image.(※ASR注:image bytes / file bytes は変数名 image_bytes / file_bytes の読み上げ)I'm going to make sure I update the variable down here as well.

I'm going to change the type right here from image to document. And then the media type will go from image slash PNG to application slash PDF.

And then finally, the question that we're asking of Claude about this document, Rather than feeding in our big prompt that we have in the previous cell, I'm going to ask Claude to summarize the document in one sentence.(※ASR注:「document, Rather」は文の切れ目の大文字化ゆらぎ。発話は同一文の続き)

And let's see what we get out of Claude. So I'm going to run this. And there's the summary. It looks like it successfully read the contents of that PDF file.

Now Claude has the ability to not only read text out of a PDF. It can also read images or charts, tables, and so on. So you should really think of Claude as being like a one-stop shop for extracting just about any kind of information out of a PDF document.

↑ 目次へ戻る

Building with the Claude API | Lesson 49「Citations」(全文ナレッジ)

動画 5分27秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンのテーマ

citations(引用)機能を使って、Claude の回答がどの出典(ソース文書)のどの箇所に基づいているかを構造化データとして受け取り、ユーザーが出典を検証できる UI を構築する方法。

出発点:出典が見えない回答の問題

  • 前レッスンから続く earth.pdf(Wikipedia の Earth 記事を PDF 化したもの・全4ページ)を題材に、最終ページの「Earth's atmosphere and oceans were formed by volcanic activity and outgassing.(地球の大気と海洋は火山活動とアウトガスによって形成された)」という記述を使う。
  • 「地球の大気と海洋はどうやって形成されたか?」と質問すると、Claude は文頭で「volcanic activity and outgassing」という適切な回答を返す。
  • しかしユーザー視点では、この回答が「Claude が記憶から直接話しているだけ」に見えてしまい、実際には何らかのソース(この場合は完璧ではないが Wikipedia)を引用していることが伝わらない。

citations の有効化方法

messages に入れる document ブロックに2つのフィールドを追加するだけ: - "title": 文書名(例:"earth.pdf"。開いている PDF ファイル名) - "citations": {"enabled": True} の辞書

{
    "type": "document",
    "source": {
        "type": "base64",
        "media_type": "application/pdf",
        "data": file_bytes,
    },
    "title": "earth.pdf",
    "citations": { "enabled": True }
},

有効化すると、レスポンスの content複数の TextBlock のリストになり、一部の TextBlock が citations リスト(CitationPageLocation オブジェクト入り)を持つ、以前よりはるかに複雑な構造になる。

citation page location の構造(スライド「Citation Structure」)

Claude が「その事実・情報をどこから得たか」を正確に伝えるための構造:

フィールド 目的
cited_text Earth's atmosphere and oceans were formed by volcanic activity and outgassing. 提供文書から Claude が引用しているテキスト(主張を裏付ける原文)
document_index 0 複数文書を渡した場合、どの文書からの引用かを示す
document_title 'earth.pdf' 引用元文書のタイトル
start_page_number 4 引用テキストの開始ページ
end_page_number 5 引用テキストの終了ページ

citations の真の狙い=出典検証 UI の構築

  • citations を返す本当の意図は、Claude の回答から引用ポップアップ付きの UI を組み立てられるようにすること。
  • 講師はデモとして、得られたレスポンスを再度 Claude に渡し「回答全体を整形されたドキュメントとしてレンダリングし、各 citation を表すポップアップを付けて」と依頼(claude.ai の Artifact として生成)。
  • 文中の [1][2][3]… にマウスオーバーすると、citation page location オブジェクトの全情報(文書名 earth.pdf・ページ 4〜5・引用原文)が整形ポップアップで表示される。
  • これにより、ユーザーは「Claude の提示情報が実在の外部ソース由来である」ことを確認でき、さらにソースに当たって Claude の解釈が正しいかを検証できる。

plain text でも使える(citation char location)

  • citations は PDF 専用ではなくプレーンテキストでも使える。
  • document ブロックの source{"type": "text", "media_type": "text/plain", "data": article_text} に変更(article_text は PDF から手動コピペしたテキストを入れた変数)、title も "Earth Article" などに変更。citations の enabled True はそのまま。
  • この場合、レスポンスには CitationPageLocation の代わりに CitationCharLocationstart_char_index / end_char_index)が返り、渡した大きなテキストブロック内の文字位置で引用箇所が示される。これでも同様の UI を構築できる。

実務ポイント(このレッスンから持ち帰るもの)

  • citations の有効化は document ブロックに "title""citations": {"enabled": True} を足すだけ。API 呼び出し自体の構造は変わらない。
  • 有効化後の content細切れの TextBlock のリストになる(citations 付きブロックと、接続用の空白のみのブロックが交互に並ぶ)。パース処理は「content はテキスト1個」という前提を捨てて書く。
  • 出典の粒度は入力形式で決まる:PDF → CitationPageLocation(ページ番号)/plain text → CitationCharLocation(文字インデックス)。UI 設計時にどちらが返るかを意識する。
  • 複数文書を渡すケースに備えて document_index がある。文書ごとに title を付けておくと、ポップアップ表示にそのまま使える。
  • RAG・文書QAなど「ユーザーが回答の根拠を検証できることがクリティカルな場面では citations を使う」のが講師の推奨。ハルシネーション対策としても、モデル任せの「出典を書いて」プロンプトより構造化された引用が得られる。

本文(時系列・日本語逐語+画面差し込み)

[0:00] 先ほどまで作業していた PDF ファイルで、一番下のページまでスクロールしていきます。そしてこの下の方に、地球の大気と海洋は火山活動とアウトガス(脱ガス)によって形成された、と書かれているのに気づくはずです。

🖥 [0:00] 画面(earth.pdf を最終ページまでスクロール) VS Code 上の Jupyter ノートブック 002_images.ipynb の手前に earth.pdf のプレビューウィンドウ。1ページ目は Wikipedia「Earth」記事(WIKIPEDIA The Free Encyclopedia のロゴ、The Blue Marble の写真、Designations 表)。軌道要素(Aphelion 152 097 597 km / Perihelion 147 098 450 km 等)、大気組成(78.08% nitrogen (dry air) / 20.95% oxygen (dry air) / ≤1% water vapor (variable)…)などの表を通過して Page 4 of 4 まで下る。 最終ページ「After formation」セクションの冒頭文がハイライトされている: Earth's atmosphere and oceans were formed by volcanic activity and outgassing.[43] Water vapor from these sources condensed into the oceans, augmented by water and ice from asteroids, protoplanets, and comets.[44] ...

[0:11] さて、ちょっとした練習として、Claude にとてもシンプルな質問をしてみたいと思います。地球の大気と海洋はどのように形成されたのかを聞いてみます。そして、おそらく「火山活動とアウトガスによって」といった類の回答が返ってくると予想されます。

[0:24] そこで、ここにあるこれをコピーして、私のプロンプトに入れます。そして Claude に「地球の大気と海洋はどのように形成されましたか?」と質問します。

🖥 [0:25] 画面(編集前のプロンプトセル) ```python messages = []

add_user_message( messages, [ { "type": "document", "source": { "type": "base64", "media_type": "application/pdf", "data": file_bytes, }, }, {"type": "text", "text": "Summarize the document in one sentence"}, ], )

chat(messages) ``` (前レッスンで使った "Summarize the document in one sentence" のテキストが、この後 "How were Earth's atmosphere and oceans were formed?"〔原文ママ・were が重複〕に差し替えられる)

[0:31] では、これをさっと実行します。少なくとも私の場合は、ここの一番最初の文で、とても適切な回答が返ってきているのが分かります。これらは火山活動とアウトガスによって形成された、と伝えられています。

[0:42] つまり、まさに予想どおりの結果です。

🖥 [0:33] 画面(citations なしの実行結果・[7] 6.4s) Message(id='msg_012p2f7TFhAaJNLg4wu1how7', content=[TextBlock(citations=None, text="According to the document, Earth's atmosphere and oceans were formed by volcanic activity and outgassing. Water vapor from these sources condensed into the oceans, and this water was augmented by additional water and ice from asteroids, protoplanets, and comets. The document mentions that sufficient water to fill the oceans may have been on Earth since it formed.\n\nThe atmosphere's greenhouse gases played an important role in early Earth by keeping the oceans from freezing when the newly forming Sun had only 70% of its current luminosity. By 3.5 billion years ago, Earth's magnetic field was established, which helped prevent the atmosphere from being stripped away by the solar wind.\n\nThe atmosphere's current composition includes primarily nitrogen (78.08%) and oxygen (20.95%), with variable water vapor (≤1%), along with smaller amounts of other gases like argon, carbon dioxide, neon, helium, methane, krypton, and hydrogen.", type='text')], model='claude-3-7-sonnet-latest', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(cache_creation_input_tokens=0, cache_read_input_tokens=0, input_tokens=9628, (content は citations=None の TextBlock 1個だけ。回答は通常の連続テキスト)

[0:44] しかし、この回答をユーザーの視点で受け取るところを想像してみてほしいのです。ユーザーがこの生成テキストを見た時、これは単に Claude が記憶から直接話しているだけだ、と思うかもしれません。そして、私たちが実際には何らかのソースを引用しているのだ、ということをユーザーはよく理解できないかもしれません。

[0:57] 今回の場合、そのソースは完璧なものではありません。Wikipedia です。しかし、少なくとも何かしらのソースではあります。

[1:02] そこで、もし何らかの方法でユーザーに知らせる、つまりこの情報をどうやって得ているのかを伝える手段があれば、本当に素晴らしいはずです。

[1:09] 幸い、Claude には citations(引用)と呼ばれる機能があります。citations は、Claude が外部の情報ソースを直接参照して、「他の文書や他のテキストソースを見てこの回答を得た」と表明できるようにするものです。

[1:21] では、citations がどう動くのかをお見せしましょう。実際に動いているところを見れば、何が起きているのかとてもよく分かると思うからです。ここにある私たちのプロンプトまでスクロールして戻ります。そして、このメッセージに入れている最初のブロックに、小さな変更を加えます。

[1:35] source フィールドのすぐ後に、earth.pdf という title を追加します。これは私たちが開いている PDF ファイルの名前だからです。それから citations フィールドを追加します。これは enabled を持つ辞書で、True にします。こんなふうに、です。

🖥 [1:30] 画面(document ブロックの編集中) python add_user_message( messages, [ { "type": "document", "source": { "type": "base64", "media_type": "application/pdf", "data": file_bytes, }, "title": "earth.pdf", "citations": { } }, {"type": "text", "text": "How were Earth's atmosphere and oceans were formed?"}, ], )"citations": { } の波括弧内にカーソルがあり、この後 "enabled": True が書き込まれる。質問文は原文ママ)

[1:50] それでは、このリクエストをもう一度送信します。そして、今度は何が返ってくるか見てみましょう。

[1:54] すると、レスポンスが以前よりはるかに複雑になっているのが分かります。ここにある content フィールドは、いくつかの text block を持つリストになっています。そして、これらの text block のいくつかが、citation page location と呼ばれるものが入った citations リストを持っているのです。

🖥 [1:55] 画面(citations 有効化後の実行結果・[8] 6.9s) Message(id='msg_01E7zYnkYhyoAWTYvCYCCDbM', content=[TextBlock(citations=None, text="Earth's atmosphere and oceans were formed through a combination of geological processes. \n\n", type='text'), TextBlock(citations=[CitationPageLocation(cited_text="[42]\r\nEarth's atmosphere and oceans were formed by volcanic activity and outgassing.\r\n", document_index=0, document_title='earth.pdf', end_page_number=5, start_page_number=4, type='page_location')], text="Earth's atmosphere and oceans were formed by volcanic activity and outgassing.", type='text'), TextBlock(citations=None, text=' ', type='text'), TextBlock(citations= [CitationPageLocation(cited_text='[43] Water vapor from\r\nthese sources condensed into the oceans, augmented by water and ice from asteroids, protoplanets,\r\nand comets.\r\n', document_index=0, document_title='earth.pdf', end_page_number=5, start_page_number=4, type='page_location')], text='Water vapor from these sources condensed into the oceans, augmented by water and ice from asteroids, protoplanets, and comets.', type='text'), TextBlock(citations=None, text=' ', type='text'), TextBlock(citations= [CitationPageLocation(cited_text='[44] Sufficient water to fill the oceans may have been on Earth since it formed.', document_index=0, document_title='earth.pdf', end_page_number=5, ... (citations=None のブロックと CitationPageLocation 付きブロックが交互に並ぶ。エディタ上では CitationPageLocation の語が選択ハイライトされる)

[2:09] それでは少しの間、citation page location とは正確には何なのかに焦点を当てましょう。図をお見せします。

🖥 [2:13] 画面(スライド「Citation Structure」) 見出し:Citation Structure。列見出し Example/Purpose の表:

Example Purpose
cited_text Earth's atmosphere and oceans were formed by volcanic activity and outgassing. Text that Claude is citing from the provided document
document_index 0 If multiple documents are provided, this will tell you which document Claude is citing from
document_title 'earth.pdf' Title of the document that Claude is citing from
start_page_number 4 Starting page of the cited text
end_page_number 5 Ending page of the cited text

左下に ANTHROP\C ロゴ。

[2:15] citation page location は、Claude が「ある事実や情報を正確にどこから得たのか」を私たちに伝えるための手段です。今回のケースでは、cited text、document index、document title、start page、end page を持つ構造が返ってきました。

[2:26] cited text は、ソース文書——今回は earth.pdf——の中から取られた、Claude の記述を何らかの形で裏付けているテキストです。document index と document title は、この記述がどこでなされたのかを正確に教えてくれます。そして start page と end page は、その文書の中のどこでその記述がなされたのかを正確に示します。

[2:42] さて、これらの citations をあなたに渡すことの本当の狙いは、Claude の回答から、これからお見せするような見た目のユーザーインターフェースを組み立てられるようにすることです。

[2:51] そこで私は、先ほど Claude から得たレスポンスを、もう一度 Claude に入力として与えて、「そのレスポンス全体を綺麗に整形されたドキュメントとしてレンダリングし、すべての citation を表すポップアップを付けてほしい」と頼みました。

[3:01] そして今、1 や 2 や 3 の上にマウスを乗せると、綺麗に整形されたポップアップが現れます。このポップアップには、あの citation page location オブジェクトの中の情報がすべて含まれています。

🖥 [3:05] 画面(Chrome・claude.ai の Artifact で引用ポップアップを表示) URL: claude.ai/artifacts/ad746547-3f18-45d7-9393-185464e484ba 整形された回答ドキュメント。各文末に青い上付きの [1]〜[7] が付く: ``` Earth's atmosphere and oceans were formed through a combination of natural processes following Earth's formation.

Earth's atmosphere and oceans were formed by volcanic activity and outgassing.[1] Water vapor from these sources condensed into the ocea…(以下ポップアップに隠れる) ice from asteroids, protoplanets, and comets.[2]

Sufficient water to fill the oceans may have been on Earth…[3] In this model, atmospheric greenhouse gases kept the oceans f…(一部隠れる) forming Sun had only 70% of its current luminosity.[4]

By 3.5 billion years ago, Earth's magnetic field was established, which helped prevent the atmosphere from being stripped away by the solar wind.[5] This magnetic field protection was crucial for maintaining the atmosphere that had developed.

The atmosphere that formed has a composition of primarily nitrogen and oxygen.[6] Water vapor is widely present in the atmosphere, forming clouds that cover most of the planet.[7] These elements combined to create the conditions necessary for life to develop on Earth. [1] にマウスオーバーして表示されたポップアップ: earth.pdf Pages 4-5 "Earth's atmosphere and oceans were formed by volcanic activity and outgassing." ```

[3:13] これは、「ここでの Claude の回答——具体的にはこの文——は、本当に外部の文書に裏付けられているのだ」ということをユーザーに知らせるためのものです。

[3:20] つまり今回の場合、この文は earth.pdf から、具体的には4〜5ページのテキストから来ています。

[3:27] そして実際に引用しているテキストは「earth's atmosphere was …(地球の大気は〜)」云々、というわけです。

[3:31] つまり、この citations 機能全体によって、このようなインターフェースを組み立てられるのです。そこでは、Claude が提示している情報が実在する外部ソースから来ていることを、ユーザーが確信できます。そしてユーザーはそのソースに当たりに行って、Claude がその外部文書の中の情報を正しく解釈しているかを確かめることができます。

[3:48] この citations 機能は、PDF 文書との組み合わせだけに限定されているわけではありません。プレーンテキストでも同様に使えます。

🖥 [3:48] 画面(現在のリクエストブロック・citations 有効の完成形) ```python add_user_message( messages, [ { "type": "document", "source": { "type": "base64", "media_type": "application/pdf", "data": file_bytes, }, "title": "earth.pdf", "citations": { "enabled": True } }, {"type": "text", "text": "How were Earth's atmosphere and oceans were formed?"}, ], )

chat(messages) ```

[3:55] ごく手短な例として、このすぐ上のセルで、PDF 文書からいくつかのテキストを手動でコピー&ペーストして、article text という変数に入れておきました。

[4:04] それでは、この下でリクエストを作っているところまで戻って、このブロックに大きな更新を加えます。

[4:09] type の document はそのまま残します。この source も残しますが、(source の)type を text に変更します。

🖥 [4:12] 画面(source を text 型へ書き換え中) python add_user_message( messages, [ { "type": "document", "source": { "type": "tex|", ← "text" を入力している途中 "media_type": "application/pdf", "data": file_bytes, }, "title": "earth.pdf", "citations": { "enabled": True } }, {"type": "text", "text": "How were Earth's atmosphere and oceans were formed?"}, ], ) (media_type と data、title はまだ書き換え前)

[4:15] media type を text スラッシュ plain に変更し、それから data を article に……。

[4:22] これがさっきの……あっ、すみません。article text です。はい、これで OK。これが、上であの変数に代入しておいたテキストです。

[4:30] 次に title を変更します。もう直接の PDF ファイルというわけではないので、そうですね、earth article のようなものにしましょうか。

[4:37] そして citations の enabled true はそのまま残しておけます。それでは、これをもう一度実行して、

🖥 [4:40] 画面(セル再実行中) セル実行カウンタ [10]、経過 2.8s のスピナー表示。出力はまだ空。上部のセルには {"type": "text", "text": "How were Earth's atmosphere and oceans were for…"} の行が見えている。

[4:42] レスポンスを見てみると、citation page location の代わりに、今度は citation char location が返ってきているのが分かります。

🖥 [4:48] 画面(plain text での実行結果・[10] 8.3s) Message(id='msg_01D5JmGrKi71KTkPW98jteGk', content=[TextBlock(citations=None, text="Based on the document, I can provide information about the formation of Earth's atmosphere and oceans.\n\n", type='text'), TextBlock(citations=[CitationCharLocation(cited_text="[42]\nAfter formation\nEarth's atmosphere and oceans were formed by volcanic activity and outgassing.\n", document_index=0, document_title='Earth Article', end_char_index=9977, start_char_index=9877, type='char_location')], text="Earth's atmosphere and oceans were formed by volcanic activity and outgassing.", type='text'), TextBlock(citations=None, text=' ', type='text'), TextBlock(citations=[CitationCharLocation(cited_text='[43] Water vapor from\nthese sources condensed into the oceans, augmented by water and ice from asteroids, protoplanets,\nand comets.\n', document_index=0, document_title='Earth Article', end_char_index=10109, start_char_index=9977, type='char_location')], text='Water vapor from these sources condensed into the oceans, and this was supplemented by water and ice from asteroids, protoplanets, and comets.', type='text'), TextBlock(citations=None, text='\n\n', type='text'), TextBlock(citations=[CitationCharLocation(cited_text='[44] Sufficient water to fill the oceans ... (document_title が 'Earth Article' になり、page 番号の代わりに start_char_index / end_char_index が返る)

[4:48] つまりこれは、Claude が引用元にしている、あの大きなテキストブロックの中での位置を与えてくれるということです。

[4:53] これを使えば、さっきブラウザの中でお見せしたものととてもよく似たインターフェースを組み立てることができます。

[4:58] というわけで、繰り返しになりますが、プレーンテキストからでも PDF 文書からでも、どちらでも引用できます。「ユーザーが、Claude がどうやって回答を組み立てているのかを何らかの方法で調査でき、Claude が何らかのソース文書——PDF またはプレーンテキスト——から情報を引き出していることを確認できる」ことがクリティカルな場面ではいつでも、この citations 機能を活用することを強くお勧めします。

英語逐語(ASR全文)

In the PDF file that we were just working with, I'm going to scroll down to the very bottom page. And down here at the bottom, you'll notice that it mentions that Earth's atmosphere and oceans were formed by volcanic activity and outgassing. Now, as a little exercise, I want to try to ask Claude a very simple question. I want to ask it how Earth's atmospheres and oceans were formed. And I would probably expect to see some kind of answer that says something like volcanic activity and outgassing. So I'm going to copy this right here and put it into my prompt. And ask Claude, how were Earth's atmosphere and oceans formed? Now, I'm going to run this really quick. And for me, at least, I'll notice that in the very first sentence here, I'm given a very appropriate answer. I'm told that these were formed by volcanic activity and outgassing. So that's really exactly what I would expect. But I want you to imagine getting this answer from the perspective of a user. When a user sees this generated text, they might think that it's just Claude speaking directly from memory here. And the user might not really understand that we are actually citing some kind of source here. In this case, the source is not perfect. It is Wikipedia, but at least it is something. So what would be really fantastic is if there was some kind of way to somehow inform the user or tell them how we are getting this information. Luckily, Claude has access to a feature called citations. Citations allow Claude to refer to some outside source of information directly and say that it got its answer by looking at some other document or some other source of text. Now let me show you how citations work because once you see it in action, I think you'll have a really good idea of what's going on. I'm going to scroll up to our prompt right here, and I'm going to make a little modification to the first block that we're putting into this message. Right after the source field, I'm going to add in a title of earth.pdf because that is the name of the PDF file that we are opening, and a citations field that will be a dictionary with enabled. True. Like so. Now I'm going to send off this request again. And let's see what we get back now. Now we're going to see that our response is much more complicated than it was previously. Our content field right here is a list that has some text blocks. And some of these text block things have a citations list with something called a citation page location. So let's focus on exactly what a citation page location is for just a moment. I'll show you a diagram. A citation page location is Claude's way of telling us exactly where it got some fact or some piece of information from. So in our case, we got back a structure that has a cited text, document index, document title, start page, and end page. The cited text is the text out of the source document, in our case, earth.pdf, that is somehow supporting Claude's statement. The document index and the document title tell us exactly where this statement was made, and the start and end page tells exactly where inside of that document that statement was made. Now the real intent behind giving you these citations is to allow you to build up a user interface that looks something like this out of Claude's answer. So I took the response that we just got out of Claude, I fed it back into Claude and asked it to render that entire response in a nicely formatted document and give me some pop-ups to represent all the different citations. But now if I mouse over the one or the two or the three, I'll see a nicely formatted pop-up appear. This pop-up contains all the information out of that citation page location object. It is meant to inform the user that Claude's response here, specifically this sentence really is being informed by some outside document. So in this case, this sentence is coming from earth.pdf, specifically some text on pages four to five. And the actual text that we're citing is earth's atmosphere was, et cetera, et cetera. So this entire citations feature allows you to build up interfaces like this, where a user can be assured that the information being presented by Claude is coming from some actual outside source. The user can then go and refer to that source and make sure that Claude is correctly interpreting the information inside that outside document. This citations feature is not restricted to only being used with PDF documents. You can also use it with plain text as well. So as a very quick example, in the cell right above, I manually copy pasted in some text out of the PDF documents and I send it to a variable of article text. Now I can go back down to where I'm making my request down here, and I'm going to make a big update to this block. I'm going to leave the type of document. I'm going to leave this source, but I'm going to change the type to be text. I'm going to change the media type to be text slash plane, and then the data to be article. So that is the text that was, oh, sorry. It's article text. There we go. So that's the text that I assigned to that variable up there. I'm then going to change the title to how about something like earth article, since it's not really directly a PDF file anymore. and then I can leave the enabled true with citations in there. So now if I run this again and take a look at the response, we will see that instead of a citation page location, now we get a citation char location. So this is going to give us a position inside of that big block of text that Claude is citing from. We can now use this to build up a very similar interface to the one I just showed you inside the browser. So again, you can cite from plain text or PDF documents either way i really recommend you make use of the citations feature anytime that it's critical to make sure that users can somehow investigate how claude is building up its response and ensure that claude is drawing information from some source document either a pdf or plain text

↑ 目次へ戻る

Building with the Claude API | Lesson 50「Prompt caching」(全文ナレッジ)

動画 3分36秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンのテーマ

prompt caching(プロンプトキャッシング)の目的と仕組みの直感的理解。prompt caching は Claude の応答を高速化し、テキスト生成のコストを削減するための機能。このレッスンは概念編で、実装の詳細(コードの書き方)は次のレッスンで扱う。

キャッシュなしの通常リクエストで内部的に起きていること

  • すべては私たちが Claude にメッセージを送るところから始まる(例:「この長いテキストを要約して」)。
  • Claude はレスポンスの出力テキストを生成し始める前に、入力メッセージに対して膨大な仕事をする。内部的に膨大な数のデータ構造を作り、入力テキストだけを対象に膨大な計算を行う。
  • スライド上の内訳:①プロンプトのトークナイズ ②各トークンの埋め込み(embeddings)生成 ③周辺テキストに基づく文脈付加 —— ここまでが「入力メッセージに対する仕事」。その後 ④出力テキスト生成。
  • その先行作業をすべて使って出力テキストを生成し、assistant メッセージとしてレスポンスを返す。
  • レスポンス送信後、Claude は出力テキストと入力メッセージに対して行った計算結果を全部ゴミ箱に捨てる。すべての仕事が煙となって消え、「次のリクエストを処理する準備ができました!」と宣言する。

同一メッセージ再送時の無駄

  • 会話を継続するフォローアップリクエストでは、①さっき送ったのとまったく同じユーザーメッセージ ②返ってきた assistant メッセージ ③会話を進める新しいユーザーメッセージ、をリストにして全部まとめて再送する。
  • Claude は最初のメッセージを見て(擬人化すれば)イライラする:「このメッセージ、ついさっき処理したばかりだ。あれだけ仕事をしたのに計算を全部捨ててしまった。10秒前にやって捨てた仕事を再利用できたらいいのに」。
  • もしその仕事を保存していれば、同じ仕事を繰り返さずに済むので、はるかに速くレスポンスを返せるはず。

解決の発想=計算結果を一時キャッシュに保存して再利用する

  • 初回リクエストで入力メッセージに対して行った分析結果を、ゴミ箱に捨てる代わりにキャッシュ(一時データストア)に保存する。
  • フォローアップリクエストにまったく同じ入力ユーザーメッセージが含まれていたら、Claude はキャッシュを見て「同じメッセージをさっき見た。分析の仕事も保存してある」と気づき、再分析せずに以前の仕事を再利用する。
  • これによりテキスト生成プロセスが劇的に高速化する。「リクエストで行った仕事を保存して後で使う」——この考え方こそが prompt caching のすべて。

終盤サマリースライドの要点

  • cached content を使うリクエストは、より安く・より速く実行される
  • 初回リクエストはキャッシュへ write する
  • フォローアップリクエストはキャッシュから read できる
  • キャッシュの寿命は1時間
  • 同じコンテンツを繰り返し送る場合にのみ有用(ただしこれは極めて頻繁に起きるパターン)

実務ポイント(このレッスンから持ち帰るもの)

  • prompt caching の効果は2軸:応答レイテンシの削減 + 生成コストの削減。どちらも「入力の前処理をやり直さない」ことから生まれる。
  • LLM は毎リクエスト、入力全体(会話履歴含む)をゼロから前処理している。会話継続のたびに同じ先頭メッセージ群を再送するチャットアプリは、毎ターン同じ計算を捨てて作り直している——これがキャッシュの対象。
  • 運用モデルは「初回=cache write / 後続=cache read」。キャッシュ寿命は1時間(本レッスンのスライド時点)。
  • 再利用の条件は「まったく同じ入力メッセージ(exact same message)」。同一内容の再送で初めて効く、という前提を設計に織り込む。
  • 「同じ内容を繰り返し送る」ことは会話継続・長文コンテキストの再利用などで極めて頻繁に起きるため、実アプリではほぼ常に検討価値がある。具体的な実装方法は次レッスンで扱う。

本文(時系列・日本語逐語+画面差し込み)

[0:00] 次に注目していく機能は prompt caching(プロンプトキャッシング)です。prompt caching は、Claude の応答を高速化し、テキスト生成のコストを下げるために使われます。

[0:09] prompt caching がどのように機能するかを理解してもらうために、prompt caching の類をまったく有効にしていない状態で行う典型的なリクエストの間に、Claude の内部で何が起きるかを順に見ていきます。つまり、これは単なる通常のリクエストです。通常のリクエストと全体の流れについてはすでに少しお話ししましたが、心配いりません。今回は Claude 自体の内部で何が起きているかについて、少しだけ詳細を足してお話しします。

🖥 [0:18] 画面(スライド「This is what usually happens during a request」) 左に「Our Server」、右に「Claude」の縦長ボックス。中央を貫く黒い矢印(Our Server → Claude)の上にメッセージのボックス: User Message | Summarize this long text: <text>

[0:29] いつものように、すべては私たちが Claude にメッセージを送信するところから始まります。

[0:33] そして Claude がこのメッセージを受け取ると、実際に出力テキストを生成し始める前に、入力メッセージに対して膨大な量の仕事を行います。言い換えると、Claude は内部で膨大な数の内部データ構造を作り、入力テキストだけを対象に膨大な数の計算を行うのです。

🖥 [0:33] 画面(Claude の吹き出し) User Message「Summarize this long text: 」が Claude に届き、Claude の吹き出し: ``` Well, I guess I better get started on generating a response...

Before generating anything, I need to do a LOT of work to analyze the user's input ```

🖥 [0:44] 画面(Claude 内部の処理ステップ図) Claude の箱の中に4つの処理ボックスが縦に並ぶ: - Tokenize the prompt - Create embeddings for each token - Add context based on surrounding text - Generate output text

上3つ(Tokenize/Create embeddings/Add context)を波括弧でくくり、注記「Work done on the input message only」。 左上から User Message「Summarize this long text: 」が Claude へ入り、左下へ Assistant Message「\<Summary>」が返っていく矢印。

[0:50] その後、先に行ったすべての仕事を使って、最終的に出力テキストを生成し、何らかの assistant メッセージという形で私たちにレスポンスを送り返します。

[0:59] レスポンスが私たちに送られた後、Claude は出力テキストと、入力メッセージに対して行われた先ほどのすべての計算の結果を取り出して、それらを全部ゴミ箱に放り込みます。全部消えていきます。あの仕事のすべてが、いわば完全に煙となって消えていくのを目にするわけです。

🖥 [1:00〜1:10] 画面(処理結果が Trash へ移る) 左に「Claude」の箱、右に破線枠の「Trash」。最初は Claude 側にあった4つの処理ボックス(Tokenize the prompt/Create embeddings for each token/Add context based on surrounding text/Generate output text)が、まとめて Trash 側へ移動し、Claude の箱は空になる。

[1:15] Claude はその後片付けをすべて終えると、世界に向かってこう宣言します。「次のリクエストを処理する準備ができました!」

🖥 [1:14] 画面(クリーンアップ完了) Claude の吹き出し: I'm ready to process the next request! 右の破線枠「Trash」の中には4項目(Tokenize the prompt/Create embeddings for each token/Add context based on surrounding text/Generate output text)が入ったまま。

[1:20] さてここで、この最初のリクエストを行った後に、フォローアップのリクエストを行うと少し想像してみましょう。そしてこのフォローアップリクエストでは、この会話を継続しているのだと想像してください。つまり、メッセージのリストを添付することになります。1つ目は、ついさっき送ったのとまったく同じメッセージ。次に、返ってきた assistant メッセージのレスポンス。そして、会話をさらに進めるために添付する、何らかの新しいユーザーメッセージです。これらすべてのメッセージをまとめて Claude に送り込みます。

🖥 [1:25] 画面(スライド「Follow Up Request」) 3行のメッセージテーブルが Claude へ矢印で送られる図: User Message | Summarize this long text: <text> Assistant Message | <Summary> User Message | The summary needs more focus on... 1行目の User Message に向けて注記「Same message we sent a moment ago」(さっき送ったのと同じメッセージ)の矢印が付いている。

[1:46] そして内部的には、Claude はその最初のメッセージを見たとき、おそらく少しイライラすることになります。Claude はその最初のメッセージを見て、心の中でこう思うわけです——もちろん、実際に起きていることとは少し違います。舞台裏で起きていることを、いわばこんなふうにイメージできる、という話です。

🖥 [1:52] 画面(Claude の吹き出し・不満) Follow Up Request テーブルの1行目「User Message | Summarize this long text: 」を指す矢印付きで、Claude の吹き出し: ``` Hey, wait, I just processed that message a moment ago!

Why did I throw away all the work I did, I could have reused it :( ```

[2:01] Claude はその最初のメッセージを見てこう言うのです。「このメッセージ、ついさっき見たばかりだ。これを処理するためにあれだけの仕事をしたのに、その計算を全部捨ててしまった」。そして Claude は心の中で思います。「ほんの10秒前にやって、捨ててしまったあの仕事のすべてを、本当に再利用できたらいいのに」と。

[2:16] もし Claude が、ついさっき捨てたその仕事を保存していたなら、同じ仕事をすべて繰り返す必要がないので、おそらくはるかに速くレスポンスを送り返せるでしょう。

[2:26] さて、この問題が見えたところで、これを解決できそうな方法を考えてみましょう。

[2:31] この問題に対処できる、1つの可能なやり方がこれです。Claude に最初のリクエストを送り、Claude が、送信したユーザーメッセージに対する最初の仕事を一通り行うときはいつでも、その分析すべての結果をゴミ箱に放り込むのではなく、代わりにその仕事をすべてキャッシュする、つまり何らかの一時的なデータストアに入れることにできるかもしれません。

🖥 [2:33] 画面(Trash の代わりに Cache へ、という対比図) 左:「Initial Request」= User Message「Summarize this long text: 」が「Claude」へ入り、Assistant Message「\<Summary>」が返る。Claude の箱の中には4つの処理ステップ(Tokenize the prompt/Create embeddings for each token/Add context based on surrounding text/Generate output text)。 右側に2つの保存先候補が並ぶ:破線枠の「Trash」(空)と、実線枠の「Cache」(空)。

[2:53] そうすれば、その後フォローアップリクエストを行い、そこにまったく同じ入力ユーザーメッセージを含めた場合、Claude は自分のキャッシュの中を見てこう言えるのです。「おや、このまったく同じメッセージをついさっき見たぞ。そして、この特定のメッセージにまつわる分析の仕事を全部保存してある」。そのメッセージをもう一度分析し直すのではなく、以前に行った仕事をすべて再利用できるわけです。

🖥 [3:00] 画面(Cache の中身)Cache」枠の中に2列のテーブル: - 左列見出し「If I ever see this message again...」(もしこのメッセージをまた見かけたら…)→ 中身:User Message | Summarize this long text: <text> - 右列見出し「...I'll reuse this work I already did」(…すでにやったこの仕事を再利用する)→ 中身:Tokenize the prompt / Create embeddings for each token / Add context based on surrounding text

(右列に入っているのは入力処理の3ステップで、「Generate output text」はキャッシュ対象に含まれていない)

[3:13] そして、これによってテキスト生成のプロセスが劇的に高速化することが期待できます。繰り返しになりますが、すでにやってあった仕事の一部を再利用しているからです。

[3:20] リクエストで行った仕事の一部を保存して、後で使えるようにする——この考え方こそ、prompt caching のすべてです。それでは、すぐ後で戻ってきて、prompt caching の実装の詳細をいくつか順に見ていき、それが Claude によってどのように実装されているのかをしっかり理解していきましょう。

🖥 [3:24] 画面(スライド「Prompt Caching」まとめ) 左側の箇条書き: - Requests that use cached content are cheaper and faster to execute(太字) - Initial request will write to the cache - Follow up requests can read from the cache - Cache lives for one hour - Only useful if you're repeatedly sending the same content(太字)(but this happens extremely frequently)

右側の図(上下2段): - 上段「Initial Request」:「Please summarize this...」→ Claude。右の「Cache」枠の中に黒いボックス「Work done to process message」が入っている(=初回は仕事をキャッシュへ書き込む)。 - 下段「Follow up Request」:「Please summarize this...」→ Claude。今度は黒いボックス「Work done to process message」が Claude の箱の中に置かれ、右の「Cache」枠は空(=キャッシュから読み出して Claude 側で再利用)。

英語逐語(ASR全文)

The next feature that we are going to focus on is prompt caching. Prompt caching is used to speed up Claude's response and decrease the cost of text generation. To help you understand how prompt caching works, we're going to walk through what happens inside of Claude during a typical request that we make without any kind of prompt caching enabled at all. So again, this is just a normal request. Now we've already spoken a little bit about normal requests in the entire flow, but don't worry, this time I'm going to add in a little bit of detail on what happens inside of Claude itself. As usual, everything begins with us sending a message off to Claude. And when Claude receives this message, before actually generating any output text at all, it does a tremendous amount of work on the input message. In other words, Claude is going to internally create a tremendous number of internal data structures and do a tremendous number of calculations solely on the input text. It will then eventually generate the output text using all that earlier work it did, and then send a response back to us in the form of some assistant message. After the response is sent off to us, Claude is going to then take the output text and the result of all those earlier calculations that were done on the input message and just throw them all into the trash. Away it all goes. We see all that work kind of go up in smoke entirely. Once Claude has gone through all that cleanup, it declares to the world, I am ready to process the next request. Now let's imagine for a moment that after making this initial request, we then make a follow-up request. And in this follow-up request, let's just imagine that we are continuing this conversation. So we're going to attach a list of messages. The first one will be the exact same message we had sent in a moment ago, and then the assistant message response we got back, and then some new user message that we're going to attach just to further the conversation along. So we're going to take all these messages and send them into Claude. And internally, Claude is probably going to be a little bit frustrated when it sees that first message. Because Claude is going to see that first message and think to itself, of course, this isn't quite what happens. We can imagine this is kind of what's going on behind the scenes. Claude is going to see that first message and say, I just saw this message. I just did so much work to process it. And then I threw away all those calculations. And Claude is going to think to itself, I really wish I could reuse all that work that I did just 10 seconds ago and threw away. If Claude had saved that work that it threw away just a moment ago, it would probably be able to send us back a response much more quickly because it doesn't have to repeat all that work. So now that we have seen this problem, let's think of some possible way to solve it. Well, here's one possible way that we could handle this problem. Maybe we could say that whenever we make an initial request off to Claude and Claude goes through all that initial work on our user message that we are sending in, rather than taking the results of all that analysis and throwing into the trash, maybe we could instead cash all that work or put it into some temporary data store. Then if we ever make a follow-up request and we include the exact same input user message, Claude could go into its cache and say, hey, I just saw this exact same message a moment ago, and I saved the work of all the analysis around that particular message. So rather than reanalyze the message again, it could reuse all the work that it did previously. And hopefully this would dramatically speed up the process of generating some amount of text, because again, we are reusing some work that we had already done. This idea of saving some work from a request to be used later on is exactly what prompt caching is all about. So let's come back in just a moment and we're going to walk through some of the implementation details of prompt caching and really understand how it is implemented by Claude.

↑ 目次へ戻る

Building with the Claude API | Lesson 51「Rules of prompt caching」(全文ナレッジ)

動画 6分41秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンのテーマ

前回(理論編)に続き、prompt caching(プロンプトキャッシング)が Claude で実際にどう動くか=運用ルールを学ぶ。cache breakpoint の付け方・書式・完全一致条件・付与できる場所・結合順序・個数上限・最小トークン数まで、実装に必要なルールを一通り網羅する。

キャッシュの基本動作(write → read の2相プロセス)

  • initial request:Claude がメッセージを処理し、その作業成果を一時キャッシュに write する
  • follow-up request寸分違わず同一のメッセージを含んでいれば、再処理せずキャッシュから作業成果を read してロードする
  • キャッシュは永続ではなく、保持は1時間だけ
  • したがって「同じコンテンツを何度も何度も繰り返し送る」場面でだけ有効(ただしこれは実務上きわめて頻繁に起こる)。初回は必ず write のコストを払い、恩恵を受けられるのは follow-up だけ、という2段階(two-phase)プロセス

有効化は手動:cache_control(cache breakpoint)の明示追加が必須

  • キャッシュはデフォルトでは無効。メッセージ内のブロックに cache breakpoint を手動で追加して初めてオンになる
  • 書き方はブロックに "cache_control": {"type": "ephemeral"} フィールドを付けるだけ

ショートハンド記法では書けない → ロングハンドが必須

  • コースで多用してきた "content": "Hi there!"(文字列直接代入)のショートハンドには cache_control を置く場所がない
  • ロングハンドcontentリストを代入し、その中に {"type": "text", "text": ...} の辞書を置く形式にすれば、そこに cache_control フィールドを追加できる
  • つまり prompt caching を使いたい text ブロックはロングハンドで書き直す必要がある

breakpoint までの全コンテンツがキャッシュされる

  • breakpoint をあるブロックに置くと、リクエスト全体のうちその breakpoint まで(breakpoint を含む)のすべてのコンテンツがキャッシュ対象になる
  • breakpoint より後ろのブロックの処理はキャッシュされない(例:1つ目の text ブロックに breakpoint → 1つ目は Cached、2つ目の text ブロックは Not Cached)

follow-up はbreakpoint まで完全一致が条件

  • follow-up リクエストは breakpoint に至るまでの中身が完全に同一でなければキャッシュが使われない
  • 例:breakpoint 付きブロックの文頭に "please" を1語加えただけでもはや同一ではなくなり、キャッシュは使われず、そのブロック全体+それより前の全コンテンツが再処理される

複数メッセージ・複数ブロック跨ぎ可(assistant メッセージもキャッシュ可)

  • breakpoint は複数のメッセージ・複数のブロックをまたいで効く
  • 例:user → assistant → user と送り、最後のメッセージのブロックに breakpoint → 3メッセージ全部の処理成果がキャッシュされる(assistant メッセージも対象)
  • follow-up で breakpoint までが同一である限り、まとめてキャッシュから取り出される

付与できる場所は text ブロックに限らない

  • image ブロック、tool_use、tool_result など、他のほぼどんな種類のブロックにも付けられる
  • さらに tool schema(tool 定義)と system prompt にも付けられる
  • 多くのアプリでは system prompt と tools リストはリクエスト間で変化しない → 最も定番のキャッシュ好機であり、breakpoint を置く絶好の場所

内部では tools → system → messages の順に結合され単一キャッシュ

  • tool schema/system prompt/message ブロックの3箇所に置けるが、3つの独立したキャッシュシステムではない
  • 裏側では Claude に投入される際に「最初に tools、次に system prompt、最後に messages」の順で1本に結合される
  • したがって「最後の tool に breakpoint」→ tools まで全部キャッシュ・system と messages は対象外。follow-up で assistant メッセージを変えても、tools のキャッシュは生きたまま節約できる

breakpoint は最大4個まで置ける

  • 例:最後の tool schema に1個+途中の assistant メッセージに1個
  • 後段(下流)の user メッセージを変更 → tools+system+そこまでのメッセージの再処理を丸ごと節約
  • 前段(上流)の user メッセージを変更 → そこから下のキャッシュは無効化されるが、tools のキャッシュは残る
  • どこに置くかは各アプリケーション次第。tools 全体+system prompt+一定数のメッセージ、のように複数置くのが実務では一般的

最小コンテンツ長:1024 トークン

  • キャッシュ対象(キャッシュしようとしている全メッセージ/ブロックの合計)は最低 1024 トークン必要
  • "Hi there!" 1個に breakpoint を置いても 1024 トークンに満たないのでキャッシュに書き込まれない
  • 同じ text ブロックを 500 回複製すれば 1024 トークンを超え、そのブロックリスト全体がキャッシュされる

実務ポイント(このレッスンから持ち帰るもの)

  • prompt caching は「初回 write → follow-up read」の2相プロセスで、保持は1時間。同一コンテンツを繰り返し送るワークロード(チャット履歴の積み上げ、長文コンテキストの再利用、agent ループ)でのみ効く。
  • デフォルト無効。効かせたいブロックに "cache_control": {"type": "ephemeral"} を明示追加する。text ブロックはショートハンド(content に文字列直接代入)では書けないので、content をリスト+{"type":"text","text":...}ロングハンドに書き換える。
  • キャッシュヒットの条件は breakpoint まで完全一致。"please" 1語の追加でも無効化されるので、breakpoint より前に timestamp・乱数など毎回変わる文字列を入れない設計にする。
  • 実務の定番は tools の最後+system prompt への breakpoint(リクエスト間でほぼ変わらないため)。内部結合順は tools → system → messages なので、前段を安定・後段を可変にするのが基本形。
  • breakpoint は最大4個。多段に置けば「下流だけ変わった時は上流のキャッシュが全部生きる」「上流が変わっても tools 分は生きる」と段階的に節約できる。
  • 1024 トークン未満はキャッシュに書き込まれない(breakpoint を付けても静かに無視される)。短いコンテンツに付けて効いたつもりにならないこと。

本文(時系列・日本語逐語+画面差し込み)

[0:00] さて、理論を理解したところで、ここからは Claude のプロンプトキャッシングが実際にどう動くのかを探っていきます。プロンプトキャッシングの核となる考え方は、前回のビデオで話したものとまったく同じです。まず Claude に最初のリクエストを送ります。Claude はその最初のメッセージに対して何らかの処理を行い、そしてその作業の成果をすべて一時的なキャッシュに保存します。

🖥 [0:00] 画面(スライド「Prompt Caching」・前回の復習) 左側の箇条書き: - Requests that use cached content are cheaper and faster to execute(キャッシュ済みコンテンツを使うリクエストは、より安く・より速く実行される) - Initial request will write to the cache - Follow up requests can read from the cache - Cache lives for one hour - Only useful if you're repeatedly sending the same content(but this happens extremely frequently)

右側に上下2つの図: - Initial Request:「Please summarize this…」→ Claude ボックス内に「Work done to process message」→ Cache ボックス内の点線枠に「Write processing work here」 - Follow up Request:「Please summarize this…」→ Claude ボックス内の点線枠に「Read previously processed work here」→ Cache ボックス内に黒地の「Work done to process message」

[0:19] その後、将来のどこかの時点でフォローアップのリクエストを送り、そこに寸分違わずまったく同一のメッセージを含めた場合、Claude はそのメッセージを一から処理し直すのではなく、代わりにキャッシュの中を見て、すでに保存してあった作業を見つけ出し、それをロードします。

[0:35] 念のため明確にしておくと、私たちがキャッシュに保存した作業は永久に残るわけではありません。そこに保存されるのは1時間だけです。プロンプトキャッシングが最も役立つのは、同じコンテンツを Claude に何度も何度も何度も繰り返し送っている時だ、と分かるはずです。というのも、これは実質的に2段階のプロセスだからです。まず最初のリクエストを送ってキャッシュにデータを書き込む必要があり、そして事前に済ませておいたその作業の恩恵を受けられるのは、フォローアップのリクエストだけなのです。

[0:57] このキャッシュシステムは、Claude ではデフォルトで有効になっていません。キャッシングをオンにするには、いずれかのメッセージ内のブロックに cache breakpoint(キャッシュブレークポイント)を手動で追加する必要があります。

🖥 [1:00] 画面(スライド「Cache Breakpoints」) 左側の箇条書き: - Work done on messages is not cached automatically - We have to manually add a 'cache breakpoint' to a block - Work done for everything before the breakpoint will be cached - Cache will only be used on follow up requests if the content up to and including the breakpoint is identical

右側のコード例。cache_control の部分が枠でハイライトされ、「Cache Breakpoint」というラベルから矢印が指している: python user_message = { "role": "user", "content": [ { "type": "text", "text": "<Long prompt>", "cache_control": { "type": "ephemeral" } }, ] }

[1:08] その例を、右側のこのサンプル user メッセージの中に示してあります。キャッシングをオンにするためにやるべきことは、この小さな cache_control フィールドを入れる、それだけです。ただし、この cache_control というものが実際に何をするのかについては、たくさんのルールがあります。しかし、そのルールに入り込んで何が起きているのかをきちんと説明する前に、ここでちょっとしたコツ、皆さんの作業を少し楽にしてくれるものをお伝えしておきたいと思います。

[1:26] このコースを通して、私たちは text ブロックを書き出すためのちょっとしたショートハンド(省略記法)を非常に頻繁に使ってきました。それを左側に示しています。これは、text ブロックを定義するのにショートハンドを使っている user メッセージです。ほんの少しのテキストだけが割り当てられた user メッセージであれば、その content フィールドに文字列を直接代入できるのです。

🖥 [1:30] 画面(スライド「Shorthand / Longhand for writing a Text Block」) 左右2枚のコードパネル比較。 - 左「Shorthand for writing a Text Block」: python user_message = { "role": "user", "content": "Hi there!" } - 右「Longhand for writing a Text Block」: python user_message = { "role": "user", "content": [ { "type": "text", "text": "<Long prompt>", "cache_control": { "type": "ephemeral" } }, ] } 右パネルの下にキャプション:「Use longhand form when adding a cache breakpoint」

[1:50] text ブロックにはもう1つの書き方があり、その別の書き方を右側に示しています。もう1つの方法とは、content フィールドを書き、そこにリストを代入し、その中に、type が text である辞書と、実際のテキストを入れた text フィールドを持たせる、というものです。

[2:06] さて、これまでこのロングハンド(省略しない記法)はほとんど使ってきませんでした。しかし、cache_control を使いたい、つまりこのプロンプトキャッシング機能をオンにしたいのであれば、これらの text フィールドをロング形式で書き出さなければなりません。そうすることで初めて、あの cache_control フィールドを付け足せるのです。言い換えると、cache_control をどこかに書き出す必要がある、ということです。そして右側のこちらの書き方なら、それを置く場所があります。ショート形式を使うと、置く場所がないのです。

[2:25] ブロックに breakpoint を追加すると、リクエスト全体の中のすべてのコンテンツが、その breakpoint まで(breakpoint を含む)キャッシュされます。

🖥 [2:26] 画面(図「Initial Request」・breakpoint より前だけ Cached) 左に User Message の表(2ブロック): - 「Text Block with Breakpoint」|Summarize this long text: \<text> - 「Text Block」|Focus on…

中央の Claude ボックス内: - 「Work done to process "Summarize this…"」(上段) - 「Work done to process "Focus on…"」(下段)

上下の間に点線が引かれ、右側に「Cached」/「Not Cached」の注記。右端の Cache ボックス内には点線枠で「Write processing work here」。

[2:35] つまりこのシナリオでは、最初のブロックに breakpoint が付いたこのようなリクエストを送ると、Claude はここにあるこのテキストを処理するために一定量の作業を行い、その結果として一定量の作業成果が生まれます。この text ブロックには breakpoint が付いているので、この作業成果はキャッシュの中に保存されます。しかし、この後の方の作業は breakpoint より後ろにあります。だからキャッシュされません。

[2:52] その後、後からフォローアップのリクエストを送ると、Claude はキャッシュの中を確認し、この一番最初のブロックの処理についてはすでにいくらかの作業が済んでいたことを見つけます。そこでその作業成果を取り出して使い、自分の手間を少し省くのです。

🖥 [2:54] 画面(図「Follow Up Request」・キャッシュ read) 左の表は4行に増えている: - User Message:「Text Block with Breakpoint」|Summarize this long text: \<text>、「Text Block」|Focus on… - Assistant Message:「Text Block」|\<Summary> - User Message:「Text Block」|How does this compare to…

Claude ボックス内:最初のブロックだけ点線枠「Read previously processed work here」、残りは「Work done to process "Focus on…"」「Work done to process "Summary…"」「Work done to process "How does…"」と新規処理。 Cache ボックス内には前回書き込んだ「Work done to process "Summarize this…"」が置かれている。

[3:04] 1つ心に留めておくべきことは、フォローアップのリクエストは、その breakpoint に至るまでの中身が完全に同一でなければならない、という点です。例えば、breakpoint の付いたこの最初の text ブロックに、please という単語を1つ加えただけだとしましょう。

🖥 [3:06] 画面(スライド「Remember, content must be identical to cache!」) 先ほどと同じ4行の表だが、最初のブロックの中身が「Please summarize this long text: \<text>」に変わっている(Please が太字強調)。 Claude ボックス内では4つとも「Work done to process "Summarize this…"」「"Focus on…"」「"Summary…"」「"How does…"」と全部フレッシュに処理**され、右端の Cache ボックスは使われていない(空)。

[3:18] もはやこのコンテンツは同一ではありません。したがって、この作業成果はキャッシュから利用されなかったはずです。代わりに、Claude はこのブロック全体と、それより前にあるすべてのコンテンツを再処理することになります。

[3:28] cache breakpoint は、複数の異なるメッセージ、そして複数の異なるブロックをまたぐことができます。

🖥 [3:28] 画面(スライド「Cache Breakpoints span Messages and can cache Assistant Messages」) 左の表(3メッセージ): - User Message:「Text Block」|Summarize this long text: \<text> - Assistant Message:「Text Block」|\<Summary> - User Message:「Text Block with Breakpoint」|Focus the summary on…

Claude ボックス内に3つの「Work done to process "Summarize this…"/"Summary"/"Focus…"」、右端の Cache ボックス内には点線枠の「Write processing work here」が3つ並ぶ。3つ目のブロックの下に点線(キャッシュ境界)が引かれている。

[3:32] 例えば、user メッセージを送り、次に assistant、さらにもう1つ user を送るとして、ここの一番最後のメッセージに breakpoint 付きのブロックがあるとすると、ここにあるこのブロックまでのすべて(このブロックを含む)がキャッシュされます。つまり、これら3つのメッセージすべてを処理するために行われた作業が、キャッシュの中に保存される、とイメージできるわけです。

[3:50] その後フォローアップのリクエストを送る時、その breakpoint までのすべて(breakpoint を含む)が同一である限り、作業成果はキャッシュから取り出されます。そして再び、私たちは手間を少し省けるのです。

🖥 [3:50] 画面(図「Follow Up Request」・3ブロックまとめて read) 同じ3メッセージの表。Claude ボックス内は3つとも点線枠「Read processing work here」に変わり、右端の Cache ボックス内には「Work done to process "Summarize this…"」「Work done to process "Summary"」「Work done to process "Focus…"」の3つが実体として格納されている。

[4:01] キャッシュポイントを追加できるのは text ブロックに限りません。image ブロックや tool use、tool result のような、他のほぼどんな種類のブロックにも追加できます。さらに、tool schema にも、そして system prompt にも追加できます。その両方の例を右側に示してあります。

🖥 [4:01] 画面(スライド「Breakpoint Location」) 左側の箇条書き: - Not restricted to text blocks! - Can add cache breakpoints to system prompts and tool definitions - Useful when you have a long system prompt or a long list of tools - These are the most common caching opportunities since they rarely change between requests!

右上のコード(ラベル「Tool definitions will be cached」、cache_control 行がハイライト枠): python tools = [ add_duration_to_datetime_schema, get_current_datetime_schema, { "name": "set_reminder", "description": "Sets a reminder...", "input_schema": { ... }, "cache_control": {"type": "ephemeral"} } ] 右下のコード(ラベル「System prompt will be cached」、cache_control 行がハイライト枠): python system = [ { "type": "text", "text": "You are a senior software...", "cache_control": {"type": "ephemeral"} } ]

[4:17] 皆さんは、tools に対しても system prompt に対してもキャッシングを有効にすることが非常に多くなるはずです。というのも、すべてではないにせよ多くのアプリケーションでは、system prompt と tools のリストは結局のところ変化しない、と分かっているからです。だからこれらは、cache breakpoint を置くのに絶好の場所なのです。

[4:33] まとめると、breakpoint は tool schema、system prompt、そして message ブロックに適用できます。ただし、これらは3つの独立したキャッシュシステムではありません。それがどういう意味なのか、正確にお見せしましょう。

[4:44] tools、system prompt、messages を追加すると、その裏側では、Claude に投入される時にこれらすべてが結合されます。しかも、ある特定の順序で結合されます。最初に tools、次に system prompt、そして最後にメッセージのリスト、という順です。

🖥 [4:43] 画面(スライド「Cache Ordering」) 左右2本の縦積みスタック(上から下へ矢印)。 - Request #1:Tool #1 → Tool #2 → Tool #3 Cache Breakpoint(強調色)→ System Prompt → User Message → Assistant Message → User Message - Request #2:Tool #1 → Tool #2 → Tool #3 Cache Breakpoint → System Prompt → User Message → Assistant Message Changed(赤で強調)→ User Message

内部結合が「tools → system prompt → messages」の1本の並びであることを示す図。

[4:57] したがって、一番最後の tool に cache breakpoint を置くと、その最後の tool までのすべて(それを含む)がキャッシュされますが、system prompt とメッセージのリストはキャッシュされません。その後フォローアップのリクエストを送り、そこにある assistant メッセージを変更したとしても、まったく問題ありません。tools のリストは事前にキャッシュされていたので、それでも作業を少し節約できるのです。

[5:19] 最後に手短に触れておきたいのは、cache breakpoint は複数追加できて、合計で最大4個まで、ということです。例えば私は、渡す最後の tool schema に cache breakpoint を追加し、さらにこの下にある assistant メッセージにも cache breakpoint を追加する、と決めるかもしれません。

🖥 [5:30] 画面(スライド「Cache Ordering」・breakpoint 2個のパターン) - Request #1:Tool #1 → Tool #2 → Tool #3 Cache Breakpoint → System Prompt → User Message → Assistant Message Cache Breakpoint(どちらも強調色)→ User Message - Request #2:Tool #1 → Tool #2 → Tool #3 Cache Breakpoint → System Prompt → User Message Changed(赤で強調)→ Assistant Message Cache Breakpoint → Assistant Message

上流(Tool #3)と下流(Assistant Message)の2箇所に breakpoint があり、間の User Message が変更されたケースを示す。

[5:32] その後フォローアップのリクエストを送って、この下の方の user メッセージを変更しても、問題ありません。tools のリスト全体と system prompt、さらに user メッセージまでを再処理しなければならない作業を、丸ごと節約できます。同様に、最初の user メッセージを変更した場合は、そう、ここから下のすべてについてキャッシュが無効になりますが、tools のリストについてはキャッシュ済みの作業がまだ残っています。

[5:51] というわけで、適切な場面では、複数の異なる cache breakpoint を追加することが非常に多くなるはずです。tools のリスト全体と system prompt、さらにもしかすると一定数のメッセージもキャッシュする、と決めるかもしれません。これらの breakpoint を正確にどこへ置くかは、結局のところ、皆さんの個々のアプリケーション次第です。

[6:06] 一番最後に共有しておきたいのは、キャッシングには最小コンテンツ長がある、ということです。何らかの量のコンテンツをキャッシュするためには、少なくとも 1024 トークンをキャッシュしなければなりません。

🖥 [6:09] 画面(スライド「Minimum Content Length」) 左側の箇条書き: - Content must be at least 1024 tokens long to be cached (sum of all messages/blocks you're trying to cache)

右上のコード(キャプション「"Hi there!" isn't 1024 tokens long – not written to cache」): python conversation = [{ "role": "user", "content": [ { "type": "text", "text": "Hi there!", "cache_control": { "type": "ephemeral" } }, ] }] 右下のコード(キャプション「"Hi there!" duplicated 500 times is >1024 tokens. Cached!」): python conversation = [{ "role": "user", "content": [ {"type": "text", "text": "Hi there!"}, {"type": "text", "text": "Hi there!"}, {"type": "text", "text": "Hi there!"}, # repeat 500 times... { "type": "text", "text": "Hi there!", "cache_control": { "type": "ephemeral" } }, ] }]

[6:20] 右上の例では、"hi there" というテキストしか持たないメッセージに cache breakpoint を置いています。これは明らかに 1024 トークンの長さはありません。だからこのコンテンツはキャッシュに書き込まれないでしょう。しかし、その text ブロックを取って 500 回複製すれば、今度はおそらく 1024 トークンを超えています。だから、この異なるブロックのリスト全体がキャッシュされることになります。

英語逐語(ASR全文)

Now that we understand the theory, we are going to explore how prompt caching Claude actually works. The core idea of prompt caching is identical to what we discussed in the last video. We will make an initial request off to Claude. Claude is going to do some processing on that initial message, and then Claude is going to save all that work into a temporary cache. Then, if we make a follow-up request at some future point in time and include the identical exact same message, rather than processing that message all over again, Claude is going to instead look into the cache, find the work that it had already saved, and load it up. Just to be clear, the work that we saved to the cache does not persist forever. It is only stored there for one hour. You're going to find prompt caching is most useful anytime that you are repeatedly sending the same content over to Claude again and again and again, because it really is this kind of two-phase process. We have to make that initial request to write some data into the cache, and then only the follow-up requests are going to be able to take advantage of that work that is done ahead of time. This caching system is not enabled by default with Claude. Instead, to turn on caching, we have to manually add a cache breakpoint to a block inside of one of our different messages. And I've showed an example of that on the right hand side in this example user message. So all we have to do to turn on caching is put in that little cache control field. And there's a lot of rules around what that cache control thing actually does. But before we go into those rules and really explain what's going on, I want to give you a little tip here, something that's going to make your life a little bit easier. Throughout this course, we've been making use of a little shorthand for writing out text blocks very frequently, and I'm showing that on the left-hand side. So this is a user message that is using the shorthand for defining a text block. If we have a user message that only has a little bit of text assigned to it, we can assign a string directly to that content field. There's another way in which we can write out a text block, and I'm showing this alternative way of writing out a text block on the right-hand side. So the alternative method is to write out a content field, assign a list to it, and then inside there, put a dictionary that has a type of text and then a text field that contains your actual text. Now we have not really been making use of that longhand form, but if you want to use cache control, you want to turn on this prompt caching feature, you have to write out these text fields in long form. So you can actually add on that cache control field. In other words, we have to write out cache control somewhere. And over here on the right-hand side, we have some place to put it. If we use the short form, there's no place for us to put it. When we add a breakpoint into a block, all the content in our entire request is going to be cached up to and including that breakpoint. So in this scenario, if we send in a request like this, where the first block has a breakpoint, Claude is going to do some amount of work processing this text right here, and it's going to result in some amount of work done. Because this text block has a breakpoint, this work is going to be stored inside the cache. But this later work is after the breakpoint. So it's not going to be cache. If we then send a follow-up request later on, Claude is going to take a look into the cache and find that some work was already done for processing this very first block. So it's going to retrieve that work and use it to save itself a little bit of effort. One thing to keep in mind is that our follow-up request must have identical content inside of it all the way up to that breakpoint. So for example, if our initial text block right here that had the breakpoint, if we added in just the word please to it, No longer is this content identical. And so this work would not have been used as out of the cache. Instead, Claude would reprocess this entire block and all the content before it. Cache breakpoints can span across multiple different messages and multiple different blocks. So for example, if we send in a user message and then an assistant and then another user, and the very last message here has a block with a breakpoint, everything is going to be cached up to and including this block right here. So we'd imagine that the work that is done to process all three of those messages is going to be stored inside the cache. Then when we make our follow request later on, as long as everything up to and including that breakpoint is identical, the work is going to be retrieved out of the cache. And again, we're going to save ourselves a little bit of effort. We are not restricted to adding cache points onto text blocks. We can also add them onto almost any other type of block, like an image block or a tool use or a tool result. We can also add these onto tool schemas and onto system prompts as well. And I've shown an example of both those on the right-hand side. You are very often going to enable caching for your tools and for your system prompt as well, because it turns out that for most applications, not all, but for many applications, your system prompt and your list of tools don't end up changing. So these are excellent places to place a cache breakpoint. In total, we can apply a breakpoint to tool schemas, system prompts, and message blocks. Now, these are not three separate cache systems. And let me show you exactly what I mean by that. Whenever you add in tools, a system prompt, and messages, behind the scenes, these all get joined together when they are fed into CLOT, and they get joined together in that particular order. It is first the tools, then the system prompt, and then your list of messages. So if you place a cache breakpoint on your very last tool, everything up to and including that last tool will be cached. but the system prompt and your list of messages will not be cached. So if we then make a follow request and we change that assistant message right there, totally fine. We're still going to save ourselves a little bit of work because the list of tools was cached ahead of time. The last thing I want to mention very quickly is that we can add in multiple different cache breakpoints up to four in total. So I might decide to add in a cache breakpoint at the last tool schema that I pass in. And then maybe I add in a cache breakpoint on this assistant message down here. If I then make a follow-up request and I change the user message down here, no problem. We're going to save ourselves the work of having to reprocess the entire list of tools and system prompt and user message as well. Likewise, if we change the first user message, well, then we're going to invalidate the cache for everything down here, but we'll still have the cache work for our list of tools. So we are very often going to add in multiple different cache breakpoints, if appropriate. We might decide to cache our entire list of tools and the system prompt and maybe some number of messages as well. Exactly where you place these different breakpoints really just comes down to your particular application. The very last thing I want to share with you is that there is a minimum content length for caching. So in order to cache some amount of content, we must cache at least 1024 tokens. So on the top right hand example, I've got a cache breakpoint on a message that has only the text, hi there. This is definitely not 1024 tokens long, so this content would not be written to the cache. But if I took that text block and duplicate it 500 times, now I've probably got greater than 1024 tokens, so this entire list of different blocks would be cache.

↑ 目次へ戻る

Building with the Claude API | Lesson 52「Prompt caching in action」(全文ナレッジ)

動画 7分21秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンのテーマ

前レッスンで学んだ prompt caching(プロンプトキャッシング)の概念を、実際のノートブック(003_caching.ipynb)で手を動かして確かめる実践編。chat() ヘルパー関数に tools とシステムプロンプトのキャッシュをデフォルトで組み込み、レスポンスの usage フィールドの実測値でキャッシュの書込(write)・読出(read)・無効化(invalidation)を1つずつ観察する。

実験素材(003_caching.ipynb)

  • 約6kトークンの巨大プロンプト code_prompt(「Javascript Code Generator for Document Analysis Flow Builder」=QuickJS サンドボックス向けコード生成の長大な指示書)→ システムプロンプトとして使用
  • ツールスキーマ4本db_query_schema / add_duration_to_datetime_schema / set_reminder_schema / get_current_datetime_schema)→ 合計約1.7kトークン
  • chat() ヘルパー関数params 辞書を動的に組み立てて client.messages.create(**params) を呼ぶ形)。中に2つの TODO:①tools リストが渡されたら常にキャッシュ、②システムプロンプトが渡されたら常にキャッシュ。1リクエスト内に複数のキャッシュブレークポイントを置けるので、両方渡せばブレークポイントは2つになる。

実装①:tools の「最後のスキーマ」に cache_control(copy パターン)

  • キャッシュブレークポイントは Claude に渡す一番最後のツールスキーマcache_control フィールドを付けて設定する。
  • 素朴には tools[-1]["cache_control"] = {"type": "ephemeral"} でも動くが、渡された tools リストの中身そのものを破壊的に書き換えてしまうのが難点。後でアプリ側がツールの順序を入れ替えると、cache_control 付きスキーマが複数箇所に散らばり、意図しない複数ブレークポイントができてしまう恐れがある。
  • そこで定石は「コピーしてから付ける」:
if tools:
    tools_clone = tools.copy()
    last_tool = tools_clone[-1].copy()
    last_tool["cache_control"] = {"type": "ephemeral"}
    tools_clone[-1] = last_tool
    params["tools"] = tools_clone
  • このコピーのロジックは厳密には必須ではなく good practice(将来ツールリストを変更する場合への備え)。

実装②:system を text ブロックのリストにして cache_control

  • system は文字列のままではブレークポイントを付けられないので、text ブロックの辞書を入れたリストに置き換える:
if system:
    params["system"] = [
        {
            "type": "text",
            "text": system,
            "cache_control": {"type": "ephemeral"},
        }
    ]

計測実験:usage フィールドの読み方(実測値つき)

usagecache_creation_input_tokens=キャッシュへの書込量cache_read_input_tokens=キャッシュからの読出量。実験の流れと実測値:

実験ステップ usage の実測値(画面) 意味
①ツールなし・プロンプトなしで what's 1+1? input_tokens=14, output_tokens=11、cache 系は 0 キャッシュ不使用のベースライン
tools=tools を付けて初回実行 cache_creation_input_tokens≈1,700(ナレーション) ブレークポイントを検知し約1.7kトークンをキャッシュへ書込(初回=write)
③何も変えず即再実行 cache_read_input_tokens=1773 2回目=キャッシュ読出(read)。保存→取り出しに成功
④ユーザーメッセージだけ変更(末尾の ? を削除) 引き続き cache read キャッシュ順序は tools → system → messages。後段(メッセージ)の変更は前段のキャッシュを壊さない
⑤先頭ツールの description を1文字変更(adds→add) cache_creation_input_tokens=1772、read は 0 ツールが1文字でも変わればキャッシュ無効化=Claude から見れば「完全に別物のリスト」で再書込
system=code_prompt を追加 cache read 1,700 + cache write 6,320(ナレーション読み上げ) tools は同一→読出、system は新規ブレークポイント→書込が同時に起きる(部分ヒット)
⑦プロンプト末尾の単語 "builder" を削除して再実行 cache_read_input_tokens=1772cache_creation_input_tokens=6331 tools 分は読出継続、system は「完全に別のプロンプト」として再書込。system 分の旧キャッシュは全損

キャッシュの性質(クロージングのまとめスライドより)

  • キャッシュ済みコンテンツを使うリクエストは安く・速く実行できる
  • 初回リクエストがキャッシュへ write、追随リクエストが read
  • キャッシュの寿命は1時間(スライド表記)
  • 同一コンテンツを繰り返し送る時だけ有効——ただしそれは(同じ messages・同じツールスキーマ・同じシステムプロンプトという形で)極めて頻繁に起きる

実務ポイント(このレッスンから持ち帰るもの)

  • キャッシュが効いているかは usage で計測するcache_creation_input_tokens(書込)と cache_read_input_tokens(読出)。初回=write、2回目以降=read になっていれば正常動作。
  • ヘルパー関数にキャッシュをデフォルトで組み込むのが実務の型:tools は最後のスキーマに、system は text ブロック化して cache_control: {"type": "ephemeral"} を付ける。1リクエストに複数ブレークポイント可。
  • 渡された tools を直接 mutate せず、copy してから cache_control を付与する。後からツール順序を変えた時に cache_control 付きスキーマが散らばって意図しない複数ブレークポイントになる事故を防ぐ(必須ではないが good practice)。
  • プレフィックスの順序は tools → system → messages。メッセージだけ変えても tools/system のキャッシュは生きる。逆に tools や system を1文字変えただけ(description の s 1個、プロンプト末尾の1単語)で、そのブレークポイントのキャッシュは全無効=書込やり直し。
  • 同一内容(同じメッセージ列・同じツールスキーマ・同じシステムプロンプト)を繰り返し送る場面では常に prompt caching を使う、が定石。

本文(時系列・日本語逐語+画面差し込み)

[0:00] いよいよ prompt caching を実際に手を動かして試していきます。003 caching という新しいノートブックを作りました。もちろん、このレクチャーに添付されています。この中には「prompt with 6k tokens(約6kトークンのプロンプト)」というセクションがあります。これはシステムプロンプトとして使うためのもので、この後すぐ使います。加えて、tool schema(ツールスキーマ)のセクションもあります。その中には複数の異なるツールスキーマが定義されています。これらのツールスキーマを全部合わせると、合計およそ1.7kトークンになります。

🖥 [0:05] 画面(Jupyter ノートブック 003_caching.ipynb VS Code + Jupyter。ウィンドウタイトル「003_caching.ipynb — 08」。上部に実行済み(✓)の折りたたみセクション「# Load env variables and create client」「# Helper functions」。その下に「# Prompt with ~6k Tokens」のセル(実行カウント [4]): ```python

Prompt with ~6k Tokens

code_prompt = """

Javascript Code Generator for Document Analysis Flow Builder

You are an expert Javascript code generator. Your specialty is creating code for a document ana…(右端見切れ)

Your Goal: Generate functional Typescript code that defines both the logic and user interface f…(右端見切れ)

Think of this as writing code for a very specific, constrained platform. Standard web developm…(右端見切れ)

Constraints and Environment Details:

  1. Sandboxed Javascript (QuickJS) Environment:

Your code operates within a QuickJS sandbox. This means you have a restricted set of pre-defin…(右端見切れ) ```

[0:25] これから、この上の「helper functions」というセルの中にある chat 関数を更新していきます。chat 関数が、ツールスキーマとシステムプロンプトに対して、デフォルトで常に prompt caching を有効にするようにします。では、そのやり方をお見せしましょう。

[0:39] helper functions のセルの中で、chat 関数までスクロールします。ここ、これですね。もう少し下にスクロールすると、私が2つの TODO 項目を追加してあるのに気づくはずです。1つ目は、tools のリストが渡されたら、常にそのツールのリストをキャッシュしたい、というものです。

🖥 [0:44] 画面(chat() 関数の中身・params 動的組み立て部) chat 関数のシグネチャ末尾(thinking_budget=1024, / ):)から params 辞書と分岐が見える(セル実行カウント [27]): ```python params = { "model": model, "max_tokens": 4000, "messages": messages, "temperature": temperature, "stop_sequences": stop_sequences, }

if thinking:
    params["thinking"] = {
        "type": "enabled",
        "budget_tokens": thinking_budget,
    }

if tools:
    # TODO: always cache tools
    params["tools"] = tools

```

[0:54] 2つ目は、システムプロンプトを渡す場合、それも同様にキャッシュしたい、というものです。1つのリクエストの中には複数の異なるキャッシュブレークポイントを置けたことを思い出してください。つまり、システムプロンプトとツールのリストの両方を渡すことになった場合は、2つの異なるキャッシュブレークポイントを設定することになるわけです。

🖥 [1:00] 画面(2つの TODO) ```python if tools: # TODO: always cache tools params["tools"] = tools

if system:
    # TODO: always cache the system prompt
    params["system"] = system

message = client.messages.create(**params)
return message

`` その下にdef text_from_message(message):/return "\n".join(` の冒頭が見える。

[1:09] まず、ツールのリストのキャッシュから片付けましょう。これをやるには、Claude に渡す一番最後のツールスキーマを変更する必要があったのを思い出してください。具体的には、あの cache_control フィールドを付け足す必要があります。ですから、こんなふうにもできます。単に tools のマイナス1番目(最後の要素)を指定して、その上に type が ephemeral の cache_control フィールドを、このように設定するのです。

🖥 [1:30] 画面(素朴な書き方=直接 mutate) python if tools: tools[-1]["cache_control"] = {"type": "ephemeral"} params["tools"] = tools

[1:34] これでも間違いなく動きますが、コーディングのテクニックとしてはベストではありません。というのも、これは実際に私たちのツールスキーマそのものを、cache_control フィールドを追加する形で書き換えてしまうからです。アプリケーションによっては、後になって、渡すツールスキーマの順序を変更しようと決める場面があるかもしれません。そうすると、ツールスキーマの中に複数の異なるキャッシュブレークポイントが設定されてしまうことになり、それは私たちが望むものとは違うかもしれません。

[1:56] そこで、もう少し良いやり方は、まずツールのリストのコピーを作ることです。それから、その中の最後のツールスキーマをクローンして、そこに cache_control フィールドを追加します。では、そのやり方をお見せしましょう。

🖥 [2:00] 画面(copy パターンへの書き換え・途中) ```python if tools: tools_clone = tools.copy()

    params["tools"] = tools

``tools_clone` の下に波線(この時点では未使用の警告)。

[2:09] tools_clone という新しい変数を作ります。これは tools.copy の呼び出しから得られます。これでリストの完全なコピーができます。次に、その中の一番最後のツールをコピーします。それから、その最後のツールスキーマに cache_control フィールドを付け足します。type は ephemeral です。

🖥 [2:30] 画面(copy パターン・入力途中) python if tools: tools_clone = tools.copy() last_tool = tools_clone[-1].copy() last_tool["cache_control"] = {"type": "ephemeral"} tools_clone[-1] ← ここまで入力中(カーソル) params["tools"] = tools

[2:34] それから、tools_clone の最後の要素を、いま作ったツールスキーマのコピーで上書きします。tools_clone のマイナス1番目が last_tool になります。そして、tools パラメータには私の tools_clone を代入します。

[2:51] ここで手短に繰り返しておくと、ここにあるこのコピーのロジックは全て、厳密には必須ではありません。あくまで良い習慣というだけです。繰り返しますが、いつかツールのリストを変更しようと決めた場合に備えてのものです。

🖥 [3:00] 画面(tools 側の完成形+system 側の編集開始) ```python if tools: tools_clone = tools.copy() last_tool = tools_clone[-1].copy() last_tool["cache_control"] = {"type": "ephemeral"} tools_clone[-1] = last_tool params["tools"] = tools_clone

if system:
    params["system"] = [

    ]

message = client.messages.create(**params)
return message

`` (params["system"] = [ ]の空リストを入力した直後。下にdef text_from_message(message): return "\n".join([block.text for block in message.content if block.type == "text"])` が見える。セル [27] ✓ 0.0s)

[3:02] 次は、2つ目の TODO を片付けます。システムプロンプトを渡した場合には、必ずそこにキャッシュブレークポイントを設定するようにしたいのです。そのために、コメントを削除します。system をリストに置き換えます。この中に text ブロックを入れます。つまり、type が text の辞書で、

[3:20] text は system、そして最後に cache_control の type は ephemeral です。これで完了です。

🖥 [3:30] 画面(テスト用の最下部セル・未実行) Markdown 見出し「# Tool Schemas, ~1.7k tokens」(実行済✓)。その下のテストセル(まだ未実行): ```python tools = [ db_query_schema, add_duration_to_datetime_schema, set_reminder_schema, get_current_datetime_schema, ] messages = []

add_user_message(messages, "what's 1+1?")

chat(messages) ```

[3:33] よし、それではセルを実行しましょう。一番下まで行って、いま設定したこのキャッシュをテストします。この一番下のところでは、すでにツールのリストを定義してあります。これらは、すぐ上のセルの中で定義されているツールスキーマです。それから、あの非常に大きなシステムプロンプト、code_prompt もここにあります。まずは、何も渡さないところから試してみましょう。ツールのリストもなし、プロンプトも一切なしです。「1+1は何?」というメッセージを処理して応答を生成するのに使われるトークン数を、ただ見てみます。

[3:57] これを実行すると、出力が返ってきて、この中に usage フィールドがあることに気づけます。どうやら14トークンを送り込んで、11トークンが出てきたようです。それでは、ツールのリストを追加してみましょう。

🖥 [4:00] 画面(ツールなし実行の結果+tools=tools を追記した直後) chat 呼び出しが chat(messages, tools=tools) に書き換えられた直後(まだ再実行前)。下の出力は直前の「ツールなし」実行([30]・0.9s)のもの: Message(id='msg_01TVySrgZQ2emwySFR2mS7sX', content=[TextBlock(citations=None, text='1+1 = 2', type='text')], model='claude-3-7-sonnet-latest', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(cache_creation_input_tokens=0, cache_read_input_tokens=0, input_tokens=14, output_tokens=11, server_tool_use=None))output_tokens=11 付近がハイライト。キャッシュ系は全て 0、入力14・出力11)

[4:13] これを追加して実行すると、今度は大きく違う usage フィールドが見られます。今度は usage に cache_creation_input_tokens の1700があります。これは、私たちがスキーマをキャッシュしたいことを Claude が認識し、合計およそ1700トークンをキャッシュに書き込んだ、という意味です。

[4:32] では、何も変えずにすぐさま追いかけのリクエストを送ると、今度は一定数のトークンをキャッシュから読み出すことになるのが分かります。今度は1700のキャッシュ読み出しです。これは、スキーマをキャッシュの中に保存することに成功し、その後、未来のある時点でそれを取り出した、という意味です。

🖥 [4:30] 画面(追いかけリクエスト=キャッシュ読出) セル [32]・1.8s。出力: Message(id='msg_01MworVhN4ApVej7xs3wkMbQ', content=[TextBlock(citations=None, text="I don't need to use any specialized tools to answer this simple arithmetic question.\n\n1+1 = 2\n\nThis is a basic addition problem that doesn't require database queries, datetime manipulation, or reminders. Is there anything else I can help you with?", type='text')], model='claude-3-7-sonnet-latest', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(cache_creation_input_tokens=0, cache_read_input_tokens=1773, input_tokens=152, output_tokens=58, server_tool_use=None))cache_read_input_tokens=1773 がハイライト。ナレーションの「1700」は概数で、実測は 1,773 トークンの読出)

[4:53] さて、ここでユーザーメッセージを何かしら変更したら――例えば末尾のクエスチョンマークを削除して――再実行しても、やはりキャッシュから読み出します。なぜなら、キャッシングの順序は、ツールのリスト、次にシステムプロンプト、そして各メッセージ、だったのを思い出してください。だから、このように、依然としてキャッシュから読み出せるのです。

🖥 [5:00] 画面("?" を削除して再実行=それでも cache read) ユーザーメッセージが add_user_message(messages, "what's 1+1")? なし)に変更済み。セル [33]・2.5s。出力: Message(id='msg_013d7LXcyo1ikdmtaeQwnnmc', content=[TextBlock(citations=None, text='To answer a basic arithmetic question like "what\'s 1+1", I don\'t need to use any of the available tools. This is a simple calculation I can answer directly:\n\n1+1 = 2\n\nThis is a fundamental arithmetic operation where we\'re adding the number 1 to itself, which equals 2.', type='text')], model='claude-3-7-sonnet-latest', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(cache_creation_input_tokens=0,(下端見切れ) (usage の続きはフレーム下端で見切れ。cache_creation_input_tokens=0 まで確認できる)

[5:09] しかし、ツールのどれかを少しでも変更すると、キャッシュを無効化してしまいます。ツールのリストのところまで上がって、一番最初のツールの description を変更します。「adds」という単語の S を取り除きます。これで「add a specified duration」になりました。ここでセルを再実行すると、私はツールスキーマを変更したことになります。つまり、ツール全体に適用していたキャッシュブレークポイントは、もはや適用されなくなるということです。

[5:32] 一番下のセルをもう一度実行すると、usage の更新された値が見られます。今度はまたキャッシュ書き込みになります。もう読み出しではありません。書き込みに戻ったのです。Claude から見れば完全に別物のツールのリストを送り込んだからです。

🖥 [5:30] 画面(ツール1文字変更後の再実行=cache write に逆戻り) セル [35]・2.6s、呼び出しは chat(messages, tools=tools)。出力: Message(id='msg_019sXbeCDUku1hBrMNUuHetZ', content=[TextBlock(citations=None, text='The question "what\'s 1+1" is a simple arithmetic calculation. The answer is 2.\n\nThis is a basic mathematical addition that doesn\'t require any of the specific tools I have available. The tools I have access to are for database queries, datetime operations, setting reminders, and getting the current date and time — none of which are needed to answer this arithmetic question.', type='text')], model='claude-3-7-sonnet-latest', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(cache_creation_input_tokens=1772, cache_read_input_tokens=0, input_tokens=151, output_tokens=82, server_tool_use=None))cache_creation_input_tokens=1772 付近がハイライト。description の s を1文字消しただけで読出 0・書込 1,772 に)

[5:49] よし。では今度はシステムプロンプトを追加してみましょう。chat 関数のところへ行って、system=code_prompt を追加します。これでこのリクエストを送る時、キャッシングの順序を思い出してください。ツール、次にシステムプロンプト、それからメッセージのリストです。ツールのリストは完全に同一のまま、システムプロンプトの方を変えているので、部分的なキャッシュ読み出しと、それと同時にキャッシュ書き込みが見られるはずだと予想します。見られるはずのキャッシュ読み出しは、同じツールのリストを使い回しているからです。そしてキャッシュ書き込みは、この新しいプロンプトを送ることで新しいキャッシュブレークポイントを設定するからです。

🖥 [6:00] 画面(system=code_prompt を追記) 呼び出し行が chat(messages, tools=tools, system=code_prompt) になり、system=code_prompt が選択ハイライトされている。下にはまだ直前の [35] 出力(cache_creation_input_tokens=1772 のもの)が残っている。

[6:24] では、これを実行します。すると――ほら、出ました――1,700のキャッシュ読み出しと、6,320のキャッシュ書き込みが見えるはずです。

🖥 [6:30] 画面(ノートブック全体像・システムプロンプト付き初回実行) セクション見出し「# Load env variables and create client」「# Helper functions」「# Prompt with ~6k Tokens」「# Tool Schemas, ~1.7k tokens」がすべて実行済✓で並ぶ。最下部のテストセル(実行カウント [36]): ```python tools = [ db_query_schema, add_duration_to_datetime_schema, set_reminder_schema, get_current_datetime_schema, ] messages = []

add_user_message(messages, "what's 1+1")

chat(messages, tools=tools, system=code_prompt) ``` (このフレームでは出力部分は画面外。ナレーションの読み上げでは cache read 1,700・cache write 6,320)

[6:33] さて、ツールのリストの時とちょうど同じように、システムプロンプトのところへ上がって、このプロンプトを何かしら変更したら――例えば一番最後にある「builder」という単語を削除するだけでも――そしてそのセルを再実行します。するとまたしても、Claude から見れば、次のリクエストを送れば、これは完全に別のシステムプロンプトです。ですから、システムプロンプトまわりで以前持っていたキャッシュデータは、全て失うことになります。

[6:54] では、もう一度これを送ります。すると再び、約1.7kのキャッシュ読み出しが見られます。ほら、出ました。そして再び、この真新しいシステムプロンプトを書き込んでいます。それがもう1回分の約6.3kです。

🖥 [7:00] 画面(まとめスライド「Prompt Caching」) Anthropic のスライドにカット。見出し:Prompt Caching。左側の箇条書き: - Requests that use cached content are cheaper and faster to execute(キャッシュ済みコンテンツを使うリクエストは、より安く・速く実行できる) - Initial request will write to the cache(最初のリクエストがキャッシュへ書き込む) - Follow up requests can read from the cache(後続リクエストはキャッシュから読み出せる) - Cache lives for one hour(キャッシュの寿命は1時間) - Only useful if you're repeatedly sending the same content(同じコンテンツを繰り返し送る場合にのみ有効)(but this happens extremely frequently)(ただしそれは極めて頻繁に起きる)

右側の図:上段 Initial Request("Please summarize this…" → Claude「Work done to process message」/Cache 側は点線で「Write processing work here」)、下段 Follow up Request("Please summarize this…" → Claude 側は点線で「Read previously processed work here」/Cache「Work done to process message」)。左下に ANTHROP\C ロゴ。

[7:08] というわけで、皆さん、これが prompt caching です。繰り返しますが、同一のコンテンツを送り込む時はいつでも――それが同じメッセージのリスト、同じツールスキーマ、あるいは同じシステムプロンプトという形であれ――prompt caching を非常に頻繁に使うことになるはずです。

🖥 [7:09] 画面(ノートブックに戻る・最終実行の結果) 画面はノートブックに戻り、"builder" 削除後の再実行(セル [38]・1.8s)の出力が表示されている: Message(id='msg_01W9BHiw7UVm1JbLHWFyrp7S', content=[TextBlock(citations=None, text="The answer to 1+1 is 2.\n\nThis is a basic arithmetic addition. Is there anything else you'd like to know or any specific document analysis workflow you'd like me to help you create?", type='text')], model='claude-3-7-sonnet-latest', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(cache_creation_input_tokens=6331, cache_read_input_tokens=1772, input_tokens=147, output_tokens=47, server_tool_use=None)) (ツール分の 1,772 トークンはキャッシュ読出のまま、新しいシステムプロンプト 6,331 トークンを再書込。部分ヒットの実例)

英語逐語(ASR全文)

Time to get our hands dirty with prompt caching. I've created a new notebook called 003 caching. You will of course find it attached to this lecture. Inside of here you'll find a section called prompt with 6k tokens. So this is meant to serve as a system prompt. We're going to use it in a little bit. In addition there is a tool schema section. It has several different tool schemas to find inside of it. All these different tool schemas put together total up to about 1.7k tokens. We're going to work on updating our chat function which is inside the cell up here called helper functions, we're going to make sure that our chat function always enables prompt caching for our tool schemas and our system prompt by default. So let me show you how you would do that. Inside the helper function cell, I'm going to scroll down to our chat function. Here it is right here. I'm going to scroll down a little bit further and you'll notice I added in two to-do items. First, if a tools list is provided, I want to always cache the list of tools. Secondly, if we provide a system prompt, I want to cache that as well. Remember that we can have multiple different cache breakpoints inside of a single request. So if we end up passing both a system prompt and a list of tools, we're going to set two different cache breakpoints. Let's first take care of caching our list of tools. Now to do so, remember we need to modify the very last tool schema that we pass into Claude. We need to, in particular, add on that cache control field. So we could do something like this. We could just say tools negative one, and then set a cache control field on it of type ephemeral like so. This would definitely work, but it's not the best coding technique. You see, this will actually modify our tool schema by adding in the cache control field to it. There might be some scenario in our application where we later on decide to change the order of our tool schemas that we're passing in. And if we did so, we would then end up with multiple different cache breakpoints being set up inside of our tool schema, which might not be exactly what we desire. So a little bit better way of doing this would be to first create a copy of our tools list. Then we will clone the last tool schema inside there and add the cache control field to it. So let me show you how to do that. I'm going to make a new variable called tools clone, which will come from calling tools.copy. So that's going to make a total copy of the list. Next, I'm going to copy the very last tool inside there. I'm then going to add on the cache control field to the last tool schema. So that will be type ephemeral. Then I'm going to overwrite the last element inside tools clone with the tool schema copy that I just made. Tools clone at negative one will be the last tool. And I'm going to assign my tools clone to the tools param. Now, just to repeat here really quickly, all this copy logic I have inside of here, it's not strictly necessary. It's just good practice, again, in case we ever decide to change our list of tools at some point in time. Next up, we'll take care of the second to do. So if we pass in a system prompt, I want to make sure that we always set a cache breakpoint on it. To do so, I'm going to remove the comment. I will replace system with a list. We're going to put inside of here a text block. So it will be a dictionary with a type of text. text of system, and then finally a cache control type ephemeral. And that's it. All right, so let's now run the cell. We're going to go down to the very bottom and test out this caching that we have set up. So down here at the very bottom, I've already defined a list of tools. These are tool schemas that are defined inside the cell right above. We've also got that very large system prompt right here of code prompt. So let's first try just passing in nothing at all. So no list of tools, no prompt whatsoever. And we're just going to see the number of tokens that are used to process the message of what's one plus one and generate a response. So if I run this, I'll get back an output and we can notice that there is a usage field inside of here. So it looks like we sent in 14 tokens and we got 11 out. Let's now try adding in our list of tools. so when i add that in and run this we're now going to see a very different usage field now our usage has a cache creation input tokens of 1700 that means that claude has seen that we want to do cache our schemas so it has written into the cache a total of about 1700 tokens so now if we make a follow-up request immediately without changing anything about it we'll see that we are now going to read a certain number of tokens out of our cache so now we've got a cache read of 1700. That means that we have successfully stored our schemas inside the cache and then retrieve them at some point in time in the future. Now, if we change our user message here in any way, maybe by deleting that question mark at the end, and then rerun this, we are still going to read out of the cache because remember the caching order is the list of tools, then our system prompt, and then our different messages. So we'll still read out of the cache like so. However, if we change any of our tools in any way whatsoever, we're going to invalidate the cache. So if I go up to my list of tools, I'm going to change the description on the very first tool. I'm going to remove the S on the word ads. So now it's just add a specified duration. Now, if I rerun the cell, I've changed my tool schema. And that means that the cache breakpoint that we have applied to all of our different tools is no longer going to apply. If I run the very bottom cell again, We'll see an updated value of usage. So now we're going to have a cache write once again. So we're no longer reading. We're now back to writing because we have sent in a list of tools that, as far as Claude is concerned, is completely different. All right. So now let's try adding in our system prompt. I'm going to go to our chat function and I'll add in system code prompt. So now when we make this request, remember the order of caching. It is tools, then the system prompt, and then our list of messages. Because we are leaving our list of tools completely identical, but we are changing the system prompt, I would expect to see a partial cache read and then a cache write at the same time. The cache read that we're going to see is because we are making use of the same list of tools. And the cache write is going to be because we are setting up a new cache breakpoint by sending in this new prompt. So I'm going to run this. And now we should see a, there we go, a cache read of $1,700 and a cache write of $6.320. re now, just like our list of tools, if we go up to our system prompt and we change this prompt in any way, maybe by just removing the word builder at the very end here, and then rerunning that cell. Now, once again, as far as Claude is concerned, if we send in another request, this will be a completely different system prompt. So we are going to lose out on all the cash data we had previously around the system prompt. So now we'll send this again, and we will once again, see a cache read of about 1.7. There we go. And then we are once again writing this brand new system prompt. So that's another 6.3. All right, my friends, that is prompt caching. Again, you're going to very often use prompt caching anytime you are sending in identical content, either in the form of the same list of messages, the same tool schemas, or the same system prompt.

↑ 目次へ戻る

Building with the Claude API | Lesson 53「Code execution and the Files API」(全文ナレッジ)

動画 11分07秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-03

日本語ナレッジ(要点整理)

このレッスンのテーマ

Anthropic API が提供する2つの機能——Files APIcode execution(コード実行)ツール——を学ぶ。一見別々の機能に見えるが、組み合わせると「CSV をアップロードして Claude に自動でデータ分析させ、グラフ画像まで生成・回収する」といった複雑なタスクを丸ごと委任できる。

Files API の仕組み

  • これまでは画像や PDF を Base64 エンコードした生データを image ブロック等に埋め込んで送っていた。Files API はそのひねり版。
  • 事前に個別リクエストでファイルをアップロード(PDF・画像・テキストファイル等なんでも)→ File Metadata Object が返る。最重要プロパティは file ID
  • 以後の会話では、image ブロックの source に生データの代わりに file ID だけを入れれば、Claude が事前アップロード済みのファイルを見つけて解釈する。
  • スライド上の例:{"type": "image", "source": {"type": "file", "file_id": "file_011C…"}}
  • つまり「画像や PDF を Claude に渡すもう1つの方法」であり、先にファイルを送っておき、将来の任意の時点で参照できるのがポイント。

Code execution ツールの仕組み

  • server-based tool(サーバー型ツール)=自前でツールの実装を用意する必要がない。定義済みのツールスキーマを request に含めるだけで有効化される。
  • デモでの指定:tools=[{"type": "code_execution_20250522", "name": "code_execution"}]
  • Claude は必要と判断すると、隔離された Docker コンテナ内で Python コードを実行できる。同じコンテナ内で複数回実行することも可能。
  • コードが print した内容は Claude に返され、Claude はその結果を解釈して最終応答を書く。
  • Docker コンテナにはネットワークアクセスが一切ない。外部 API を叩くコードは書けない。→ コンテナへのデータの出し入れは Files API との組み合わせで行う。

2つを組み合わせたデータ分析パターン

  1. Files API で CSV(例 my_data.csv)をアップロード → file ID を得る。
  2. 後続リクエストの user メッセージに container upload ブロックを入れる:{"type": "container_upload", "file_id": file_metadata.id}。これは「事前アップロード済みのファイルをコンテナ内に注入(配置)する」という意味。
  3. 別の text ブロックで「このファイルのデータを分析して」と依頼。
  4. 裏側で Claude が code execution ツールを使い、コンテナ内のファイルにアクセスしてコードを書き、結果を処理してレポートを返す。 - 実行コードを見ると、ファイルはコンテナ内の /tmp/input/streaming.csv(別ランでは os.environ.get('INPUT_DIR', '.') 配下)に置かれている。

デモ:streaming.csv の churn(解約)分析

  • ノートブック 005_code_execution.ipynb と、動画ストリーミングサービスの偽データ streaming.csv を用意。列は UserID / SubscriptionTier / TotalViewingHoursLastMonth / TopGenre / BingeWatchingSessionsLastMonth など、最後の列が churned(0=未解約、1=解約済)
  • ヘルパー関数セルに upload / list_files / delete_file / download_file / ファイル情報取得の関数を追加済み。
  • file_metadata = upload("streaming.csv") → File Metadata Object と ID が返る。
  • プロンプトは「churn の主要因を詳細分析し、発見を要約する詳細なプロットを最低1枚含めよ」。さらに Critical note として「コード実行のたびに完全なクリーンスレートから始まる(前回実行の変数や import は残らない。毎回再宣言・再 import が必要)」を明記。
  • 実行には時間がかかる(デモでは 2m59s)。レスポンスは膨大なテキスト:Claude が書いた全コード+print 出力+最終分析が入る。
  • 実際のレスポンスでは、最初の実行で import seabornModuleNotFoundError(return_code: 1)になり、Claude がコードを書き直して再実行する様子まで観察できる(複数回実行の実例)。

レスポンスの構造(content ブロックの並び)

  • TextBlock(問題のフレーミング)→ ServerToolUseBlockname="code_execution", type="server_tool_use", input={"code": ...}=コンテナで実行したいコード)→ TextBlock(type="code_execution_tool_result")content={"type": "code_execution_result", "stdout": ..., "stderr": ..., "return_code": ..., "content": [...]})…がテキスト→コード→結果→テキスト…と複数回繰り返される
  • この生コンテンツをユーザーに見せるかはアプリ次第。デモでは Claude に整形させて「Streaming Service Churn Analysis - Conversation Terminal」という見やすい HTML レポート(INITIAL RESPONSE / CODE EXECUTION TOOL / INPUT CODE / EXECUTION OUTPUT)に仕立てた例も提示。
  • レスポンス末尾には model='claude-sonnet-4-20250514'usage=Usage(input_tokens=39812, output_tokens=8883, ...) に加えて container={'id': 'container_011CPY…', 'expires_at': ...} が付く(コンテナの ID と有効期限)。

生成ファイル(プロット)のダウンロード

  • Claude が生成したプロット画像は Docker コンテナ内に保存される。これを Files API でダウンロードできるのが code execution の最も面白い側面の1つ。
  • レスポンス内で、code_execution_tool_result の中に入れ子の content プロパティを持つブロックを探す → {"type": "code_execution_output", "file_id": "file_011CPYZqxoMSsfbrSzFw8j9X"} が生成ファイルの ID。見つからなければ「code execution output」で検索。
  • その file ID を download_file("file_011CPYZqxoMSsfbrSzFw8j9X") に渡して実行すると、ノートブックと同じディレクトリに PNG/JPEG が現れる(ファイル名は Claude が付けたもの=例 churn_analysis_comprehensive.png)。
  • 開くと「Comprehensive Churn Analysis - Key Drivers and Patterns」=サブスク層別/ジャンル別 churn 率、特徴量重要度、視聴時間帯別 churn 率などを網羅した集約ビジュアライゼーションが得られた。

実務ポイント(このレッスンから持ち帰るもの)

  • Files API=「先にアップロードして file ID で参照」。画像・PDF を毎回 Base64 で送る代わりに、uploadFileMetadata.id を image ブロック等の source: {"type": "file", "file_id": ...} に入れる。
  • code execution はサーバー型ツール。実装不要で、tools=[{"type": "code_execution_20250522", "name": "code_execution"}] のように定義済みスキーマを渡すだけで Claude が隔離 Docker コンテナ内で Python を実行できる。
  • コンテナはネットワーク遮断。データの持ち込みは {"type": "container_upload", "file_id": ...} ブロック、持ち出しは code_execution_output の file_id を Files API の download で回収——入出力は Files API が唯一の経路と覚える。
  • プロンプトに「クリーンスレート注意」を明記するのが実務の定石:コード実行のたびに変数・import は消えるので、毎回再宣言/再 import が必要なことを Claude に伝えておく。
  • レスポンスは TextBlock / ServerToolUseBlock / code_execution_tool_result複数回繰り返される長大な構造。エラー(例:seaborn 不在で return_code 1)→書き直し→再実行も自動で起きるので、stdout/stderr/return_code を見れば実行過程を追跡できる
  • 生成ファイルの探し方:レスポンス内で入れ子 contenttype: "code_execution_output" を持つブロック(無ければ文字列検索)→ 直下の file_id をコピー → download_file(file_id)。ファイル名は Claude 任せなので、取得後にリネーム運用を決めておく。
  • code execution の実行は時間がかかる(デモは約3分)し、応答も長い(例:input 39,812 / output 8,883 tokens)。生コンテンツをそのまま見せるか、整形レポートに加工するかはアプリケーション設計の判断。データ分析以外にも幅広いタスク委任に使える。

本文(時系列・日本語逐語+画面差し込み)

[0:00] このビデオでは、Anthropic API を通じて提供される2つの機能を見ていきます。この2つの機能は、少し毛色が違う、ちょっと別々のものに見えるでしょうが、実は非常に面白い形で組み合わせられることがわかります。では始めましょう。

🖥 [0:00] 画面(タイトルスライド) 黒背景に ANTHROP\C ロゴ。タイトル: Code Execution and Files API

[0:13] まず最初に、Files API とは何なのかを理解するところから始めます。

[0:17] このコースの前の方で、画像を Claude に渡して、その画像自体を解釈するよう Claude に頼めることを説明しました。そして、image ブロックを含める方法をお見せしましたね。そこには Base64 でエンコードされた実際の生の画像データを含めることができます。PDF ドキュメントのアップロードでも、非常によく似たプロセスが使われるのも見ました。

🖥 [0:17] 画面(復習スライド:従来の画像送信のシーケンス図) 「Our Server」と「Claude」の2本のライフライン。 - User MessageImage Block<Raw Image Data>Text BlockWhat do you see in this image? → Claude へ送信 - Assistant MessageText BlockI see a tall tree… が返る

[0:35] Files API は、この仕組み全体に少しひねりを加えるものです。File API を使うと、事前に個別のリクエストを送って特定のドキュメントをアップロードできます。PDF でも画像でもテキストファイルでも、何でも構いません。

[0:49] つまり、まず Claude に対してそのファイルをアップロードする最初のリクエストを送ります。すると、File Metadata Object(ファイルメタデータオブジェクト)と呼ばれるものが返ってきます。このファイルメタデータオブジェクトにはいくつかの情報が含まれますが、私たちにとって最も興味深い、あるいは重要なプロパティは file ID です。

[1:03] この ID によって、将来のある時点で、アップロード済みのそのファイルを参照し直すことができます。

[1:08] そして後になって、将来のある時点で、ユーザーが「この画像の中に何が見える?」というようなメッセージを送るとします。このとき、ここにある image ブロックの中に、画像そのものの生ファイルデータを含める代わりに、file ID だけを含めることができます。file ID をそこに入れると、Claude は事前にアップロードしておいたその画像を探しに行ってくれます。

🖥 [1:10] 画面(スライド「Files API のシーケンス図」) 「Our Server」→「Claude」のシーケンス図。 1. File Upload Request →(Server から Claude へ) 2. ← FileMetadata(id="file_011CPYfeeBS1CUbF") が返る(黒いコードボックス) 3. User Message を送信: - Image Blockjson { "type": "image", "source": { "type": "file", "file_id": "file_011C…" } } - Text BlockWhat do you see in this image? 4. ← Assistant MessageText BlockI see a tall tree…

[1:31] Claude はその画像を、できる限り解釈しようとします。

[1:34] つまり file API を使うと、事前にファイルをアップロードしておき、後からリクエストを送るとき、元のファイルに関するデータをそのリクエストの中に含めて Claude に渡せる、というわけです。

[1:44] ですから、これは画像や PDF を Claude に提供するもう1つの方法だと考えてしまって構いません。

[1:49] さて、この file API の基本——ファイルを送信しておき、将来の時点でそれを参照し直せるという考え方——を理解したところで、ギアを切り替えて、このビデオのもう1つのフォーカスである code execution(コード実行) について説明します。

[2:02] code execution はサーバー型ツール(server-based tool)です。つまり、このツールのために実際の実装を用意する必要はありません。やるべきことは、定義済みのツールスキーマを提供することだけです。

[2:11] Claude に送る最初のリクエストの中に、この特別に定義されたツールスキーマを、送りたいユーザーメッセージと一緒に含めます。すると裏側で、Claude は必要に応じて、隔離された Docker コンテナの中で一定量の Python コードを実行することを選択できます。Claude はこのコンテナの中で、何度でもコードを実行できます。

🖥 [2:05] 画面(スライド「Code Execution Tool Schema」) 「Our Server」→「Claude」のシーケンス図。 - 送信:Code Execution Tool Schema(黄帯)+ User MessageCalculate PI to the 50th decimal place. Use your code execution tool - 右側の注釈:Claude executes code in a Docker container(Claude は Docker コンテナ内でコードを実行する) - ← Assistant Message(3ブロック構成): - Server Tool Use Block\<Code to calculate PI> - Code Execution Tool Result Block3.1415926535… - Text BlockPI to the 50th decimal place is 3.1415926535…

[2:31] Claude がこれらのコード実行から print したものは何であれ Claude に送り返され、Claude はその結果を解釈して、私たちへの最終応答を書くことができます。これらの Docker コンテナには、ネットワークアクセスが一切ありません。つまり Claude は、ネットワークリクエストを行ったり、外部の API にアクセスしようとしたりするコードを書くことはできません。

[2:50] その代わり、Docker コンテナに情報を入れたり、そこから情報を取り出したりするには、先ほど説明した file API と、この code execution ツールを混ぜ合わせて使うことに頼ります。では、これが全体としてどう機能するかをお見せしましょう。

[3:01] 例えば、my_data.csv という名前の CSV ファイルがあり、その中に Claude に分析してもらいたい大量の表形式データが入っているとしましょう。Claude にコードを書き出してもらって、それを自分たちで手動実行する、というような本当に複雑なコード段取りを踏む代わりに、この file API と code execution ツールを一緒に使って、Claude にファイルを自動で分析させ、結果を生成させることができます。

[3:27] そのためには、まず file API を使って CSV ファイルをアップロードします。まず上のここで、何らかのデータが入った CSV ファイルをアップロードします。すると file ID が返ってきます。次に、その file ID を Claude への後続リクエストの中に含めます。そこで、container upload ブロックと呼ばれるものを追加します。container upload ブロックとは、以前 Claude にアップロードしたファイルを、どうにかしてコンテナの中に注入する、あるいは配置する、という意味です。

[3:54] そこで、type が container upload で、file ID プロパティには元のファイルをアップロードしたときに返ってきた ID を入れた、この非常に特別に作られたブロックを追加します。

🖥 [3:30] 画面(スライド「Files API × Code Execution の複合フロー」) 「Our Server」→「Claude」のシーケンス図。 1. Upload my_data.csv, get back a file id(my_data.csv をアップロードし、file id を得る) 2. User Message を送信: - Container Upload Blockjson { "type": "container_upload", "file_id": file_metadata.id } - Text BlockAnalyze the data in the file 3. 右側の注釈:Claude executes code in a Docker container 4. ← Assistant Message(3ブロック): - Server Tool Use Block\<Code to analyze the file> - Code Execution Tool Result BlockResults of data analysis… - Text BlockHere is the result of my analysis:

[4:03] それから、別の text ブロックの中で、Claude に何らかの分析を頼みます。「このファイルの中のデータを分析して」というくらいシンプルなものでも構いません。すると裏側で、Claude は code execution ツールを利用します。Claude は Docker コンテナの中で、そのアップロード済みファイルにアクセスできます。そこで Claude はファイルを分析するコードを書き、結果を処理し、そのファイルの中の全データについての完全なレポートを私たちに返してくれます。

[4:28] それでは、このフロー全体の例を見てみましょう。

[4:31] 事前に 005 code execution というノートブックを用意しておきました。さらに streaming.csv という別の CSV ファイルも作ってあります。

🖥 [4:32] 画面(VS Code:005_code_execution.ipynb のヘルパー関数セル) # Load env variables and create client セルの下に # Helper functions セル: ```python

Helper functions

from anthropic.types import Message

def add_user_message(messages, message): user_message = { "role": "user", "content": message.content if isinstance(message, Message) else message, } messages.append(user_message)

def add_assistant_message(messages, message): assistant_message = { "role": "assistant", "content": message.content if isinstance(message, Message) else message, } ``` エクスプローラには images / .env / 001_thinking_complete.ipynb / 001_thinking.ipynb / 002_citations_complete.ipynb / 002_images.ipynb / 003_caching.ipynb / 005_code_execution.ipynb / earth.pdf / streaming.csv が並ぶ。

[4:41] この中には、動画ストリーミングサービスの偽データが大量に入っています。このファイルには、個々のユーザーに関する情報——どのサブスクリプション層(tier)に属しているか、つまりどのレベルのアクセス権を持っているか——と、その特定ユーザーに関するたくさんの統計が入っています。視聴した合計時間数、トップジャンル、等々です。

🖥 [4:41] 画面(streaming.csv の中身) ヘッダー行:UserID,SubscriptionTier,TotalViewingHoursLastMonth,TopGenre,BingeWatchingSessionsLastMonth,Numb…(右端は画面外で見切れ) データ行の例: USER_00001,Basic,47.9,Comedy,5,15,32.6,3,7.99,0 USER_00002,Premium,41.4,Drama,5,9,45.7,3,17.99,0 USER_00003,Standard,33.6,Action,1,7,32.3,4,12.99,1 USER_00004,Standard,115.6,Action,12,33,57.3,1,12.99,0 USER_00005,Basic,93.8,Documentary,9,27,90.0,2,7.99,1 USER_00006,Basic,105.6,Romance,10,27,80.0,2,7.99,0 …(USER_00019 まで表示。各行末尾の列が 0 / 1 の churned フラグ)

[4:59] そして一番最後、最終列は churned という名前です。churned は、そのユーザーがサブスクリプションを解約したかどうかを示すものです。

[5:08] 0 は解約していないことを意味します。そして 1 は解約したことを意味します。

[5:14] さて、このファイルの中のデータを分析して、これらのさまざまな特徴量とユーザーの解約の有無との間に何らかの相関があるかどうかを調べるコードを、私自身が大量に書くこともできます。

[5:24] しかし、これを全部自分でやる代わりに、タスク全体をそのまま Claude に丸投げすることに決めてもよいでしょう。まずこの streaming.csv ファイルをアップロードし、それから Claude に code execution ツールを使ってもらって、この中の全データの分析をしてもらうのです。

[5:38] では、そのやり方をお見せしましょう。

[5:40] まず、ノートブックに戻って、ヘルパー関数のセルを見てほしいのです。少し下にスクロールすると、いくつかの関数を私が追加してあるのに気づくはずです。upload 関数を追加しました。ファイルパスを与えると特定のファイルを自動でアップロードしてくれます。list files 関数も追加しました。私たちが Claude にアップロードした全ファイルを一覧してくれます。ファイルの削除もできますし、ファイルのダウンロードもできますし、特定のファイルに関する情報の取得もできます。

🖥 [5:30] 画面(ヘルパー関数セル内の chat() 関数) スクロール中に見える、これまでのレッスンで組み上げてきた chat(): ```python def chat( messages, system=None, temperature=1.0, stop_sequences=[], tools=None, thinking=False, thinking_budget=2000, ): params = { "model": model, "max_tokens": 10000, "messages": messages, "temperature": temperature, "stop_sequences": stop_sequences, }

if thinking:
    params["thinking"] = {
    (以下は画面外)

``` (upload / list_files / delete_file / download_file の各関数は口頭説明。定義本体はこのフレームでは画面外)

[6:05] これらの関数は、この後すぐに使います。そこで、そのセルを折りたたみ、もちろん忘れずに実行もしておきます。

[6:12] 次に、その下のセルで、streaming.csv ファイルのアップロードを試みます。このセルをすぐに実行すると、file metadata オブジェクトが返ってきます。そしてこの中に、ID があります。

🖥 [6:12] 画面(アップロードのセル) python file_metadata = upload("streaming.csv") file_metadata その下のセルには次のプロンプトの messages = []add_user_message(...) が見えている。

[6:22] これが、このファイルを Claude に対して識別する一意の ID です。将来、会話の中にこのファイルを含めたくなったら、この特定の ID を参照することになります。

[6:31] 次にその下のセルには、短いプロンプトを用意してあります。Claude に詳細な分析を実行して、顧客がなぜサブスクリプションを解約しているのかを突き止めるよう頼むものです。さらに、発見したことすべてを要約するプロットを出力することも Claude に頼んでいます。

[6:46] そしてその後に、container upload ブロックがあります。これが、あのアップロード済みファイルを実際にこのリクエストの中に含めるものです。

🖥 [6:31] 画面(プロンプトのセル全体) ```python messages = []

add_user_message( messages, [ { "type": "text", "text": """ Run a detailed analysis to determine major drivers of churn. Your final output should include at least one detailed plot summarizing your findings.

Critical note: Every time you execute code, you're starting with a completely clean slate. No variables or library imports from previous executions exist. You need to redeclare/reimport (この行の続きは画面右で見切れ) """, }, {"type": "container_upload", "file_id": file_metadata.id}, ], )

chat( messages, tools=[{"type": "code_execution_20250522", "name": "code_execution"}], ) ```

[6:52] それではセルを実行します。ただし注意してほしいのは、code execution を使うときは、完了までに実際にちょっと時間がかかることがある、という点です。

[7:00] 返ってくるレスポンスには、途方もない量のテキストが含まれることになります。このメッセージの中には、Claude が書くと決めた全コードが、Claude が得たすべての print 文と出力、そして最終的な分析と一緒に入っています。

[7:13] Claude はコンテナの中でコードを複数回実行すると決めることもできます。ですから、このメッセージの中には、実際に複数のコードブロックと複数の実行結果が見えるかもしれません。

🖥 [7:00] 画面(実行完了直後の生レスポンス。セル実行時間 2m 59.0s) Message(id='msg_0171kKQtGd9oNSoMacz3yyUY', content=[TextBlock(citations=None, text="I'll perform a detailed churn analysis on your streaming data. Let me start by examining the dataset structure and then conduct a comprehensive analysis to identify the major drivers of churn.", type='text'), ServerToolUseBlock(id='srvtoolu_01WKMPmT3yWLoM83FQgHaneF', input= {'code': 'import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.ensemble import RandomForestClassifier\nfrom sklearn.preprocessing import LabelEncoder\nfrom sklearn.metrics import classification_report, confusion_matrix\nimport warnings\nwarnings.filterwarnings(\'ignore\')\n\n# Load and examine the data\ndf = pd.read_csv(\'/tmp/input/streaming.csv\')\n\nprint("Dataset shape:", df.shape)\nprint("\\nColumn names:")\nprint(df.columns.tolist())\nprint("\\nFirst few rows:")\nprint(df.head())\nprint("\\nData types:")\nprint(df.dtypes)\nprint("\\nMissing values:")\nprint(df.isnull().sum())'}, name='code_execution', type='server_tool_use'), TextBlock(citations=None, text=None, type='code_execution_tool_result', tool_use_id='srvtoolu_01WKMPmT3yWLoM83FQgHaneF', content={'type': 'code_execution_result', 'stdout': '', 'stderr': 'Traceback (most recent call last):\n File "/code/93a23edde150442b89232385207c9839.py", line 4, in <module>\n import seaborn as sns\nModuleNotFoundError: No module named \'seaborn\'\n', 'return_code': 1, 'content': []}), ServerToolUseBlock(id='srvtoolu_01Dqd1ig15WEi1iNTZhWH9hf', input={'code': 'import pandas as … ※最初の実行が seaborn 不在で失敗(return_code: 1)し、Claude が即座に次の ServerToolUseBlock でコードを書き直して再実行している。

[7:21] そして、このメッセージの中で何が起きているのかを理解しやすくするために、私はその全コンテンツを取り出して、もう少しきれいに整形しました。何が起きているかを理解できるようにするためです。では、そのメッセージがこちらです。content のリストがあり、その中にさまざまな種類のブロックが入っています。最初のブロックは text ブロックで、Claude が最初の問題をフレーミング(設定)しているだけのテキストが入っています。

[7:39] 次に Claude は server tool use ブロックを提供します。これには、Claude がコンテナ内で実行したいコードが入っています。そしてここにあるのが code execution tool result です。そのコードの実際の実行に関する情報——standard out(標準出力)のデータ、standard error(標準エラー)、エラーハンドリングが必要な場合に備えた return code、など——が入っています。

🖥 [7:29] 画面(整形済みメッセージ。別エディタタブ「Message( Untitled-1」冒頭) python Message( id="msg_014pQ7ANu17rxaxcpLijEqvk", content=[ TextBlock( citations=None, text="I'll help you analyze the major drivers of churn in your streaming dataset. L…", type="text", ), ServerToolUseBlock( id="srvtoolu_019YgS5sqrHHpJZnjwHQX1fN", input={ "code": "import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as p…" }, name="code_execution", type="server_tool_use", ), TextBlock( citations=None, text=None, type="code_execution_tool_result", tool_use_id="srvtoolu_019YgS5sqrHHpJZnjwHQX1fN", content={ "type": "code_execution_result", "stdout": "Dataset Shape: (500, 10)\n\nColumn Names:\n['UserID', 'SubscriptionTi…", "stderr": "",

[8:00] そして、この場合は Claude がその後さらにコードを実行することにしたようです。ここで追加の分析を行い、さらに結果を受け取り、さらに分析を行い……という具合です。つまりこのケースでは、Claude は徹底的な分析を行うために、何度か続けてコードを実行しました。

🖥 [8:05] 画面(整形済みメッセージの続き:繰り返し構造) 50行目付近: python "stdout": "Churn Distribution:\nNot Churned (0): 307 (61.4%)\nChurned (1): 193…", "stderr": "", "return_code": 0, "content": [], }, ), TextBlock( citations=None, text="Now let me analyze the numerical features and their relationship with churn:", type="text", ), ServerToolUseBlock( id="srvtoolu_0171RHz7WBNTgwaTuW5XmyNh", input={ "code": "import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as p…" }, name="code_execution", type="server_tool_use", ), さらに下には text="Now let me examine the churn distribution and perform a detailed analysis of t…" の TextBlock と ServerToolUseBlock(id="srvtoolu_01NgH1JbFzMbFzQw6McsViid", …) が続き、テキスト→コード→結果の並びが何度も繰り返されている。

[8:16] さて、このコンテンツ全部をユーザーに見せるかどうかは、完全にあなた次第であり、あなたが取り組んでいる特定のアプリケーション次第です。望むなら、本当に見栄えの良いレポートを組み立てることもできます。例えばこんな感じです。先ほど見ていたあの情報を全部取り出して、Claude を使ってとてもきれいに整形しただけのものです。これで最初の応答がそこに見えます。これは、返ってきた最初の text ブロックの情報そのままです。

🖥 [8:28] 画面(Chrome:claude.ai/artifacts/fa819337-… の整形レポート) 青いヘッダー:⚡ Streaming Service Churn Analysis - Conversation Terminal - ● ASSISTANT RESPONSE セクション → INITIAL RESPONSE ボックス: I'll help you analyze the major drivers of churn in your streaming dataset. Let me start by exploring the data structure and then perform a comprehensive churn analysis. - 青帯:🔧 CODE EXECUTION TOOL(右端に SRVTOOLU_019YGS5SQRHHPJZNJWHQX1FN) - INPUT CODE: ```python import warnings warnings.filterwarnings('ignore')

# Load the data import os input_dir = os.environ.get('INPUT_DIR', '.') df = pd.read_csv(os.path.join(input_dir, 'streaming.csv'))

# Initial data exploration print("Dataset Shape:", df.shape) print("\nColumn Names:") print(df.columns.tolist()) print("\nFirst 5 rows:") print(df.head()) print("\nData Types:") print(df.dtypes) print("\nMissing Values:") print(df.isnull().sum()) print("\nBasic Statistics:") print(df.describe()) `` - 緑帯:**✓ EXECUTION OUTPUT** →Dataset Shape: (500, 10)`

[8:41] そしてこちらが私の code execution ツールです。Claude が実行することにしたコードと、それを実行した出力があります。そして、これが何度か続けて繰り返されているのが分かります。

[8:51] さらにここにもう少しテキストがあり、また別のコード実行があり……という具合です。

[8:56] 最後に、code execution の最も興味深い側面の1つをお見せしたいと思います。思い出してほしいのですが、私が送ったこのプロンプトの中で、発見を要約する詳細なプロットを1枚含めるよう Claude に頼んでいました。

🖥 [9:02] 画面(プロンプトセルの再掲) add_user_message セルの "Run a detailed analysis to determine major drivers of churn. Your final output should include at least one detailed plot summarizing your findings." の部分(特に plot summarizing がハイライトされる場面もある)。

[9:09] Claude は裏側で、実際に画像ファイルの中にプロットを生成しました。そしてそれは、私たちの Docker コンテナの中に保存されています。file API を使えば、Docker コンテナ内に生成されたそのプロットをダウンロードできます。やり方をお見せしましょう。

[9:25] まず、こちらで私が整形したメッセージを詳しく見に行きます。スクロールしていくと、やがて、追加で入れ子になった content プロパティを持つ text ブロックが見つかるはずです。そしてその中に、code execution output という type が見えるはずです。

[9:36] はい、私のものはここにあります。もし皆さんのレスポンスの中に見当たらなければ、「code execution output」で検索してみてください。

[9:42] そのすぐ下に file ID があります。

🖥 [9:30] 画面(整形済みメッセージ内の code_execution_output と file_id) python TextBlock( citations=None, text=None, type="code_execution_tool_result", tool_use_id="srvtoolu_01Mvsgg9rb7MmNREvBNy4394", content={ "type": "code_execution_result", "stdout": "Comprehensive churn analysis visualization saved!\n", "stderr": "", "return_code": 0, "content": [ { "type": "code_execution_output", "file_id": "file_011CPYZqxoMSsfbrSzFw8j9X", } ], }, ), TextBlock( citations=None, text="Now let me create a detailed summary report of the key findings:", "file_011CPYZqxoMSsfbrSzFw8j9X" が選択(ハイライト)されている。この直前(105〜120行目)には text="Now let me create comprehensive visualizations to summarize the findings:" の TextBlock と ServerToolUseBlock(id="srvtoolu_01Mvsgg9rb7MmNREvBNy4394", …) がある。

[9:45] ですから、この file ID を使ってファイルをダウンロードできます。これをコピーして、ノートブックに戻ります。

🖥 [9:50] 画面(参考:ノートブック側の生レスポンスの末尾) スクロール中に見える生 Message の終端部分: …The visualizations show clear patterns that can guide targeted retention strategies, with engagement metrics being the most actionable predictors of churn risk.", type='text')], model='claude-sonnet-4- 20250514', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(cache_creation_input_tokens=0, cache_read_input_tokens=0, input_tokens=39812, output_tokens=8883, server_tool_use=ServerToolUsage(web_search_requests=0), service_tier='standard'), container={'id': 'container_011CPYeGbizqC5Jf39FpWyU9', 'expires_at': '2025-05-27T21:14:00.571197+00:00'}) ※レスポンス末尾に container の id と expires_at が含まれている点に注目。この生出力の途中には、churn 率のキーファインディング(Basic tier 43.5% / Standard 39.5% / Premium 24.1%、Horror 52.3% など)を print した stdout も見える。

[9:51] 一番下までスクロールして、新しいセルを追加します。

[9:56] さあ、いきますよ。download file を呼び出します。これは、このビデオの冒頭で見た、あの定義済み関数のうちの1つでした。そして、たった今レスポンスの中で見つけた file ID を貼り付けます。

🖥 [9:57] 画面(ダウンロードのセル) ノートブック末尾に追加した新しいセル(パンくずにも表示): python download_file("file_011CPYZqxoMSsfbrSzFw8j9X")

[10:07] それからこれを実行すると、正常に実行されます。そして今、ノートブックがあるのと同じディレクトリを見てみると、その中に PNG か JPEG のファイルが見つかるはずです。

[10:17] ファイル名はランダムになります。厳密に言えば本当にランダムというわけではなく、Claude が付けることにした名前になります。

🖥 [10:14] 画面(エクスプローラに生成ファイルが出現) VS Code のエクスプローラ(EXPLORER: 08)に churn_analysis_comprehensive.png が新しく現れ、選択されている。並びは images / .env / 001_thinking_complete.ipynb / … / 005_code_execution.ipynb / churn_analysis_comprehensive.png / earth.pdf / streaming.csv。

[10:23] そのファイルを開いてみると——そのファイルを開いてみると、Claude があの CSV ファイルから抽出した大量の情報が見えます。

[10:30] これは、私が知る必要のあることをすべて教えてくれる、素晴らしいビジュアライゼーションです。視聴時間帯別の churn 率、月額コスト帯別の churn 率、等々です。

🖥 [10:26] 画面(生成されたプロット churn_analysis_comprehensive.png タイトル:Comprehensive Churn Analysis - Key Drivers and Patterns。11枚のチャート+サマリーボックスのグリッド: - Churn Rate by Subscription Tier:Basic 43.5%/Premium 24.1%/Standard 39.5% - Churn Rate by Top Genre:Action 44.6%・Comedy 33.0%・Documentary 25.9%・Drama 35.3%・Horror 52.3%・Romance 41.8%・SciFi 40.5%・Thriller 48.3% - Random Forest Feature Importance:TotalViewingHours (LM) 0.237/AverageSessionDurationMinutes 0.195/NumberOfUniqueTitlesWatched (LM) 0.170/TopGenre 0.118/BingeWatchingSessions (LM) 0.116/CustomerServiceInteractions (LY) 0.089/MonthlyCost 0.043/SubscriptionTier 0.032 - Logistic Regression Coefficients:CustomerServiceInteractions (LY) 0.406/SubscriptionTier 0.246/TopGenre 0.093/BingeWatchingSessions (LM) 0.059/AverageSessionDurationMinutes 0.013/TotalViewingHours (LM) -0.014/NumberOfUniqueTitlesWatched (LM) -0.017/MonthlyCost(負の値・棒のみ) - 分布ヒストグラム4枚(No Churn vs Churned の重ね合わせ):TotalViewingHours (LM)/CustomerServiceInteractions (LY)/AverageSessionDurationMinutes/NumberOfUniqueTitlesWatched (LM) - Churn Rate vs Customer Service Interactions:0回 0.0% → 6回 75.0%(27.3%・24.8%・40.7%・56.7%・62.1% と単調増加) - Churn Rate by Monthly Cost Ranges:$8.0-10.0 43.5%/$10.0-12.0〜$14.0-16.0 帯 39.5%/$16.0-18.0 帯 24.1% - Churn Rate by Viewing Hours:0-32h 72.0%/32-65h 38.6%/65-97h 41.5%/97-129h 26.2%/129-162h 12.5% - サマリーボックス:Total Users: 500/Overall Churn Rate: 38.6%/Highest Risk Tier: Basic (43.5%)/Highest Risk Genre: Horror (52.3%)/Top Churn Driver: Customer Service Interactions/Avg CS Interactions (Churned): 3.2/Avg CS Interactions (Retained): 2.5/Model Accuracy: 62.0%

[10:38] Claude はここで、このファイルの中身を私が理解できるように、非常に徹底した分析をしてくれました。

[10:43] このデモを踏まえてお分かりのとおり、file API と code execution ツールを組み合わせることで、かなり複雑なタスクを Claude に委任できるようになります。

[10:53] もちろん、できることはデータ分析だけに限りません。code execution と files API の組み合わせは、実に多種多様なタスクの実行に使えます。これを自分のアプリケーションにどう統合するかを決めるのは、本当にあなた次第です。

英語逐語(ASR全文)

In this video, we're going to take a look at two features offered through the Anthropic API. These two features are going to seem a little bit different, just a little bit separate, but it turns out they can be combined together in really interesting ways. So let's get to it. We're going to first begin by understanding what the files API is all about. Earlier on inside this course, we discussed how you can pass images into Claude and ask Claude to interpret the image itself. And I showed you how we can include an image block, which can include the actual raw image data encoded in Base64. We also saw a very similar process being used for uploading of PDF documents as well. The Files API allows for a little bit of a twist on this whole system. With the File API, we can make an individual request ahead of time to upload a particular document, be it a PDF or an image or a text file or whatever else. So we might make an initial request off to Claude to upload that file. We'll then get back something called a File Metadata Object. This file metadata object contains some different information, but the most interesting or important property to us is the file ID. This idea allows us to refer back to that uploaded file at some point in time in the future. So then later on, at some point in the future, a user can submit a message like, what do you see inside this image? And inside of our image block right here, rather than including the raw file data from the image itself, we can include just the file ID. So we can put in the file ID right there, and that's going to get Claude to go and find that image that we had uploaded ahead of time. Claude is then going to try to interpret the image as best as it can. So the file API allows us to upload a file ahead of time and then make a request later on and include some data about the original file inside the request to Claude. So you can really just think of this as being another way that we can provide an image or a PDF to Claude. Now that we understand the basics behind this file API, just the idea that allows us to submit a file and then refer back to it at a future point in time, we are going to switch gears and discuss the other focus of this video, which is code execution. Code execution is a server-based tool, so we do not have to provide an actual implementation for this tool. All we have to do is provide a predefined tool schema. Inside of an initial request we make off to Claude, we will include this specially defined tool schema, along with whatever user submitted message we want to include. Then behind the scenes, Claude can optionally decide to execute some amount of Python code inside of an isolated Docker container. Claude can run code inside this container multiple different times. Whatever Claude prints up from these code executions will be sent back to Claude, and the Claude can interpret the results and write a final response to us. These Docker containers do not have any network access. That means that Claude cannot write out any code that make a network request or attempt to access any kind of outside api instead to get information into the docker container and to get information out of it we rely upon mixing together that file api that we just discussed along with this code execution tool so let me show you how this works in total let's imagine that we have a csv file called maybe my data.csv and it contains a lot of tabular information that we want to get analyzed by claude rather than going through some really complicated code setup where we ask Claude to write out some code and then we manually execute that ourself, we can instead make use of this file API and the code execution tool together to get Claude to automatically analyze the file and produce some results for us. So to do so, we will first use the file API to upload our CSV file. So we'll initially up here, upload our CSV file with some amount of data inside of it. And that's going to give us back a file ID. We will then include that file ID inside of a follow-up request to Claude. So we will add in something called a container upload block. A container upload block just means that we want to take a file that we previously uploaded to Claude and somehow inject it or place it inside of the container. So we're going to add in this very specially crafted block with a type of container upload and a file ID property of whatever ID we got back from when we had uploaded our original file. Then inside of a separate text block, we'll ask Claude to do some analysis, maybe something as simple as analyze the data inside this file. Then behind the scenes, Claude is going to make use of the code execution tool. Claude is going to have access to that uploaded file inside the Docker container. So Claude can write out some code to analyze the file, process the results, and then give us a full report of all the data inside that file. Let's now take a look at an example of this entire flow. So I've put together a notebook ahead of time called 005 code execution. I've also created a separate CSV file called streaming.csv. Inside of here is a bunch of fake data from a video streaming service. This file contains information about particular users, what subscription tier they are included in, so what kind of level of access they have, and then a lot of statistics about this particular user. So the total number of hours they have viewed, top genre, et cetera, et cetera. And then at the very end, the very last column is called churned. Churned is an indication of whether or not the user has canceled their subscription. So zero means they have not canceled their subscription. And a one means they have canceled their subscription. Now I could write out a lot of code to analyze the data inside this file and figure out whether or not there is some correlation between these different features and whether or not a user has canceled their subscription. But instead of doing all this myself, I might decide to just hand the entire task off to Claude. I could first upload this stringing.csv file and then ask Claude to make use of its code execution tool to do an analysis of all the data inside of here. So let me show you how we would do that. First, back inside the notebook, I want you to take a look at the helper function cell. If you scroll down a little bit, you'll notice that I added in a couple of different functions. I added in a upload function, which will automatically upload a particular file given a file path. I have added in a list files function, which will list out all the different files we have uploaded to Claude. We can delete a file, we can download a file, and we get information about a particular file as well. We're going to make use of these functions very shortly. So I'm going to collapse that cell and of course make sure that I run it as well. Then in the next cell down, I'm going to attempt to upload the streaming.csv file. So I'm going to run that cell right away and we'll get back our file metadata object. And inside of here, there's our ID. So that is the unique ID that identifies this file to Claude. And if we ever want to include it inside of a conversation in the future, we'll refer to that particular ID. Then inside the next cell down, I've got a short prompt that is asking Claude to run a detailed analysis and figure out why customers are canceling their subscription. I'm also asking Claude to print out a plot that summarizes all of its findings. Then after that, I've got the container upload block, which is going to actually include that uploaded file inside of our request. So now I'm going to run the cell and just be aware that whenever you make use of code execution, sometimes actually takes a little bit of time to complete. The response we get back is going to contain a tremendous amount of text. Inside this message is going to be all the code that Claude decided to write, along with all the print statements and output that it got, along with some final analysis as well. Claude can decide to run code multiple times inside the container. So we might actually see multiple code blocks and multiple execution results inside this message. and to help you understand what's going on inside the message, I took all that content and I formatted it just a little bit more nicely so we can understand what's happening. So here's that message. I got my content list with a variety of different blocks inside of it. The first block is a text block, which is going to contain some amount of text that just has Claude framing the initial problem. Claude is then going to provide a server tool use block. This is going to contain some amount of code that Claude wants to run inside the container. And then here is our code execution tool result. It's going to contain some information about the actual execution of that code. So data from standard out, standard error, return code in case there's any error handling required, and so on. And then it looks like in this case, Claude decided to run some more code after that. So it did some further analysis right here, got back some more results, and it did some more analysis and so on. So in this case, Claude ran some code several times in a row to do some thorough analysis. Now, whether or not you decide to show all this content to your users is totally up to you and the particular application that you are working on. If you want to, you could build up a really nice looking report, maybe something like this. So I took all that information that we were just looking at, and I use Claude to just format it very nicely. So now I can see the initial response right there. So that's the exact information from the initial text block that we got back. Then here's my code execution tool. There's the code that Claude decided to run along with the output from executing it. And we can see this is repeated several times in a row. So now I've got some more text right here, another code execution, and so on. Finally, I want to show you one of the most interesting aspects of code execution. So you may recall that back inside this prompt that I sent off, I asked Claude to include one detailed plot summarizing its findings. Cloud behind the scenes did generate a plot inside of a image file, and that is stored inside of our Docker container. We can use the file API to download that generated plot inside of the Docker container. Let me show you how. First, I'm going to go and take a close look at the message that I formatted over here. So if I scroll through, I might eventually see a text block that has an extra nested content property. And inside that, I might see a type of code execution output. So Here's mine right here. If you don't see it inside of your response, try searching for code execution output. Right underneath that is a file ID. So I can use this file ID to download the file. I'm going to copy it, go back over to my notebook. I'm going to go down to the very bottom and add in a new cell. Here we go. I'm going to call download file, which was one of those predefined functions that we took a look at at the start of this video. and I'm going to paste in the file ID that I just found inside the response. I'm then going to run this and it's going to run successfully. And now if I take a look at the same directory that my notebook is in, I'm going to find maybe a PNG or a JPEG file inside there. Their name is going to be random. It's not truly random per se. It's going to be whatever Claude decided to name it. If I then open up that file, if I then open up that file, I'll see a bunch of information that Claude extracted from that CSV file. So this is a great visualization that tells me everything I need to know. So churn rate by viewing hours, by monthly cost ranges, and so on. Claude did a very thorough analysis here to help me understand the contents of this file. So as you can see, based upon this demo, combining together the file API and the code execution tool allows us to delegate rather complex tasks off to Claude. Of course, you are not limited to just doing data analysis. You can use the combination of code execution along with the files API to execute a wide variety of different tasks. It's really up to you to decide how to integrate this into your application.

↑ 目次へ戻る

Building with the Claude API | Lesson 54「Introducing MCP」(全文ナレッジ)

動画 4分40秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04

日本語ナレッジ(要点整理)

MCP の定義

  • MCP(Model Context Protocol)は「通信レイヤー」。開発者が大量の面倒なコード(tedious code)を書かなくても、Claude にコンテキストとツールを提供できるように設計されている。
  • 入門時に必ず目にする定番図:MCP Client(クライアント)MCP Server(サーバー) の2大要素。サーバー内部には Tools/Prompts/Resources という内部コンポーネントがあり、各 MCP サーバーは Outside Service(外部サービス) につながる。

サンプルアプリで理解する(GitHub チャットボット)

  • 題材:ユーザーが自分の GitHub データについて Claude とチャットできるチャットインターフェース。
  • 「全リポジトリ横断でオープンな PR は?」という質問に対し、Claude がツールで GitHub にアクセスしてユーザーのアカウントを見に行く想定。実装は「ツール一式(a set of tools)」になる。
  • 問題:GitHub にはリポジトリ・PR・イシュー・プロジェクト等の膨大な機能がある。完全な GitHub チャットボットには途方もない数のツール(スキーマ+関数)を自作する必要があり、それは全部開発者が書き・テストし・保守するコード。
  • この「開発者に大量のインテグレーションを保守させる負担」こそ、MCP が解決を狙う主要課題。

MCP サーバーの本質

  • MCP はツールの定義と実行の負担を、自分のサーバーから「MCP サーバー」へ移す(スライド原文:Shifts the burden of tool definitions and execution onto MCP Servers.)。ツールは MCP サーバーの内部で作られ、実行される。
  • MCP サーバー=外部サービスへのインターフェース(スライド原文:MCP Servers provide access to data or functionality implemented by some outside service.)。例:GitHub MCP サーバーは GitHub 固有のデータと機能へのアクセスを提供する。GitHub まわりの大量の機能をツール一式として MCP サーバーに包み込むイメージ。
  • 利点=開発者がツールスキーマや関数を自作しなくてよい。

よくある3つの質問(Common Questions スライド)

  1. 誰が MCP サーバーを作るのか? → 誰でも作れる。ただしサービス提供者自身が公式実装を出すことが非常に多い(例:AWS が公式 MCP サーバーを公開し、多様なツールを同梱するようなケース)。
  2. API 直接呼び出しと何が違うのか? → MCP サーバーは「ツールスキーマ+関数」を提供してくれる。API を直接呼ぶなら、スキーマも関数実装も自分で書くことになる。MCP サーバーを挟むだけで、その分の時間を節約できる。
  3. 「MCP とツールユースは同じもの」という批判 → MCP を理解していない人からよく出る。MCP サーバーとツールユースは別物だが補完関係。MCP の考え方は「ツールの関数とスキーマを自分では書かず、他者が書いて MCP サーバーに包んだものを使う」。つまり MCP サーバーの話は「実際の作業を誰がやるか」の話。

実務ポイント(このレッスンから持ち帰るもの)

  1. MCP はツールユースの置き換えではない。「ツールの定義・実行の所有者を自サーバーから MCP サーバーへ移す」仕組み。「MCP=tool use」という批判には「別物だが補完関係・誰が実装するかの話」と切り返せる。
  2. 外部サービス統合を検討するときは、まずサービス公式の MCP サーバー実装の有無を確認する(提供者自身の公式実装が出ていることが多い)。
  3. API 直叩きとの差分は「schema+fn を自分で書くかどうか」だけ。工数見積り・採用判断はこの1点で整理できる。
  4. フル機能の統合を自作すると「書く・テストする・保守する」の3重負担がツール数分だけ膨らむ。統合の保守負担が大きいサービスほど MCP 採用のリターンが大きい
  5. アーキテクチャ図の読み方:Our Server の中に MCP Client が住み、MCP Client が複数の MCP サーバー(各サーバーが Tools/Prompts/Resources を持ち外部サービスへつながる)に接続する、が基本形。

本文(時系列・日本語逐語+画面差し込み)

🖥 [0:00] 画面(MCP 基本アーキテクチャ図・Anthropic ロゴ入りスライド) - 左:大きな枠「Our Server」、その内部にオレンジのボックス「MCP Client」 - 右:「MCP Server」枠が上下に2つ。それぞれ内部に「Tools」「Prompts」「Resources」の3ボックス - MCP Client から2つの MCP Server へ曲線矢印、各 MCP Server から右端の「Outside Service」へ矢印

[0:00] このモジュールでは、Model Context Protocol(モデル・コンテキスト・プロトコル)に集中して取り組みます。

[0:04] MCP は、開発者であるあなたが大量の面倒なコードを書かなくても済むように、Claude にコンテキストとツールを提供するために設計された通信レイヤーです。

[0:13] MCP を使い始めると、このような見た目の図を非常に頻繁に目にすることになります。

[0:17] この図は MCP の2つの主要な要素、すなわちクライアントとサーバーを示しています。

[0:22] サーバーの中には多くの場合、tools・resources・prompts と名付けられた複数の内部コンポーネントが含まれています。

[0:28] さて、ここには用語がたくさん出てきました。

[0:30] そこで、これらすべてを理解しやすくするために、私たちが小さなアプリを作っているところを想像して、そこに MCP がどうはまるのかを見ていきます。

🖥 [0:38] 画面(スライド「Sample App」) - 箇条書き: - Chat interface, using a LLM with tools that can access a user's Github account(ユーザーの GitHub アカウントにアクセスできるツール付き LLM を使ったチャットインターフェース) - Claude will need a set of tools to access the user's data(Claude はユーザーデータへアクセスするためのツール一式を必要とする) - 右側(赤茶色の背景パネル):チャット UI のモック。吹き出し You:「What open pull requests are there across all my repositories?」、AI:「…」、下部にテキスト入力欄と Send ボタン

[0:37] サンプルアプリは、また別のチャットインターフェースにします。

[0:41] ユーザーが自分の GitHub データについて Claude とチャットできるようにするものです。

[0:45] たとえばユーザーが「私のすべてのリポジトリを横断して、どんなオープンなプルリクエストがあるか?」のような質問をしたら、

[0:50] 期待される動きとしては、Claude はおそらくツールを使って GitHub にアクセスし、そのユーザーのアカウントを見て、どんなオープンなプルリクエストがあるか、あるいはオープンなリポジトリなり何なりを確認するはずです。

🖥 [0:53] 画面(図が展開) 左にチャット UI、中央に「Our Server」枠。その内部に2つのツール: - tool get_repos() —(黒いピル型ボタン)schemafn - tool get_pull_requests() —(黒いピル型ボタン)schemafn

両ツールから右の「Github API」(赤茶色ボックス)へ矢印

[1:02] ここでのポイントは、これをおそらく「ツール一式(a set of tools)」を使って実装するだろう、ということです。

[1:07] ここで手短に触れておきたいのは、GitHub には膨大な量の機能があるということです。

[1:12] リポジトリ、プルリクエスト、イシュー、プロジェクト、その他大量のものがあります。

🖥 [1:10] 画面(スライド「Tool Functions」) - 箇条書き: - To handle all of Github's functionality, we'd have to create an incredible number of tool schemas and functions(GitHub の全機能を扱うには、途方もない数のツールスキーマと関数を作る必要がある) - This is all code that we (developers) have to write, test, and maintain(これは全部、私たち開発者が書き・テストし・保守しなければならないコード)〔太字〕 - 右側:「Our Server」枠の中にツールが 8 個並ぶグリッド。各ツールに schemafn の黒ピル: get_repos() / list_repos() / create_repos() / search_issues() / update_issue() / create_issue() / get_issue() / create_file()

[1:18] ですから、完全な GitHub チャットボットを作ろうとすると、本当に途方もない数のツールを自作しなければならなくなります。

[1:24] あのサンプルアプリを作りたければ、これらすべてのスキーマとすべての関数を書く責任を、私たちが負うことになります。

[1:29] そしてこれは全部、あなたや私のような開発者が書いて、テストして、保守しなければならないコードです。

[1:34] それは大変な労力で、私たちに大きな負担がのしかかります。

[1:41] この「開発者に大きなインテグレーション群を保守させる」という課題こそ、Model Context(Protocol)が解決を狙う主要な難題の1つです。

🖥 [1:50] 画面(スライド「Model Context Protocol」) 見出し下の説明文:Shifts the burden of tool definitions and execution onto MCP Servers.(ツールの定義と実行の負担を MCP サーバーへ移す) - 左:「Our Server」枠。内部に tool get_repos()schemafn の黒ピル付き) - 右上:「MCP Server」枠(この時点では中身は空) - 右下:「Claude」ボックス - Our Server ↔ MCP Server、Our Server ↔ Claude の双方向矢印

[1:49] MCP は、ツールを定義して実行するという負担を、あなたのサーバーから「MCP サーバー」と呼ばれる別のものへ移します。

[1:57] つまり、ここにあるこのツールは、もう私たちが作らなくてよいのです。

[2:00] その代わり、それはどこか別の場所——この MCP サーバーの内部——で作られ、実行されることになります。

[2:05] これらの MCP サーバーは、外部のサービスへのインターフェースのようなものだと考えることができます。

🖥 [2:06] 画面(スライド「MCP Servers」) 見出し下の説明文:MCP Servers provide access to data or functionality implemented by some outside service.(MCP サーバーは、外部サービスが実装するデータや機能へのアクセスを提供する) - 左:「Our Server」枠(中身は空) - 中央:「MCP Server for Github」枠。内部に tool get_repos()schemafn)。そこから右の「Github」(赤茶色ボックス)へ矢印 - 下:「Claude**」ボックス - Our Server ↔ MCP Server for Github、Our Server ↔ Claude の双方向矢印

[2:10] たとえば私は GitHub MCP サーバーを持てるでしょう。これは、まさに GitHub が提供するデータと機能へのアクセスを提供してくれます。

[2:15] 本質的には、GitHub まわりの大量の機能を包み上げて、ツール一式という形でこの MCP サーバーの中に配置しているわけです。

[2:26] この時点で、MCP サーバーが何なのか、ごく基本的な理解ができました。MCP サーバーは、何らかの外部サービスに関連する機能を公開するツール一式へのアクセスを与えてくれるものです。

[2:35] そしてここでの利点は、あなたや私が、これらのさまざまなツールスキーマや関数などを自作しなくてよい、という点です。

[2:42] さて、この基本的な理解ができたところで、MCP サーバーを初めて学んだ多くの人が抱く、非常によくある質問にいくつか答えておきたいと思います。

[2:47] いつも決まって出てくる、3つのよくある質問です。

🖥 [2:50] 画面(スライド「Common Questions」) — 3行の Q&A テーブル | 質問(左・グレー地) | 回答(右) | |---|---| | Who authors the MCP Server?(誰が MCP サーバーを作るのか?) | Anyone! Often the service provider itself will make their own MCP implementation. You can make a MCP server to wrap up access to some service.(誰でも! 多くの場合、サービス提供者自身が自前の MCP 実装を作る。あなたも、何らかのサービスへのアクセスを包む MCP サーバーを作れる) | | How is using an MCP Server different from just calling a service's API directly?(MCP サーバーの利用は、サービスの API を直接呼ぶのとどう違う?) | MCP Servers provide tool schemas + functions.(MCP サーバーはツールスキーマ+関数を提供する)
If you want to directly call an API directly, you'll be authoring those on your own.(API を直接呼びたいなら、それらを自分で書くことになる) | | Sounds like MCP Servers and tool use are the same thing.(MCP サーバーとツールユースは同じものに聞こえるが) | MCP Servers provide tool schemas + functions already defined for you.(MCP サーバーは、すでに定義済みのツールスキーマ+関数を提供してくれる) |

[2:53] 1つ目のよくある質問は「これらの MCP サーバーは誰が作るのか?」です。答えは「誰でも」。誰でも MCP サーバーの実装を作れます。

[2:58] ただし非常によくあるのは、サービス提供者が自分たちの公式実装を作るケースです。

[3:03] たとえば AWS が、自社の公式 MCP サーバー実装をリリースすると決めるかもしれません。その中には、あなたが使えるさまざまなツールが幅広く入っているかもしれません。

[3:15] 2つ目のよくある質問は「MCP サーバーを使うことは、サービスの API をただ直接呼ぶのと何が違うのか?」です。

[3:22] 先ほど見たように、GitHub のような API を直接呼びたいなら、このツールを自分たちで書かなければなりません。そうすれば GitHub を直接呼べます。

🖥 [3:26 → 3:30] 画面(スライド「MCP Servers」に戻り、アニメーション) Q2 の説明に合わせて図が変化:get_repos() ツール(schemafn)が MCP Server for Github の枠から「Our Server」枠の内部へ移動し、Our Server 内のツールから「Github」へ直接矢印が伸びる(=API 直叩きの構図)。下段の Our Server ↔ Claude の双方向矢印はそのまま。

[3:38] では、(直接呼ぶ場合)何が変わったのでしょうか? 実際に変わったのは、スキーマを自分たちで書き、関数の実装も自分たちで書くようになった、という点だけです。

[3:47] つまり MCP サーバーを挟むだけで、私たちは少しだけ時間を節約しているのです。

[3:49] 最後のよくある質問は、質問というより、MCP に対して人々が抱きがちな「よくある批判」です。

🖥 [3:46] 画面 — スライドが「Common Questions」に戻る(3つ目の行にフォーカスが移る)

[3:54] そしてこの批判は、たいてい MCP が何なのかをよく理解していない人たちから出てきます。

[3:59] よく見かけるのは「MCP とツールユースは同じものだ」という発言です。

[4:05] 今しがた説明したとおり、MCP サーバーとツールユースは補完関係にあります。別のものですが、互いに補完し合うのです。

[4:12] MCP の背後にある考え方は、ツールの関数とツールのスキーマを、あなたが書かなくてよい、というものです。

[4:16] それは誰か他の人があなたの代わりにやってくれて、この MCP サーバーの中に包み込まれています。

[4:21] ですから、あるレベルでは、確かに似ています。どちらの場合もツールユースの話をしているのですから。しかし MCP サーバーの話は、実際には「その作業を誰がやるのか」についての話なのです。

[4:32] もしこの批判をどこかで見かけたら——繰り返しますが——それはたいてい、MCP が何なのかをよく理解していない人の発言です。

英語逐語(ASR全文)

In this module, we are going to focus on model context protocol. MCP is a communication layer designed to provide Claude with context and tools without requiring you, the developer, to write a bunch of tedious code. When you first get started with MCP, you will see diagrams that look like this very often. It shows two major elements of MCP, namely the client and the server. The server often contains a number of internal components, named tools, resources, and prompts. Now there's a lot of terminology here. So to help you understand all of this, we're going to imagine that we are building a small app and see how MCP fits into it. Our sample app is going to be another chat interface. It's going to allow a user to chat with Claude about their GitHub data. So if a user asks a question like, what open pull request do I have across all my different repositories? the expectation is that Claude is probably going to make use of a tool to reach out to GitHub, access the user's account, and see what open pull requests they have, maybe open repositories or whatever else. The point here is that we would implement this probably by using a set of tools. Now one thing I want to mention really quickly is that GitHub has a tremendous amount of functionality. There are repositories, pull requests, issues, projects, and tons of other things. So to have a complete GitHub chatbot, we would really have to author a tremendous number of tools. If we wanted to build that sample app, we would be on the hook for authoring all these schemas and all these functions. And this is all code that you and I as developers would have to write, test and maintain. That's a lot of effort, a lot of burden being placed on us. This challenge of making developers maintain a big set of integrations is one of the primary difficulties that model context aims to solve. MCP shifts the burden of defining and running tools from your server to something else called an MCP server. So no longer would you and I have to author this tool right here. Instead, it would be authored and executed somewhere else inside of this MCP server. These MCP servers can really be thought of as like an interface to some outside service. So I might have a GitHub MCP server that provides access to data and functionality provided by specifically GitHub. We're essentially wrapping up a ton of functionality around GitHub and placing it into this MCP server in the form of a set of tools. So at this point, we have a very basic understanding of what a MCP server is. It gives us access to a set of tools that exposes functionality related to some outside service. And the benefit here is that you and I do not have to author all these different tool schemas and functions and so on. Now that we have this basic understanding, I want to address some very common questions that a lot of people have when they first learn about MCP servers. So three common questions that seem to always come up. The first common question is who authors these MCP servers? And the answer is anyone. Anyone can make an MCP server implementation. But very often you will find that service providers make their own official implementation. So for example, AWS might decide to release their own official MCP server implementation and inside of it, it might have a wide variety of different tools available for you to use. The second common question is how is using a MCP server different than just calling a services API directly? Well, as we just saw, if we wanted to call a API directly, such as GitHub, then we would have to author this tool ourselves. And now we can call GitHub directly. So what did we gain here? Well, all that really changed was we are now having to author the schema ourselves and the function implementation ourselves. So simply by adding in the MCP server, we are saving ourselves a little bit of time. The final common question is more of a common criticism that you're going to see people have around MCP. And this criticism is most often coming from people who don't quite understand what MCP is all about. So very often you will see people saying MCP and tool use are the same thing. Well, as I have just laid out to you, MCP servers and tool use, they are complementary. They are different things, but they are complementary. The idea behind MCP is that you do not have to author the tool function and the tool schema. That is something that's done for you by someone else and is being wrapped up inside of this MCP server. So at some level, yeah, they're kind of similar because we are talking about tool use in both cases, but MCP servers are really talking about who is doing the actual work. So if you ever see this criticism, again, it's usually because people don't quite understand what MCP is all about.

↑ 目次へ戻る

Building with the Claude API | Lesson 55「MCP clients」(全文ナレッジ)

動画 5分06秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04

日本語ナレッジ(要点整理)

MCP クライアントの役割

  • MCP クライアントの目的=自分のサーバーと MCP サーバーの間の通信手段を提供すること(スライド原文:The MCP Client provides communication between your server and a MCP Server)。
  • クライアントは、その MCP サーバーが実装する全ツールへのアクセスポイントになる。

トランスポート非依存(Transport Agnostic)

  • MCP は transport agnostic=クライアントとサーバーはさまざまなプロトコルの上で通信できるという意味のしゃれた用語。
  • 現在非常によくある構成:MCP サーバーを MCP クライアントと同じ物理マシンで動かす。同一マシンなら standard input/output(標準入出力・stdio) で通信できる。このコースのこのセクションで後ほどセットアップするのはこの形。
  • 他にも HTTP/WebSocket ほか多様な方式で接続できる。

メッセージ交換(MCP Communication)

  • 接続確立後、クライアントとサーバーはメッセージの交換で通信する。許されるメッセージはすべて MCP 仕様(spec)で定義されている。
  • このコースで注目する主なメッセージ型:
  • ListToolsRequest(クライアント→サーバー):「提供している全ツールを列挙して」
  • ListToolsResult(サーバー→クライアント):提供可能な全ツールのリストを返す
  • CallToolRequest(クライアント→サーバー):「このツールをこの引数で実行して」
  • CallToolResult(サーバー→クライアント):ツール実行の結果を返す

フルフロー:6者のシーケンス(User / Our Server / MCP Client / MCP Server / GitHub / Claude)

「リポジトリを教えて」という1つの質問が処理される流れ(後の実装で全部品が登場する重要図解): 1. User → Our Server:「What repositories do I have?」のようなクエリを送信。 2. Our Server は Claude へのリクエストにツール一覧を含める必要があると気づく → まず MCP クライアント経由の「寄り道」をする。 3. Our Server → MCP Client:「Claude に送るツール一覧が欲しい」→ MCP Client → MCP Server:ListToolsRequestMCP Server → MCP Client:ListToolsResult → クライアントがツール一覧を Our Server に渡す。 4. Our Server → Claude:ユーザーの元メッセージ+ツール一覧(Query + Tools)でリクエスト。 5. Claude はツールを見て「この質問に答えるにはツールを呼びたい」と判断 → ToolUse メッセージパートで応答。 6. Our Server は「Claude がツールを実行したがっている」と認識するが、ツール実行の担当はもはや Our Server ではなく MCP サーバー。→ Our Server → MCP Client:「このツールを Claude 指定の引数で実行して」。 7. MCP Client 自身もツールは実行しない。→ CallToolRequest を MCP Server へ送信。 8. MCP Server → GitHub:フォローアップのリクエスト(ここで実際にそのユーザーのリポジトリ一覧を取得)→ GitHub が応答。 9. MCP Server が結果を CallToolResult に包んで MCP Client へ → クライアントが結果を Our Server へ渡す。 10. Our Server → Claude:tool result を user メッセージの中に格納してフォローアップリクエスト。 11. Claude は必要情報が揃い、最終応答(「Your repositories are…」)を作成 → Our Server → User へ返す。

実務ポイント(このレッスンから持ち帰るもの)

  1. 役割分担を1行で:MCP クライアント=通信の窓口(アクセスポイント)、MCP サーバー=ツールの実装と実行。クライアントもツールを実行しない(CallToolRequest を転送するだけ)という点を混同しない。
  2. stdio はローカル同居構成の標準。クライアントと同一マシンで動かすなら標準入出力、リモートなら HTTP/WebSocket 等——transport agnostic なので選べる。
  3. メッセージは MCP 仕様で定義済み。まず覚えるのは ListTools(Request/Result)と CallTool(Request/Result) の2ペア4種で十分。
  4. ツールユースの往復構造は API 単体のときと同じ(tool use ブロック → tool result を user メッセージで返す)。変わるのは「ツールの取得元と実行場所」が MCP クライアント経由になる点だけ——既存のツールユース知識がそのまま使える。
  5. このフロー図の各部品(client.list_tools / client.call_tool 相当)は、後のレッスンで自作 MCP クライアント/サーバーの実装にそのまま対応する。デバッグ時は「どの矢印で止まっているか」で切り分けると速い。

本文(時系列・日本語逐語+画面差し込み)

🖥 [0:00] 画面(スライド「MCP Client」) 見出し下の説明文:The MCP Client provides communication between your server and a MCP Server(MCP クライアントは、あなたのサーバーと MCP サーバーの間の通信を提供する) - 左:「Our Server」枠、内部に強調表示された「MCP Client」ボックス - 中央:「MCP Server for Github」枠、内部に tool get_repos()schemafn の黒ピル)→ 右の「Github」(赤茶色ボックス)へ矢印 - MCP Client ↔ MCP Server for Github の双方向矢印

[0:00] 次に調べていく Model Context Protocol の部分は、クライアントです。

[0:04] クライアントの目的は、あなたのサーバーと MCP サーバーの間の通信手段を提供することです。

[0:09] このクライアントが、そのサーバーによって実装されたすべてのツールへの、あなたのアクセスポイントになります。

🖥 [0:14] 画面(スライド「Transport Agnostic」) 見出し下の説明文:Communication between the Client and Server can be done over many different protocols(クライアントとサーバー間の通信は、多くの異なるプロトコルの上で行える) - 左:「Our Server」枠内の「MCP Client」 - 右:「MCP Server for Github」枠、下に斜体で「Running locally」(ローカルで実行中) - 両者を結ぶ太い双方向矢印の上にラベル「Standard IO

[0:14] さて、MCP は transport agnostic(トランスポート非依存)です。これはしゃれた用語ですが、要は、クライアントとサーバーはさまざまな異なるプロトコルの上で通信できる、というだけの意味です。

[0:20] 現時点で MCP サーバーを動かす非常によくある方法は、MCP クライアントと同じ物理マシン上で動かすことです。

[0:30] そして、この2つが同じマシン上で動いているなら、標準入出力(standard input output)を介して通信できます。それが、このセクションの後の方で私たちがセットアップしていくものです。

[0:38] ただし、MCP クライアントと MCP サーバーをつなぐ方法は他にもあります。HTTP や WebSocket、その他いくつもの方式・テクニックで接続することもできます。

🖥 [0:50] 画面(スライド「MCP Communication」) 見出し下の説明文:The MCP specification defines different types of messages that can be exchanged(MCP 仕様は、交換できるさまざまな種類のメッセージを定義している) - 左:「Our Server」枠内の「MCP Client」。右:「MCP Server for Github」枠 - 上の矢印(Client→Server):赤茶のラベル「ListToolsRequest」、注記(斜体)Give me a list of tools you provide(あなたが提供するツールの一覧をちょうだい) - 下の矢印(Server→Client):赤茶のラベル「ListToolsResult」、注記(斜体)Here are the tools I can run(私が実行できるツールはこれです)

[0:51] クライアントとサーバーの間に接続が確立されると、両者はメッセージを交換することで通信します。

[0:57] 許可される正確なメッセージは、すべて MCP の仕様(spec)の中で定義されています。

[1:02] あなたと私が注目していくメッセージ型のいくつかが、list tools request と list tools result です。

[1:09] 想像のとおり、ListToolsRequest はクライアントからサーバーへ送られ、サーバーが提供するさまざまなツールをすべて列挙するように求めます。

[1:13] サーバーはそれに対して ListToolsResult メッセージで応答します。この中には、サーバーが提供できるさまざまなツールすべてのリストが入っています。

[1:18] これから見ていくもう2つのよくあるメッセージ型が、call tool request と call tool result です。

[1:23] 前者は、何らかの特定の引数を付けてツールを実行するようサーバーに求めるもので、後者にはそのツール実行の結果が入ります。

[1:33] さて現時点で、サーバーとクライアントという考え方は手に入りましたが、これが全部どう連携して動くのかは、おそらくまだあまりクリアではないと思います。

[1:40] そこで、このビデオの残りでやることはこうです。たくさんの登場者たちの間で行われる呼び出しの例を、順に歩いて見ていきます。

[1:49] ですから、これはやや込み入ったプロセスになりますが、ユーザー、私たちが組み立てている自分のサーバー、MCP クライアント、MCP サーバー、データを取りに行く先のプロバイダーとしての GitHub、そして Claude、これらの間で交わされる通信を想像していきます。

🖥 [1:50] 画面(シーケンス図・初期状態) 6本のライフライン(上部に角丸ボックス、下へ点線): User / Our Server / MCP Client / MCP Server / Github / Claude (まだ矢印なし。※この直後のステップ描画から Claude と Github の列が入れ替わり、以降は User/Our Server/MCP Client/MCP Server/Claude/Github の並びで矢印が追加されていく)

[2:04] では始めましょう。もう一度、ステップ・バイ・ステップで。

[2:09] 最初に起きると期待されるのは、ユーザーが「私はどんなリポジトリを持っている?(What repositories do I have?)」のような、何らかのクエリや質問を私たちのサーバーに送ってくることです。

🖥 [2:05〜] 画面(シーケンス図・ステップ1) User → Our Server への矢印。ラベル(斜体):What repositories do I have?

[2:14] この時点で、Claude へリクエストを送るのは私たちのサーバーの仕事になります。

[2:18] ただしそのリクエストの中では、Claude がアクセスできるさまざまなツールをすべて列挙したいのです。

[2:22] そこで、私たちのサーバーが Claude へリクエストを送れるようになる前に、まず MCP クライアントとサーバーを経由する、ちょっとした寄り道をすることになります。

[2:29] 何が起きるかというと、こうです。

[2:31] サーバーは、ユーザーのクエリと一緒に Claude へ送るためのツール一覧を見る必要がある、と気づきます。

[2:37] そこでサーバーは MCP クライアントに、ツールの一覧を取ってくるように頼みます。

[2:40] MCP クライアントは、それを受けて ListToolsRequest をサーバーへ送ります。

🖥 [2:30] 画面(シーケンス図・ステップ2〜3) - Our Server → MCP Client の矢印。ラベル(斜体):I need a list of tools to send to Claude(Claude に送るツールの一覧が必要だ) - MCP Client → MCP Server の矢印。赤茶のラベルボックス「ListToolsRequest

[2:45] そしてサーバーは ListToolsResult で応答します。

[2:48] MCP クライアントはツールの一覧を手に入れたので、そのツール一覧をサーバーへ返します。

🖥 [3:00] 画面(シーケンス図・ステップ4〜5) - MCP Server → MCP Client の矢印。赤茶のラベルボックス「ListToolsResult」 - MCP Client → Our Server の矢印。ラベル(斜体):Here are the tools(ツールはこれです) - Our Server → Claude への長い矢印。ベージュのラベルボックス「Query + Tools

[2:54] これで私たちのサーバーは、Claude への最初のリクエストを行うために必要なものをすべて持っています。

[2:58] ユーザーからの元メッセージと、含めるべきツールの一覧の両方です。

[3:03] そこで私たちのサーバーは、そのクエリとツール一式を持って Claude へリクエストを送れます。

[3:08] Claude はツールを眺めて、こう気づきます。「そうだな、ここにあるユーザーの元の質問に答えるためには、ぜひツールを呼びたい」。

[3:17] そこで Claude は、何らかの tool use メッセージパートで応答してきます。

🖥 [3:30] 画面(シーケンス図・ステップ6〜7) - Claude → Our Server への長い戻り矢印。ベージュのラベルボックス「ToolUse」 - Our Server → MCP Client の矢印。ラベル(斜体):Please run this tool with these args(このツールをこの引数で実行して)

[3:20] この時点で私たちのサーバーは、Claude がツールを実行したがっていると気づきます。しかし、私たちのサーバーは、もはやツールの実行を実際に担当してはいません。

[3:28] その代わり、私たちのツールは MCP サーバーによって実行されることになります。

[3:34] そこで、Claude が求めているツールを実行するために、私たちのサーバーは MCP クライアントに「Claude から提供された特定の引数付きで、ツールを実行して」と頼みます。

[3:44] ところが MCP クライアントは、実際にはツールを実行しません。

[3:45] クライアントは CallToolRequest を MCP サーバーへ送ります。

[3:51] MCP サーバーはそのリクエストを受け取り、GitHub へフォローアップのリクエストを行います。

[3:56] ここが、この特定のユーザーに属するリポジトリの一覧を、実際に取得する場所になります。

[4:01] GitHub はそのリポジトリ一覧で応答します。

[4:04] それから MCP サーバーは、そのデータを CallToolResult の中に包み込み、MCP クライアントへ送り返します。

[4:08] そして MCP クライアントが、その結果を私たちのサーバーへ手渡します。

🖥 [4:00] 画面(シーケンス図・ステップ8〜10) - MCP Client → MCP Server の矢印。赤茶のラベルボックス「CallToolRequest」 - MCP Server → Github への長い矢印。ラベル(斜体):Request to Github - Github → MCP Server への戻り矢印。ラベル(斜体):Response - MCP Server → MCP Client の矢印。赤茶のラベルボックス「CallToolResult」 - MCP Client → Our Server の矢印。ラベル(斜体):Here's the result of running the tool(ツール実行の結果はこれです)

[4:14] これで私たちのサーバーはリポジトリの一覧を持ったので、その tool result を user メッセージの中に格納して、Claude へフォローアップのリクエストを行えます。

[4:21] この tool result には、Claude が求めていたリポジトリの一覧が入っています。

[4:27] これで Claude は、最終的な応答を組み立てるために必要な情報をすべて持ちました。

[4:32] そこで Claude は「あなたのリポジトリは……(Your repositories are...)」といったテキストを書き出し、それを私たちのサーバーへ送り返します。そして私たちのサーバーが、それをユーザーへと送り返すのです。

🖥 [4:40] 画面(シーケンス図・完成形) 最後の3本が追加され、フロー全体が完成: - Our Server → Claude への長い矢印。ベージュのラベルボックス「toolResult」 - Claude → Our Server への戻り矢印。ラベル(太字斜体):Your repositories are... - Our Server → User への矢印。ラベル(太字斜体):Your repositories are...

[4:42] はい、というわけで、このフローは、ええ、かなり複雑です。

[4:47] これをお見せした理由は、この後あなたと私が自作の MCP クライアントと MCP サーバーを実装し始めるときに、これらのさまざまな部品をすべて目にすることになるからです。

英語逐語(ASR全文)

The next portion of model context protocol that we're going to investigate is the client. The purpose of the client is to provide a means of communication between your server and a MCP server. This client is going to be your access point to all the tools implemented by that server. Now MCP is transport agnostic. This is a fancy term that just says that the client and the server can communicate over a variety of different protocols. A very common way to run a MCP server right now is on the same physical machine as the MCP client. And if these two things are running on the same machine, then they can communicate over standard input output. And that's what we are going to be setting up later on inside this section. There are, however, other ways we can connect the MCP client with the MCP server. So they can also connect over HTTP or web sockets or any of a number of other varieties or techniques. Once a connection has been formed between the client and the server, they communicate by exchanging messages. The exact messages that are allowed are all defined inside of the MCP spec. Some of the message types that you and I are going to be focusing are the list tools request and the list tools result. As you guessed, list tools request is sent from the client to the server and asks the server to list out all the different tools that it provides. The server would then respond with a list tools result message, which contains a list of all the different tools that it can provide. Two other common message types that you and I are going to see are the call tool request and call tool result. The first will ask the server to run a tool with some particular arguments and the second will contain the result of the tool run. Now at this point in time we've got this idea of a server and a client but I suspect it's probably not really clear how all this stuff really works together. So here's what we're going to do in the remainder of this video. We are going to walk through a example call between a lot of different things. So this will be kind of an involved process, but we're going to imagine the communication that goes on between a user, our server that we're putting together, a MCP client, the MCP server, GitHub as some provider that we're trying to access some data from, and Claude. So let's get to it. Again, step by step. First thing we would expect to happen is a user to submit some kind of query or question to our server, like what repositories do I have? At this point, it would be up to our server to make a request off to Claude. But in that request, we want to list out all the different tools that Claude has access to. So before our server can make the request off to Claude, it's first going to go through a little side detour through the MCP client and the server. So here's what happens. The server is going to realize that it needs to see a list of tools to send off to Claude, along with the user's query. So it's going to ask the MCP client to get a list of tools. The MCP client, in turn, is going to send a list tools request off to the server. And the server will respond with a list tools result. Now that our MCP client has a list of the tools, it will give that list of tools back to the server. And now our server has everything it needs to make an initial request off to Claude. It has both the original message from the user and a list of tools to include. So our server can make a request off to Claude with that query and the set of tools. Claude is going to take a look at the tools and realize, you know what, in order to answer the user's original question right here, I really want to call a tool. So Claude would respond with some tool use message part. At this point, our server is going to realize that Claude wants to run a tool, but our server is no longer really in charge of executing any tools. Instead, our tools are going to be executed by the MCP server. So in order to run the tool that Claude is asking for, our server is going to ask the MCP client to run a tool with some particular arguments that were provided by Claude. The MCP client, however, doesn't actually run the tool. It's going to send a call tool request off to the MCP server. The MCP server will receive that request and make a follow-up request off to GitHub. So this is where we would actually be getting a list of repositories that belong to this particular user. GitHub would respond with that list of repositories. Then the MCP server would wrap up that data inside of a call tool result and send that back to the MCP client. Then the MCP client in turn would hand the result off to our server. Now our server has the list of repositories and it can make a follow-up request to Claude with the tool result parked inside of a user message. So this tool result would include the list of repositories that Claude was asking for. And now Claude has all the information it needs to formulate a final response. So it'll write out some text of something like your repositories are, and then send that back to our server, and our server would send it on back to our user. All right, so this flow, yes, it is rather complicated. The reason I want to show you this is that we are going to see all these different pieces as you and I start to implement our own custom mcp client and mcp server a little bit later on

↑ 目次へ戻る

Building with the Claude API | Lesson 56「Project setup」(全文ナレッジ)

動画 3分08秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04

日本語ナレッジ(要点整理)

これから作るもの:CLI ベースのチャットボット

  • MCP の理解を深めるため、自作の CLI チャットボットを実装していく。クライアントとサーバーが実際にどう連携するかを体感するのが狙い。
  • プロダクト仕様(Project! スライド):
  • ドキュメント一式を扱える CLI チャットボット(ドキュメントはフェイクで、メモリ内にのみ保持
  • Claude がドキュメントを読めること/編集できること
  • ユーザーは @doc_name でドキュメントを「メンション」できる(そのドキュメントの中身が自動でコンテキストに含まれる)
  • ユーザーは /command_name で「コマンド」を実行できる
  • 小さな MCP クライアントを作り、自作のカスタム MCP サーバーに接続する。サーバーには当面2つのツールを実装:①ドキュメントの内容を読むツール、②ドキュメントの内容を更新するツール。

重要な注意(Important Note!)

  • 通常のプロジェクトでは、MCP クライアントか MCP サーバーの「どちらか一方」だけを実装するのが普通。
  • MCP サーバーだけを作る例:自分たちのサービスへのアクセスを世界中の開発者に配布したい場合。
  • MCP クライアントだけを作る例:他のエンジニアが実装済みの外部 MCP サーバー群に接続したい場合。
  • このプロジェクトで両方作るのは、仕組みの理解のためにひとつのプロジェクトに同居させているだけ(実務の標準形ではない)。

セットアップ手順

  • ビデオ添付の cli_project.zip をダウンロード → 解凍 → そのプロジェクトディレクトリでコードエディタを開く。
  • プロジェクト構成(VS Code 上):core/.env.gitignoremain.pymcp_client.pymcp_server.pypyproject.tomlREADME.mduv.lock
  • README.md にセットアップ手順あり:
  • .env ファイルに API キーを入れるANTHROPIC_API_KEY=""
  • 依存パッケージのインストール(uv を使う場合/使わない場合の両方の手順)
  • 起動方法:プロジェクトディレクトリ内で、uv 利用なら uv run main.py、uv なしなら python main.py
  • 起動するとチャットプロンプトが表示され、「what's one plus one」のような質問にすぐ応答が返れば成功。以降のビデオで新機能を足していく。

実務ポイント(このレッスンから持ち帰るもの)

  1. 実務では client か server の片方だけ実装が標準形。「自サービスを配布したい→サーバーを書く」「既存サーバー群を使いたい→クライアントを書く」。両方書くのは学習用の構成だと理解しておく。
  2. ドキュメントをメモリ内 dict で持つフェイク構成は、MCP の学習に外部依存(DB・ファイル IO)を持ち込まないための設計。プロトタイプでは同じ手が使える。
  3. @doc_name メンション(=コンテキスト自動注入)と /command_name コマンドという UI パターンは、後のレッスンの Resources と Prompts にそれぞれ対応していく伏線。
  4. セットアップの型:zip 展開 → README.md の手順どおり .env に ANTHROPIC_API_KEY → uv で依存導入 → uv run main.py。uv がなければ python main.py でも動く。
  5. 着手前に素の状態で起動確認(1+1 が返るか)をしてから機能追加に入る——動く基盤を確かめてから積む、という進め方自体が実務の定石。

本文(時系列・日本語逐語+画面差し込み)

🖥 [0:00] 画面(スライド「Project!」) - 箇条書き(左): - CLI based chatbot that allows users to chat with a set of documents(ドキュメント一式とチャットできる CLI ベースのチャットボット) - Claude should be able to read a document(Claude はドキュメントを読めること) - Claude should be able to edit a document(Claude はドキュメントを編集できること) - Users can "mention" a document by writing out "@doc_name"(ユーザーは「@doc_name」と書いてドキュメントを「メンション」できる) - The doc's contents will automatically be included as context(そのドキュメントの内容は自動でコンテキストに含まれる) - Users can run a "command" with "/command_name"(ユーザーは「/command_name」で「コマンド」を実行できる) - 右:黒いターミナルのモック。入力行 > Can you please summarize the contents of @ の直後に補完リストがポップアップ: deposition.md / design.md / financials.md / outlook.md / plan.md / spec.md の各行に「Resource」タグ

[0:00] MCP のいくつかの側面をよりよく理解するために、私たちは自作の CLI ベースのチャットボットを実装し始めます。

[0:06] これによって、クライアントとサーバーが実際にどう連携して動くのか、より良いイメージがつかめるはずです。

[0:10] このビデオでは、少しだけプロジェクトのセットアップを行い、これから何を作るのかを正確に理解する手助けをしたいと思います。

[0:15] こちらに、作るものについてのプロダクト説明がたくさん書いてあります。

[0:18] これは時間をかけて全部見ていきます。

[0:20] 今のところは、高レベルの理解だけつかんでもらえれば大丈夫です。

[0:23] さて、申し上げたとおり、これは CLI ベースのチャットボットになります。

[0:27] ユーザーがドキュメントのコレクションを扱えるようにします。

[0:30] これらはフェイクのドキュメントです。単にメモリ内に保存されるだけです。

[0:33] 小さな MCP クライアントを作り、それが私たち自身のカスタム MCP サーバーに接続するようにします。

🖥 [0:28] 画面(スライド「Our MCP Server」図) - 左:「Our Server」枠、内部に「Our MCP Client」ボックス - 中央:「Our MCP Server」枠、内部に赤茶のボックス2つ:「Tool to read a doc」(ドキュメントを読むツール)/「Tool to update a doc」(ドキュメントを更新するツール) - 右:ドキュメント群のボックス4つ:document.pdf / spreadsheet.xlsx / report.txt / spec.md - Our MCP Client ↔ Our MCP Server の双方向矢印、サーバーから各ドキュメントへ結線

[0:40] 当面、このサーバーには2つのツールが実装されます。

[0:43] ドキュメントの内容を読むツールが1つ、そしてドキュメントの内容を更新するツールが1つです。

[0:49] 繰り返しますが、これらのドキュメントは右側にあるこれらです。

[0:52] 全部フェイクなので、メモリ内にのみ永続化されます。

[0:55] それだけです。

[0:55] さて、先へ進む前に、非常に重要な注意があります。このプロセス全体について、ぜひ理解しておいてほしいことです。

🖥 [1:03] 画面(スライド「Important Note!」) 見出し下の説明文: Normally a project will implement either an MCP client or an MCP server.(通常、プロジェクトは MCP クライアントか MCP サーバーの「どちらか」を実装する) Our project will implement both just so we understand how they work(このプロジェクトでは、仕組みを理解するために「両方」を実装する) - 図:「Our Server」枠(内部に「MCP Client」)↔「MCP Server」枠(中身は空)の双方向矢印

[1:03] それは、普通のプロジェクトでは、典型的には MCP クライアントか MCP サーバーのどちらか一方を実装する、ということです。

[1:11] つまり実際のプロジェクトでは、MCP サーバーだけを作って世界に配布し、私たちが作り上げた何らかのサービスに開発者たちがアクセスできるようにする、ということがあるでしょう。

[1:19] あるいは逆に、MCP クライアントだけを作るプロジェクトかもしれません。

[1:24] その場合の意図は、他のエンジニアたちによってすでに実装されている外部の MCP サーバー群に接続する、というものになるでしょう。

[1:29] ですのでこのプロジェクトでは、クライアントとサーバーの両方を作りますが、それは、この仕組みが実際にどう連携するのかをよりよく理解してもらうために、1つのプロジェクトの中でやっているだけです。

[1:44] さて、この断り書きが済んだところで、少しだけセットアップをやっていきましょう。

[1:48] このビデオに添付されているはずの、cliproject.zip という名前のファイルを見つけてください。

[1:50] その中に、このプロジェクトのスターターコードが入っています。

[1:53] その zip ファイルを必ずダウンロードし、解凍して、そのプロジェクトディレクトリの中でコードエディタを開いてください。

[1:59] 少し時間を節約するために、私はもうやってあります。

[2:02] つまり、その小さなプロジェクトの中で、すでにコードエディタを開いた状態です。

🖥 [2:01] 画面(VS Code・プロジェクト「mcp」) エクスプローラーにプロジェクト構成: core(フォルダ)/.env.gitignoremain.pymcp_client.pymcp_server.pypyproject.tomlREADME.mduv.lock (エディタ本体は未オープンで VS Code のショートカット一覧が見える:Show All Commands ⇧⌘P ほか)

[2:06] このプロジェクトの中では、README.md ファイルを見てみることをおすすめします。

[2:10] この中に、セットアップの手順を置いておきました。

[2:12] このプロジェクト内の .env ファイルにあなたの API キーを確実に入れるプロセスを、順に案内してくれます。

[2:18] さらに、依存パッケージをインストールするプロセスも案内します。uv を使う場合と、uv を使わない場合の両方です。

🖥 [2:00〜2:24] 画面(VS Code・README.md を開いた状態) パンくず:README.md > # MCP Chat > ## Prerequisites。本文(Markdown ソース): - - Python 3.10+- Anthropic API Key - ## Setup - ### Step 1: Configure the environment variables - 1. Create or edit the .env file in the project root and verify that the following variables are set correctly: - コードブロック:ANTHROPIC_API_KEY="" # Enter your Anthropic API secret key - ### Step 2: Install dependencies - #### Option 1: Setup with uv (Recommended) - [uv](https://github.com/astral-sh/uv) is a fast Python package installer and resolver.

[2:24] このセットアップを全部済ませたら、すぐにスタータープロジェクトを実行できます。

[2:28] 実行するには、ターミナルの中で、自分のプロジェクトディレクトリの中にいることを確認してください。

[2:34] 私はプロジェクトを MCP と名付けました。その中に、さまざまなプロジェクトファイルやフォルダが全部入っています。

[2:39] プロジェクトを実行するには、uv を使っている場合は uv run main.py を実行します。

[2:45] uv を使っていない場合は、単に python main.py になります。

🖥 [2:30] 画面(ターミナル・zsh) (mcp) → mcp ls __pycache__ main.py mcp_server.py core mcp_client.py README.md (mcp) → mcp uv run main.pyls でプロジェクト内容を確認したあと、uv run main.py を入力しているところ)

[2:47] さて、私は uv を使っているので、uv run main.py を実行します。

[2:54] それを実行すると、チャットプロンプトが表示されるはずです。そして「what's one plus one(1 足す 1 は?)」と聞けば、かなり素早く応答が見えるはずです。

[3:00] セットアップは以上です。これで、このアプリケーションに新しい機能を追加していくことに集中し始められます。

英語逐語(ASR全文)

To better understand some aspects of MCP, we are going to start to implement our own CLI-based chatbot. This is going to give us a better idea of how clients and servers actually work together. In this video, I want to do a little bit of project setup and just help you understand exactly what we're going to make. I've got a lot of product description over here of what we're going to build. We're going to go through all this over time. For right now, I just want you to get a high-level understanding. So, as I mentioned, it's going to be a CLI-based chatbot. We're going to allow users to work with a collection of documents. These are going to be fake documents. They're just going to be stored in memory. We're going to build out a small MCP client that is going to connect to our own custom MCP server. For right now, this server is going to have two tools implemented inside of it. One tool to read the contents of a document and one tool to update the contents of a document. Again, these documents are here on the right hand side. They're all fake, so they are going to be persisted only in memory. That's it. Now, before we go any further, there is a very important note, something I really want you to understand around this entire process. And that is that on a normal project, typically we would be implementing either a client or an MCP server. So on a real project, we might be authoring just an MCP server to distribute to the world and allow developers to access some service we have built up. Alternatively, we might be building a project where we make only a MCP client. and the intent here would be that we would be connecting to some outside MCP servers that have already been implemented by some other engineers. So in this project we are making both a client and a server and we're just doing that in one project so you get a better understanding of how this stuff actually works together. All right now that we have this disclaimer out of the way let's go through just a little bit of setup. Attached to this video you should find a file named cliproject.zip. Inside there is some starter code for our project. Make sure you download that zip file, extract it, and then open up your code editor inside of that project directory. Just to save a little bit of time, I have already done so. So I've already got my code editor open inside of that small project. Inside this project, I would encourage you to take a look at the readme.emd file. Inside here, I've placed some setup directions. So it's going to walk you through the process of making sure you put your API key into the .emd file inside this project. and it's also going to walk you through the process of installing dependencies, either with UV or without UV. Once you have gone through all this setup, you can then run the starter project right away. To do so, inside of your terminal, make sure that you are inside of your project directory, so I called my project MCP, and inside there I've got all my different project files and folders. To run the project, we will run uv run main.py if you are making use of UV. If you are not making use of UV, then it'll be just python main.py. Now I'm making use of uv so I'm going to do a uv run main.py and then when I run that I should see a chat prompt appear and if I ask what's one plus one I should see a response rather quickly. That is it for our setup so now we can start to focus on adding in some new features to this application.

※ ASR 注記:上記の「readme.emd」「.emd file」は音声認識の誤りで、実際の画面ではそれぞれ README.md.env ファイルを指している。

↑ 目次へ戻る

Building with the Claude API | Lesson 57「Defining tools with MCP」(全文ナレッジ)

動画 7分03秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04

日本語ナレッジ(要点整理)

このレッスンのゴール

  • CLI チャットボット用の MCP サーバーを作り始める。CLI 自体はすでに動く(Claude とチャットできる)が、MCP サーバーまわりの機能はまだ何もない。
  • サーバーには当面 2つのツールを実装:①ドキュメントを読むツール、②ドキュメントの内容を更新(編集)するツール。
  • 実装場所は、プロジェクトルートの mcp_server.py。ここには下準備として、基本の MCP サーバーのセットアップメモリ内にのみ存在するドキュメント群(docs 辞書)TODO 項目のリスト(今回は最初の2つ=ツール2本だけをやる)が置いてある。

公式 MCP Python SDK の威力

  • 過去に手書きしたツール定義は構文が多い(巨大な JSON スキーマ)。このプロジェクトでは公式 MCP Python SDK(コード中の mcp パッケージ)を使う。
  • SDK なら 1行で MCP サーバーが作れるmcp = FastMCP("DocumentMCP", log_level="ERROR")
  • ツール定義も簡単:@mcp.tool(name=..., description=...) デコレータ+関数定義を書くだけで、裏で MCP がツールの JSON スキーマを自動生成し、それを Claude に渡せる。
  • スライドの例:@mcp.tool(name="add_ints", description=...) を付けた def tool_fn(a=Field(...), b=Field(...)) -> int: return a + b で「add integers」ツールができる。

ツール①:read_doc_contents(ドキュメントを読む)

  • 目的:ドキュメント名を受け取り、その内容を返すだけ。ドキュメントは docs 辞書に格納済み(キー=ドキュメントの ID(実質は名前)、値=ドキュメントの内容)。
  • 実装手順: 1. 最初の TODO の直下に @mcp.tool(name="read_doc_contents", description="Read the contents of a document and return it as a string.")。 2. 理想的にはもっと肉付けした description を書く(Claude がいつ使うべきか明確になるように)。デモでは時間節約のため簡素な文で済ませる。 3. ツール関数 def read_document(doc_id: str = Field(description="Id of the document to read")): を定義。Field は Pydantic からインポートfrom pydantic import Field をファイル先頭に追加)。 4. 関数本体:存在しないドキュメントを Claude が要求したケースを先に処理 → if doc_id not in docs: raise ValueError(f"Doc with id {doc_id} not found") → 通過したら return docs[doc_id]
  • これだけでツール名・説明・期待される引数・引数の型・引数の説明が揃い、デコレータや Field 型を Python MCP SDK がまとめて解釈して JSON スキーマを生成してくれる。

ツール②:edit_document(ドキュメントを編集する)

  • まったく同じプロセスの繰り返し。@mcp.tool(name="edit_document", description="Edit a document by replacing a string in the documents content with a new string")
  • 関数 edit_document(read_document と命名を揃える)は3引数:
  • doc_id: str = Field(description="Id of the document that will be edited")
  • old_str: str = Field(description="The text to replace. Must match exactly, including whitespace")
  • new_str: str = Field(description="The new text to insert in place of the old text")
  • 中身は単純な find and replace:存在チェック(ValueError)→ docs[doc_id] = docs[doc_id].replace(old_str, new_str)
  • 2ツールとも完成したら TODO を削除。スキーマ手書きよりはるかに簡単——これが MCP Python SDK でツールを定義する良さ。

実務ポイント(このレッスンから持ち帰るもの)

  1. FastMCP でサーバーは1行mcp = FastMCP("名前", log_level="ERROR")。ツールは @mcp.tool(name=..., description=...)+関数シグネチャで宣言し、JSON スキーマは SDK が自動生成——手書きスキーマ時代の構文コストが消える。
  2. 引数メタデータは Pydantic の Field(description=...) で与える(from pydantic import Field を忘れない)。型注釈(doc_id: str)+Field 説明のセットがそのままスキーマの型と説明になる。
  3. description は本来しっかり書く。デモは簡素だが、実務では「いつこのツールを使うべきか」が Claude に伝わる肉付けをするのが定石。
  4. 不正入力はツール内で早期に raise(例:ValueError(f"Doc with id {doc_id} not found"))。存在チェック→本処理の順は両ツール共通の型。
  5. 編集ツールを old_str/new_str の完全一致 find & replace で設計するのは Claude 系ツールの頻出パターン(text editor ツールと同型)。「Must match exactly, including whitespace」を description に明記するのがポイント。
  6. 📄 実物コード(本プロジェクト完成版 mcp_server.py)のツール2本は下記のとおり。自作サーバーの雛形としてそのまま流用できる。

本文(時系列・日本語逐語+画面差し込み)

[0:00] それでは、私たちの CLI チャットボットのための MCP サーバーを作り始めましょう。

[0:04] ご覧いただいたとおり、CLI 自体はすでに動いていて、もう Claude とチャットできます。

[0:08] ただし、それに結びついた MCP サーバーまわりの追加機能は、まだ何もありません。

[0:12] そこで、当面2つのツールを持つことになる、この MCP サーバーを追加する作業をやっていきます。

🖥 [0:12] 画面(スライド「Our MCP Server」) - 「Our MCP Server」枠の内部に赤茶のボックス2つ:「Tool to read a doc」/「Tool to update a doc」 - 右側にドキュメント群:document.pdf / spreadsheet.xlsx / report.txt / spec.md(サーバーから各ドキュメントへ結線)

[0:17] ドキュメントを読むツールが1本、ドキュメントの内容を更新するツールが1本です。

[0:22] サーバーの実装は、ルートプロジェクトディレクトリ内の mcp_server.py ファイルに置くことになります。

[0:29] この中では、基本的な MCP サーバーをセットアップするための作業を、私がすでに少しだけ済ませてあります。

🖥 [0:29] 画面(VS Code・mcp_server.py ```python from mcp.server.fastmcp import FastMCP

mcp = FastMCP("DocumentMCP", log_level="ERROR")

docs = { "deposition.md": "This deposition covers the testimony of Angela Smith, P.E.", "report.pdf": "The report details the state of a 20m condenser tower.", "financials.docx": "These financials outline the project's budget and expenditu…", "outlook.pdf": "This document presents the projected future performance of the…", "plan.md": "The plan outlines the steps for the project's implementation.", "spec.txt": "These specifications define the technical requirements for the equ…", }

TODO: Write a tool to read a doc

TODO: Write a tool to edit a doc

TODO: Write a resource to return all doc id's

TODO: Write a resource to return the contents of a particular doc

TODO: Write a prompt to rewrite a doc in markdown format

TODO: Write a prompt to summarize a doc

``` (行末はエディタの表示幅で見切れ。エクスプローラー:core/.env/.gitignore/main.py/mcp_client.py/mcp_server.py/README.md)

[0:33] それから、メモリ内にのみ存在することになるドキュメントのコレクションを定義しました。

[0:38] そして最後に、いくつかの TODO 項目をまとめておきました。

[0:41] これらは、あなたと私がこのファイルの中で完了させていく、さまざまなタスクです。

[0:44] 今のところは、先ほど言ったとおり、最初の2項目——2つのツールを書き出すこと——だけに取り組みます。

[0:50] さて、私たちは過去にツールを自作したことがありますが、そこで見たとおり、まあ、構文が多いですよね。

[0:56] あの大きな JSON スキーマの数々です。

[0:58] でも、ここで良い知らせがあります。

[1:00] このプロジェクトでは、公式の MCP Python SDK を使います。

[1:05] この中で使っている、この MCP パッケージがそれです。

[1:09] この MCP パッケージは、そこに見えているような、たった1行のコードで私たちの MCP サーバーを作ってくれます。

🖥 [1:06] 画面(スライド「MCP SDK」) - 箇条書き: - The MCP project provides SDK's for building servers and clients in a variety of languages(MCP プロジェクトは、多様な言語でサーバーとクライアントを作るための SDK を提供している) - Our project is using the Python MCP SDK(このプロジェクトでは Python MCP SDK を使う) - The Python SDK makes it very easy to declare tools(Python SDK はツールの宣言をとても簡単にしてくれる) - 右:黒いコードパネル(右端は見切れ): python @mcp.tool( name="add_ints", description="Add two integers togeth…" ) def tool_fn( a=Field(description="First number to…"), b=Field(description="Second number t…") ) -> int: return a + b

[1:17] この SDK は、ツールの定義もとても簡単にしてくれます。

[1:20] ツールを定義するには、こちらの右側に見えているものを書き出すだけでいいのです。

[1:24] これで、この説明文と、渡すことが必須になる2つの引数を持った、add integers という名前のツールができます。

[1:31] このようなツール定義をひとたび書き出せば、舞台裏で MCP が私たちのためにツールの JSON スキーマを生成してくれます。それを取り出して Claude に渡せるのです。

[1:42] というわけで、見てのとおり、ツールを定義するといった基本的なことが、すぐにずっと簡単になり始めます。

[1:47] さて、申し上げたとおり、最初のタスクはこの2つの異なるツールを実装することです。

[1:51] それでは早速、mcp_server.py ファイルへ戻りましょう。

[1:56] そして、1本目のツール——ドキュメントを読むツール——の実装を始めます。

[1:59] ここでの唯一のゴールは、何らかのドキュメントの名前を受け取り、その内容を返すことです。

[2:05] 私たちのドキュメントはすべて、この docs 辞書の中にすでに置かれています。

[2:08] キーは、ドキュメントの ID、実質的には名前です。

[2:12] そして値が、ドキュメントの内容です。

[2:14] ですから、私たちのツールは本当にシンプルです。

[2:16] これらの文字列のうち1つを受け取り、この docs 辞書の中の該当する値を見て、それを返す。やるべきことはそれだけです。

[2:24] これを実装するために、最初の TODO を見つけて、そのすぐ下に、@mcp.tool と書き出して新しいツールを定義します。

[2:34] このツールには read doc contents という名前を与え、「read the contents of a document and return it as a string(ドキュメントの内容を読み、文字列として返す)」という説明を付けます。

🖥 [2:30] 画面(VS Code・mcp_server.py 編集中) ```python

TODO: Write a tool to read a doc

@mcp.tool( name="read_doc_contents", description="Read the contents of a document and return it as a string" ) `` (タブ表示はmcp_server.py 2 ●`=未保存変更あり。下に残りの TODO 5行が続く)

[2:41] 思い出してください。完璧な世界では、ここには本当に肉付けされた説明を書き込んで、このツールをいつ使うべきかが Claude に対して超明確になるようにします。

[2:51] でも今は、いつもどおり、少し時間を節約して、あなたに大量のテキストを打たせないために、ごく簡素な説明のままにしておきます。

[3:00] 次に、実際のツール関数を定義します。これは、このツールを実行すると決めたときに必ず実行される関数です。

[3:05] read document と呼ぶことにします。これは doc ID という引数を受け取ります。これは文字列(string)になります。

[3:14] そしてそれを、「ID of the document to read(読むドキュメントの ID)」という説明を持つ field に設定します。

[3:23] それから、この Field クラスをファイルの先頭で確実にインポートする必要があります。

[3:26] そこで先頭まで上がっていって、from Pydantic import field というインポートを追加します。

🖥 [3:30] 画面(VS Code・mcp_server.py ```python

TODO: Write a tool to read a doc

@mcp.tool( name="read_doc_contents", description="Read the contents of a document and return it as a string." ) def read_document( doc_id: str = Field(description="Id of the document to read") ): | ← カーソル(関数本体はまだ空)

TODO: Write a tool to edit a doc

…(残り TODO 5行)…

if name == "main": mcp.run(transport="stdio") `` (ファイル末尾には最初からmcp.run(transport="stdio")` のエントリポイントが用意されている)

[3:36] それから、ここの関数本体の中に戻ってきて、実際の実装を入れていきます。

[3:41] まず最初にやるのは、Claude が実際には存在しないドキュメントを要求してきたケースを、確実に処理しておくことです。

[3:47] そこで、if doc ID not in docs と書きます。

[3:51] 言い換えると、渡されたドキュメント ID が、この辞書の中にキーとして見つからなければ、

[3:56] value error を raise します。F 文字列で「doc with ID(doc ID)not found」とします。

[4:06] そして、このチェックを通過したら、実際のドキュメントを返します。

[4:09] つまり、return docs at doc ID です。

🖥 [4:00] 画面(VS Code・mcp_server.py ```python def read_document( doc_id: str = Field(description="Id of the document to read") ): if doc_id not in docs: raise ValueError(f"Doc with id {doc_id} not found")

return docs[doc_id]

`` (パンくずにmcp_server.py > Pyright > read_document`)

[4:13] はい、以上です。

[4:14] ツールを定義するのに必要なのは、これだけです。

[4:16] ツールの名前、その説明、期待される引数、その型、そしてその引数の説明まで指定しました。

[4:25] これらのさまざまなデコレータや field 型などは、すべてこの Python MCP SDK によってまとめて解釈され、私たちのために JSON スキーマを生成してくれます。

[4:36] この最初のツールを実装し終えたので、そこにある TODO を削除します。

[4:39] それでは、もう1つのツール——ドキュメントを編集するツール——を実装しましょう。まったく同じプロセスを繰り返します。

[4:46] mcp.tool と書き、edit document という名前を与え、説明は「edit a document by replacing a string in the document's context——失礼、content——with a new string(ドキュメントの内容の中の文字列を新しい文字列で置き換えることでドキュメントを編集する)」とします。

[5:03] それから実装のために、この関数を edit doc と呼ぶことにします……いや、一貫性を保つために edit document と呼びましょう。

[5:11] そして、ここではいくつかの異なる引数を受け取ります。1つ目はドキュメント ID、次に検索対象の old string、そしてその old string を置き換える new string です。

[5:22] では全部書き出しましょう。doc id は文字列で、「ID of the document that will be edited(編集されるドキュメントの ID)」という説明を持ちます。

[5:28] old string は文字列で、「the text to replace, must match exactly, including white space(置き換える対象のテキスト。空白を含め完全一致が必須)」という説明。

🖥 [5:30] 画面(VS Code・mcp_server.py 編集中) ```python

TODO: Write a tool to edit a doc

@mcp.tool( name="edit_document", description="Edit a document by replacing a string in the documents content wi…" ) def edit_document( doc_id:str = Field(description="Id of the document that will be edited"), old_str: str = Field(description="The text|") ← 入力中 ) `` (パンくずにmcp_server.py > Pyright > edit_document > old_str`。下に残り TODO 4行)

[5:53] それから new string。

[5:58] 「the new text to insert in place of the old text(古いテキストの代わりに挿入する新しいテキスト)」です。

🖥 [6:00] 画面(VS Code・mcp_server.py python def edit_document( doc_id:str = Field(description="Id of the document that will be edited"), old_str: str = Field(description="The text to replace. Must match exactly, incl…"), new_str: str = Field(description="The new text to insert in place of the old te…") ): if doc_id_ ← 本体を入力し始めたところ

[6:02] つまり、ここでのドキュメント編集は、ごくシンプルな find and replace(検索と置換)にすぎません。

[6:05] それだけです。

[6:07] もう一度、この中でも、Claude が実際に存在するドキュメントを要求していることを確認します。

[6:13] つまり、if doc ID not in docs なら、raise value error、

[6:18] F 文字列で「doc with ID not found」です。

[6:25] そして、正しいドキュメントが見つかった場合、編集はこうやります。

[6:27] docs at doc id は、docs at doc id の replace で old string を new string に置き換えたもの、とします。それで完成です。

🖥 [6:30] 画面(VS Code・mcp_server.py・edit_document 完成) ```python

TODO: Write a tool to edit a doc

@mcp.tool( name="edit_document", description="Edit a document by replacing a string in the documents content wi…" ) def edit_document( doc_id:str = Field(description="Id of the document that will be edited"), old_str: str = Field(description="The text to replace. Must match exactly, incl…"), new_str: str = Field(description="The new text to insert in place of the old te…") ): if doc_id not in docs: raise ValueError(f"Doc with id {doc_id} not found")

docs[doc_id] = docs[doc_id].replace(old_str, new_str)

TODO: Write a resource to return all doc id's

TODO: Write a resource to return the contents of a particular doc

```

[6:36] はい、というわけで、こんな感じで、本当にあっという間に2つのツール実装を組み上げました。

[6:42] 何度でも繰り返しますが、この MCP Python SDK でツールを定義するのは、スキーマ定義を手で書き出すよりずっと簡単です。

[6:53] 両方のツールが組み上がったので、そこの TODO を削除します。

🖥 [7:01] 画面(VS Code・mcp_server.py 冒頭を最終確認スクロール) ```python from pydantic import Field from mcp.server.fastmcp import FastMCP

mcp = FastMCP("DocumentMCP", log_level="ERROR")

docs = { "deposition.md": "This deposition covers the testimony of Angela Smith, P.E.", … }

@mcp.tool( name="read_doc_contents", description="Read the contents of a document and return it as a string.", ) `` (先頭にfrom pydantic import Field` が追加済み。ツール2本の TODO は消え、resource/prompt の TODO 4行が残る)

[6:57] よし、これで良いスタートです。私たちは MCP サーバーを組み上げ、その中に2つのツールを実装しました。


📄 実物コード(完成版 mcp_server.py・ツール2本の該当部分)

出典: docs/academy-notes/introduction-to-mcp/assets/cli_project_COMPLETE/mcp_server.py

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("DocumentMCP", log_level="ERROR")


docs = {
    "deposition.md": "This deposition covers the testimony of Angela Smith, P.E.",
    "report.pdf": "The report details the state of a 20m condenser tower.",
    "financials.docx": "These financials outline the project's budget and expenditures.",
    "outlook.pdf": "This document presents the projected future performance of the system.",
    "plan.md": "The plan outlines the steps for the project's implementation.",
    "spec.txt": "These specifications define the technical requirements for the equipment.",
}

from pydantic import Field


@mcp.tool(
    name="read_doc_contents",
    description="Read the contents of a document and return it as a string.",
)
def read_document(
    doc_id: str = Field(description="Id of the document to read"),
):
    if doc_id not in docs:
        raise ValueError(f"Doc with id {doc_id} not found")

    return docs[doc_id]


@mcp.tool(
    name="edit_document",
    description="Edit a document by replacing a string in the documents content with a new string",
)
def edit_document(
    doc_id: str = Field(description="Id of the document that will be edited"),
    old_str: str = Field(
        description="The text to replace. Must match exactly, including whitespace"
    ),
    new_str: str = Field(
        description="The new text to insert in place of the old text"
    ),
):
    if doc_id not in docs:
        raise ValueError(f"Doc with id {doc_id} not found")

    docs[doc_id] = docs[doc_id].replace(old_str, new_str)


if __name__ == "__main__":
    mcp.run(transport="stdio")

英語逐語(ASR全文)

Let's start to make an MCP server for our CLI chatbot. As you saw, the CLI itself already works, and we can already chat with Claude, but there's no additional functionality around the MCP server tied to it just yet. So we're going to work on adding in this MCP server that is going to have two tools in it for right now. It is going to have one tool to read a document, and one tool to update the contents of a document. The implementation of the server is going to be placed into the mcpserver.py file inside of the root project directory. Inside of here, I've already gone through a little bit of work to set up a basic MCP server. And then I defined a collection of documents that are going to exist only in memory. And then finally, I put together some different to-do items. So these are different tasks that you and I are going to complete inside of this file. For right now, as I just mentioned, we're going to be working only on these first two items, writing out two tools. Now we have authored tools in the past, and we saw that, well, there's a lot of syntax there. There's those big JSON schemas. But I got good news for you here. In this project, we're making use of the official MCP Python SDK. So that's what this MCP package is that we're making use inside of here. This MCP package is going to create our MCP server for us with just one line of code, like what you see right there. This SDK also makes it really easy to define tools. To define a tool, all we have to do is write out what you see on the right-hand side over here. This will make a tool named add integers with this description and two arguments that are going to be required to be passed into it. Once we write out a tool definition like this, behind the scenes, MCP is going to generate a tool JSON schema for us, which we can take and pass off to Claude. So right away, as you can see, it starts to get a lot easier to do some basic things like define tools. Now, as I mentioned, our first task is going to be to implement these two different tools. So let's go back over to our mcpserver.py file right away. And we're going to start to implement the first tool of reading a document. So the only goal here is to take in the name of some document and return the contents of it. All of our documents are already placed inside of this docs dictionary. The keys are the IDs or essentially names of a document. And the value is a document's contents. So our tool is really simple. We're going to take in one of these strings, look at the appropriate value inside this docs dictionary, and then return it. That's all we need to do. So to implement this, I'm going to find the first to-do and right underneath it, I'm going to define a new tool by writing out at mcp.tool. I'm going to give this tool a name of read doc contents and a description of read the contents of a document and return it as a string. And remember in a perfect world, we put in a really fleshed out description right here to make sure it's super clear to Claude exactly when to use this tool. But for right now, just as usual to save a little bit of time and keep you from having to type out a bunch of text here, I'm just going to leave in a very simple description. Then I will define my actual tool function. So this is the function to run whenever we decide to run this tool. I will call it read document. It's going to take in a argument of doc ID that is going to be a string, I'm going to set that to a field with a description of ID of the document to read. And then we need to make sure that we import this field class at the top. So I'm going to go up to the top and add an import from Pydantic import field. Then back down here inside of the function body, I'm going to put in my actual implementation. So the first thing I'm going to do is just make sure I handle the case in which Claude asks for a document that doesn't actually exist. So I'll say if doc ID not in docs. So in other words, if the provided document ID is not found as a key inside of this dictionary, then I'm going to raise a value error with a F string of doc with ID, doc ID not found. And then if we get past that check, I'll go ahead and return the actual document. So I'll return docs at doc ID. And that's it. That's all it takes to define a tool. So we've specified the name of the tool, its description, the argument that is expected, its type, and a description for that argument as well. All these different decorators and field types and whatnot are all going to be taken together by this Python MCP SDK, and it's going to generate a JSON schema for us. Now that we've implemented this first tool, I'm going to remove the to-do right there. and then we will implement our other tool the one to edit a document so we're going to repeat the exact same process I'll say mcp.tool I'll give it a name of edit document with a description of edit a document by replacing a string in the document's context or excuse me content with a new string then for the implementation i'll call this function edit doc actually will be consistent call edit document and then we're going to take in a couple of different arguments here first is going to be a document id and then a old string to find and then a new string to replace the old string with so let's write this all out we're going to have a doc id that will be a string with a description of ID of the document that will be edited old string will be a string with a description of the text to replace must match exactly including white space and then our new string the new text to insert in place of the old text. So our document editing here is just a very simple find and replace. That's it. Once again, inside of here, I'm going to make sure that Claude is asking for a document that actually exists. So if doc ID not in docs, raise value error, with a F string of doc with ID not found. And then if we do find the correct document, here's how we will do our edit we'll say docs at doc id is docs at doc id replace old string with the new string and that's it all right so just like that we have put together two tool implementations really really quickly i can't repeat it enough defining tools with this mcp python sdk is a lot easier than writing out the schema definition manually now that we've got both tools put together. I'm going to delete the to-do right there. Okay, so this is a good start. We have put together our MCP server, and we've implemented two tools inside of it.

↑ 目次へ戻る

Building with the Claude API | Lesson 58「The server inspector」(全文ナレッジ)

動画 3分52秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04 本レッスンは「Model Context Protocol」章。別コース『Introduction to MCP』Lesson 06「The server inspector」と同一カリキュラムの別録画(対応ノート: ../introduction-to-mcp/06_server_inspector.md)。用語はそちらと揃えつつ、本文は本動画の逐語・画面に基づく。

日本語ナレッジ(要点整理)

このレッスンのテーマ

前レッスンで MCP サーバーに 2 つのツール(read_doc_contents / edit_document)を実装したが、動くかどうかを確かめる手段がまだ無い。MCP Python SDK にはブラウザ内デバッガ=MCP Inspector が付属しており、実アプリに配線する前にサーバー単体をテストできる。

Inspector の起動手順

  1. Python 仮想環境を有効化(正確なコマンドは README に記載)
  2. mcp dev mcp_server.py を実行(mcp dev <サーバーファイル名>
  3. 「Proxy server listening on port 6277」と表示され、アクセス用の直接アドレス(http://127.0.0.1:6274)が案内される → ブラウザで開く - ポートは2つ:6277=プロキシサーバー、6274=Inspector の UI。ブラウザで開くのは案内された 6274 側

Inspector の使い方(録画時点 v0.10.2)

  • Inspector は活発に開発中。視聴時点では UI が大きく変わっている可能性があるが、機能はほぼ同等のはず(「Tools セクションを探す」が本質)
  • 左ペイン:Transport Type(STDIO)/Command(uv)/Arguments(run --with mcp mcp run mcp_server.py)→ Connect ボタンで自作サーバーを起動
  • 接続後の上部メニュー:Resources / Prompts / Tools / Ping / Sampling / Roots
  • Tools → List Tools で実装済みツールが列挙される(read_doc_contentsedit_document、各説明文つき)
  • ツールをクリックすると右パネルが変わり、引数を入れて Run Tool で手動呼び出しできる → 実アプリに配線せずライブ開発ができる

検証フロー実演(write → read-back 検証)

  1. read_doc_contents:エディタの docs 辞書からドキュメント ID deposition.md をコピー → doc_id に入力 → Run Tool → Success+本文("This deposition covers the testimony of Angela Smith, P.E.")。エディタの文字列と完全一致することを確認
  2. edit_document:doc_id=deposition.md、old_str=This大文字小文字が区別される)、new_str=A report → Run Tool → Success のみ(このツールは編集するだけで内容を返さない)
  3. 検証のため read_doc_contents を同じ doc_id で再実行 → 「A report deposition …」に変わっていることを確認 - 下部 History には initialize → tools/list → tools/call → … と MCP プロトコルの実メッセージが順に積まれる

実務ポイント(このレッスンから持ち帰るもの)

  1. mcp dev <server file> が MCP サーバー開発の基本ループ。クライアント(実アプリ)に繋ぐ前に Inspector で単体テストする。自作サーバー開発では毎回使うことになる。
  2. ブラウザで開くのは 6274(Inspector UI)。6277 はプロキシ側。
  3. 書き込み系ツールは戻り値がないことがあるedit_document は Success のみ)。検証は読み取り系ツールを再実行する write→read-back パターンで行う。
  4. 置換系ツールの old_str は大文字小文字・空白まで完全一致が必要。テスト入力の選び方から意識する。
  5. History パネルは initialize → tools/list → tools/call という JSON-RPC の実順序を見せてくれる——プロトコル理解のライブ教材。
  6. Inspector の UI は変わる前提で、「ボタンの位置」ではなく「接続 → ツール列挙 → 手動呼び出し」という機能で覚える。

本文(時系列・日本語逐語+画面差し込み)

[0:00] MCP サーバーの中に機能をいくつか組み上げましたが、それが動くのかどうかは全く分かりません。ですから、なんらかの方法でこれをテストできると本当に良いですよね。

🖥 [0:00] 画面(VS Code:mcp_server.py ― 前レッスンまでの完成形) タブ mcp_server.py、パンくず mcp_server.py > Pyright > read_document。 ```python @mcp.tool( name="read_doc_contents", description="Read the contents of a document and return it as a string.", ) def read_document( doc_id: str = Field(description="Id of the document to read"), ): if doc_id not in docs: raise ValueError(f"Doc with id {doc_id} not found")

return docs[doc_id]

@mcp.tool( name="edit_document", description="Edit a document by replacing a string in the documents content with a new str…", ) def edit_document( doc_id: str = Field(description="Id of the document that will be edited"), old_str: str = Field( ```

[0:06] 実は、この Python SDK を使うことで、ブラウザ内デバッガへのアクセスが自動的に手に入ります。これで、このサーバーが期待どおりに動いているかを確認できます。

[0:16] 使い方を手短にお見せしましょう。

[0:18] ターミナルに戻って、自分の Python 環境が有効化されていることを確認します。

🖥 [0:20] 画面(ターミナル) ウィンドウタイトル ..s/prod/11/mcp (-zsh)。プロンプトは (mcp) → mcp(venv 名+カレントディレクトリ名)。mcp まで入力した状態。

[0:24] README ドキュメントに、その環境を有効化するために実行する正確なコマンドが詳しく書いてあることを思い出してください。

[0:31] 有効化されていることを確認できたら、mcp dev、そしてサーバーが入っているファイル名を実行します。今回は mcp_server.py です。

[0:42] これを実行すると、ポート 6277 でサーバーがリッスンしていると表示され、実際にアクセスするための直接のアドレスが与えられます。

🖥 [0:52] 画面(ターミナル出力。右からブラウザがスライドイン) text (mcp) → mcp mcp dev mcp_server.py Starting MCP inspector... ⚙️ Proxy server listening on port 6277 🔍 MCP Inspector is up and running at http://127.0.0.1:6274 🚀

[0:50] そのアドレスを、ブラウザの中で開きます。

[0:53] そこに行くと、このような見た目のものが表示されます。これが MCP インスペクターです。

🖥 [1:00] 画面(ブラウザ:MCP Inspector v0.10.2・接続前) ```text MCP Inspector v0.10.2

Transport Type: [STDIO ▾] Command: [uv] Arguments: [run --with mcp mcp run mcp_serv…]

Environment Variables ⚙ Configuration [▶ Connect] ● Disconnected

(中央)Connect to an MCP server to start inspecting (下部)History: No history yet | Server Notifications: No notifications yet (左下)テーマ: Light ```

[0:58] さて、すぐに理解しておいてほしい重要なことがあります。このインスペクターは、活発に開発が進んでいます。ですから、あなたがこのビデオを見ている頃には、いま画面に見えているものは、私がお見せしているものとは非常に大きく違っているかもしれません。とはいえ、おそらく非常に似た機能は持っているはずです。

[1:14] 左側に、あなたの MCP サーバー——つまり、さっき編集したあのファイル——を起動する Connect ボタンが見えます。

[1:19] Connect をクリックすると、すぐに画面の上のほうにいくつかのものが表示されます。まず、上部のメニューバーに注目してほしいのです。Resources、Prompts、Tools、その他が並んでいます。

🖥 [1:30] 画面(Inspector・接続直後) ```text 上部メニュー: Resources | Prompts | [Tools] | Ping | Sampling | Roots 左ペイン: [Restart] [Disconnect] | ● Connected

Tools [List Tools] [Clear] 右パネル: Select a tool Select a tool from the list to view its details and run it

History: 1. initialize ▸ Server Notifications: No notifications yet ```

[1:29] 繰り返しますが、このビデオを見る頃には UI が変わっているかもしれません。もしこのメニューバーが見えなくても、私たちが本当に探しているのは、何らかの Tools セクションだけです。

[1:40] Tools をクリックしたら、List Tools をクリックします。すると、さっき組み上げたツールの名前が表示されます。

[1:50] 1つをクリックすると、右側のパネルが変わります。このパネルを使って自分のツールを手動で呼び出し、期待どおりに動いているかを確認できるのです。

[2:00] これが、実際のアプリケーションに配線することなく、MCP サーバーのライブ開発を行う方法です。

🖥 [2:10] 画面(Inspector・Tools 一覧と read_doc_contents 選択) ```text Tools [List Tools] [Clear] read_doc_contents Read the contents of a document and return it as a string. edit_document Edit a document by replacing a string in the documents content with a new string

右パネル: read_doc_contents Read the contents of a document and return it as a string. doc_id: [Id of the document to read](プレースホルダ) [🡒 Run Tool]

History: 2. tools/list ▸ / 1. initialize ▸ ```

[2:07] read_doc_contents ツールを使うには、ドキュメント ID を入れるだけです。

[2:13] エディタに戻って、この docs 辞書まで上がっていくと、これらのドキュメント ID の1つをコピーできます。

🖥 [2:25] 画面(VS Code:mcp_server.py の docs 辞書) ファイル冒頭は from pydantic import Fieldfrom mcp.server.fastmcp import FastMCPmcp = FastMCP("DocumentMCP", log_level="ERROR")python docs = { "deposition.md": "This deposition covers the testimony of Angela Smith, P.E.", "report.pdf": "The report details the state of a 20m condenser tower.", "financials.docx": "These financials outline the project's budget and expenditures.", "outlook.pdf": "This document presents the projected future performance of the system.", "plan.md": "The plan outlines the steps for the project's implementation.", "spec.txt": "These specifications define the technical requirements for the equipment.", } deposition.md の値の文字列を選択(ハイライト)しながら ID をコピーしている。右端には Inspector のサイドバーが覗く。

[2:21] では deposition.md を取り出します。

[2:26] それを doc ID として入れて、Run Tool をクリックします。すると、Run Tool の Success と、ドキュメントの内容が表示されるはずです。

🖥 [2:35] 画面(Inspector・read_doc_contents 実行結果) ```text read_doc_contents doc_id: [deposition.md] [🡒 Run Tool]

Tool Result: Success "This deposition covers the testimony of Angela Sm…"

History: 3. tools/call ▸ / 2. tools/list ▸ / 1. initialize ▸ ```

[2:34] まさにそれです。検証できます。あそこ(エディタ)に見えているのと、まったく同じ文字列です。

[2:39] これとまったく同じテクニックで、もう1つのツールもテストできます。

[2:43] では、edit_document ツールに切り替えます。

🖥 [2:43] 画面(VS Code:置換対象の確認) docs 辞書の deposition.md 行で先頭の単語 This をダブルクリック選択している(old_str の候補確認)。

[2:47] ここでドキュメント ID と、置き換えたい古い文字列を入れます。「deposition」という単語を置き換えるのはどうでしょう? 実は、もっと打ちやすい単語があります。単に「This」ではどうでしょう? そのほうが少し楽です。

[2:58] というわけで、私の old string は「This」です。これは大文字・小文字が区別されることを覚えておいてください。そして「A report」に置き換えます。

[3:06] ツールを実行すると、Success が返ってきます。

🖥 [3:00〜3:06] 画面(Inspector・edit_document 実行) ```text edit_document Edit a document by replacing a string in the documents content with a new string doc_id: [deposition.md] old_str: [This] new_str: [A report] [🡒 Run Tool]

Tool Result: Success

History: 4. tools/call ▸ / 3. tools/call ▸ / 2. tools/list ▸ / 1. initialize ▸ ```

[3:10] このツールは、実際にはドキュメントの内容を返さないことを思い出してください。ドキュメントを編集するだけです。

[3:16] そこで、編集が正しく行われたことを確認するために、read_doc_contents ツールに戻り、同じドキュメント ID でもう一度実行します。すると「A report deposition」うんぬん、と表示されるはずです。

🖥 [3:30] 画面(Inspector・read_doc_contents 再実行=write→read-back 検証) ```text read_doc_contents doc_id: [deposition.md] [🡒 Run Tool]

Tool Result: Success "A report deposition covers the testimony of Angela Smith, P.E."

History: 5. tools/call ▸ … ```

[3:30] はい。というわけで、見てのとおり、この MCP インスペクターを使えば、実装中の MCP サーバーを、実際のアプリケーションに配線することなく、とても簡単にデバッグできます。

[3:41] 自分自身の MCP サーバーを作り始めると、このインスペクターツールをかなり使うことになると思います。このモジュールの中でも、私たちのサーバー開発が順調に進んでいることを確認するために、もう少し使うことになるでしょう。

英語逐語(ASR全文)

We have put together some functionality inside of our MCP server, but we have no idea if it works. So it'd be really great if we could test this out somehow. It turns out that by using this Python SDK, we automatically get access to a in-browser debugger. So we can make sure that this server is working as expected. Let me show you how to use it really quickly. Back inside my terminal, I'm going to make sure that I have my Python environment activated. Remember, the readme document goes into detail on the exact command to run to make sure that you have activated that environment. Once you are sure that it is activated, we'll run mcp, dev, and then the name of the file that contains our server. In this case, it is mcpserver.py. Once I run that, I'll then be told that I have a server listening on port 6277, and I'll be given a direct address to actually access it. I'm going to open up that address inside my browser. And once you go there, you'll see something that looks like this. This is the MCP inspector. Now, right away, there's something important I want you to understand here. This inspector is in active development. So by the time you are watching this video, what you see on the screen right now might be very, very different than what I am showing. Nonetheless, it's probably still going to have some very similar functionality. On the left-hand side, you'll see a connect button that is going to start up your MCP server. so that file that we just edited. I'm going to click on connect and then right away we'll see a couple of different things on the screen up here. I first want you to notice the top menu bar up here. It lists out resources, prompts, tools, and some other stuff. Again the UI might change by the time you watch this video so if you do not see this menu bar up here all we are really looking for here is some tools section. Once I click on tools I will click on list tools and And we'll see the name of the tools that we just put together. If I click on one, the right hand panel is then going to change and I can use this panel over here to manually invoke one of my tools to make sure that it is working as expected. So this is how we can do some live development on our MCP server without actually having to wire it up to a real application. In order to use the read.contents tool, all we have to do is put in a document ID. If I go back over to my editor and go up to the docs dictionary right here, I can copy one of these document IDs. So I will take out deposition.md. I will put it in as the doc ID and then click on run tool. I should then see run tool of success with the contents of the document. That is it right there. I can verify it. It's the same exact string as what I see right there. We can use this same exact technique to test out the other tool as well. So I will change over to the edit document tool. Now I'll put in my document ID, my old string that I want to replace. How about we replace the word deposition? Actually, I have an easier word to type out. How about just this? That'll be a little bit easier. So my old string is this. Remember that is going to be capital sensitive and I'm going to replace it with a report. And if I run the tool, I'll then be given a success. Remember that tool does not actually return the document's contents. It just edits the document. So now to verify the edit was done correctly, I can go back over to the redock contents tool, run that one again with the same document ID, and I should see a report deposition and then blah, blah, blah. All right. So as you can see, this MCP inspector allows us to very easily debug a MCP server that we are implementing without actually having to wire the server up to an actual application. As you start building your own NCP servers, I expect you'll be using this inspector tool quite a bit, and we'll probably use it a little bit more inside of this module just to make sure that our server development is going along pretty well.

(注:ASR の「mcpserver.py」=画面上の実体は mcp_server.py、「read.contents」「redock contents」=read_doc_contents、「NCP servers」=MCP servers、「capital sensitive」=case sensitive の意。)

↑ 目次へ戻る

Building with the Claude API | Lesson 59「Implementing a client」(全文ナレッジ)

動画 7分26秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04 本レッスンは「Model Context Protocol」章。別コース『Introduction to MCP』Lesson 07「Implementing a client」と同一カリキュラムの別録画(対応ノート: ../introduction-to-mcp/07_implementing_a_client.md)。用語はそちらと揃えつつ、本文は本動画の逐語・画面に基づく。

日本語ナレッジ(要点整理)

このレッスンのテーマ

サーバー側が一段落したので、MCP クライアントの実装に移る。対象はプロジェクトルートの mcp_client.py通常のプロジェクトではクライアント側かサーバー側のどちらか一方だけを実装するが、この教材プロジェクトでは学習のため両方を実装する(スライド「Important Note!」)。

MCPClient クラスの構造(なぜコードが多いのか)

  • mcp_client.pyMCPClient クラス 1 つで構成される
  • このクラスは ClientSession(クライアントセッション)をラップする
  • ClientSession = MCP サーバーへの実際の接続。MCP Python SDK の一部
  • スライドの整理:MCP Client=「セッションを使いやすくするために自作しているカスタムクラス」/Client Session=「サーバーへの実際の接続」
  • セッションはリソースのクリーンアップ(プログラム終了時・サーバー不要時の後片付け)が必要で、そのための定型コードが connect / cleanup / __aenter__ / __aexit__ に書き込み済み。セッションを生で使わず、リソース管理を面倒見る大きめのクラスでラップするのが一般的な実践
  • 実装詳細(コードより):AsyncExitStackstdio_client(StdioServerParameters(command, args, env))ClientSession を積み、await session.initialize()cleanupexit_stack.aclose()

クライアントは何のためにあるか

  • 以前見たフロー図の再掲:Our Code はある時点で「Claude に渡すツール一覧」が必要になり、その後「Claude が要求したツールの実行」が必要になる。MCP サーバーに手を伸ばすその 2 箇所で MCP クライアントを使う
  • シーケンス図(User / Our Code / MCP Client / MCP Server / Claude / Github):
  • ListToolsRequestListToolsResult(= Claude に送るツール一覧の取得)
  • Query + Tools → Claude → ToolUse
  • CallToolRequest →(サーバーが Github へリクエスト)→ CallToolResult → toolResult を Claude へ → 最終応答
  • つまりクライアントは「サーバーが実装している機能を、コードベースの残りの部分へ公開する」存在。core/ ディレクトリ内の既製コードが list_tools / call_tool / list_prompts / get_prompt などを呼ぶ

今回実装する 2 関数(どちらも1〜2行)

async def list_tools(self) -> list[types.Tool]:
    result = await self.session().list_tools()
    return result.tools

async def call_tool(
    self, tool_name: str, tool_input
) -> types.CallToolResult | None:
    return await self.session().call_tool(tool_name, tool_input)
  • self.session()=サーバーへの接続を取得 → SDK 組み込み関数を呼ぶだけ
  • call_tool にはツール名と、Claude が指定した入力引数をそのまま渡す

テスト(2段階)

  1. ファイル末尾のテストハーネスmcp_client.py を直接実行すると自作サーバーへ接続して任意のコマンドを試せる。async with MCPClient(command="uv", args=["run", "mcp_server.py"]) as _client: の with ブロック内に result = await _client.list_tools()print(result) を追加し、uv run mcp_client.py を実行(UV を使わない場合は python mcp_client.py。command/args を変える旨のコメントもファイル内にある) - 出力=ツール定義のリストTool(name='read_doc_contents', description=..., inputSchema={...})Tool(name='edit_document', ...)。各ツールに description と inputSchema がある——これがいずれ Claude へ渡るツール定義
  2. CLI 全体テストlist_tools / call_tool を呼ぶ側のコードは既に実装済みなので、uv run main.py で CLI を起動し「What is the contents of the report.pdf document?」と質問(report.pdf を正確に入力)→ リクエストと共にツール一覧が送られ、Claude が read ツールを使うと判断 → 「20m の凝縮塔(condenser tower)に関するレポート」との回答が返る

実務ポイント(このレッスンから持ち帰るもの)

  1. ClientSession は生で使わずラップする。接続(connect)と後片付け(cleanup / async context manager)を面倒見るカスタムクラスに包むのが常套。AsyncExitStack に stdio_client → ClientSession の順で積み、閉じるときは aclose() 一発。
  2. クライアントの役割は 2 点に集約:①サーバーのツール定義一覧を取得して Claude に渡す(list_tools)、② Claude の ToolUse 要求をサーバーで実行して結果を返す(call_tool)。SDK があるので実装はそれぞれ実質 1〜2 行。
  3. 通常のプロジェクトではクライアントかサーバーのどちらか一方だけを書く。両方書くのは学習用の構成——自分のプロダクトでどちら側に立つのかを最初に見極める。
  4. list_tools() の戻りは result.toolscall_tool()CallToolResult をそのまま返す——戻り値の「殻」を剥がす層をクライアントに置くと呼び出し側が綺麗になる。
  5. テストハーネスをクライアントファイル自体に持たせるif __name__ == "__main__":)と、CLI 全体を動かさずにサーバー接続だけを単体確認できる。Windows では asyncio.WindowsProactorEventLoopPolicy() の切替も定型。
  6. ツール定義(name / description / inputSchema)がそのまま Claude へ渡る素材——Inspector や print で中身を直接見ておくと、Claude 側のツール選択の挙動が理解しやすい。

本文(時系列・日本語逐語+画面差し込み)

[0:00] さて、サーバーが良い状態になったので、少しギアを切り替えて、MCP クライアントの作業を始めます。クライアントは、プロジェクトのルートディレクトリにある MCP client、つまり mcp_client.py ファイルの中にあります。

🖥 [0:00] 画面(スライド「Important Note!」) 見出し:Important Note! Normally a project will implement either an MCP client or an MCP server. Our project will implement both just so we understand how they work (通常のプロジェクトは MCP クライアントか MCP サーバーのどちらか一方を実装する。このプロジェクトでは仕組みの理解のために両方を実装する) 図:灰色ボックス「Our Server」の中に「MCP Client」、その右に「MCP Server」。双方向矢印で接続。

[0:10] さて、このファイルの中で何かをする前に、ちょっとしたリマインダーです。以前お話ししたことを思い出してください。通常の典型的なプロジェクトでは、私たちはクライアントを利用するか、サーバーを実装するかの、どちらか一方です。両方をやっているのは、私たちが取り組んでいるこの特定のプロジェクトだけです。パズルの両面を見られるようにするためですね。

[0:28] さて、このファイルの中の MCP クライアント自体は、単一のクラスで構成されています。中にはたくさんのコードがあり、さっきサーバーの中に書いたコードほど綺麗には見えないことに気づくでしょう。そこで、このファイルの中で何が起きているのか、なぜこんなに大きいのかを正確に説明します。

🖥 [0:14] 画面(VS Code:mcp_client.py 冒頭) ```python import sys import asyncio from typing import Optional, Any from contextlib import AsyncExitStack from mcp import ClientSession, StdioServerParameters, types from mcp.client.stdio import stdio_client

class MCPClient: def init( self, command: str, args: list[str], env: Optional[dict] = None, ): self._command = command self._args = args self._env = env self._session: Optional[ClientSession] = None self._exit_stack: AsyncExitStack = AsyncExitStack() ```

[0:44] それでは。このファイルの中で、私たちは MCP client クラスを作っています。このクラスは、client session(クライアントセッション)と呼ばれるものをラップします。クライアントセッションとは、MCP サーバーへの実際の接続です。このクライアントセッションは、MCP Python SDK の一部です。

🖥 [0:44] 画面(スライド「mcp_client.py」) 枠「mcp_client.py」の中に2つのボックス: - MCP Client — 下の注釈:Custom class we are authoring to make using the session easier(セッションを使いやすくするために自作しているカスタムクラス) - Client Session — 下の注釈:Actual connection to the server(サーバーへの実際の接続) Client Session から右の MCP Server ボックスへ線が伸びる。

[1:00] 繰り返すと、このセッションが、外側のサーバーへのこの接続を与えてくれるものです。セッション自体は、少しばかりリソースのクリーンアップを必要とします。言い換えると、プログラムを終了するときや、もうサーバーが必要ないと判断したときには、ちょっとした後片付けのプロセスを通る必要があるのです。

[1:14] そして、そのクリーンアップコードの多くは、私がすでに MCP client クラスの中に書き出してあります。それこそが、このクラスがそもそも存在する理由です。その後片付けを少し楽にするためだけに、です。

[1:24] そのクリーンアップコードの一部は、connect 関数の中と、少し下の cleanup、async enter、async exit 関数にも見えます。つまり、このクライアントセッションを直接使うのではなく、こうしたリソース周りの管理を代わりにやってくれる、より大きなクラスの中にラップするのが、ごく一般的な実践なのです。

🖥 [1:24] 画面(VS Code:mcp_client.py の connect 関数) python async def connect(self): server_params = StdioServerParameters( command=self._command, args=self._args, env=self._env, ) stdio_transport = await self._exit_stack.enter_async_context( stdio_client(server_params) ) _stdio, _write = stdio_transport self._session = await self._exit_stack.enter_async_context( ClientSession(_stdio, _write) ) await self._session.initialize() さらに下に cleanupawait self._exit_stack.aclose() / self._session = None)と __aenter__ / __aexit__ が並ぶ。

[1:46] 次にはっきりさせたいのは、そもそもなぜこのクライアントが存在するのか、です。言い換えれば、このクライアントは実際のところ私たちのために何をしてくれているのか? ええと、少し前に見たこのフローを思い出してください。ここに私たちのコードがありました。そしてある時点で、Claude へ送るためのツールの一覧が必要になりました。さらにその後で、Claude からリクエストされたツールを実行することも必要になりました。

🖥 [1:54] 画面(スライド:ツール利用の全体シーケンス図) レーン:User | Our Code | MCP Client | MCP Server | Claude | Github 1. User → Our Code:What repositories do I have? 2. Our Code → MCP Client:I need a list of tools to send to ClaudeListToolsRequest → MCP Server 3. ← ListToolsResultHere are the tools) 4. Our Code → Claude:Query + Tools → Claude から ToolUse が返る 5. Our Code → MCP Client:Please run this tool with these argsCallToolRequest → MCP Server →(Request to Github)→ Github 6. Github →(Response)→ MCP Server → CallToolResultHere's the result of running the tool) 7. Our Code → Claude:toolResult → Claude:Your repositories are… → User へ最終回答

[2:06] MCP サーバーに手を伸ばして、このツール一覧を取得したり、ツールを実行したりするために——そこで私たちは MCP クライアントを使っているのです。ですから、このクライアントは、サーバーに属する機能を、コードベースの残りの部分に公開しているのだとイメージできます。

[2:22] このコードベースの中、このプロジェクトの中、具体的には core ディレクトリの中には、私が組んでおいた、このクラスを利用するコードがすでにたくさん入っています。つまり、ここに見える様々な関数——list tools、call tool、list prompts、get prompt など——を呼び出す別のコードがあるということです。

🖥 [2:23] 画面(スライド:同じシーケンス図に赤い注釈2つ) - 上段(ListToolsRequest/Result〜Query+Tools を赤枠で囲み):Our CLI code is using the client to get a list of tools to pass to Claude(CLI コードは、Claude に渡すツール一覧を得るためにクライアントを使う) - 下段(CallToolRequest/Result〜toolResult を赤枠で囲み):Our CLI code is using the client to call a tool(CLI コードは、ツールを呼び出すためにクライアントを使う)

[2:37] 今回このビデオでは、list tools と call tool の 2 つの関数の実装に集中します。さっき見た図のとおり、この 2 つの関数はコードベースの別々の場所で使われ、Claude へ提供するツール一覧を取得し、Claude がツール呼び出しを要求したときに実際にツールを呼び出します。

[2:58] この 2 つの関数の実装は、本当にシンプルで単純明快です。やり方をお見せしましょう。まず list tools から始めます。中の to do を取り除いて、こう置き換えます:result = await self.session()——これを関数のように呼び出します——.list_tools()。そして return result.tools。以上です。

[3:16] つまりこれは、私たちのセッション——MCP サーバーへの実際の接続——へアクセスし、そのサーバーが実装しているすべてのツールの定義、つまり一覧を取得する組み込み関数を呼び出します。result を受け取って、tools を返すだけ。それだけです。

[3:32] 次に、そのすぐ下で call tool も、とてもよく似たやり方で実装できます。return await self.session().call_tool(tool_name, tool_input) です。もう一度、セッション=サーバーへの接続にアクセスして、特定のツールの呼び出しを試みます。ツールの名前が引数として渡され、それと一緒に、Claude から提供された入力パラメータ(入力引数)も渡されます。

🖥 [3:30] 画面(VS Code:mcp_client.py ― 2関数の実装中) サイドバー:core / .env / .gitignore / main.py / mcp_client.py(変更 2 件)/ mcp_server.py / README.md ```python async def list_tools(self) -> list[types.Tool]: result = await self.session().list_tools() return result.tools

async def call_tool(
    self, tool_name: str, tool_input: dict
) -> types.CallToolResult | None:
    return await self.session().call_    ← 入力途中

async def list_prompts(self) -> list[types.Prompt]:
    # TODO: Return a list of prompts defined by the MCP server
    return []

async def get_prompt(self, prompt_name, args: dict[str, str]):
    # TODO: Get a particular prompt defined by the MCP server
    return []

``` (list_prompts / get_prompt はまだ TODO のまま)

[4:02] さて、この時点で、この 2 つの関数を手早くテストしたいと思います。そのために、このファイルの一番下へ行きます。そこに、私たちのためにとても小さなテストハーネスを用意しておきました。ここです。このテスト用ブロックに気づくでしょう。これで、この mcp client.py ファイルを直接実行できます。

[4:19] そうすると、私たちの MCP サーバーへの接続が形成され、それに対していくつかコマンドを実行して、何が返ってくるかを見られます。あなたのバージョンのコードでは、UV を使っていない場合に、ここの command と args を変更することについてのコメントが入っています。UV を使っていない人は、必ずそのコメントを見ておいてください。

🖥 [4:00] 画面(VS Code:ファイル末尾のテストハーネス) ```python

For testing

async def main(): async with MCPClient( command="uv", args=["run", "mcp_server.py"], ) as _client: pass ← このブロックがハイライトされている

if name == "main": if sys.platform == "win32": asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) asyncio.run(main()) `` (直前の行にはaexitawait self.cleanup()` が見える)

[4:37] この with ブロックの中に、テストコードを少し追加します。result = await _client.list_tools() と書き、受け取った結果をそのまま print します。

[4:51] これで、私たちの MCP サーバーのコピーが起動し、それが定義しているすべてのツールの一覧の取得を試み、結果を print するはずです。これをテストするために、ターミナルに戻って uv run mcp_client.py を実行します。いつものように、UV を使っていない場合は python mcp_client.py です。

[5:17] はい。実行します。そして、これが私たちのツール定義のリストです。

🖥 [5:18] 画面(ターミナル:uv run mcp_client.py の出力)
```text
(mcp) → mcp uv run mcp_client.py
[Tool(name='read_doc_contents', description='Read the contents of a document
and return it as a string.', inputSchema={'properties': {'doc_id': {'descri
ption': 'Id of the document to read', 'title': 'Doc Id', 'type': 'string'}},
'required': ['doc_id'], 'title': 'read_documentArguments', 'type': 'object'
}), Tool(name='edit_document', description='Edit a document by replacing a s
tring in the documents content with a new string', inputSchema={'properties'
{'doc_id': {'description': 'Id of the document that will be edited', 'titl e': 'Doc Id', 'type': 'string'}, 'old_str': {'description': 'The text to rep lace. Must match exactly, including whitespace', 'title': 'Old Str', 'type': 'string'}, 'new_str': {'description': 'The new text to insert in place of t he old text', 'title': 'New Str', 'type': 'string'}}, 'required': ['doc_id', 'old_str', 'new_str'], 'title': 'edit_documentArguments', 'type': 'object'} )] ```

[5:20] この中に、少し前に組み上げた read doc contents ツールと、edit document ツールがあるのが見えます。それぞれに description と input schema もあります。これが私たちのツール定義で、最終的に Claude へ渡されるものです。

[5:37] さて、先へ進む前に、もう1つテストしたいことがあります。思い出してください。私たちはたった今、いくつかのツールを列挙して Claude へ渡すための関数と、MCP サーバーが実装しているツールを呼び出して、その結果を Claude へ渡すための関数を実装しました。

[5:53] list tools と call tool を呼び出すコードは、このプロジェクトの別の場所に、私がすでに実装してあります。ですから、この機能を追加した今——これらのツールの定義と、特定のツールを呼び出す能力を手に入れた今——CLI をもう一度実行して、Claude にこれらのツールを使わせてみることができます。言い換えれば、Claude に特定のドキュメントの中身を調べさせたり、ドキュメントを編集させたりできるのです。

[6:17] では、そのやり方をお見せします。私の MCP サーバーの中に、ID が report.pdf のドキュメントがあって、「20 メートルの凝縮塔(condenser tower)」のようなテキストが入っていることを、リマインドしておきます。

🖥 [6:22] 画面(VS Code:mcp_server.py の docs 辞書を再確認) タブは mcp_client.py と mcp_server.py の2枚。 python docs = { "deposition.md": "This deposition covers the testimony of Angela Smith, P.E.", "report.pdf": "The report details the state of a 20m condenser tower.", "financials.docx": "These financials outline the project's budget and expenditures.", "outlook.pdf": "This document presents the projected future performance of the system.", "plan.md": "The plan outlines the steps for the project's implementation.", "spec.txt": "These specifications define the technical requirements for the equipment.", }

[6:31] ターミナルに戻って、uv run main.py でプロジェクトを実行します。

[6:39] そして Claude に、report.pdf ドキュメントの中身は何かと尋ねます。ここでは正確に report.pdf と入力するようにしてください。

[6:50] これを実行すると、リクエストと一緒に、私たちのツール一覧を送っています。Claude は read document ツールを使うことを決め、ドキュメントの中身を取得します。そして、はい、Claude はそのドキュメントの中身を取得できました。レポートは 20 メートルの凝縮塔に関する何かだ、と教えてくれています。

🖥 [7:08] 画面(ターミナル:CLI でのツール利用テスト) ```text (mcp) → mcp uv run main.py

What is the contents of the report.pdf document? I'll check the contents of the report.pdf document for you.

Response: The report.pdf document contains information about the state of a 20m conden ser tower.

``` (Response の本文がハイライトされている)

[7:08] はい。というわけで、この時点で私たちはクライアント周りの機能を追加できました。思い出してください。クライアントとは、MCP サーバーの中に実装されている機能へアクセスできるようにしてくれるものです。現時点で、サーバーが作成したツールを列挙することと、サーバーが実装しているツールを実行することができるようになりました。

英語逐語(ASR全文)

Now that our server is in a good place, we're going to shift gears a little bit and start working on our MCP client. The client can be found inside the MCP client, the .py file inside the root project directory. Now, before we do anything inside this file, I just want to give you a very quick reminder here. Remember what I told you about earlier. Usually in a typical project, we are either making use of a client or we are implementing a server. It's just in this one particular project that we are working on that we are doing both. Again, just so you can see both sides of the puzzle. Now the MCP client itself inside of this file is consisting of a single class. You'll notice there's a lot of code inside of here, and it doesn't look quite as pretty as some of the code we just wrote out inside of the server. So let me tell you exactly what's going on inside this file and exactly why it is so large. Okay, so inside this file, we are making the MCP client class. This class is going to wrap up something called a client session. The client session is the actual connection to our MCP server. This client session is a part of the MCP Python SDK. So again, this session is what gives us this connection to the outside server. The session itself requires a little bit of resource cleanup. In other words, whenever we close down our program or decide that we don't need the server anymore, we have to go through a little bit of a cleanup process. And I have already written out a lot of that cleanup code inside of the MCP client class. So that's really why this class exists at all. Just to make that cleanup a little bit easier. You can see some of that cleanup code inside the connect function and down a little bit lower at the cleanup, async enter, and async exit functions as well. So it's very common practice to not just make use of this client session directly. Instead, very common to wrap it up inside of a larger class that's going to manage some of this different resource stuff for you. The next thing I want to clarify is why this client exists at all. So in other words, what is the client really doing for us here? Well, remember this flow that we looked at a little bit ago. So we had our code right here. And at certain points in time, we needed say a list of tools to send off to Claude. And then later on after that, we also needed to run a tool that was requested by Claude. In order to reach out to our MCP server and get this list of tools or to run a tool, that's where we are making use of the MCP client. So we can imagine that this client is exposing some functionality that belongs to the server to the rest of our code base. So inside of our code base, inside this project, specifically inside of the core directory, there is a lot of code already inside there that I put together that is making use of this class. So there's some other code that's going to call some of the different functions you see inside of here, like list tools, call tool, list prompts, get prompt, and so on. For right now, in this video, we're going to focus on implementing two functions, list tools and call tool. So as you just saw in the diagram we looked at a moment ago, these two functions are going to be used in different parts of our code base to get a list of tools to provide off to Claude and eventually call a tool whenever Claude requests to call a tool. Implementing these two functions is going to be really simple and straightforward. So let me show you how we're going to do it. We'll first begin with list tools. I'm going to remove the to do inside there and replace it with result is await self.session. I'm going to call that like a function. List underscore tools. And then I will return result.tools. And that's it. So this is going to get access to our session, which is our actual connection to the MCP server. It's going to call a built-in function to get a definition or a list of all the different tools that are implemented by that server. I'm going to get back result and then just return the tools and that's it. Then we can implement call tool right here in a very similar fashion. So this will be return await self dot session, call tool, tool name, and tool input. Once again, getting access to the session, that is our connection to the server, and I'm going to attempt to call a very specific tool, the name of the tool will be passed in along with the input parameters or input arguments to it that were provided by Claude. Now at this point in time, I would like to test out these two functions really quickly. To do so, we're going to go down to the bottom of this file where I put together a very small testing harness for us. So down here, you'll notice I put together this testing block. So we can run this mcp client.py file directly. And if we do so, we're going to form a connection to our mcp server, and then we can just run some commands against it and just see what we get back. Notice that in your version of the code, there's a comment in there about changing the command and args right here in case you are not making use of UV. So if you're not using UV, make sure you take a look at that comment. Inside of this with block, I'm going to add in a little bit of testing code. So I'll say result is await underscore client list tools. And then I'm going to just print out the result that we get back. So this should start up a copy of our MCP server, then attempt to get a list of all the different tools that are defined by it. And then just print out the result to test this out. I will flip back over to my terminal and do a UV run MCP underscore client.py. And as usual, if you are not making use of UV, you'll just do a Python MCP client.py. Okay. So I'll run that. And there is our list of tool definitions. So I can see inside of here that I have the read doc contents tool, which we put together a little bit ago and our edit document tool as well. Each one has a description and a input schema as well. So this is our tool definition, which will eventually be passed off to Claude. Now, before we move on, there's one other thing I want to test. Remember, we just implemented the function that's going to allow us to list out some tools and pass them off to Claude and the function that's going to allow us to call a tool that is implemented by the MCP server and then pass the result off to Claude as well. I have already implemented the code that is going to call list tools and call tool for us somewhere else inside this project. So now that we have added in this functionality, now that we have defined these tools and the ability to call a particular tool, we can now run our CLI again and attempt to get Claude to make use of these tools. In other words, we can ask Claude to inspect the contents of some particular document and even edit a document. So let me show you how we do that. Inside of my MCP server, I just want to give you a reminder that there is a document with a ID of report.pdf and it has some text here of something like a 20 meter condenser tower. I'm going to go back over to my terminal and I'm going to run my project with a uvrun main.py. And then I'm going to ask Claude what is the contents of the report.pdf document. And make sure you put in exactly report.pdf here. And when we run this, we're sending off along with the request, our list of tools. Claude is going to decide to use the read document tool and it's going to get the contents of the document. And then we will see that, yes, Claude was able to get the contents of that document. We are told that the report is something about a 20 meter condenser tower. All right, so at this point, we have added in some functionality around our client. Remember, the client is what allows us to access some functionality that is implemented inside of the MCP server. At this point in time, we have been able to list out some tools that are created by the server and execute a tool that has been implemented by the server.

(注:ASR の「mcp client.py」「MCP underscore client.py」=mcp_client.py、「uvrun」=uv run、「async enter / async exit」=__aenter__ / __aexit__ の意。)

↑ 目次へ戻る

Building with the Claude API | Lesson 60「Defining resources」(全文ナレッジ)

動画 9分45秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04 本レッスンは「Model Context Protocol」章。別コース『Introduction to MCP』Lesson 08「Defining resources」と同一カリキュラムの別録画(対応ノート: ../introduction-to-mcp/08_defining_resources.md)。用語はそちらと揃えつつ、本文は本動画の逐語・画面に基づく。

日本語ナレッジ(要点整理)

このレッスンのテーマ

MCP サーバーの次の主要機能 リソース(resources) を学ぶ。題材として CLI に @メンション機能を追加する:ユーザーが @ドキュメント名 と打つと、①@ 入力時にメンション可能なドキュメント一覧をオートコンプリートで表示し、②メンション入りメッセージ送信時にそのドキュメントの中身を自動取得してプロンプトへ注入する。

なぜツールではなくリソースか

  • 例:「What's in the @report.pdf file?」→ 送信するプロンプトは「Answer the users query: <query>…</query> + The user may have referenced a document. Here is the content of the document: <document id="report.pdf">…</document>」という形に組み立てる
  • Claude にツールで中身を調べさせるのに頼らず、ユーザーが先回りしてファイルをメンションし、こちらがコンテキストを事前注入するという設計

リソースの基本

  • リソース=MCP サーバーからクライアントへデータを公開する仕組み。スライドの整理:
  • 典型的な HTTP サーバーの GET リクエストハンドラに似ている
  • 読み取り操作 1 つにつきリソース 1 つを定義するのが通例
  • 返せるデータは文字列・JSON・バイナリなど何でも。何を返すかのヒントとして mime_type を設定する
  • 2 種類ある:direct(直接)と templated(テンプレート)
  • 今回の要件は「ドキュメント一覧の取得」と「単一ドキュメントの中身の読み取り」→ リソースを 2 つ作る
  • アクセスフロー:ユーザーが @ を打つ → Our Code → MCP クライアント → ReadResourceRequest(中に URI=読みたいリソースのアドレス、例 docs://documents)→ MCP サーバーが URI に対応する関数を実行 → ReadResourceResult(doc 名のリスト)→ オートコンプリートに表示
  • URI はリソース定義時に決める(ルートハンドラのパスに相当)

direct(静的)リソースと templated リソース

Direct Resource Templated Resource
URI パラメータを含まない固定 URI(例 docs://documents URI に 1 個以上のパラメータ(例 docs://documents/{doc_id}
挙動 常に同じものを返す {doc_id} 部分を Python SDK が自動でパースして関数のキーワード引数として渡す(引数名は {} 内の文字列と同名)
使い所 一覧など固定の読み取り 要求内容に選択・多様性・カスタマイズを持たせたいとき

実装(mcp_server.py)

@mcp.resource("docs://documents", mime_type="application/json")
def list_docs() -> list[str]:
    return list(docs.keys())

@mcp.resource("docs://documents/{doc_id}", mime_type="text/plain")
def fetch_doc(doc_id: str) -> str:
    if doc_id not in docs:
        raise ValueError(f"Doc with id {doc_id} not found")
    return docs[doc_id]
  • mime_type="application/json" は「構造化 JSON を含む文字列を返す」というクライアントへのヒント。デシリアライズはクライアントの責務
  • list_docs は明示的に JSON 文字列を作らない——返した値は SDK が自動で文字列化してくれる
  • 2 つ目はワイルドカード {doc_id} を入れたのでテンプレートリソース。今回は変化をつけて素の本文(プレーンテキスト)を返す
  • 実務なら read a document は ID・content・著者名などを含むドキュメントレコード(辞書)ごと返すのが普通。今回はプレーンテキストの返し方を見せるための簡略化
  • パラメータを増やしたければ(例 doc_type)URI に足せば、そのままキーワード引数が増える
  • 存在チェック → ValueError を投げるバリデーションも入れる

Inspector での検証

  • uv run mcp dev mcp_server.py → Web サーバーはポート 6277…もといデフォルトは 6274(言い直しに注意:6277=プロキシ、6274=UI)→ ブラウザで開いて Connect
  • Resources タブ
  • List Resources静的(direct)リソースだけを列挙 → docs://documents のみ表示
  • List Templates(Resource Templates)を別に列挙 → fetch_doc が表示
  • docs://documents を実行 → 返ってくる実際のメッセージ構造が見える:contents[0]uri / mimeType: "application/json" / text プロパティ(データが JSON 文字列としてシリアライズされて入る:"[\"deposition.md\", \"report.pdf\", …]")→ CLI 側でこの text をデシリアライズして使える文字列リストに戻す必要がある
  • fetch_doc を実行:doc_id に report.pdf を入力 → Read Resource → 本文が返り、今度は text/plain(=JSON としてパースするなというヒント)
  • History には resources/listresources/templates/listresources/read が積まれる(テンプレート入力時には completion/complete も飛ぶ)

実務ポイント(このレッスンから持ち帰るもの)

  1. 「ユーザーが先回りできる読み取りはリソース、Claude に判断させる操作はツール」という切り分け。@メンションのような UI 主導のコンテキスト注入は、ツール往復(レイテンシ+トークン)を節約できる。
  2. 読み取り操作 1 つ=リソース 1 つ、URI は GET ルートのパス設計と同じ感覚で決める(docs://documentsdocs://documents/{doc_id} の対)。
  3. mime_type は「クライアントへのパース指示」application/json なら受け手が json.loads する契約、text/plain ならそのまま使う契約。サーバー側の返し方とペアで決める。
  4. SDK が戻り値を自動で文字列化するので、サーバー側で json.dumps は不要。逆にクライアント側のデシリアライズは自前——この非対称を覚えておく。
  5. テンプレートリソースの {param}そのままの名前で関数のキーワード引数になる。パラメータ追加=URI に {} を足すだけ。存在しない ID には ValueError などで明示的に失敗させる。
  6. Inspector では List Resources(direct のみ)と List Templates が別枠。「テンプレートが一覧に出ない!」と焦らない。
  7. 実務の読み取り系リソースは本文だけでなくレコード(ID・content・author 等の辞書)を返す設計が基本。教材の text/plain は例示目的。

本文(時系列・日本語逐語+画面差し込み)

[0:00] このビデオでは、MCP サーバーの次の主要機能であるリソースに進みます。リソースを理解してもらうために、私たちのプロジェクトにもう1つ機能を実装していきます。

[0:10] 追加するものはこれです。ユーザーがアットマーク(@)に続けてドキュメント名を打つことで、ドキュメントを「メンション」できるようにしたいのです。そうしたら、そのドキュメントの中身を自動的に取得して、Claude へ送るプロンプトに挿入したい。

🖥 [0:09] 画面(スライド「Next Feature」) - Users can "mention" a document by writing out "@doc_name" - Typing "@" should show a list of all the available documents - When a document is mentioned, its contents should be automatically injected into the prompt 右側に CLI のモック: ```text

Can you please summarize the contents of @▌ deposition.md Resource design.md Resource financials.md Resource outlook.md Resource plan.md Resource spec.md Resource ```

[0:22] つまり全体として、この機能には 2 つの側面があることになります。ユーザーがメッセージの中で @ を打ったら、メンションできるすべてのドキュメントの一覧を、小さなオートコンプリートウィンドウの中に自動表示します。次に、ユーザーがメンション入りのメッセージを送信したら、そのドキュメントの中身を自動的に取得して、Claude へ送るプロンプトに挿入します。

[0:43] 例えば、ユーザーが「what's in the @report.pdf file」のように言ったら、私はこんなプロンプトを組み立てて Claude に送りたいのです。中にはユーザーからのクエリが入ります。さらに Claude には、ユーザーが何らかのドキュメントを参照した可能性があること、そして「これがそのドキュメントの中身です」ということも伝えます。

🖥 [0:47] 画面(スライド:プロンプト組み立ての図) 上:ターミナルのモック > What's in the @report.pdf file?▌ 下:Our Code →(黒いプロンプトボックス)→ Claude ```text Answer the users query: What's in the @report.pdf file?

The user may have referenced a document. Here is the content of the document: ...condition of a 20m condenser tower... ```

[1:01] ここでのアプローチ、というか狙いは、report.pdf ファイルの中に何があるかを突き止めるために、Claude が何かのツールを使いに行くことに頼らなくて済むようにする、ということです。代わりに、ユーザーが先回りしてファイルをメンションでき、私たちが自動的に、事前にコンテキストを挿入します。

[1:17] ここで1つはっきりさせたいのは、私たちは実質的に 2 つの別々の機能の話をしている、ということです。1 つ目の機能:ユーザーが @ を打ち込んだときには、ユーザーがメンションし得るすべてのドキュメントの一覧を MCP サーバーからもらう必要があります。そして 2 つ目の側面:ユーザーがメンションを含むメッセージを送信したときには、単一ドキュメントの中身を MCP サーバーからもらう必要があります。

🖥 [1:20] 画面(スライド:2つの機能の対比) 上段:When a user types an '@'… → CLI モック(オートコンプリート表示)→ …We need the MCP Server to give us a list of documents 下段:When a message contains a document mention… → プロンプトボックス(<document id="report.pdf">…condition of a 20m condenser tower…</document>)→ …We need the MCP Server to give us the contents of a single document

[1:41] この情報を MCP サーバーから取り出すために、私たちはリソースを利用します。リソースは、MCP サーバーが一定量のデータをクライアントへ公開することを可能にします。通常、個別の読み取り操作 1 つごとに 1 つのリソースを定義します。

[1:52] 私たちの例では、ドキュメントの一覧を取得することと、単一ドキュメントの中身を読むことが必要です。ですから、おそらく 2 つの別々のリソースを作ることになるでしょう。1 つ目のリソースは、オートコンプリートに入れられるようにドキュメント名のリストだけを返す役割。そしてもう1つのリソースは、ドキュメント ID に基づいて単一ドキュメントの中身を公開するものです。

🖥 [1:44] 画面(スライド「Resources」) 左(箇条書き): - Allow the MCP Server to expose data to the client - Similar to GET request handlers in a typical HTTP server - Can return any type of data - strings, JSON, binary, etc. - We set the 'mime_type' to give the client a hint as to what data we are returning - Two types: direct and templated 右(MCP Server 枠内に Resource コード2つ): python @mcp.resource( "docs://documents", mime_type="application/json" ) def list_docs(): # Return a list of document names python @mcp.resource( "docs://documents/{doc_id}", mime_type="text/plain" ) def fetch_doc(doc_id: str): # Return the contents of a doc

[2:13] これらのリソースを定義すると、それらは MCP クライアント経由でアクセスされることになります。最終的に組み上げるフロー全体はこうです。ユーザーが「what's in the @」のような何かを打ち込むと——おそらくそこに続けて何かを入れるわけですが——その @ という文字を打った瞬間に、オートコンプリートに入れるためのドキュメント名の一覧を表示する必要があります。

[2:37] そこで私たちのコードは MCP クライアントに手を伸ばし、クライアントは read resource request(リソース読み取りリクエスト)を MCP サーバーへ送ります。その read resource request の中には、URI と呼ばれるものを含めます。それは本質的に、読みたいリソースのアドレスです。この URI は、最初にリソースを組み立てるときに定義されます。つまり URI とは、そこにあるあれのことです。

🖥 [2:30] 画面(スライド:リソース読み取りのシーケンス図) レーン:User | Our Code | MCP Client | MCP Server 1. User → Our Code:What's in the @… 2. Our Code → MCP Client:I need a list of document names to put in the autocomplete 3. MCP Client → MCP Server:ReadResourceRequest(ボックス内に docs://documents) 4. MCP Server → MCP Client:ReadResourceResult(ボックス内に List of doc names) 5. MCP Client → Our Code:Great! I'll put these doc names into the autocomplete

[3:02] この read resource request を送ると、MCP サーバーは中に入れられた URI を正確に見て、そこで組み立てておいた関数を実行し、その結果を取り、read resource result メッセージの中に入れて私たちに送り返します。そうしたら中のデータを取り出してオートコンプリートに表示するなり、必要なことに使うなりできます。

[3:23] リソースには 2 つの異なるタイプがあります。direct(直接)と templated(テンプレート)です。direct リソースは static(静的)リソースと呼ばれることもあります。direct リソースは静的な URI を持つだけです。つまり常に完全に同じもの、例えば docs://documents のようなものです。

[3:38] templated リソースは、URI の中に 1 つ以上のパラメータを持ちます。例えば documents スラッシュに続けて、ここにある種のワイルドカードを置けます。そうすれば、好きなドキュメント ID を入れられます。そしてこのリソースを要求するたびに、URI 内のそのドキュメント ID が Python MCP SDK によって自動的にパースされ、関数へのキーワード引数として提供されます。キーワード引数は、そこに書いた文字列とまったく同じ名前になります。つまり、そこの doc ID が、そこの doc ID になるわけです。

🖥 [3:26] 画面(スライド:Direct vs Templated) 左: python @mcp.resource( "docs://documents", # URI mime_type="application/json" ) def list_docs(): # Return a list of document names Direct ResourceURI doesn't contain any params. 右: python @mcp.resource( "docs://documents/{doc_id}", # URI mime_type="text/plain" ) def fetch_doc(doc_id: str): # Return the contents of a doc Templated ResourceURI contains one or more params. The Python SDK parses these and passes them as args to your function.

[4:09] お察しのとおり、MCP サーバーへ求めるものに、もう少し選択や多様性、カスタマイズを許したいときにはいつでも、templated リソースを使うことになります。

[4:20] リソースの実装はかなり単純です。エディタに戻って、さっそくサーバーにリソースを追加していきましょう。

[4:27] はい、エディタに戻りました。mcp_server.py ファイルを見つけます。少し下にスクロールすると、「すべてのドキュメント ID を返すリソースを書く」と「特定のドキュメントの中身を返すリソースを書く」というコメントが見つかります。

[4:42] さて、この 1 つ目についてですが、私はコメントに document IDs と書きました。思い出してください。私たちにとって、ドキュメント ID は実質的にドキュメントの名前です。ですから実際には、これらの ID を返すだけです。ID が名前の役割を果たしてくれます。つまり、そのままオートコンプリート要素に入れられるということです。

[4:58] それでは、リソースを作るために、その to-do を削除して、@mcp.resource を追加します。第 1 引数は、これにアクセスするための URI になります。繰り返しますが、これはルートハンドラとほぼ同等のものです。そこで docs://documents を使い、さらに mime type として application/json を追加します。

🖥 [5:30] 画面(VS Code:mcp_server.py ― 1つ目のリソースのデコレータ) サイドバー:core / .env / .gitignore / main.py / mcp_client.py / mcp_server.py(変更 3 件)/ README.md ```python @mcp.resource( "docs://documents", mime_type="application/json" ) ← この4行がハイライト(入力直後)

TODO: Write a resource to return the contents of a particular doc

TODO: Write a prompt to rewrite a doc in markdown format

TODO: Write a prompt to summarize a doc

if name == "main": mcp.run(transport="stdio") `` (上にはedit_documentdocs[doc_id] = docs[doc_id].replace(old_str, new_str)` が見える)

[5:22] リソースはどんなタイプのデータでも返せます。プレーンテキストでも、JSON でも、バイナリデータでも、何でも。どんな種類のデータを返そうとしているのか、クライアントにヒントを与えるのは私たちの役目です。そのために、この MIME タイプを定義します。

[5:37] application/json という MIME タイプは、最終的にこのリソースを要求してくるクライアントに対する「構造化された JSON データを含む文字列を送り返しますよ」というヒントです。そして、そのデータをデシリアライズするのは——このデコレータの下で、実質的に使えるデータ構造へ変換し直すのは——クライアントの責務になります。

[5:56] 関数 list_docs を書き出します。文字列のリストを返すことにします。中では list(docs.keys()) を返します。あの辞書からすべてのキーを取り出してリストにして、返すだけです。

[6:12] ここで気づいてほしいのは、私たちは明示的な JSON を返してはいない、ということです。言い換えると、実際には文字列を返していません。MCP Python SDK が、私たちが返したものを自動的に文字列へ変換してくれるのです。

[6:23] それでは 2 つ目のリソースに取りかかりましょう。コメントを削除して、@mcp.resource("docs://documents/…") に置き換えます。今回はテンプレートリソースが欲しいので、ここにワイルドカードを入れます。そして今回の MIME タイプは、ちょっとした変化をつけるために、プレーンテキストを返すことにします。ドキュメントの中身そのものになるだけで、何の構造にも包まないからです。

🖥 [6:30] 画面(VS Code:2つ目のリソースを入力中) ```python @mcp.resource( "docs://documents", mime_type="application/json" ) def list_docs() -> list[str]: return list(docs.keys())

@mcp.resource( "docs://documents/{doc_id}", mime_type="▌" ← 入力途中 )

TODO: Write a prompt to rewrite a doc in markdown format

TODO: Write a prompt to summarize a doc

```

[6:51] 念のため言っておくと、実際のアプリケーションで「ドキュメントを読む」のようなものなら、私はおそらくドキュメントレコード全体を返すでしょう。つまり、ID、content、著者名、著者 ID などを含む何らかの辞書です。しかし例示のために、ここではドキュメントのテキストだけを返して、プレーンテキストを普通はどう返すかをお見せします。

[7:13] というわけで、このシナリオでは MIME タイプは text/plain になります。そして fetch_doc を作ります。doc ID——文字列です——を受け取り、文字列を返します。もう一度言いますが、そこ(URI)に置いた単語が何であれ、それが関数の中にキーワード引数として現れます。もしここに追加のパラメータ、例えば doc type のようなものを足したら、それも同じように追加のキーワード引数として現れます。

[7:43] それからこの中で、まず、この人が要求している ID が実際に存在することを確認します。if doc_id not in docs: なら、f-string で「Doc with ID not found」という ValueError を raise します。そしてそのチェックを通過したら、docs[doc_id] を返します。以上です。

🖥 [7:30] 画面(VS Code:fetch_doc のシグネチャまで完成) ```python @mcp.resource( "docs://documents", mime_type="application/json" ) def list_docs() -> list[str]: return list(docs.keys())

@mcp.resource( "docs://documents/{doc_id}", mime_type="text/plain" ) def fetch_doc(doc_id: str) -> str: ▌

TODO: Write a prompt to rewrite a doc in markdown format

TODO: Write a prompt to summarize a doc

```

[8:06] さあ、この一連のものを、もう一度 MCP インスペクターの中でテストしてみましょう。

[8:12] 思い出してください。ターミナルで uv run mcp dev mcp_server.py コマンドを実行できます。これで Web サーバーがポート 6277 で——いや失礼、デフォルトは 6274 です——立ち上がります。それをブラウザで開くようにします。

🖥 [8:00] 画面(ターミナル) プロンプト (mcp) → mcpuv ru まで入力中(uv run mcp dev mcp_server.py を打っているところ)。

[8:28] さあ行きますよ。Connect をクリックします。次に Resources を見つけます。そうすると、利用可能なすべてのリソースを列挙できるはずです。

[8:36] さて、リソースを列挙すると、これは特に static つまり direct リソースになります。ですから docs://documents だけが見えます。そして別に、リソーステンプレートもすべて列挙できます。すると、fetch_doc という 1 つのリソーステンプレートがあるのが見えます。

🖥 [8:30] 画面(Inspector・Resources タブ) ```text 上部メニュー: [Resources] | Prompts | Tools | Ping | Sampling | Roots Resources Resource Templates Select a resource or template [List Resources] [List Templates] Select a resource or template [Clear] [Clear] from the list to view its contents 📄 docs://documents

History: 2. resources/list ▸ / 1. initialize ▸ `` (この直後に List Templates でfetch_doc` が出る)

[8:51] まず、この slash documents を実行してみます。何が返ってくるか見てみましょう。これが実際のメッセージ、MCP サーバーから返ってくる正確な構造です。text プロパティがあることに気づくでしょう。その中に、私たちが返しているすべてのデータが、JSON 文字列としてシリアライズされて入っています。

[9:08] つまり繰り返すと、CLI アプリケーションの中で、この text を取り出して、この JSON 文字列から使える文字列のリストへデシリアライズし直すのは、私たちの責務だということです。

🖥 [9:00] 画面(Inspector・docs://documents の読み取り結果) 右パネル見出し:docs://documents([⟳ Refresh] ボタン付き) json { contents: [ 0: { uri: "docs://documents" mimeType: "application/json" text: "[\"deposition.md\", \"report.pdf\", \"financials.docx\", \"outlook.pdf\", \"plan.md\", \"spec.txt\"]" } ] } Resource Templates 枠には fetch_doc が表示されている。 History: 4. resources/read ▸ / 3. resources/templates/list ▸ / 2. resources/list ▸ / 1. initialize ▸

[9:19] それから fetch_doc もテストできます。クリックします。doc ID を入力する必要があります。入れましょう。report.pdf ファイルを読みたいと思います。そして、リソースを読み取ります。すると、その特定のドキュメントの中身が見えるはずです。

[9:35] そして今回は、もう一度気づいてほしいのですが、text plain が返ってきています。つまりこれは「これはプレーンテキストであり、JSON からのデシリアライズを一切試みるべきではない」という私へのヒントなのです。

🖥 [9:30] 画面(Inspector・fetch_doc の読み取り結果) 右パネル見出し:docs://documents/report.… json { contents: [ 0: { uri: "docs://documents/report.pdf" mimeType: "text/plain" text: "The report details the state of a 20m condenser tower." ← 選択ハイライト } ] } History: 6. resources/read ▸ / 5. completion/complete ▸ / 4. resources/read ▸ / 3. resources/templates/list ▸ / 2. resources/list ▸ (5 の completion/complete は、テンプレートの doc_id 入力時に Inspector が補完リクエストを送っている)

英語逐語(ASR全文)

In this video, we're going to move on to the next major feature inside of MCP servers, which is resources. To help you understand resources, we're going to be implementing another feature inside of our project. Here's what we're going to add in. I want to allow a user to mention a document by putting in an at symbol and then the name of a document. Whenever they do so, I want to automatically fetch the contents of that document and insert it into the prompt that we send off to Claude. So in total, there's going to be kind of two aspects to this feature. Whenever a user types out the at symbol inside of a message, we're going to automatically show a list of all the different documents that they can mention inside of a little autocomplete window. Then whenever a user submits a message with a mention inside of it, we're going to automatically get the contents of that document and insert it into the prompt that we send off to Claude. So for example, if a user says something like what's in the at report.pdf file, I would want to assemble a prompt like this and send it to Claude. So we're going to have the query inside there from the user. And then we're also going to tell Claude that the user might have referenced some document and here is the contents of the document. So the approach here or the idea here is that we will not have to rely upon Claude to go and make use of some tool to figure out what is inside of the report.pdf file. Instead, the user can just preemptively mention the file and we're going to automatically insert some context ahead of time. Now, one thing I want to clarify here is that we're kind of talking about two separate features. The first feature is that whenever a user types in the at symbol, we really need the MCP server to give us a list of all the different documents that the user can possibly mention. And then the second aspect here is that whenever a user submits a message that contains a mention, then we need the MCP server to give us the contents of a single document. To get this information out of our MCP server, we are going to be making use of resources. Resources allow our MCP server to expose some amount of data to the client. We usually define one resource for each distinct read operation. So in our example, we need to get a list of documents and read the contents of a single document. So we would probably end up making two separate resources. One resource would be responsible for returning just a list of document names so we can put them inside the autocomplete. And then we would probably make another resource that will expose the contents of a single document based upon its document ID. When we define these resources, they're going to be accessed through our MCP client. So the entire flow that we're going to eventually put together here, whenever a user types in something like what's in the at, and then presumably they're going to put in something right there. As soon as they type in that at character, we need to display a list of document names to put in the autocomplete. So our code is going to reach out to the MCP client, which in turn is going to send a read resource request off to the MCP server. Inside of that read resource request, we're going to include something called the URI. That is essentially the address of the resource we want to read. This URI gets defined whenever we put together our resource initially. So the URI is that right there. When we send off this read resource request, the MCP server is going to look at the exact URI that we put inside of here and then run the function we put together right there, take the result, and send it back to us inside of a read resource result message. We can then take the data inside there and display it inside of our autocomplete or do whatever else we need to do with it. There are two different types of resources, direct and templated. You'll also sometimes see direct resources referred to as static resources. A direct resource just has a static URI, So it's always going to be the exact same thing, such as docs colon slash slash documents. A templated resource will have one or more parameters inside of its URI. So for example, we might have document slash and then kind of a wild card right here. So we can put in any document ID we want to. And whenever we ask for this resource, that document ID right there inside the URI will be automatically parsed by the Python NCP SDK and provided as a keyword argument to our function. The keyword argument will have the exact same name of whatever string you put in right there. So doc ID right there will be doc ID right there. As you can probably guess, we'll make use of templated resources anytime that we want to allow a little bit more selection or variety or customization in what someone is asking for out of our MCP server. Implementing resources is pretty straightforward. So let's go back over to our editor and we're going to add in some resources to our server right away. All right, so back over inside my editor, I will find the mcpserver.py file. I'm then going to scroll down a little bit, and I'm going to find some comments for writing resource to return all document IDs, and writing resource to return the contents of a particular document. Now, for this first one right here, I put in the comment document IDs. Remember, for us, our document IDs are essentially the name of the document. So for us, we're really just returning these IDs. They're going to serve the purpose of the name. That means we can put them directly into that autocomplete element. All right, so to make our resource, I'm going to delete that to-do, and then I'll add in a mcp.resource. The first argument is going to be the URI for accessing this thing. Again, it's kind of equivalent to a route handler. So I will use docs, colon, slash, slash, documents, and I'm also going to add in a mime type of application, slash, JSON. A resource can return any type of data. So it can be plain text. It can be JSON. It can be binary data, anything. It's up to us to kind of give our client a hint as to what kind of data we are returning. To do so, we're going to define this MIME type. A MIME type of application slash JSON is a hint to our client, who's eventually going to ask for this resource right here, that we're going to be sending back a string that contains some structured JSON data. And so it would be up to our client to deserialize that data. We're essentially turning into some usable data structure underneath that decorator. I'll write out my function of list docs, and I'm going to return a list of strings. And then inside there, I will return list docs keys. So just take all the keys out of that dictionary and turn it into a list. And I'm going to return it. Now you'll notice that we are not returning distinct JSON here. In other words, we're not actually returning a string. the mcp python sdk is going to automatically take whatever we return and turn it into a string for us all right let's take care of our second resource so i'm going to delete that comment and then replace it with mcp resource docs colon slash slash documents and then this time i want a templated resource because i'm putting in this wild card right here and then my mime type this time around, just for a little bit of variety, I'm going to be returning plain text because it's going to be just the contents of the document and I'm not going to wrap it up in any kind of structure. Now, just so you know, in a real application, something like read a document, I would probably return an entire document record. So some kind of dictionary that contains maybe the ID, the content, the author name, the author ID and stuff like that. But just for the sake of an example, I'm going to return just the text of the document to show you how we would normally return plain text. So in this scenario, my MIME type would be text plain, and then I will make fetch doc. I'm going to take doc ID, which is going to be a string, and I'm going to return a string. Once again, whatever word you put right there, it's going to show up as a keyword argument inside of your function. If we added in some additional parameters inside of here, such as maybe doc type or something like that, it would just show up as an additional keyword argument like so. Then inside of here, I'm going to first make sure that the ID that this person is asking for actually exists. So if doc ID not in docs, I'm going to raise a value error with a F string that says doc with ID not found. And then if we get past that check, I will return docs doc ID. And that's it. Now let's try testing this stuff out inside of our MCP inspector once again. So remember at our terminal, we can run the command uvrun mcpdev mcpserver.py. That's going to start up a web server at port 6277, or excuse me, 6274 as the default. So I'm going to make sure I open that up inside my browser. Here we go. I'll click connect. I'll then find resources. And then I should be able to list out all the different resources that are available. Now when I list out resources, this is going to be specifically static or direct resources. So I'll see only doc slash documents. And then I can separately list out all my different resource templates. And so I'll see that I have one resource template of fetch doc. I'm going to first try to run the slash documents right here. We'll see what we get back. So this is the actual message, the exact structure that gets returned from our MCP server. You'll notice that it has a text property. And inside there is all the data that we are returning serialized as JSON string. So again, it would be up to us inside of our CLI application to take this text right here and deserialize it from this JSON string into a usable list of strings. Then we can also test out fetch doc. So I'll click on that. I have to enter a doc ID. So I'm going to put it in. I want to read the report.pdf file. And I'll read the resource. And now I should see the contents of that particular document. and you'll notice this time around once again i get a text plain so that's a hint to me that this is plain text and i should not attempt to deserialize it from json in any way

(注:ASR の「uvrun mcpdev mcpserver.py」=uv run mcp dev mcp_server.py、「Python NCP SDK」=Python MCP SDK、「docs colon slash slash documents」=docs://documents、「doc slash documents」=docs://documents の意。)

↑ 目次へ戻る

Building with the Claude API | Lesson 61「Accessing resources」(全文ナレッジ)

動画 4分38秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04 本レッスンは「Model Context Protocol」章。別コース『Introduction to MCP』Lesson 09「Accessing resources」と同一カリキュラムの別録画(対応ノート: ../introduction-to-mcp/09_accessing_resources.md)。用語はそちらと揃えつつ、本文は本動画の逐語・画面に基づく。

日本語ナレッジ(要点整理)

このレッスンのテーマ

前レッスンでサーバー側に 2 つのリソース(docs://documents=一覧/docs://documents/{doc_id}=本文)を定義した。今回はクライアント側にそれを要求する能力を持たせる。追加するのは mcp_client.pyread_resource 関数 1 つだけ——これをコードベースの別の場所(既製の CLI コード)が呼び出して、①@オートコンプリート用のドキュメント名一覧、②プロンプトへ注入する単一ドキュメントの本文、を取得する。

read_resource の設計

ゴール:URI を引数に受け取り、MCP サーバーへ読み取りリクエストを送り、返ってきた内容を MIME タイプに応じてパースして、データをそのまま返す。

# 冒頭に追加する 2 つの import(型を綺麗に扱うため)
import json
from pydantic import AnyUrl

async def read_resource(self, uri: str) -> Any:
    result = await self.session().read_resource(AnyUrl(uri))
    resource = result.contents[0]

    if isinstance(resource, types.TextResourceContents):
        if resource.mimeType == "application/json":
            return json.loads(resource.text)

        return resource.text
  • AnyUrl(uri):pydantic の型。型を整合させるためだけのラッパー
  • result.contents[0]:前レッスンの Inspector で見た応答構造そのもの——result は contents リストを持ち、要素が並ぶが、関心があるのは先頭の 1 個だけ
  • 先頭要素から mimeType を見る:
  • application/json → サーバーが「JSON を返した」とヒントをくれている → json.loads(resource.text) でパースして返す(=ドキュメント名一覧のケース)
  • それ以外 → resource.text をそのまま返す(プレーンテキスト=単一ドキュメント本文のケース。何もパースしない)
  • isinstance(resource, types.TextResourceContents) でテキスト系コンテンツであることを確認してから mimeType を見る

動作テスト(CLI の @メンション機能が完成)

  • 呼び出し側のコードは既に実装済みなので、これですべて動くはず
  • uv run main.py → 「what's in the @」と打つとリソース一覧(ドキュメント名)がポップアップ表示され、矢印キーでスクロールスペースで挿入 → 「Whats in the @report.pdf document?」
  • 送信すると、ドキュメントの中身は既にプロンプトに入っているので即座に応答が返る:「The report.pdf document contains details about the state of a 20-meter condenser tower.」
  • 今回は Claude がツールを使う必要がなかった——これがリソースによる事前コンテキスト注入の効果

まとめ

リソースとは、MCP サーバーから一定量の情報を公開するための仕組み。サーバー側で定義(Lesson 60)し、クライアント側の read_resource で URI 指定で読み取り、MIME タイプに従ってパースして使う(本レッスン)。

実務ポイント(このレッスンから持ち帰るもの)

  1. クライアントの read_resource は「URI → データ」の変換器として書く:session().read_resource(AnyUrl(uri))contents[0] → mimeType 分岐 → パース済みデータを返す。呼び出し側に生のプロトコル構造を漏らさない。
  2. mimeType 分岐はサーバーが出したヒントの「受け取り側」application/json なら json.loadstext/plain ならそのまま——Lesson 60 のサーバー側 mime_type 設定と対になる契約。
  3. result.contentsリストだが、単一リソース読み取りでは先頭要素 [0] だけを使うのが定石。Inspector で実際の応答構造を見てから書くと迷わない。
  4. AnyUrl(pydantic)や types.TextResourceContentsisinstance ガードは型整合のための定型——SDK の型に素直に合わせる。
  5. ツール往復なしの即答が体感できる:@メンションで本文を先に注入すれば、Claude はツール呼び出しの 1 往復(レイテンシ+トークン)を省略して直接回答できる。「ユーザーが指定できる読み取りはリソースで先回り」の設計原則。
  6. クライアントの関数はアプリの複数箇所から呼ばれる共有 API(オートコンプリート用の一覧取得と、プロンプト組み立て用の本文取得)。1 つの read_resource が両方を賄えるのは、URI と MIME タイプで抽象化しているから。

本文(時系列・日本語逐語+画面差し込み)

[0:00] 私たちは MCP サーバーの中に、2 つの別々のリソースを定義しました。そこで今度は、クライアントに、これらのリソースを要求する能力が必要です。

🖥 [0:00] 画面(スライド:リソース読み取りのシーケンス図・再掲) レーン:User | Our Code | MCP Client | MCP Server User → Our Code:What's in the @… / Our Code → MCP Client:I need a list of document names to put in the autocomplete / MCP Client → MCP Server:ReadResourceRequestdocs://documents)/ ← ReadResourceResultList of doc names)/ MCP Client → Our Code:Great! I'll put these doc names into the autocomplete

[0:08] そのために、MCP クライアントの中に関数を 1 つ追加します。思い出してください。この MCP クライアントは、私たちが組み立てている機能を持ち、それはアプリケーションの残りの部分から使われることになります。そして、そのコードは私がすでに組んであります。つまり、このプロジェクトのどこか別の場所で、これから MCP クライアントに追加しようとしているこの関数を、何かが使おうとするのです。

[0:28] 始めるために、MCP クライアントのファイルをもう一度開きます。下にスクロールして、ここにある read resource を見つけます。

🖥 [0:29] 画面(画面転換:スライドから VS Code へ) スライドが右へスライドアウトし、左から VS Code(mcp_client.py)が現れる。

[0:37] さて、ここでのゴールは、MCP サーバーへリクエストを送って特定のリソースを読み取り、返ってきた内容を、その MIME タイプに応じてパースし、得られたデータをそのまま返すことです。

[0:50] 引数が URI であることに気づくでしょう。これは、サーバーから取得したいリソースの URI になります。

[0:56] リクエストを行うために——型をすべて綺麗に整えるためでもありますが——ファイルの一番上に 2 つの import を追加します。JSON モジュールの import を追加し、そして PyDantic から AnyUrl を import します。

🖥 [1:00] 画面(VS Code:mcp_client.py をスクロール中) パンくず mcp_client.py > Python > AnyUrl。ファイル上部へ移動する途中で __init__ の末尾と connect 関数が見える: python async def connect(self): server_params = StdioServerParameters( command=self._command, args=self._args, env=self._env, ) stdio_transport = await self._exit_stack.enter_async_context( stdio_client(server_params) ) _stdio, _write = stdio_transport self._session = await self._exit_stack.enter_async_context( ClientSession(_stdio, _write) ) await self._session.initialize()

[1:08] それでは read resource 関数へ戻ります。コメントと return 文を消します。そして result = await self.session().read_resource(...) で結果を受け取ります。そして——これも本当に型を整合させるためなのですが——入力された URI を AnyUrl に包んで入れます。

[1:36] 次に、その result から、response——失礼、result.contents の 0 番目を取り出します。そして、なぜこれを追加するのかをここではっきりさせておきたいのです。ついさっき、インスペクターの中で、返ってくるレスポンスを見ましたよね。あれが本質的にこの result 変数です。

[1:53] result は contents リストを持っていて、その中に要素のリストが入ります。私たちが本当に関心を持つのは、一番最初の 1 個だけです。だから最初の辞書を取得したいのです。

🖥 [1:53] 画面(VS Code+Inspector の並び) 左:エディタに read_resource(AnyUrl(uri)) まで書かれたコードが見える。右:MCP Inspector v0.10.2 の Resources タブ——Resources に docs://documents、Resource Templates に fetch_doc、History に 6. resources/read5. completion/complete4. resources/read3. resources/templates/list2. resources/list(前レッスンで読み取った応答構造を参照している)。

[2:01] その type プロパティと MIME タイプにアクセスしたい。特に MIME タイプが欲しいのは、どんな種類のデータが返ってきたのかを理解する助けになるからです。もし JSON なら、テキストを JSON としてパースして、その結果を返すようにしたいのです。

[2:16] では、どうやるかをお見せしましょう。if isinstance(resource, types.TextResourceContents): を追加します。そしてその中で、if resource.mimeType == "application/json":

[2:36] これが、あのヒントの使いどころです。サーバーが「JSON を返しているよ」と教えてくれたのなら、テキストコンテンツを JSON としてパースする必要があります。その場合は json.loads(resource.text) を返します。

[2:50] そして、その if 文に入らず早期リターンもしなかった場合には、単に resource.text を返します。このシナリオでは、テキストをただのプレーンテキストとして返しているわけです。何もパースしません。これはまさに、単一ドキュメントの中身が返ってくるケースですね。

🖥 [3:16] 画面(VS Code:完成した read_resource) パンくず MCPClient > read_resource。 ```python async def read_resource(self, uri: str) -> Any: result = await self.session().read_resource(AnyUrl(uri)) resource = result.contents[0]

    if isinstance(resource, types.TextResourceContents):
        if resource.mimeType == "application/json":
            return json.loads(resource.text)

        return resource.text

async def cleanup(self):
    await self._exit_stack.aclose()
    self._session = None

`` (上にはget_prompt` の TODO スタブが見える。右端に Inspector のサイドバー)

[3:08] はい。これで本当に終わりのはずです。read resource が組み上がりました。

[3:13] さて、もう一度リマインドさせてください。何度も言っているのは分かっていますが、少し分かりにくいかもしれないので念を押します。MCP クライアントの中に書いているこのコードは、このコードベースの他のいくつかの場所から使われます。つまり、このコードベースのどこか別の場所で、たった今組み立てたあの関数を呼び出して、ドキュメント名の一覧を取得し、やがてはドキュメントの中身を取得してプロンプトに入れることになります。

[3:35] ですからこの時点で、実質的にすべてが動くはずです。残りの仕事はすでに済んでいるのですから。

[3:41] それを踏まえて、ターミナルに戻りましょう。CLI アプリケーションをもう一度テストして、このメンション機能が動くかどうかを見てみます。

[3:49] はい、こちらに戻ってきました。uv run main.py を実行します。そして今や、例えば「what's in the @」のように言えるはずです。ほら、出ました。リソースの一覧が見えます。そして矢印キーでスクロールできます。

[4:02] 気に入ったリソースのところに来たら、スペースを押すだけで、そのリソースが挿入されます。というわけで、「what's in the report.pdf document」です。

🖥 [4:00] 画面(ターミナル:@メンションでの挿入直後) ```text (mcp) → mcp uv run main.py

Whats in the @report.pdf document?▌ `` (@を打った時点でドキュメント名のオートコンプリートがポップアップ表示され、矢印キーで選択 → スペースでreport.pdf` が挿入された状態)

[4:11] そして、ここではすべてが期待どおりに動いていると言えます。言い換えると、このドキュメントの中身は、プロンプトの中に入れられて Claude へ送られています。ですから、これを送信すると、即座に応答が見えるはずです。report.pdf の中に何があるかを教えてくれます。

🖥 [4:24] 画面(ターミナル:即答が返る) ```text (mcp) → mcp uv run main.py

Whats in the @report.pdf document?

Response: The report.pdf document contains details about the state of a 20-meter conde nser tower.

``` (Response の本文がハイライトされている)

[4:25] 今回は、Claude はドキュメントの中身を読むためにツールを使う必要がありませんでした

[4:31] はい。これがリソースです。繰り返しますが、私たちは MCP サーバーから一定量の情報を公開するためにリソースを利用するのです。

英語逐語(ASR全文)

We have defined two separate resources inside of our MCP server. So now our client needs the ability to request these resources. To do so, we're going to add in a single function inside of our MCP client. And remember, this MCP client is going to have some functionality that we're putting together that is going to be used by the rest of our application. And I've already put together that code already. So somewhere else inside this project, something is going to try to make use of this function that we're about to add into the MCP client. To get started, I'm going to open up the MCP client file again. I'm going to scroll down and find read resource right here. So our goal inside of here is to read a particular resource by making a request off to our MCP server and then parse the contents that come back depending upon its MIME type and then just return whatever data we get. So you'll notice that a argument to it is the URI. This is going to be the URI of the resource that we want to fetch from the server. In order to make a request, just to get all of our types nicely, we're going to add two imports at the very top of the file. I'm going to add in an import for the JSON module, and from PyDantic, I will import any URL. Then I'll go back down to our read resource function. I'm going to clear out the comment and the return statement. Then I'll get a result from calling a wait, self session. I want to read resource. And then again, this is really just to get the types to work out. We're going to put in a, any URL with the input URI. Then I'm going to take from that result response or excuse me, result contents at zero. And I want to make this clear right here, why we are adding this in. So just a moment ago inside of our inspector, we saw the response we get back. So this is essentially that result variable. The result has a contents list and there's going to be a list of elements inside there. We really only care about the very first one. So I want to get the first dictionary. I want to access the type property and the MIME type. I want specifically the MIME type because it's going to help me understand what kind of data we got back. If it is JSON, then I want to make sure I parse the text as JSON and return that result. So let me show you how we're going to do that. I'm going to add in a, if is instance of resource types, text, resource contents. And inside there, if resource MIME type is equal to application JSON. So this is our hint in use. If the server told us that it's giving us back some JSON, we need to make sure that we parse the text content as JSON. So I will return in that case, a JSON loads of resource dot text. And then otherwise, if we don't fall into that if statement and return early, I want to just return resource dot text. So in this scenario, we'd be returning the text as just plain text. We're not parsing anything. So this would really be the case in which we get back the contents of a single document. All right. So that should really be it. We've got our read resource put together. Now, again, I want to remind you, I know I've said this several times, but I just want to remind you because I think it might be a little bit unclear. The code that we're writing inside of the MCP client is being used from several other places inside of this code base. So somewhere else in this code base, we're going to be calling that function that we just put together to get the list of document names and then eventually get the contents of a document to put into a prompt. So at this point, everything should essentially work because the rest of the work has already been done for us. So with that in mind, let's go back over to our terminal, and we're going to test out our CLI application again and see if this mention feature works. Okay, so back over here, I'll do a UV run main.py, and now I should be able to say something like, what's in the at, and there we go, I see my list of resources, and I can use the arrow key to scroll through. Once I am at a resource I like, I'll just hit space, and we'll insert that resource. So what's in the report.pdf document. And now I can tell you that everything is working as expected here. In other words, the contents of this document is being sent off to Claude inside of a prompt. So if I submit this, I should see an immediate response and it's going to tell me what is inside of report.pdf. So this time around, Claude did not have to use a tool to read the contents of the document. All right. So that is resources. Again, we make use of resources to expose some amount of information from our MCP server.

(注:ASR の「PyDantic any URL」=from pydantic import AnyUrl、「is instance of resource types, text, resource contents」=isinstance(resource, types.TextResourceContents)、「JSON loads of resource dot text」=json.loads(resource.text)、「UV run」=uv run の意。)

↑ 目次へ戻る

Building with the Claude API | Lesson 62「Defining prompts」(全文ナレッジ)

動画 7分44秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04

日本語ナレッジ(要点整理)

このレッスンのテーマ

MCP サーバーの最後の主要トピック=prompts(プロンプト)。resources のときと同じく、プロジェクト(ドキュメント管理 CLI)に小さな機能を1つ実装しながら prompts の正体を理解する。今回の機能はスラッシュコマンド対応:ユーザーが / を打つとコマンド一覧が出て、/format <doc_id> を実行すると Claude がそのドキュメントを Markdown 記法に整形して返す。

機能要件(Next Feature)

  • / 入力で、アプリが対応するコマンド一覧をオートコンプリート表示(当面は format の1つだけ)。
  • format を選ぶとドキュメント ID(report.pdf など)の入力を促す。
  • 実行すると Claude が「I'll help you reformat the document…」→ ツールでドキュメント内容を読む → 最終応答で Markdown 整形済みの本文を出力。
  • 背景:MCP サーバー内のドキュメントは現状ただのプレーンテキストdocs 辞書の文字列)。これを Claude に食わせて Markdown で書き直させたい。

この機能の面白いところ=「開発者のコードが不要な操作」をなぜ機能化するのか

  • 実はユーザーは今でも CLI に「reformat the report.pdf file in Markdown syntax」と打てば同じことができ、Claude はそこそこ上手くやる(デモでも完璧に動いた)。
  • では何のための機能か? ユーザー任せの「convert this to Markdown」ではそこそこの結果しか出ないかもしれない。このシナリオ専用に作り込まれた強力なプロンプトがあれば、はるかに良い結果になり得る。
  • そこで MCP サーバーの作者(=私たち)が腰を据えて、書いて・テストして・eval して仕上げた徹底的で高品質なプロンプトを用意し、それをユーザーに使ってもらう。これが MCP サーバーの prompts 機能の本当の狙い
  • 考え方:サーバーが特化している領域(今回ならドキュメント管理・閲覧・編集)にカスタムテイラーされた高品質プロンプト集を事前に定義し、任意のクライアントアプリ(今作っている CLI など)から使えるように公開する。
  • プロンプトを CLI のコードベースに直接書くことも当然可能。だが、特定タスクに特化した MCP サーバーがプロンプトも一緒に配布すれば、利用者は自分でプロンプトを開発しなくても「来てそのまま使う」だけで済む。

プロンプトの定義方法(tools / resources とそっくりの構文)

  • @mcp.prompt デコレータを使い、name と(任意で)description を付ける。
  • クライアントがこのプロンプトを要求してきたら、メッセージのリストを返す。これは実際の user / assistant メッセージなので、そのまま Claude に送信できる
  • 実装した完成形(mcp_server.py): ```python from mcp.server.fastmcp.prompts import base # 先頭に import 追加

@mcp.prompt( name="format", description="Rewrites the contents of the document in Markdown format.", ) def format_document( doc_id: str = Field(description="Id of the document to format"), ) -> list[base.Message]: prompt = f""" Your goal is to reformat a document to be written with markdown syntax.

  The id of the document you need to reformat is:
  <document_id>
  {doc_id}
  </document_id>

  Add in headers, bullet points, tables, etc as necessary. …
  Use the 'edit_document' tool to edit the document. …
  """

  return [base.UserMessage(prompt)]

`` - 引数doc_idはツールのときと同様に **Field(description=...)` で説明を付け、型注釈(str)も明示。 - プロンプトの中身:ドキュメント ID を受け取り、(暗黙に)read document ツールで内容を取得させ、Markdown 記法で書き直し、最後に edit_document ツールで更新をサーバーに保存**させる、という一連の指示。

MCP Inspector でのテスト

  1. ターミナルで uv run mcp dev mcp_server.py → ブラウザで Inspector(127.0.0.1:6274)に接続。
  2. Prompts セクション → List Prompts → 定義済みプロンプトは format の1件。
  3. format をクリック → doc_id 入力欄に outlook.pdf を入れて Get Prompt
  4. 返ってくるのは事前に組み立てられたメッセージのリストmessages[0]role: "user"、その text パートにフルのプロンプトが入っており、doc_id(outlook.pdf)がプロンプト本文に補間済みであることを確認できる。
  5. あとはこのメッセージ群を Claude に送れば、適切な応答が返ってくるはず(クライアント側の接続は次レッスン)。

締めのメッセージ

MCP サーバーに実装する prompts の発想は一貫して「よくテストされ、よく eval され、1つのユースケースに本当に特化したプロンプトを提供すること」。

実務ポイント(このレッスンから持ち帰るもの)

  • MCP prompts=「作者が eval 済みの必勝プロンプトを配布するチャネル」。ユーザーが自力で頼めるタスクでも、専用に作り込んだプロンプトの方が結果が安定する——この差分こそが prompts 機能の存在理由。
  • 定義は tools / resources と同型:@mcp.prompt(name=..., description=...) を関数に付け、引数がそのままプロンプト変数になる(Field(description=...)+型注釈で仕様を明示)。
  • 返り値は list[base.Message]from mcp.server.fastmcp.prompts import base)。base.UserMessage(prompt) を返せば、クライアントは受け取ったメッセージ列を無加工で Claude に転送できる。
  • プロンプト設計の型(スライドの模範例が参考になる):役割宣言 → 対象 ID を <document_id> などの XML タグで注入 → 手順の番号付き指示 → 書式ガイドライン → 出力構造の例示。ツール利用(read → rewrite → edit_document で保存)を指示文に織り込むことで、プロンプト1発で複数ツールのワークフローを駆動できる。
  • スラッシュコマンド UI と prompts は1対1対応させるのが定石(/format = prompt name format)。コマンドの説明文には prompt の description がそのまま使える。
  • テストは実装前にクライアントを書かず MCP Inspectoruv run mcp dev mcp_server.py)で:List Prompts → 引数入力 → Get Prompt → 返却 messages に変数が補間されているかを目視確認、が最短ループ。
  • プロンプトをクライアント側コードに直書きするか、サーバー側に置くかの判断基準:複数クライアントで再利用させたい/サーバーの専門性とセットで配布したいなら MCP サーバー側に置く。

本文(時系列・日本語逐語+画面差し込み)

[0:00] MCP サーバーの中で最後に大きく取り上げるのは、prompts(プロンプト)です。

[0:04] resources のときとまったく同じように、プロジェクトの中に小さな機能を1つ実装し、その機能を使ってプロンプトとは何なのかを理解していきます。少し前に resources でやったのと同じやり方ですね。

[0:14] これからプログラムに追加する機能について説明しましょう。スラッシュコマンドのサポートを追加します。

[0:21] たとえば、format コマンドを用意したいのです。動作イメージのスクリーンショットをこちらに用意してあります。

🖥 [0:16] 画面(スライド「Next Feature」) 左に機能要件の箇条書き、右に CLI モックアップ3枚(黒背景ターミナル風)を縦の矢印でつないだ構成: - Users can ask Claude to 'format' a document as markdown - User will initiate the process by typing a "/" to list out possible commands - User will specify the ID of the document to format - Claude should read the document's contents, then print a markdown-formatted version

モックアップ①(/ でコマンド一覧): ```

/ /format Rewrites the conten... モックアップ②(doc_id の補完ドロップダウン): /format doc_id deposition.md report.pdf financials.docx outlook.pdf モックアップ③(実行イメージ): /format report.pdf I'll help you reformat the document with markdown syntax. First, let me read the current content of the document. Now I'll reformat the document using markdown syntax:

Response:

Condenser Tower Assessment Report

``` 左下に ANTHROP\C ロゴ。(この直後 [0:30] には、Our Server → MCP Client → 2つの MCP Server(Tools / Resources / Prompts=オレンジでハイライト)→ Outside Service というアーキテクチャ図スライドも表示され、prompts がサーバーの第3のプリミティブであることが示される)

[0:26] ユーザーがスラッシュを打つたびに、アプリケーションがサポートするコマンドをいくつか一覧表示します。今のところ、コマンドは format の1つだけにします。

[0:35] つまり、スラッシュだけを打てば、ここに小さなオートコンプリートが出るはずで、その唯一の候補が format であるべきです。

[0:42] そこで format を選択したら、その後ろに何らかのドキュメント ID を入力するよう促されるはずです。ドキュメント名のどれか——report.pdf などですね。

[0:52] そしてユーザーがこのコマンドを実行したときのゴールは、Claude にこのドキュメントを Markdown 記法で整形し直させることです。言い換えると、いま各ドキュメントの中にある、特別な書式が何も付いていないプレーンな文字列を——思い出してください、MCP サーバーの中の現在のドキュメント内容はただのプレーンテキストです——Claude に食わせて、どうにか Markdown 記法で書き直させたいのです。

🖥 [1:08] 画面(エディタ:mcp_server.py・docs 辞書) VS Code。「mcp_server.py — mcp」。サイドバー:core / .env / .gitignore / main.py / mcp_client.py / mcp_server.py(選択中)/ README.md。 ```python from pydantic import Field from mcp.server.fastmcp import FastMCP

mcp = FastMCP("DocumentMCP", log_level="ERROR")

docs = { "deposition.md": "This deposition covers the testimony of Angela Smith, P.E.", "report.pdf": "The report details the state of a 20m condenser tower.", "financials.docx": "These financials outline the project's budget and expenditures.", "outlook.pdf": "This document presents the projected future performance of the system.", "plan.md": "The plan outlines the steps for the project's implementation.", "spec.txt": "These specifications define the technical requirements for the equipment.", }

@mcp.tool( name="read_doc_contents", description="Read the contents of a document and return it as a string.", ) ``` =各ドキュメントの中身がプレーンな1行文字列であることを確認している場面。

[1:14] つまりこれを Claude に渡して、なんとか Markdown 記法で書き直させたい。そうすると、こんな出力が期待できるはずです。「I'll help you reformat the document(ドキュメントの整形をお手伝いします)」のような文言があり、次に Claude がツールを使ってドキュメントの内容を読み、そして最後の応答の中で、そのドキュメントの内容が Markdown 記法で書き直されてこの下に表示される、というものです。

[1:34] さて、この機能について指摘しておきたい興味深い点があります。この機能の本当の核心——本当のゴール——は、ユーザーがドキュメントを Markdown 記法に整形できるようにすることです。そしてそれは、実のところ、あなたと私=開発者が実装コードを一切書く必要のない操作なのです。

[1:51] どういう意味でしょうか? 実は、ユーザーは今でも私たちの CLI を起動して、「reformat the report.pdf file in Markdown syntax(report.pdf を Markdown 記法で整形して)」のように言うことができます。

[2:03] ユーザーは今すぐこれができます。何の問題もなく。そして Claude はそれをそれなりに上手くやってのけます。ドキュメントの内容を取ってきて、Markdown に整形するでしょう。ご覧のとおり、完璧に動きました。

🖥 [2:11] 画面(ターミナル:手打ち依頼でも動く実演の結果) iTerm「uv (python3)」。CLI 上の Claude の応答: ``` Response: Here's the report reformatted in Markdown:

```markdown

Condenser Tower Report

The report details the state of a 20m condenser tower. ```

``` 講師が「# Condenser Tower Report」付近をマウスで選択して見せている。

[2:15] では、この機能で私たちは本当のところ何をやろうとしているのでしょうか?

[2:17] 考え方はこうです。もしこのプロセスをユーザー任せにして、「convert this to Markdown」のような文を手打ちさせるだけなら、そこそこの結果は得られるかもしれません。しかし、ドキュメントを Markdown に変換するというこの特定シナリオのためにカスタムテイラーされた、本当に強力なプロンプトがあれば、はるかに良い結果が得られるかもしれないのです。

🖥 [2:18] 画面(スライド:ユーザー任せの一文 vs 作り込んだプロンプトの比較) 2カラム構成のスライド。 :「If we left this process up to a user, here's what they'd write:」 黒いボックス内:Convert report.pdf to markdown その下:「Yes, it'd work, but the user might get a better result with some strong prompt engineering」 :「User might have more luck if they use our thoroughly-eval'd prompt instead!」 黒いボックスに長文のプロンプト全文(等幅フォント): You are a document conversion specialist tasked with rewriting documents in Markdown format. Your goal is to take the content of a given document and convert it into well-structured Markdown, preserving the original meaning and enhancing readability. Here is the identifier of the document you need to convert: <document_id> {{doc_id}} </document_id> Instructions: 1. Retrieve the content of the document associated with the given document_id. 2. Analyze the structure and content of the document. 3. Convert the document to Markdown format, following these guidelines: - Use appropriate header levels (# for main titles, ## for subtitles, etc.) - Properly format lists (both ordered and unordered) - Use emphasis (*italic* or **bold**) where appropriate - Add links and images using Markdown syntax if present in the original document - Preserve any special formatting or structure that's important to the document's meaning Before providing the final Markdown output, in <document analysis> tags: - Identify the main sections and subsections of the document - Count the number of sections and subsections to ensure proper nesting of headers This will help ensure a thorough and well-organized conversion. After your analysis, present the converted document in Markdown format. Use markers to denote the beginning and end of the Markdown content. Example output structure: [Your analysis of the document structure and conversion plan] ```markdown

Document Title

Section 1

Content of section 1...

Section 2

Content of section 2... - List item 1 - List item 2 Link text Image description Please proceed with your analysis and conversion of the document.

[2:36] つまり、あなたと私が MCP サーバーの作者として腰を据え、本当に徹底的で素晴らしいプロンプト——右側に見えているようなもの——を書き、テストし、eval し、開発プロセス全体を最後までやり切ったなら、ユーザーはずっと幸せになれるかもしれない、ということです。

[2:52] 繰り返しますが、そうです、ユーザーはこのワークフロー全体を自力で実行できます。でも、代わりにこちらの立派なプロンプトを使ってくれたなら——まあ、その方がずっと良いはずです。これが MCP サーバーにおける prompts 機能の本当のゴールです。

[3:02] 発想はこうです。あらかじめ、サーバーが本当に特化している領域にカスタムテイラーされた一連のプロンプトを、サーバーの中に定義しておけるのです。

[3:13] 私たちの場合、このサーバーはドキュメントの管理・閲覧・編集などがすべてです。そこで、非常に高品質で、eval もテストも済んでいて、さまざまなシナリオで機能すると分かっているプロンプトのセットを追加することにしてもよいでしょう。

[3:27] そして、これらのプロンプトを、任意のクライアントアプリケーション——いま組み立てているこの CLI アプリのような——の中で使えるように公開できます。

[3:34] ここで1つ指摘しておきたいのは、このプロンプトを開発して、CLI のコードベースに直接置くこともできる、ということです。それは完全に可能です。当然、やろうと思えばできます。

[3:44] しかし、繰り返しになりますが、考え方はこうです。特定のタスクに特化したあなたの MCP サーバーが、いくつかのプロンプトも公開していれば、人々は事前にプロンプトを開発する心配をせずに、ただやって来てそのまま使うことができるのです。

[3:55] MCP サーバーの中にプロンプトを定義するには、すでに組み立ててきたツールやリソースと非常によく似た、ちょっとした構文を書きます。prompt デコレータを使い、プロンプトに名前を付け、任意で説明も付けます。

🖥 [3:40〜4:30] 画面(スライド「Prompts」) タイトル:Prompts - Defines a set of User and Assistant messages that can be used by the client - These prompts should be high quality, well-tested, and relevant to the overall purpose of the MCP

右側にカード「MCP Server」、ラベル Prompt、黒いコードボックス: python @mcp.prompt( name="format", description="Rewrites the contents of a document in Markdown format", ) def format_document( doc_id: str, ) -> list[base.Message]: # Return a list of messages

[4:04] そして、クライアントがこのプロンプトを要求してきたら、メッセージのリストを送り返します。これらは実際の user および assistant メッセージなので、そのまま Claude に送信できます。

[4:20] よし、それではサーバーへ移動して、自分たちのプロンプトを組み立ててみましょう。いまここに見えているとおり、内容は「ドキュメントの中身を受け取って、どうにか Markdown 形式で書き直す」ことがすべてです。

[4:30] エディタに戻りました。MCP サーバーのファイルを見つけます。少し下へ行くと、「ドキュメントを Markdown 形式で書き直す」ことについてのコメントがあります。

🖥 [4:32] 画面(エディタ:mcp_server.py・素材の確認) docs 辞書が表示され、8行目 "deposition.md" の説明文 "deposition covers the testimony of Angela Smith," が青く選択されている(プレーンテキストのドキュメント説明文=整形対象の素材を再確認する場面)。パンくずは mcp_server.py > Python > docs

[4:40] その TODO を削除して、MCP prompt を追加します。名前は format、説明は「Rewrites the contents of the document in Markdown format.(ドキュメントの内容を Markdown 形式で書き直す)」。

🖥 [5:00] 画面(エディタ:@mcp.prompt を書き始める) ```python def fetch_doc(doc_id: str) -> str: if doc_id not in docs: raise ValueError(f"Doc with id {doc_id} not found") return docs[doc_id]

@mcp.prom ``@mcp.promまでタイプ中(赤い波線)。下方に# TODO: Write a prompt to summarize a docif name == "main": mcp.run(transport="stdio")` が見える。

[4:56] 次に実装本体を追加します。format_document です。引数として doc id を受け取ります。そして任意で、ここにも Field の説明を追加できます——先ほどツールでやったのと同じですね。「Id of the document to format(整形するドキュメントの ID)」という説明を任意で付けます。さらに型注釈 string も追加して、そこも本当に明確にしておきます。

🖥 [5:30] 画面(エディタ:デコレータ完成・Field() 入力中) python @mcp.prompt( name="format", description="Rewrites the contents of the document in Markdown format." ) def format_document( doc_id=Field() ) Field( の括弧内にカーソル。構文はまだ未完成(赤波線)。

[5:24] この関数からは、メッセージのリストを返すことになります。

[5:32] 先に、この base というものの import を忘れずに先頭へ追加しておきます。既存の MCP server の import のすぐ下に、from mcp.server.fastmcp.prompts import base を追加します。

🖥 [6:00] 画面(エディタ:import 文をタイプ中) ファイル先頭: python from pydantic import Field from mcp.server.fastmcp import FastMCP from mcp.server.fastmcp.prompts import 3行目の import の直後にカーソル(base を打つ直前)。

[5:44] それから下の方に戻って、この中に、非常によくテストされ、非常によく eval されたプロンプトを定義します。プロンプトは事前に書いておきました。このように貼り付けます。

[5:57] このプロンプトは、Claude にドキュメント ID を受け取らせるものです。暗黙のうちに、私たちは Claude に read document ツールを使ってそのドキュメント ID の内容を取得するよう頼んでいる形になります。そしてドキュメントを取得したら、そのまま Markdown 記法で書き直す。最後に、書き直した後で、ドキュメントを edit(編集)して、その更新をサーバーの中に保存することまで指示しています。

🖥 [6:30] 画面(エディタ:プロンプト f-string の完成形) ```python ) def format_document( doc_id: str=Field(description="Id of the document to format"), ) -> list[base.Message]: prompt = f""" Your goal is to reformat a document to be written with markdown syntax.

The id of the document you need to reformat is:
<document_id>
{doc_id}
</document_id>

Add in headers, bullet points, tables, etc as necessary. Feel free to add in ex…
Use the 'edit_document' tool to edit the document. After the document has been…
"""

``` (右端2行は画面外で切れている。後の Inspector 表示から「…Feel free to add in extra text, but don't change the meaning of the report. Use the 'edit_document' tool to …」と続くことが確認できる)

[6:16] さて、このプロンプトを定義したら、次はメッセージのリストを返します。ここで、base.UserMessage を入れたリストを return し、それに今書いたプロンプトを食わせます。このように。

[6:31] ではこのファイルを保存して、MCP development inspector を起動し、そのインターフェースからこのプロンプトをテストしてみましょう。ターミナルで、また同じコマンドを実行します。

🖥 [6:41] 画面(ターミナル:Inspector 起動コマンド) (mcp) → mcp uv run mcp dev mcp_server.py

[6:42] そして、ブラウザの中でそのアドレスへ移動します。

🖥 [6:44〜7:00] 画面(ブラウザ:MCP Inspector v0.10.2) 左ペイン:Transport Type STDIO / Command uv / Arguments run --with mcp mcp run mcp_server.pyRestart / Disconnect ボタン、緑ドット「Connected」。 上部タブ:Resources | Prompts | Tools | Ping | Sampling | Roots。 (直前まで Resources タブに前レッスンの状態——docs://documents リソース、Resource Templates の fetch_doc、report.pdf の読み出し JSON——が残っている)

[6:46] サーバーに接続できていることを確認します。次に Prompts セクションを見つけます。利用可能なプロンプトをすべて一覧表示してみます。現時点で、プロンプトは1つだけ——format だけです。

🖥 [7:01] 画面(MCP Inspector:Prompts タブ) List Prompts / Clear ボタン。一覧に1件:format — Rewrites the contents of the document in Markdown format. 右ペインに「format」の詳細:説明文、doc_id 入力欄(プレースホルダ「Enter doc_id」)。History は 2. prompts/list / 1. initialize

[6:57] そこで format をクリックすると、ここにドキュメント ID を入力しなければなりません。今回は、そうですね——ドキュメント ID は outlook.pdf にしてみましょうか。

🖥 [7:04〜7:09] 画面(エディタに一瞬戻って確認) return [base.UserMessage(prompt)] まで入った保存済みの完成コードと、ファイル先頭の from mcp.server.fastmcp.prompts import base を確認。docs 辞書の outlook.pdf キーが青く選択されている(テストに使う doc id を確かめている場面)。

[7:09] それを入力して、Get Prompt します。すると——これが私たちのメッセージのリストです。

🖥 [7:30] 画面(MCP Inspector:Get Prompt 実行結果) 右ペイン:見出し format / 説明「Rewrites the contents of the document in Markdown format.」/ doc_idoutlook.pdf / 注記「Id of the document to format (Required)」/ 黒い Get Prompt ボタン。 結果 JSON: { messages: [ 0: { role: "user" content: { ... } 2 items } ] } History:4. prompts/get / 3. completion/complete / 2. prompts/list / 1. initialize

[7:15] これらは事前に組み立てられたものです。メッセージパートが1つあります。text パートで、そこに私たちのフルのプロンプトがそのまま入っています。ドキュメント ID がその中に補間されているのが見て取れますね。

🖥 [7:33] 画面(MCP Inspector:content を展開して補間を確認) content を展開すると: ``` content: { type: "text" text: " Your goal is to reformat a document to be written with markdown syntax.

    The id of the document you need to reformat is:
    <document_id>
    outlook.pdf
    </document_id>

    Add in headers, bullet points, tables,
    etc as necessary. Feel free to add in extra
    text, but don't change the meaning of the report.
    Use the 'edit_document' tool t…

``` =f-string に outlook.pdf が補間された実プロンプトが緑色テキストで確認できる。

[7:26] これらのメッセージが手に入ったので、あとは Claude に送り出せます。そうすれば、何かしら適切な応答が返ってくることが期待できます。

[7:32] 改めて繰り返すと、MCP サーバーの中に実装するこれらのプロンプトの背後にある考え方の全体は、私たちが定義するプロンプトがよくテストされ、よく eval され、1つの特定のユースケースに本当に特化したものである、ということなのです。

英語逐語(ASR全文)

The last major focus we're going to have inside of our MCP server is going to be on prompts. Once again, just like we did with resources, we're going to implement a small feature inside of our project, and we're going to use this feature to understand what prompts are all about, just like we did with resources a moment ago. Let me tell you about the feature we're going to add into our program. We're going to add in support for slash commands. So for example, I want to have a format command. I've got some screenshots over here of how it's going to work. Whenever a user types in a slash, we're going to list out some number of commands that are supported by our application. For right now, we're going to have just one command called format. So if I type in just slash, I should see a little autocomplete right here. And the only autocomplete option should be format. If I then select format, I should be prompted to add in some document ID after it. So one of our different document names like report.pdf or whatever else. Then whenever user runs this command, the goal is to get Claude to reformat this document using Markdown syntax. So in other words, take the plain string that we have without any special formatting tied to it inside of each of our documents right now. Remember inside of our MCP server, our current document content is just plain text. We want to feed this into Claude and somehow get Claude to rewrite it using Markdown syntax. So I would expect to see some output like this, something that says i'll help you reformat the document claude is then going to use a tool to read the contents of the document and then finally inside the final response i want to see the content of that document rewritten down here in markdown syntax now there's something interesting about this feature that i want to point out the real core of this feature like the real goal here is to allow a user to reformat a document into markdown syntax and that is an operation that actually doesn't require you and I, the developers to write out any code to implement. What do I mean by that? Well, a user can already launch our CLI and say something like reformat the report.pedia file in Markdown syntax. A user can already do this. No issue whatsoever. And Claude is going to do a reasonable job of it. It's going to take the contents of our document and reformat it into Markdown. And as you can see right here, it worked entirely perfectly. So what are we really doing with this feature? Well, the thought process here is that if we just left this up to users and allowed them to manually type in something like convert this to Markdown, they might get a okay result, but they might get much better result if they had a really strong prompt that is custom tailored for this particular scenario of converting a document into Markdown. So a user might be a lot happier if you and I sat down as the MCP server authors and wrote out and tested and evaled and went through the entire process of developing a really thorough, fantastic prompts, like the one you see on the right-hand side. So again, just repeat, yes, the user can execute this entire workflow on their own, but if they use this fancy prompt over here instead, well, I think they would be all the better. This is the real goal of the prompts feature inside of MCP servers. The thought here is that ahead of time, we can define a set of prompts inside of our server that are custom tailored to whatever our server is really specialized to do. In our case, our server is all about managing documents, reading documents, editing documents, and so on. So we might decide to add in a set of prompts that are very high quality that have been evaled and tested. And we know that they work in a wide variety of different scenarios. We can then expose these prompts for use inside of any client application, like the CLI app that we are putting together right now. Now, one thing I want to point out here is that we could develop this prompt and just put it directly into our CLI code base. That is totally possible. We could do that, obviously. But again, the thought here is that your MCP server that might specialize in some particular task might expose some number of prompts that people can just come and use without having to worry about developing them ahead of time. To define a prompt inside of our MCP server, we're going to write out a little bit of syntax, very similar to the tools and resources we have already put together. We will use the prompt decorator. we'll add a name to the prompt and optionally a description as well then whenever the client asks for this prompt we'll send back a list of messages these are actual user and assistant messages so we can take the messages and send them off to clod directly all right so let's go over to our server and we're going to try putting together our own prompt and just like you see right here it's going to be all about taking the contents of a document and somehow rewriting it in markdown format okay So back inside my editor, I'm going to find my MCP server file. I'm going to go down a little bit to the comment about rewriting a document in Markdown format. I'll delete that to do, and then I'll add in a MCP prompt, the name of format, and a description of rewrites the contents of the document in Markdown format. I'll then add in an actual implementation so format document I'm going to receive as an argument a doc id and then optionally we can add in a field description here as well just like we did with our tool earlier on so I can optionally add in a field with a description of id of the document to format and I'm also going to add in a type annotation of string just to make sure that's really clear as well. From this function, we are going to return a list of messages. I'm going to make sure I add in an import for this base thing at the top right away. So right underneath the existing MCP server import, I will add in from MCP server, fast MCP prompts, import base. Then back down at the bottom inside of here, we're going to define our very well tested, very well evaled prompt. I wrote a prompt out ahead of time. I'm going to paste it in like so. So this prompt is just asking Claude to take in a document ID. Implicitly, we are kind of asking Claude to fetch the document ID's contents using the read document tool. And then after getting that document, just go ahead and rewrite it with markdown syntax. And finally, after rewriting it, edit the document as well to save those updates inside of our server. now after defining this prompt we're then going to return a list of messages so down here i'm going to return a list with base user message and i'm going to feed in our prompt that we just wrote out to it like so now i'm going to save this file and then let's go start up our mcp development inspector and test out this prompt from that interface so at my terminal i'll run that same command again, and then navigate to that address inside my browser. I'll make sure I connect to my server. I'll then find the prompts section. I'm going to list out all the different prompts that are available to us. And at this point in time, we have one prompt, just format. So I'll click on format and then I have to enter in a document ID right here. Let's this time around, maybe we'll put it in a document ID of how about a outlook.pdf. So I'll put that in and then get prompt. And then here is our list of messages. So these have been put together ahead of time. I've got one message part here. So a text part with our full prompt right there. We could see that the document ID was interpolated into it. Now that we have these messages, we can send them off to Claude. And hopefully we're going to get back some appropriate kind of response. So once again, the entire idea here behind these prompts we might implement inside of our MCP server is that the prompts we are defining are going to be well tested, well evaled, really specialized to one particular use case.

↑ 目次へ戻る

Building with the Claude API | Lesson 63「Prompts in the client」(全文ナレッジ)

動画 3分02秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04

日本語ナレッジ(要点整理)

このレッスンのテーマ

前レッスンで MCP サーバー側に定義した prompt(format)を、MCP クライアント側から使えるようにする。実装するのは2つのメソッドだけ——①サーバーに定義された全プロンプトの一覧を取る list_prompts()②特定のプロンプトを引数(変数)補間つきで取得する get_prompt()。実装後、CLI で /format plan.md を実行して動作確認し、プロンプト機能の全体ワークフローを総括する。

クライアント実装(mcp_client.py・各2行)

  • list_promptspython async def list_prompts(self) -> list[types.Prompt]: result = await self.session().list_prompts() return result.prompts
  • get_promptpython async def get_prompt(self, prompt_name, args: dict[str, str]): result = await self.session().get_prompt(prompt_name, args) return result.messages
  • get_prompt に渡す args 辞書のキーは、サーバー側プロンプト関数の引数名と対応する。例:format_document(doc_id=...) に対しては args に doc_id キーが入っている想定で、その値が該当関数にキーワード引数として渡され、プロンプト本文に補間(interpolate)される。
  • 返り値 result.messagesそのまま Claude に投げられる会話(user/assistant メッセージ列)

CLI での動作確認(/format plan.md)

  1. uv run main.py で CLI 再起動 → / を打つと format コマンドがオートコンプリートに出る(format はプロンプト名そのもの)。
  2. format を選んでスペース → ドキュメント候補(deposition.md / report.pdf / financials.docx / outlook.pdf / plan.md / spec.txt)から plan.md を選択して Enter。
  3. これでプロンプト全体(実体は user メッセージ1つ)がそのまま Claude に投入される。Claude は「ドキュメントを Markdown に整形せよ」という指示と対象ドキュメント ID を受け取った状態。
  4. Claude はまず read/get document ツールで plan.md の中身を取得し、最後に Markdown 版のドキュメントを応答する。実際の出力には # Project Implementation Plan 以下、##見出し・箇条書き・Markdown テーブル(Resource Allocation)まで含まれていた。

プロンプト機能の総括(ワークフロー4段階)

  1. 書いて eval する:MCP サーバーの目的に関連したプロンプトを書き、評価・テストする(ドキュメントサーバーなら「文書を別スタイルに書き直す」機能は理にかなう)。
  2. サーバーに定義@mcp.prompt でプロンプトを定義する。
  3. クライアントがいつでも要求:client は任意のタイミングでそのプロンプトを要求できる。
  4. 引数が補間される:要求時に渡した引数がプロンプト関数にキーワード引数として供給され、関数はそれをプロンプト本文の中で利用する。

実務ポイント(このレッスンから持ち帰るもの)

  • クライアント側のプロンプト対応は実質4行session.list_prompts().promptssession.get_prompt(name, args).messages。SDK が一覧取得・引数補間・メッセージ組み立てを全部やってくれる。
  • 契約は「args のキー=サーバー側プロンプト関数の引数名」。クライアントは {"doc_id": "plan.md"} のような辞書を渡すだけで、サーバー側でテンプレートに補間される。キー名の不一致が典型的なバグポイント。
  • get_prompt が返すのは完成済みメッセージ列。アプリ側で文字列連結せず、返ってきた messages をそのまま会話履歴に入れて Claude に送るのが正しい使い方。
  • スラッシュコマンド UI とプロンプト名を対応させる設計(/format = prompt name "format")はそのまま流用できる定石。コマンド一覧=list_prompts()、引数候補=リソース(ドキュメント一覧)で出す、という前レッスンからの合わせ技。
  • 動作確認は「①コマンドが補完に出る → ②引数候補が出る → ③ツールで文書取得 → ④Markdown 出力」の4点をチェック。出力に見出し・箇条書き・表が入っていれば、プロンプト内の書式指示まで効いている証拠。
  • 総括の4段階(write/eval → define → request → interpolate)はプロンプト機能を人に説明するときの最短フレーム。「よくテストされ、evalされ、特定ユースケースに特化したプロンプトの配布チャネル」が MCP prompts の本質。

本文(時系列・日本語逐語+画面差し込み)

[0:00] 最後の大きなタスクは、MCP クライアントの中に機能を実装することです。MCP サーバーに定義されているすべてのプロンプトを一覧できるようにし、さらに、変数が補間された特定のプロンプトを取得できるようにします。

🖥 [0:00〜0:30] 画面(エディタ:mcp_client.py・実装前) VS Code 風エディタ。タイトル「mcp_client.py — mcp」。サイドバーに core / .env / .gitignore / main.py / mcp_client.py / mcp_server.py / README.mdMCPClient クラス内の2つの TODO メソッドが見えている(list_prompts の TODO 部分をドラッグ選択): ```python async def call_tool( self, tool_name: str, tool_input: dict ) -> types.CallToolResult | None: return await self.session().call_tool(tool_name, tool_input)

async def list_prompts(self) -> list[types.Prompt]: # TODO: Return a list of prompts defined by the MCP server return []

async def get_prompt(self, prompt_name, args: dict[str, str]): # TODO: Get a particular prompt defined by the MCP server return [] ```

[0:08] それではまず、list prompts から実装しましょう。コメントを削除して、こう置き換えます——result = await self.session().list_prompts()

[0:21] そして result.prompts を return します。これでほぼ完成です。

🖥 [0:53] 画面(エディタ:mcp_client.py・list_prompts 実装済み) mcp_server.py のタブも開いた状態。list_prompts が実装済みに: python async def list_prompts(self) -> list[types.Prompt]: result = await self.session().list_prompts() return result.prompts get_prompt はまだ TODO のまま。

[0:27] 次は get prompt です。はっきりさせておくと、個別のプロンプトを取得するとき、私たちはいくつかの引数(arguments)を渡されます。これらの引数は、最終的にプロンプト関数の中に現れます。

[0:38] たとえば、この format document の中では、document ID を受け取ることを期待していますね。この args 辞書の中には document ID のキーが入っているはず、というのが期待で、それがこちらの該当する関数に渡されます。そしてその値が、プロンプト本文の中に補間されるわけです。

🖥 [1:02] 画面(エディタ:mcp_server.py・format プロンプト定義を再確認) 前レッスンで定義したサーバー側プロンプト。doc_id 引数がハイライトされている: ```python @mcp.prompt( name="format", description="Rewrites the contents of the document in Markdown format.", ) def format_document( doc_id: str = Field(description="Id of the document to format"), ) -> list[base.Message]: prompt = f""" Your goal is to reformat a document to be written with markdown syntax.

The id of the document you need to reformat is:
<document_id>
{doc_id}
</document_id>

``` (下方に「Add in headers, bullet points, tables, etc as necessary. …」「Use the 'edit_document' tool to edit the document. …」の行が続く)

[0:56] それでは get prompt 関数の中です。self.session().get_prompt(...) から result を受け取ります。プロンプト名を渡します——これは取得したいプロンプトの名前です。

[1:08] それから引数(arguments)を渡し、そして result.messages を return します。

🖥 [1:30] 画面(エディタ:mcp_client.py・get_prompt 実装済み) python async def get_prompt(self, prompt_name, args: dict[str, str]): result = await self.session().get_prompt(prompt_name, args) return result.messages

[1:13] これが返ってくるメッセージたちです。これらは何らかの会話を形成していて、私たちはそれをそのまま Claude に直接投入したいのです。

[1:19] 以上です。クライアント側でやるべきことはこれだけです。

[1:22] これで、CLI 本体の中でテストできます。画面を切り替えて、プロジェクトをもう一度実行します。

🖥 [1:43] 画面(ターミナル:MCP まわりの起動ログ→クリア) iTerm に既存プロセスのログ: Stdio transport: command=/opt/homebrew/bin/uv, args=run,--with,mcp,mcp,run,mcp_server.py Spawned stdio transport Connected MCP client to backing server transport Created web app transport Created web app transport Set up MCP proxy Received message for sessionId a3f4e9c8-8544-4d4d-8e82-2644d4ee3967 直後に画面をクリアし、プロンプト (mcp) → mcp だけの状態に。

[1:30] さて、ここでスラッシュを入力すると、この format コマンドにアクセスできるのが分かります。

[1:35] format というのは、実のところ、これから呼び出すプロンプトの名前そのものです。

[1:39] これを選択してスペースを押すと、今度は複数あるドキュメントの中から1つを選ぶよう求められます。plan.md でいきましょう。Enter を押します。

🖥 [2:00] 画面(ターミナル:CLI デモ・/format とオートコンプリート) ``` (mcp) → mcp uv run main.py

/format plan.md 入力行の下にドキュメント候補のドロップダウン: deposition.md report.pdf financials.docx outlook.pdf plan.md ← ハイライト選択中 spec.txt ```

[1:48] これで、あのプロンプト全体——実際にはたった1つの user メッセージですが——を、そのまま Claude に直接投入しています。

[1:54] つまり Claude はいま、ドキュメントを Markdown 記法に整形しに行くための指示を持っており、さらに、整形してほしいドキュメントの ID も与えられています。

[2:04] そこで Claude が最初にやるべきことは、そのドキュメントの中身を取りに行くことです。get document ツールを使ってそれを行います。

[2:10] そして最後に、Claude はこのドキュメントの Markdown 版を応答してくれます。

[2:15] はい、これが Markdown 記法がたっぷり入ったドキュメントです。

🖥 [2:15] 画面(ターミナル:Claude の応答=Markdown 整形結果・冒頭) ```

/format plan.md I'll help you reformat the document with markdown syntax. Let me first read the current content of the document to understand what I'm working with. Now I'll reformat the document with markdown syntax to enhance its presentation:

Response:

Project Implementation Plan

Overview

The plan outlines the steps for the project's implementation. This document serves as a roadmap for all team members to follow throughout the project lifecycle.

Implementation Phases

```

🖥 [2:16〜2:19] 画面(ターミナル:スクロールで整形結果の全体を確認) ## Implementation Phases 以下に5フェーズの見出し+箇条書きが続く: ```

Phase 1: Planning & Requirements Gathering

  • Requirements Analysis: Identify all functional and non-functional requirements
  • Scope Definition: Clearly define project boundaries and deliverables
  • Timeline Development: Create detailed project timeline with milestones

Phase 2: Design & Architecture

  • System Architecture: Develop the technical blueprint
  • UI/UX Design: Create wireframes and user experience flows
  • Data Modeling: Design database schemas and relationships

Phase 3: Development

  • Environment Setup: Configure development, testing, and production environments
  • Core Functionality: Implement primary features
  • Integration Points: Develop connections to external systems

Phase 4: Testing

  • Unit Testing: Verify individual components
  • Integration Testing: Ensure components work together
  • User Acceptance Testing: Validate with stakeholders

Phase 5: Deployment & Maintenance

  • Deployment Strategy: Plan for smooth transition to production
  • Training: Prepare users for the new system さらに **Markdown テーブル**(プロンプトの「tables も足してよい」指示が効いている):

Resource Allocation

Resource Type Allocation Timeline
Development Team 5 FTE Phases 1-5
QA Engineers 2 FTE Phases 3-4
Project Management 1 FTE All Phases
Infrastructure Cloud-based Continuous

Risk Management

  • Identified Risks:
  • Timeline delays
  • Scope creep
  • Technical challenges
  • Resource constraints

  • Mitigation Strategies: ```

[2:20] さて、問題なく動いたようですから、プロンプトについて手短に総括して、これが何なのかをしっかり理解しておきましょう。

[2:24] まず、自分の MCP サーバーの目的に関連性のあるプロンプトを書いて eval するところから始めます。私たちの場合はドキュメントサーバーを作っていたので、ドキュメントを別のスタイルに書き直す機能というのは、まあ理にかなっていると思います。

[2:42] プロンプトがまとまったら、MCP サーバーの中にプロンプトとして定義します。そうすれば、クライアントはいつでもそのプロンプトを要求できます

[2:50] プロンプトを要求するときには、いくつかの引数を渡します。それがこのプロンプト関数にキーワード引数として供給され、関数はそのキーワード引数をプロンプト本文の中で利用できる、というわけです。

🖥 [2:53〜3:00] 画面(遷移→まとめスライド「Prompts」) ターミナルが左へスライドアウトし、白背景のスライドへ遷移。 タイトル:Prompts - Defines a set of User and Assistant messages that can be used by the client - These prompts should be high quality, well-tested, and relevant to the overall purpose of the MCP

右側にグレーのカード「MCP Server」、内側にラベル Prompt と黒いコードブロック: python @mcp.prompt( name="format", description="Rewrites the contents of a document in Markdown format", ) def format_document( doc_id: str, ) -> list[base.Message]: # Return a list of messages 左下に ANTHROP\C ロゴ。

英語逐語(ASR全文)

our last major task is to implement some functionality inside of our mcp client and allow us to list out all the different prompts that are defined inside the mcp server and also get a particular prompt with some variables interpolated into it so let's first implement list prompts i will delete the comment and replace it with a result is a weight self session list prompts and then i will return result dot prompts and that's pretty much it And then get prompt. Now, to be clear, when we get a individual prompt, we're going to be given some number of arguments. These arguments will eventually show up inside of our prompt function. So for example, inside a format document right here, we expect to receive a document ID. Inside of this arcs dictionary, the expectation is that there will be a document ID key, and that will be passed in to the appropriate function over here. And then we will get that value interpolated into the prompt itself. So inside the get prompt function, I will get a result from self session, get prompt. I'm going to pass in the prompt name. That's the name of the prompt I want to retrieve. And then I'll pass in the arguments and then I will return result dot messages. So those are the messages coming back. They form some kind of conversation that we want to feed directly into Claude. And that's it. That's all we have to do for our client. So now we can test this out inside of the CLI itself. I'll flip back over, run the project again. And now if I put in a slash right here, I'll see that I can access this format command. Now, format is really just the name of the prompt that we're going to invoke. So if I select that and then hit space, I'll then be asked to select one of the different documents. And you'll go with plan.md. I'll hit enter. And then we're taking that entire prompt, really just that single user message, and feeding it directly into CLOT. So Claude now has the instructions to go and reformat a document into Markdown syntax. And it has also been given the ID of the document that we want to reformat. So the first thing it needs to do here is go and fetch that document's contents. And it will do so by using the Git document tool. And then finally, Claude is going to respond with the Markdown version of this document. So here is the document with a bunch of Markdown syntax inside of it. All right, since it looked like this worked just fine, let's do a quick recap on prompts and make sure we understand what they are all about. We begin by writing out and evaling a prompt that has some relevancy to our MCP server's purpose. In our case, we were making a document server. So having some functionality or something about rewriting a document in a different style, I think it kind of makes sense. Once we have put our prompt together, we'll define a prompt inside the MCP server. And then our client can ask for that prompt at any point in time. When we ask for the prompt, we will put in some number of arguments that will be provided to this. Prompting function right here as keyword arguments, and then our function can make use of those keyword arguments inside the prompt itself.

↑ 目次へ戻る

Building with the Claude API | Lesson 64「MCP review」(全文ナレッジ)

動画 4分12秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04

日本語ナレッジ(要点整理)

このレッスンのテーマ

MCP のプロジェクト(ドキュメント管理 CLI + MCP サーバー/クライアント)が完成したところで、学んだ3つのサーバープリミティブ——tools(ツール)・resources(リソース)・prompts(プロンプト)——を総復習する。切り口は「アプリの中で誰がそれを実行するのか(誰のためのものか)」。

3プリミティブの「制御主体」対比(本レッスンの核)

プリミティブ 制御主体 意味 主な用途
Tools モデル制御(model controlled) いつツールを実行するかは Claude だけが判断する。結果を使うのも Claude Claude に追加の能力を与える
Resources アプリ制御(app controlled) アプリ内のコードが「このデータが必要だ」と判断してリソースを実行し、返ってきたデータを UI 表示などに使う アプリにデータを取り込む・メッセージに文脈を足す
Prompts ユーザー制御(user controlled) プロンプトをいつ走らせるかはユーザーが決める。ボタンやメニューのクリック、スラッシュコマンドなどで起動 ユーザー入力起点の定型ワークフロー
  • 本プロジェクトでの実例:リソースは「オートコンプリート候補一覧の表示」と「@メンションでのプロンプト増強」に使った——どちらも開発者が書いたアプリ側コードが実行主体。プロンプトはスラッシュコマンドで起動した——実行主体はユーザー。
  • 制御主体を意識する理由=各プリミティブの目的が見えてくるから:
  • Claude に能力を足したい → tools(サーバー側で実装 or クライアントで他サーバーのツールを消費)
  • UI 表示などのためにアプリへデータを入れたい → resources
  • 事前定義されたワークフローを提供したい → prompts

claude.ai に見る3プリミティブの実例

公式の Claude インターフェース(claude.ai)に、3つの考え方がすべて観察できる: 1. プロンプト(ユーザー制御):チャット入力欄の下のボタン(Write / Learn / Code / Career chat…)。ボタン→例文クリックで即チャットが開始される。これはユーザーが起動を決めたワークフローで、裏には事前に書かれ最適化済みのプロンプトがあるはず。このボタン群を自分で実装するなら=MCP サーバーに一連の prompts を定義する。 2. リソース(アプリ制御):+ボタン内の「Add from Google Drive」。クリックすると自分のドキュメント一覧が出て、選ぶとその内容がチャットのコンテキストに注入される。どのドキュメントを一覧に描画するか・選択時に内容を注入する処理はすべてアプリ側コード=resource で実装するのが筋。 3. ツール(モデル制御):チャットに「what is square root of three, use JavaScript to calculate the value」と送ると、Claude が JavaScript 実行ツールを使うと自分で判断する。100% モデル制御=tool で実装する。

まとめの一行

tools はモデルに、resources はアプリに、prompts はユーザーに仕える。これは高レベルの指針(厳密なルールではない)だが、何を作るかに応じてどのプリミティブを選ぶかの判断軸になる。

実務ポイント(このレッスンから持ち帰るもの)

  • MCP サーバー設計の第一問は「この機能の実行を決めるのは誰か?」。モデルが判断→ tool / アプリのコードが判断→ resource / ユーザーの操作が起点→ prompt。この3択で迷いがほぼ消える。
  • tools=Claude への能力付与。「計算して」「実行して」系のように、使うかどうかの判断ごと Claude に委ねたい機能はツール化する。
  • resources=アプリへのデータ供給。オートコンプリート候補・ファイル一覧・コンテキスト注入のような UI/アプリ都合のデータ取得にモデルの判断を挟まない。リソースを叩くのは自分が書いたアプリコード。
  • prompts=ユーザー起動の定型ワークフロー。スラッシュコマンド・ボタン・メニューの裏に、事前にテスト/eval 済みの高品質プロンプトを置く。
  • 既製品の分解観察が設計練習になる:claude.ai の「入力欄下のボタン=prompts」「Add from Google Drive=resource」「JS で計算して=tool」のように、目の前のAIアプリのUIを3プリミティブに逆引きすると、自作サーバーで何を実装すべきかが見える。
  • 「tools→モデル、resources→アプリ、prompts→ユーザー」は厳密な規則ではなく高レベル指針。境界事例では柔軟に判断してよい、という留保も講師自身が付けている。

本文(時系列・日本語逐語+画面差し込み)

[0:00] プロジェクトはすべて完成しました。ですが先へ進む前に、学んできた3つのサーバープリミティブ——つまり tools、resources、prompts——を手短に総復習しておきたいと思います。

🖥 [0:00] 画面(スライド「MCP Server Primitives」・p.48) クリーム系3色のカードが横並び(このスライドが前半 [0:00]〜[1:55] ごろまで表示され続ける): - ToolsModel-controlled: Claude decides when to call these. Results are used by Claude Used for: Giving additional functionality to Claude - Resources — *App-controlled: Our app decides when to call these. Results are used primarily by our app.* Used for: Getting data into our app / Adding context to messages - Prompts — *User-controlled: The user* decides when to use these. Used for: Workflows to run based on user input, like a slash command, button click, or menu option

左下に ANTHROP\C ロゴ、右下にページ番号 48。

[0:09] 特に強調したいのは、それぞれについての興味深い点、すなわちアプリのどの部分が実際にそれを実行する責任を持つのかです。言い換えると、典型的なアプリケーションの中で、これらを実際に動かしているのは誰で、その恩恵を受けるのは誰なのか、ということです。

[0:18] まず、tools はモデル制御(model controlled)だと言えます。つまり、いつツールを実行するかを決める責任は、実質的に Claude だけが持っています。

[0:29] resources はアプリ制御(app controlled)です。言い換えると、あなたのアプリの中で動いている何らかのコードが、「リソースが提供するデータが必要だ」と判断します。リソースを実行し、返ってきたデータを何らかの形で——たとえば UI でそのデータを使うなどして——活用すると決めるのは、あなたのアプリのコードです。

[0:51] 私たちのプロジェクトの場合では、リソースを取得して、そのデータを UI の中でオートコンプリート候補の一覧を出すために使いました。また、プロンプトを増強する(コンテキストを注入する)ためにもリソースを取得しました。この2つはどちらも、あなたと私=開発者が書いた、まさにアプリケーション側のコードでした。

[1:02] そして最後に、prompts は本質的にユーザー制御(user controlled)です。つまり、プロンプトをいつ走らせるかはユーザーが決めます。ユーザーは、ボタンやメニューオプションのような UI 要素をクリックすることでプロンプトの起動を開始するかもしれませんし、スラッシュコマンドを使うかもしれません——私たちがやったのはこれですね。

[1:20] それぞれを何が制御しているのかを強調する理由は、それぞれの目的のイメージを掴んでもらうためです。

[1:25] もし Claude に能力を追加する必要があるなら、おそらく MCP サーバーの中にツールを実装するか、MCP クライアントを通じて他のサーバーのツールを消費することを検討するはずです。

[1:37] UI にコンテンツを表示する目的などでアプリにデータを取り込みたいなら、おそらくリソースを使いたいはずです。

[1:45] そして、何らかの事前定義されたワークフローを実装したいなら、おそらくプロンプトを検討すべきです。

[1:52] さて、これらの考え方の実例はすべて、公式の Claude インターフェース、claude.ai の中に見ることができます。私の環境では今こんな見た目になっています。

🖥 [1:58] 画面(Chrome:claude.ai/new) ブラウザで claude.ai/new を開いた状態。中央に「Back at it, Stephen」の挨拶と入力ボックス「How can I help you today?」。ボックス内に+ボタン・設定アイコン・「Research BETA」ボタン、右側にモデル表示「Claude 3.7 Sonnet」と送信ボタン。 入力欄の下にボタン列:Write / Learn / Code / Career chat / Connect apps。 (タブバーには講師の資料タブが多数:「000 - Modul…」×7、「000 - Featur…」「000 - Agent…」「000 - MCP -」「Claude」「MCP Inspec…」)

[2:01] メインのチャット入力欄の下に、いくつかボタンがあるのに気づくでしょう。この1つをクリックし、さらに例文の1つをクリックすると、すぐにチャットに入るのが分かります。

🖥 [2:22] 画面(claude.ai:例文クリックで生成されたチャット) チャットタイトル「Comparing Your Writing Style to Famous Authors」。ユーザー(SG)の吹き出しには事前に書かれたプロンプトが丸ごと入っている: "Hi Claude! Could you compare my writing style to famous authors? If you need more information from me, ask me 1-2 key questions right away. If you think I should upload any documents that would help you do a better job, let me know. You can use the tools you have access to — like Google Drive, web search, etc. — if they'll help you better accomplish this task. Do not use analysis tool. Please keep your responses friendly, brief and conversational." "Please execute the task as soon as you can - an artifact would be great if it makes sense. If using an artifact, consider what kind of artifact (interactive, visual, checklist, etc.) might be most helpful for this specific task. Thanks for your help!" Claude の応答:「Hi there! I'd be happy to compare your writing style to famous authors. To do this effectively, I'll need to see some samples of your writing. …」と、文章サンプルの共有・アップロードを求める内容([2:31] ごろ左端に「戻る」矢印が出て前の画面へ戻る)。

[2:12] つまりこれはユーザー制御のアクションでした。ユーザーである私が、この特定のワークフローを開始すると決めたのです。そして私が使っているのは、おそらく事前に書かれ、何らかの形で最適化されているプロンプトです。ですから、あそこにあるボタンの一覧を実装するなら、私たちはおそらく MCP サーバーの中に一連の異なるプロンプトを用意することになるでしょう。

[2:32] 同様に、戻って、この小さなタブ——+ボタン——をクリックしてみると、「Add from Google Drive」というボタンがあるのに気づくはずです。

🖥 [3:00] 画面(claude.ai:+ボタンのメニュー) 入力ボックスの+ボタンを押して開いたメニュー: - 🔍 Search menu - 📎 Upload a file - 📷 Take a screenshot - 🟢 Add from Google Drive →(カーソルがここに) - 🗂 Use a project →

[2:38] 今はクリックしません。私の内部ドキュメントがいくつか表示されてしまうので。ただ、このボタンをクリックすると、このチャットにコンテキストとして追加できるドキュメントの一覧が表示されます。その一覧に実際にどのドキュメントを描画するかを知ること、そしてどれかをクリックしたときにその内容をこのチャットのコンテキストへ自動的に注入すること——これはすべてアプリケーション側のコードです。

[3:01] つまり、ここに描画すべきドキュメントの一覧を知る必要があるのは、純粋にアプリケーションだけです。そしてこれは、まさに UI 関連の要素です。ですから、Google Drive からのドキュメント一覧表示を実装するなら、私はおそらく MCP サーバーの中にリソースを実装することを検討します。

[3:13] そして最後に、このチャットに「what is square root of three, use JavaScript to calculate the value(3の平方根はいくつか、JavaScript を使って値を計算して)」のようなメッセージを入力して送信するとしましょう。

🖥 [3:30] 画面(claude.ai:新規チャット画面) 「Back at it, Stephen」のホーム画面に戻り、入力ボックスにカーソルがある状態(平方根の質問を打ち込む直前の場面)。

[3:22] 私は明らかに、Claude が何らかの方法で JavaScript コードを実行することを期待しています。それはおそらくツールの使用を通じて行われるでしょう。この場合、ツールを使うという判断は 100% モデル制御でした。JavaScript ツールの実行を使うと決めたのはモデルです。こういうものを MCP サーバーの中で実装するなら——もうお分かりですね——ツールを提供することになるでしょう。

[3:44] というわけで、以上が3つのサーバープリミティブでした。

🖥 [3:45] 画面(Google Slides → 全画面スライド) 一瞬 Google Slides の編集画面(docs.google.com/presentation…、タブ「000 - MCP -」)越しに「MCP Server Primitives」のスライドが映り、直後に再び全画面表示に戻る(内容は冒頭 [0:00] と同じ3カードの対比スライド・p.48)。

[3:48] そして、それぞれが実質的に、アプリ全体の異なる部分によって使われることを意図しています。つまり、tools は概してモデルに仕え、resources は概してアプリに仕え、prompts はユーザーに仕えるのです。

[4:02] 繰り返しになりますが、これらは高レベルの指針です。これに触れた唯一の理由は、何を作ろうとしているかに応じて、それぞれのプリミティブをいつ使うべきかの感覚を掴んでもらうためです。

英語逐語(ASR全文)

We are all done with our project, but before we move on, I want to do a quick recap on the three server primitives that we learned about. So tools, resources, and prompts. In particular, I want to highlight something interesting about each of these, namely what part of an app is really responsible for running each. In other words, in a typical application, who is really running each of these things and who benefits from them? Well, we would say that tools are model controlled. This means that Claude alone is really responsible for deciding when to run a tool. Resources are app controlled. In other words, some code running inside of your app is going to decide that it needs some data provided by a resource. It will be your app's code that decides to execute a resource and use the return data in some way, maybe by using that data in the UI or something like that. In our case, we fetch a resource and then use that data inside the UI to provide a list of autocomplete options. We also fetch a resource to augment a prompt. Both those things were really application related code that was authored by you and I to put together. And finally, prompts are really user controlled. So a user decides when a prompt is going to run. A user might start the invocation of a prompt by clicking on some UI element, like a button or a menu option, or they might make use of a slash command, which is what we did. The reason I highlight what is controlling each of these is to give you some idea of their purpose. So if you ever need to add capabilities to Claude, you're probably going to want to look at implementing some tools inside of your MCP server or consuming some server's tools through your MCP client. If you ever want to get some data into your app for the purposes of showing content in the UI or something similar, then you probably want to use a resource. And if you ever want to implement some kind of predefined workflow, you probably want to look at prompts. Now you can see examples of all these ideas inside of the official Claude interface at Claude.ai. So here's what it currently looks like for me. You'll notice that underneath the main chat input are some buttons right here. If I click on one and then click on one of these examples, you'll see that I immediately dive into a chat. So this was a user-controlled action. I, as the user, decided to start up this particular workflow. and I'm making use of a prompt that was probably already written ahead of time and probably has been optimized in some way. So to implement that list of buttons right there, we would probably want to put together a series of different prompts inside of a MCP server. Likewise, if I go back and maybe click on this little tab right here, the plus button, you'll notice that I have a add from Google drive button. Now I'm not going to click on it because it's going to show some of my internal documents, but if I click on that button, I'm going to see some documents that I can add into this chat as some context. Knowing what documents to actually render in that list, and then whenever I click on one, automatically injecting its contents into the context of this chat, that is all application-related code. So it is solely the application that needs to know the list of documents to render here. And that's, again, specifically UI-related elements. So to implement that listing of documents from Google Drive, I would probably look at implementing a resource inside of a MCP server. And then finally, if I enter in a message to this chat of something like what is square root of three, use JavaScript to calculate the value and send it off. I'm clearly expecting Claude to somehow execute some JavaScript code, which would likely be done through the use of a tool. In this case, the decision to use a tool was 100% model controlled. It is the model that decided to use some JavaScript tool execution. To implement something like this inside of a MCP server, we'd likely want to, you guessed it, provide a tool. So in total, that's our three different server primitives. And each one is really intended to be used by a different portion of your overall application. So we've got tools, which are generally going to serve your model, resources, which are generally going to serve your app, and prompts, which are going to serve your users. And once again, these are high-level guidelines, and the only reason I mention them is to just give you a sense of when you should use each of these primitives, depending upon what you are trying to put together.

↑ 目次へ戻る

Building with the Claude API | Lesson 65「Anthropic apps」(全文ナレッジ)

動画 0分43秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04

日本語ナレッジ(要点整理)

このレッスンのテーマ

新モジュール「Anthropic apps」の導入。Anthropic 自身が作って世に出している2つのアプリケーション——Claude Code と Computer Use——を題材に学ぶモジュールの全体像を 43 秒で予告する。

取り上げる2つのアプリ

  • Claude Code:ターミナルで動くコーディングアシスタント。このモジュールではセットアップ手順を一緒に進め、小さなサンプルプロジェクト上で実際に使いながら「中でどう動いているのか」を理解する。
  • Computer Use:Claude の能力を劇的に拡張するツール群。Claude がデスクトップ環境そのものを操作(Web サイトへのアクセス、アプリの操作など)できるようになる。

なぜこの2つを扱うのか(本当の狙い)

  • Claude Code も Computer Use もそれ自体が非常に有用だが、扱う理由はもう1つある。
  • 両者は「エージェント」の完璧な実例だから。この2つの動作原理を理解すれば、エージェントとは何か・効果的なエージェントをどう作るかを理解する準備が格段に整う。
  • スライドの学習プラン:Claude Code → Computer Use → Agents! の3段構え。最後の問いは「Claude Code と Computer Use を成功させているものは何か?」。

次のビデオ

まず Claude Code のセットアップ手順から始める。

実務ポイント(このレッスンから持ち帰るもの)

  • このモジュールの読み方:Claude Code / Computer Use は「製品紹介」ではなく「エージェント設計の教材」。個別機能よりも「なぜこれが上手く機能するのか」という視点で追うと後半の Agents 章(エージェント設計論)に直結する。
  • Claude Code=ターミナル常駐のエージェント型コーディングアシスタント(ファイル編集・バグ修正・質問応答など)。Computer Use=Claude にデスクトップ操作能力を与えるツールセット(Web サイトアクセス・アプリ操作など)。役割の違い(アプリ vs ツール群)をまず区別して覚える。
  • 学習順序は ①Claude Code を触る → ②Computer Use を見る → ③両者の共通構造からエージェントの一般論を抽出。自作エージェントを設計するときも「動く実例を先に観察 → 原理を抽出」の順が有効、というコース設計自体が実務の示唆になっている。

本文(時系列・日本語逐語+画面差し込み)

[0:00] このモジュールでは、Anthropic が構築してデプロイした2つのアプリケーションを見ていきます。Claude Code と Computer Use です。

🖥 [0:04] 画面(スライド「Our Plan」) クリーム色の背景に3段の矢印型ロードマップ:Claude Code → Computer Use → Agents! その下に3つのカード: - Investigate Claude Code - Agentic coding assistant made by Anthropic - Runs in the terminal - Edit files, fix bugs, answer questions, etc. - Investigate Claude's Computer Use - Set of tools that allow Claude to interact with a desktop computer environment - Access websites, interact with apps, etc - Investigate Agents - What makes Claude Code and Computer Use successful?

左下に ANTHROP\C ロゴ。(このスライドが動画全編を通して表示される)

[0:07] Claude Code は、ターミナルベースのコーディングアシスタントです。

[0:09] セットアップの手順を一緒に進めたあと、ごく小さなサンプルプロジェクトの上で Claude Code を使い、これが正確にどう動くのかを理解していきます。

[0:16] そのあとで Computer Use を見ていきます。

[0:18] Computer Use は、Claude の能力を劇的に拡張するツールのセットです。

[0:23] Claude Code も Computer Use も、それ単体で非常に有用なものですが、この2つを取り上げるのにはもう1つ理由があります。

[0:28] というのも、Claude Code と Computer Use はエージェントの完璧な実例なのです。この2つがどう動いているのかを理解すれば、エージェントとは何か、そして効果的なエージェントをどう構築するかを理解するための準備が、はるかに整った状態になります。

[0:38] それでは、次のビデオで Claude Code のセットアップ手順から始めましょう。

英語逐語(ASR全文)

In this module, we are going to take a look at two applications that have been built and deployed by Anthropic. They are CloudCode and ComputerUse. CloudCode is a terminal-based coding assistant. I'm going to help you go through the setup process, and then we're going to use CloudCode on a very small sample project to understand exactly how it works. After that, we're going to take a look at ComputerUse. ComputerUse is a set of tools that dramatically expands Cloud's capabilities. Both CloudCode and ComputerUse are extremely useful in their own right, but there's another reason that we're going to discuss them. You see, Cloud Code and computer use are perfect examples of agents, and once we understand how they work, we'll be much better prepared to understand what agents are and how to build an effective agent. So let's go through the setup process of Cloud Code in the next video.

(※ ASR は「CloudCode/ComputerUse/Cloud」と誤認識しているが、実際の発話・画面表記は「Claude Code/Computer Use/Claude」)

↑ 目次へ戻る

Building with the Claude API | Lesson 66「Claude Code setup」(全文ナレッジ)

動画 1分32秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04

日本語ナレッジ(要点整理)

このレッスンのテーマ

Claude Code 章の導入回。Claude Code とは何か(ターミナルベースのコーディングアシスタント)を1枚のスライドで押さえ、3ステップのセットアップ手順(Node.js → npm install → claude 実行でログイン)を案内する。次のレッスンから小さなプロジェクトを使った実践に入るため、視聴者は各自ここでインストールを済ませておく回。

Claude Code とは

  • ターミナルの中で動くコーディングアシスタント。コード関連の多種多様なタスクを手伝うプログラム。
  • コーディングを支援するために多くのツールを内蔵している:
  • 基本ツール:ファイルの検索(Search)・読み取り(Read)・編集(Edit)
  • 高度なツール:Web フェッチ(docs やコードの検索)、ターミナルアクセス(コマンド実行)
  • さらに Claude Code は MCP クライアントとして振る舞える。= MCP サーバーが提供するツールを消費できる。MCP サーバーを足すことで Claude Code の能力を簡単に拡張できる(この伏線は Lesson 68 で回収)。
  • スライド表記では対応 OS は macOS / Windows WSL / Linux

セットアップ3ステップ(コース収録時点の手順)

  1. Node.js をインストール(nodejs.org/en/download)。既に入っているかは、ターミナルで npm help を実行して結果が返るかで確認。
  2. ターミナルから Claude Code をインストールnpm install -g @anthropic-ai/claude-code
  3. claude コマンドで起動→ Anthropic アカウントへのログインを促される。 - 完全なセットアップガイドは公式ドキュメント(docs.anthropic.com)にある。

補足(2026 年時点の最新事情)

  • 動画のターミナル画面には「Welcome to the Claude Code research preview!」とあり、収録は research preview 期。現行の推奨インストールは curl -fsSL https://claude.ai/install.sh | bash(macOS/Linux/WSL)や brew install --cask claude-code などのネイティブインストーラに移行しており、npm install -g は旧経路。最新は公式クイックスタート(code.claude.com/docs)を参照。
  • 「Claude Code in Action」コースのセットアップ回(t1_claude_code_setup.md)と同じ位置づけの回。あちらはテキストレッスンで現行手順、こちらは動画で preview 期手順という関係。

実務ポイント(このレッスンから持ち帰るもの)

  • Claude Code の本質=「ツールを持った Claude がターミナルに常駐する」。ファイル検索・読み書き・編集+シェル実行+Web フェッチが素の状態で揃っており、エージェントループはこのツール群の上で回る。
  • MCP クライアント内蔵が拡張性の核。自作・サードパーティの MCP サーバーを接続すれば、Claude Code のツールセットを動的に増やせる(Lesson 68 で自作サーバーを実際に接続する)。
  • インストール確認の小技:npm help が返ってくれば Node 導入済み(コース当時の判定法。今はネイティブインストーラなら Node 不要)。
  • 起動は claude 一発。初回に Anthropic アカウント認証が走る。詰まったら公式 docs のセットアップガイドへ。
  • 対応環境は macOS / Windows(WSL)/ Linux。Windows ネイティブは WSL 経由がコース当時の前提。

本文(時系列・日本語逐語+画面差し込み)

[0:00] Claude Code を見ていきましょう。セットアップをして、どう動くのかを学び、いくつかの高度なユースケースを見ていきます。Claude Code はターミナルベースのコーディングアシスタントです。これはあなたのターミナルの中で動くプログラムで、コードに関わる実に幅広いタスクを手伝ってくれます。

🖥 [0:00] 画面(スライド「Claude Code」) 左側に箇条書き: - Terminal-based coding assistant(ターミナルベースのコーディングアシスタント) - Capabilities:(能力) - Search/Read/Edit files(ファイルの検索/読み取り/編集) - Terminal access (can run commands)(ターミナルアクセス=コマンド実行可) - Web access (search docs, code, etc)(Web アクセス=docs やコードの検索など) - MCP Server support (add additional tools)(MCP サーバー対応=追加ツールを足せる) - Works with MacOS, Windows WSL, and Linux 右側にターミナルのモックアップ: ``` ✳ Welcome to the Claude Code research preview! • Hi! What can I help you with today?

Try "fix lint errors" ! for bash mode · / for commands · tab to undo \⏎ for newline ```

[0:13] コーディングプロジェクトを支援するために、Claude Code は多くの異なるツールにアクセスできます。ファイルを検索・読み取り・編集する能力のような基本的なツールをたくさん持っています。しかしそれだけでなく、Web フェッチターミナルアクセスのような高度なツールも数多く備えています。

[0:27] 最後に、Claude Code は MCP クライアントとして振る舞うことができます。それが何を意味するかは、もうお分かりですね。MCP サーバーが提供するツールを消費できるということです。つまり、追加の MCP サーバーを足すことで、Claude Code の能力を簡単に拡張できるのです。

[0:44] それでは、少しセットアップをして、あなたのマシンに Claude Code をインストールしましょう。セットアップは簡単です。まず Node.js のコピーをインストールします。すでにマシンに Node が入っているかもしれません。それを確かめるには、ターミナルを開いてコマンド npm help を実行してください。

🖥 [0:47] 画面(スライド「Claude Code Setup」) 左見出し:Claude Code SetupFull setup guide available at docs.anthropic.com(完全なセットアップガイドは docs.anthropic.com にある) 縦のタイムラインに3つのボックス: 1. Install NodeJSnodejs.org/en/download(下部注記:Run ‘npm help’ to see if you already have Node installed) 2. Install Claude Code from your terminal — 黒いコードボックス:npm install -g @anthropic-ai/claude-code 3. Start Claude Code and Login — 黒いコードボックス:claude

[0:58] 結果が返ってきたら、おそらく Node はすでにインストール済みという意味です。

[1:02] Node が入ったら、npm install コマンドを実行します。これが Claude Code 本体をインストールします。

[1:08] インストールが完了したら、ターミナルでコマンド claude を実行してください。すると Anthropic アカウントへのログインを求められます。

[1:15] なお、完全なセットアップガイドは公式の Anthropic ドキュメント、docs.anthropic.com にあります。

[1:21] このセットアッププロセスは、皆さん自身で進めてもらうことにします。繰り返しますが、ここにある3ステップだけです。

[1:26] 終わり次第、一緒に小さなプロジェクトを進めながら、Claude Code が私たちに何をしてくれるのか、その実力を見ていきましょう。

英語逐語(ASR全文)

Let's take a look at CloudCode. We'll do some setup, learn how it works, and see some advanced use cases. CloudCode is a terminal-based coding assistant. This is a program running in your terminal that can help with a wide variety of code-related tasks. To help you with coding projects, CloudCode has access to many different tools. So it has many basic tools like the ability to search, read, and edit files. But it also has many advanced tools, like web fetching and terminal access. Finally, CloudCode can act as an MCP client. And you know what that means. It means it can consume tools that are provided by MCP servers. So we can easily expand the capabilities of CloudCode by adding in some additional MCP servers. Let's now go through a little bit of setup and install CloudCode on your machine. Setup is easy. We're going to first install a copy of Node.js. You might already have Node installed on your machine. And to figure out Whether or not you do, open up your terminal and execute the command npm help. If you see a result come back, that means you probably already have Node installed. Once you have Node installed, you'll do a npm install command that will install clod code itself. Once installation is complete, run the command clod at your terminal. This will prompt you to log into your Anthropic account. Now a full setup guide can be found at the official Anthropic documentation at docsanthropic.com. Now I'm going to let you go through this setup process on your own. Again, just these three steps right here. As soon as you are done, we're going to walk through a little project together and see what Claude Code can really do for us.

↑ 目次へ戻る

Building with the Claude API | Lesson 67「Claude Code in action」(全文ナレッジ)

動画 10分59秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04

日本語ナレッジ(要点整理)

このレッスンのテーマ

講義添付の zip(小さな MCP サーバーのプロジェクト)を題材に、Claude Code の実戦投入を一気に体験する回。柱は4つ:①Claude Code は「コードを書くだけのツール」ではなく、隣で働くもう一人のエンジニアという位置づけ、②/init と CLAUDE.md(3種のメモリ)、③git 操作の委任、④効果を跳ね上げる2つのワークフロー(Explore→Plan→Code と TDD)。キーフレーズは「Claude Code は effort multiplier(労力の増幅器)」。

Claude Code の正しい捉え方

  • コードを書くこと「も」できるが、それだけではない。このプロジェクトで隣に座って働くもう一人のエンジニアとみなす。
  • 普通のプロジェクトで踏む工程——プロジェクトの初期セットアップ、新機能の設計、デプロイ、サポート——のすべてを丸ごと委任できる。
  • スライド「Claude Code is not just for writing code」では工程を 1. Discover / 2. Design / 3. Build / 4. Deploy / 5. Support & Scale の5段に分け、このレッスンでは Onboard & Setup → Develop tech specs → Write and execute tests → Implement code → (後の回で)本番エラーの Debug errors / Monitor usage & performance を実演すると予告。

デモ①:セットアップの委任

  • エディタ内ターミナルで claude を起動し、最初の指示:「README.md を読んで、そこに書かれたセットアップ手順を実行して」。
  • Claude は README(59行)を読み、仮想環境の作成(uv venv)→ アクティベート(source .venv/bin/activate)→ 依存インストール(uv pip install -e .を一連の Bash ツール呼び出しで実行(Done・3 tool uses・17.4s)。

/init と CLAUDE.md

  • /init=Claude Code 内で実行するコマンド。コードベースを自動スキャンして、プロジェクトの全体アーキテクチャ・コーディングスタイルなどを把握し、結果を CLAUDE.md という特別なファイルに書き出す。
  • 以後 claude を起動するたびに、CLAUDE.md は自動でコンテキストに含まれる
  • CLAUDE.md(メモリ)は3種類project / local / user
  • スライド注記:Project=git にチェックインしてチーム共有、Local=git に入れない自分用、User=全プロジェクト横断。
  • /init 実行時にフォーカスさせたい領域の特別指示を追記できる。デモでは「MCP ツールの定義方法について詳細なノートを含めて」と指示。
  • 生成された CLAUDE.md の構成(デモ実物):冒頭にコードベースの要約 → Build/Test Commandsuv venvsource .venv/bin/activateuv pip install -e .uv run main.pyuv run pytest/単一テスト実行例)→ Code Style(Pydantic の Field でパラメータ説明、docstring 詳細、型注釈、import 順、例外処理)→ 特別指示どおり MCP Tool Definitionmcp.tool() デコレータで登録、docstring 充実、Pydantic Field、適切な戻り値型)。
  • プロジェクトは変わっていくもの。CLAUDE.md は手で編集してもよいし、/init を再実行すれば更新される

#(pound)ショートカットでメモ追記

  • 入力を # で始めると、その一文を CLAUDE.md に追記するメモとして扱える(画面のプレースホルダは「# Add to memory.」、入力欄下に「# to memorize」)。
  • デモ:「always apply appropriate types to function args(関数引数に適切な型を必ず付ける)」→ 実行するとどのメモリに保存するか(project / local / user)を選択するよう聞かれる → チーム全員と共有したいので project memory を選択 → CLAUDE.md の Code Style 付近か末尾に追記される。

git の委任

  • 新規ファイル(CLAUDE.md)を git にステージ→コミットする作業も、「stage and commit all changes」と頼むだけ。
  • Claude は git diff --stagedgit log -n 3 --oneline などで変更とコミット規約を確認し、説明的なコミットメッセージを書いてコミット(デモ実物:Add CLAUDE.md with build commands and coding guidelines、フッターに 🤖 Generated with [Claude Code](https://claude.ai/code)Co-Authored-By: Claude <noreply@anthropic.com>[main 6a8fe61] 1 file changed, 25 insertions(+))。

effort multiplier と2つのワークフロー

  • 題材機能:この MCP サーバーに「Word/PDF ファイルを読んで内容を Markdown に変換するツール」を追加する。
  • 「make a Word doc plus PDF file to Markdown conversion tool」と一行で頼むこともできるが、少しの手間をかけると結果が劇的に良くなる。=Claude Code は effort multiplier(労力の増幅器)
  • ワークフロー1(Explore → Plan → Code)、スライド「Common Workflows」の3ステップ: 1. Feed context into Claude:機能に関係するファイルを自分で特定し、Claude に読ませ・分析させる(Find files that are relevant to a feature that you want to build) 2. Tell Claude to plan a solution:作りたい機能を伝え、解決手順を計画させる。「まだコードは書くな」と明示するのがコツ(Consider telling Claude specifically to not write any code.) 3. Ask Claude to implement the solution:計画ができてから実装させる(Claude will write code based upon the context and thinking that it already completed
  • デモ(ワークフロー1):
  • 関連ファイル=tools/math.py既存ツールの書き方の手本)と tools/document.pybinary_document_to_markdown=バイナリ→Markdown 変換の便利関数)。まず「この2ファイルを読んで」と指示。
  • 次に「document_path_to_markdown という新ツールを計画して。PDF/Word のパスを受け取り、ファイルを読み、Markdown に変換して返す。計画だけ。まだコードは書かない」。
  • Claude が詳細な実装計画を返す → 「この計画を実装して」。
  • 実装は document.py 更新(ファイル存在チェック、拡張子から file_type 判定、pdf/docx 以外は ValueError、バイナリ読み込み→既存関数で変換)→ main.py 更新mcp.tool()(document_path_to_markdown) で MCP サーバーに登録)→ テスト作成テストスイート実行まで一気通貫。
  • サマリーには「存在しない/未対応のファイル型のエラーハンドリングを追加した」と、明示的に頼んでいない適切な気配りも含まれていた。
  • ワークフロー2(TDD ワークフロー)、スライド「Test Driven Development Workflow」の4ステップ: 1. Feed context into Claude(関連ファイルを読ませる) 2. Ask Claude to think of some test cases for your new feature:コードはまだ書かせずテスト案を考えさせる 3. Ask Claude to implement those tests関連しそうなテストだけ選んで実装させる 4. Ask Claude to write code that passes the testsテストが通るまで解決策を反復させる
  • デモ(TDD):
  • まず /clear で会話履歴(コンテキスト)をリセット。ここでは前の解答をカンニングさせないため。
  • 同じ2ファイルを再度読ませる → 「この機能を評価するために書けそうなテストを考えて。まだコードは書かない」。
  • Claude はフィクスチャ(tests/fixtures/mcp_docs.docxmcp_docs.pdf)を見つけ、テスト案 8 個を提示:①基本 PDF 変換 ②基本 DOCX 変換 ③存在しないファイル ④不正なファイル型 ⑤空ドキュメント ⑥複雑な要素(表・画像)⑦パーミッション拒否 ⑧出力フォーマット検証。
  • ⑥⑦⑧は今回の範囲を超えるので「テスト1〜5だけ実装して」→ 最後に「これらのテストを通すコードを書いて」→ 8 passed in 0.27s(既存3本+新規5本)で全テスト通過。
  • 締め:シンプルな指示でも Claude はベストを尽くすが、こちらが少し労力をかけるだけで効果は劇的に増幅される

実務ポイント(このレッスンから持ち帰るもの)

  • 委任の範囲を「コードを書く」に限定しない。README どおりの環境構築、git のステージ/コミット、テスト実行まで、工程まるごと Claude Code に任せられる。「もう一人のエンジニア」として扱うのがメンタルモデル。
  • プロジェクト着手時は /init。コードベースをスキャンして CLAUDE.md(コマンド・スタイル・アーキテクチャの要約)を生成し、以後の全リクエストに自動注入される。/init に「◯◯を重点的に書け」と引数的な特別指示を足せるのは覚えておく(例:MCP ツール定義の詳細ノート)。
  • CLAUDE.md は3スコープ:project(git 共有・チーム向け)/local(git 外・自分専用)/user(全プロジェクト共通)。# で始めるとワンライナー追記でき、保存先スコープをその場で選べる。陳腐化したら手編集 or /init 再実行で更新。
  • effort multiplier の実践形=「読ませる→計画させる→実装させる」。特に「まだコードを書くな」の一言で計画と実装を分離するのが効く。関連ファイルの指定は人間の土地勘(どのファイルが手本になるか)が価値を出すポイント。
  • TDD ワークフローは品質をさらに上げる:テスト案を発散させる→人間が取捨選択(1〜5だけ等)→テスト実装→「テストが通るまで書く」。ループの終了条件がテストになるので、Claude の反復が暴走しない。
  • /clear はコンテキストリセット。別アプローチを試す・前の解に引きずられたくない時に使う(このデモでは「カンニング防止」)。
  • git 委任時のコミットは説明的メッセージ+Claude Code 署名フッター付きで作られる。git stash による作業退避も自然言語(「stashing these changes」)で頼める。

本文(時系列・日本語逐語+画面差し込み)

[0:00] Claude Code のハンズオン体験をしてもらうために、一緒に取り組む小さなプロジェクトを用意しました。このプロジェクトのソースは、この講義に添付された zip ファイルにあります。

[0:08] この zip をダウンロードして中身を展開し、その新しいプロジェクトディレクトリの中でコードエディタを立ち上げることをおすすめします。私はすでにエディタでこのプロジェクトを開いてあります。

[0:17] さて、プロジェクトを開くと、中身を眺めたり、README ファイルに書かれているセットアップを進めたりしたくなるかもしれません。でもその前に、Claude Code について理解しておいてほしい大事なことがあります。

[0:30] Claude Code は、ただコードを書いてくれるだけのツールではありません。 もちろん書くこともできますが、それだけが Claude Code のすべてではないのです。

[0:39] そうではなく、Claude Code を「このプロジェクトであなたの隣で働いている、もう一人のエンジニア」として見てほしいのです。普通のプロジェクトで通る工程のすべてを、まるごと Claude に委任できます。

[0:50] これには、プロジェクトの初期セットアップから、新機能の設計、デプロイ、そしてサポートまで、すべてが含まれます。

🖥 [0:30] 画面(スライド「Claude Code is not just for writing code」) サブタイトル:It can help you with every step of a project(プロジェクトのあらゆる工程を手伝える) 5列の工程表。ハイライト(濃色)されたセルがこのコースで実演する項目: - 1. Discover:Explore codebase and history / Search documentation / Onboard & Setup(濃色) - 2. Design:Plan project / Develop tech specs(濃色)/ Define architecture - 3. BuildImplement code(濃色)/ Write and execute tests(濃色)/ Create commits and PRs - 4. Deploy:Automate CI/CD / Configure environments / Manage deployments - 5. Support & ScaleDebug errors(濃色)/ Large-scale refactor / Monitor usage & performance(濃色) 濃色セル同士が矢印で結ばれ、Onboard & Setup → Develop tech specs → Write and execute tests → Implement code → Monitor usage & performance / Debug errors の流れを示す。

[0:57] このプロジェクトを進めながら、さまざまなステップで Claude を大いに活用していきます。Claude にプロジェクトのセットアップをさせ、新機能を計画させ、テストとコードを書かせます。そして後ほど、少し別のプロジェクトで、本番環境のエラーを自動で発見して修正するために Claude Code を使う方法もお見せします。

[1:17] では始めましょう。

[1:18] エディタに戻り、ターミナルを開いて、その中で claude を実行して Claude Code を起動します。開いたら、最初の指示を入れます。README ファイルの内容を読んで、そこに列挙されているセットアップ手順を実行するよう頼みます。

🖥 [1:45] 画面(VS Code ターミナル:セットアップの委任) プロンプト app git:(main) claude で起動。ボックス内: ✳ Welcome to Claude Code research preview! /help for help cwd: /Users/sgrider/code/workspace/app ユーザー指示:Read the @README.md file and execute the setup directions listed in it.Read README.md (59 lines) Call(Creating virtual environment and installing package)…Bash(uv venv)…Bash(source .venv/bin/activate && echo 'Virtual …Bash(uv pip install -e .)… ステータス行:✻ Setting… (7s · ⚒ 115 tokens · esc to interrupt) エクスプローラには tests / tools / .gitignore / main.py / pyproject.toml / README.md / uv.lock が並ぶ。

[1:40] Claude はさまざまなツールを使ってそのファイルを読み、一連のコマンドを実行していきます。新しい仮想環境を作り、環境をアクティベートし、依存パッケージをいくつかインストールします(完了表示:Done・3 tool uses・17.4s)。

[1:53] それが完了したら、Claude が私たちのプロジェクトをより深く理解するのを助けるコマンドを実行します。init コマンドです。これは Claude Code の中で実行するコマンドです。

[2:01] このコマンドを実行すると、Claude はあなたのコードベースを自動でスキャンして、プロジェクトの全体的なアーキテクチャやコーディングスタイルなどを理解します。完了すると、Claude は発見したことすべてを Claude.md(CLAUDE.md)という特別なファイルに書き出します。

[2:17] 今後 claude を再び起動するたびに、このファイルは自動的にコンテキストとして含まれます

[2:22] 知っておいてほしいのですが、Claude ファイルには project・local・user の3種類があります。この後すぐ、これらへの言及をいくつか目にするので、そのとき中身を説明します。

🖥 [1:56]〜[2:46] 画面(右側に随時表示されるスライド「The “/Init” Command」) - Claude Code will scan your codebase — 構造や依存関係、コマンドを把握 - Findings will be summarized in a CLAUDE.md file(発見は CLAUDE.md に要約される) - When working on your project, Claude will automatically read the contents of this file(作業時に自動で読み込まれる) - You can have multiple CLAUDE.md files:(CLAUDE.md は複数持てる) - “Project”(git にチェックインして共有) - “Local”(git に入れない・自分と Claude 用) - “User”(全プロジェクト横断で使われる) - TIP: when running “/init” you can add directions(/init 実行時に指示を追記できる)

[2:31] この init コマンドを実行するとき、Claude に注目してほしい領域について特別な指示を任意で追加できます。では実際に試して、私たちのプロジェクトで何が生成されるか見てみましょう。

[2:42] init コマンドを実行します。その際、特別な指示を渡します。MCP ツールの定義に関する詳細なノートを含めるよう頼みます。

🖥 [2:52] 画面(/init の実行結果:CLAUDE.md 生成プレビュー) ターミナルに生成中の CLAUDE.md 内容がプレビュー表示される(抜粋): `` - Run all tests:uv run pytest- Run single test:uv run pytest tests/test_document.py::TestBinaryDocumentToMarkdown::test_binary_document_to_markdown_with_docx -v`

Code Style

  • Use Pydantic's Field for parameter descriptions
  • Add detailed docstrings with one-line summary, functionality explanation, when to use/not use, and examples
  • Use type annotations for all functions and parameters
  • Import organization: standard library first, then third-party, then local modules
  • Error handling: use appropriate exceptions with descriptive messages

MCP Tool Definition

When defining a tool, register it with the MCP server using the mcp.tool() decorator. Each tool should: - Have comprehensive docstrings - Use Pydantic Field for parameter descriptions - Return appropriate types ``` 確認プロンプト:Do you want to create CLAUDE.md? ❯ 1. Yes / 2. Yes, and don't ask again this session (shift+tab) / 3. No, and tell Claude what to do differently (esc)

[2:52] すべて完了したら、新しく生成された Claude.md ファイルを見てみます。

[2:57] 一番上にあるのは、Claude がこのコードベースをどう捉えたかの要約です。今後実行が必要になりそうな重要コマンドの一覧が並びます。このプロジェクトで使っているコーディングスタイルについての記述もあります。そして、私が特別にリクエストしたとおり、MCP ツールの定義に関する情報も含まれています。

🖥 [3:29] 画面(エディタで CLAUDE.md を開く) タブ:CLAUDE.md(U=未追跡)。内容(先頭部): ```markdown

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Build/Test Commands

  • Create virtual env: uv venv
  • Activate env: source .venv/bin/activate
  • Install package: uv pip install -e .
  • Run MCP server: uv run main.py
  • Run all tests: uv run pytest ``` 右側ターミナルには完了メッセージ:CLAUDE.md file created with build/test commands and code style guidelines, including detailed notes on defining MCP tools from the README. パンくず:CLAUDE.md > # CLAUDE.md > ## MCP Tool Definition(セクションの存在が確認できる)

[3:13] 少し前に触れたとおり、このファイルは今後の私たちのあらゆるフォローアップリクエストで、Claude のコンテキストとして含まれることになります。

[3:19] さて、プロジェクトは時間とともに変わっていきます。コーディングスタイルを変えるかもしれないし、コマンドを追加するかもしれません。そうなったら、このファイルをそのまま手で編集してもいいし、init コマンドを再実行してもかまいません。再実行すれば、Claude が CLAUDE.md の内容を更新してくれます。

[3:36] 最後に、ごく小さなショートカットとして、ポンド記号(#)を打ってから、そのファイルの内容に追記したい specific なメモをタイプできます。これは、以後のすべてのリクエストに含まれる、ごく小さく具体的な指示を Claude に与えるためのツールとして使えます。

[3:52] 私の場合なら、たとえば「always apply appropriate types to function args(関数引数には必ず適切な型を付ける)」のような指示をここで追加するでしょう。実行すると、この小さなメモをどこに追加したいかを聞かれます

🖥 [3:45] 画面(# ショートカットの入力) ターミナルの入力ボックスがメモモードに変わっている: ```

Add to memory. Try "Always use descriptive variable names"

# to memorize `` (#` に続けて自分のメモを入力する。プレースホルダ例が表示されている)

[4:04] ここで、さきほどの project memory・local memory・user memory が登場します。私の場合、これはこのプロジェクトで働く全員と共有したいメモなので、project memory に追加します。

[4:15] 追加できたら、CLAUDE.md の内容を確認します。Code Style の下のどこか、あるいは一番下あたりに、いま追加したメモが載っているはずです。

[4:25] この時点で、私たちはプロジェクトに新しいファイルを1つ追加しました。そしてこのプロジェクトは Git で管理されています。普通なら、ターミナルを開いて、作ったばかりのこの新しいファイルを Git にステージし、変更をコミットするでしょう。

[4:37] 全部手動でやることもできますが、Claude に頼んでしまうほうがずっと速い。そこで「stage and commit all changes(すべての変更をステージしてコミットして)」と頼みます。

[4:46] Claude はコードベースに加えた変更をすべて確認し、説明的なコミットメッセージを書いて、ファイルをコミットします。

🖥 [4:50] 画面(コミットの実行結果) ``` └ Bash(git diff --staged)… └ Bash(git log -n 3 --oneline)… └ Done (4 tool uses · 3.8s)

● Bash(git commit -m "Add CLAUDE.md with build commands and … coding guidelines

  🤖 Generated with [Claude Code](https://claude.ai/code)

  Co-Authored-By: Claude <noreply@anthropic.com>")

└ [main 6a8fe61] Add CLAUDE.md with build commands and coding guidelines 1 file changed, 25 insertions(+) create mode 100644 CLAUDE.md

● Changes committed successfully with a descriptive message. ```

[4:53] 次は、コードを書くときの Claude の効果を高めるテクニックをお見せしたいと思います。このプロジェクトに新機能を1つ追加します。

[5:00] 思い出してください。このプロジェクトはとても小さくシンプルな MCP サーバーです。そのサーバーに、Word ドキュメントまたは PDF ファイルを読んで、内容を Markdown に変換する新しいツールを追加するよう Claude に頼みます。

[5:13] もちろん、「make a Word doc plus PDF file to Markdown conversion tool(Word と PDF を Markdown に変換するツールを作って)」のように、指示をそのままタイプすることも絶対にできます。

[5:21] しかしその前に知っておいてほしいのです。ほんの少し労力を足すだけで、Claude の効果を劇的に高められるということを。その方法をお見せしましょう。

[5:30] Claude Code を「effort multiplier(労力の増幅器)」だと考えてください。 Claude への指示の出し方に少し労力をかければ、返ってくる結果は格段に良くなります。

[5:39] これから、2つの異なるワークフロー——タスクへの取り組ませ方の2つの流儀——をお見せします。どちらもあなたの側に少し労力を要求しますが、その分 Claude がはるかに複雑な問題に取り組めるようになります。

[5:52] 最初のワークフローでは、3つの明確なステップを踏みます。第一に、作ろうとしている機能に関係するとわかっているコードベースの領域を特定します。そして Claude に、それらのファイルを specifically に読んで分析するよう頼みます。

🖥 [5:30] 画面(スライド「Common Workflows」) サブタイトル:Claude is an effort multiplier. 3つの矢羽根(左→右): 1. Feed context into ClaudeFind files that are relevant to a feature that you want to build 2. Tell Claude to plan a solutionConsider telling Claude specifically to not write any code. 3. Ask Claude to implement the solutionClaude will write code based upon the context and thinking that it already completed

[6:06] 第二に、作りたい機能を Claude に伝え、解決策の計画を頼みます。つまり、その機能や問題を実装するために実際に踏むステップです。

[6:16] そして最後に、Claude が計画ステップを終えたあとで、その解決策を実際に実装するよう頼みます

[6:22] 私たちの機能——新しいドキュメント変換ツールの追加——でこれをどうやるか、お見せしましょう。

[6:29] まず、コードベースを見て回って、関連するファイルをいくつか特定します。tools ディレクトリの中に math.py があります。これはすでに組み上がっているツールの一例です。作ろうとしている機能に関連するファイルと言えるでしょう。Claude が「ツールをどう書くべきか」のより良いイメージを持てるからです。

[6:49] 第二に、document.py を見るよう Claude に頼むとよいでしょう。ここにはとても便利な関数 binary_document_to_markdown があります。ある量のバイナリデータを受け取り、Markdown に変換する関数です。この2ファイルを見るよう伝えれば、Claude は「そもそもツールをどう書くか」と「変換を実際にどうやるか」の両方について、より良いイメージを掴めます。

[7:10] そこで Claude に指示を入れて、まずはそれらのファイルの内容を読むだけを頼みます。

[7:15] 次に、Document Path to Markdown という新ツールの実装計画を頼みます。PDF または Word ドキュメントへのパスを受け取り、ファイルを読み、内容を Markdown に変換して結果を返すツールです。あわせて、「機能を計画するだけで、まだコードは一切書かないように」と specifically に伝えます

[7:33] それに応えて、Claude はかなり練られた計画を返してきます。この機能全体の実装に必要になるいくつかのステップを順に説明するものです。

[7:44] 最後に、この計画を実装するよう頼みます。Claude はまず document.py を更新します。これは間違いなく正しい。次に main.py を更新します。これも良い。そして最後に、この新ツールのテストを書きテストスイートを実行してテストが実際に通ることを確認します。

🖥 [7:46] 画面(document.py への編集提案) ターミナルに Edit file の diff(緑ハイライト)が表示される: ```python

Validate file exists

if not os.path.exists(file_path): raise FileNotFoundError(f"Document file not found: {file_path}")

Determine file type from extension

_, extension = os.path.splitext(file_path) file_type = extension.lower().lstrip('.')

Validate file type

if file_type not in ["pdf", "docx"]: raise ValueError(f"Unsupported file type: {file_type}. Only PDF and DOCX are supported.")

Read binary data from file

with open(file_path, "rb") as f: binary_data = f.read()

Convert to markdown using existing function

return binary_document_to_markdown(binary_data, file_type) ``` 確認プロンプト:Do you want to make this edit to document.py? ❯ 1. Yes / 2. Yes, and don't ask again this session (shift+tab) / 3. No, and tell Claude what to do differently (esc)

🖥 [7:50] 画面(main.py への編集提案) Update(main.py)… → Edit file ボックス: python 1 from mcp.server.fastmcp import FastMCP 2 from tools.math import add 3 from tools.document import binary_document_to_markdown, document_path_to_markdown ← 緑(追加) 4 5 mcp = FastMCP("docs") 6 7 mcp.tool()(add) 8 mcp.tool()(document_path_to_markdown) ← 緑(追加) 9 10 if __name__ == "__main__": 11 mcp.run() 確認プロンプト:Do you want to make this edit to main.py?

🖥 [7:56] 画面(テスト作成と実行) tests/test_document.py に追加されるテストの diff(緑):test_document_path_to_markdown_with_unsupported_extension(一時テキストファイルを作り、pytest.raises(ValueError) で変換を試み、finally で後片付け)など。 その下で Bash(uv run pytest)…Running…、ステータス行 ✳ Implementing… (2s · ⚒ 927 tokens · esc to interrupt)

[7:59] サマリーメッセージに注目してください。Claude は「存在しないファイルや未対応のファイル型に対するエラーハンドリングを追加した」とも報告しています。これは間違いなく適切な判断ですが、私が機能の説明の中で explicitly に頼んだことではありませんでした。

[8:12] この機能の実装を Claude に頼む、もう1つのやり方をお見せしたいと思います。

[8:17] その代替案を見せる前に、Claude にいま書いたコードを全部取り除いてもらいます。変更を stash するのです。これで、さきほど実装した新機能に関するものが何もない、まっさらな状態に戻りました。

🖥 [8:30] 画面(stash の実行結果) ユーザー指示:stage and stash all changes ● I'll stage and commit all the changes made to implement the document_path_to_markdown tool. ● Call(Stage all changes)… └ Bash(git status)… / Bash(git add .)… / Bash(git status)… └ Done (3 tool uses · 3.6s) ● Bash(git stash)… └ Saved working directory and index state WIP on main: 6a8fe61 Add CLAUDE.md with build commands and coding guidelines ● The changes have been staged and stashed successfully. All modifications to main.py, tools/document.py, and tests/test_document.py are now saved in the stash.

[8:32] 2つ目に探るテクニックは、テスト駆動開発(TDD)ワークフローです。これもあなたの側に前もって少し労力を求めますが、Claude の効果を劇的に高めます。

[8:41] このワークフローでも、まず Claude に関連コンテキストを読ませます。次に、コードを書く前に、この機能に関連して書けそうなテストをいくつか考えるよう頼みます。次に、最も関連の深いテストを選んで実装させます。最後に、動くテストが揃ったところで、テストが実際に通るまで解決策のコードを書くよう頼みます。

🖥 [9:08] 画面(スライド「Test Driven Development Workflow」) 4つの矢羽根(左→右、右端は黒): 1. Feed context into ClaudeFind files that are relevant to a feature that you want to build 2. Ask Claude to think of some test cases for your new featureTell Claude to not write any code yet 3. Ask Claude to implement those testsOnly select tests that look relevant to your feature 4. Ask Claude to write code that passes the testsClaude will iterate on a solution until the tests pass

[9:03] では、まったく同じ機能——ハードドライブからドキュメントを読んで内容を Markdown に変換するツール——で、このアプローチがどう見えるか、お見せしましょう。

[9:14] まず slash clear(/clear)コマンドを実行します。これは Claude との会話履歴を消去する、要するにコンテキストをリセットするものです。今回これをやるのは、さっきの自分の解答をカンニングさせないためです。

[9:24] それから、あの2つの関連ファイルをもう一度読むよう頼みます。

[9:32] 次に、とても明確な指示を追加します。これから作りたいこの新機能を評価するために書けそうなテストを考えるように、と。ここでも、まだコードは一切書かないよう specifically に頼みます。

[9:42] 返ってきた提案は実に堅実です。

🖥 [9:45] 画面(テスト案の提示) ``` Looking at the test directory, I can see there are fixtures available: - tests/fixtures/mcp_docs.docx - tests/fixtures/mcp_docs.pdf These would be perfect to use in the tests. I also looked at test_document.py, which likely has existing tests for the document module, and I would want to follow similar patterns for consistency.

● Tests for document_path_to_markdown: 1. Test basic PDF conversion 2. Test basic DOCX conversion 3. Test with non-existent file 4. Test with invalid file type 5. Test with empty document 6. Test with file containing complex elements (tables, images) 7. Test with permission-denied file 8. Test output format validation ```

[9:45] ただ、テスト6・7・8については今回は気にしたくありません。いまやっていることの範囲を少し超えた、専門的なものだからです。そこで Claude には、テスト1〜5だけを実装するよう頼みます。

[9:59] では最後のステップに進みます。これらのテストを通すコードを書くようClaude に頼みます。

🖥 [10:10] 画面(テスト全通過) pytest の実行結果(抜粋): ``` tests/test_document.py::TestDocumentPathToMarkdown::test_document_path_to_markdown_with_nonexistent_file PASSED [ 75%] tests/test_document.py::TestDocumentPathToMarkdown::test_document_path_to_markdown_with_invalid_extension PASSED [ 87%] tests/test_document.py::TestDocumentPathToMarkdown::test_document_path_to_markdown_with_empty_file PASSED [100%]

======================== 8 passed in 0.27s ========================

● All tests are now passing! I implemented document_path_to_markdown function that: 1. Checks if file exists and has a supported extension 2. Handles empty files by returning an empty string 3. Properly handles conversion errors 4. Added the tool to the MCP server in main.py ```

[10:13] 素晴らしい。すべてのテストが通りました。

[10:15] 先に進む前に、もう一度だけ念を押させてください。Claude Code は本当に effort multiplier(労力の増幅器)です。 とてもシンプルな指示を入れるだけでも Claude はベストを尽くしてくれます。しかし、あなたの側でも少しだけ労力をかければ、Claude の効果を劇的に高めることができるのです。

英語逐語(ASR全文)

To get you some hands-on experience with CollideCode, I created a small project for us to work on. You should find the source for this project in a zip file attached to this lecture. I would encourage you to download this zip, extract its contents, and then start up your code editor inside of this new project directory. I have already opened up the project inside of my editor. Now once you have the project open, you might be tempted to look through the project contents and maybe go through a little bit of setup which is described inside the readme file. But before you do, there's something important I would like you to understand about Claude Code. Claude Code is not a tool that is just going to write code for you. Naturally, it absolutely can, but that's not the only thing that Claude Code is about. Instead, you really want to view Claude Code as another engineer who is working on this project alongside you. Every task that you would normally go through on a normal project can really be fully delegated off to Claude. So this includes everything from initially setting up a project, to designing new features, to deployment, and to support. As we go through this project, we're going to leverage Claude heavily to aid us in various steps. We will use Claude to set up the project for us, to plan out a new feature, to write tests and code, and then later on, on a slightly different project, I'll show you how we can use Claude code to automatically discover and fix errors in a production environment. So let's get to it. Back inside my editor, I'm going to open up my terminal and then launch Claude code inside there by executing Claude. Then once I have this open, I'm going to put in my first directive to Claude. I'm going to ask it to read the contents of the readme file and go through any setup directions that are listed inside of here. I will ask Claude to read the contents of the readme file and execute the setup directions listed in it. Claude will then use a variety of different tools to read that file and then execute a series of different commands. It will create a new virtual environment, activate the environment, and install some dependencies. Once that is complete, we are going to run a command that will help Claude get a better understanding of our project. We're going to do so by running the init command. This is a command that we're going to execute inside of Claude code itself. When you execute this command, Claude will automatically scan your code base to understand your project's general architecture, coding style, and so on. Once complete, Claude will write all of its findings into a special file named Claude.md. Whenever we run Claude again in the future, this file will be automatically included as context. Just so you know, there are three different Claude files, project local and user. We're going to see some references to these in a minute, so we'll discuss what they're all about then. Whenever you run this init command, you can also optionally add in some special directions for some areas for Claude to focus on. So let's try this out right now and see what is generated for our project. I'm going to run the init command. And when I do, I'm going to pass in some special instructions. I'm going to ask Claude to include some detailed notes on defining MCP tools. Once it is all done, I'm going to take a look at the newly generated Claude.md file. So this is a summary of what Claude thinks of our code base at the top. It will list out some important commands that it might need to run in the future. We'll get some listing around our coding style that we've used inside this project. And then as I specially requested, it also included some information around defining MCP tools. As I mentioned a moment ago, this file is going to be included as context for Claude in any follow-up requests we make in the future. Now, projects change over time. We might change our coding style or add in some additional commands. So if that ever happens, we can very easily manually edit this file, or we can choose to rerun the init command. If you rerun this command, Claude will update the contents of the Claude MD file. Finally, as a very small shortcut, we can put in a pound and then type in some specific note that we want to be appended into the contents of that file. So we can use this as a tool to give very specific small directions to Claude that will be included in all follow-up requests. So for me, I might add in a direction here that says something like always apply appropriate types to function arcs. If I run that, I'll then be asked where I want to add this little note. This is where we see that project memory, local memory, and user memory appear. In my case, this is a note that I want to be shared with everyone who is working on this project, so I will add it to project memory. Once I add that in, I can then check the contents of my claw.md file, and either somewhere under code style or perhaps at the very bottom, I'll probably see whatever note I just added in. At this point, we have added a new file to our project, and this project is being managed by Git. So normally we would open up our terminal, stage this new file that we just created into Git, and then commit those changes. We could do all that manually, but it'd be a lot faster if we just asked Claude to do it for us. So I will ask Claude to stage and commit all changes. Claude will then take a look at all the different changes we have made to the codebase, write a descriptive commit message, and commit those files. Next up, I would like to show you some techniques for increasing Claude's effectiveness when writing code. We are going to add a new feature into this project. As a reminder, this project is a very small, very simple MCP server. We are going to ask Claude to add in a new tool to the server that will read a Word document or a PDF file and convert the contents to Markdown. Now, we absolutely could just type in directions for that to Claude, something like make a Word doc plus PDF file to Markdown conversion tool. But before doing that, I want you to know that we can dramatically increase the effectiveness of Claude by putting in a little bit more effort. So let me show you how. Think of Claude code as being an effort multiplier. If you put a little effort into how you direct Claude, you will get back significantly better results. I'm going to show you two different workflows, two different ways of instructing Claude on how to approach a task. Both of these workflows require a little bit of effort on your side, but they allow Claude to tackle much more complex problems. In this first workflow, we're going to go through three distinct steps. First, we're going to identify some areas of our code base that we know are relevant to a feature that we are trying to work on. We're then going to ask Claude to specifically read and analyze those files. Second, we're going to tell Claude about the feature we want to build and ask it to plan out a solution. So the steps will actually go through to implement whatever feature or problem you're trying to solve. And then finally, after Claude has gone through that planning step, we will ask Claude to actually implement the solution. Let me show you how we would do this for our particular feature of adding in some new document conversion tool. So first I'm going to go through my code base and identify some different relevant files. You'll notice there is a tools directory and inside there is a math.py file. This is an example of a tool that has already been put together. So this might be some file that is relevant for the feature that we are trying to build, just because it gives Claude a better idea of how to author a tool. Second, we might ask Claude to take a look at the document.py file, which includes a very helpful function, binary document to markdown. So this will take in some amount of binary data and convert it to markdown. So we can tell Claude to take a look at these files and Claude will get a better idea of how to write a tool in the first place and then how to actually do the conversion. So I'm going to add in some instructions to Claude and ask it to just read the contents of those files. Next, I'm going to ask Claude to plan out the implementation of a new tool called Document Path to Markdown, which will take in a path to a PDF or Word document, read the file, convert the contents to Markdown, and return the result. I'm also going to tell Claude specifically to just plan the feature out and not to write any code just yet. In response, Claude is going to give us a pretty well-detailed plan that's going to go through a couple of different steps that will be required to implement this entire thing. Finally, I will ask Claude to implement this plan. It's first going to update the document.py file, which is definitely correct. It's then going to update the main.py file, which is also good. And then finally, it will author a test around this new tool. It's then going to run the test suite and make sure that the test actually pass. You'll notice that in the summary message, Claude also tells me that it added in some error handling for non-existent or unsupported file types, which was definitely appropriate, even though it wasn't something that I actually asked for explicitly inside of my description of feature. I'd like to show you another way that we could have asked Claude to implement this feature. Before I show you this alternative, I'm going to get Claude to remove all the code that it just wrote by stashing these changes. Now I am back to a clean slate where I don't have anything related to that new feature that was just implemented. The second technique that we are going to explore is a test-driven development workflow. Again, this requires some more effort from you up front, but it dramatically increases Claude's effectiveness. With this workflow, we will again ask Claude to take a look at some relevant context. Then before writing any code, we're going to ask Claude to think of some different tests it could write related to our feature. Next, we will select the most relevant tests and ask Claude to implement them. Finally, after we have got some working tests put together, we will ask Claude to write out a solution and write code until the tests actually pass. So let me show you how this approach would actually look for the same exact feature, one where we are building a tool that's going to read a document off of our hard drive and convert its contents into Markdown. First, I'm going to run the slash clear command. This is going to clear up my conversation history with Claude, essentially reset its context. In this case, I'm doing this just so it doesn't cheat off of its previous solution. Then I will ask Claude to read those two relevant files once again. Then I'm going to add in some very clear instructions to think of some tests that it might write to evaluate this new feature that we want to build. Again, I'm going to ask it specifically to not write any code just yet. The suggestions I get back are really solid. Now, I don't really want to have to worry about tests six, seven, and eight, because those are a little bit more specialized beyond what we're doing right now. So I'm going to ask Claude to just implement tests one through five. Now we will move on to the last step where we will ask Claude to write out some code to make these tests pass. Very good, all the tests pass. Now, before we move on, I just want to remind you one more time that Claude code really is an effort multiplier. You can put in very simple directions, and Claude will do its best, but you can dramatically increase Claude's effectiveness by putting in a little bit of effort on your side as well. Thank you.

↑ 目次へ戻る

Building with the Claude API | Lesson 68「Enhancements with MCP servers」(全文ナレッジ)

動画 2分30秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04

日本語ナレッジ(要点整理)

このレッスンのテーマ

Claude Code の最も面白い側面のひとつ——MCP クライアントが内蔵されていること——を実演する回。Lesson 67 で自作したばかりの MCP サーバー(document_path_to_markdown ツール入り)を claude mcp add で Claude Code に接続し、Claude Code の能力を動的に拡張する。後半は Sentry / JIRA / Slack など、開発フローに組み込める MCP サーバーの実例カタログ。

自作 MCP サーバーを Claude Code に接続する手順

  1. 実行中の Claude Code セッションを Ctrl+C で終了する。
  2. シェルで claude mcp add を実行して MCP サーバーを登録する。指定するのは: - サーバー名:何でもよい。今回はドキュメント関連のサーバーなので documents。 - サーバーの起動コマンド:このプロジェクトでは uv run main.py
  3. 以上。claude で Claude Code を再起動すると、起動メッセージに「✔ Found 1 MCP server · /mcp」と表示され、さきほど作ったツールが使えるようになる。 - 画面ではこのタイミングでエクスプローラに .mcp.json(未追跡)が現れており、登録情報がプロジェクトに書き出されたことがわかる。あわせて「✔ Installed Vscode extension」「✔ Loaded project + user memory · /memory」の行も見える(CLAUDE.md が自動ロードされている)。

動作テスト:自作ツールを Claude Code が呼ぶ

  • テスト素材は tests/fixtures/ フォルダの2つのデモファイル:mcp_docs.docxmcp_docs.pdf(どちらも MCP 自体の小さなドキュメントが入っている)。
  • 「どちらかのファイルの内容を Markdown に変換して」と頼む → 期待どおり、Claude はいま作ったツール documents:document_path_to_markdown (MCP)file_path: ".../app/tests/fixtures/mcp_docs.docx" で呼び出す。
  • 返ってきた Markdown は MCP の概要文書そのもの:「# Overview — The Model Context Protocol allows applications to provide context for LLMs in a standardized way…」「## Key Features of this Python SDK」、そして Primitive/Control/Description/Example Use の表Prompts=User-controlled・スラッシュコマンドやメニュー選択で起動/Resources=Application-controlled・ファイル内容や API レスポンス等のコンテキストデータ/Tools=Model-controlled・LLM に公開されアクションを取る関数)。
  • Claude のまとめ:「ドキュメントは Markdown 形式に変換された。MCP の概要、SDK の主要機能、3つのコアプリミティブ(Prompts / Resources / Tools)を含む」=自作ツール経由で PDF/Word の読解能力が Claude Code に追加された

開発フローを拡張する MCP サーバーの実例(スライド「Extending Claude with MCP」)

MCP サーバー 機能
sentry-mcp Sentry に記録されたバグを自動で発見・修正する
playwright-mcp ブラウザ自動操作。開発中・トラブルシュート中のビジュアルコンポーネントを Claude 自身が確認できる
figma-context-mcp Figma のデザインを Claude に公開する
mcp-atlassian Confluence と Jira へのアクセス(チケット内容の閲覧など)
firecrawl-mcp-server Web スクレイピング能力の追加
slack-mcp メッセージ投稿・特定スレッドへの返信(作業完了通知などに)
  • これらは可能な拡張のほんの一部。自分の開発ワークフローをどう強化できるか考える価値は大いにある。

補足(2026 年時点の最新事情)

  • 現行 CLI の claude mcp addclaude mcp add <name> -- <command> [args...]-- 区切り)や --transport http/sse、スコープ指定 -s local|project|user をサポート。動画は research preview 期のためシンプルな形だが、「名前+起動コマンドを登録するだけ」という本質は同じ
  • プロジェクト共有したい場合は .mcp.json(project スコープ)にチェックインする運用が現行の定石。

実務ポイント(このレッスンから持ち帰るもの)

  • Claude Code=MCP クライアントという設計が拡張性のすべて。API 側(Lesson 27〜38)で学んだ「ツールを提供して Claude に使わせる」構図が、そのままローカルの開発アシスタントにも適用できる。
  • 接続は3点セット:①既存セッションを終了 ② claude mcp add+名前+起動コマンド ③再起動して起動バナーの「Found N MCP servers」を確認。ステータス確認は /mcp
  • 自作ツールのドッグフーディング:この講座で作った MCP サーバーを、自分の開発環境(Claude Code)にそのまま挿せる。「アプリ向けに作ったツール」が「自分の開発アシスタントの能力」に転用できるのが MCP の標準化の旨み。
  • 開発フロー系 MCP の型を覚える:本番監視(Sentry)→ エラー自動修正、チケット(Jira)→ 仕様の取り込み、通知(Slack)→ 完了報告、ブラウザ(Playwright)→ 実装した UI の自己確認、デザイン(Figma)→ 実装の元ネタ、スクレイピング(Firecrawl)。「Claude に足りない感覚器・効果器を MCP で足す」という発想で自分のフローを棚卸しすると良い。
  • MCP プリミティブの整理(変換されたドキュメント自体が良い復習になっている):Prompts=ユーザー制御/Resources=アプリ制御/Tools=モデル制御

本文(時系列・日本語逐語+画面差し込み)

[0:00] このビデオでは、Claude Code の最も興味深い側面のひとつをお見せしたいと思います。Claude Code には MCP クライアントが埋め込まれています。 つまり、MCP サーバーを Claude Code に接続して、その機能を劇的に拡張できるということです。

[0:14] これを実演するために、私たちがずっと作ってきた MCP サーバーを Claude Code に接続します。私たちはちょうど Document Path to Markdown というツールを作ったところでした。このツールを Claude Code に公開すれば、PDF や Word ドキュメントの内容を読む能力を Claude Code に与えられます。つまり、Claude Code の能力を動的に拡張しているわけです。

🖥 [0:14] 画面(スライド:Claude Code と自作 MCP サーバーの接続図) 左のボックス Claude Code の中に MCP Client。右のボックス Our MCP Server の中に document_path_to_markdownTool we just authored(いま作ったばかりのツール)。MCP Client から矢印が伸びて接続される。

[0:31] セットアップの方法をお見せしましょう。ターミナルに戻って、実行中のセッションを Ctrl+C で終了します。それから、claude mcp add を実行して Claude Code に MCP サーバーを追加します。

[0:43] 次に、MCP サーバーの名前を入れます。名前は何でも好きなもので構いません。私たちの場合はドキュメントに関係するサーバーを作っているので、documents と名付けます。

[0:56] そして最後に、サーバーを起動するのに使うコマンドを入れます。私たちの場合は uv run main.py です。

🖥 [0:45] 画面(ターミナルでコマンド入力中) VS Code のターミナルに app git:(main) プロンプト。claude mcp add documents uv run main.py を打ち込んでいる途中のカット(claude mc… まで見える)。

[1:04] これだけです。これを実行して、claude で Claude Code を再び立ち上げます。

🖥 [1:10] 画面(再起動後の Claude Code:MCP サーバー検出) ``` ✳ Welcome to Claude Code research preview! /help for help cwd: /Users/sgrider/code/workspace/app

✔ Found 1 MCP server · /mcp ✔ Installed Vscode extension · re-open terminal to use ✔ Loaded project + user memory · /memory

✻ Tip: Start with small features or bug fixes, tell Claude to propose a plan, and verify its suggested edits

Try "write a test for " ``` エクスプローラには .mcp.json(U)CLAUDE.md が新たに並んでいる。

[1:10] これで、さきほど組み上げたあのツールを使えるようになりました。

[1:13] 実際に試すために、tests ディレクトリの中に fixtures フォルダがあります。その中に2つのデモファイル、Word ドキュメントと PDF ドキュメントが入っています。どちらも、MCP 自体に関するごく小さなドキュメントを含んでいます。

[1:26] そこで Claude に、どちらかのファイルの内容を Markdown に変換するよう頼みます。私の期待は、Claude がついさっき私たちの作ったあのツールを使ってくれることです。

[1:38] 少し上にスクロールしてみると——期待どおり、うまくいきました。これが実際にそのファイルの中身です。

🖥 [1:38] 画面(自作 MCP ツールの呼び出しと変換結果) ``` ● documents:document_path_to_markdown (MCP)(file_path: "/Users/sgrider/code/works…ce/app/tests/fixtures/mcp_docs.docx") └ # Overview The Model Context Protocol allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. ## Key Features of this Python SDK … +10 lines (ctrl+r to expand) | Primitive | Control | Description | Example Use | | --- | --- | --- | --- | | Prompts | User-controlled | Interactive templates invoked by user choice | Slash commands, menu options | | Resources | Application-controlled | Contextual data managed by the client application | File contents, API responses | | Tools | Model-controlled | Functions exposed to the LLM to take actions | API calls, data updates |

● The document has been converted to markdown format. It contains an overview of the Model Context Protocol (MCP), describing its key features, SDK capabilities, and the three core primitives: Prompts, Resources, and Tools. ```

[1:44] この MCP サーバーを消費できる能力は、Claude Code に途方もない柔軟性を与え、実に面白い開発の可能性への扉を開きます。

[1:53] たとえば、あなた特有の開発フローに関連する MCP サーバーを一式追加する、という選択もあるでしょう。

[1:58] たとえば、本番監視に Sentry を使っているなら、Sentry の MCP サーバーを追加して、本番で起きているエラーの詳細を Claude に取得させられます。

[2:07] JIRA を使っているなら、特定チケットの内容を Claude が閲覧できるようにする MCP サーバーを追加できます。

[2:13] Slack ユーザーなら、Claude が特定の問題の作業を終えるたびにあなたへメッセージを送るように、Slack を追加できます。

🖥 [1:53] 画面(スライド「Extending Claude with MCP」) 2列の表:MCP Server | Functionality - sentry-mcp — Automatically discover and fix bugs logged in Sentry - playwright-mcp — Gives Claude browser automation capabilities. Allows Claude to inspect visual components it is developing or troubleshooting - figma-context-mcp — Exposes Figma designs to Claude - mcp-atlassian — Allows Claude to access Confluence and Jira - firecrawl-mcp-server — Adds web scraping capabilities to Claude - slack-mcp — Allows Claude to post messages or reply to specific threads

[2:19] これらは、Claude Code に追加できる拡張のほんの一部にすぎません。

[2:25] ですから、あなた自身の開発ワークフローをどう強化できるか、考えてみる価値は間違いなくあります。

英語逐語(ASR全文)

In this video, I want to show you one of the most interesting aspects of CloudCode. CloudCode has an MCP client embedded inside of it. That means that we can connect MCP servers to CloudCode and dramatically expand its functionality. To demonstrate this, we are going to connect CloudCode to the MCP server that we have been working on. We just authored a tool called Document Path to Markdown. We can now expose this tool to CloudCode, allowing it to read the contents of PDF and Word documents. So we are dynamically expanding the capabilities of Claude Code. Let me show you how to set this up. Back inside of my terminal, I'm going to end my running session with a control C. I'm then going to add in an MCP server to Claude Code by executing Claude MCP add. Then we're going to put in the name for our MCP server. Now the name can be anything you want it to be. In our case, we are making a server related to documents. So I'm to call our server documents. And then finally, we're going to put in the command that we use to start up our server, which for us is uv run main.py. So uv run main.py. And that's it. I'm going to execute that and I'll start Claude code back up with Claude. And now we can make use of that tool that we just put together. To really test it out inside of our test directory, there's a fixtures folder. Inside there are two demo files. So a Word doc and a PDF doc. They both contain just a tiny bit of documentation around MCP itself. So I'm now going to ask Claude to convert the contents of either one of those files into Markdown. And my expectation is that Claude will make use of that tool that we just authored a moment ago. And if we scroll up just a little bit here, sure enough, it worked. So this actually is the contents of that file. This ability to consume MCP servers adds an incredible amount of flexibility to Claude code and really opens the door to some really interesting development opportunities. For example, you might decide to add in a series of MCP servers related to your particular development flow. For example, if you use Sentry for production monitoring, you can add a Sentry MCP server to allow Claude to fetch details about errors that are occurring in production. If you make use of JIRA, you can add in an MCP server that will allow Claude to view the contents of specific tickets. If you're a Slack user, you can add Slack to message you whenever Claude is completed working on some particular problem. These are just a small fraction of the possible enhancements that you can add into Claude code. So it would definitely be worth your time to think about how you can enhance your particular development workflow.

↑ 目次へ戻る

Building with the Claude API | Lesson 69「Agents and workflows」(全文ナレッジ)

動画 4分21秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04

日本語ナレッジ(要点整理)

このレッスンのテーマ

新モジュール「Workflows and Agents」の導入回。ワークフローとエージェントの定義と使い分けの経験則を確立し、最初のワークフローパターン Evaluator-Optimizer(評価者-最適化者) を「画像→3D モデル(STEP ファイル)」という実例で学ぶ。このビデオと続く数本はワークフローに 100% 集中する。

ワークフローとエージェントとは

  • どちらも、Claude への1回のリクエストでは完了できないユーザータスクを処理するための戦略
  • 実は本コースを通じてすでに両方を作ってきた。例:ツールのレッスンでは、タスクを Claude に投げ、提供したツールを使って完了方法を Claude 自身に考えさせた——あれはエージェントの例。
  • 使い分けの経験則(rule of thumb)
  • 完了すべきタスクの姿が非常に明確で、完了までの正確な手順列がわかっているワークフロー
  • タスクの詳細がよくわからないエージェント
  • スライドの定義(そのまま覚える価値あり):
  • WorkflowsA series of calls to Claude meant to solve a specific problem through a predetermined series of steps.(特定の問題を、事前に決めた一連のステップで解くための、Claude への一連の呼び出し)
    • 使う場面:①Claude が問題を解くために通るべき正確なフロー/ステップを思い描ける ②アプリの UX がユーザーを特定タスク群に制約している
  • AgentsClaude is given a goal and a set of tools. Claude is expected to figure out how to complete the goal through the provided tools(Claude にゴールとツール一式を与え、提供ツールでゴールを達成する方法は Claude に考えさせる)
    • 使う場面:どんなタスク/タスクパラメータを Claude に渡すことになるか、正確には分からないとき

実例:Image To CAD アプリ(ワークフローで解ける問題)

  • 想定アプリ:金属部品の画像をドラッグ&ドロップすると、3D モデルを構築して STEP ファイルを返す小さな Web アプリ。
  • STEP ファイル=3D モデルを伝達・共有するための業界標準フォーマット。
  • 実装のステップ(事前に設計し切れる): 1. ユーザーがアップロードした画像を Claude に入れ、その物体を詳細に記述させる 2. その記述を別のリクエストで Claude に渡し、CadQuery(Python の 3D ソリッドモデリングライブラリ、STEP 出力可)でモデリングさせる 3. 一発で正確にはならない可能性が高いので、エラーチェック工程を足す:モデルから普通の画像としてレンダリングを作り、それを Claude に渡して「このレンダリングは元画像をどれくらい忠実に表しているか」を問う 4. 大きな問題があればステップ2に戻して再モデリング。正確なモデルになるまで反復
  • 重要ポイント:この一連の流れは前もって想像でき、机上で設計し切れて、コードに書き下せる(講師は実際にほぼ同じものを実装した経験あり)。だからこれはワークフロー

Evaluator-Optimizer パターン

  • 上のモデリングワークフローは Evaluator-Optimizer(評価者-最適化者) と呼ばれるパターンの一例。
  • 構造(スライドの図):Input → Producer →(Submission)→ Grader →(合格なら)Output/(不合格なら Feedback を Producer に返して改善) のループ。
  • 今回の Producer=CadQuery で部品をモデリングしてレンダリングを作る Claude
  • Grader=出力(レンダリング)が基準を満たすか判定する側。満たせば終了、満たさなければフィードバックを返す
  • ループは Grader が出力を受理するまで繰り返す。

ワークフロー論の位置づけ(重要な但し書き)

  • ワークフローを「特定した」だけでは何も起きない。実際に動かすには、結局そのコードを自分で書く必要がある
  • それでもワークフローが盛んに議論される理由:多くのエンジニアが同じパターンで実装して大きな成功を収めてきたから。
  • スライトのまとめ:The goal of identifying different workflows is to give you a set of repeatable recipes for implementing your own features(ワークフローを識別する目的は、自分の機能を実装するための再利用可能なレシピ集を手にすること)/The Evaluator-Optimizer is one workflow pattern that has worked well for other engineers – consider using it in your own app!

実務ポイント(このレッスンから持ち帰るもの)

  • 迷ったらこの二分法:手順を紙に書き下せるならワークフロー(コードで制御・LLM 呼び出しを部品化)、書き下せない・入力が読めないならエージェント(ゴール+ツールを渡して委任)。Anthropic 公式の「Building effective agents」と同じ整理で、UX がタスクを絞り込んでいるアプリはワークフロー向き。
  • Evaluator-Optimizer は「生成→採点→フィードバック→再生成」のループ。Producer と Grader を分離するのが肝で、Grader には「元画像 vs レンダリング」のような照合可能な判定材料を渡す。評価(Lesson 15〜20 の eval・model-based grading)で学んだ発想の実行時適用と捉えると繋がりが良い。
  • マルチモーダルの実務パターンとして Image To CAD は示唆的:①画像→詳細テキスト記述(vision)②記述→コード生成(CadQuery)③生成物→画像レンダリング→再び vision で品質判定、とモダリティを往復させて自己修正ループを組む
  • 「パターン識別=実装ではない」を忘れない。ワークフローパターンはライブラリでも機能でもなく、成功実績のある設計レシピ。採用したら自分でコード化する(キューイング、リトライ上限、ループ打ち切り条件などは自分の設計事項)。
  • ループには終了条件を必ず入れる(Grader 受理 or 最大反復回数)。「hopefully eventually 正確なモデルに行き着く」と講師も言うとおり、収束保証はないため上限設計が実務では必須。

本文(時系列・日本語逐語+画面差し込み)

[0:00] このモジュールでは、ワークフローとエージェントに焦点を当てます。さっそく飛び込んで、これらが何なのかを理解しましょう。

[0:06] ワークフローとエージェントは、Claude への1回のリクエストでは完了できないユーザータスクを処理するために使う戦略です。

[0:13] 信じられないかもしれませんが、皆さんはこのコースを通じて、すでにワークフローとエージェントを作り続けてきました。たとえばツールについて学んだとき、私たちはタスクを Claude に投げ込み、提供したツールを使ってそれをどう完了するかを Claude に考えさせました。あれはエージェントの一例です。

🖥 [0:05] 画面(スライド「Workflows and Agents」) 左のボックス Workflows: - A series of calls to Claude meant to solve a specific problem through a predetermined series of steps.(事前に決めた一連のステップで特定の問題を解くための、Claude への一連の呼び出し) - Used when: ①You can picture the exact flow or steps that Claude should go through to solve a problem(Claude が通るべき正確なフロー/ステップを思い描ける)②The UX in your app constraints a user to a set of tasks(アプリの UX がユーザーを特定タスク群に制約している) 右のボックス Agents: - Claude is given a goal and a set of tools. Claude is expected to figure out how to complete the goal through the provided tools(Claude にゴールとツール一式が与えられ、提供ツールでゴールを達成する方法は Claude が考える) - Used when: You're not sure exactly what task or task parameters will give to Claude(どんなタスク/パラメータを渡すことになるか正確には分からない)

[0:27] さて、ワークフローとエージェントのどちらを作るべきかを決めるときに使う経験則がこれです。完了すべきタスクについて非常に precise なイメージがあり、それを完了するための正確な一連のステップがわかっているなら、ワークフローを使います。そうでなく、Claude に解かせるタスクの詳細がよくわからないなら、エージェントを使います。

[0:47] このビデオと続く数本では、ワークフローに 100% 集中します。いくつかのワークフローの例をお見せするので、さっそく最初の例を見てみましょう。

[0:54] 私たちが小さな Web アプリケーションを作っているとしましょう。このアプリの目的は、金属部品の画像を画面にドラッグ&ドロップしてもらうことです。その画像を受け取って、どうにかしてそこから 3D モデルを構築します。そしてユーザーには STEP ファイルと呼ばれるものを返します。

[1:11] STEP ファイルをご存じなくても、まったく問題ありません。STEP ファイルは、3D モデルを伝達・共有するための業界標準の方法にすぎません。要するに、画像から 3D モデルを作るのです。

🖥 [0:56] 画面(スライド「Image To CAD」アプリのモックアップ) ブラウザ枠の中に見出し Image To CAD、点線のドロップゾーン:Drag and drop an image of your part here and we'll create a STEP file for you(部品の画像をここにドラッグ&ドロップすれば STEP ファイルを作ります)。 下段に実例:金属の L 字ブラケットの写真 → 矢印 → 同形状の 3D CAD モデル。 右側の注記:STEP file: industry standard for 3D models

[1:21] さて、3D モデリングにあまり馴染みがなくても、少しの助けと少しの時間があれば、このアプリケーションを実装する方法をきっと思いつけるはずです。

[1:29] 私たちならこうやるでしょう。ユーザーがアップロードした画像を Claude に入れて、この物体を大いに詳細に記述するよう頼みます。

[1:37] 次に、その記述を別途 Claude にフィードバックして、CADQuery という Python ライブラリを使ってその物体をモデリングするよう頼みます。CADQuery は 3D ソリッドモデリングができる Python ライブラリで、そのプロセスから STEP ファイルを出力できます。

[1:53] ただ、Claude が最初の一発でこのモデルを完全に正確に作れない可能性は十分にあります。そこで、最初のモデルを組み上げたら、ちょっとしたエラーチェックのステップを追加するとよいでしょう。普通の画像としてレンダリングを作成し、その画像を Claude にフィードバックして、この画像が、ユーザーがアップロードした元の画像をどれだけよく表しているかを尋ねるのです。

[2:13] そして、レンダリングに重大な問題があると Claude が判断したら、2番目のステップに戻って、部品のレンダリングを再試行させます。このプロセスを何度も何度も繰り返せば、願わくば最終的に、元の部品のそれなりに正確なモデルにたどり着きます。

🖥 [1:22] 画面(スライド「Workflow」:手順の書き下し) 左に箇条書き: - We have a pretty good idea of exactly what to do when a user supplies an image file(ユーザーが画像を渡してきたときに何をすべきか、かなり明確にわかっている) - We can easily write all of this out with code as a predefined series of steps(これら全部を、事前定義されたステップ列としてコードに書き下せる) 縦のタイムライン: 1. Feed an image into Claude, asking it to describe the object(画像を Claude に入れ、物体を記述させる) 2. Based on the description, ask Claude to use the Cadquery library to model the object(記述を基に CadQuery でモデリングさせる) 3. Create a rendering(レンダリングを作成) 4. Ask Claude to grade the rendering against the original image. If there are issues, fix them(レンダリングを元画像と突き合わせて採点させ、問題があれば修正)→ ステップ4からステップ2へ戻る矢印 → Done 右側に Image To CAD アプリのモックアップ。

[2:26] ここで理解しておくべき大事なことは、これが前もって想像できる一連のステップの流れ全体だということです。腰を据えて、このプロセス全体を設計し切ることができます。それを実装するコードも簡単に書き出せるでしょう。実のところ、私は以前、ほとんどまさにこれと同じものを実装したことがあります。

[2:46] これらのステップすべてを事前に明示的に列挙し、詳細化できるからこそ、これをワークフローと呼ぶのです。思い出してください。ワークフローとは、非常に特定の問題を解くことを目的とした、Claude への一連の呼び出しであり、そのステップがどうあるべきかを事前に正確に知っているものと定義しました。

[3:01] 私が説明したこのモデリングワークフローは、Evaluator-Optimizer(評価者-最適化者) と呼ばれるものの一例です。

[3:07] このワークフローの考え方はこうです。何らかの入力を Producer(生産者) と呼ばれるものに押し込みます。私たちのケースでは、Producer は CADQuery ライブラリを使って部品をモデリングし、そこからレンダリングを作る Claude です。

[3:16] この出力——レンダリング——は Grader(採点者) と呼ばれるものに送られます。Grader は出力を見て、何らかの基準を満たしているかを判定します。満たしていれば、ワークフローは終了します。

[3:27] そうでなく、出力が基準を満たしていなければ、フィードバックが Producer に戻され、Producer は出力を何らかの形で改善する機会を得ます。このサイクルは、最終的に Grader が出力を受理するまで繰り返され続けます。

🖥 [3:00] 画面(スライド「Workflow Type: Evaluator-optimizer」) 左:さきほどの4ステップのタイムライン(Feed an image… → Cadquery… → Create a rendering → Grade the rendering…、4→2 の戻り矢印、Done)。 右:抽象化した図。 Input → Producer、Producer と Grader の間を Submission(提出)と Feedback(フィードバック)の矢印が循環し、Grader から Accepted(受理)で Output へ。

[3:39] さてこの時点で、この evaluator-optimizer なるものが何なのか、それなりに妥当なイメージは掴めたと思います。しかしおそらくこう思っているでしょう——「で、このワークフローというやつはいったい何の話なんだ?」と。

[3:49] ここで手短にはっきりさせておきたいことがあります。ワークフローを識別すること自体は、それだけでは本質的に何もしてくれません。 これらを実装するには、結局実際のコードを書き下ろす必要があります

[4:02] それでも私たちがワークフローを論じ、そして皆さんが今後ワークフローを人気の議論トピックとして目にすることになる唯一の理由は、他の多くのエンジニアが、まさにこれと同じパターンを使ってワークフローを実装し、大きな成功を収めてきたからです。

[4:11] 私がこれらのさまざまなワークフローをお見せするのは、皆さんが同じパターンを自分のプロジェクトで使い、他のエンジニアたちにとってうまく機能してきたこれらのパターンで、願わくば成功を収められるようにするためです。

🖥 [3:40] 画面(スライド「Workflows」まとめ) 2つのボックス: - The goal of identifying different workflows is to give you a set of repeatable recipes for implementing your own features(さまざまなワークフローを識別する目的は、自分の機能を実装するための再利用可能なレシピ集を手にすること) - The Evaluator-Optimizer is one workflow pattern that has worked well for other engineers - consider using it in your own app!(Evaluator-Optimizer は他のエンジニアでうまく機能してきたパターンのひとつ。自分のアプリでの採用を検討しよう)

英語逐語(ASR全文)

In this module, we are going to focus on workflows and agents. Let's dive in immediately and understand what these things are. Workflows and agents are strategies that we use to handle user tasks that can't be completed by Claude in a single request. Believe it or not, you have already been creating workflows and agents throughout this course. For example, when learning about tools, we fed tasks into Claude and relied upon Claude to figure out how to complete them using some provided tools. That was an example of an agent. Now here's the rule of thumb that we use whenever we are trying to decide whether to create a workflow or an agent. If we have a very precise idea of the task we need to complete, and we know the exact series of steps that are used to complete it, we'll use a workflow. Otherwise, if we're not really sure about the details of a task that Claude needs to solve, we'll use an agent. In this video and the next couple, we're going to be 100% focused on workflows. I'm going to show you several examples of workflows, so let's take a look at our first one right now. Let's imagine that we are building a small web application. The goal of this app is to allow a user to drag and drop an image of some metal part onto the screen. We're then going to take that image and then somehow build a 3d model out of it. We're then going to give the user back something called a step file. Now, if you're not familiar with a step file, totally fine. Step file is just a industry standard way of communicating or sharing 3d models. So essentially we're making a 3d model out of an image. Now, even if you are not very familiar with 3d modeling with a little bit of help and a little bit of time, I bet you could figure out a way to implement this application. Here's how we might do it. We might take the image that the user uploaded and feed it into Claude and ask Claude to describe this object in great detail. Then we could take that description, feed it back into Claude separately, and ask Claude to use a Python library called CADQuery to model the object. CADQuery is a Python library that allows you to do 3D solid modeling, and you can output a step file from that process. Now it's entirely possible that Claude is not going to get this model entirely accurate the first time around. So once we build up this initial model, we might decide to add in a little error checking step where we could create a rendering as a plain image and then feed that image back into Claude and ask it how well this image represents the original image that the user uploaded. And if Claude decides that there are major issues in our rendering, we can then go back to the second step and ask Claude to attempt to render the part again. We can then repeat this process over and over again until hopefully we eventually end up with some kind of accurate model of the original part. Now the important thing to understand here is that this is an entire flow of steps that we can kind of imagine ahead of time. We can sit down and design out this entire process. We could easily write out some code to implement it. As a matter of fact, I have previously implemented something almost exactly like this. Because we can explicitly list out and detail all these steps ahead of time, we would refer to this as a workflow. Remember, we define a workflow as a series of calls off to Claude meant to solve some very specific problem where we really know exactly what those steps are supposed to be ahead of time. This modeling workflow that I've described is an example of something we call an evaluator optimizer. The idea behind this workflow is that we push some input into something called a producer. In our case, the producer is Claude, using the CAD query library to model a part and then creating a rendering out of it. This output, the rendering, is fed into something called a grader. The grader will look at the output and decide if it meets some criteria. If it does, then the workflow ends. Otherwise, if the output doesn't meet some criteria, feedback is given back into the producer, which gets an opportunity to improve the output in some way. This cycle is then going to keep on repeating until eventually the grader accepts the output. Now, at this point in time, you've probably got a somewhat reasonable idea of what this evaluator optimizer thing is all about. but you're probably wondering, okay, what exactly is going on with these workflows? So there's just something I want to clarify here really quickly. Identifying workflows doesn't really inherently do anything for us. We still have to write down and write out the actual code to implement these things. The only reason that we are discussing workflows and the reason that you're going to see workflows as a popular discussion topic is that many other engineers have implemented workflows using these exact same patterns and found a lot of success. So the reason I'm showing you these different workflows is so that you can use these same patterns on your own projects and hopefully find some success with them because they have worked well for other engineers.

↑ 目次へ戻る

Building with the Claude API | Lesson 70「Parallelization workflows」(全文ナレッジ)

動画 3分44秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04

日本語ナレッジ(要点整理)

このレッスンのテーマ

Agents and Workflows 章のワークフローパターン第1弾、parallelization workflow(並列化ワークフロー)。題材は「部品画像をドラッグ&ドロップすると、最適な材料(metal / polymer / ceramic / composite / elastomer / wood)の分析レポートを返す Material Designer アプリ」。1つの巨大プロンプトに全部詰め込む代わりに、タスクをサブタスクに分割して並列リクエストし、最後に集約(アグリゲート)する設計を学ぶ。

ダメな実装①:短すぎるプロンプト

  • ユーザー画像+短いプロンプト(「この部品を分析して、metal / polymer / ceramic…のどれで作るべきか決めて」)を1回のリクエストで送る。
  • 一応動くが、この短いプロンプトで Claude に要求しすぎ。材料選定で考慮すべき実際の判断基準(considerations)を何も伝えていないため、ベストな結果は得られにくい。

ダメな実装②:巨大プロンプト

  • 自然な改善案は「プロンプトに詳細を足す」こと。metal を推奨すべきシナリオ、polymer を推奨すべきシナリオ…と足していくと、materialごとの判定基準を全部並べた超巨大プロンプトになる(スライド例では Metal Criteria として High Strength & Stiffness / Durability / Electrical Conductivity / Thermal Conductivity / High Operating Temperature / Machinability / Magnetic Properties / Recyclability の8項目、さらに Polymer Criteria…と続く)。
  • 1つのステップ内で大量の分析・作業をさせることになり、Claude が混乱しかねない。これもベストな結果につながらない可能性がある。

正解:並列リクエスト+アグリゲータ

  1. ユーザーが画像を投稿したら、Claude への複数リクエストを並列で発行する。
  2. 各リクエストは「画像+その材料専用の特化プロンプト」( / / / …)。1リクエスト=1材料の適合性判断なので、Claude は他の材料を気にせず1つに集中できる。
  3. 返ってきた個別の Analysis Result 群を、フォローアップリクエストで再び Claude に投入し、「各分析結果を検討して最終材料を決めて」と頼む(=アグリゲータ・ステップ)。
  4. 最終 Claude は全材料を最初から比較する必要がなく、有望そうな分析結果を見比べるだけで Final Material Recommendation を出せる。

parallelization workflow の定義(スライド)

  • 1つのタスクを複数のサブタスクに分割する
  • サブタスクを並列に実行する
  • 結果を集約(aggregate)する
  • 並列サブタスクは同一である必要はない——それぞれが特化プロンプト・特化ツールセットなどを持てる
  • 図式:User Task → Parallelizable Sub-Task ×N →(合流)→ Aggregator

この設計の3つのメリット

  1. Claude が一度に1タスクへ集中できる。巨大プロンプトで全材料の長所短所を同時に考えさせると混乱・注意散漫が起きうる。
  2. 各サブタスクのプロンプトを個別に改善・評価しやすい(プロンプト評価の単位が小さくなる)。
  3. スケールしやすい。他のサブタスクへ影響を与えずに、いつでもサブタスクを追加できる。

実務ポイント(このレッスンから持ち帰るもの)

  • 「1プロンプトに判断基準を全部詰める」は アンチパターン。基準が材料×8項目のように増殖し始めたら、並列化ワークフローへの分割シグナル。
  • 分割の単位は「独立して判断できる軸」(この例では材料の種類)。各サブタスクにはその軸専用の特化プロンプトを書く——Claude は1リクエストで1つの観点だけ考えればよくなる。
  • アグリゲータも Claude 呼び出しとして実装する。入力は各サブタスクの Analysis Result 群、指示は「各結果を検討して最終推奨を出せ」。生入力(画像そのもの)を再送する必要はなく、結果テキストだけで判断させられる。
  • 並列サブタスクは同一形である必要はない:プロンプトもツールも別々でよい(スライドで明言)。「同じ質問をN回」ではなく「観点別にN種類」が本パターンの本領。
  • 運用メリットとして評価(evals)との相性が良い:サブタスク単位でプロンプトを改善・計測できる(このコースの評価章で学んだ手法をサブタスク単位に適用できる)。
  • 拡張が安い:新しい材料(新観点)のサブタスクを追加しても他のサブタスクに影響しない。スケール要件があるワークフローでは最初から並列化を選ぶ価値がある。
  • 実装面では各リクエストが独立なので、asyncio.gather などで同時発行してレイテンシを短縮できる(動画では概念のみ、コード実装は後続レッスン)。

本文(時系列・日本語逐語+画面差し込み)

[0:00] 別のワークフローを見ていきましょう。今回はアプリケーションを少し変更します。

[0:05] 引き続き、ユーザーには部品(part)の画像を画面にドラッグ&ドロップしてもらいます。ただし今回は、ユーザーに分析——さまざまな基準に応じて、その部品をどの材料で作るのがベストかを教えてくれるレポート——を返すことにします。

🖥 [0:05] 画面(スライド:Material Designer アプリのモック) ブラウザ枠のワイヤーフレーム。タイトル Material Designer。 破線の枠内に:Drop an image of your part here and we'll give you an analysis of the best material to build your part out of(部品の画像をここにドロップすると、その部品を作るのに最適な材料の分析を返します) 左下に ANTHROP\C ロゴ。

[0:18] この機能を実装するには、ユーザーが提供した画像を、短いプロンプトと一緒に Claude へ送る、という手が考えられます。プロンプトの中では、この部品を metal(金属)、polymer(ポリマー)、ceramic(セラミック)などのどれで作るのがベストかを Claude に決めてもらいます。

🖥 [0:19] 画面(スライド:短いプロンプトのシーケンス図) 「Server」と「Claude」の2本のライフライン。User Message が1つ: - Image Block<Image> - Text BlockAnalyze the part in the attached image then decide if it would be best to make it out of a metal, polymer, ceramic, composite, elastomer, or wood.(添付画像の部品を分析し、metal / polymer / ceramic / composite / elastomer / wood のどれで作るのがベストか決めよ) → 矢印が Claude へ。

[0:27] さて、これはおそらく動くには動きます。しかし、この非常にシンプルなプロンプトで、私たちは Claude に本当に多くを求めすぎています。例えば、どの材料を使うか決める際に考慮すべき実際の判断基準を、私たちは Claude にまったく伝えていません。

[0:43] つまり、これでも動くかもしれませんが、ベストな結果は得られないかもしれません。ここでの自然な改善は、おそらくこのプロンプトに戻って、もっと多くの詳細を足すことでしょう。metal や polymer を推奨すべきさまざまなシナリオを Claude に教える、といった具合です。

[0:57] そうすると、こんな感じの本当に本当に大きなプロンプトになるかもしれません。metal を使うべきか判断する基準をいくつか与え、次に polymer を使うべきか判断する基準を与え、それを ceramic、composite、elastomer、wood…と繰り返します。

🖥 [0:57] 画面(スライド:巨大な基準列挙プロンプト) 「Server」→「Claude」のシーケンス図。User MessageImage Block<Image>Text Block(長文): Analyze the part in the attached image. Based on the visual cues and your understanding of material properties, decide if it would be best to make it out of a metal, polymer, ceramic, composite, elastomer, or wood. Metal Criteria:(部品を金属で作るべきか判断する基準) - High Strength & Stiffness(大きな荷重に変形せず耐える必要があるか) - Durability(摩耗・衝撃・疲労への耐性が重要か) - Electrical Conductivity(導電性が必要か) - Thermal Conductivity(効率的な熱伝達が必要か) - High Operating Temperature(使用中に高温に晒されるか) - Machinability(機械加工による複雑な形状・高精度公差が必要か) - Magnetic Properties(磁性の有無が重要か) - Recyclability(リサイクル容易性が優先事項か) Polymer Criteria:(ポリマーで作るべきか判断する基準)…(以下続く)

[1:12] 結果として、本当に大きなプロンプトができあがり、それは Claude を少し混乱させることになるかもしれません。1つのステップの中で、大量の分析と大量の作業をしなければならないからです。ですから、これはベストな結果につながらないかもしれません。

[1:22] この機能を実装する、もっと良いアプローチをお見せしましょう。

[1:25] ユーザーが最初に画像を投稿したときに、Claude への複数の異なるリクエストを並列で発行する、という設計にできます。

🖥 [1:26] 画面(スライド「Requests in Parallel」) 「Server」→「Claude」のシーケンス図。中央に見出し Requests in Parallel。 4つの User Message が並列の矢印で Claude へ送られる: 1. Image Block<Image>Text Block\<Metal Criteria> 2. Image Block<Image>Text Block\<Polymer Criteria> 3. Image Block<Image>Text Block\<Ceramic Criteria> 4. Image Block<Image>Text Block\<Composite Criteria>

[1:32] 個々のリクエストには、それぞれ特化したプロンプトを含めます。この部品を metal で作るのは良いアイデアか、polymer ではどうか、ceramic では、composite では…と Claude に尋ねるのです。

[1:43] つまり、それぞれ別のリクエストで、この部品を1つの材料で作った場合の適合性を Claude に尋ねています。

[1:49] このアプローチなら、個々のプロンプトをその材料専用に特化させられます。そして Claude は、すべての材料について気を揉む必要がなくなります。一度に1つの材料だけに集中すればよいのです。

[2:00] さて、やがて Claude からレスポンスがいくつか返ってきたら——ここで図の構成を少し変えます。全部を1画面に収めるためです。

🖥 [2:03] 画面(スライド:並列化フロー全体図の組み替え中) 4列のフローチャート。最上段に Metal criteria / Polymer criteria / Ceramic criteria / Composite criteria の4箱(Ceramic criteria の箱が選択ハンドル付き=講師が配置調整中)。 各列がそれぞれ Claude の箱へ → その下に Analysis Result の箱 → 4本の矢印が横長の Claude の箱に合流 → Final Material Recommendation へ。

[2:06] Claude から、これらの個別の分析結果が返ってきます。それぞれが、与えられた部品を metal、polymer、ceramic、composite などで作った場合の適合性を教えてくれます。

[2:17] そこで、これらの分析結果を1つずつ取り出して、フォローアップリクエストで再び Claude に投入します。そして Claude に、各分析結果を検討して、使用する最終的な材料を決定するよう頼みます。

[2:28] これで Claude は、これらすべての材料を最初から比較する心配をしなくてよくなります。その代わり、最も有望に見える分析結果だけを見ればよいのです。

[2:36] これが parallelization workflow(並列化ワークフロー) の一例です。

🖥 [2:37] 画面(スライド「Workflow Type: Parallelization」) 左側に箇条書き: - Split a single task into multiple sub-tasks(1つのタスクを複数のサブタスクに分割する) - Run the sub-tasks in parallel(サブタスクを並列に実行する) - Aggregate the results together(結果をまとめて集約する) - The parallelized sub-tasks don't need to be identical - each can have a specialized prompt, set of tools, etc.(並列化するサブタスクは同一である必要はない——それぞれが特化したプロンプトやツールセットを持てる) 右側に汎用図:User TaskParallelizable Sub-Task ×3(並列)→ 合流 → Aggregator

[2:40] 並列化ワークフローの背後にある考え方は、1つのタスクを複数の異なるサブタスクに分割する、というものです。これらのサブタスクは並列に、つまり同時に実行できます。その後、それらすべてのサブタスクの結果を取り出し、最後のアグリゲータ(aggregator)ステップで1つに結合します。

[2:56] 私たちのケースでは、アグリゲータは、ここにある Claude を使った最後のステップでした。各並列タスクの結果をアグリゲータに投入し、Claude がこの最終推奨(Final Material Recommendation)を返してくれたわけです。

🖥 [2:58] 画面(スライド:並列化フロー全体図・完成版) 4列フローチャートの完成形。Metal / Polymer / Ceramic / Composite criteria → 各 Claude → 各 Analysis Result → 合流して横長の Claude(=アグリゲータ)→ Final Material Recommendation

[3:06] このワークフローにはいくつかのメリットがあります。

[3:08] 第一に、Claude が一度に1つのタスクに集中できること。ついさっき言ったことを思い出してください。元の部品画像を、多数の材料タイプの基準を列挙した本当に大きなプロンプトと一緒に Claude へ投入することもできました。そのシナリオでは、Claude はすべての材料の長所と短所を同時に検討しようとして、少し混乱したり注意散漫になったりするかもしれません。

🖥 [3:12] 画面(スライド:巨大な基準列挙プロンプトの再掲) [0:57] と同じ「Metal Criteria: / Polymer Criteria: …」の長文プロンプトのシーケンス図に戻って提示(「大きなプロンプト」の比較対象として振り返り)。

[3:26] 第二のメリットは、各サブタスクの中で使われているプロンプトを、非常に簡単に改善・評価できることです。

🖥 [3:28] 画面(スライド「Workflow Type: Parallelization」再掲) メリットの説明に合わせて、箇条書き+ User Task → Parallelizable Sub-Task ×3 → Aggregator の汎用図に戻る。

[3:33] 最後に、このフローは一般に非常によくスケールします。実行中の他のサブタスクから何かを差し引くことなく、望むならいつでも追加のサブタスクを足すことができるのです。

英語逐語(ASR全文)

Let's take a look at another workflow. We're going to change up our application a little bit this time around. We're still going to ask the user to drag and drop an image of a part onto the screen, but this time we're going to give the user back an analysis, a report that's going to tell the user the best material to build their part out of, depending upon some various criteria. To implement this feature, we might take the user's supplied image and send it off to Claude along with a short prompt. and in the prompt we might ask Claude to decide whether it would be best to make this part out of metal, polymer, ceramic, and so on. Now this would probably work but we are really asking a lot out of Claude in this very simple prompt. For example we haven't really told Claude any of the real considerations that it should take into account when deciding which material to use. So even though this might work we might not get the best results. So a natural improvement here would be probably to go back to this prompt and add in a lot more detail. Maybe tell Claude some the different scenarios in which it should recommend metal or polymer and so on. So we might end up with a really, really large prompt like this. We might give some criteria for deciding when to use metal and then some criteria for deciding when to use polymer and then repeat with ceramic, composite, elastomere, wood, and so on. We would end up with a really, really large prompt that might end up being a little bit confusing to Claude because it has to do a lot of analysis and a lot of work inside of one single step. So this might not lead to the best results. Let me show you a better way to approach implementing this feature. We could decide to make a series of different requests in parallel off to Claude whenever a user initially submits an image. Each individual request could then include a specialized prompt asking Claude if making this given part would be a good idea using metal, or polymer, or ceramic, or composite, and so on. So in each separate request, we are asking Claude for the suitability of building this part in one individual material. With this approach, we could specialize each individual prompt for the given material. And now Claude doesn't have to worry about all these different materials. It's really just focused on one individual material at a time. Now, when we eventually get some responses back from Claude, I'm going to change the structure of this diagram just a little bit so I can fit everything on one screen. So we're going to get back these individual analysis results from Claude. Each one is going to tell us the suitability of building out the given part in, say, metal, polymer, ceramic, composite, and so on. We can then take each of these analysis results and then feed them back into Claude in a follow-up request and ask Claude to consider each of the different analysis results and decide upon a final material to use. Now Claude doesn't really have to worry about comparing all these different materials up front. Instead, it can just take a look at the analysis results that seem to be the most promising. This is an example of a parallelization workflow. The idea behind a parallelization workflow is that we're going to take one task and break it up into multiple different subtasks. Each of these subtasks can be ran in parallel. So at the same time, we will then take the results from all those different subtasks and then join them all together in a final aggregator step. In our case, the aggregator was this final step with Claude right here. So we fed the results of each parallel task into the aggregator and Claude gave us this final recommendation. There are several benefits to this workflow. First, it allows Claude to focus on one task at a time. So remember just a moment ago, and I told you that we could feed the original image part into Claude with a really large prompt that listed out some criteria for many different material types. In this scenario, Claude might get a little bit confused or distracted as it tried to consider all the different pros and cons of each material simultaneously. The second benefit is that we can very easily improve and evaluate the prompts that are being used inside of each subtask. Finally, this flow can generally scale very well. We can add in additional subtasks at any point if we want to without really subtracting from the other subtasks that are being executed.

↑ 目次へ戻る

Building with the Claude API | Lesson 71「Chaining workflows」(全文ナレッジ)

動画 4分36秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04

日本語ナレッジ(要点整理)

このレッスンのテーマ

ワークフローパターン第2弾、chaining workflow(チェイニング/プロンプト連鎖ワークフロー)。一見「当たり前で単純」に見えるが、講師いわく実際には最も有用なワークフローの1つ。特に「大きなプロンプトで制約(constraints)を大量に並べたのに Claude が守ってくれない」という、誰もが必ず一度は遭遇する状況への決定打になる。

題材:ソーシャルメディア・マーケティングツール

  • ユーザーがアカウントのテーマ(topic)を入力 → アプリが動画を自動生成して SNS に投稿する。
  • 凝ったツール満載のエージェントは不要。事前定義されたステップを1つずつ進むワークフローで実装できる: 1. 入力トピックから Twitter のトレンドトピックを検索 2. トピック一覧を Claude に渡して一番面白いトピックを選ばせる(Use Claude here) 3. フォローアップリクエストでそのトピックの Web リサーチ(Use Claude here) 4. リサーチ完了後、ショート動画の台本を書かせる(Use Claude here) 5. AI アバター+TTS で動画を生成 6. SNS に投稿

chaining workflow の定義(スライド)

  • 大きなタスクを、並列化できない小さなサブタスク群に分割する(Input → Processing Task #1 → #2 → #3 → Output の直列)
  • 各タスクの間に、LLM 以外の処理(non-LLM processing)を挟んでもよい
  • Claude を全体タスクの1つの側面に集中させ続けるのに有効
  • 上の例では「トピック選定・リサーチ・台本執筆」を1つのプロンプトに詰めることもできたが、3つの別々の呼び出しに分けることで、Claude は一度に1つの個別タスクだけに集中できる

本命ユースケース:「制約を守らない Claude」への処方箋

  • シナリオ:Claude に記事を書かせると、①「私は AI です」と自己言及する、②絵文字を使いすぎる、③陳腐(cliché)な表現を多用する——といった不満が出る。
  • 対策としてプロンプトに CONSTRAINTS(DO NOT のリスト) を積み上げていく(スライド例:「用語定義だけで終わるな」「単純すぎる言葉遣いをするな」「見下した口調・過度に技術的な口調を使うな」「無関係な個人的意見や逸話を入れるな」「AI・言語モデルであることに言及するな。人間のエキスパートテクニカルライターとして書け」「絵文字を使うな!」「陳腐でサムい文章を書くな!」)。
  • しかし何度制約を繰り返しても、絵文字だらけ・AI 自己言及・非プロフェッショナルな寄りの記事が返ってくることがある(スライドの失敗例:「🧙✨ Unleashing the Magic of Python Functions! ✨🐍 Hello, future coders and curious minds! 👋 It's your friendly neighborhood AI assistant…」)。
  • 解決策=プロンプト連鎖: 1. 制約入りの長いプロンプトを投げ、制約違反を含む初稿が返ってくることを受け入れる。 2. フォローアップリクエストで、Claude が書いたばかりの記事を渡し、書き直しを指示する:
    • 記事を下に示す。以下の手順で書き直せ:
    • 1. 著者が AI であると明かしている箇所を特定して削除
    • 2. 絵文字をすべて見つけて削除
    • 3. サムい文章を見つけて、テクニカルライターが書くような文章に置き換える
  • タスクを複数ステップに分割することで、Claude は提示された個々のタスクに大きく集中できる。最初の長いプロンプトで全要件を満たせなくても、フォローアップで「本当に大事な制約」だけに集中させれば、望むスタイルに書き直させられる。

使いどころ

制約が多いタスクで、Claude が期待したほど制約に従ってくれないとき、いつでも。単純に見えるが使用頻度は非常に高い。

実務ポイント(このレッスンから持ち帰るもの)

  • 「生成→修正」の2段チェーンを標準装備にする。制約 DO NOT リストを膨らませ続けるより、「初稿を受け入れて、違反項目だけを列挙した書き直しプロンプトを2発目に投げる」方が確実に効く。
  • 書き直しプロンプトの型:記事全文を <article>{article}</article> などで渡し、番号付き手順で修正指示(①AI 自己言及の削除、②絵文字の全削除、③陳腐表現をプロの文体へ置換)。修正観点を明示的に分けるのがコツ。
  • チェーンの各ステップは Claude 呼び出しとは限らない:Twitter 検索・動画生成・SNS 投稿のような non-LLM 処理を間に挟んでよい(スライドで明言)。ワークフロー=「LLM 呼び出し+通常コード」の直列パイプライン。
  • 1つの巨大プロンプトに複数タスク(選定+リサーチ+執筆)を詰めない。個別呼び出しに分ければ、各ステップの入出力が明確になり、品質も評価もデバッグも楽になる。
  • 並列化ワークフロー(前レッスン)との違い:チェイニングは前段の出力が次段の入力になる(並列化できない)タスク分解。分割軸が独立なら並列化、依存があるならチェイニング。
  • 「凝ったエージェント+大量のツール」を最初から作らない。予め決まった手順を1つずつ実行するだけのワークフローで足りるケースは多い(この SNS ツールも predefined steps で実装可能)。
  • 制約が守られない問題はモデルの気まぐれではなく設計の問題として扱う:制約の反復追加はいずれ限界に達する、というのが講師の実務所見。チェーン分割が正攻法。

本文(時系列・日本語逐語+画面差し込み)

[0:00] 次に見ていくワークフローは、ちょっと当たり前で単純に思えるでしょう。でも信じてください。これは実際には、最も有用なワークフローの1つなのです。特に、皆さんが非常に頻繁に遭遇することになる、ある特定の状況においてはそうです。では始めましょう。

[0:13] アプリケーションを少し変えましょう。今回も、ソーシャルメディア・マーケティングツールを作っていると想像してください。

🖥 [0:15] 画面(スライド:Social Media Marketing アプリのモック) ブラウザ枠のワイヤーフレーム。タイトル Social Media Marketing。 説明文:Enter a topic and we'll create and post videos to your social media accounts(トピックを入力すると、あなたの SNS アカウントに動画を作成・投稿します) Topic の入力欄と Start ボタン。

[0:19] ユーザーには、自分の SNS アカウントのテーマ——アカウントのフォーカスが何なのか——となるトピックを入力してもらいます。そして、このアプリケーションのゴールは、動画をいくつか生成して、そのアカウントに投稿することです。

[0:30] では、これを実際にどう実装するか。これを実装するのに、大量の凝ったツールを持つ凝ったエージェントは必要ありません。事前に定義された一連のステップを1つずつ進んでいくだけのワークフローを組めばよいのです。

🖥 [0:37] 画面(スライド「Workflow to automate the creation and posting of videos to social media」) 縦のタイムラインに6つのステップ: 1. Find related trending topics on Twitter(Twitter で関連するトレンドトピックを探す) 2. Select the most interesting topic(最も面白いトピックを選ぶ)— 右注釈 Use Claude here 3. Research the topic(トピックをリサーチする)— Use Claude here 4. Write a script for a short format video(ショート動画の台本を書く)— Use Claude here 5. Use an AI avatar and text to speech to create a video(AI アバターと TTS で動画を作る) 6. Post the video to social media(動画を SNS に投稿する)

[0:42] ステップ1では、ユーザーがフォームに入力したトピックを受け取り、Twitter でトレンドトピックを検索します。

[0:49] 次に、そのトピックのリストを全部 Claude に投入し、最も面白いトピックを選ぶよう Claude に頼みます。

[0:56] それから、Claude へのフォローアップリクエストで、そのトピックについて Web リサーチをするよう頼みます。

[1:01] リサーチが完了したら、ショートフォーマット動画の台本を書くよう Claude に頼みます。

[1:06] 台本ができたら、AI アバターと何らかの text-to-speech プログラムを使って実際の動画を作成し、最後にその動画を SNS に投稿します。

[1:14] これが chaining workflow(チェイニングワークフロー) の一例です。

🖥 [1:15] 画面(スライド「Workflow Type: Chaining」) 左側に箇条書き: - Split a large task into smaller non-parallelizable subtasks(大きなタスクを、並列化できない小さなサブタスク群に分割する) - Optionally, do non-LLM processing in between each task(任意で、各タスクの間に LLM 以外の処理を挟む) - Useful for keeping Claude focused on one aspect of the overall task(Claude を全体タスクの1つの側面に集中させ続けるのに有効) 右側に直列フロー図:Input → Processing Task #1 → Processing Task #2 → Processing Task #3 → Output

[1:17] チェイニングワークフローでは、1つの大きなタスク——当初は「動画を生成して SNS に投稿する」でした——を、一連の明確に区別されたステップに分割します。

[1:23] 私たちのケースでは、サブタスクは Claude へ送った個々の呼び出しでした。これら3つのタスクを、Claude への1回の呼び出しの中で全部こなそうとすることもできたはずです。

🖥 [1:29] 画面(スライド:6ステップのタイムライン再掲・Claude 使用の3ステップを選択) [0:37] のタイムラインに戻り、Select the most interesting topic / Research the topic / Write a script for a short format video の3つ(それぞれ Use Claude here 付き)が青い選択枠でまとめてハイライトされている。

[1:32] つまり、トピックのリストを Claude に投入して、最も面白いトピックを選び、そのトピックをリサーチし、台本まで書く——これを1つのプロンプトの中で頼むこともできました。しかし、これを3つの別々の呼び出しに分割することで、Claude は一度に1つの個別タスクだけに集中できるようになります。

[1:53] さて、先ほど言ったように、これはおそらく単純で当たり前のワークフローに見えるでしょう。過去にすでに実装したことがあるかもしれず、議論する価値すらないと思われるかもしれません。

[2:02] しかし、私がこのワークフローを特に取り上げるのには、1つの非常に特定の理由があります。かなり大きなプロンプトを使うときに、Claude から安定して高品質な出力を得るために理解すべき、最も重要なワークフローの1つになるからです。

[2:13] では、ちょっとしたシナリオを一緒に見ていきましょう。まだ遭遇したことがないかもしれませんが、いつかどこかの時点でほぼ確実に遭遇する、と私は保証できます。

[2:17] あなたが Claude を使って、あるトピックの記事を書かせているとしましょう。最初はごくシンプルなプロンプトを Claude に送り、記事を書くよう頼むかもしれません。すると結果が返ってきますが、それなりの出来ではあっても、気に入らない側面がいくつかあるかもしれません。

[2:32] 最初に気づくのは、Claude が「この記事は AI が執筆している」という事実に言及していること——これはおそらく望まないでしょう。絵文字を過剰に使っているかもしれない——これも望まないかもしれない。そして、そこら中でちょっと陳腐(cliché)な言い回しを使っているかもしれない——これも、やはり望まないでしょう。

[2:48] そこで時間をかけてこのプロンプトを育てていくうちに、やがて「Claude に絶対やるなと伝えること」の長い長いリストを作り上げることになるかもしれません。

🖥 [2:50] 画面(スライド:制約リスト付きの記事執筆プロンプト) 「Server」→「Claude」のシーケンス図。User Message(長文): Write a comprehensive, high-quality article on a particular programming technique, suitable for publication on a reputable technical blog or documentation platform.(評判の良い技術ブログやドキュメントプラットフォームに掲載できる水準の、特定のプログラミング技法に関する包括的で高品質な記事を書け) The programming technique you are required to write about is: <article_subject> ${article_subject} </article_subject> CONSTRAINTS (ABSOLUTELY DO NOT DO THE FOLLOWING): - DO NOT simply define the term and stop.(用語を定義しただけで終わるな。記事は包括的で、要求構成の全セクションをカバーせよ) - DO NOT use overly simplistic language throughout;(全体を通して単純すぎる言葉遣いをするな。基本概念の説明は必要だが、経験豊富な読者にも応えよ) - DO NOT use a condescending or overly technical tone that alienates readers.(読者を遠ざける見下した/過度に技術的な口調を使うな) - DO NOT include personal opinions or anecdotes that are not directly relevant.(技法の説明に直接関係ない個人的意見や逸話を入れるな) - DO NOT mention or discuss the fact that you are an AI or a language model. Write as if you are a human expert technical writer.(自分が AI・言語モデルである事実に言及・議論するな。人間のエキスパートテクニカルライターとして書け) - DO NOT use emojis!(絵文字を使うな!) - DO NOT use cliched or cringey writing!(陳腐でサムい文章を書くな!) ※直後の場面では最後の「use cliched or cringey」の部分がドラッグ選択されて強調される。

[2:56] しかし必然的に——これらの項目を何度繰り返しても——Claude は、なぜかいつも絵文字を使い、AI によって書かれたことに言及し、全体的にどこかサムくて非プロフェッショナルなトーンの応答を返してくることがあります。

🖥 [3:00] 画面(スライド:制約違反レスポンスの実例) シーケンス図:User Message<Long Prompt>Assistant Message(返ってきた記事の冒頭): 🧙✨ Unleashing the Magic of Python Functions! ✨🐍 Hello, future coders and curious minds! 👋 It's your friendly neighborhood AI assistant here, ready to guide you through one of the most fundamental and powerful concepts in Python: Functions! 🎉 I've processed countless lines of code and documentation, and trust me, understanding functions is like unlocking a superpower! 💪 Let's dive in! 🚀 Intro: What's the Deal with Functions Anyway? 🤔 Ever found yourself writing the same lines of code over and over? 😩 … Copy-pasting code is messy, hard to update, and makes your programs look like a spaghetti monster! 🍝 That's where functions come to the rescue! ✨ … Think of them as tools in your coding toolkit! 🛠 My AI brain sees patterns everywhere…! 🧠 ※「絵文字だらけ」「AI 自己言及(friendly neighborhood AI assistant / My AI brain)」「サムいトーン」という3つの違反が全部入った失敗例。

[3:11] そして Claude は、こうした制約——やってはいけないことのリスト——を何度繰り返しても、このような記事を書き続けるかもしれません。

[3:20] この問題に対処するために、ごくシンプルなプロンプト連鎖ワークフローが使えます。やり方はこうです。まず、これらの制約が全部入ったあの長い最初のプロンプトを投入します。そして、求めている水準に達しない初稿の記事が返ってくることを、いったん受け入れるのです。Claude は必然的に、あなたが並べた制約のいくつかに違反することを選ぶかもしれません。

[3:39] そうした問題を修正するために、今度は Claude へフォローアップリクエストを送ります。Claude が書いたばかりの記事を渡すのです。

[3:45] そして記事の下に、その記事を特定のやり方で書き直すよう Claude に頼みます。

🖥 [3:46] 画面(スライド:チェーンによる修正フロー全体) シーケンス図に3つ目のメッセージが追加された完成形: 1. User Message<Long Prompt> 2. Assistant Message<Response that violates many constraints from the original message>(元メッセージの制約の多くに違反した応答) 3. User Message(書き直し指示): Revise the article provided below.(下に示す記事を修正せよ) <article> {article} </article> Follow these steps to rewrite the article:(以下の手順で記事を書き直せ) 1. Identify any location where the text identifies the author as an AI and remove them.(本文中で著者を AI と明かしている箇所をすべて特定し、削除する) 2. Find and remove all emojis(絵文字をすべて見つけて削除する) 3. Locate any cringey writing and replace it with text that would be written by a technical writer(サムい文章を見つけ、テクニカルライターが書くであろう文章に置き換える)

[3:49] 例えばこう言えます。「著者が AI であると名乗っている箇所を見つけて、その言及を削除せよ。絵文字をすべて見つけて削除せよ。そして、プロのテクニカルライターがやるようなやり方で文章を書け」。

[4:00] このチェイニングワークフローを使い、タスクを複数のステップに分割することで、Claude は提示された個々のタスクに、はるかに強く集中できるようになります。

[4:08] ですから、最初の長いプロンプトに入れた要件をすべては満たせなかったとしても、フォローアップのプロンプトによって、Claude はあなたが本当に気にかけている制約だけに集中でき、望むスタイルで記事を書き直してくれることが期待できます。

[4:22] 繰り返しになりますが、プロンプト連鎖は当たり前で単純なものに見えるとしても、実際にはかなり頻繁に使うことになるものです。Claude へのタスクに多くの制約があり、Claude が期待するほどには制約に従ってくれないように見えるときは、いつでもこれの出番です。

🖥 [4:24] 画面(スライド「Workflow Type: Chaining」再掲・締め) 締めの言葉に合わせて、Chaining の定義スライド(箇条書き+ Input → Processing Task #1〜#3 → Output)に戻って終了。

英語逐語(ASR全文)

The next workflow that we're going to take a look at is going to seem a little bit obvious and simple, but trust me, this actually ends up being one of the most useful workflows around, specifically in one particular situation that you're going to run into very often. So let's get to it. Let's change up our application a little bit. Once again, we'll imagine that we are building a social media marketing tool. Users will be asked to enter a topic for when their social media accounts, kind of what the focus of their account is. Then the goal of our application is to generate and post some videos to their account. So here's how we might actually implement this. We don't really need a fancy agent with a ton of fancy tools to implement this. We can build out a workflow that will just go through a series of predefined steps one by one. So in step one, we might take whatever topic the user entered into that form and do a search for trending topics on Twitter. We could then take the list of topics and feed them all into Claude and ask Claude to select the most interesting topic. Then we could do a follow-up request to Claude, asking it to do some web research on the topic. Then once the research is complete, we could ask Claude to write a script for a short format video. Once we have the script, we could then use an AI avatar and some text-to-speech program to create an actual video, and finally post that video off to social media. This is an example of a chaining workflow. In a chaining workflow, we take one large task, which was initially to generate some videos and post them to social media, and we break it up into a series of distinct steps. In our case, the subtasks were the individual calls that we sent off to Claude. We could have attempted to accomplish all three of these tasks inside of a single call off to Claude. So we could have just fed in a list of topics to Claude, asked it to select the most interesting topic and research the topic and write a script inside of a single prompt. But by breaking this up into three separate calls, we allow Claude to focus on just one individual task at a time. Now, like I said, this probably seems like a kind of simple and obvious workflow, maybe not even worth discussing because it might be something you've already implemented in the past. But there's one very particular reason that I point out this workflow in particular, because it actually ends up being one of the most important workflows to understand to get quality outputs out of Claude consistently when using rather large prompts. So let me walk you through a little scenario. You might not have encountered this before, but I can almost guarantee you that you will at some point in time. Let's imagine that you are making use of Claude to write a article on some given topic. You might initially just send in a very simple prompt to Claude and ask it to write an article, and then you might get back some results, and although it might be okay, there might be some aspects to it that you don't like. So you might initially find that Claude might be mentioning the fact that it is an AI authoring the article, which you probably don't want. It might make excessive use of emojis, which you might not want, and it might use a little bit cliched language all over the place, which again, you might not want. And so over time, as you start to develop this prompt, you might eventually set up a big long list of things that you tell Claude to just not do it all. But inevitably, Claude might eventually, no matter how many times you repeat these items, Claude might give you back a response that seems to somehow always use emojis, mention the fact that it has been written by an AI, and just generally have kind of a cringy, non-professional tone to it. And Claude might persist in writing an article like this, no matter how many times you repeat these constraints or things that Claude should not do. So to address this problem, you can use a very simple prompt chaining workflow. Here's what you might do. You might feed in that initial long prompt that has all these constraints in it, and then just accept that you are going to get back some initial article that doesn't really fit the bill of what you're looking for. Claude might inevitably decide to violate some of those different constraints you laid out. To fix those issues, you could then make a follow-up request back to Claude, providing the article that Claude just wrote. And underneath the article, you could ask Claude to rewrite the article in some particular way. So you could say, find any location where the author identifies as an AI and remove that mention, find and remove all emojis, and then write the text in a way that a professional technical writer would do it. By using this chaining workflow and breaking the task up into multiple steps, you allow Claude to focus much more on each individual task presented to it. So even though it might not really satisfy all the requirements you put into the long prompt originally, the follow-up prompt allows Claude to focus on just the restrictions that you really care about, and will hopefully rewrite the article in a style that you are really looking for. So once again, even though prompt chaining seems like something kind of obvious and simple, this does end up being something that you're going to use rather often anytime that you have a task for Claude with many constraints, and Claude doesn't seem to be always following those constraints as much as you might expect.

↑ 目次へ戻る

Building with the Claude API | Lesson 72「Routing workflows」(全文ナレッジ)

動画 3分17秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04

日本語ナレッジ(要点整理)

このレッスンのテーマ

ワークフローパターン第3弾、routing workflow(ルーティングワークフロー)。前レッスンのソーシャルメディア・マーケティングツール(トピック入力→動画生成→SNS 投稿)を改善する形で学ぶ。まず Claude にユーザー入力を分類(categorize)させ、その分類結果に応じて専用の後続パイプラインへ振り分ける設計。

問題意識:トピックによって台本の性質は全然違うべき

  • 台本生成プロセス——動画で使われるトーンと言葉遣い——を考える。
  • 例:トピックが programming(プログラミング) なら、情報量が多く、丁寧に説明された定義がある、教育的(educational)な動画台本が欲しい(スライドの台本例:「(0-3秒) Python コードを整理したい?繰り返しを避けたい? (3-8秒) Python 関数の習得は基本です。関数は特定のタスクを実行する再利用可能なコードブロックで…」)。
  • 例:トピックが surfing(サーフィン) なら、教育色の薄い、まったく別物の台本が欲しい。「サーフィンとは何か」の長い定義など要らない(台本例:「(0-3秒) 海に乗るってどんな感じか、考えたことある? (3-8秒) 自然の力だけで水面を滑る——それがサーフィンの魔法。…(25-35秒) でも波をつかまえたその瞬間…最高なんだ!」)。
  • つまり同じ台本生成プロンプトを全トピックに使うのは無理筋

実装手順

  1. 事前に、ユーザーが依頼しそうな動画のジャンルを洗い出す。この例では6ジャンル:entertainment / educational / comedy / personal vlog / reviews / storytelling。programming → educational、surfing → entertainment に該当。
  2. ジャンルごとに専用の台本生成プロンプトを書く(スライドの対応表): - Entertainment:Write a high-energy, culturally-relevant script about [TOPIC] using trendy language and engaging hooks…(トレンド感のある言葉と引き込むフックを使った、ハイエナジーで文化的関連性の高い台本) - Educational:Develop a clear, engaging script about [TOPIC] that transforms complex information into digestible insights using relatable examples and thought-provoking questions.(複雑な情報を消化しやすい洞察に変える、明快で魅力的な台本) - Comedy/Personal vlog/Reviews/Storytelling にも各1本。
  3. 実行時は2段階の Claude 呼び出し: - 1回目(ルーティング呼び出し):ユーザーのトピックだけを渡し、「この動画のトピックを、列挙したカテゴリのどれかに分類せよ」と頼む。プロンプト例:Categorize the topic of a video into one of the listed categories: <topic>Python functions</topic> <categories> - Educational - Entertainment - Comedy - Personal vlog - Reviews - Storytelling </categories> → Assistant Message は「Educational」のようにカテゴリ名だけが返る。 - 2回目(専用パイプライン):分類結果に対応する専用プロンプト(educational 用)に topic を埋めて、台本を書かせる。教育動画にふさわしいトーン・内容の台本が返ってくる。

routing workflow の定義(スライド)

  • 最初の呼び出しで、ユーザーのクエリ/タスクを分類する(この routing step 自体もたいてい Claude への呼び出し)
  • そのクエリ/タスクを、そのカテゴリ処理専用のパイプラインへ転送する
  • 選ばれた経路の先は、ワークフローでも、プロンプトでも、ツールセットでも何でもよい
  • 図式:User Input → Router → Workflow, Prompt, Tools, etc ×3 のうち1つだけに転送(User input only sent to one
  • 各ルーティング先には、別のワークフロー、カスタマイズされたプロンプト、そのタスク専用に特化したツールセットなどを実装できる。

実務ポイント(このレッスンから持ち帰るもの)

  • 「万能プロンプト1本」より「分類→専用プロンプト」。入力の性質によって出力のトーン・構成・粒度が変わるべきタスク(台本、返信文、レポート等)はルーティングが正攻法。
  • ルーター実装の型:分類専用の軽い Claude 呼び出しを1発。<topic>…</topic><categories>…</categories> を XML タグで渡し、カテゴリ名だけを返させる(後段で分岐キーとして使うため、余計な文章を返させない)。
  • カテゴリは事前に自分で設計する(この例では6ジャンル)。ルーティングの品質はカテゴリ設計の網羅性に依存する——「ユーザーが頼みそうなもの」を先に洗い出すのが第一歩。
  • ルーティング先は1つだけに送るのがこのパターンの本質(スライドの User input only sent to one)。全部に送るなら並列化ワークフロー(Lesson 70)であり、使い分けを意識する。
  • ルーティング先の中身は自由度が高い:カスタムプロンプト1本でも、別のワークフロー全体でも、専用ツールセットでもよい。カテゴリごとに複雑さを変えられる。
  • 3パターンの整理:依存する手順の直列分解=chaining(71)/独立観点の同時実行+集約=parallelization(70)/入力の種類で1経路を選ぶ=routing(72)。実アプリではこれらを組み合わせる(例:ルーティング先の各パイプラインがチェーンになっている、など)。
  • ルーター呼び出しは出力が短く安価。分類ステップを足すコストはわずかで、後段の品質向上リターンが大きいというのがこのパターンの旨味。

本文(時系列・日本語逐語+画面差し込み)

[0:00] 次のワークフローでは、このソーシャルメディア・マーケティングツールを改善する1つの方法を見ていきます。

🖥 [0:02] 画面(スライド:Social Media Marketing アプリのモック再掲) ブラウザ枠のワイヤーフレーム。タイトル Social Media MarketingEnter a topic and we'll create and post videos to your social media accountsTopic 入力欄、Start ボタン(前レッスンと同じアプリ)。

[0:04] 今回も、ユーザーがトピックを入力すると想像してください。私たちは何らかの方法で動画を作り、ユーザーの SNS アカウントに投稿します。

[0:12] ここで、皆さんに本当に考えてほしいことがあります。台本生成プロセスについてです。言い換えると、これらのさまざまな動画で使われる実際のトーンと言葉遣いについてです。

[0:20] 2つの異なるトピック——左側に programming(プログラミング)、右側に surfing(サーフィン)——が与えられたとき、私たちは本来、性質のまったく異なる動画台本が返ってくることを期待するはずです。

🖥 [0:20] 画面(スライド:Programming と Surfing の台本比較) 左:Programming(topic)→ Video Script: - (0-3 seconds) Need to organize your Python code? Avoid repetition?(Python コードを整理したい?繰り返しを避けたい?) - (3-8 seconds) Mastering Python functions is fundamental. They are reusable blocks of code designed to perform a specific task.(Python 関数の習得は基本。特定のタスクを実行する再利用可能なコードブロックだ) - (8-13 seconds) Functions encapsulate logic, promoting modularity and making your code easier to read, test, and maintain. - (13-25 seconds) Defining a function starts with the def keyword, followed by the function name, parentheses, and a colon.… - (25-35 seconds) Functions can accept inputs called parameters,… They can also return values using the return keyword.… 右:Surfing(topic)→ Video Script: - (0-3 seconds) Ever wondered what it feels like to ride the ocean?(海に乗るってどんな感じか、考えたことある?) - (3-8 seconds) To glide across the water, powered by pure nature? That's the magic of surfing. - (8-13 seconds) It's more than just a sport. It's about connecting with the sea. Feeling the rhythm of the waves. - (13-25 seconds) Yeah, it takes a little practice. Learning to paddle… getting a feel for the board… and timing that perfect moment to stand up. - (25-35 seconds) But when you catch that wave… that feeling is incredible! Pure exhilaration. ※左は定義と説明が並ぶ教育調、右はエモーショナルな娯楽調——同じ「台本」でも別物。

[0:28] 左側のプログラミングでは、情報量が多く、丁寧に説明された定義があり、全体として教育的な性格を意図した動画が欲しいでしょう。

[0:38] 一方、ユーザーがトピックとして surfing を入力したなら、おそらくまったく違う動画台本が返ってきてほしいはずです。教育的な色合いはずっと薄く、「サーフィンとは何か」についての長い定義などは入っていないものです。

[0:52] そこで、与えられたトピックから、そのトピックの性質に本当によく合った動画台本が生成されることを保証するために使えるワークフローをお見せしましょう。

[1:01] まず、腰を据えて、ユーザーが作成を依頼してきそうな動画のあらゆるジャンルについて考えます。

[1:07] そして、ユーザーが与えてくるトピックは6つの異なるジャンル——entertainment、educational、comedy など——のどれかに収まる、と決めたとしましょう。

🖥 [1:03] 画面(スライド:ジャンル×台本生成プロンプトの対応表) 2列のテーブル。ヘッダー:Genre of Video | Script Generation Prompt - EntertainmentWrite a high-energy, culturally-relevant script about [TOPIC] using trendy language and engaging hooks that balance entertainment value with insider insights. - EducationalDevelop a clear, engaging script about [TOPIC] that transforms complex information into digestible insights using relatable examples and thought-provoking questions. - ComedyCreate a sharp, unexpected script about [TOPIC] that subverts expectations with clever observations, perfect timing, and relatable scenarios that build to satisfying punchlines. - Personal vlogWrite an authentic, intimate script about [TOPIC] that balances vulnerability with relatable insights, using conversational storytelling that invites viewers into your personal experience. - ReviewsCreate a decisive, experience-based script about [TOPIC] highlighting key strengths and weaknesses with specific examples that support your clear final verdict - StorytellingWrite an immersive script about [TOPIC] using vivid details and strategic pacing that builds emotional connection through authentic storytelling. ※各プロンプトの [TOPIC] にユーザーのトピックを差し込んで使う。

[1:17] さきほどの例に戻ると、programming は educational に、surfing は entertainment に当たるでしょう。

🖥 [1:17] 画面(スライド:Programming / Surfing 比較の再掲) [0:20] の台本比較スライドに一瞬戻り、「programming=educational、surfing=entertainment」の対応を確認してから、すぐ対応表スライドへ戻る。

[1:22] 次に、これらのジャンルごとに、台本生成プロンプトを書き出します。

[1:26] つまり、誰かが educational な性格だと私たちが分類するトピックを依頼してきたら、ここにあるこのプロンプト(Educational 行)を使って Claude に台本を書かせます。このプロンプトは、面白い例や興味深い問いかけなどを備えた、明快で魅力的な台本を作るよう Claude に頼むものです。

[1:42] ユーザーが surfing というトピックを与えてきたら、それは entertainment に分類するでしょう。そこで、こちらのプロンプト(Entertainment 行)を取り、トピックとして surfing を差し込み、プロンプト全体を Claude に投入して、トレンド感のある言葉遣いや引き込むフックなどを備えたサーフィンの台本を書くよう頼みます。

[2:00] では、このフロー全体が実際にはどう見えるかをお見せしましょう。まず、ユーザーが入力したトピックだけ——例えば「Python functions」——を含むリクエストを Claude に送り、いま考え出したカテゴリのどれかにこのトピックを分類するよう Claude に頼みます。

🖥 [2:01] 画面(スライド:ルーティング呼び出しのシーケンス図) 「Server」→「Claude」のシーケンス図。 User MessageCategorize the topic of a video into one of the listed categories:(動画のトピックを、列挙したカテゴリのどれか1つに分類せよ) <topic>Python functions</topic> <categories>(このブロックがハイライトされている) - Educational - Entertainment - Comedy - Personal vlog - Reviews - Storytelling </categories>Assistant MessageEducational(カテゴリ名のみが返る)

[2:14] このシナリオでは、Python functions は educational のカテゴリに最も近いでしょう。

[2:20] そこで、この Claude からのレスポンスを受けて、フォローアップリクエストを送ります。Python functions についての、明快で魅力的な情報と、示唆に富む例を備えた台本を書くよう Claude に頼むのです。

[2:31] そしておそらく、そうした品質と、教育動画にふさわしいトーンを備えた台本が返ってくるでしょう。

[2:36] これが routing workflow(ルーティングワークフロー) の一例です。

🖥 [2:37] 画面(スライド「Workflow Type: Routing」) 左側に箇条書き: - Use an initial call to categorize the user query/task(最初の呼び出しで、ユーザーのクエリ/タスクを分類する) - Forward the query/task on to a dedicated pipeline for handling that category(そのクエリ/タスクを、そのカテゴリ処理専用のパイプラインへ転送する) - The chosen path can be a workflow, prompt, set of tools, etc(選ばれる経路は、ワークフロー、プロンプト、ツールセットなど何でもよい) 右側に図:User InputRouter(説明中に講師が Router の箱を選択して強調する場面あり)→ 3つの Workflow, Prompt, Tools, etc の箱へ分岐。 図の下に注記:User input only sent to one(ユーザー入力は1つだけに送られる)

[2:41] ルーティングワークフローでは、ユーザーの元の入力を受け取り、routing step(ルーティングステップ)に投入します。このルーティングステップは、おそらく Claude 自体への呼び出しになるでしょう。ユーザーの入力なりタスクなりを、何らかの形で分類するよう Claude に頼むのです。

[2:53] そして Claude の答えに応じて、ユーザーの入力を、ある非常に特定の後続処理パイプラインへ転送します。これかもしれないし、こっちかもしれないし、あっちかもしれない——ただし、この3つのうちおそらく1つだけです。

[3:02] これらのルーティング先には、それぞれ、その中に実装された別のワークフローがあるかもしれませんし、カスタマイズされたプロンプトや、ユーザーが依頼しているまさにそのタスクの処理に特化した、カスタマイズ済みのツールセットがあるかもしれません。

英語逐語(ASR全文)

Our next workflow is going to show us one way of improving this social media marketing tool. So once again, we'll imagine that a user is going to enter in a topic. We're then going to produce some videos somehow and post them to a user's social media account. Now there's something I really want you to think about, about the script generation process. In other words, the actual tone and language used in these different videos, given two different topics, such as programming on the left-hand side, and maybe surfing on the right-hand side, we would really expect to get video scripts that are very, very different in nature. So on the left-hand side with programming, we would want to get a lot of information, carefully explained definitions, and probably just overall a video that is meant to be educational in nature. And if a user entered in surfing as a topic, we would probably want to get back a very different video script, probably something that is much less educational in nature and doesn't have a long definition on what surfing is or anything like that. So let me show you a workflow that we could use to make sure that a given topic will result in a video script that fits the nature of that topic really well. First, we would sit down and think about all the different possible genres of videos that users might ask us to create. So we might decide that the topics that users are going to give to us are going to fit into one of six different genres, entertainment, educational, comedy, and so on. So in our example, back over here, programming might be educational and surfing might be entertainment. Then for each of these different genres, we might write out a script generation prompt. So if someone asked for a topic that we categorize as being educational in nature, we would ask Claude to write a script using this prompt right here. And the prompt is asking Claude to make a clear, engaging script that has some interesting examples and interesting questions and so on. If a user gives us a topic of surfing, we might categorize that as being entertainment. So then we would take this prompt right here, put in the topic as surfing and feed the whole prompt into Claude and ask Claude to write a script about surfing that maybe has some trendy language and engaging hooks and so on. So let me show you what this entire flow would look like in practice. We would initially send a request off to Claude that contained just the topic the user had entered, maybe something like Python functions, and ask Claude to categorize this topic into one of our different categories that we just came up with. In this scenario, Python functions might be most closely related to the category of educational. So then we would take this response from Claude and then make a follow-up request asking Claude to write a script that has some clear engaging information about Python functions that has thought provoking examples. And then presumably we would get back some script with those different qualities and a tone appropriate for an educational video. This is an example of a routing workflow. In a routing workflow, we're going to take the user's original input and feed it into a routing step. This routing step will probably be a call to Claude itself, asking Claude to categorize the user input or task in some way. Then depending upon Claude's answer, we're going to forward the user's input onto some very particular follow-up processing pipeline. So maybe this one right here, or this one right here, or this one right here, and so on. But probably only one of these different three. Each of these different routing options might have a different workflow implemented inside of it, or a customized prompt, or a customized set of tools that are specialized for handling the exact task that the user is asking for.

↑ 目次へ戻る

Building with the Claude API | Lesson 73「Agents and tools」(全文ナレッジ)

動画 5分02秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04

日本語ナレッジ(要点整理)

このレッスンのテーマ

ワークフロー各種パターンの学習を終え、エージェント(agents)へピボットする回。エージェントの理解はワークフローとの対比が最短ルート:ワークフロー=タスク完了に必要な正確な手順が事前に分かっている時に最も有効。対してエージェント=どんな手順が必要か正確には分からない時に有効。このシナリオでは Claude にタスクとツール一式を与え、「与えられたツールでタスクを完了する計画」を立てる仕事を Claude に委ねる

エージェントの定義(スライドの整理)

  • Claude にゴールreasonably abstract(ほどよく抽象的)なツール一式を与える
  • Claude が、それらのツールをどう使ってゴールを達成するかの計画を(明示・暗黙を問わず)自分で立てる
  • 私たちの目標は確実に(reliably)かつ経済的に(economically)タスクを完了させること——これはエージェントでは往々にして難しい
  • エージェントの柔軟性は、より柔軟な UX を可能にする
  • 「一度エージェントを作ってそこそこ動くことを確認すれば、多種多様なタスクを解ける」という発想が魅力。ただし大きな欠点もある(詳細は後のレッスンで比較)

エージェントの核=ツールの組み合わせ能力

コース前半で作った3つの datetime ツールを復習素材にする:get_current_datetime(現在日時を文字列で取得)/add_duration_to_datetime(日時文字列に期間を加算)/set_reminder(特定日時にリマインダー設定)。個々は単純でも、Claude は事前に計画していなかった多様なタスクを、これらを驚くような組み合わせで解いてしまう。

Task → Tool Calls の対応例(スライドの6行): | タスク | Claude のツール呼び出し | |---|---| | What's the time?(今何時?) | get_current_datetime のみ | | 11日後は何曜日? | get_current_datetimeadd_duration_to_datetime | | 2030年3月21日 8AM にリマインダー | set_reminder のみ | | 私の1000日目の誕生日はいつ? | add_duration_to_datetime のみ | | 来週水曜にジムへ行くリマインダー | get_current_datetimeadd_duration_to_datetimeset_reminder の3連鎖 | | 私の90日保証はいつ切れる? | Claude: 「保証はいつ取得しましたか?」(ユーザーに確認)→ add_duration_to_datetime |

最後の例が重要:ツールを正しく呼ぶために追加情報が必要だと Claude 自身が判断し、ユーザーに聞き返せる(保証を今日取得したとは限らないため)。

第一の大教訓:ツールは reasonably abstract に

エージェントに与えるツール一式はほどよく抽象的でなければならない。抽象的=汎用的・一般的・目的が限定されていないという意味で、過度に特化(hyper-specialized)していないこと。

手本は Claude Code: - 持っているツール(少数の抽象ツール):Bash(コマンド実行)/Glob(ファイル検索)/Grep(ファイル内容検索)/LS(ファイル一覧)/Read(ファイル読み取り)/Write(ファイル作成)/Edit(ファイル編集)/WebFetch(URL取得) - 持っていないツール(超特化ツール):Refactor(ファイルを魔法のようにリファクタ)/Run Tests(テスト発見・実行)/Create Migration(DB マイグレーション作成)/Install Dependencies(依存の一括インストール) - リファクタしたければ左側の汎用ツールの組み合わせで実現すべきだし、依存インストールなら Read でプロジェクト設定を理解 → Bash で適切なコマンドを実行する。Claude Code はこの少数の抽象ツールの組み合わせだけで、既存コードベースの修正・機能追加を驚くほど上手くやってのける。

適用例:ソーシャルメディア動画作成エージェント

このコースで繰り返し使う「SNS 動画作成エージェント」に教訓を適用すると、与えるツールは4つ: 1. bash — FFMPEG(画像・動画・テキスト・音声から動画を生成できる定番 CLI)へのアクセスを提供 2. generate_image — プロンプトから画像生成 3. text_to_speech — テキストを音声に変換(動画生成プロセスの補強) 4. post_media — 生成したコンテンツを SNS アカウントに投稿

この抽象ツール一式は予期しない使われ方も可能にする: - 左の例:「Python プログラミングの動画を作って投稿して」→ そのまま作成・投稿 - 右の例:「動画を作りたいが、まず動画に使うサンプルカバー画像を先に出して」→ エージェントは先に画像を生成してユーザーに見せ、承認を得てから動画生成プロセスへ進む——というより動的な対話も、同じツール一式で自然に成立する

実務ポイント(このレッスンから持ち帰るもの)

  • 使い分けの軸:手順を事前に描けるタスク=ワークフロー、手順が読めない・多様なタスク=エージェント。エージェントは「タスク+ツールを渡して計画は Claude に任せる」構図。
  • ツール設計の鉄則=reasonably abstractrefactorinstall_dependencies のような1シナリオ専用ツールを作らず、Bash / Read / Write のような組み合わせ可能な汎用ツールを少数渡す。Claude Code のツール構成(Bash・Glob・Grep・LS・Read・Write・Edit・WebFetch)が公式の手本。
  • 単純なツール3つでも組み合わせは多彩(datetime 3種の6例)。エージェントの価値は個々のツールではなく組み合わせの創発にある——ツール表を作る時は「単体で何ができるか」より「連鎖で何ができるか」で設計する。
  • 情報不足時に聞き返すのもエージェントの挙動(90日保証の例)。必要な引数が揃わない場合に Claude がユーザーへ確認する流れを UX として想定しておく。
  • 抽象ツールは UX の柔軟性も生む:「先にカバー画像だけ見せて→承認→本処理」のような中間確認フローが、ツールを追加せずに成立する。
  • エージェントには大きな欠点もある(成功率・テスト性)——この時点で万能視しない。詳細は Lesson 75 の workflows vs agents 比較で。

本文(時系列・日本語逐語+画面差し込み)

[0:00] さて、いくつかの異なるワークフローを見てきたところで、方向を変えて、エージェントについての議論を始めます。エージェントの理解は、ワークフローのこと——特に、実際にどんな時にワークフローを使うのか——を思い返すのが一番簡単です。

🖥 [0:00] 画面(スライド「Agents」) 左に箇条書き: - We give Claude a goal and a reasonably abstract set of tools(Claude にゴールと「ほどよく抽象的な」ツール一式を与える) - Claude formulates a goal (stated or unstated) on how to use those tools to achieve the goal(Claude はそれらのツールでゴールを達成する方法を、明示・暗黙を問わず自ら組み立てる) - Our goal is to reliably and economically complete tasks: this is often harder to do with agents(我々の目標は確実に・経済的にタスクを完了すること。エージェントではしばしば難しい) - Agent flexibility allows for more flexible UX(エージェントの柔軟性はより柔軟な UX を可能にする)

右に図:GoalTools の2ボックス → Claude(オレンジ枠)→ Plan to that will use the provided the tools to achieve the goal(提供されたツールでゴールを達成する計画)。

[0:09] ワークフローが最も効果的だったのは、あるタスクを完了するために必要な正確な手順の連なりを私たちが知っている時でした。一方エージェントは、どんな手順が必要なのか正確には分からない時に、より効果を発揮します。

[0:18] そこでこうしたシナリオでは、Claude にタスクとツール一式を与え、与えられたツールを使ってタスクを完了する計画を作ることを Claude に頼るのです。

[0:29] エージェントを巡るこの柔軟性こそが、開発対象として非常に魅力的な理由です。発想としてはこうです:エージェントを1つ作り、そこそこ上手く動くことを確認すれば、そのエージェントは多種多様なタスクを解決できる、と。

[0:38] ただし、このアプローチには大きな欠点もいくつかあります。それについては少し後で議論します。

[0:42] エージェントの重要な側面は、ツールをさまざまな組み合わせで使いこなす能力です。これを理解してもらうために、このコースの前の方でやった例——3つの異なるツールを組み立てた例——を思い返してほしいのです。

🖥 [0:48] 画面(スライド「Tools make the agent」) 見出し:Tools make the agent / サブタイトル:We are relying upon Claude's abilities to combine different tools(我々は Claude の「異なるツールを組み合わせる能力」に頼っている) Name / Description の表: | Name | Description | |---|---| | get_current_datetime | Get the current datetime as a string | | add_duration_to_datetime | Add a specified duration to a provided datetime string and return the result | | set_reminder | Set a reminder at a particular datetime |

[0:56] get_current_datetimeadd_duration_to_datetimeset_reminder といったツールを作りましたね。これらのツールはどれも性質としてはかなり単純でした。しかし Claude は、それらをちょっと驚くような形で組み合わせて、私たちが事前に計画していたわけでもない多種多様なタスクを達成できたのです。

[1:10] いくつか例をお見せしましょう。この図の左側には、あの3つのツールと一緒に Claude に投げ込めるタスクの例が並んでいます。そして右側の部分が、その与えられたタスクを完了するために Claude が行うであろう一連のツール呼び出しです。

🖥 [1:13] 画面(スライド「Task / Tool Calls」対応図) 左列 Task(ベージュのボックス)→ 右列 Tool Calls(矢印で連結): 1. What's the time?get_current_datetime 2. What day of the week is it in 11 days?get_current_datetimeadd_duration_to_datetime 3. Set a reminder for March 21, 2030 at 8AMset_reminder 4. What day will my 1000th birthday be on?add_duration_to_datetime 5. Set a reminder to go to the gym next Wednesdayget_current_datetimeadd_duration_to_datetimeset_reminder 6. When does my 90-day warranty expire?Claude: When did you obtain the warranty?(赤茶のボックス=Claude がユーザーへ質問)→ add_duration_to_datetime

[1:26] 例えば Claude に「今何時?」と聞けば——簡単ですね、Claude は get_current_datetime ツールを単独で呼び出して、それだけで質問に答えられます。

[1:35] 「11日後は何曜日?」と聞けば、まず get_current_datetime を呼び、次に add_duration_to_datetime を呼べばいい。

[1:42] 「来週の水曜日にジムへ行くリマインダーをセットして」と頼めば、Claude はまず今日が何曜日かを把握し、そこに期間を加算し、そしてその特定の日にリマインダーをセットする、という流れが取れます。

[1:52] 最後に、Claude はツールをうまく呼び出すために追加の情報が必要になるタイミングも自分で見極められます。

[1:58] 例えばユーザーが「私の90日保証はいつ切れる?」と尋ねたとします。ユーザーがその保証を今日手に入れたという保証は、実際どこにもありませんよね。

[2:05] そこで Claude はまず、ユーザーに少し追加情報を求めるでしょう。具体的には、実際にいつ保証を取得したのかを。そしてユーザーがその情報を与えたら、Claude は add_duration_to_datetime を呼び出して、保証がいつ切れるのかを割り出せます。

[2:17] これらはすべて、Claude がツール一式を受け取り、与えられたタスクを解くためにそれらを面白いやり方で組み合わせる、その実例です。

[2:25] そしてここから、エージェントを巡る最初の大きな教訓・理解へとつながっていきます。

[2:30] それは、エージェントに提供するツール一式は「ほどよく抽象的(reasonably abstract)」である必要がある、ということです。

[2:34] この好例——そして私がここで「抽象的」と言って本当に意味しているものを理解する助け——として、Claude Code に立ち返り、そこに提供されているツールを具体的に見てみましょう。

🖥 [2:30] 画面(スライド「Tools should be abstract」) 左:Tools available to Claude Code(Claude Code が使えるツール) - Bash Run commands / Glob Find files / Grep Search file contents - LS List files / Read Read a file / Write Create a file - Edit Edit a file / WebFetch Fetch a URL

右:Tools that Claude Code does not have(Claude Code が持っていないツール) - RefactorRefactor the given file, extracting reusable code where possible - Run TestsDiscover and execute tests in the project - Create MigrationCreates a database migration with the specified schema - Install DependenciesInstalls all dependencies for the project based on the environment

[2:43] Claude Code は、ごく少数の抽象的なツール一式へのアクセスを得ています。私が「抽象的」と言う時、それは汎用的・一般的、いわば目的があいまいという意味です。どんな形であれ、過度に特化(hyper-specialized)してはいないのです。

[2:55] つまり Claude Code は、コマンドを実行するための Bash、URL を取得する WebFetch、ファイルを作成する Write、といったツールへのアクセスを得ています。

[3:04] そして Claude Code は、これらの異なるツールを組み合わせることで、既存のコードベースに本当に見事なやり方で修正を加え、機能を追加する方法を自分で見つけ出せるのです。

[3:12] Claude Code は、1つの特定シナリオで1つの非常に特定のタスクだけをこなす、超特化型のツールへのアクセスは持っていません。

[3:20] 例えばこの図の右側にあるのは、Claude Code が持っていないことで知られるツールたちです。ファイルを魔法のようにリファクタしてくれるだけの refactor ツールなどは存在しません。

[3:29] その代わり、Claude Code は左側のツールをどう使えば何かをリファクタできるかを、自分で見つけ出すべきなのです。

[3:35] 同様に、install dependencies ツールもありません。その代わり Claude Code は、プロジェクト設定を理解するためにファイルを読み、それから bash ツールで適切なコマンドを実行して依存をインストールする必要があります。

[3:46] ここから引き出せる教訓はこうです:エージェントを作る時は必ず、Claude が何らかのゴール達成のためにどうにか組み合わせ方を見つけられる、ほどよく抽象的なツールを提供すること。

🖥 [3:47] 画面(スライド「Best practice」) 見出し:Best practice: Provide reasonably abstract tools that Claude can combine together(ベストプラクティス:Claude が組み合わせられる、ほどよく抽象的なツールを提供せよ) 表題:Tools for a Social Media Video Agent | Name | Description | |---|---| | bash | Provides access to FFMPEG, a CLI tool that can stitch together images and sound to form a video | | generate_image | Generate an image based upon a prompt | | text_to_speech | Converts written text into spoken audio | | post_media | Posts text, images, or videos to a social media account |

[3:57] 例えば、ある種のソーシャルメディア動画作成エージェントを作るという例に戻るなら、4つの異なるツールを提供することになるでしょう。1つは Bash で、これは FFmpeg へのアクセスを与えます。FFmpeg は、入力の画像・動画・テキスト・音声などから動画を生成するのに使える、よく使われる CLI ツールです。

[4:16] さらに generate image ツール、動画生成プロセスを補強するための text-to-speech ツール、そして最後に post media ツールを与えます。post media は、生成したコンテンツ——何を作ったのであれ——をソーシャルメディアのアカウントに投稿できるようにするものです。

[4:31] Claude は、このツール一式をかなり予想外のやり方で使うことができます。

🖥 [4:31] 画面(スライド「Marketing Agent」チャット2態) ブラウザ風モックが左右に2つ、どちらもタイトル Marketing Agent: - :You「Create and post a video on Python programming」→ AI「OK, I will create and post the video」(そのまま作成・投稿する直行フロー) - :You「I want you to make a video on Python, but first I want to pick out an initial image for the video.」→ AI「Sure! Does this one work?」+コードのサムネイル画像を提示(先にカバー画像を確認してもらう動的フロー)

[4:34] 例えば、この左側に見えるようなフローが可能になります。ユーザーが私たちのエージェントとチャットして、Python プログラミングについての動画を作成して投稿するよう頼む、というものです。

[4:43] しかしこのツール一式は、ユーザーとのもっと動的なやり取りも可能にします。例えば右側では、ユーザーは動画を頼みつつ、まず動画に使うサンプルのカバー画像を生成するようエージェントに求めています。

[4:54] すると私たちのエージェントは、まず画像を生成してユーザーに見せ、ユーザーの承認を得てから、動画生成プロセスへと進むことができるのです。

英語逐語(ASR全文)

Now that we have taken a look at several different workflows, we are going to pivot and start to discuss agents. Understanding agents is easiest if you think back to workflows, specifically when we would actually use them. Workflows were most effective when we knew the precise series of steps required to complete a given task. Agents, on the other hand, are more effective when we don't really know exactly what steps are required. So in these scenarios, we give Claude a task and a set of tools, and then we rely upon Claude to create a plan to complete the task using the given tools. This flexibility around agents is what makes them really attractive for building. The thought process is that you can make an agent, make sure it works reasonably well, and then the agent can solve a wide variety of different tasks. However, there are some major drawbacks to this approach, which we will discuss in a little bit. A key aspect of agents is their ability to make use of tools in different combinations. So to help you understand this, I want to think back to an earlier example that we went through earlier on inside this course, where we put together three different tools. We created tools like get current date time, add duration to date time, and set reminder. Each of these tools were rather simple in nature, but Claude was able to combine them in different kind of surprising ways to achieve a wide variety of different tasks that we might not really have planned out ahead of time. Let me show you some examples. So on the left-hand side of this diagram, I've got some example tasks that we could feed into Claude along with those three different tools. And then on the right-hand portion is a series of different tool calls that Claude might make to complete the given task. So for example, if we ask Claude, what's the given time? Easy enough, Claude can just call the get current date time tool by itself and answer the question. If we ask Claude, what day of the week is in 11 days? It could first call get current date time and then add duration to date time. If we ask Claude to say, set a reminder to go to the gym next Wednesday, Claude could first figure out the current day of the week, add a duration onto it, and then set a reminder for that particular day. Finally, Claude can also figure out when it needs some extra information in order to successfully call a tool. So if a user asked, when does my 90-day warranty expire? Well, there's really no guarantee that the user got the warranty today. So Claude might first ask the user for a bit of extra information, particularly when they actually obtained the warranty. And once the user gives them that information, then Claude can call at duration to date time and figure out when the warranty will expire. These are all examples of ways in which Claude can take a set of tools and combine them together in an interesting fashion in order to solve a given task. And this is going to lead us into our first big lesson or understanding around agents. And that is that the set of tools we provide to an agent need to be reasonably abstract. And a great example of this, and to help you understand what I really mean here by abstract, is to go back and look at Claude Code and specifically some of the tools that are provided to it. Cloud Code gets access to a very small set of abstract tools. And when I say abstract, I mean generic or general or kind of vague in purpose. They are not hyper specialized in any way. So Cloud Code gets access to tools like Bash in order to run commands, WebFetch to fetch a URL, write to create a file and so on. And Cloud Code can figure out how to modify and add features to an existing code base in really amazing ways by combining together these different tools. Cloud Code does not have access to hyper-specialized tools that just fulfill one very specific task in one specific scenario. So for example, on the right-hand side of this diagram, these are all tools that Cloud Code notably does not have. So there is no refactor tool that will just magically refactor a file. Instead, Cloud Code should figure out how to make use of the tools on the left-hand side in order to refactor something. Likewise, there is no install dependencies tool. Instead, Cloud Code needs to read files to understand the project configuration and then run the bash tool to run the appropriate command to install the dependencies. The lesson that we can take from this is that whenever we create an agent, we want to make sure that we provide reasonably abstract tools that Cloud can somehow figure out how to piece together in order to achieve some goal. So for example, if we go back to our example of building some kind of social media video creation agent, we might provide four different tools. One might be Bash, which would give it access to FF Impeg. That's a commonly used CLI tool that you can use to generate videos, given some input images or videos or text or audio and so on. We might also give it a generate image tool, a text-to-speech tool just to augment the video generation process, and then finally a post media tool so that it can take the generate content, whatever it made, and post that content to a social media account. Klai can use that set of tools in rather unexpected ways. So for example, it would enable a flow like what you see on the left-hand side where a user might chat with our agent and ask it to create and post a video on Python programming. But this set of tools might also allow for more dynamic interactions with the user. For example, on the right-hand side, a user might ask for a video but first ask the agent to generate a sample cover image to use in the video. Then our agent could first generate an image, show it to the user, get the user's approval, and then go into the video generation process.

↑ 目次へ戻る

Building with the Claude API | Lesson 74「Environment inspection」(全文ナレッジ)

動画 3分05秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04

日本語ナレッジ(要点整理)

このレッスンのテーマ

エージェント設計の第2の考え方=environment inspection(環境の検査)。スライドの一言でいえば 「Claude は盲目的に(blindly)操作している——だから環境を観察できる必要がある」。行動した結果どうなったかを Claude 自身が確かめる手段を、エージェント設計者が必ず用意する、という原則。

出発点:computer use の観察

  • computer use のデモを思い返すと、左側にログされるすべてのアクション(タイプ、マウス移動)の直後に、必ずスクリーンショットが続いていた
  • Claude 視点で考えると:タイプやクリックをすればページは変わるはずだが、どう変わったかは Claude には分からない。ボタンをクリックしたら新しいページに遷移するかもしれないし、メニューが開くだけかもしれない。
  • 自分が取った行動の結果を理解するために、Claude は新しい状態=環境を知るためのスクリーンショットを必要としていた。computer use ではツール呼び出しが自動でスクリーンショットを返す設計になっているのはこのため。

一般原則への拡張

  • この考え方は、私たちが組み立てるあらゆるエージェントに当てはまる
  • 行動の後(そして時には行動の前)に、Claude は行動の結果を評価する手段を必要とする。しかも単なるツールの戻り値以上のものであることが多い。
  • Claude が環境を理解できるよう助けると:①タスク完了への進捗をより正確に把握できる、②予期しない結果やエラーへの対処が上手くなる。

Claude Code に見る同じ原則:read before write

  • スクリーンショットの例:「main.py を更新して /items ルートを追加して」という単純な依頼。
  • 当たり前に見えるが、ファイルを修正する前に Claude はまずファイルの中の現在のコードを理解する必要がある→ ファイル内容を読む手段が要る。
  • 実際の Claude Code の挙動:Read(main.py)(31行読む)→ 現状把握 → Update(main.py) で5行追加(@app.get("/items") ハンドラ等)。
  • 「書く前に読む」——自前のエージェントを作る時は、この一見自明な考え方を必ず設計に織り込むこと。

SNS 動画エージェントへの適用:system prompt で検査手順を教える

  • タスク「Python の動画を作って私の SNS アカウントに投稿して」+ツール一覧(post_media / web_search / image_generator / bash)を渡す時、system prompt に「動画生成後に環境をどう検査するか」の特別な指示を入れる。
  • 講師の想定:FFmpeg で動画を作らせると、ダイアログ(セリフ音声)の配置ミス——text-to-speech で生成した音声クリップの再生タイミングのずれ——が時々起きるはず。
  • 対策1:bash ツールで whisper.cpp を実行させる。動画からタイムスタンプ付きのキャプションファイルを自動生成するプログラムで、これを使えばダイアログが正しい位置に置かれたかを Claude が自己検証できる。
  • 対策2:bash ツールで FFmpeg を実行し、動画から毎秒(または10秒ごと)にスクリーンショットを抽出させ、その画像を Claude 自身が見て「想定どおりの見た目か」を確認させる。
  • こうして Claude は自分の行動の結果=実際に作った動画そのものを検査し、タスクをあるべき形で完了しつつあるかを確かめられる。

スライドの System Prompt 例(原文)

After generating videos, do the following: - Use the bash tool to run whisper.cpp and generate a caption file with timestamps. Verify that the dialog was placed correctly. - Use the bash tool to run FFMPEG and extract a screenshot from every one second of the video. Verify that the video looks as you expected

実務ポイント(このレッスンから持ち帰るもの)

  • エージェント設計の必須チェック項目:「Claude は自分の行動の結果をどうやって観察するのか?」 ツールの戻り値だけでは足りないことが多い——環境の新しい状態を見る専用の手段(スクリーンショット、ファイル読み取り、検証コマンド)を用意する。
  • read-before-write の原則:書き込み・修正系の操作の前に、必ず現状を読む手段と手順を確保する。Claude Code の Read→Update がリファレンス実装。
  • 検証手順は system prompt に明文化して教える。「生成後に whisper.cpp でキャプションを作ってタイムスタンプを検証せよ」「FFmpeg で毎秒スクショを抜いて見た目を確認せよ」のように、具体的なツール名・コマンド・頻度まで指示するのが実務の型。
  • 成果物のセルフ検証をエージェントの標準工程に:作って終わりでなく「作ったものを自分で開いて確かめる」工程を組み込むと、進捗把握とエラー回復の両方が向上する。
  • 失敗モードを先回りで想定して検査を仕込む:講師は「TTS 音声の配置ミスが起きそう」という予想から whisper.cpp 検証を設計した。自分のエージェントでも「どこでミスりそうか→それをどう機械的に検出できるか」のペアで考える。
  • computer use の「アクション→自動スクリーンショット」は environment inspection がプロトコルに組み込まれた例。自作エージェントでも「行動系ツールが状態スナップショットを返す」設計は有効。

本文(時系列・日本語逐語+画面差し込み)

[0:00] 次に議論する、エージェントを巡る考え方は environment inspection(環境の検査)です。

🖥 [0:00] 画面(スライド「Environment Inspection」) タイトル:Environment Inspection / サブタイトル:Claude operates blindly - it needs to be able to observe the environment(Claude は盲目的に操作している——環境を観察できる必要がある) 右側に computer use のログの抜粋(黒パネル2段): 1. 🤖 Now I'll type "Did you read @":Tool Use: computerInput: {'action': 'type', 'text': 'Did you read @'} —— 注釈:Claude uses a tool to type some text(Claude はツールを使ってテキストをタイプする) 2. その直下に Firefox(Test Mentioner — test-mentioner.vercel.app)のスクリーンショット。テキストエリアに「Did you re」と入力途中 —— 注釈:Tool call automatically returns a screenshot so Claude can understand the new state(ツール呼び出しは自動でスクリーンショットを返し、Claude が新しい状態を理解できるようにする)

[0:04] 以前 computer use を見ていた時、何か面白いことに気づいたかもしれません。左側にログとして出力されるすべての単一アクション——タイプするとかマウスを動かすとか——の後には、必ずと言っていいほど、直後にスクリーンショットが続いていましたよね。それがまさに、この図でお見せしているものです。

[0:18] Claude は何かテキストをタイプしようとし、それがこの上の最初のパネルにログとして残っています。そしてその直後に、すぐスクリーンショットが現れるのを見ました。

[0:26] ここで、computer use を Claude の視点から見てほしいのです。Claude はどこかにタイプしたりクリックしたりを試みます。おそらくページは変化するでしょう。しかし Claude には、どう変化するのかが本当のところ分からないのです。

[0:36] ボタンをクリックすれば、新しいページに遷移するかもしれないし、メニューが開くだけかもしれない。

[0:40] 自分が取ったあらゆる行動の結果を理解するために、Claude には、自分が置かれた新しい状態、つまり環境を理解するためのスクリーンショットが必要だったのです。

[0:48] そしてこれと同じ考え方は、私たちが組み立てるどんなエージェントにも当てはまります

[0:52] 行動を取った後——そして時には行動を取る前に——Claude には、その行動の結果を評価する手段が本当に必要です。それも多くの場合、単にツールが返してくるもの以上の手段が。

[1:01] Claude が自分の環境を理解できるように助けてやることで、Claude はタスク完了に向けた自分の進捗をより正確に測れるようになり、さらに予期しない結果やエラーにもより上手く対処できるようになります。

[1:06] 非常によく似た考え方は、Claude Code を使う時にも見られます。

🖥 [1:10] 画面(スライド:Claude Code の read-before-write 実例) 黒いターミナル風パネル: - 一番上(オレンジ枠):> Update the main.py file. Add an '/items' route that returns a list of items —— 注釈:User Request - 中段:I'll update the main.py file to add an '/items' route. Let me first check the current content of the file.Read(main.py)… Read 31 lines (ctrl+r to expand) —— 注釈:Claude first reads the contents of the file to understand the current state(Claude はまずファイル内容を読んで現状を理解する) - 下段:Now I'll add the '/items' route to return a list of items:Update(main.py)… Updated main.py with 5 additions。差分表示(15〜17行目が緑ハイライト): python @app.get("/items") async def get_items(): return items 続けて既存の @app.get("/items/{item_id}")async def read_item(item_id: int): も見える —— 注釈:Now Claude knows it can safely update the file(これで Claude は安全にファイルを更新できると分かる) - 最後に:Added the '/items' route that returns the list of items.

[1:11] このスクリーンショットの一番上で、私は Claude に main.py ファイルを更新するよう頼みました。ここで Claude に与えたタスク——ルートを1つ追加する——は本当に単純なものです。

[1:22] しかし、そのファイルを何らかの形で修正できるようになる前に——これはものすごく当たり前に聞こえるでしょうが——Claude はまず、そのファイルの中に今どんなコードがあるのかを理解する必要があります

[1:31] つまり Claude には、ファイルの内容を読む何らかの手段が必要なのです。繰り返しますが、ものすごく当たり前に聞こえるのは分かっています。それでも、「書き込む前にファイルを読む」というこの考え方を、自分自身のエージェントを作る時にはいつでも意識することを勧めます。

[1:41] この考え方は、私たちのソーシャルメディア動画エージェントにも適用できます。

🖥 [1:44] 画面(スライド:動画エージェントへの適用図) 左側に3つのボックスが波括弧でまとめられ、右の Claude(ライフライン)へ矢印: - TaskCreate a video on Python and post it to my social media account - Toolspost_mediaweb_searchimage_generatorbash - System PromptAfter generating videos, do the following: - Use the bash tool to run whisper.cpp and generate a caption file with timestamps. Verify that the dialog was placed correctly. - Use the bash tool to run FFMPEG and extract a screenshot from every one second of the video. Verify that the video looks as you expected

[1:46] Claude にリクエストを送る時は、「Python の動画を作って、私のソーシャルメディアアカウントに投稿して」のようなタスクを、ツール一覧と一緒に与えることになるでしょう。そしてその上で、system prompt の中に特別な指示を入れて、動画を生成した後に自分の環境をどう検査できるかを Claude が理解できるようにするのです。

[2:00] 個人的には、もし FFmpeg を使った動画生成を Claude に頼るなら、ダイアログ(セリフ)の配置——具体的には、text-to-speech 機能で生成した音声クリップが再生されるタイミング——のあたりで、時々ミスをするだろうと予想します。

[2:15] こうした動画を作る時のタスク完了に向けた進捗を Claude がもっとよく理解できるように、私なら、bash ツールを使って whisper.cpp というプログラムを実行するようにという指示を与えます。

[2:29] これは、動画からキャプションファイルを自動生成するのに使えるプログラムです。そしてそのキャプションファイルにはタイムスタンプが入っています。だから Claude はこのプログラムを使って、ダイアログが正しく配置されたことを確かめられるのです。

🖥 [2:43] 画面(同スライド:System Prompt の「FFMPEG」がハイライト) System Prompt ボックス内の2つ目の箇条書き Use the bash tool to run FFMPEG and extract a screenshot from every one second of the video… の「FFMPEG」が青く選択されている。

[2:38] また、bash ツールで FFmpeg を実行することも Claude に勧めるでしょう。FFmpeg には、動画からスクリーンショットを抽出する機能があります。毎秒、あるいは10秒ごとにスクリーンショットを抽出して、その画像を見て、動画が自分の期待どおりの見た目になっているかを確かめるように、と Claude に伝えるわけです。

[2:58] これによって Claude は、自分の行動の結果——実際に自分が作った動画——を検査し、タスクをあるべき形で完了しつつあることを確認できるのです。

英語逐語(ASR全文)

The next idea around agents that we are going to discuss is environment inspection. When we were taking a look at computer use previously, you might recall something interesting. After every single action that we saw logged out on the left-hand side, like typing or moving the mouse, we always seemed to see a screenshot immediately after, and that's what I'm showing you in this diagram right here. Claude attempted to type out some text, and we see that logged in the first panel up here, and then right after it we saw a screenshot appear immediately. I'd like you to look at computer use from Claude's perspective. Claude attempts to type or click somewhere, and presumably the page is going to change, but Claude doesn't really understand how. Clicking on a button might navigate to a new page, or it might open up a menu. In order to understand the result of any action it took, Claude needed a screenshot to understand the new state or the environment that it was in. This same idea holds for any agent that we assemble. After taking an action, and sometimes before taking an action, Claude really needs a way of evaluating the result of the action, often beyond whatever just a tool returns. By helping Claude understand its environment, it can better gauge its progress towards completing a task, and also better deal with unexpected results or errors. We can see a very similar idea when we make use of Claude code. So in this screenshot at the very top, I've asked Claude to update the main.py file. Now, the task I've given Claude here of adding in an additional route is really simple, but before we can ever modify that file in any way this is going to seem really obvious well Claude needs to understand what the current code is inside the file first so Claude needs some way of reading the contents of a file now again I know it seems really obvious but I would encourage you to think about this idea around reading a file before writing to it anytime you are building an agent of your own this idea is even applicable to our social media video agent so whenever we make a request off to Claude we might give it a task like create a video on Python and post it to my social media account, along with our list of tools. Then we might provide some special instructions inside of a system prompt, helping Claude understand how it can inspect its environment after generating a video. Personally, if I was relying upon Claude to use FFmpeg to generate a video, I would kind of expect it to maybe sometimes make mistakes around the placement of dialogue, so specifically when audio clips would play that it generated using some text-to-speech functionality. To help Claude better understand its progress around completing a task when making one of these videos, I might give it some instructions to make use of the bash tool specifically to run a program called specifically whisper CPP. This is a program we can use to generate caption files automatically out of a video. And those caption files have timestamps inside them. So Claude could use this program to make sure the dialogue was placed correctly. We might also advise Claude to use the bash tool to run FFmpeg, which has the ability to extract screenshots out of a video. We might tell Claude to extract a screenshot from every second or every 10 seconds and take a look at the screenshots just to make sure that the video looks as it kind of expects it to look. This allows Claude to inspect the results of its actions, the actual video it created, and make sure that it's completing the task as it should.

↑ 目次へ戻る

Building with the Claude API | Lesson 75「Workflows vs agents」(全文ナレッジ)

動画 1分59秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04

日本語ナレッジ(要点整理)

このレッスンのテーマ

Agents 章の締めくくりとして、ワークフローとエージェントの比較・対照で総括する回。2分弱の短いレッスンだが、この章全体の結論——「可能な限りワークフロー、エージェントは真に必要な時だけ」——が明言される最重要回。

ワークフロー側の整理

  • 定義:事前に定義された、Claude への一連の呼び出し(a predefined series of calls to Claude)。
  • 使いどき:タスク完了に必要な正確な手順の連なりを事前に思い描ける時。
  • 共通テーマ:大きなタスクをずっと小さなタスク群に分割する。小タスクは性質がより特定的になり、Claude は一度に1つの領域だけに集中できる。
  • 利点①精度:この集中の高まりが、エージェントと比べてタスク完了の精度を総じて高くする
  • 利点②テスト性:ワークフローが実行する手順は正確に分かっているので、テストと評価がはるかに容易

エージェント側の整理

  • 定義:どんなタスクが来るか正確には分からない前提で、堅実な基本ツール一式を渡し、Claude がそれらを組み合わせてタスクを完了することを期待する方式。
  • 利点①柔軟な問題解決:石に刻まれた手順に縛られない。Claude が創造的に多種多様な課題への対処方法を見つけられる。
  • 利点②柔軟な UX:ワークフローが「非常に特定の入力セット」を期待するのに対し、エージェントはユーザーのクエリから自分で入力を作り出せるし、必要なら追加の入力をユーザーに求めることもできる。
  • 欠点①成功率:Claude に委ねる仕事量が大きいぶん、ワークフローに比べてタスク完了の成功率が総じて低い
  • 欠点②テスト困難:エージェントがどんな手順を実行するか事前に分からないことが多く、テストと評価が難しい

スライドの対比表(原文の整理)

Workflows Agents
Summary 既知の問題(群)を解くための事前定義された一連の Claude 呼び出し。手順の流れを事前に描けるタスクに使う Claude に基本ツール一式を与え、それらを使ってタスクを完了する計画を Claude が立てることを期待する
Benefits 一度に1つのサブタスクに集中→総じて高精度/各ステップが既知→評価・テストがはるかに容易 より柔軟な UX/はるかに柔軟なタスク完了。Claude がツールを予想外の形で組み合わせ、多種多様なタスクをこなす
Downsides 柔軟性が大きく劣る(特定タスク専用)/UX がより制約される(フローへの正確な入力が必要) タスク完了成功率が低い/計測・テスト・評価がより困難

結論:エンジニアの本分は「確実に解く」こと

  • エージェントは本当に面白い。しかし、エンジニアとしての第一の目標は「問題を確実に(reliably)解決すること」
  • ユーザーはあなたが「イケてるエージェント」を作ったことなど、おそらく気にしない。欲しいのは 100% 動く製品だけ。
  • したがって一般的な推奨は:可能な場所では常にワークフローの実装に注力し、エージェントは真に必要とされる時にのみ頼る(only resort to agents when they are truly required)

実務ポイント(このレッスンから持ち帰るもの)

  • 設計判断のフローチャートはただ1問:「タスク完了までの手順を事前に描けるか?」→ 描ける=ワークフロー、描けない・入力が多様=エージェント。
  • デフォルトはワークフロー、エージェントは最後の手段。「agent と呼べる物を作りたい」から始めるのは本末転倒——ユーザーが評価するのは信頼性 100% の動作であって、アーキテクチャの格好良さではない。
  • 精度が要る・テストしたい→ワークフロー:手順が固定だから各ステップに eval を張れる(この章の前半までで学んだ prompt evals がそのまま効く)。
  • 入力の形が読めない・対話が動的→エージェント:ユーザークエリから入力を自分で構成でき、不足時は聞き返せる。この UX 要件がある時がエージェントの出番。
  • エージェント採用時は欠点2つをセットで受け入れる覚悟を:①成功率の低下(委任量が大きい)②評価の難しさ(手順が不定)。緩和策は前レッスンまでの「抽象ツール設計」「environment inspection」。
  • 大タスクの分割はワークフローの本質。分割で各呼び出しの焦点が絞られ精度が上がる——これは Claude 全般のプロンプト設計にも通じる普遍則。

本文(時系列・日本語逐語+画面差し込み)

[0:00] ワークフローとエージェントのさまざまな側面を比較・対照しながら、まとめに入りましょう。

🖥 [0:00] 画面(スライド「Workflows / Agents」対比表。このレッスンは全編このスライド1枚) 左列 Workflows(ベージュ)/右列 Agents(テラコッタ)で Summary・Benefits・Downsides を対比: - Workflows – SummaryA predefined series of calls to Claude meant to solve a known problem or set of problemsWorkflows are used to handle tasks where you can picture the flow of steps ahead of time - Workflows – BenefitsClaude can often focus on one subtask at a time, generally leading to higher accuracyFar easier to evaluate and test, since we know each exact step - Workflows – DownsidesFar less flexible - dedicated to solving specific of tasksGenerally more constrained UX - we need to know the exact inputs to the flow - Agents – SummaryClaude is given a set of basic toolsWe expect Claude to formulate a plan to use these tools to complete a task - Agents – BenefitsAllow for more flexible UXFar more flexible task completion. Claude can combine tools in unexpected ways to complete a wide variety of tasks - Agents – DownsidesLower successful task completion rateMore challenging to instrument, test, and evaluate

[0:04] まず思い出してください。ワークフローとは、事前に定義された、Claude への一連の呼び出しです。ワークフローを使うのは多くの場合、タスクを完了するために必要な正確な手順の連なりについて、しっかりした見当がついている時です。

[0:15] 一方エージェントでは、どんなタスクが与えられるのかを正確には知りません。そこで代わりに、堅実な基本ツール一式を提供し、与えられたタスクを完了するために Claude がそれらのツールを組み合わせることを期待します。

[0:26] お気づきかもしれませんが、ワークフローに共通するテーマは、大きなタスクを、ずっと小さなタスク群へと分割することです。

[0:33] これらの小さなタスクの一つひとつは、性質としてより特定的なものになり、Claude は一度に単一の領域だけに集中できるようになります。

[0:39] この集中の高まりは、エージェントと比較して、タスク完了のより高い精度へと総じてつながります。

[0:45] また、ワークフローが実行する正確な手順の連なりを私たちは知っているので、テストや評価もはるかに容易です。

[0:51] エージェントの場合、石に刻まれたような一連の手順に縛られることはありません。その代わり Claude は、多種多様な課題への対処方法を創造的に見つけ出すことができます。

[1:03] この柔軟性に加えて、ユーザー体験(UX)における柔軟性も手に入ります。ワークフローが非常に特定の入力セットを受け取ることを期待するのに対し、エージェントはユーザーから受け取ったクエリに基づいて自分自身で入力を作り出せますし、必要な時にはユーザーに追加の入力を求めることもできます。

[1:14] エージェントの欠点は、ワークフローと比べてタスク完了の成功率が総じて低いことです。なぜなら、非常に多くの仕事を Claude に委任しているからです。

[1:25] 加えて、テストや評価もより難しいです。エージェントが与えられたタスクを完了するためにどんな一連の手順を実行するのか、私たちにはしっかりした見当がつかないことが多いからです。

[1:35] 結局のところ、エージェントは本当に興味深いものです。しかし思い出してください。エンジニアとしてのあなたの第一の目標は、問題を確実に(reliably)解決することです

[1:41] ユーザーはおそらく、あなたが小洒落たエージェントを作ったことなんて気にしていません。彼らが本当に欲しいのは、100% きちんと動く製品なのです。

[1:50] ですからこれを踏まえた一般的な推奨は:可能な場所では常にワークフローの実装に注力し、エージェントには、それが真に必要とされる時にのみ頼ること。これに尽きます。

英語逐語(ASR全文)

Let's wrap up by comparing and contrasting some different aspects of workflows and agents. First, recall that workflows are a predefined series of calls to Claude. We often use workflows when we have a good idea of the exact series of steps that are needed to complete a task. With agents, on the other hand, we don't know exactly what tasks will be provided. So we instead provide a solid set of basic tools and expect Claude to combine these tools together to complete a given task. You might have noticed that a common theme around workflows is that we take a big task and we divide it up into much smaller tasks. Each of these smaller tasks are much more specific in nature, allowing Claude to focus on a single area at a time. This increased focus generally leads to higher accuracy for completing a task, compared to agents. Because we know the exact series of steps that a workflow executes, they're also far easier to test and evaluate. it. With agents, we aren't constrained to a series of steps etched in stone. Instead, Claude can creatively figure out how to handle a wide variety of challenges. Along with this flexibility, we also get flexibility in the user experience. While workflows expect to receive a very particular set of inputs, agents can create their own inputs based on queries received from the user, and agents can also ask user for more input when it's needed. The downside to agents is that they generally have a lower successful task completion rate compared to workflows because we are delegating so much work to Claude. In addition, they're also harder to test and evaluate since we often don't have a good idea of what series of steps an agent will execute to complete a given task. At the end of the day, agents are really interesting, but remember your primary goal as an engineer is to solve problems reliably. Users probably don't care that you've made a fancy agent. They really just want a product that's went to work 100% of the time. So with this in mind, the general recommendation is to always focus on implementing workflows where possible and only resort to agents when they are truly required.

↑ 目次へ戻る

Building with the Claude API | Lesson 76「Course Wrap Up」(全文ナレッジ)

動画 3分11秒 / 講師: Anthropic Academy / 文字起こし: mlx_whisper large-v3-turbo / 2026-07-04

日本語ナレッジ(要点整理)

このレッスンのテーマ

コース最終回。ここまで完走した受講者への労いから始まり、①コース全体で扱った主要トピックの高速リキャップと、②今後自分で研究すべき推奨フォローアップトピックの2本立てで締めくくる。

リキャップ:コースで扱った7領域(スライド「What we've covered」)

  1. Models offered by Anthropic(Anthropic のモデル群):高速・小規模リクエスト向けの Haiku、より高い知能の Sonnet を使い分ける。
  2. Accessing Claude via the API(API 経由のアクセスとパラメータ)temperature・stop sequences・message prefilling など。Claude を特定の方向に導き、創造性をコントロールし、期待どおりのデータ形式・出力を確実に得るためのパラメータ群。
  3. Prompt evaluations(プロンプト評価):講師が「強調してもしきれない」と言う、自分のプロジェクトに実装すべき圧倒的最重要プラクティス。 - 自分でプロンプトを10回走らせて「問題なし」と思っても、本番デプロイした途端、ユーザーは望んだ結果を得られないかもしれない。 - 効果的なプロンプトを書けていると確かめる唯一の方法が評価(eval)。 - しかも eval は難しくなくていい:立派なフレームワークも大掛かりなセットアップも不要。eval フレームワーク自体を Claude に生成させられる——実際このコースで使った eval フレームワークのコードの多くは Claude が直接書いたもの。
  4. Prompt engineering(プロンプトエンジニアリング):覚えておくべき一握りのテクニック。筆頭は「明確に・直接的に、Claude に期待することをそのまま伝える」こと。
  5. Tool use(ツール使用):おそらくコース内で最も複雑だったセクション。Claude の能力を劇的に拡張する、極めて重要な機能。
  6. Claude Code and Computer Use:Anthropic が直接リリースした2つの重要アプリケーション。講師自身はコード執筆に Claude Code を使っており、凝ったエディタ内アシスタントはあまり使わず、動画で見せたのと全く同じスタイルでターミナルから Claude Code を走らせている
  7. Workflows and Agents:エージェントは確かに面白くエキサイティングなトピック。しかし多くの場合、ワークフローの方が良い結果と高い精度を得られる(前レッスンの結論を最終確認)。

フォローアップ推奨トピック(スライド「Additional topics to investigate」)

時間の都合でコースで扱えなかった「大物」たち。自分で研究する価値があるもの: - Agent orchestration(エージェントオーケストレーション):複数エージェントを協調させる方法 - Agent evaluation and instrumentation(エージェントの評価と計測):エージェントの性能を評価・監視する方法 - Agentic RAG:RAG の変種。エージェント的に検索を回す方式 - RAG evaluation(RAG の評価) - Tool evaluation(ツールの評価):prompt evals と似た営み。ツールの description が、本当に期待どおりの形で Claude を助けているかを確かめる

締め

このコースを楽しんでもらえたことを願うとともに、このコンテンツに取り組んでくれた時間に感謝を——で全76レッスンが完結。

実務ポイント(このレッスンから持ち帰るもの)

  • コース全体の最重要メッセージ=「prompt evals を自分のプロジェクトに実装せよ」。手元で10回動いた≠本番で動く。効果的なプロンプトの唯一の証明手段は eval。
  • eval のハードルは低くしてよい:フレームワーク導入不要。「eval を書くこと」自体を Claude に委任する(このコースの eval コードの多くが Claude 製という実例つき)。
  • モデル選定の基本形:速度・コスト優先の小リクエスト=Haiku、知能優先=Sonnet。
  • プロンプト技法で迷ったら第一原則に戻る:明確・直接的に期待を伝える(clear and direct)。temperature/stop sequences/prefilling は出力の方向・形式の制御装置。
  • ツール使用が Claude 活用の分水嶺:能力拡張の本丸であり、コースでも最も複雑な章だった。ここへの習熟投資は回収が大きい。
  • 講師の実務スタイルも参考に:エディタ内アシスタントよりもターミナルの Claude Code をそのまま使う。
  • アーキテクチャ選定の最終結論:エージェントは魅力的だが、精度と結果を求めるならまずワークフロー
  • 次に学ぶべき5トピックをロードマップ化:agent orchestration → agent evaluation/instrumentation → agentic RAG → RAG evaluation → tool evaluation。特に tool evaluation は「tool description が Claude を期待どおり助けているかの検証」で、prompt evals の延長として着手しやすい。

本文(時系列・日本語逐語+画面差し込み)

[0:00] このコースをここまでやり遂げたのなら、あなたは途方もない量の努力を注ぎ込んできたということです。おめでとうございます。

[0:05] 締めくくる前に、これまで議論してきた主要トピックをごく手短に振り返り、今後研究してほしいおすすめのフォローアップトピックをいくつかお伝えしたいと思います。

[0:13] まず、私たちがカバーしてきた内容の簡単な振り返りです。

🖥 [0:13] 画面(スライド「What we've covered」) 左に見出し What we've covered、右に7つのボックスが縦に並ぶ: 1. Models offered by Anthropic 2. Accessing Claude via the API 3. Prompt evaluations 4. Prompt engineering 5. Tool use 6. Claude Code and Computer Use 7. Workflows and Agents

[0:16] このコースは、Anthropic が提供するさまざまなモデルの話から始まりました。思い出してください。私たちは、高速で小さなリクエストのための Haiku と、より高い知能のための Sonnet を使えます。

[0:25] それから多くの時間を使って、API 経由で Claude にアクセスする方法と、Claude の応答を調整するために与えられるさまざまなパラメータについて議論しました。

[0:33] 例えば、temperature、stop sequences、message prefilling について話しましたね。これらはすべて、Claude を特定の方向に導き、その創造性をコントロールし、期待するデータや出力の正しいフォーマットを確実に得るために使えるパラメータです。

[0:46] その後、プロンプト評価(prompt evaluations)についてかなりの時間をかけて話しました。

[0:49] これは、いくら強調しても足りません。

[0:51] プロンプト評価は、あなた自身のプロジェクトで実装すべき圧倒的に最重要のプラクティスです。

[0:57] 自分で腰を据えてプロンプトを10回走らせて、すべて問題ないと思うかもしれません。しかし本番環境にデプロイした途端、ユーザーはあなたが望んだ結果を得られないかもしれないのです。

[1:06] 効果的なプロンプトを書けていることを確かめる唯一の方法は、それらを評価することです。

[1:10] そして思い出してください。プロンプト評価は、難しいものである必要はありません。

[1:13] 立派なフレームワークを使う必要も、大掛かりで複雑なセットアップをする必要もありません。

[1:18] プロンプト評価フレームワークを、Claude を使って自分で生成することができるのです。

[1:22] そしてそれこそが、このコースの中であなたが見てきたものにほかなりません。私たちが使ったプロンプト評価フレームワークは、そのコードの多くを Claude が直接書いたものでした。

[1:29] それから、プロンプトエンジニアリングについて時間をかけて議論しました。

[1:31] 覚えておくべきテクニックは一握りです。その筆頭は、ただシンプルに、明確に・直接的に、Claude に期待することを伝えることです。

[1:40] その後はツール使用(tool use)に多くの労力を割きました。あれはおそらく、このコースの中でもより複雑なセクションの1つでしたね。

[1:46] ツール使用は極めて重要です。なぜなら、Claude の能力を劇的に拡張するからです。

[1:50] それから、Anthropic が直接リリースした2つの重要なアプリケーション、Claude CodeComputer Use に時間を使いました。

[1:57] Claude Code のハンズオン体験を楽しんでもらえていたら嬉しいです。

[2:01] 個人的には、私がコードやプロジェクトを書くのを助けてもらうのに使っているのが、まさに Claude Code です。

[2:04] 実のところ、凝ったエディタ内アシスタントはそれほど使っていません。あの動画で見てもらったのと全く同じスタイルで、ターミナルで Claude Code を走らせているだけなのです。

[2:13] そして最後に、ワークフローとエージェントの議論にかなりの時間を使いました。

[2:17] 覚えておいてください。エージェントは間違いなく面白い。とてもエキサイティングなトピックです。しかし非常に多くの場合、ワークフローを使う方が、より良い結果とより高い精度を得られるのです。

[2:26] 時間の都合で、このコースの中ですべてのトピックをカバーすることはできませんでした。

[2:30] あなた自身でフォローアップして研究することをおすすめする「大物」がいくつかあります。

🖥 [2:27] 画面(スライド「Additional topics to investigate」) 左に見出し Additional topics to investigate、右に5つのボックス: 1. Agent orchestration 2. Agent evaluation and instrumentation 3. Agentic RAG 4. RAG evaluation 5. Tool evaluation

[2:35] その筆頭は、エージェントを巡る多くのトピックです。エージェントオーケストレーションでエージェント同士を協働させる方法を理解すること、エージェントの性能を評価・監視する方法を理解すること、そして agentic RAG として知られる RAG の変種——これらはすべて、自分で時間を取って研究することをおすすめするトピックです。

[2:53] また、RAG の評価ツールの評価のさまざまなテクニックにも目を通すことをおすすめします。

[2:58] ツール評価は、プロンプト評価と似ています。ツールの description が、私たちが本当に期待するとおりの形で Claude を助けているのかを確かめたいのです。

[3:06] さて、ひとまずこれで終わりです。

[3:07] 申し上げたとおり、このコースを楽しんでもらえたなら嬉しいです。このコンテンツに取り組むために割いてくれたあなたの時間に感謝します。

英語逐語(ASR全文)

If you have made it through this course, you have put in a tremendous amount of effort and I congratulate you. Before we close things out, I want to very quickly recap some of the major topics we discussed and give you some recommended follow-up topics to research in the future. First, a quick recap of what we have covered. We began this course by discussing some of the different models offered by Anthropic. Remember, we have access to Haiku for fast, small requests and Sonnet for greater intelligence. We then spent a lot of time discussing how to access Claude via the API and different parameters we could feed into Claude in order to adjust its response. For example, we discussed temperature and stop sequences, message prefilling. All these are parameters we can use to direct Claude in certain directions, control its creativity, and ensure that we get the correct format of data or output that we expect. We then spent a good amount of time talking about prompt evaluations. I cannot stress it enough. Prompt evaluations is by far the most important practice for you to implement on your own projects. You might sit down and run a prompt 10 times on your own and think everything is okay. But as soon as you deploy it in production, your users might not get the results that you want them to get. The only way to make sure that you are writing effective prompts is by evaluating them. And remember, prompt evals, they don't have to be hard. You do not have to make use of a fancy framework or do any kind of a big complicated setup. You can use Claude to generate a prompt evaluation framework on your own. And that's kind of what you saw inside this course. The prompt eval framework that we made use of, a lot of that code was written directly by Claude. We then spent some time discussing prompt engineering. Remember, there's a handful of techniques for you to keep in mind. Chief among them is just simply being clear and directly telling Claude what you expect of it. After that, we spent a lot of effort on tool use, and that was probably one of the more complicated sections inside the course. Tool use is extremely important because it dramatically expands the capabilities of Claude. We then spent some time with two important applications that have been released by Anthropik directly, Claude Code and Computer Use. I hope you enjoyed getting some hands-on experience with Claude Code. Personally, it is what I use to help me author code and projects. I don't actually use a fancy in-editor assistant that much. I really just run Claude Code at the terminal in the exact same style that you saw in those videos. And then finally, we spent a decent amount of time discussing workflows and agents. Now, remember, agents are definitely interesting. It's a very exciting topic, but you very often are going to get better results and higher accuracy by the use of workflows. For a lack of time, we were not able to cover every topic inside of this course. There are some big ticket items that I recommend you follow up and do some research on your own. Chief among them is a lot of topics around agents. understanding how to get agents to work together with agent orchestration, understanding how to evaluate an agent's performance and monitor it, and a variation on RAG known as agentic RAG are all topics that I recommend you spend some time to research on your own. I also recommend you take a look at some different techniques for RAG evaluation and tool evaluation. Tool evaluation is similar to prompt evals. We want to make sure that our tool descriptions are helping Claude in the way we really expect. Well, that's it for now. Like I said, I hope you enjoyed this course and I appreciate your time in working through this content.

↑ 目次へ戻る