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

Building with the Claude API 第2章 — プロンプト評価とプロンプトエンジニアリング

Lesson: Prompt evaluation(プロンプト評価とは)

これまで Claude へのアクセス方法を学んできたが、ここからは焦点を変えて2つの新しいトピック、prompt engineering(プロンプトエンジニアリング)と prompt evaluation(プロンプト評価)を扱う。この2つはどちらも「Claude から最良の出力を得られるプロンプトを書くこと」を目的としている。

この節ではまず prompt evaluation に焦点を当てて、プロンプトの効果を測定する方法を理解したあとに、prompt engineering のテクニックを見ていく。

プロンプト評価がプロンプト作成プロセスのどこに位置するか: プロンプトを最初に書いたとき、一般的には3つの道が考えられる。

  1. 選択肢1: 書いたプロンプトを1〜2回テストして、「もう十分良い」と判断してそのまま本番投入する。
  2. 選択肢2: 自分で用意したカスタム入力で何回かテストし、気づいたコーナーケースを1つ2つ手直しする。
  3. 選択肢3: プロンプトを evaluation pipeline に通し、プロンプトのパフォーマンスを示す客観的なスコアを得る。それをもとにプロンプトを少しずつ改善していく。

講師が強調するのは、選択肢1と選択肢2は「エンジニアなら誰もが(講師自身も含めて)陥ってしまう罠」だということ。本番の重要なアプリケーションで使われることになるプロンプトを書き始めても、それがちゃんと期待通り動くかを十分にテストしないまま進んでしまいがちである。そのため、プロンプトを書くときは常に選択肢3、つまり evaluation pipeline を通して客観的なスコアを得るやり方を強く推奨する。そのスコアをもとにプロンプトを反復改善し、できる限り良いパフォーマンスになるようにしていく。


Lesson: A typical eval workflow(典型的なevalワークフロー)

このレッスンでは、典型的な prompt evaluation ワークフローが実装するステップ全体を順を追って見ていく。

まず2点、前提として理解しておくべきことがある。

  1. ワークフローの組み立て方は複数ある。 業界全体で標準化された唯一の確立された手法があるわけではない。
  2. オープンソースのパッケージや有料のオンラインサービスも数多く存在する。 このコースおよびこのモジュールでは、Jupyter notebook の中でゼロから自前のカスタムワークフローを実装していく。理由は、これらのワークフローがどう振る舞うかを理解する助けになるだけでなく、prompt eval を行うのに重厚なソリューションを導入する必要はない、ということを理解してもらうためでもある。小さく始めて全体の動きを把握し、そこからスケールアップしていくことができる。

典型的な prompt eval のステップ:

このワークフロー全体の概要を把握したところで、次のレッスンから Jupyter notebook 内で実際に自前の eval フレームワークを実装していく。


Lesson: Generating test datasets(テストデータセットの生成)

自前の prompt evaluation ワークフローの構築を実際に始める。プロンプトを書き、それを評価するコードを書いていく。

プロンプトのゴール: AWS のユースケースに特化したコードを書くのをユーザーが助けるためのプロンプトを作る。ユーザーがヘルプが必要なタスクを入力すると、次の3種類のうちいずれかの出力で応答する。

ユーザーがタスクを依頼したら、上記3種類のいずれかの出力だけを、余計な説明・ヘッダー・フッターなしで返す必要がある。これが全体のゴール。

ステップ1: ドラフトプロンプトを書く。 すでに用意済みのV1プロンプトは「please provide a solution to the following task(次のタスクの解決策を提供してください)」というシンプルな内容で、そこにユーザーのタスクを挿入する。

ステップ2: データセットを組み立てる。 データセットには、プロンプトに投入する入力の数々が含まれる。プロンプトと入力のあらゆる組み合わせを実行することになる。今回のケースでは、task プロパティを持つ JSON オブジェクトの配列を用意する。各タスクは Claude にやってほしいことを記述しており、それぞれをプロンプトに投入して Claude に結果を返させる。データセットは手作業で組み立てることも、Claude で自動生成することもできる。Claude を使ってデータセットを生成する場合、Haiku のような高速なモデルを使う絶好の機会でもある。

実装: notebook を開き、generate_dataset という関数を定義する。この関数の中に、かなり大きめのプロンプトを用意しておく(notebook はレクチャーに添付されているのでダウンロードして使い回すことが推奨される)。このプロンプトは Claude にテストケースをいくつか生成させるためのもので、テストケースは task プロパティを持つ JSON オブジェクトの配列として表現される。デモでは3つのオブジェクトを生成するよう指示している。

