Amazon BedrockにおけるLLMのためのプロンプトエンジニアリングのベストプラクティス #AWSreinvent

2024.01.08

セッション概要

  • セッション ID: AIM377
  • タイトル:Prompt engineering best practices for LLMs on Amazon Bedrock
  • スピーカー:John Baker/AWS, Nicholas Marwell/Anthropic
  • レベル:300 - Advanced

プロンプトエンジニアリングは、大規模言語モデル(LLM)が望ましい出力を生成するように導くプロセスです。このセッションでは、プロンプトエンジニアリングのベストプラクティスの概要を理解し、精度とパフォーマンスを向上させながら生成 AI ソリューションを最大限に活用するために、最も適切なフォーマット、フレーズ、単語、記号を選択する方法を学びます。このセッションでは、プロンプト・エンジニアリングが複雑な顧客のユースケースの解決にどのように役立つかを、クロード 2 LLM を例に説明します。また、プロンプトをお客様のアーキテクチャに統合する方法や、Amazon Bedrock を使用してモデルパラメータをチューニングするための API パラメータの使用方法についても学びます。

イントロダクション

LLM から望む結果を出力することは一見簡単そうに見えますが、一貫性のある回答を安定して返すプロンプトをクライアントが望みそれを実現する場合は少々難易度が上がります。本セッションではこの課題を乗り越えるためのベストプラクティスをみなさんにお伝えします。

プロンプトエンジニアリングとは

ここにプロンプトのサンプルがあります。『10 + 10 = は何ですか?』と聞いています。計算機を使うまでもなく 20 と答えることができますね。しかし、『10 + 10』答えとして考えられる正しい回答はいくつかあります。先ほどのプロンプトに次の文章を加えた場合答えはどのように変わるでしょうか。

  • 1 + 1 は加算問題
  • 1 - 1 は減算問題
  • 1 x 1 は掛け算問題
  • 1 / 1 は割り算問題

答えは『10 + 10 は加算問題です』となります。

モデルから我々の欲しい正確な答えを出力したい場合コンテキストを与えることが重要ということがわかります。これがプロンプトエンジニアリングの大部分を占める要素です。

次に別のサンプルを見てみましょう。『量子もつれについて説明してください』という質問のコンテキストとして『あなたは高校の物理教師です』という文章を加えました。

返答に含まれる語彙や説明は高校生に対して妥当なものだと言えます。次にコンテキストを『あなたは砂糖をたくさん摂取して幸せな 3 歳児です』に変更します。3 歳児が量子もつれについて説明できるかは別として、回答に含まれる語彙や表現が変化します。

こちらが求める形式で回答が欲しい際、ペルソナを設定することが有効です。もし同様の機能をカスタマーサポートに利用したい場合は正確な回答に加え一貫性のあるトーンで返答するために『あなたは礼儀正しい親切なカスタマーサポートエージェントである』という一文を追加することで内容の回答に対し私たちの求める語彙やトーンのバイアスを追加することができます。

プロンプトテクニック:初級〜中級

シンプルなプロンプトエンジニアリングのテクニックに One Shot Prompt と Few Shots Prompt というのがあります。以下はそのサンプルです。One Shot Prompt ではコンテキストを1つだけ渡し、Few Shots Prompt では複数のコンテキストを渡します。

