Claude 学習
コースガイド(分割)

Building with the Claude API 第1章 — API基礎

Lesson: Welcome to the course(コースへようこそ)

講師は Stephen Grider(Anthropic の Technical Staff メンバー)。このレッスンではコース全体の構成と受講にあたっての前提・コツが説明される。

カバーする内容の流れ:

  1. Anthropic が提供するモデルの背景知識から開始
  2. 公式 Anthropic API を通じたモデルへのアクセス方法の理解
  3. プロンプティング — まず evaluation(評価)、次に engineering(エンジニアリング)のテクニック
  4. tool use(ツール使用)による Claude の能力拡張のディープダイブ
  5. RAG(Retrieval Augmented Generation)でタスク固有のコンテキストを Claude に与える方法
  6. MCP(Model Context Protocol)で Claude を新しいサービスに接続する方法
  7. Anthropic が作った2つのエージェント「Claude Code」と「Computer Use」を実際に使ってみる
  8. 最後に、workflows(ワークフロー)と agents(エージェント)でより複雑なタスクを扱う方法

受講の前提条件:

成功のためのコツ(講師からの4つの助言):

  1. コードは一緒に書くこと。 他人が書くコードを見ているだけでは十分に吸収できない。
  2. 動画の再生速度を上げること。 速いペースでコンテンツをこなせるようになり、特に難しい動画を見直すときにも役立つ。
  3. 学んだテクニックを実際に適用すること。 作成した notebook を拡張したり改造したりしてみる。
  4. 行き詰まったら Claude に助けを求めること。 Claude はほとんどどんな状況からも抜け出す手助けをしてくれるし、混乱しているポイントを明確にしてくれる。

Lesson: Overview of Claude models(Claudeモデルの概観)

このレッスンでは Claude の3つのモデルファミリーを検証し、それぞれの用途に最適なモデルを見極める。各モデルの主要な特徴を説明した上で、正しいモデルを選ぶためのシンプルなフレームワークを示す。

大前提: 3つのモデルはすべて Claude のコア能力(テキスト生成、コーディング、画像解析など多数のタスク)を共有している。本当の違いは「どう最適化されているか」にある。1つは知性(intelligence)に焦点、1つは速度とコスト効率、1つは知性と速度のバランスを重視している。

Opus — 最も知的なモデル

Opus は Claude の中で最も capable(能力が高い)なモデル。「capable」というのは、Claude から得られる最高水準の知性を提供するという意味である。実務的には、Opus は複雑な要件があり、それを完遂するために高いレベルの知性と計画性が必要なシナリオ向けに設計されている。

Sonnet — バランスの取れたモデル

Sonnet は Claude のラインナップの中で一種の「スイートスポット」に位置する。知性・速度・コストの良いバランスを持ち、大多数の実務ユースケースに非常に有用である。

Haiku — 最速のモデル

Haiku は Claude の中で最も速いモデルで、特にレスポンスタイムが重要なアプリケーション向けに作られている。

モデル選択のフレームワーク

3モデルの位置づけは「高い知性」対「コストと速度」というトレードオフの軸で理解できる。

モデルを選ぶ決め手は、自分のユースケースにとって何が最も重要かを特定することである。

重要な補足: 多くのチームは1つのモデルだけを選んで使い続けるわけではない。同じアプリケーションの中で複数の異なるモデルを使うことがよくある。例えば、速度が重要なユーザー向けインタラクションには Haiku を、メインのビジネスロジックには Sonnet を、深い推論が必要な本当に複雑なタスクには Opus を、といった使い分けである。

以上が Claude の3つのモデルファミリーとその選び方である。なお、このコースでは最も頻繁に Claude Sonnet を使用する——3つの性質の素晴らしいバランスを与えてくれるからである。


Lesson: Accessing the API(APIへのアクセス)

このモジュールでは、Claude にアクセスしてテキストを生成させる方法を検証する。リクエストの完全なライフサイクルを一つずつウォークスルーし、その裏で Claude の内部で何が起きているかも簡単に見ていく。

題材となるシナリオ: シンプルで標準的なチャットボットアプリを考える。ユーザーがメッセージを入力して送信をクリックすると、何らかの応答が魔法のように画面に現れることを期待する——というものである。この画面裏で何が起きているかを5つの別々のステップに分解して、一つずつウォークスルーする。

ステップ1: クライアント → 自分のサーバー

ユーザーがテキストを入力して送信をクリックすると、そのテキストは開発者自身が実装するサーバーに送られる。ここで重要な点として:Web/モバイルアプリから直接 Anthropic API にアクセスしてはいけない。 API へのリクエストには秘密の API key を含める必要があり、このキーを秘密に保つ最善の方法は、クライアント側のアプリに絶対に含めず、開発者自身が実装するサーバーを経由してのみ API へリクエストすることである。

ステップ2: 自分のサーバー → Anthropic API

サーバーがクライアントからリクエストを受け取ったら、サーバーは Anthropic API に直接リクエストを行う。通常は Anthropic が公開している SDK のいずれかを使ってこのリクエストを行う。公式 SDK 実装は Python、TypeScript(JavaScriptも同一パッケージでカバー)、C#、Go、Java、PHP、Ruby 向けにある(言語の顔ぶれは今後も増える可能性がある)。SDK を使わなくてもよく、プレーンな HTTP リクエストを行うこともできる。

このリクエストを行う際、いくつかのデータを一緒に渡す必要がある:

ステップ3: Anthropic API(テキスト生成の内部プロセス)

ここが実際にテキストが生成される場所。このプロセスは複雑なので、簡略化した高レベルの概要が示される。テキスト生成プロセスを4つの別々のステージに分解する。

  1. トークン化 (tokenization): ユーザーの入力がより小さな文字列に分解される。この各テキストの断片は token(トークン) と呼ばれる。トークンは単語全体のこともあれば、単語の一部、あるいはスペースや記号のこともある。話をわかりやすくするため、ここでは各単語が1つのトークンを形成すると仮定する。

  2. embedding(埋め込み)への変換: 各トークンは embedding に変換される。embedding とは長い数値のリストであり、これらのリストは「与えられた単語の数値ベースの定義」のようなものだと考えることができる。ここで書き言葉の興味深い側面として、単一の単語が多くの可能な意味を持ち得るという点が挙げられる。ある単語の文中での位置と、その周囲に存在する他の単語だけが、その定義を1つの特定の意味に絞り込む。例えば「quantum(量子)」という単語は多くの異なる定義を持ち、この単語を見ただけでは周囲の他の単語を見るまでその意味はわからない。同様に、各 embedding は各単語のあらゆる可能な意味を含んでいるものと考えることができる。

  3. contextualization(文脈化): 各 embedding を単一の正確な定義に絞り込むために、contextualization というプロセスが使われる。contextualization では、各 embedding がその周囲にある他の embedding に基づいて調整される。このプロセスは、各 embedding の意味のうち、その隣接する embedding に照らして最も意味の通る部分を際立たせる助けになる。

  4. generation(生成): これが実際にテキストが書かれる最終ステップ。この時点までに、各 embedding はその隣接するものから膨大な量の情報を吸収している。最終的に処理された embedding は output layer(出力層)に渡され、そこで可能な次の単語それぞれについて確率が生成される。ここでモデルは自動的に最高確率のものを選ぶわけではない。代わりに、確率とランダム性を組み合わせて単語を選択し、これがより自然でバラエティに富んだ応答を生み出す助けになる。選ばれた単語は embedding のリストの末尾に追加され、プロセス全体が再び繰り返される。