コードの流れ:

  1. messages リストを宣言し、user message としてプロンプトを追加する。
  2. assistant message として「```json」(バッククォート3つ + json)を追加する。
  3. chat をメッセージリストと stop sequence(この場合は「```」の3つのバッククォート)を指定して呼び出す。これは前のモジュールで学んだ prefilling + stop sequence の手法をここでも使っている(前述の通り、この prefilling は現行の最新世代モデルではサポートされなくなっている点に注意)。
  4. 最後に json.loads(text) を返す。

セルを実行して関数を定義し、実際にテストしてデータセットを print してみると、Python 関数を書くケース・JSON configuration を書くケース・正規表現を書くケースの3つのテストケースがちゃんと生成されていることを確認できる。

データセットをファイルへ保存: 後で評価する際に簡単に読み込めるよう、dataset.json というファイル名で write モードで開き、json.dumpindent=2 を指定して書き出す。セルを実行すると、notebook と同じディレクトリに dataset.json ファイルが作成され、その中にタスクのリストが入っている。これで eval データセットの準備が整った。


Lesson: Running the eval(evalの実行)

データセット生成が完了したので、次はデータセット内の各レコード(これを test case(テストケース) と呼ぶ)を1つずつプロンプトとマージし、その結果を Claude に投入し、得られた出力すべてを grader(グレーダー)に通す、という流れを実装する。グレーダーについてはまだ扱っていないが、すぐ後で扱う。

時間短縮のため、以下3つの関数がすでに用意されている。それぞれ何をするかが明確なコメント付きで書かれている。

1. run_prompt 関数: test case(先ほど生成した JSON オブジェクトの1つ)を受け取って呼び出される。この関数の中で、生成したタスクをプロンプトとマージし、Claude でテキストを生成し、結果を返す。

2. run_test_case 関数: 個々のテストケースを1つ受け取り、先ほどの run_prompt を呼び出して Claude から出力を得て、その結果を採点し、何が起きたかを説明する dictionary を返す。

3. run_eval 関数: データセットを引数として受け取る(または読み込む)。データセットをループし、テストケースごとに run_test_case を呼び出して結果をまとめる。

これで3つの主要関数のアウトラインができた。実は、これがすでに eval pipeline の大部分を占めている。唯一まだ実装していないのが grading(採点)ロジックだけである。

テスト実行: dataset.json を開いて JSON としてパースし、run_eval 関数をデータセット全体で呼び出す。結果を results に代入し、すべてのセルを再実行する。初回実行にはかなりの時間がかかる。Haiku を使っていても、このデモでは実行に約31秒かかった(後ほど実行時間を短縮するテクニックを紹介する予定だが、今はそのまま進める)。

resultsprint(json.dumps(results, indent=2)) で綺麗に出力すると、各テストケースからの出力を表すオブジェクトの配列が得られる。Claude から得た出力、その元になったテストケースの定義、そしてスコア(現時点ではハードコードされた10)が確認できる。

これでデータセットとテストプロンプトをマージし、Claude から出力を得て、それらをまとめ上げるところまで完了した。残る最後のステップは、入力と Claude からの結果を実際の grader に投入することであり、これは次のレッスンから学んでいく。


Lesson: Model based grading(モデルベースの採点)

prompt evaluation ワークフローに grading system(採点システム)を実装していく。グレーダーはモデルからの出力を受け取り、何らかの客観的なシグナル(数値や真偽値など、何でもよい)を返してくれることを期待するものである。非常によく見かけるのは、1〜10の数値で出力されるパターンで、10は非常に高品質な出力、1は非常に低品質な出力を意味する(ただし数値出力が必須というわけではなく、あくまで一般的な慣行)。

3種類のグレーダー:

  1. Code-based grader(コードベースのグレーダー): モデルからの出力を、自分たちが書いたコードのスニペットに投入する。このコードの中では、プログラム的なチェックを何でも行える。例えば、出力が長すぎたり短すぎたりしないかの検証、特定の単語が含まれているか/いないかの確認、JSON やコードを返す場合の構文検証(syntax validation)をプログラム的に行うこと、さらには生成されたテキストがユースケースに適した読解レベルにあるかを判定する readability score のような複雑なチェックも実装できる。唯一の要件は、このコードを実行した結果として何らかの実際のシグナル(通常は1〜10の数値だが、必須ではない)を返すことだけ。
  2. Model-based grader(モデルベースのグレーダー): 元のモデル呼び出し(すでに行ったもの)からの出力を、追加のモデルに投入する。つまり、もう1回別の API リクエストを行うということ。モデルグレーダーを使うと非常に高い柔軟性が得られる。応答の全般的な品質、プロンプトの指示にどれだけ忠実に従ったか、応答の完全性など、思いつく限りほぼ何でも評価するようモデルに依頼できる。ここでも唯一の要件は、モデルが何らかの確固たる客観的シグナル(通常は1〜10の数値)を返すことである。
  3. Human-based grading(人間による採点): モデルからのすべての出力を実際の人間の前に置き、その人がそれらの応答を何らかの形で評価する。人間は非常に柔軟なので、思いつく限りどんな観点・指標でも評価を依頼できる。大きな欠点は、一般的に時間がかかり、非常に地道な作業になること。

