Building with the Claude API 第1章 — API基礎
Lesson: Welcome to the course(コースへようこそ)
講師は Stephen Grider(Anthropic の Technical Staff メンバー)。このレッスンではコース全体の構成と受講にあたっての前提・コツが説明される。
カバーする内容の流れ:
- Anthropic が提供するモデルの背景知識から開始
- 公式 Anthropic API を通じたモデルへのアクセス方法の理解
- プロンプティング — まず evaluation(評価)、次に engineering(エンジニアリング)のテクニック
- tool use(ツール使用)による Claude の能力拡張のディープダイブ
- RAG(Retrieval Augmented Generation)でタスク固有のコンテキストを Claude に与える方法
- MCP(Model Context Protocol)で Claude を新しいサービスに接続する方法
- Anthropic が作った2つのエージェント「Claude Code」と「Computer Use」を実際に使ってみる
- 最後に、workflows(ワークフロー)と agents(エージェント)でより複雑なタスクを扱う方法
受講の前提条件:
- Python の基礎知識があること
- notebook を実行できる Python 環境があること(コースのコードの大部分は notebook に書いて実行する)
- Anthropic API へのアクセス(Anthropic アカウントまたは API key が必要。API key の取得手順は後のレクチャーで説明される)
成功のためのコツ(講師からの4つの助言):
- コードは一緒に書くこと。 他人が書くコードを見ているだけでは十分に吸収できない。
- 動画の再生速度を上げること。 速いペースでコンテンツをこなせるようになり、特に難しい動画を見直すときにも役立つ。
- 学んだテクニックを実際に適用すること。 作成した notebook を拡張したり改造したりしてみる。
- 行き詰まったら Claude に助けを求めること。 Claude はほとんどどんな状況からも抜け出す手助けをしてくれるし、混乱しているポイントを明確にしてくれる。
Lesson: Overview of Claude models(Claudeモデルの概観)
このレッスンでは Claude の3つのモデルファミリーを検証し、それぞれの用途に最適なモデルを見極める。各モデルの主要な特徴を説明した上で、正しいモデルを選ぶためのシンプルなフレームワークを示す。
大前提: 3つのモデルはすべて Claude のコア能力(テキスト生成、コーディング、画像解析など多数のタスク)を共有している。本当の違いは「どう最適化されているか」にある。1つは知性(intelligence)に焦点、1つは速度とコスト効率、1つは知性と速度のバランスを重視している。
Opus — 最も知的なモデル
Opus は Claude の中で最も capable(能力が高い)なモデル。「capable」というのは、Claude から得られる最高水準の知性を提供するという意味である。実務的には、Opus は複雑な要件があり、それを完遂するために高いレベルの知性と計画性が必要なシナリオ向けに設計されている。
- 複雑なプロジェクトに長時間、独立して取り組める。数時間続くようなタスクで、モデルが多段階のプロセスを管理し、人間の介入をほとんど受けずに多くの異なる要件をナビゲートする必要がある場合に向く。
- Opus は「reasoning(推論)」と呼ばれる機能をサポートする(この機能は公式には Extended Thinking と呼ばれ、APIでは
thinkingパラメータとして提供される。詳細は後述の Extended thinking のレッスンで扱う)。つまり、単純なタスクには素早く応答でき、複雑なタスクにはじっくり時間をかけて考えることができる。 - トレードオフ: 中程度のレイテンシと高いコスト。高い知性を得られる一方、リクエストごとに少し多くの時間とコストがかかる。
Sonnet — バランスの取れたモデル
Sonnet は Claude のラインナップの中で一種の「スイートスポット」に位置する。知性・速度・コストの良いバランスを持ち、大多数の実務ユースケースに非常に有用である。
- Sonnet を優れたものにしているのは、強力なコーディング能力と、速いテキスト生成である。
- 多くの開発者が評価しているのは、複雑なコードベースに精密な編集を加えられる能力——つまり、既存の機能を壊さずにプロジェクトに変更を加えられる点である。
Haiku — 最速のモデル
Haiku は Claude の中で最も速いモデルで、特にレスポンスタイムが重要なアプリケーション向けに作られている。
- 重要な注意点: Haiku は Opus や Sonnet が持つ reasoning 機能をサポートしていない。その代わり、Haiku は速度とコスト効率に最適化されている。
- この特性により、Haiku はリアルタイムのインタラクションを必要とするユーザー向けアプリにとって非常に良い選択肢になる。
モデル選択のフレームワーク
3モデルの位置づけは「高い知性」対「コストと速度」というトレードオフの軸で理解できる。
- Opus: 知性側に位置する。非常に知的、より高価、レイテンシも高い。
- Haiku: コストと速度側に位置する。中程度の知性、低コスト、最高速度。
- Sonnet: ちょうど真ん中に位置し、これらの異なる特性の良いバランスを取っている。
モデルを選ぶ決め手は、自分のユースケースにとって何が最も重要かを特定することである。
- 知性が最優先(複雑なタスクで強力な推論が必要)→ Opus。品質を、速度とコストより優先している状態。
- 速度が最優先(リアルタイムのユーザーインタラクション、あるいは高ボリューム処理で応答をできるだけ速く返す必要がある)→ Haiku。
- 知性・速度・コストのバランス(多くのアプリケーションで当てはまるケース)→ Sonnet が最善の選択肢になることが多い。
重要な補足: 多くのチームは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 リクエストを行うこともできる。
このリクエストを行う際、いくつかのデータを一緒に渡す必要がある:
- API key
- 実行したいモデルの名前
- messages のリスト(ユーザーが送信したテキストを含む)
- max tokens 値(Claude が生成するテキストの長さを制限する)
ステップ3: Anthropic API(テキスト生成の内部プロセス)
ここが実際にテキストが生成される場所。このプロセスは複雑なので、簡略化した高レベルの概要が示される。テキスト生成プロセスを4つの別々のステージに分解する。
-
トークン化 (tokenization): ユーザーの入力がより小さな文字列に分解される。この各テキストの断片は token(トークン) と呼ばれる。トークンは単語全体のこともあれば、単語の一部、あるいはスペースや記号のこともある。話をわかりやすくするため、ここでは各単語が1つのトークンを形成すると仮定する。
-
embedding(埋め込み)への変換: 各トークンは embedding に変換される。embedding とは長い数値のリストであり、これらのリストは「与えられた単語の数値ベースの定義」のようなものだと考えることができる。ここで書き言葉の興味深い側面として、単一の単語が多くの可能な意味を持ち得るという点が挙げられる。ある単語の文中での位置と、その周囲に存在する他の単語だけが、その定義を1つの特定の意味に絞り込む。例えば「quantum(量子)」という単語は多くの異なる定義を持ち、この単語を見ただけでは周囲の他の単語を見るまでその意味はわからない。同様に、各 embedding は各単語のあらゆる可能な意味を含んでいるものと考えることができる。
-
contextualization(文脈化): 各 embedding を単一の正確な定義に絞り込むために、contextualization というプロセスが使われる。contextualization では、各 embedding がその周囲にある他の embedding に基づいて調整される。このプロセスは、各 embedding の意味のうち、その隣接する embedding に照らして最も意味の通る部分を際立たせる助けになる。
-
generation(生成): これが実際にテキストが書かれる最終ステップ。この時点までに、各 embedding はその隣接するものから膨大な量の情報を吸収している。最終的に処理された embedding は output layer(出力層)に渡され、そこで可能な次の単語それぞれについて確率が生成される。ここでモデルは自動的に最高確率のものを選ぶわけではない。代わりに、確率とランダム性を組み合わせて単語を選択し、これがより自然でバラエティに富んだ応答を生み出す助けになる。選ばれた単語は embedding のリストの末尾に追加され、プロセス全体が再び繰り返される。
停止条件の判定: 出力トークンを1つ生成するたびに、モデルは一時停止していくつかの問いを自らに投げかけ、生成が完了したかどうかを決める。
- まず、これまでに生成したトークン数を数え、それが入力リクエストとともに渡された max tokens パラメータより大きいかどうかを確認する。この max tokens パラメータは、モデルが生成するトークンの総数を制限する。
- また、モデルが生成し得る特別な end of sequence token も存在する。これは通常の単語ではない。モデルが「自然な生成の終わり」に達したと判断し、生成を止めるべきだということを示す特別なシグナルである。
ステップ4・5: 応答の送信と表示
生成が完了すると、API はレスポンスをサーバーに送り返す。レスポンスには、生成されたテキストが入った message、そして usage と stop reason が含まれる。
- usage: モデルに入力したトークンの数と、生成されたトークンの数のカウント。
- stop reason: モデルがなぜテキストの生成を止めたかを正確に教えてくれる——自然な end of sequence token に到達したのか、あるいは割り当てられたトークン数を超過したのか、など。
サーバーがこのレスポンスを受け取ったら、生成されたテキストを 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
これはマジックコマンドで、%、pip、install、anthropic、python-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-5 や claude-opus-4-8 など最新のモデルIDに置き換える必要がある。
ステップ4: リクエストを実行する
先ほど作成したクライアントを使って実際にリクエストを行う。コードを書く前に、いくつかの用語を説明しておく。
- Claude には Anthropic SDK 内の
create関数を使ってアクセスする。この関数は3つの異なる keyword 引数(model、max tokens、messages)を必要とする。 - model: 実行したいモデルの名前。前のセルであらかじめ定義した変数を使う。
- max_tokens: Claude が生成できるトークン数の最大予算を設定する。例えば max tokens に 1000 を渡すと、Claude がそれより長いものを生成しようとした場合、生成は自動的に止められ、生成された最初の1000トークンが返される。ここで注意すべき点として、Claude は max_tokens の数値を狙って書こうとするわけではない。つまり Claude は1000トークンちょうどの応答を書こうとするのではなく、適切だと思う応答を書く。したがって max_tokens は「テキストを生成しすぎないようにするための安全機構」として捉えるべきである。
- messages: これはこの先の動画で大きな焦点になっていく部分なので、特に注目してほしい。messages とは何かを理解するために、先ほど議論したチャットアプリケーションを思い出してほしい。ユーザーが何か質問を Claude に入力し、答えを期待する、というやり取りである。create 関数に渡す messages は、まさにこうしたやり取りを表現するためのものである。
メッセージには2種類ある:
- user message(ユーザーメッセージ): Claude に投入したいテキストを含む。user message の中身のテキストは、ユーザーか、あるいは開発者自身が書いたテキストである。つまり user message には、何らかの人間が書いたテキストが含まれる。
- assistant message(アシスタントメッセージ): モデルによって生成され、返送されてきたテキストを含む。
実際にリクエストを実行する
notebook の最後のセルで、message という変数を宣言し、client.messages.create からの戻り値を代入する。渡す引数は次の通り:
model=modelmax_tokens=1000(十分に安全な上限)messages=[...](入力メッセージのリスト)
このリストには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つのことをする必要がある:
- コードの内部で、交換しているすべてのメッセージのリストを手動で維持する。
- フォローアップのリクエストを行うたびに、そのメッセージのリスト全体を毎回提供する。
実証: Claude はメッセージを保存しないことの確認
講師はまず、Claude が実際にメッセージを保存していないことを証明するサンプルコードを書く。最初に「量子コンピューティングとは何か」と尋ね、次に「もう一文書いて」と尋ねる、という会話をシミュレートする。この2番目のリクエストでは、量子コンピューティングとは全く関係のないテキストが返ってくることが示される。
なぜこの結果になるのかを図で説明する:
- 最初に Claude へ似たようなリクエストを送る。user message が1つあり、量子コンピューティングを一文で定義するよう Claude に頼んでいる。すると期待通りの応答——量子コンピューティングの一文定義が返ってくる。
- 次に、ボディの中に message が1つだけの2番目のリクエストを送る。その唯一のメッセージは「もう一文書いて」と頼むものである。これを送信すると、Claude は過去の会話や以前交換したメッセージについて一切記憶を持っていない。したがって Claude は自分にできる最善を尽くしてこのリクエストを満たそうとする——何らかの一文は書くが、それはおそらく量子コンピューティングについてのものではない。
解決策: メッセージ履歴を自前で維持する
この問題を解決する方法を図で示す:
- まず、user message が1つだけの最初のリクエストを、もう一度行う。
- 次に、返ってきた assistant message を取得し、それをメッセージのリストに追加(append)する。
- この会話をフォローアップしたい・続けたい場合、リストの末尾に user message を追加(append)する。
- これにより会話は「本当の会話」として読めるようになる: 量子コンピューティングを定義してくれと頼み、応答を得て、そして別の質問——「もう一文書いて」——を追加している。
- このメッセージのリストを Claude に送ると、Claude は会話全体の文脈と履歴を持つことになる。これまで交換したすべての以前のメッセージを見た上で、より合理的な応答——前の回答をもう少し拡張する一文のフォローアップ——を返してくれることが期待できる。
コードでの実装: 3つのヘルパー関数
この全体のフローをコードで実装するため、会話の履歴・文脈を維持する助けになる3つのヘルパー関数を作る。これらのヘルパー関数はコースの残り全体を通してかなり頻繁に使うことになる。
1つ目: add_user_message
messages のリストと text を受け取る。user_message という変数を作り、role が "user"、content が渡された text を持つ。この新しい user_message を messages のリストに追加(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 上で動く非常に小さなチャットボットを構築する。
動作の仕組み:
- セルを実行するたびに、組み込みの
input関数を使ってユーザーにテキストの入力を促す。 - ユーザーが入力したものを取得し、messages のリストに追加する。
- messages のリストを取得し、先ほど組み立てた
chat関数を使って API に渡す。これにより Claude からのフィードバック(生成されたテキスト)が得られる。 - その生成されたテキストを取得し、messages のリストに追加する。
- その生成されたテキストを print する。
- ステップ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 を実装する
- 開始時点で完全に空の messages のリストを初期化する。
while Trueループを作り、これは永遠に動作する。- while ループの内部で、組み込みの
input関数を使ってユーザーにテキストの入力を促す。ユーザーが表示された入力欄に入力すると、user_input変数に代入される。
解答
残りの実装は、コード中に置かれたコメントに従うだけでよい。
- その
user_inputを取得し、先ほど組み立てたadd_user_message関数を使って messages のリストに追加する:add_user_messageを呼び出し、messages のリストとuser_inputを渡す。 - 組み込みの
chat関数を使って Claude を呼び出し、messages のリストを渡す。これによりanswerが得られる。 - その answer を取得し、
add_assistant_messageを使って messages のリストに assistant message として追加する。 - 生成されたテキストを 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 を解く助けを求めるかもしれない。
数学の家庭教師にやってほしいこと・やってほしくないことがいくつかある。
やってほしいこと:
- 最初は生徒にヒントだけを与える。問題への最初のアプローチの仕方について、ちょっとしたコツを1つか2つ与えるだけ。
- それでも生徒がまだ解き方をよく理解できていない場合に限り、生徒を段階的に解答へと導く。
- 生徒にインスピレーションを与えるために、類似問題の解答例を示す。
やってほしくないこと:
- Claude がすぐに完全な答えで応答すること。
- Claude が生徒に「電卓を使え」のようなことを言うこと。
システムプロンプティングという技法
この問題を解決するために、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 を全く提供しない、つまり削除して実行すると、エラーメッセージが出る。system に None を渡すことは許されていない。 そのため、create 関数に渡すパラメータをもう少し動的に組み立てる必要がある。system が None の場合、このパラメータを一切含めたくない。
小さなリファクタリングでこれを実現する方法:
model、max_tokens、messagesを切り取り、辞書構文に変換する(ダブルクォート、コロンを使う形)——paramsというディクショナリを作る。- system prompt が渡されたかどうかをチェックする。渡されていれば、それを
paramsディクショナリの中にsystemキーとして追加する。 - 下の
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: 例えば データ抽出のようなタスクでは、ランダム性や創造性は本当に必要ない。Claude に大きなテキストの塊を与えて、そこから非常に特定の情報を抽出するよう頼む場合、そこには創造性は一切必要ない。ただ Claude が提供した正確なテキストを見て、最も関連性の高い情報を引き出すだけでよい。
- 高い 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_start、content_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つある:
- 時々、単一のコマンドだけが返ってくることがある。これは、Claude が3つの別々の markdown コードブロックを書きたがっているかもしれないことを示すサインである。
- 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つのブロックの中に並んでいる状態——が得られるはずである。
章末まとめ(要点整理)
- モデル選択はタスク単位で行う。 Opus=最高の知性(複雑な推論・計画、コスト/レイテンシ高)、Sonnet=知性・速度・コストのバランス(コーディングに強い)、Haiku=最速・最安(reasoning機能なし)。1アプリで複数モデルを使い分けるのが一般的。
- API へのリクエストの流れは5ステップ: クライアント→自分のサーバー→Anthropic API(tokenization→embedding→contextualization→generation の4段階で生成)→レスポンス(message + usage + stop_reason)→クライアント表示。APIキーは絶対にクライアント側に含めない。
create関数には model・max_tokens・messages が必須。max_tokensは狙う長さではなく安全装置。message.content[0].textで生成テキストにアクセスする。- Anthropic API はステートレス。 Claude 側は会話をセッションとして記憶しない(内部的には安全性審査目的で入出力を最大30日ほど一時保持するが、会話の文脈維持には使われない)。マルチターン会話を実現するには、自分でメッセージ履歴のリストを保持し、フォローアップのたびに全履歴を毎回送信する必要がある。
add_user_message/add_assistant_message/chatの3ヘルパー関数がこのコース全体で使われる基本パターン。 - system prompt は「どう答えるか」を制御する。 役割(role)を1行目で割り当て、振る舞い方を指示する。
Noneの場合はcreateにsystemキー自体を含めない実装上の工夫が必要。 - temperature(0〜1)は次トークン選択の確率分布を操作する。 0は決定的(最高確率のトークンを常に選択)。高いほど低確率トークンが選ばれやすくなり、創造性が増す。データ抽出など正確性重視のタスクは低温度、ブレスト・文章・ジョークなど創造性重視のタスクは高温度。ただし高温度=必ず違う出力ではなく、あくまで確率が上がるだけ。
- streaming は UX 改善のための技法。 レイテンシ(10〜30秒かかることもある)の間、テキストをチャンク単位でリアルタイム表示できる。最重要イベントは
content_block_delta(実際のテキストを含む)。client.messages.stream()のtext_streamで簡単にテキストだけ取り出せ、get_final_message()で全チャンクを1つの完全なメッセージに組み立てられる(DB保存等に利用)。 - 構造化データ生成には prefilling + stop sequences の組み合わせを使う(旧世代モデルのみ)。 assistant message をプリフィル(例:
```json)してClaudeに「もう書き始めた」と思わせ、stop sequence(例:```)で閉じデリミタが出た瞬間に生成を止める。これにより余計なヘッダー・フッター・解説なしの生データだけを取得できる。この技法は JSON に限らずあらゆる構造化データ(コード、リスト等)に応用できるが、現行の最新世代モデルではサポートされなくなっており、現行モデルでは structured outputs が標準解。
情報源は動画の公式英語字幕(トランスクリプト)。具体例・数値・デモ手順・コード例・比喩はできる限り漏らさず反映している。技術用語(eval, grading, XML tags, prompt 等)は英語表記のまま用いる。