停止条件の判定: 出力トークンを1つ生成するたびに、モデルは一時停止していくつかの問いを自らに投げかけ、生成が完了したかどうかを決める。

ステップ4・5: 応答の送信と表示

生成が完了すると、API はレスポンスをサーバーに送り返す。レスポンスには、生成されたテキストが入った message、そして usagestop reason が含まれる。

サーバーがこのレスポンスを受け取ったら、生成されたテキストを Web アプリやモバイルアプリに送り返し、そこで画面に表示する。

以上が全体のフローである。このレッスンでは多くのトピックをカバーしたが、今すぐ全部を暗記する必要はない。目標は、Claude に API 経由でアクセスする際によく使われる用語に慣れ始めることだけである。


Lesson: Making a request(リクエストを作る)

これまでは話をしてきただけなので、このレッスンでは実際に手を動かしてコードを少し書く。Anthropic API へのシンプルで基本的なリクエストの作り方を学ぶ。セットアップは4ステップで構成される。

ステップ1: notebook を開き、パッケージをインストールする

Jupyter notebook を開き、Anthropic Python SDK と python-dotenv というパッケージをインストールする。講師はすでに notebook にガイド用のコメントを書き込んでいる。

%pip install anthropic python-dotenv

これはマジックコマンドで、%pipinstallanthropicpython-dotenv という並びで書く。Visual Studio Code の中で notebook を書いている場合、この % から赤い構文エラーが表示されることがあるが、これは問題なく無視してよい。このコマンドを書いたら実行してパッケージをインストールする。その後、画面を見やすくするために出力をクリアする。

ステップ2: API key を保存・読み込む

次に python-dotenv パッケージを使って API key を保存・読み込む。API key の作成方法は前のレクチャーで説明済みという前提。まだ作っていない場合は前のレクチャーに戻って手順を確認する必要がある。

このキーをエディタ内に保存するために、notebook と同じディレクトリに、とても特別な名前のファイルを作成する。ファイル名は .env。この中に、先ほど生成した API key を配置する。具体的には次のように書く:

ANTHROPIC_API_KEY="<ここにキーを入れる>"

ANTHROPIC_API_KEY とイコール記号、そしてダブルクォートの中にキーを入れる。このファイルを作ってキーを中に入れる理由は、バージョン管理を使う際にこのファイルを ignore できるようにするためである。これにより、このファイルを誤って git にコミットし、誰でも見られる公開リポジトリに誤ってプッシュしてしまう事態を防げる。Git や類似のバージョン管理システムを使っている場合、作業をコミットするときは必ずこのファイルを ignore するようにする。

notebook に戻り、この環境変数をセキュアに読み込む。

ステップ3: API クライアントを作成する

Anthropic パッケージを使って API クライアントを作成する。同じセルの中で、model という変数名も宣言する。これは文字列で、Anthropic API 内で実行したいモデルの名前を含む。講師は収録当時の Claude 3.7 Sonnet を使用しているが、このモデルは2026年2月19日付でAPI提供が終了しており、現在同じ手順を試す場合は claude-sonnet-5claude-opus-4-8 など最新のモデルIDに置き換える必要がある。

ステップ4: リクエストを実行する

先ほど作成したクライアントを使って実際にリクエストを行う。コードを書く前に、いくつかの用語を説明しておく。

メッセージには2種類ある:

実際にリクエストを実行する

notebook の最後のセルで、message という変数を宣言し、client.messages.create からの戻り値を代入する。渡す引数は次の通り:

このリストには1つの user message を入れる。中身は Claude に送りたい質問やクエリである。user message を作るには、role"user" で、content に Claude へ投入したい実際の文字列を持つディクショナリを作る。ここでは Claude に量子コンピューティングを定義するよう頼む例として "what is quantum computing answer in one sentence"(量子コンピューティングとは何か、一文で答えて)を使う。

message = client.messages.create(
    model=model,
    max_tokens=1000,
    messages=[
        {"role": "user", "content": "what is quantum computing answer in one sentence"}
    ]
)

これを実行すると、実際に Claude にアクセスするため少し時間がかかる。次のセルで message 変数を print してみると、多くの情報が返ってくる中に、量子コンピューティングとは何かの定義が含まれているのがわかる。この message 変数の中では、テキストがかなり深くネストされている。生成されたテキスト部分だけが欲しく、他のプロパティは気にしないことが非常に多い。生成されたテキストだけにアクセスするには次のように書く:

message.content[0].text

このセルをもう一度実行すると、生成されたテキストだけが表示されるようになる。


Lesson: Multi-Turn conversations(マルチターン会話)

ここまで書いてきたコードは、モデルとの非常にシンプルな一往復のやり取りをシミュレートするものだった。この会話はチャットボックスの中でイメージできる: 「量子コンピューティングとは何か、一文で答えて」という質問を送り、非常にシンプルな一文の返答を得た。当然、この会話をどこかの時点で続けたくなる。例えば「もう一文書いて」というフォローアップを送りたくなり、それに対して量子コンピューティングについて何らかの形で拡張した応答が返ってくることを期待するだろう。

Anthropic API がステートレスであること