評価基準(evaluation criteria)を事前に決める: どのスタイルの grading を使うにしても、事前にどんな評価基準を使うかを決めておく必要がある。今回のユースケースでは、3つの評価基準を設定した。

  1. Python・JSON・正規表現のいずれかだけが返ってきていて、Claude による余計な説明が含まれていないこと。
  2. その Python/JSON/正規表現が有効な構文になっている(タイプミスなどがない)こと。
  3. 一般的なタスク遂行度。モデルがユーザーのタスクに明確に対応し、重大なエラーやロジックミスを含まない、概ね正確なコードで回答しているか。

1つ目と2つ目は code grader で評価できる(フォーマットの評価、コードの構文検証)。3つ目の「一般的な応答とユーザーの質問に明確に対応しているか」は、その柔軟性から model grader のほうが適している。

まず model grader を実装する(実はこちらのほうが組み立てやすい):

  1. run_test_case 内の TODO 部分に戻り、その直前に grade_by_model という関数を新しいセルとして追加する。この関数は test case dictionary(データセットの各値、つまりテストケース)とオリジナルのモデル呼び出しからの output を引数に取る。
  2. この中でモデルを呼び出して出力を採点させる。そのためにはやや長めのプロンプトを書く必要があるので、事前に用意されたプロンプトをペーストして使う。このプロンプトは以下の構成: role(役割)を設定し、AI が生成した solution(解決策)を評価するよう明確に依頼し、task を印字し、モデルが生成した solution を列挙し、そして応答方法についての指示を与える。指示の内容は、AI が生成した solution の strengths(強み)と weaknesses(弱み)のリスト、そこに至る reasoning(理由付け)、そして実際のスコアを求めるというもの。
    • 重要な工夫: スコアだけを求めることもできるが、それだけだとほぼ常に6点あたりの「無難な」スコアばかりが返ってくる傾向がある。追加の strengths・weaknesses・reasoning を求めずにスコアだけを聞くと、モデルは「まあ良い面も悪い面もあるだろうから6にしておこう」と判断しがちになる。strengths・weaknesses・reasoning を求めることで、モデルはより具体的なスコアに絞り込むようになる。
  3. プロンプトを用意したら、grading model を呼び出すコードを書く。messages リストを作り、user message を追加。JSON を受け取るので、ここでも prefilled assistant message + stop sequence を使ってクリーンに抽出する。assistant message として「```json」を追加し、eval_text を得るために chat を stop sequence(閉じの「```」)付きで呼び出す。この eval_text は特定の構造を持つ JSON オブジェクトになっているはずなので、それをパースして返す(return json.loads(eval_text))。これで model grader の完成。
  4. このグレーダーを実際に呼び出す: run_test_case の TODO に戻り、scoregrade_by_model からの model_grade に置き換える。test_case と、実際にプロンプトを実行した output を渡す。model_grade から score を取り出し、さらにスコアの背後にある reasoning(理由付け)も抽出する。model_grade dictionary には strengths と weaknesses のリストも含まれているが、例をシンプルに保つためここでは抽出しない。scorereasoning を最終的な出力 dictionary に含めるようキーを追加する。
  5. セルを再実行(run_test_case の更新と評価の再実行)。実行には少し時間がかかる(このデモでは約22秒)。結果を print すると、生成された出力、モデルによって生成されたスコアとその理由が確認できる。1つ目は8点、2つ目は7点、3つ目は6点、という結果が得られた。

最後の仕上げ: すべてのスコアを合計して平均を出し、最終的な客観的スコアとして print する。run_eval 関数の中で statistics パッケージから mean をインポートし、mean(result['score'] for result in results) のような comprehension で average score を計算し、print(average_score) する。再実行すると、平均スコア 7.00(8点・7点・6点の平均)が得られた。これでようやく実際の客観的な指標が手に入った。グレーダー自体がモデルによるものなので多少気まぐれになることもあり、採点のガイダンスをもっと工夫する余地もあるが、少なくとも今後上げていくべきスコアの土台ができた。