一つ目の One Shot Prompt ではエアポートのコードを配列で返して欲しいので”[”を文頭に置き回答形式のコンテキストとして渡します。Few Shots Prompt では質問に対してこちらが欲しい回答の形式をサンプルとして複数渡します。

次にさらに上級テクニックとして Chain of Thought(CoT)プロンプトと Retrieval Augmented Generation(RAG)があります。

CoT を利用すると問題に対する回答だけでなくその回答に行き着くまでの過程も取得することができるのでさらに複雑な問題の回答を生成するのに適しています。以下は同じ回答に行き着くまでに複数の異なるアプローチが取れる問題に CoT を利用したケースです。

RAG は私が観測する限り今一番利用されているソリューションであると言えます。RAG を利用すると回答に外部のリソースからも情報を引っ張ってくることができます。

RAG の設計には2つの要素があります。1つ目はデータソースの実装とそのデータをクエリするためのデータの整形です。回答に必要な情報を置く場所を決めたらデータをチャンクに分けて我々が欲しい形式に合わせてトークン化します。この図では1から実装することを前提とするためベクターデータベースと書かれていますが自然言語のクエリをサポートする Amazon Kendra などの検索エンジンサービスも利用することもできます。

2つ目はクエリ部分です。RAG システムを設計する際は R(Retrieval)、A(Augmentation)、G(Generation) がそれぞれきちんと実装されているかを確認することが重要です。以下は Q&A サービスの例ですが、回答に含まれる情報源が指定したデータソースであること(Retrieval)、指定された形式で返答を構築していること:シンプルな回答のみが求められているのか、自然言語的な文章での回答が求められているかを判断しているか(Augmentation)、そして自然言語で回答が生成されているか(Generation)をそれぞれ確認することで RAG の実装に必要な要素が実装されているかが確認できます。

Prompting Philosophy

LLM にリクエストする際に人間に対して質問する時と同様だと考えてください。例えば『牛について話してください』とだけ伝えた場合返ってくる答えの形式がばらつきます。ここにどれくらいの長さで、誰に対して話すように、などの詳細を渡すことでよりこちらが欲しい答えに近いものが返ってきます。

次のスライドで実際の開発で利用されているプロンプトの例を示します。プロンプトは最大で 11 の要素から成り立ちます。冒頭の"\n\n"から始まるワードに続く部分がインストラクションで残りの要素は回答の形式と回答例を加えたものになります。

11 のすべての要素は必要ないことが多いですが、開発初期はプロンプトでなるべく多くの情報を渡すことが効果的です。理想に近い回答が安定して得られるようになった段階でトークンの節約が可能な部分を削除していくやり方が良いでしょう。

安定したプロンプトの実装

プロンプトから十分に安定した回答を得られるかの検証を行うにはまず初めに複数のテストケースを記述します。エッジケースのハンドリングに多くの時間を費やすことになるので複数のエッジケースのテストケースも忘れずに記述するようにしてください。

ここで注意したいのはテストケースによってモデルが過学習されないようにすることです。LLM にそれぞれのテストケースの模範回答を返すように仕向けるのは簡単はことですがそれではテストとしては機能しません。

簡潔で指示がわかりやすいものが良いプロンプトとされていますが、自分のプロンプトが簡潔でわかりやすいかを判断する方法があります。人間の友人に同じ内容を伝えることです。その友人が内容を理解してその内容にそった行動をしてくれるならそのプロンプトはおそらく LLM に渡しても同様に効果的なもので、逆に人間にも理解が困難な書き方で記述されている場合、LLM からも正しい内容が返される確率が下がると言えます。

多数の回答例をプロンプトに加える

こちらの望む形式で返答を得たい時に例文をプロンプトに含めることが効果的という話は既に言及された通りですが、プロンプトを調整する際に 1〜2つの例文を渡してもうまく動かない時、そこでプロンプト自体の構成を見直すことがよくあります。私がここで言いたいのはプロンプトに渡す例文の数は多い方が良いです。1〜2つの例文を渡した時点でこちらの望む形式での回答が既に返っている場合はもちろんそれ以上の例文は不要ですが、回答の形式をこちら望む形に向上したい際プロンプトに含む例文の数を 10~15 に増やして試してみることを勧めます。

モデルに回答する前に考える時間を与える

『回答する前にまず問題文のタグ内についてよく考えてくださいしてください。その後にタグ内に回答を書き込んでください』という一文は私もよく利用するものです。LLM に回答前に考える時間を与えることで質問内容の種別に関わらず回答内容が向上します。

語彙やトーン以外のロールの影響について

先ほど高校教師、幼稚園児、などのロールをプロンプトに渡すことで話し方のトーンや回答に利用される語彙に変化を与えられるという話がありましたが、ロールを与えることには他にも効果があります。ロールを指定することで回答の正解率が上がるのです。

以下のプロンプトの例ではロールを渡さずに複雑なロジック問題を解いてもらう場合とロールを渡して同じ問題を解いてもらった結果です。ロールを渡さなかった時は間違った回答が返りましたがロールを渡した時は正しい答えを得ることができました。ロールが回答内容にも影響するのです。仮にトーンや語彙の調整が不要だったとしてもロールを渡すことで回答内容の向上が見込めます。

架空の回答を返さないようにする

LLM を使う上でよく心配されるのが架空の回答が返るのではないかということです。これは人間でも言えることですが、どうしても何か回答しなければいけない場面で回答を知らない場合、予測で回答することがあります。

これを回避するため、LLM に知らないと回答することを許可する、そして正しいという裏付けが取れている場合のみに回答を返却するように指示をすることが有効です。これに加えた上級テクニックとして質問の回答に関する関連資料をデータソースから探した上で、その内容から回答を構築するというのも効果的です。この手法を用いた際 LLM が架空の回答を返却する確率が下がり、かつ情報ソースを回答に加えることでユーザーにこの回答が正しいことを判断する材料を同時に提供することができます。

悪意あるユーザーのインプットからモデルを守る

また、どうやってプロンプトインジェクションや悪意あるユーザーのインプットから LLM モデルを守れば良いかという質問がよく聞かれます。Claude はプロンプトインジェクションの影響を受けづらく設計されていますが、さらにもうひと段階セキュリティを強化したい場合 Parallel Harmlessness Screen という手法が有効です。

具体的には LLM のインプットに悪意あるものは含まれないかチェックするスクリプトを通常はより安価なインスタンスで実装し通常の LLM の処理と並行してチェックを走らせます。通常の処理より早く終了するためレイテンシへの影響はありませんし、違法なアクティビティの検出に加え自社のプロダクトに無関係のインプットを弾くのにも有用です。悪意あるインプットからの影響からモデル守るだけでなく無関係の処理に貴重なリソースを利用されないようにするためにもこの手法は有効です。

上級プロンプトテクニック:Prompt Chaining

1 つのプロンプトに複数の要件が含まれる場合、それらの注文を区切ってあげることで回答の精度が上がります。これは人間にも言えることですが、一つの文章に複数の注文が含まれている場合、ミスをする確率が上がります。

ここで注意したいのは注文を区切ることでトークンの消費量が上がりコストに結びつくというところです。なのでどうしても必要なときのみこの手法を利用するようにしてください。

次のスライドではドキュメントに含まれる人名を抽出しアルファベット順にソートするという注文を LLM に渡したものです。一つのリクエストで渡すのではなく人名の抽出とアルファベット順にソートするという二つのタスクに分けることで少し多いトークンを消費するものの回答の精度が安定します。

Youtube 動画