このようなマルチメッセージの会話を持つためには、Anthropic API と Claude 自体について理解しておくべき本当に重要なことがある。それは、Anthropic の Messages API は「ステートレス」であるということである。つまり Claude 側はどの会話ともセッションやメモリを結び付けて記憶しておらず、あるリクエストで送ったメッセージやその応答を、次のリクエストのために自動的に覚えておいてくれるわけではない(なお、正確には「一切保存されない」わけではなく、通常は安全性審査の目的で入出力が最大30日ほど一時的に保持された後に自動削除される。ゼロデータ保持契約を結んでいれば保存されない)。したがって、複数のメッセージが文脈や流れを維持するような会話をしたい場合、次の2つのことをする必要がある:

  1. コードの内部で、交換しているすべてのメッセージのリストを手動で維持する。
  2. フォローアップのリクエストを行うたびに、そのメッセージのリスト全体を毎回提供する。

実証: Claude はメッセージを保存しないことの確認

講師はまず、Claude が実際にメッセージを保存していないことを証明するサンプルコードを書く。最初に「量子コンピューティングとは何か」と尋ね、次に「もう一文書いて」と尋ねる、という会話をシミュレートする。この2番目のリクエストでは、量子コンピューティングとは全く関係のないテキストが返ってくることが示される。

なぜこの結果になるのかを図で説明する:

解決策: メッセージ履歴を自前で維持する

この問題を解決する方法を図で示す:

  1. まず、user message が1つだけの最初のリクエストを、もう一度行う。
  2. 次に、返ってきた assistant message を取得し、それをメッセージのリストに追加(append)する。
  3. この会話をフォローアップしたい・続けたい場合、リストの末尾に user message を追加(append)する。
  4. これにより会話は「本当の会話」として読めるようになる: 量子コンピューティングを定義してくれと頼み、応答を得て、そして別の質問——「もう一文書いて」——を追加している。
  5. このメッセージのリストを Claude に送ると、Claude は会話全体の文脈と履歴を持つことになる。これまで交換したすべての以前のメッセージを見た上で、より合理的な応答——前の回答をもう少し拡張する一文のフォローアップ——を返してくれることが期待できる。

コードでの実装: 3つのヘルパー関数

この全体のフローをコードで実装するため、会話の履歴・文脈を維持する助けになる3つのヘルパー関数を作る。これらのヘルパー関数はコースの残り全体を通してかなり頻繁に使うことになる。

1つ目: add_user_message

messages のリストと text を受け取る。user_message という変数を作り、role"user"content が渡された text を持つ。この新しい user_messagemessages のリストに追加(append)する。

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

2つ目: add_assistant_message

上の関数をコピーして名前を add_assistant_message に変え、"user" と書かれている箇所をすべて "assistant" に置き換える。

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

3つ目: chat

先ほどの messages.create の関数呼び出しを取り出し、名前を chat に変える。chat を呼び出すときは messages のリスト——つまりメッセージ履歴——を渡す。この呼び出しをインデントし、messages を引数として渡し、この関数の戻り値は message.content[0].text にする。

def chat(messages):
    message = client.messages.create(
        model=model,
        max_tokens=1000,
        messages=messages
    )
    return message.content[0].text

以上が3つのヘルパー関数である。これらはコースの残り全体を通してかなり頻繁に使う。これらのヘルパー関数のおかげで、時間をかけて何らかの履歴や文脈を維持する会話を持つことが、著しく簡単になる。

実際に使ってみるデモ

まず空の messages リストを作る。この messages 変数は会話履歴全体を格納していると考えられる。時間をかけて、さまざまな user メッセージと assistant メッセージのコレクションをここに追加していく。

次に、最初の user message を追加する。add_user_message 関数を呼び出し、メッセージを追加するリストを渡し、user のテキストとして "define quantum computing in one sentence"(量子コンピューティングを一文で定義して)を渡す。ここで正しい方向に進んでいるかを確認するため messages のリストを print してセルを実行すると、正しい構造のメッセージがすぐに確認できる——リストの中に、role"user" で、Claude に投入したい何かを含む content を持つディクショナリが1つある、という構造である。

次に、先ほど組み立てた chat 関数を呼び出して Claude を簡単に呼び出せる。chat を呼び出して messages のリストを渡すと、何らかの答えが返ってくる。この answer を print してセルをもう一度実行すると、量子コンピューティングについての一文が表示されるはずである。

この時点で、Claude に最初のメッセージを送り、応答として assistant message を受け取った状態になっている。今度はこの answer を取得し、会話履歴に追加(append)する必要がある。先ほど定義した add_assistant_message 関数を使って追加する: add_assistant_message を呼び出し、messages のリストに、たった今取得した answer の content を追加する。

再度、メッセージのリストが正しく見えるか確認する。messages を print すると、user message、そして Claude から返ってきた content を持つフォローアップの assistant message が見えるはずである。

最後のステップとして、最後の user message を1つ追加し、会話履歴全体をもう一度 Claude に送る。そのために、もう1回 add_user_message を、messages のリストと共に呼び出す。フォローアップの質問・リクエストは "write another sentence"(もう一文書いて)とする。それから chat をもう一度、更新された messages のリストと共に呼び出し、その結果を answer に代入し、answer を print する。

これを実行すると、一瞬の間の後、量子コンピューティングについての内容であることが明らかなフォローアップメッセージが返ってくる。したがって、会話履歴全体を正しく維持できたことになる。これで、コースの残り全体で使い続けることになる、再利用可能な3つのヘルパー関数が手に入った。


Lesson: Chat exercise(チャット演習)

ここまでの内容が理解できているかを確認するための短い演習。この演習では、Jupyter notebook 上で動く非常に小さなチャットボットを構築する。

動作の仕組み:

  1. セルを実行するたびに、組み込みの input 関数を使ってユーザーにテキストの入力を促す。
  2. ユーザーが入力したものを取得し、messages のリストに追加する。
  3. messages のリストを取得し、先ほど組み立てた chat 関数を使って API に渡す。これにより Claude からのフィードバック(生成されたテキスト)が得られる。
  4. その生成されたテキストを取得し、messages のリストに追加する。
  5. その生成されたテキストを print する。
  6. ステップ1に戻ってループし、プロセス全体を繰り返す。

講師のデモ(意図した動作の確認)

講師はすでに解答をセルの中に隠して用意しており、意図した動作を見せるためにそのセルを実行する。セルを実行すると、組み込みの input 関数を使ってすぐにユーザーに入力を促す。ここで "what's one plus one"(1足す1は?)のようなことを入力する。メッセージがすぐに print され、chat 関数を使って Claude からの応答を得て、それも print される。そして再び入力を促される。

次に "add to that answer"(その答えに足して)と入力すると、会話の履歴・文脈が維持されていることが確認できる——「前の答え(2)にさらに足すと4になるはず」という応答が返ってくる。これは、割り込みボタンをクリックして Escape を押すことでプロセスを中断するまで、永遠に続く。

