re:Invent 2023: AWSとAnthropicが語るLLMのプロンプトエンジニアリング実践技法
はじめに
海外の様々な講演を日本語記事に書き起こすことで、隠れた良質な情報をもっと身近なものに。そんなコンセプトで進める本企画で今回取り上げるプレゼンテーションはこちら!
📖 AWS re:Invent 2023 - Prompt engineering best practices for LLMs on Amazon Bedrock (AIM377)
この動画では、AWSのJohn BakerとAnthropicのNicholas Marwellが、プロンプトエンジニアリングの最新テクニックを解説します。Large Language Modelsの精度を上げる方法や、RAGの実装、Claudeに特化した最適化手法など、実践的な内容が盛りだくさんです。特に、プロンプトチェーニングやツールの使用といった高度なテクニックの解説は、エンジニアの知的好奇心を刺激するはずです。AIモデルの能力を最大限に引き出すヒントが満載の90分です。
※ 動画から自動生成した記事になります。誤字脱字や誤った内容が記載される可能性がありますので、正確な情報は動画本編をご覧ください。本編
プロンプトエンジニアリングとLLMの概要
素晴らしい、今日は皆さんにお会いできて嬉しいです。re:Inventの初日です。re:Inventの最初のトークで、ワクワクしますね。今日は、プロンプトエンジニアリング、大規模言語モデル、そして生成AIについてお話しします。今年はこのトピックがニュースでよく取り上げられましたよね?多くのことが変わりました。大規模言語モデルから素晴らしい結果を得るのは簡単そうに見えます。しかし、お客様の前に出せるような予測可能で一貫性のある結果を得るのは、より大きな課題です。皆さんはこの課題に直面していますか?
私はJohn Bakerと申します。AWS Bedrockのプリンシパルエンジニアです。今日はこの課題を克服する方法についてお話しします。AnthropicのNicholas Marwellさんも同席しています。彼はAnthropicのベストプラクティスを共有してくれます。元々、数日前のスケジュールでは、AnthropicのMatt Bellが来る予定でしたが、この時期の旅行事情で、彼の制御外の事情により参加できなくなりました。しかし、Nicholasが特別に参加してくれました。彼は顧客と協力し、AnthropicのSDKを開発してきました。そのため、Matt Bellの内容に加えて、Nicholas自身が学んだことをパーソナライズして追加してくれています。これは特別な機会となるでしょう。
では、アジェンダを見ていきましょう。まず、プロンプトエンジニアリングの一般的なテクニックについて掘り下げます。プロンプトエンジニアリングとは何か、簡単なテクニックと高度なテクニックについて説明します。そして、RAGに関するシステム設計を行います。これは非常に人気のあるユースケースのようです。その後、Nicholasに引き継ぎ、私が話したことをAnthropicのモデルに最適化する方法を示してもらいます。最後に、フォローアップできるコンテンツを紹介して、行動を促す呼びかけで締めくくります。
プロンプトエンジニアリングの基本:コンテキストの重要性
では、例を見てみましょう。10足す10は何でしょうか?ここでちょっとしたプロンプトエンジニアリングをしてみましょう。「John、このトークの概要には数学をやるとは書いてなかったよ」と思っているかもしれませんね。大丈夫です、大規模言語モデルを使いましょう。10足す10は何でしょうか?これを大規模言語モデルに入力すると、「10足す10は20です」という答えが返ってきます。明らかですね、電卓で確認する必要もありません。でも、もし違う答えが欲しかったらどうでしょう?どんな違う答えがあり得るでしょうか?
質問がやや曖昧だということに注目してください。「10足す10は何ですか?」この質問には多くの正解がある可能性があります。結局のところ、私は「10足す10を解け」とは言っていません。もし具体的に解くことを望んでいたら、「10足す10を解け」と言ったはずです。しかし、私は「10足す10は何ですか?」と聞いたので、多くの正解がありえるのです。実際、数学のチュートリアルアプリケーションを開発しているとしたら、演算の順序などを教えているかもしれません。そこで、もし学生に、あるいはあなたに事前にこんなふうに示したらどうでしょう?1足す1は足し算の問題です。1引く1は引き算の問題です。掛け算、割り算、などなど。
さて、先ほどの例をお見せしたあとで、こう言いました...「10たす10はいくつですか?」申し訳ありませんが、砂漠の空気のせいで声がおかしくなっています。少しかすれ声になっているかもしれません。さて、ここで「10たす10はいくつですか?」と聞いたとしましょう。今度はより多くの情報があるので、私が尋ねたあの曖昧な質問に対して、求めている答えを出すことができるでしょう。10たす10は足し算の問題です。 質問やユーザーのリクエストに対して、より多くのコンテキストを追加することが、prompt engineeringやin-context learningの本質です。私たちは、モデルが特定の質問に対して学習した答えや、持っているバイアスによる答えを単に出すのではなく、コンテキストを追加して、望む答えに向けて出力をバイアスさせたいのです。これこそがprompt engineeringの本質なのです。
ペルソナ設定によるLLMの出力制御
別の例を見てみましょう。Prompt engineeringの楽しい部分です。ここではBedrockのchat playgroundを使用しています。 Chat playgroundでは、指示を追加できるので、ここでペルソナを設定します。「あなたは高校の物理の先生です。」質問に答える際の特定のトーンを設定したいのです。
「あなたは高校の物理の先生です。質問に1文で答えてください。」そして、みなさんはこう思っているでしょう。「ジョン、スライドにはprompt engineeringの楽しい部分と書いてあるのに、高校と物理の先生の話をしているじゃないか。楽しいところはどこにあるんだ?これは詐欺広告か?」まあ、もう少し我慢してください。では、質問をしてみます。下にあるように、Anthropic Claude V2を使用しています。「量子もつれを説明してください。」と聞くと、こう答えます:「量子もつれは、2つ以上の粒子が大きな距離で離れていても単一のユニットとして振る舞い、各粒子の量子状態が他の粒子から独立して記述できない現象です。」私は物理の専門家ではありませんが、これは正しそうです。そして、語彙が確かに高校レベルに適していることに注目してください。
では、別のペルソナを与えてみましょう。指示を単純に変更して、こう言います。「あなたは砂糖をたくさん食べた興奮した3歳の子供です。質問に1文で答えてください。」ここからが楽しい部分です。「量子もつれを説明してください。」すると、こんな答えが返ってきます。「わあ、すごい!」語彙が全く異なりますが、概念は同じであることに注目してください。粒子がくっついていて、とても遠く離れていても、何をしているか分かっていて、魔法のようにつながっているのです。語彙は3歳児レベルのようですね。まあ、量子もつれを説明できる3歳児ということですが。
ここには実は非常に深い意味があります。エンドユーザーからのリクエストは両方とも全く同じで、「量子もつれを説明してください」でした。モデルの出力は、トーンと語彙の面で大きく異なりました。したがって、prompt engineeringを行う際、特にチャットインターフェースを使用してこれらのモデルをお客様の前に置く場合、ベストプラクティスの1つは常にペルソナを設定することです。チャットインターフェースとしてユーザーの前に置きたいトーンを必ず設定してください。つまり、作成するプロンプトの一部として、「あなたは丁寧で役立つカスタマーサービス担当者です」や「あなたは丁寧な旅行代理店の担当者です」などと言うのです。チャットボットが何をするにせよ、適切なトーンを取るようにしたいのです。繰り返しになりますが、モデルにバイアスをかけているのです。このコンテキストを使って、モデルの出力を望むトーンに向けてバイアスをかけているのです。
One-shotとFew-shot Promptingの活用
では、もう一つのシンプルなテクニックを見てみましょう。One-shot promptingです。ここで、私が示している例を見てください。人間とアシスタントのタグを使っていることに注目してください。Nicholasが後ほど、なぜ人間とアシスタントを使っているのかについて詳しく説明しますが、人間とアシスタントに注目してください。私が示している例は、ロサンゼルスからマイアミへの飛行を希望しており、そしてアシスタントに特定のフォーマットで空港コードを生成してもらいたいというものです。「[LAX, MIA]」というフォーマットに注目してください。そして、ユーザーのリクエストは「ダラスからサンフランシスコへ飛びたい」です。私はアシスタントの回答を始めています。その方法に注目してください。「Airport codes [」と言っています。これは、large language modelの結果を私が望むフォーマットに誘導するためにコンテキストを提供しているのです。空港コードが欲しいだけでなく、特定のフォーマットで欲しいのです。そうすれば、正規表現を使ってそこから取り出すことができるかもしれません。そこで、フォーマットを開始し、モデルに完成させてもらいます。しかし、このスライドで重要なのは、one-shot promptingです。欲しいものの例を1つ示しています。そのため、one-shotという名前がついています。
複数の例を示す場合、few-shot promptingと呼びます。これは分類に非常に効果的です。テキストがあり、それを特定のカテゴリーに分類したい場合、few-shot promptingは非常に効果的です。実際、large language modelは非常に優れた分類器であることがわかっています。この場合、各分類の例を示しています。negative、positive、neutralです。各分類の例を示し、そして「Human:」と書いて、括弧を残しています。これは実行時にコードを挿入するためで、エンドユーザーからの入力を挿入するコードを用意します。
そして再び、特定のフォーマットで欲しいので、sentimentを開始します。つまり、one-shotは1つの例、few-shotは複数の例です。これらがシンプルなテクニックです。
高度なプロンプト技術:Chain-of-thoughtとRAG
では、もう少し高度なテクニックに踏み込んでみましょう。Chain-of-thought、またはCoTと呼ばれることもあります。Chain-of-thoughtを使うと、複雑な問題を個々のステップに分解することができます。答え、できれば正しい答えを得るだけでなく、モデルがその問題についてどのように考えたのか、結果を生成するためにどのようなステップを踏んだのかを見ることができます。
Retrieval augmented generation、またはRAGは、現在最も実装されているユースケースだと思います。ほとんどすべての顧客がRAGソリューションを持っているか、開発中です。Retrieval augmented generationは検索を行う方法です。large language modelの外部にある知識ストア(会社内部のものかもしれませんが、large language modelの外部にあるもの)を使います。ユーザーのクエリを受け取り、そのクエリに対して結果を取得します。そして、プロンプトを構築し(これが拡張部分です)、自然言語の応答を生成します。RAGについては、システム設計を行い、各ステップを見ていきます。
それでは、chain-of-thoughtについて見ていきましょう。Chain-of-thoughtは、2020年か2021年頃に発表された特定の研究論文から生まれました。 Chain-of-thoughtプロンプティングは、大規模言語モデルに推論を引き出させます。ここに示されている問題の詳細は読まないでください。よく言われることですが、「大量のテキストや問題を提示しないでください。聞き手が話を聞かずに問題を理解しようとしてしまいますから」と。そこで、私が問題の内容を説明します。
Chain-of-thoughtプロンプティングの詳細と効果
論文の例では、ワンショット例を使っています。 「ロジャーはテニスボールを5個持っています。彼はさらに2缶のテニスボールを買います。各缶には3個のテニスボールが入っています。ロジャーは今、合計何個のテニスボールを持っていますか?」答えは11個です。正しいですね。テニスボール5個、2缶で各3個、2掛ける3で6個、5足す6で11個のテニスボールになります。
ワンショット問題を与えた後、新しい複雑な問題を与えます。 ここで複雑な問題とは、解決に複数のステップを要する問題と定義します。新しい複雑な問題はこうです。「カフェテリアにはリンゴが23個ありました。そのうち20個を昼食用に使い、さらに6個買いました。カフェテリアには今、何個のリンゴがありますか?」単に答えだけを出力すると、間違った答えになります。リンゴ23個、20個使用、3個残り、6個購入、3足す6で9個となります。
では、どのように修正したのでしょうか?モデルの微調整や再トレーニング、新しいトレーニングデータの追加は行いませんでした。代わりに、より詳細な情報を追加しました。Chain-of-thoughtプロンプティングでは、 同じワンショット例と同じ新しい質問を使いますが、今回は正しい答えが得られます。何が違うのでしょうか?青色の部分を見てください。そこにステップが示されています。先ほど私が説明したステップです。「ロジャーは最初5個のボールを持っていました。2缶で各3個のテニスボールは6個です。5足す6で11個になります。」
ここで何をしているのでしょうか?私たちは、モデルを望む答えに誘導しているのです。単に答えだけでなく、ステップも欲しいのです。そこで、モデルは私たちの望む方向に誘導されます。「カフェテリアには最初23個のリンゴがありました。昼食用に20個使ったので、23引く20で3個残ります。さらに6個買ったので、3足す6です。」正しい答えである9個に到達します。正しい答えに到達するだけでなく、関連するすべてのステップも得られます。これらのステップも記録して、なぜ特定の決定を下したのかを確認することができます。
さて、chain-of-thoughtは非常に人気があります。これのより複雑なバリエーションも見られます。そして、react promptingについては詳しく触れませんが、Nicholasがそれらの概念のいくつかに触れる予定です。彼はこの特定の問題を再検討し、Claudeについてもいくつかの詳細を示す予定です。
RAG(Retrieval Augmented Generation)の実装と最適化
では、RAGについて詳しく見ていきましょう。RAGは実際に2つの部分に分かれています。なぜなら、回答を照会するためのデータストアを扱うからです。まず最初に行うべきことは、そのデータストアを構築することです。ドキュメントを取り込み、照会可能な形式に変換します。ここでは、例えばウェブサイトのFAQなどのドキュメントがあります。これはRAGの非常に一般的なユースケースです。そこで、これらのドキュメントをAmazon S3にアップロードします。ウェブサイトをクロールし、それらのドキュメントを取得して、S3にアップロードするのです。
次に、ingestion orchestratorを用意します。このingestion orchestratorは、ドキュメントを取り込み、それらを小さなチャンクに分割し、そして埋め込みモデルを使用してこれらの単語のチャンクを数学的な表現、つまりベクトルに変換します。これを行う理由は、vector databaseを利用したいからです。そこで、vector databaseに保存し、これによってコサイン類似度のような類似度検索が可能になります。
RAGの典型的なケースとしてよく見られるのが、vector databaseと埋め込みの組み合わせです。埋め込みを使用する必要があるでしょうか?vector databaseを使用する必要があるでしょうか?いいえ、必ずしもそうではありません。自然言語クエリをサポートするもの、例えばAmazon Kendraなどを使用することもできます。その場合、この手順を踏む必要はありません。しかし、今回はゼロから構築するので、非常に一般的なユースケースであるvector databaseを使用します。
さて、取り込みの部分が完了したので、クエリの部分に進みましょう。ここがRAG(R-A-G)の出番です。例えば、「返品ポリシーは何ですか?」というFAQがモバイルクライアントに入力されたとします。モバイルクライアントはquery orchestratorを呼び出します。これはおそらくingestion orchestratorとは異なるものです。つまり、query orchestratorを呼び出すのです。そして、vector databaseを使用し、ドキュメントを埋め込みに変換したので、ユーザーのリクエストに対しても同じことを行う必要があります。
そこで、ユーザーのリクエストをベクトルに変換し、そのベクトルデータベースを使って検索を行います。これがRAGのRのステップ、つまり検索(Retrieval)にあたります。 RAGのシステム設計を行う際は、設計図上でR-A-Gを明確に示せるようにすべきです。設計をレビューする側も、実際に設計する側も、R、A、Gがどこにあるのかを確認する必要があります。これらは別々のステップとして明確に区別されるべきです。
まず検索を行います。クエリを送信すると、例えば上位5件の結果、あるいは最適化された数の上位結果が返ってきます。次に拡張(Augmentation)を行います。これがAのステップです。ここでは、「このユーザーの質問が与えられた場合」というようなプロンプトを用意します。 そしてそこにユーザーの質問を挿入します。「これらの検索結果が与えられた場合、検索結果のみに基づいて回答を生成してください」というような指示を加えます。Nicholasが後ほど、Claudeを使ってこれを効果的に行うための具体的なプロンプトをいくつか紹介してくれます。彼らの最適化テクニックを活用したものです。
ここでは詳しく説明しませんが、拡張のプロセスでは、基本となるプロンプトに検索結果とユーザーの質問を挿入し、自然言語での回答や最適な答えを求めるといった指示を加えます。そして、このプロンプトを生成AIモデルに送信し、Gの部分、つまり生成(Generation)を行います。 これがGのステップです。自然言語での応答を求めた場合、モデルは検索結果から得た事実に基づいて自然言語の応答を生成します。
さて、ここまでRAG、chain-of-thought、そしていくつかの高度なテクニックを紹介しました。また、one-shot、few-shotといった基本的なテクニックや、ペルソナの価値についても説明しました。ここからはNicholasに引き継ぎ、 Claudeに特化した最適化テクニックを使って、最高かつ一貫性のある結果を得る方法を紹介してもらいます。さらに、いくつかの新しいテクニックについても説明してくれるでしょう。Nicholas、お願いします。
Claudeに特化したプロンプトエンジニアリングの基本原則
ありがとう、John。皆さん、聞こえていますか? 素晴らしい。これからClaudeに特化したプロンプトエンジニアリングについて説明していきます。プロンプトエンジニアリングに対する私たちの基本的な考え方についても触れます。そして、Johnが既に紹介した内容を踏まえて、最後にはより高度なユースケースについても話をする予定です。
まず皆さんに強調したいのは、これらのモデルは人間が生み出したデータで訓練されているということです。つまり、モデルと対話する際には、何か具体的なことを求めるなら、明確で具体的である必要があります。プロンプトを与える時は、他の人間に指示を出す時のことを考えてみるといいでしょう。
例えば、「牛についての話をして」と言えば、非常に幅広い範囲の話が返ってくるでしょう。しかし、設定する時代や、希望する長さ、想定する聴衆などの詳細を追加すれば、より予測可能な内容の回答が得られるはずです。このように考えると、マーケティング、教育、テクニカルライティング、法律、Webデザインなど、多くの職業は本質的に人間に対するプロンプトエンジニアリングだと言えます。Large Language Models (LLMs)は、友人と話すのと非常によく似ていると考えることができます。この点については後ほど詳しく説明します。
指示が重要であるという事実は、すでに触れられています。多くの人が数学などを学ぶ際、教師が問題解決の各ステップを書き出すことを強調した理由があります。それは私たちの思考プロセスを向上させるからです。同様に、教師がそうするように、LLMsにこれを行うよう指示することで、作業を進めやすくなります。ここでは、ステップバイステップで考えることの重要性が示されています。 前のスライドでは、実際に事前に例を示すことの例を見ました。ここからは、 プロンプトエンジニアリングの具体的な内容や、プロンプトをエンジニアリングする際の考え方、そして使用する手順やプロセスについて少し詳しく説明したいと思います。
Claudeのプロンプト構造とフォーマットの重要性
これは、最大限のプロンプトの概要と、それに含まれる可能性のあるすべての要素です。申し訳ありませんが、11個あり、多すぎて誤って11番目の色が背景と同じになってしまいました。11番目の内容については、1、2枚後のスライドで詳しく説明します。プロンプトは「new line, new line human」と「new line, new line assistant」という特殊なトークンで始まり、終わります。その間には、コンテキストやトーンの指示から、モデルが質問に答えるのに役立つデータ、出力のフォーマット方法の指示まで、さまざまな要素が含まれます。
11の部分というのは多いですし、ほとんどのプロンプトがすべての11の部分を含むことはありません。毎回出てくるものもあれば、めったに出てこないものもあります。しかし、私たちが推奨するのは、 まずこの最大限のプロンプトを作成することです。これにより精度が最大限に高まり、その後、精度を期待するレベルに保ちながら、トークンを節約するために何を削除できるかを見極めます。実験と反復が、優れたプロンプトをエンジニアリングする鍵となります。これから説明しますが、この分野は経験的なものであり、理論だけですべてを解決することはできません。
プロンプトのエンジニアリングに関する私たちの哲学について少しお話ししたいと思います。私たちはこれを経験科学として捉えています。つまり、優れたプロンプトを作成する際、最初にプロンプトを書くのではなく、テストケースを書くことから始めるのです。場合によっては5つの簡単なテストケースで済むこともあれば、5,000のテストケースが必要になることもあります。目的によって大きく異なります。しかし、最も重要なのは、多様性に富んだ良質なテストケースを作成することから始めることです。理想的には、想定される大半のケースを網羅できるようにします。モデルで発生する可能性のある一般的なエッジケースも忘れずに含めましょう。実際、これらのエッジケースこそ、プロンプトで最も苦労する部分になりやすいので、それらに対するプロンプトの性能を評価することが重要です。
良いテストケースとは、プロンプトに入力されると予想されるものと、私たちが「ゴールデンアンサー」と呼ぶものから構成されます。数学の問題を解く場合、ゴールデンアンサーは常に出力されると期待される特定の値かもしれません。しかし、自由形式の文章作成のようなタスクでは、例えば短編小説の非常に良い例のようなものかもしれません。この場合、LLMの出力が完全に一致する必要はありませんが、採点する人や何かしらの基準によって、求めているものの要点が把握できるようになっています。重要なのは、これらのテストケースを開発する際、開発したテストケースに合わせてプロンプトを過度に最適化しないようにすることです。テストケースの全ての質問に正確に答えるようモデルに指示するプロンプトを書くのは簡単ですが、それでは汎用性が失われてしまいます。ですので、テストケースを多用しすぎて、「テストに合格したから、何をしたにせよプロンプトは良いはずだ」と考えないよう注意してください。
テストケースを用意したら、実は難しくて時間のかかる部分は終わっています。そこから楽しい部分に入ります。それは予備的なプロンプトを設計することです。これは、すでに話した、そしてこれからも話し続けるフレームワークのいくつかを使って行います。そして、それが完成したら、テストケースに対してプロンプトをテストし、うまくいっている部分と改善が必要な部分を確認し、改良を重ね、満足のいくものができ、共有する準備が整うまで繰り返します。共有とは、顧客向けの本番システムにデプロイすることかもしれません。
また、生産性を大幅に向上させる何かを見つけた場合、同僚と共有することもあるでしょう。もし一枚だけスライドの写真を撮るとしたら、おそらくこれが最も重要だと言えるでしょう。これから話す追加の概念の全てを完全に理解していなくても、このプロンプト開発の哲学に従えば、はるかに良い結果が得られます。
とはいえ、役立つことはいくつかあります。Claudeに特化したいくつかのことについて話しましょう。Claudeは、Anthropicのモデルファミリーの名前です。私たちにはいくつかのモデルがあります。instant モデルや、より強力なClaude 2.0および2.1モデルがあります。これらは、他の大規模言語モデルでは必要ないかもしれない、あるいはすべきでないことかもしれませんが、Claudeでは確実に行うべきことです。そして、それはプロンプトのミッションクリティカルなフォーマットから始まります。
Claudeをトレーニングする際、私たちは人間とアシスタントの対話を交互に繰り返すシーケンスで学習させます。つまり、Claudeにプロンプトを与える際には、ユーザーが話している部分とClaudeが話すべき部分を示すために、特別なトークンを使用する必要があります。改行を2回入れることと、コロンを使用することが非常に重要で、プロンプトを書く際にはこれらを使用しないと、結果が大幅に低下してしまいます。人々が見落としがちな重要な点が2つあり、よく見かける大きな落とし穴についてお話ししたいと思います。これは会話の最初の「Human:」にも適用されます。すべてのプロンプトの冒頭には2回の改行が必要です。これがないと、結果が低下してしまいます。
長文プロンプトの扱い方とXMLタグの活用
さらに、これは例えば、Claudeを使って対話を分析する場合、Humanプロンプトの一部として表示する対話の中で「Human」と「Assistant」というキーワードを使用すべきではないということを意味します。代わりに「H」と「A」や「User」と「Agent」などに置き換えることをお勧めします。実際には、「Human」と「Assistant」以外であれば何でも構いません。そうしないと、Claudeは既に会話に複数のターンがあると勘違いしてしまいます。さて、これは先ほど話したことに戻りますが、ここでもう一度強調しておきたいと思います。
つまり、プロンプティングの黄金律は、「あなたがモデルに書いたものを他の人間が理解できるか?」ということです。あなたのプロンプトを友人に見せて、タスクをどのようにこなすか確認してみてください。友人が正解できれば、LLMも正解できる可能性が高いでしょう。友人が理解できなければ、LLMもおそらく理解できないでしょう。例えば、Johnも既に話していましたが、大規模言語モデルについての真のブレークスルーとなる発見は、これらが驚くべきfew-shot、in-contextの学習者だということです。特定の振る舞いを引き出すために再トレーニングやファインチューニングを行う必要はありません。
これを実現する方法は確かに例を与えることですが、プロンプトの作成に取り組む際に人々がよく躊躇する点の1つは、追加の例による効果がすぐに逓減すると誤解していることです。実際には、その逆であることがわかっています。3つ目の例、5つ目の例、場合によっては10個目や15個目の例をプロンプトに追加することで、非常に大きな効果が得られます。もちろん、1つの例で既に期待する精度と結果が得られている場合は、これほど多くの例は必要ありません。しかし、モデルがまだ望む精度レベルに達していないにもかかわらず、追加の例を加えることをあまりにも早く諦めてしまう人をよく見かけます。ですので、皆さんには本当に追加の例を活用することをお勧めします。効果の逓減は皆さんが思うほど早くはありません。
さて、Johnが既に話したもう1つのアイデアは、Claudeに考える時間を与えるというものです。これにはいくつかの方法があります。ステップを踏んで考えるよう指示したり、単に問題について考えるよう求めたり、あるいは問題を解決するための正確な思考プロセスを与えたりすることができます。しかし、私たちがよくClaudeに質問を考えさせる、いわば「Claude流」の方法をお伝えしたいと思います。これは数学から文章分析まで、あらゆる場面で役立ちます。これをメモしておいてください。私は多くのプロンプトにこのサフィックスを付けています。「回答する前に、thinking XMLタグの中で質問について考えてください。その後、answer XMLタグの中で質問に答えてください。」そして、Claudeの応答、つまりAssistantの応答の冒頭に、そのthinking XMLの開始タグを入れることさえあります。
これから数枚のスライドで、XMLについてと、それに対するClaudeの応答の始め方についてもう少し詳しくお話しします。これは、もちろん数トークン追加する代償はありますが、Claudeの精度を向上させるのに非常に効果的です。
Johnがすでに話したことですが、ロールプロンプティングと呼ばれるものについて、もう少し詳しくお話ししたいと思います。Johnは、特定のトーン、態度、または複雑さのレベルを引き出すためにロールを使用することについて説明しました。例えば、幼稚園児向けかPh.D.学生向けかで話し方を変えるといったことです。しかし、実はロールプロンプティングには別の用途もあります。それは、多くの場合、実際に精度を向上させるということです。
右に表示されている例を見てみましょう。Claudeに「このロジックパズルを解いてください」と頼んでパズルを与えると、かなりうまく解きます。全く同じロジックパズルと全く同じプロンプトを使用しても、Claudeが複雑なロジック問題を解答するために設計されたマスターロジックボットであるという設定を前置きすると、信じられないかもしれませんが、より良い結果が得られます。特定のトーンや態度を引き出そうとしているわけではなくても、Claudeにタスクに適したロールを与えることで、結果の質が向上します。
これらのスライドで私がよく行っていることの一つは、XMLタグを使用することです。これはClaudeに非常に特化したものです。Claudeは、XMLとして構造化されたデータや、実際にはあらゆるものと非常にうまく連携するように訓練されています。特に、より長いプロンプトを作成したり、Claudeに特定の方法で回答をフォーマットさせようとする場合、XMLはClaudeが入力のどこを見れば回答を導くために必要なデータやルールを見つけられるかを理解するのに役立ちます。
これは非常にシンプルな例です。XMLタグによるこのような区切りがないと、Claudeは編集すべき実際のメールがどこから始まりどこで終わるのかわからなくなる可能性があります。しかし、このようなタグを使用することで、編集すべき内容を明確に認識できるようになります。他にも、以前の「thinking」の例で見たように、ユーザーに表示したい最終的な回答と、精度を向上させるために行わせたい思考プロセスを分離するのにも使えるなど、いくつかの利点があります。
Claudeの出力制御とハルシネーション対策
その点に関して、もう一つできることは、Claudeに代わって話すことです。これはしばしば、必ずしもそうではありませんが、出力フォーマットと密接に関連しています。Johnが言及したように、多くの場合、モデルから得た結果をエンドユーザーに表示する際の後処理に役立つため、非常に特定のフォーマットでClaudeに応答してもらいたいことがあります。しかし、時々Claudeは、実際に求めている応答の前に長い前置きを与えることがあります。これは解析を難しくするだけでなく、結局のところコストと遅延に相当するトークンを無駄にしているため、厄介です。
例えば、ClaudeにJSONを出力として求めた場合、実際にはAssistantの後に開き波括弧でClaudeの応答を始めることができます。これは全く問題ありません。Claudeはそこから続けます。基本的に、Claudeに対して「JSONから始めて、前置きはいらない。今取り組んでいることについて考える必要はないと思う」と指示したことになります。これは、Johnが言及したように、Claudeの応答にバイアスをかけることで、出力の特定の動作を強制する非常に良い方法です。
さて、あらゆる種類の言語モデルとやり取りをするユーザーから非常によく聞くのは、ハルシネーションが誰もが気にしている問題だということです。ハルシネーションに対処するためにできることは多々ありますが、Claudeを使用する際には、まず3つのことから始めるべきです。信じられないかもしれませんが、1つ目は単にClaudeに「知らない」と言う許可を与えることです。人間も少しそうだと思いますが、答える必要があると思われる状況に追い込まれると、何かを知らないと認めるのが適切でないと思えば、実際にはむしろ推測してしまう傾向があります。Claudeも同じです。
Claudeに「知らない」と言う許可を与えることで、答えを作り上げるのではなく、答えが得られないときにそれを伝えるようになります。正しく作り上げることもあるかもしれませんが、多くの場合そうではありません。2つ目は、非常に自信がある場合にのみ答えるようClaudeに伝えることです。これもまた、Claudeが答えを見つけられないとか、持っている知識で答えるのに快適ではないと、より頻繁に伝えるようにバイアスをかけ、何かを作り上げようとする可能性を低くします。
3つ目のより高度な方法は、最初の2つを試してからこれに取り組むべきですが、Claudeに質問にすぐに答えるのではなく、まず質問に答えるのに役立つ関連する引用を見つけ、それらの引用のみに基づいて答えを出すよう求めることです。これによりClaudeは利用可能なデータに基づいて答えを根拠付け、再び何かを作り上げる可能性を低くします。
これはまた、ユーザーに対して最終的な体験を提供する際にも非常に役立ちます。ユーザーはClaudeが回答をどこから得たかの監査証跡を見ることができ、これはモデルから得られる回答に対するユーザーの信頼を構築するための素晴らしい戦略です。
さて、ハルシネーションに加えて、開発者からよく聞かれる懸念事項として、「プロンプトインジェクションや悪意のあるユーザーの行動、そしてモデルを望まない目的で利用されることにどう対処すればよいか」というものがあります。まず良いニュースとして、Claudeは、Reinforcement Learning from Human Feedback (RLHF)や、私たちが Constitutional AI と呼ぶものなどのトレーニング技術のおかげで、プロンプトインジェクションや悪意のあるユーザーの行動に対して自然に非常に強い耐性を持っています。しかし、Claudeがすでに保護している事項に加えて、あるいはClaudeが一般的には話題にしても問題ないと考えているが、あなたの製品では適切でないカスタムトピックに対して、追加の保護層が必要かもしれません。例えば、競合他社の製品やサービスについて話すことなどです。
最大限の保護を行う方法は、私たちが「parallel harmlessness screen」と呼ぶものを実行することです。これは、通常、より安価な instant モデルを使用して、通常の生成よりも速く完了し、レイテンシーに影響を与えないように、Claudeに完了を生成するよう依頼すると同時に与える2番目のプロンプトです。これは、ユーザーの入力を見て、その入力に回答で表示したくないコンテンツが含まれているかどうかを判断するよう求めます。繰り返しになりますが、これは多くの人が悪いと考える違法行為などの内容かもしれませんし、あなたの会社や使用ケースに特有の内容かもしれません。
これは、競合他社の製品について話さないことから、自社のLLMとコストを製品に関係のないランダムなタスクに利用されたくないというケースまで、様々な用途で使用されているのを見てきました。例えば、一般的な質問応答サービスになって、そのコストを負担したくないという場合などです。はい、これがプロンプトインジェクションについてです。
プロンプトのテンプレート化とチェーニング
次の2枚のスライドについては、非常に簡単に説明します。これらを最終的にコードに組み込んで本番環境で使用する際には、いずれにせよプロンプトをテンプレート化する必要があります。プロンプトエンジニアリングの反復プロセスの早い段階でこれを行うことをお勧めします。特に長文書を扱う場合、大きな利点があり、はるかに速く反復できるようになります。基本的に、これは入力をテンプレート化して、多くのケースを迅速に処理できるようにすることです。こちらが長文書での実際の例です。
長文に関して一つ強調しておきたいことがあります。これは後でも繰り返しますが、非常に重要なポイントです。ドキュメントをユーザーの質問に対してどこに配置するかによって、定量的に測定された大きな違いがあります。ドキュメントを最初に置き、理想的にはドキュメントXMLタグ内に配置し、Claudeへの指示(多くの場合、ユーザーからの質問のようなもの)をプロンプトの最後に置くようにしてください。最初に置かないでください。
さて、ここからが楽しい、エキサイティングな部分に入ります。より高度なプロンプト技術について見ていきましょう。4つのことを取り上げますが、特に最後の2つが私のお気に入りです。
では、プロンプトチェーニングについて話しましょう。これは皆さんの中にはすでに聞いたことがある用語かもしれません。プロンプトチェーニングの考え方は、1つのプロンプトでClaudeに複数のことを依頼する場合、それを複数のプロンプトに分割することができるというものです。これを行う理由は、人間と同様に、多くのタスクを与えられると、どれか1つでミスを犯す可能性が高くなるからです。Claudeにとっても、タスクを個々の部分に分割することで、各タスクの信頼性と精度が向上する可能性が高くなります。
ただし、これにはコストがかかります。多くのタスクでは、トークン数が大幅に増加することになります。例えば、Claudeへの質問のたびに同じ長文を分析する必要がある場合、チェーニングを行いたくないかもしれません。しかし、実際には、求める結果を得るために、チェーンの各ステップに全てのトークンを渡す必要がない多くのタスクが存在します。ここに示すのはまさにその典型例です。まずClaudeにテキストを分析して名前を抽出させ、次にClaudeにその名前をアルファベット順に並べ替えさせる必要があります。アルファベット順に並べ替えるためにテキスト全体は必要ありません。
そこで実際にできるのは、まずコンテンツ、つまりテキストだけを与えて名前を抽出させることです。その言語モデルの出力を新しいプロンプトに渡し、「これらの名前をアルファベット順に並べてください」と指示します。こうすることで、わずかに少ないトークン数で信頼性を大幅に向上させることができます。信じられないかもしれませんが、ここでプロンプトをチェーンしても、レイテンシーやコストをあまり増加させずに済みます。通常、Claudeに一度に多くのことを依頼していて、毎回全てを正確に処理するのに苦労している場合は、チェーニングを検討すべきです。では、次の重要なトピックに移りましょう。
Function CallingとTool Useの実装
長いプロンプトについてはすでに話しましたが、ここで長いプロンプトを扱う際の5つのゴールデンルールを説明したいと思います。 Claudeの素晴らしい点の1つは、非常に長いコンテキスト長を持っていることです。私たちのインスタントモデルであるClaude 2.0は10万トークンをサポートしています。最近リリースしたClaude 2.1は、実に20万トークンまでを1つのプロンプトでサポートできるようになりました。ただし、1万トークンを超えるようなプロンプトを扱う場合は、いくつかの特別な実践方法をお勧めします。
まず、これは既に言及しましたが、長文書のQ&Aを行う場合は、文書を最初に置き、質問やその他の指示(例えば、Claudeに要約を求めるなど)を最後に置くことです。ここには大きな、そして定量的に測定可能な違いがあります。長文書やテーブルなど、どのような入力データであっても、dataやdocumentなどの適切と思われるXMLタグで囲むことをお勧めします。Claudeに対して、まず文書から関連する引用を見つけ、それらの引用が見つかった場合にのみ、そしてそれらの引用に基づいてのみ回答するよう指示してみてください。信じられないかもしれませんが、Claudeに文書を注意深く読むよう指示するだけでも、ある程度効果があります。これは多くの場合、精度を向上させるための安価な数トークンのテクニックです。
最後に、他のすべての場合と同様に、Claudeに例を与えることは有用です。長文書で例を使用することに対してよく受ける反論は、「いや、別の長文書全体を入れなければならないのでは?それによってトークン数が倍になってしまうのでは?」というものです。実は、ここで使える一つの方法は、問題の文書から抜粋した質問と回答のサンプルを使用することです。これにより、例としてまったく新しい文書を入れる必要がなくなります。同じ有限の文書セットを繰り返し使用する場合は、これらを事前に作成しておくこともできます。また、時にはClaudeに対して、その場で作成するよう依頼し、著者は誰かなどのより簡単な質問を与えて、それらを扱わせることもできます。
さて、おそらく最も面白い部分に移りましょう。 皆さんの中には、agents、function calling、tool useといった用語を聞いたことがある方もいるでしょう。ここでは、function callingとtool useをほぼ同じ意味で使います。両者には若干の違いがありますが、ここでは深入りしません。function callingやtool useを使うと、これまで話してきたプロンプトの作成と、Claudeが使用する回答やさらなるデータを返す外部関数の呼び出しを組み合わせることで、Claudeの機能を大幅に拡張できます。Claudeがこれらの関数を直接呼び出すわけではありません。Claudeは、あなたが解析方法を知っている出力を生成し、その出力を検出したら、必要な関数を解析して実行し、その関数の実行結果をClaudeに返すのです。
プロンプトの構成に関しては、 基本的に2つのものをClaudeのために組み合わせています。多くの場合、ユーザーの入力(例えば、「サンフランシスコの現在の天気は?」など)と、Claudeがアクセスできるツールセットの説明、そしてClaudeがそれらのツールを使用する方法に関する指示を組み合わせます。説明は、これよりも少し詳細になります。数スライド後にお見せしますが、get_weatherというツールやplay_musicというツールを与えるかもしれません。Claudeは一度に複数のツールを扱うことができ、基本的にはコンテキスト長によって制限されます。これらを1つのプロンプトにまとめて、Claudeに与えることになります。
そして、Claudeは与えられた関数の関連性を判断し、質問に答えるためにそれらが必要かどうかを判断します。もしClaudeが、サンフランシスコの現在の天気を知るのに役立つ情報が何もないと判断した場合、おそらく現在の天気情報にアクセスできないと伝えるでしょう。しかし、この場合はget_weather関数にアクセスできます。そのため、左下の「はい」のパスに似たような出力や完了を行います。そして、あなたの仕事は、その出力を解析し、お持ちのget_weather関数を呼び出し、結果をClaudeに返すことです。これにより最終的に、「はい、サンフランシスコの現在の天気は晴れで、気温は68度です」というような答えを得ることができます。
さて、ツールの使用について一つ言及したいことがあります。これは、人々がこのようなものを実装する際に十分な時間をかけていないと思われる点です。それは、ツールの説明を書くことです。これはClaudeの開発者ドキュメントです。私たちは皆、不十分な開発者ドキュメントで作業することが、実装の難しさを桁違いに増加させた経験があるでしょう。Claudeのためにツールの素晴らしい説明と、そのツールの引数の素晴らしい説明を書くために余分な4分を費やさないと、Claudeの性能は向上せず、得られる結果も良くなりません。おそらく、アーキテクチャの残りの部分を設計するのにそれ以上の時間を費やすことになるでしょう。ツールの説明を書くのが面倒だからといって、失敗させないでください。さらに、プロンプト内、つまり説明の中で、余裕があり適切な場所があれば、Claudeにいくつかの例を提供することをお勧めします。また、stop sequencesと呼ばれる引数を渡すこともできます。通常これは単に改行2回とhumanですが、このようなXMLタグの関数呼び出しを追加することで、Claudeが呼び出しを終了したときに停止するのに役立ちます。信頼性の高いパフォーマンスが得られない場合、これは連鎖のための良い機会ですが、ほとんどの場合、それは必要ないと考えています。
また、Anthropicが現在ベータ版のSDKを提供しており、これによって多くの作業が抽象化され、1時間以内にツールの使用を開始できることをお伝えしたいと思います。このSDKは現在Bedrockをサポートしています。ですので、これを試してみたい方は、アクセスをリクエストしていただければ、こちらで設定いたします。
RAGの高度な活用とまとめ
RAGについて、ツールの使用例を一つ簡単にご紹介して締めくくりたいと思います。先ほどJohnがRAGについて話しましたが、RAGは素晴らしいものです。彼が言ったように、現在これらのモデルをデプロイしているほぼ全ての人が、何らかの形でRAGを使用しています。RAGは最新の知識での拡張を可能にし、モデルを証拠に基づいて根拠付けるのに役立ちます。そして、Claudeがクライアントのデータに安全に接続することを可能にします。このデータは、そもそもトレーニングセットに含めたくないかもしれません。
従来、RAGは基本的にJohnが説明したように機能します。ユーザーの質問を受け取り、それを埋め込み、検索ツールに送り、結果とクエリをLLMに渡します。LLMは結果を見て質問に答えます。しかし、もっと汎用的なアプリケーションの場合はどうでしょうか?例えば、Amazonのホームページにチャットボットがあり、毎回RAGを使う必要がない場合や、時には直前に得た結果に基づいて複数回のやり取りをしたい場合、あるいは扱うクエリによって異なるデータソースからRAGを行いたい場合はどうでしょうか。
これが従来のRAGのセットアップですが、ここではそういったことを行う余地がほとんどありません。毎回検索を行わなければならず、埋め込みを行い、検索し、結果を取り込んでから生成する必要があります。毎回これを行う必要があると分かっている場合は、これは素晴らしいセットアップであり、使用すべきです。
しかし、もしユーザーが私たちのチャットボットに来て、単に「こんにちは、助けが必要です」と言い始めたらどうでしょうか。すぐに多くの製品を検索するのはあまり有用ではないでしょう。赤で示されたこれらのステップのどれも実行したくありません。私たちが生成したい回答は、おそらく「はい、今日はどのようなお手伝いができますか?」というものでしょう。
あるいは、チャットボットが十分に汎用的で、質問に応じて異なるデータソースを必要とする質問に答えられるとしたらどうでしょうか?時には製品を検索する必要があり、時には顧客サービスに関する有用なドキュメントを検索する必要があるかもしれません。さらに、Claudeがユーザーのクエリを検索対象のデータベースに最適化するために書き換えたい場合や、最初の検索結果を見て「必要な回答がここにはない、もう一度検索する必要がある」と判断する状況ではどうなるでしょうか?
従来のRAGでは、これらのことや、私たちがまだ話していない多くのエッジケースを処理できるシステムを設計するのは非常に難しいでしょう。ここで私が提案したいのは、RAG自体が「天気を取得する」や「音楽を再生する」のようなツールになり得るということです。そして、それを「検索ツール」と呼ぶこともできるかもしれません。このアーキテクチャでは、これまで話してきたRAGのステップを毎回実行する代わりに、Claudeが検索したい可能性のある各データソースに対してRAGツールを作成します。
その後、RAGの出力をユーザーのクエリとすぐに組み合わせるのではなく、それらのツールの説明をユーザーのクエリと組み合わせます。そして、Claudeがどのツールを使用すべきか(もしあれば)を決定し、そのデータソースに最適なクエリを書き直す機会を得てツールを呼び出します。結果を取り込み、質問に答えるために必要な情報を既に持っているか、あるいは最初に参照されたドキュメントに基づいてさらに製品を調べたり別の文書を探したりする必要があるかを判断します。必要な情報が揃ったと判断したら、元のRAGセットアップと同じように完全な回答を生成することができます。
さて、次のスライドに進みましょう。これで高度なプロンプティングの話は以上です。お役に立てたなら幸いです。 ここでお見せしたすべての内容はdocs.anthropic.comで利用可能であることを強調しておきたいと思います。ぜひそちらをご覧ください。私の話よりもずっと優れています。では、Johnに戻したいと思います。
Nicholas、ありがとうございました。あなたをここにお迎えし、これらの学びやベストプラクティスを共有できたことは素晴らしいことでした。会場はライブのQ&Aには設定されていませんが、これから20分から30分ほど滞在する予定です。 皆さんとお会いできるのを楽しみにしています。発表内容について質問がある方は、ぜひお話しさせてください。また、大規模言語モデルを使って何をされているのか、ぜひお聞かせください。QRコードも表示されていますので、ご利用ください。では、最後のスライドをお願いします。ありがとうございました。
※ こちらの記事は Amazon Bedrock を様々なタスクで利用することで全て自動で作成しています。
※ どこかの機会で記事作成の試行錯誤についても記事化する予定ですが、直近技術的な部分でご興味がある場合はTwitterの方にDMください。
Discussion