Lesson: Code based grading(コードベースの採点)

続いて code grader を実装する。この code grader は、モデルからの出力を受け取り、余計な説明なしに Python・JSON・正規表現のいずれかだけが返ってきているかを検証する。加えて、実際に得られたコードの種類に応じた有効な構文になっているかも検証する。

構文検証のトリック: 3つのヘルパー関数 validate_jsonvalidate_pythonvalidate_regex を定義する。それぞれの中で、モデルから得た出力を JSON としてパースしようとする/Python の抽象構文木(AST: abstract syntax tree)としてパースしようとする/正規表現としてコンパイルしようとする、という処理を行う。パース・ロード等が成功すればスコア10を、パース中にエラーが発生すれば構文チェックに完全に失敗したとみなしてスコア0を返す。

データセットに format キーが必要: どのバリデータ(採点関数)を実行すべきかを知るには、テストケースのデータセットに「この出力がどのフォーマットになるはずか」という期待値を含めておく必要がある。データセットのファイルを手動で編集することもできるが、代わりにデータセットを生成するプロンプト自体を更新して、将来大きなデータセットを生成する際にも対応できるようにする。

実装のチェックリスト:

  1. JSON・Python・正規表現を検証する関数を追加する。 run_test_case セルの上に新しいセルを追加し、3つのバリデータ関数を貼り付ける。セルの先頭で必要な2つのヘルパーモジュールをインポートし、下部に grade_syntax という汎用の振り分け関数を用意する。grade_syntax は test case(特に format プロパティ)を見て、それに応じた適切な validator 関数を呼び出す。
  2. データセットに format キーを追加するよう更新する。 generate_dataset のプロンプトの出力例に、task の後にカンマを追加し format キー(値は「JSON」「Python」「RegX」のいずれか)を加える。これでセルとその下のデータセット生成セルを再実行すると、生成されたデータセットに format が含まれるようになる(例: 「JSON configuration を作成する」タスクには JSON、「Python を書く」タスクには Python、「正規表現を書く」タスクには RegX)。
  3. ドラフトプロンプトのテンプレートを更新する。 現状のドラフトプロンプトは「try to solve the task(タスクを解決してみてください)」としか言っておらず、これでは必ず JSON でも Python でもないコンテンツが返ってきて、構文チェックに毎回失敗してしまう。run_prompt のプロンプトに、「Python・JSON・正規表現のいずれかだけで応答すること」「コメントや解説を一切追加しないこと」という指示を追加する。さらに、本当に欲しい生のコンテンツだけを得るため、prefilled assistant message + stop sequence を再び使う(この prefilling は現行の最新世代モデルでは使えなくなっている点に前述の通り注意)。今回は、事前にどの言語形式(Python/JSON/RegX)が返ってくるかが分からないため、assistant message には具体的な言語名ではなく単に「```」+「code」という一種の裏技的なプリフィルを入れる。これにより Claude に「これからコードを書くのだ」と伝えつつ、具体的な言語を先に指定する必要がなくなる。閉じ側の stop sequence として「```」を追加する。
  4. model grader と code grader のスコアをマージする。 run_test_case に戻り、model grader を実行している箇所のすぐ下に syntax grader(code grader)を追加する。syntax_score = grade_syntax(output, test_case) のようにして outputtest_case を渡す。その後、syntax score と model score をマージする。まず既存の scoremodel_score にリネームして明確化し、score = (model_score + syntax_score) / 2 として2つのスコアの平均を取る。

実行結果: これで code grading の追加は完了。run_eval を呼び出して全体の平均スコアを再計算すると、最終スコアは 8.166 となった。このスコアが「良い」のかどうかは、実はまだ分からない。それを知る唯一の方法は、プロンプトを何らかの形で変更してみて、より良いスコアが得られるかどうかを確かめることである。次の演習では、プロンプトを少し変更してスコアの改善を試みる。


Lesson: Exercise on prompt evals(プロンプト評価の演習)

課題: model grader をもう少し改善する。具体的には、良い solution とはどのようなものかについて、より多くのコンテキストをグレーダーに提供する。一見難しそうに聞こえるが、実際には2つのステップだけで済む。