組み込みの input 関数についてのヒント

もし組み込みの input 関数に馴染みがなくても問題ない。テンプレートとして使えるコードが提示される。

一般的な構造:

messages = []

while True:
    user_input = input("...")
    # ここで add_user_message → chat → add_assistant_message → print を実装する

解答

残りの実装は、コード中に置かれたコメントに従うだけでよい。

  1. その user_input を取得し、先ほど組み立てた add_user_message 関数を使って messages のリストに追加する: add_user_message を呼び出し、messages のリストと user_input を渡す。
  2. 組み込みの chat 関数を使って Claude を呼び出し、messages のリストを渡す。これにより answer が得られる。
  3. その answer を取得し、add_assistant_message を使って messages のリストに assistant message として追加する。
  4. 生成されたテキストを print する。任意で、小さなダッシュの区切り記号を入れて、これが AI によって生成されたものであることを利用者に明確にできる: print("---") のように、2つの間に answer を print する。
messages = []

while True:
    user_input = input("Enter your message: ")
    add_user_message(messages, user_input)
    answer = chat(messages)
    add_assistant_message(messages, answer)
    print("---")
    print(answer)
    print("---")

これでテストするには、セルを実行する。すると、Claude とチャットを開始した後、セルの下に出力が表示されるはずである。"what's one plus one"(1足す1は?)と Claude に尋ねると、応答が返ってきて、続けて "and two more"(さらに2を)と尋ねると、「1足す1にさらに2を足すと4になるはず」という応答が得られるはずである。

以上が、ループするチャットボットの非常にシンプルな実装である。


Lesson: System prompts(システムプロンプト)

このレッスンでは、Claude が生成する応答のトーンとスタイルをどうカスタマイズできるかを見る。

なぜ重要か: 数学の家庭教師チャットボットの例

なぜこれが重要かを理解するために、何らかの数学の家庭教師チャットボットを作っている場面を想像する。ユーザーはこのチャットボットを使って、数学の問題を解く助けを求める。例えば、ユーザーは 5x + 2 = 3 を解く助けを求めるかもしれない。

数学の家庭教師にやってほしいこと・やってほしくないことがいくつかある。

やってほしいこと:

やってほしくないこと:

システムプロンプティングという技法

この問題を解決するために、system prompting(システムプロンプティング)と呼ばれる技法を使う。System prompts は、Claude が応答するスタイルとトーンをカスタマイズするために使われる。system prompt はプレーンな文字列として定義し、create 関数呼び出しに渡す。

system prompt の1行目では通常、Claude に役割(role)を割り当てる。例えば、Claude に「あなたは忍耐強い数学の家庭教師です」と直接伝える。これにより、Claude は本物の数学の家庭教師が応答するのと同じように応答することが促される——おそらく忍耐強くなり、多くの説明を提供するが、生徒の質問に直接答えることはせず、代わりに解答へと導いていくようになる。

デモ: システムプロンプトあり/なしでの違い

notebook には、初期のクライアント作成とあの3つのヘルパー関数が既に引き継がれている(新しい notebook を作る必要は必ずしもない)。

system prompt なしの場合

まず、Claude にとてもシンプルな質問をする——簡単な数学の問題を解いてもらう。messages のリストを作り、user message を追加し、"solve 5x + 3 = 2 for x"(5x + 3 = 2 を x について解いて)と頼む。chat を呼び出して answer を取得し、print する。

このセルを実行すると、おそらくこの解き方の正確なステップバイステップの解答が表示される。これはおそらく生徒にとって有用ではある(ステップバイステップの解法を示すことになるので)が、目指しているものとは少し違う。生徒に自分で考えてほしいし、自力で解答にたどり着いてほしい。ただ小さなステップを与え、正しい方向に導きたいだけである。

system prompt を追加する

chat 関数の中で、system という新しい変数を作る。これに複数行の文字列を代入する。この中に、あらかじめ書いておいた system prompt を組み立てる: Claude に、忍耐強い数学の家庭教師であることを伝え、生徒の質問に直接答えるべきではなく、代わりに問題の解き方について少しガイダンスを与えるべきだと伝える。

この system prompt を create 関数への system キーワード引数として確実に渡すようにし、セルを再実行する。

すると、これはずっと良い答えになる。生徒に単に解き方を直接教える代わりに、Claude はステップバイステップで生徒に解答へ進むよう促すようになる。Claude はまず生徒に、方程式の片側に x を孤立させることを提案し、それをどうやるかを生徒に尋ねる。これにより、生徒にとってよりインタラクティブな体験が生まれる。これはおそらく、直接答えを与えるよりも、ここで何が起きているかを学ぶ助けになるだろう。

明らかに、system prompt を使うことは、Claude を特定の方向に誘導するための強力なツールである——与えられたユーザー入力にどう応答すべきかを操作できる。

リファクタリング: chat 関数を再利用可能にする

次に、chat 関数に対して少しリファクタリングを行う。system prompt をハードコードするのではなく、chat 関数を呼び出すたびに system prompt を指定できるようにしたい。

これを、今あるコードを切り取ってセルの下に移し、system prompt を渡せるようにする形で行う。これで、将来さまざまな問題に対して使える、ハードコードされた system prompt を持たない、より再利用可能な chat 関数が手に入る。

system 引数を create 関数に渡す必要がある。ただし、これは思ったよりも少し追加の作業が必要になる。まず chat 関数に system という keyword 引数を追加し、デフォルトを None にする。この状態でセルを実行し、下のセルも実行すると、すべて期待通りに問題なく動く。

しかし、chat 関数を呼び出すときに system prompt を全く提供しない、つまり削除して実行すると、エラーメッセージが出る。systemNone を渡すことは許されていない。 そのため、create 関数に渡すパラメータをもう少し動的に組み立てる必要がある。systemNone の場合、このパラメータを一切含めたくない。

小さなリファクタリングでこれを実現する方法:

  1. modelmax_tokensmessages を切り取り、辞書構文に変換する(ダブルクォート、コロンを使う形)——params というディクショナリを作る。
  2. system prompt が渡されたかどうかをチェックする。渡されていれば、それを params ディクショナリの中に system キーとして追加する。
  3. 下の create 呼び出しを **params(ダブルスターでのアンパック)に更新する。
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

これで再実行すると、system keyword 引数なしで chat を呼び出しても問題なく動く。system prompt を提供したい場合も、問題なく動く。これで chat 関数の中に system prompt のサポートが備わったことになる。


Lesson: System prompts exercise(システムプロンプト演習)

system prompts に関する短い演習。notebook を更新して、Claude に「文字列内の重複文字をチェックする Python 関数」を書くよう依頼している。これを実行して answer を print すると、大量のコードが生成されるのがわかる。コードの一部は本当にコードだが、多くの説明とコメントも一緒に生成される。

演習内容: この生成されるコードの量を減らす演習に取り組む。この関数の実装を、できるだけ簡潔(concise)にしたい。そのために、system prompt を書いて chat 関数呼び出しに渡してほしい。system prompt は Claude に役割を割り当て、できるだけ簡潔に応答するよう促すものにする。

解答

chat 関数呼び出しに system prompt を渡す。その中で、Claude に非常に簡潔なコードを書くよう促す役割を割り当てる: "You are a Python engineer who writes very concise code."(あなたは非常に簡潔なコードを書く Python エンジニアです)

セルを実行すると、今度ははるかに狙い通りに近い応答が得られる。この一意性チェックを実装するために実際に書く必要のあるコードは非常に短く、先ほど見たコードよりもずっと短い。


Lesson: Temperature(テンパラチャー)

このコースの前の方で、Claude が実際にどのようにテキストを生成するかについて簡単に話した。おさらいすると、"what do you think"(あなたはどう思いますか)のような何らかのテキストを Claude に投入する。Claude はこのテキストをトークン化する、つまりより小さなチャンクに分解する。それから予測フェーズに入り、次にどんな単語が来得るかを判断し、それぞれの異なる選択肢に確率を割り当てる。最後に、サンプリングフェーズで、これらの確率に基づいてトークンが実際に選ばれる。

画面上の図では、"what do you think"(あなたはどう思いますか)という入力が与えられたとき、可能な次のトークンとして "about"、"wood" などが挙げられる——右側に見えるものすべてである。これらそれぞれに確率が割り当てられる。そしてこの場合、Claude は "about" を最も可能性の高い次のトークンとして選ぶかもしれない。そうすると "what do you think about"(あなたは何について思いますか)というフレーズになる。この全体のプロセスが繰り返されて文全体、メッセージ全体が完成する。

念のため確認すると、ここで示している数字は確率——それぞれのトークンが選択される確率のパーセンテージである。これらの確率をより理解しやすくするため、このレッスンの残りではチャート形式で表示する。同じ確率だが、理解しやすいフォーマットである。左から右に並べ替えているのも気づくだろうが、これは実際の内部的な並べ替えがあるわけではなく、このチャートを理解しやすくするために確率の大きい順に単純に並べているだけである。

Temperature とは何か

Claude がテキストを生成する仕組みを思い出したところで、これらの確率に直接影響を与え、Claude が実際にどのトークンを選ぶかを制御できる方法を1つ示す。temperature と呼ばれるパラメータを使ってこれらの確率を制御できる。

Temperature は、モデル呼び出しを行う際に提供する 0 から 1 の間の小数値である。create 関数(client.messages.create、Anthropic Python SDK)を呼び出すたびに、temperature はまさにこの確率の分布に影響を与える。これは少し理解しづらいので、図やチャートを見るか、あるいは plot を使った簡単なデモが用意されている。

デモ: 同じチャートを見ながら、temperature の値を0に下げていくと、最も高い確率が起こりやすくなっていく。最初の最高確率は "about" だったが、これがどんどん増加して100%になっていく。つまり temperature が0のとき、決定的な出力(deterministic output)が得られ、常に最初の確率が最も高いトークンを選ぶようになる。

その後、temperature を上げ始めると、最初の確率がより低かったトークンを選ぶ可能性が上がっていく。例えば "we" を次のトークンとして選ぶ確率が0%だったものが、9%まで増える、という具合である。

実世界での意味: タスクごとの推奨レンジ

これが temperature の理論だが、実世界ではこれは実際にどういう意味を持つのか。実際のタスクに応じて、異なる temperature の値を使い分けることになる。以下は、それぞれのサンプルレンジに当てはまる例のレンジとタスクである。

コードで temperature を調整する

chat 関数を更新して、create 関数呼び出しに渡す temperature 引数を受け取れるようにする。引数のリストに temperature を追加し、デフォルトを 1.0 にする。より創造的な側に倒しておきたいためである。それからその引数を受け取り、Params オブジェクトに temperature として追加する。これがアプリケーションに temperature 調整のサポートを追加するために必要なことのすべてである。

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

実験デモ: temperature = 0.0 のとき

このセルを再実行し、下のセルへ行き、Claude に「一文の映画のアイデアを生成して」と頼む。最初は temperature を 0.0 で試す。理論上、常に少し似たような性質の映画のアイデアが返ってくるはずである。

実行すると、まず "time-traveling archeologist"(タイムトラベルする考古学者)というアイデアが返ってくる。少なくとも講師にとっては、これは非常によくあるパターンで、temperature が0のとき、時間旅行する何か、というアイデアを非常によく得るという。もう一度実行すると、また時間旅行する何かが返ってくる。そう、同じようなもの。もう一回実行しても、やはり "jaded time-traveling historian"(うんざりした時間旅行する歴史家)のような、似た種類のアイデアが返ってくる。

実験デモ: temperature = 1.0 のとき

今度は temperature を 1.0 に上げてみる。Claude にもっとオリジナルで創造的なアイデアを促すためである。もう一度実行すると、今度はうんざりした時間旅行者のようなアイデアにはならないことを期待するが、ほぼ即座にその通りになることが確認できる。

ここで重要な注意点がある: temperature を上げたからといって、必ず劇的に異なるアイデアが得られるわけではない。異なるものを得る確率が上がるだけである。もう一度実行すると、より創造的なアイデアが出てくるかもしれない。実際、それはより創造的なもの——今回は時間旅行に関するものではない何か——になる。もう一度テストすると、やはり時間旅行者などとは関係ないものになる。

まとめ

一般的なガイダンスとして:創造性をあまり必要としないタスク、あるいは非常に決定的な出力が欲しいタスクを行うときは、低い temperature 値を使う。 一方、少し創造性が必要なタスクがあるときは、temperature を少し上げることを検討する。


Lesson: Response streaming(レスポンス・ストリーミング)

このレッスンでは、このセクションの前半で見た、元のチャットインターフェースの例を思い出す。Web アプリやモバイルアプリの中で動くチャットウィンドウがあり、ユーザーが質問を入力する、というものだった。それがサーバーに送信され、user message に詰め込んで Claude に送る。Claude は assistant message を送り返してくる。そのテキストを抽出して、モバイルアプリや Web アプリへ送り返し、その内容が画面に表示されることを期待する。