解答手順(実装):

  1. generate_dataset 関数を見つけ、大きなプロンプトの中で、各テストケースについて taskoutput format に加えて solution_criteria も生成するよう依頼する文言を追加する。「solution を評価するための重要な基準(key criteria for evaluating the solution)」という趣旨の説明文字列を添える。セルを再実行し、その下のセルでデータセットを再生成する。dataset.json を開くと、format に加えて solution_criteria が新たに含まれていることが確認できる(内容は生成のたびに変わる)。
  2. grade_by_model 関数のプロンプトを見つけ、この新しく生成された solution criteria を挿入する。すでに元の task と生成された output を含めている箇所の直後に、「ここに solution を評価する際に使うべき criteria がある(here's some criteria that you should use to evaluate the solution)」という一文を追加し、XML tags で囲む(XML tags の詳細は次の prompt engineering のレッスンで説明される)。そこに test case の solution_criteria キーの値を補間する。
  3. セルを実行してパイプライン全体を再実行する。run_eval を実行し、更新後のスコアを取得する。print(json.dumps(results, indent=2)) で結果を確認すると、モデルからの実際の出力、テストケース(task と solution criteria)、スコア(この回では 9点)、そして solution criteria を含めたことでより充実したはずの model grader による reasoning セクションが確認できる。

Lesson: Prompt engineering(プロンプトエンジニアリングとは)

prompt evaluation を理解したところで、次は prompt engineering の世界に移る。prompt engineering とは、すでに書いたプロンプトを何らかの形で改善し、より信頼性が高く高品質な出力を得られるようにすることである。

このモジュールの構成:

  1. まずゴールを設定する(プロンプトに何をしてほしいか)。
  2. そのプロンプトの初期バージョンを書く(かなり出来の悪い最初の試作)。
  3. そのプロンプトを eval し、非常に低い評価スコアが出ることをすぐに確認する。
  4. その後、異なる prompt engineering テクニックを1つずつ学び、適用していく。適用のたびに再度 evaluation を実行し、改善のたびにパフォーマンスが向上していく様子を見る。

evaluation の実行には、前のモジュールで組み立てたのと同じ種類の eval pipeline を使う。ただし1点注意が必要で、講師は元の eval pipeline に改良を加え、特定のプロンプトだけでなくほぼどんなプロンプトにも対応できる、より柔軟な evaluation pipeline を作っている。これに対応する notebook は 001_prompting という名前でダウンロードできる。

今回のプロンプトのゴール: 選手(athlete)の身長・体重・目標(physical goal)・食事制限(dietary restrictions)に基づいて、1日分の食事プラン(meal plan)を生成するプロンプトを作る。選手を記述するサンプル入力(身長・体重・目標・食事制限)を受け取り、それらをすべてプロンプトに補間してモデルに送る、という想定。第1版のプロンプトでは理想の出力とは似ても似つかない結果になるが、様々な prompt engineering テクニックを経て、最終的にはほぼ理想に近い出力を目指す。

notebook 001_prompting のツアー:

現状かなり悪いスコアなので、あとはプロンプトを改善していくだけである。次のレッスンから最初の prompt engineering テクニックを見ていく。


Lesson: Being clear and direct(明確かつ直接的であること)

スタート地点のスコア2.32からは、上がっていく一方である。最初の prompt engineering テクニックは being clear and direct(明確かつ直接的であること)

このルールが特に関わるのは、プロンプトの最初の一行。最初の一行はプロンプトの中で最も重要になりがちである。その最初の一行では、シンプルで直接的な言葉を使い、action verb(動作を表す動詞)とともに、Claude に何をすべきかを正確に伝える。

:

いずれの例でも、最初の一行で action(動作) を設定し task(タスク) を提供している。

プロンプトへの適用: 現在の1行目を、「generate a one day meal plan for an athlete that meets their dietary restrictions(選手の食事制限を満たす1日分の食事プランを生成してください)」のように更新する。冒頭で action verb を使って直接的にし、シンプルな言葉で Claude に果たすべき直接的なタスクを与えている。

結果: セルを再実行してプロンプトを更新し、再度 evaluation を実行すると、前回の2.32から 3.92 に向上した。確かに改善しているが、まだ良いとは言えない水準。次のレッスンで、もう少しプロンプトを改善するための次の prompt engineering トピックを見ていく。


Lesson: Being specific(具体的であること)

次の prompt engineering トピックは being specific(具体的であること)。プロンプトに何らかのガイドライン(guidelines)や手順(steps)を列挙して、モデルを特定の方向に導くという考え方である。

例(短編小説を書かせるプロンプト): 「隠れた才能を発見するキャラクターについての短編小説を書いてください」というプロンプトをそのまま Claude に投げると、Claude は無限にある方向性のどれにでも進める。物語の長さは大きく変わりうるし、要素を追加したり削除したりするかもしれない。キャラクターが1人だけかもしれないし、5人登場するかもしれない。特定の種類の出力を確実に得たいなら、guidelines のリストを追加する。例えば「1000語以内に収める」「rising action(盛り上がりの展開)を含める」「少なくとも1人の supporting character(脇役)を含める」といったガイドラインを追加することで、特定の種類の短編小説を書くよう Claude を方向づけられる。

ガイドラインの2つのタイプ:

タイプAでは出力の属性を導き、タイプBでは Claude が最終成果物にたどり着くまでの過程をより具体的に導く。実務のプロンプトでは、この2つを組み合わせて使うことが非常によく見られる(出力の属性をコントロールするガイドラインのリストと、モデルが従うべき手順のリストの両方を持たせる)。どちらも「具体的であること」の一例である。

プロンプトへの適用(タイプA: 属性のリスト): run_prompt 関数に戻り、出力に見られてほしい属性のリストを貼り付ける。セルを実行し、evalを再度実行すると、最終スコアは 7.86。前回の3.92から驚異的な改善であり、Claude に出力の中に何を見たいのかを正確に伝えるガイダンスを少し追加しただけでこの結果が得られた。

タイプB(手順のバリエーション)を試す: 食事プランを組み立てる際に Claude が従うべき手順を提供する。まず計算をし、次にこれを考え、それから少し計画を立てる、という具合の手順を与える。セルを再実行すると、先ほどの7.86(統計的な異常値かもしれないと講師は述べている)に対して、今回は 7.3。依然として劇的な改善ではあるが、属性のリストを列挙するほうがやや良い結果だった。そのため講師は、属性のリストを列挙するバージョンに戻すことにした。

どちらの手法をいつ使うか: 一般的には、ほぼどんなプロンプトでも「出力に持たせたい性質」を列挙することを推奨する(タイプA)。一方で、Claude により複雑な問題に取り組ませたい場合、つまり Claude が自然には考慮しないかもしれない、より広い視野や追加のトピックを考慮させたい場合には、モデルが従うべき手順を提供する(タイプB)ことが推奨される。例として、あるセールスチームの数字が前四半期で落ち込んだ理由を Claude に調べさせるプロンプトが挙げられている。このようなシナリオでは、Claude に本来考慮しないかもしれない追加の視点やデータを検討させたい場合がある。


Lesson: Structure with XML tags(XMLタグによる構造化)

次に扱うのは、プロンプトに XML tags を使って構造を持たせるという考え方。

背景: プロンプトを書くとき、しばしばある程度のコンテンツを補間することになる(今回の例でも身長・体重・目標・食事制限を補間している)。それぞれの値は比較的小さいものだが、プロンプトに大量のコンテンツを投入する必要がある場合もありうる。例えば、20ページ分の売上記録(sales records)をプロンプトに貼り付けて Claude に何らかの分析をさせるようなケース。大量のコンテンツをプロンプトに詰め込むと、Claude にとってどのテキストが何を意味するのか、あるいはテキストがどうグルーピングされているのかが分かりにくくなることがある。

解決策: 異なるコンテンツの塊を XML tags で囲むことで、プロンプトの構造をより明確にできる。例えば、先ほどの売上記録を <sales_records> タグで囲む。「sales records」という公式に決まった XML タグがあるわけではなく、これは講師が作った名前にすぎないが、その中にあるコンテンツの性質について Claude によりよい手がかりを与えられる。単に「records」や「data」と呼ぶこともできたが、より具体的な名前のほうが確実に良い出力につながる。

なぜXMLタグが必要かを示す誇張された例: 「debug my code below using the provided documentation(下記のコードを、提供されたドキュメントを使ってデバッグしてください)」という先頭行のプロンプトを考える。これは「自分が書いたバグ入りのコードと、ある程度のドキュメントがこの下に続く」ことを暗示しているが、実際にリストされたコンテンツだけを見ても、どこまでがコードでどこからがドキュメントなのかがまったく明確ではない。これを明確にする方法として、コードの各チャンクを適切な XML タグで囲む。例えば、コードを <my_code> タグ(「これが自分のコードだ」と非常に直接的で明確)で囲み、ドキュメントを表すコードを <docs> タグ(同じく明確)で囲む。こうすることで、Claude はどのコードをデバッグすべきで、どのコードがドキュメントのソースなのかをずっと理解しやすくなる。

プロンプトへの適用: 今回のケースには、区切る必要のある大きなコンテンツの塊は実はない。身長・体重・目標・食事制限といった補間されたコンテンツはすべて十分に短く、Claude が混乱する可能性は低い。それでも、これが選手に関する外部入力・情報であることを明確にするために XML tags を使うことができる。補間ブロック全体を <athlete_information>(と閉じタグ)で囲む。

結果: セルを再実行し、eval を再実行する。XML tags を追加する前のスコアは7.3だった。結果は かなり大きく上昇した(具体的な数値は明記されていないが「quite a bit(かなりの幅)」と表現されている)。講師は、意図的にシンプルで基礎的なモデルを使うことで誇張されたリターンを見せているため、受講者は同程度の大きな伸びを見ないかもしれないが、それは全く問題ない、と補足している。


Lesson: Providing examples(例を提供すること)

このレッスンで扱う prompt engineering テクニックは、講師いわく「見つかる中でも恐らく最も効果的なものの1つ」で非常に楽しみにしている技法だという。それは、プロンプトの中に 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(ええ確かに、"プラン9・フロム・アウタースペース"以来の最高の映画だったよ)」を与える。この「Plan 9 from Outer Space」は有名な「ひどい映画」なので、こんなツイートをする人物は実はおそらく皮肉(sarcastic)を言っており、実際にはその映画を全く気に入っていない可能性が高い。つまり本来はネガティブに分類すべきツイートだが、Claude はこれをうまく分類できない可能性がある。

multi-shot prompting による解決: 元のプロンプトに例を追加する。Claude に対して、これからサンプル入力と理想的な応答例を渡す旨を明確に伝える。入力と出力のペアはほぼ常に XML tags で囲み、プロンプトの構造をより明確にし、その入出力ペアの目的を Claude に明確にする。例えば、サンプル入力「great game tonight(今夜の試合最高だった)」(明らかにポジティブ)を与え、直後に理想的な出力として単に「positive」を与える。こうすることで、Claude は将来同様の入力を見たときに「これはポジティブとラベル付けすべきだ」という具体的で客観的な例を得られる。

コーナーケースへの対応: multi-shot prompting は、コーナーケースを扱いたいときに複数の例を用いる場合に活用できる。皮肉を含むツイートのようなケースはまさにコーナーケースであり、Claude に特に強調しておきたい。コーナーケースを強調する例を追加する際は、Claude に「特にこういうシナリオに気をつけるべきだ」という文脈も添えるとよい。例えば「be especially careful with tweets that contain some sarcasm(皮肉を含むツイートには特に注意してください)」と述べたうえで、すぐに例を示す。「oh yeah, I really need a flight delayed tonight, excellent(ああそうだね、今夜フライトが遅延するのが本当に必要だったんだ、最高だね)」というサンプル入力は、皮肉を理解していなければポジティブなセンチメントのツイートに見えてしまうが、実際には皮肉でありネガティブである可能性が高い。この例を見ることで、Claude が元の入力を採点する際にも「これも皮肉っぽいから、おそらくネガティブだ」と気づきやすくなる。

複雑な出力フォーマットの理解にも有効: multi-shot prompting は、コーナーケースの捕捉や Claude への明確化だけでなく、より複雑な出力フォーマットを Claude に理解させる助けにもなる。かなり複雑な構造の JSON オブジェクトを生成させたい場合、サンプル入力とその複雑な JSON 構造の例出力を Claude に見せることで、目指すべき出力の正確な構造をより良く理解させられる。

prompt evals との相性が特に良い: prompt eval を実行するたびに、同じディレクトリに HTML ファイルが作成されることを思い出してほしい。このファイルを漁って完璧な10点(あるいは少なくとも高いスコアのテストケース)を探す。デモでは10点を見つけ(見つからない場合は最もスコアが高いレコードで構わない)、これはモデルグレーダーによって「ほぼこれ以上望めない」と判断された、入力と出力の例である。これをプロンプト内の例として提供することを考える。

プロンプトへの適用:

  1. この高スコアの入力をコピーし、プロンプトに戻る。guidelines セクションの下に、これからサンプル入力と理想的な出力の例を提供する旨を明示的に Claude に伝える一文を追加する(「ここにサンプル入力と理想的な出力を含む例があります」といった趣旨)。
  2. サンプル入力を XML tags 内に配置し、理想的な出力も同様に XML tags 内に配置する。理想的な出力の中身には、先ほどコピーした出力を貼り付け、インデントを整える。
  3. 任意だが効果的な追加ステップ: なぜこれが理想的な出力なのかを Claude に理解させることが非常に有益であることが多い、と講師は述べる。レポートには、なぜこれが良い応答だと判断されたのかを説明する列(reasoning)がある。その説明の前半部分をコピーし、閉じの <ideal_output> タグの直下に貼り付け、文法を少し整えて「this example meal plan is well structured, etc.(この例の食事プランは構造がよく整っている、など)」のようにする。これにより、Claude は食料の選択と分量に関する詳細情報を含み、何より選手の目標と制限に一致する、構造化された出力を返すべきだという理解をより強く持てるようになる。

結果: セルを再実行して eval を再実行すると、スコアは少しだけ上昇して 7.96 となった。

まとめ(本レッスンの要点): このテクニックは one-shot(1つの例を提供)または multi-shot(複数の例を提供)プロンプティングと呼ばれる。Claude にコーナーケースへの対応を確実にさせたいとき、あるいは特に複雑な出力フォーマットに確実に一致させたいときに、非常によく使われるテクニックである。


Lesson: Exercise on prompting(プロンプティングの演習)

プロンプトエンジニアリングの理解度を試す演習。演習用に 003_exercise という新しい notebook が用意されている(必須のダウンロードではなく、既存の作業用 notebook を自分で書き換えてもよいし、この 003_exercise をダウンロードして講師の進捗に追いつく形でもよい)。

課題: このモジュールで学んだすべての prompt engineering トピックを使って、既存のプロンプトを改善すること。今回扱うデータセットは、学術論文(scholarly article)からのテキストの一節(passage)のシリーズを生成するもの。プロンプトのゴールは、そのテキストの一節を受け取り、そこに含まれるすべてのトピックを JSON array of strings(文字列の JSON 配列)として抽出すること。「トピック」とは、その記事が何について書かれているか、という意味で、例えば太陽光パネルについての記事であれば「solar panels」を含む JSON array of strings が欲しい、それ以外にもテキスト中で言及されている他のトピックがあればそれも含める、という具合。プロンプトへの唯一の入力は content プロパティ(1段落分のテキスト)。

開始状態: すでにデータセット生成セルは実行済み。スターター用のプロンプトも用意されているが、現時点ではかなり出来の悪いプロンプトで、明らかに改善の余地がある。このプロンプトを使って学んだ様々なテクニックを試す。進捗を評価するには evaluation セルを実行する。すでにいくつかの extra criteria が含まれており、正しい結果に到達しているかを確認できるようになっている。デフォルトのシンプルなプロンプトでの平均スコアは 2.8。目標は少なくとも7点あたりまで平均スコアを引き上げること。

解答手順:

  1. eval を最低1回実行して output.html を生成する。 これを開いて、なぜ出力がそれほど低く評価されているのかの reasoning を確認する。共通するテーマとして、「JSON array of strings で返ってきていない」ことへの不満が繰り返し見られる。
  2. being clear and direct のテクニックを適用する。 JSON array of strings としてすべてのトピックを返してほしいなら、Claude にシンプルかつ直接的に何が欲しいのかを伝える必要がある。プロンプトの最初の一行を「extract key topics mentioned from a passage of text from a scholarly journal into a JSON array of strings(学術誌のテキストの一節から言及されている主要なトピックを抽出し、JSON array of strings にしてください)」のように、できる限りシンプルかつ直接的な表現に更新する。プロンプトと evaluation を再実行すると、いきなり 9.5 という驚くべきスコアに到達した。他にも試したいテクニックがまだ残っていたためこの結果は予想外だったが、レポートを見るとテキスト中の様々なトピックを含む JSON がきちんと返ってきており、model grader も非常に満足していることが分かる。
  3. XML tags で構造化する。 ここで終わりにはせず、正しい種類の出力を確実に得るための他のテクニックも追加する。補間するコンテンツの前後を XML tags で囲み、タグ名を単純に text とする(プロンプトの最初の一行で「テキストの一節(a passage of text)」という言葉をすでに使っているため、その言及と実際に提供するテキストとの結びつきをより明確にする狙いで text という名前を選んでいる)。
  4. being specific(具体的な手順)を追加する。 Claude にやってほしいことをステップバイステップで列挙する。「follow these steps(次の手順に従ってください)」に続けて、例えば「提供されたテキストをよく調べる(closely examine the provided text)」「各トピックを特定する(identify each topic mentioned)」「各トピックを JSON array に追加する(add each topic to a JSON array)」「最終的に JSON array で応答する。他のテキストやコメントは一切含めない(respond with the JSON array. Do not provide any other text or commentary)」という手順を列挙する。
  5. one-shot / multi-shot の例を追加してもよい。 望むなら例を追加してもよいが、すでに十分良いスコアが出ているため、この演習ではそこまでは行わないことにする。
  6. プロンプトを再実行し、evaluation を再度実行すると、再び 9.5 というスコアが得られた。これは十分に強力なプロンプトであり、記事からトピックのリストを抽出する用途に確実に信頼できる、と結論づけている。

章末まとめ(要点整理)


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

なお「Fine grained tool calling」は本コースの新規追加レッスンであり、他のレッスンと同じ丁寧さで内容を反映している。


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