解決すべき問題: レイテンシとユーザー体験

これはこの時点では非常にシンプルで簡単に聞こえるが、まだ対処していない小さな問題が1つある。ユーザーメッセージを Claude に送信してから、最終的に assistant message が返ってくるまでの時間は、思っているよりずっと長くかかることがある。 場合によっては、入力メッセージと出力メッセージのサイズによっては、10秒からなんと30秒までかかることもある。

このユーザーが応答を待っている間、画面にスピナーを表示することもできるが、これは決して良いユーザー体験とは言えない。ほとんどのユーザーの期待は、"what is quantum computing"(量子コンピューティングとは何か)のような何らかの最初のメッセージを入力したら、ほぼ即座に画面上に何らかの応答が見え始めることである。

このより良いユーザー体験を得るために、streaming(ストリーミング)と呼ばれる技法を使う。

ストリーミングの仕組み

サーバーはやはり最初のユーザーメッセージを Claude に送るが、その後 Claude はほぼ即座に最初のレスポンスを送り返してくる。この最初のレスポンスには実際にはテキストコンテンツは何も含まれていない。それは単に、Claude が最初のリクエストを受け取ったこと、そして Claude がこれからいくらかのテキストを生成しようとしていることを、サーバーに知らせるサインに過ぎない。

そしてイベントのストリームを受信し始めることになる。これらのイベントが正確に何であるかについては後で詳しく見ていくが、今のところは、これらは生成されたレスポンスの断片を含んでおり、それをサーバーに送り返して最終的にユーザーに表示したいものだ、と理解しておけばよい。受信するイベントの数は、どれだけの量のテキストを生成しているかに依存する。

各イベントには、生成中の全体のメッセージのうちのほんの少しだけが含まれる。例えば最初のイベントはただ "quantum"(量子)というテキストだけを持ち、2番目は "computing"(コンピューティング)と言う、3番目は……という具合である。各イベントは単に1語だけを含むわけではなく、多くの単語、あるいは文全体を含むこともある。これは本当に、Claude がテキストの各小さな断片を生成するのにどれだけの時間がかかるかによる。

サーバーはこれらのイベントを受け取り、任意で各イベントからテキストを取り出して、即座に Web アプリやモバイルアプリなどへ送り返し、その小さなテキストの断片を画面に表示できる。これを、受信する追加のイベントごとに繰り返すことができる。結果として、ユーザーはチャットインターフェースの中でテキストがチャンクごとに現れ始めるのを見ることになる。

コードでの実装: 基本形

notebook にはまだ3つのヘルパー関数が組み込まれている。このレッスンでは chat 関数は使わない——streaming を使い始めると、これまで実装してきた chat 関数とはあまりうまく噛み合わないためである。代わりに、messages のリストを作り、手動で client.messages.create 関数を呼び出す。

空の messages のリストを作り、user message を追加する。Claude に「架空のデータベースの一文の説明を書いて」と頼む。それから client.messages.create を呼び出す。モデル名を渡し、max tokens を渡し、messages のリストを渡す。そして最後に、追加の keyword 引数として stream=True を入れる。

messages = []
add_user_message(messages, "write a one 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)

これにより最終的な答えではなく、代わりに異なるイベントのストリームが返ってくる。これは通常のイテレータなので、for event in stream として反復処理し、event を print できる。

実行すると、画面上にすぐに異なるイベントのストリームが表示され始める。これらのそれぞれは、Claude によって送り返される異なる小さなデータの断片を表している。まず message_start という名前のイベントから始まる。次に content_block_startcontent_block_delta を得る——実際、これを何度か得る。そして最後の方で、content_block_stop イベント、message_delta イベント、message_stop イベントを得る。

これらはすべて、Claude から来る単一のリクエストの文脈の中で送り返されてくるイベントである。これらの異なるイベントはそれぞれ、Claude から得ている全体のレスポンスの文脈の中で何らかの意味を持っている。

最も重要なイベント: content_block_delta

しかし、他のすべてのイベントよりも通常もう少し気にかけるべきイベントタイプが1つある。それが content_block_delta イベントである。このイベントには、Claude によって生成され、チャンクごとに送り返されてくる実際のテキストが含まれている。

実務上、ほぼ毎回同じシーケンスのイベントを得ることになる。Claude からレスポンスを得るとき、ほぼ必ず message_start を得て、次に content_block_start、そして content_block_delta のシーケンスを得ることになる。これらが実際のテキストを含んでいるものである。したがって、通常はこれらの異なるイベントをすべて集めてテキストを抽出し、Web アプリやモバイルアプリなどへ送り返したい、ということになる。

notebook のこの for ループの中で、どんな種類のイベントを扱っているかを調べるチェックを追加し、それが content_block_delta イベントの1つであれば、その中身にアクセスして実際に欲しいテキストを取得することができる。しかし、それには自分でかなり多くの追加コードが必要になる。

client.messages.stream(): よりシンプルな代替手段

ありがたいことに、Anthropic SDK には、ここで示したものとは異なる、ストリームを作成する別の方法が用意されている。この別の方法を使うと、レスポンスからテキストだけを取り出すのがずっと簡単になる。そして繰り返しになるが、通常本当に気にかけているのはテキストの部分である。

次のコードセルに進み、再び messages のリストを作り、user message を "write a one sentence description of a fake database" と共に追加する。それから、少し異なる関数を呼び出し、with ブロックの中にラップする。

messages = []
add_user_message(messages, "write a one 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="")

with client.messages.stream(...) と書き、中にモデル、max tokens、messages を入れる。しかし今回は stream=True 引数を追加する必要はない。それから as stream: と書き、すべてをインデントし、中で for text in stream.text_stream: と書く。

これで text は、それらの異なるイベントのうちのテキスト部分だけになる。つまり、本当に気にかけているテキストだけになる。これも繰り返しになるが、Claude からレスポンスをストリーミングするときにほぼ毎回本当に気にかけているものである。

実際にどう動くかを見せるために、print 文を追加してその text をログ出力する。end=""(空文字列)を追加する。これにより、これらの print 文がその末尾に改行文字を追加しないようにできる。これで、各テキストの断片が互いに隣り合ってログ出力されるのが見える。

これを実行すると、非常に速く発生するが、レスポンスがチャンクごとにストリームバックされてくるのが見える。もう一度実行すると、チャンク、チャンク、チャンクと見える。各チャンクには複数の異なる単語が含まれていることに気づくだろう。つまり、各イベントの中で必ず1つの単語だけが返ってくるとは限らず、いくつか得られることもある。

get_final_message(): ストリーミング後に完全なメッセージを得る

ここでもう1つ見せたい最後の機能がある。これまで述べてきたように、モバイルアプリや Web アプリにレスポンスをストリーミングして、ユーザーがテキストの各チャンクをできるだけ早く画面上に見られるようにすることを非常によく望む。しかし、ストリームを完了した後によく行いたいもう1つのことは、メッセージ全体を取得して、それをデータベースに保存することである——特定のユーザーとの会話全体の記録を持つためである。

これらの異なるイベントをすべて集めて、1つの最終的なメッセージにまとめる方法を示す。ここの print 文を pass に置き換えて、何も print しないようにする。そしてその後に stream.get_final_message() を実行する。

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

    final_message = stream.get_final_message()

これをもう一度実行すると、依然としてレスポンスをストリームバックしており、望めば print することもできるが、同時に受け取った個々のイベントすべてを1つの最終的なメッセージにまとめる。これをデータベースに保存したり、他に必要な何かをしたりできる。


Lesson: Structured data(構造化データの生成)

stop sequences(停止シーケンス)と assistant message prefilling(アシスタントメッセージのプリフィル)は、非常に強力な形で組み合わせられる。 これは、何らかの構造化データを生成する必要があるときにかなり頻繁に行うことになる技法である。この仕組みを理解してもらうために、非常に手早い例をウォークスルーする。

課題設定: EventBridge ルール生成 Web アプリ

画面に見えるような Web アプリを構築しているとする。これはユーザーの入力に基づいて EventBridge ルールを生成する Web アプリである。馴染みがなければ補足すると、EventBridge ルールは AWS で使われるもので、本質的には小さな JSON スニペットである。

ユーザーはこのようなプロンプトを入力し、"generate" をクリックする。ユーザーはおそらく、すぐに選択できる、あるいはこの小さなコピーボタンをクリックしてどこか他の場所で使える、生成されたルールがすぐ上に表示されることを期待するだろう。

ここでの重要なポイントは、JSON だけを表示し、それ以外は何も表示したくないということである。もし代わりに、こういう見た目のレスポンス——ルールは生成されているが、上にヘッダー、下に解説のフッターが付いているようなもの——を表示したら、ユーザーにとって決して有用ではない。ユーザーはもう「全部コピー」ボタンを使えず、その JSON を手動で選択しなければならなくなる。

これは、Claude にそんなに親切に振る舞ってほしくないという例である。説明はいらない。ある特定のデータだけが欲しく、それ以外は何もいらない。

このことは JSON の生成に限った問題ではないことを明確にしておく。実際には、Claude を使ってどんな種類の構造化データを生成するときでも、同じことが起こる。JSON かもしれないし、Python かもしれないし、あるいは単なる箇条書きのテキストアイテムのリストかもしれない。Claude は非常に頻繁に、ヘッダーやフッター、あるいは何らかの追加的な解説を挿入しようとする。そして、こうした多くのシナリオでは、その追加の解説は欲しくない。生の中身だけが欲しい——Claude に作ってほしいと頼んだ、まさにその通りのものだけである。

解法: stop sequence + プリフィルされた assistant message

Claude を軌道に乗せ続け、追加のヘッダーやフッター、解説などなしに、求めている生のコンテンツだけを与えてもらうために、stop sequence をプリフィルされた assistant message と組み合わせて使うことができる。

notebook に戻り、新しいセルを作る。再び messages のリストを作り、user message を追加する。"generate a very short event bridge rule as JSON"(非常に短い EventBridge ルールを JSON として生成して)のようなことを言う。それを渡して、まずはこの最初の状態で何が得られるかを見てみる。

すぐに、JSON が返ってくることは確認できるが、残念ながらそれには小さな ```json が先頭に付き、その後ろに対応する閉じの ``` が付いている。念のため明確にしておくと、これらのバックティックは、これをすべて markdown としてフォーマットするために存在している。markdown として render すればとても綺麗にフォーマットされる。しかし今回のケースでは、こうした追加の文字は一切欲しくない。生の JSON だけが単独で欲しい。

そのために、2つのことを行う。assistant message と stop sequence の両方を使う。まずコードを書き、その後にすべての仕組みを説明する図を示す。

まず、assistant message をプリフィルするadd_assistant_message を呼び出し、プリフィルするメッセージは バックティック3つ + json```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=["```"])
print(text)

これを実行すると、今度は JSON だけが単独で返ってくる。ここにいくつか改行文字があることに気づくが、それは全く問題ない。こうした余分な改行は、レスポンスを JSON としてパースするか、あるいは strip を呼び出すことで、非常に簡単に取り除ける。次のようにできる:

import json

text = chat(messages, stop_sequences=["```"])
print(text)
data = json.loads(text.strip())

これを実行すると、確かにきちんとフォーマットされた JSON が返ってきて、期待する任意の方法でアクセスできる。

仕組みの解説(図での説明)

assistant message と stop sequence によって実際に何が起きているのか、図を見ながら説明する。

もう一度、user message を出し、プリフィルされた assistant と stop sequence を提供している状態を考える。Claude はこのリクエストのさまざまな部分をすべて見る。まず user message のコンテンツを見て、「なるほど、ルールを完全に書く必要があるのは明らかだ。おそらくそれを説明もすべきだろう」と判断する。つまりヘッダーとフッターを付けるかもしれない——それが Claude が自然にやりたがることだからである。Claude は自分がやっている作業を説明したいのである。

しかし次に、その assistant message に出会う。そして前のレッスンで学んだ通り、Claude はそれをすでに自分がレスポンスの中で書いたものだと想定する。つまり Claude は「ああ、私はもう JSON の部分を書き始めている」と考える。だから残りにやるべきことは、実際の JSON を書き出すことだけになる。そうして、レスポンスの中にこの JSON をすべて書き出していく。

そして最後に到達すると、先ほど作り始めたと思っている markdown コードブロックを自然に閉じたくなる。だから Claude は閉じのバックティック3つ(```)を入れたくなる。しかし、そうしようとした瞬間に、stop sequence に出会い、生成が完全に停止し、即座にレスポンスが返送されてくる。

これはつまり、「これで始めて、それで終わって、間にあるものすべてをちょうだい」と言っているようなものだとイメージできる。その結果、本当に気にかけている部分だけ——JSON だけ——が単独で返ってくることになる。

これは本当に強力な技法であり、これから非常によく使うことになる。何らかの構造化データを生成したいとき、そしてそれ以外の何も付けずに、そのデータだけを得たいときはいつでも使える。そして念のため確認しておくと、この技法は JSON だけに限定されるものではない。非常に特定のコンテンツを生成したくて、それに追加の解説を一切付けずにコンテンツだけを得たいときはいつでも、assistant message prefilling と stop sequences の組み合わせを検討することになる。

2026年時点の注意: assistant message prefilling(最後に assistant ターンを事前入力する手法)は、Claude Opus 4.7 以降・Claude Sonnet 4.6 以降(Sonnet 5、Opus 4.8 などを含む最新世代)では API がサポートしなくなっており、指定すると400エラーになる。stop sequences 自体は現行モデルでも引き続き有効だが、prefilling と組み合わせるこの技法は旧世代モデル限定になっている。現行モデルで同等の結果を得るには、構造化出力(strict tool use や出力フォーマット指定)やシステムプロンプトでの指示への置き換えが公式に推奨されている。


Lesson: Structured data exercise(構造化データ演習)

stop sequences と message prefilling のアイデアが本当にクリアになっているかを確認するための、非常に手早い演習。

演習内容: 画面に表示されているのと全く同じコードを書き出す。このコードはすべて見覚えのあるものであるはずである。プロンプトを見ると、"generate three different sample AWS CLI commands"(3つの異なるサンプル AWS CLI コマンドを生成して)と書かれている。このコードを実行すると、おそらく次のような見た目の出力が返ってくる——少し読みやすくするために markdown として render されている。これがサンプルの最初の出力である。3つの異なるサンプルコマンドが返ってきているのはわかるが、その周りに多くの解説が付いている。ヘッダーがあり、それから個々のコマンドを列挙する番号がいくつか付いている。

要求される変更: このコードを取り、message prefilling と stop sequences だけを使って、3つの異なるコマンドすべてを、追加のコメントや説明などなしに、単一のレスポンスの中で並べて取得できるようにしてほしい。そして、これを message prefilling と stop sequences だけを使って行ってほしい——つまり、このプロンプト自体は一切調整しない。

ヒント: message prefilling は、バックティックのような文字を指定するためだけに使えるわけではない。どんな種類のプリフィルレスポンスでも入れることができる。

解答へのアプローチ

まず最初に、プリフィルや stop sequences など何もない状態での出力を見てみることをおすすめする。ここにあるものを見ると、3つのコマンドそれぞれがバックティック3つのシリーズで囲まれていることに気づく。だから、良い出発点はおそらく、バックティック3つのプリフィルされたメッセージを入れて、Claude に「最初のコメントは飛ばして、すぐにコマンドを書くところへ行ってください」と伝えることだろう。それから、バックティック3つの stop sequence も入れることを決めるかもしれない。これでどこまでいけるかを見てみる。

assistant message を入れる。すべてをバックティック3つで始める。そして閉じのバックティック3つの stop sequence を入れる。

messages = []
add_user_message(messages, "generate three different sample AWS CLI commands")
add_assistant_message(messages, "```")

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

これを実行してどこまでいけるかを見てみる。最初の出力はそこそこ合理的に見えるが、まだ完璧ではない。確かに3つのコマンドが得られる。1、2、3とある。しかし、先頭に bash という単語も追加されていることに気づくだろう。

なぜ bash という単語が付くのか: 最初のプリフィルされた assistant message として、あのバックティック3つを提供した。バックティック3つを置くと、それは markdown を書いていることを示すものになる。そして markdown のコードブロックをバックティックで書くとき、任意で言語識別子を入れることができる。もしそれを入れることを選ぶと、これを markdown として render するときには、そのバックティックの中身がその言語の構文ハイライトを使ってレンダリングされる。この場合、Claude は「このコンテンツをレンダリングするときは bash スタイルの構文ハイライトを使うべきだ」と伝えるために bash を入れることに決めた。今回、これは全く欲しくない。

改善1: プリフィルに bash を含める

この対処法の1つは、プリフィルされたメッセージを調整して、bash 自体を自分で含めることである。これにより、Claude 自身にとって、「あなたは今 markdown コードブロックの中にいて、このコードブロックの中では bash 形式のコマンドを書くべきだ」ということが完全に明確になる。

add_assistant_message(messages, "```bash")

これをもう一度実行してどうなるか見てみる。今度はより良く見える。

残る2つの問題

この時点から、おそらく対処したい追加のエラーが2つある:

  1. 時々、単一のコマンドだけが返ってくることがある。これは、Claude が3つの別々の markdown コードブロックを書きたがっているかもしれないことを示すサインである。
  2. bash コードブロックを使うようになったため、Claude が bash 形式のコメントを挿入しようとすることがある。例えば「このコマンドは XYZ をする」のようなもので、それが繰り返し表示されるかもしれない。これは演習の要件の1つだったため、こうしたコメントは絶対に欲しくない。

改善2: プリフィルでさらにガイドする(先ほどのヒントを使う)

こうしたコメントを取り除き、3つのコマンドをより確実に取得するために、先ほど与えたヒントを使うことができる。ヒントは、message prefilling はバックティックのような文字を指定することだけに限られるわけではない、というものだった。message prefill を使って、Claude がどう答えるかを劇的に誘導することもできる。

この場合、次のようなものを追加できる: "Here are all three commands in a single block without any comments"(コメントなしの単一ブロックに3つのコマンドすべてを示します)。それからコロンを入れ、改行を入れる。こうすることで、すべての markdown 部分が次の行から始まるようにする。

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

(コロンの後に改行を入れて markdown 部分を次の行から始める、という説明に沿った形。実際のプリフィル文字列は「```bash」に続けて、コメントなしで3つのコマンドを1ブロックにまとめる旨のガイド文を足す形になる。)

これを実行すると、今度はずっと信頼性の高い出力が得られるはずである。実際、これは良さそうである。そして当然、一日中実行し続けることもできて、おそらくほぼ毎回望んでいる結果——3つのコマンドが、コメントなしで、1つのブロックの中に並んでいる状態——が得られるはずである。


章末まとめ(要点整理)


情報源は動画の公式英語字幕(トランスクリプト)。具体例・数値・デモ手順・コード例・比喩はできる限り漏らさず反映している。技術用語(eval, grading, XML tags, prompt 等)は英語表記のまま用いる。


Claude 学習サイト — 完全ローカル静的サイト(build.py で生成)。進捗はこのブラウザの localStorage に保存されます。