RAG プロジェクトを成功させる方法 #2
~ 失敗から始まりベクトル検索をやめた理由 ~
Author : 近藤 健二郎
初めまして、AI/ML ソリューションアーキテクトの近藤です。
RAG (Retrieval Augmented Generation, 検索拡張生成) プロジェクトを成功させる方法、連載の第二回です。本連載では、RAG の手法や AWS のサービス・機能をただ単に紹介するというよりも、実践的なナレッジを発信していくことにフォーカスしています。連載前回の 第一回 では、RAG プロジェクトが始まったらいきなり RAG を作る前に、その問題が生成 AI で本当に解ける問題なのか確かめよう、というメッセージをお伝えしました。
連載第二回の今回は、あるテーマに対して実際に RAG を作ってみて、失敗し、その失敗を改善していく過程をお見せしようと思います。また、その改善の過程で、RAG の鉄板構成としてよく用いられているベクトル検索等の文章のチャンキングを検索するという手法の懸念点についても触れます。
※ 第一回の記事内に、次回予告として連載第二回はベクトル DB や GraphDB について扱う旨が書かれていましたが、予定を変更しました。そちらの内容は次回以降にご期待ください。
本記事のテーマ
本記事では実際に RAG を作っていく過程をお見せするので、そのテーマについて最初に説明します。本記事では AWS の開発者向けドキュメントの RAG を作ります。
みなさま AWS の開発者向けドキュメントを見たことがありますでしょうか。例えば Amazon Bedrock のページ はこちらの画像のようなものです。AWS サービスの仕様や利用方法について記載があり、AWS の利用者はほとんどが利用したことがあるかと思います。

この開発者向けドキュメントですが、サービスによっては PDF で換算して数 1,000ペ ージ分にもなる分量になっており、この中から知りたい情報を見つける作業に苦労した人も多いと思います。実際、私も AWS サービスの仕様についてドキュメントから情報を見つけるのにかなりの時間を使っています。
そこで、この開発者向けドキュメントの RAG を作り、ユーザーからの質問に答えてもらうことを目指します。例えば、「Claude 3.5 sonnetのモデル ID はなんですか ?」という質問を投げたら、「Claude 3.5 Sonnet には 2 つのモデルがあり、3.5 Sonnet (anthropic.claude-3-5-sonnet-20240620-v1:0) と 3.5 Sonnet v2 (anthropic.claude-3-5-sonnet-20241022-v2:0) です」のような回答が返ってくるイメージです。全ての AWS サービスを扱うと量が多すぎるので、今回は Amazon Bedrock に限定します。
これがうまくいけば、全てのAWS サービスに展開できることはもちろん、AWS の開発者向けドキュメント以外の技術ドキュメントにも応用できる可能性があります。
Step 1. 評価データの作成と実現性評価
連載第一回 では、まず評価データを 20 件ほど作成し、プロンプトレベルで実現性を評価しようということをお伝えしました。今回もそのように進めたいと思います。
Amazon Bedrock を利用する開発者がしそうな質問と回答のペアを 18 件手で作成しました。今回作成した質問は全てドキュメントに答えが載っており回答できるものになっています。また、余談ですが QA の自動作成も試みましたが、今回は諦めました。ドキュメントは 2024 年 12 月 12 日時点のドキュメントを使用しています。
質問例 1 :
Bedrockガードレールではどのようなフィルタリングができますか?
解答例 1 :
Amazon Bedrock Guardrailsでは、以下の6種類のフィルタリングが可能です:
コンテンツフィルター - 有害なコンテンツ(憎悪、侮辱、性的、暴力、不正行為など)の検出
拒否トピック - アプリケーションで望ましくないトピックのブロック
単語フィルター - 不適切な単語、フレーズ、冒涜的な表現のブロック
機密情報フィルター - PII(個人識別情報)などの機密情報のブロックまたはマスク
文脈的な根拠チェック - モデルの回答における幻覚の検出とフィルタリング
画像コンテンツフィルター - 不適切または有害な画像コンテンツの検出とフィルタリング
質問例 2 :
Titan Embedding v2 モデルは日本語に対応していますか?
解答例 2 :
はい、対応しています。
質問例 3 :
Claude 3.5 sonnetでクロスリージョン推論を使う方法は?
解答例 3 :
クロスリージョン推論プロファイルの選択:
US Anthropic Claude 3.5 Sonnet (ID: us.anthropic.claude-3-5-sonnet-20240620-v1:0) が利用可能です
以下のリージョンをカバーしています:
us-east-1
us-east-2
us-west-2
us-gov-east-1
使用方法:
APIを使用する場合:
InvokeModelやInvokeModelWithResponseStreamのAPIコールで、modelIdパラメータにクロスリージョン推論プロファイルのARNを指定します
コンソールを使用する場合:
テキスト生成プレイグラウンドで、モデル選択時にThroughputとして"Cross region inference"を選択します
評価データが作成できたら、質問と正しい参考情報を LLM に渡して正解の回答を作成できるかを検証してみましょう。
参考情報はピンポイントの部分だけでなく、関連するドキュメントを数ページ分渡しています。利用モデルは、Claude 3.5 Sonnet V2 です。
評価方法
18 件程度なので、全部目で見てチェックしてもいいのですが、今後のことを考えて、LLM によって自動評価させました。質問、正解の文章、LLM の回答の 3 つを LLM に渡して、3 段階で評価させています。何度か試して目で見て確認することで、概ね正しい評価ができていることが確認できました。少し意図と違う評価結果になってしまったものについては後述します。
評価基準はこちらです。
[評価基準]
2点: 正解の文章に含まれる重要な事実を全て正確にカバーしている
1点: 正解の文章の一部の事実は正確に含んでいるが、重要な情報の欠落や誤りがある
0点: 正解の文章の事実と大きく異なる、または完全な誤りを含む
結果
完全正答率 100%
正しい参考情報を渡した場合、全ての質問に回答できることがわかりました。つまり、今回の RAG のポイントは “正しい情報を取ってこれるか” にかかっているということがわかりました。
Step 2. とりあえずベーシックな RAG 構成で試してみる
それではここから RAG をつくっていきましょう。ひとまず感触を掴むために、最もシンプルでベーシックな構成で試してみます。つまり、ユーザーの質問文でそのままベクトル検索をして、その結果を LLM に渡して回答してもらうというやり方です。

実装方法は Amazon Bedrock Knowledge Bases を用います。理由は、実装が楽だからです。ドキュメントを Amazon S3 経由で連携するだけで、チャンキング、ベクトル化、インデックス等、いろいろな準備をしてくれてすぐに RAG の実験を始めることができます。
Amazon Bedrock Knowledge Bases の詳細についてはこちらをご覧ください。
実験設定の詳細 :
- リージョン : us-east-1
- ソースドキュメント : 英語
- チャンキング : 階層チャンキング (親チャンクサイズ 1500 - 子チャンクサイズ 300 - overlap 割合 60%)
- 検索 : ベクトル検索のみ
- 検索結果取得数 : 5
- Embedding モデル : Titan Text Embeddings v2, 次元数 1024
- 推論モデル : Claude 3.5 Sonnet v2
結果
2 点 : 13/18 (68%)
1 点 : 4/18
0 点 : 1/18
完全正答率は 68% となりました。先ほどのステップで、必要な情報があれば完全正答率 100% でしたが、今回は比較的簡単な質問を用意しているので、もう少し高い正答率を期待したいです。ここから改善していきましょう。
Step 3. 結果の分析
RAG の改善をするための最初のステップは結果を分析することです。
RAG の改善手法は巷にあふれていますが、むやみにいろいろな手法を試すことは効率が悪いです。まず結果を分析しましょう。18 件程度であれば分析も楽です。RAG に必要な要素として正答率以外にレイテンシーやソースを引用できているかのような観点もありますが、今回はいったん無視して正答率の改善にフォーカスします。
うまく回答できていない質問は以下の 5 つです。
- Bedrockガードレールではどのようなフィルタリングができますか ?
- Claude 3.5 Haikuが使えるリージョンはどこですか ?
- Knowledge Base のソースファイルのサイズ制限はありますか ?
- Claude 3.5 sonnetでクロスリージョン推論を使う方法は ?
- Claude 3 sonnetでクロスリージョン推論を使う方法は ?
より詳細に見ていきます。
#1. 「Bedrock ガードレールではどのようなフィルタリングができますか ?」
- LLM の回答 : (長いので省略しますが、5 つのフィルターが挙げられていました)
- 正解の回答 : (長いので省略しますが、 6 つのフィルターを挙げる必要があります)
ドキュメントのあるページに 6 種類のフィルタリング機能がまとまっていて、これを答えてほしいのですが、そのページを拾ってくることができていませんでした。
#2. 「Claude 3.5 Haiku が使えるリージョンはどこですか ?」
- LLM の回答 : 「検索結果から、Claude 3 Haikuは us-east-1 (米国東部/バージニア北部) リージョンで使用可能であることが確認できます。」
- 正解の回答 : 「us-east-1, us-east-2, us-west-2 で利用でき、us-east-1, us-east-2 ではクリスクロスリージョン推論でのみ利用できます」
モデルとリージョンの対応表のページを取ってきて欲しいのですが、そこをとってくることができていません。別の部分の情報から us-east-1 が使えることはわかるため、そのことだけを回答しています。
#3. 「Knowledge Base のソースファイルのサイズ制限はありますか ?」
- LLM の回答 : 「はい、Knowledge Base のソースファイルには以下のサイズ制限があります:コンソールを使用する場合は最大 200MB、それ以外の場合は最大 500MB までのファイルサイズが許可されています。画像ファイルの場合は、別途以下の制限があります:最大ファイルサイズ:5MB, 最大解像度:8k」
- 正解の回答 : 「各ファイルのサイズは 50MB を超えてはいけません。また、JPEG と PNG の最大サイズは 3.75MB です。」
しかし、検索で Bedrock Data Automation という機能のクオータのページを持ってきてしまったことにより、間違えた回答をしてしまっています。
※これは回答のプロンプトが悪い側面もあります。確実な情報が取得できていない場合には、「わかりません」と回答させるべきです。ただし、どちらにしろ正答できていないことには変わりません。
他 2 件も同様なので省略します
検索失敗のパターンとして、以下の 2 つがありそうだということが見えてきました。
- 検索すべき箇所は概ね合っているが、ピンポイントで必要な場所を取ってこれていないパターン (#1)
- 検索すべき箇所が全く的外れのパターン (#2、#3)
Step 4. 考察とチャンキング検索の懸念
結果の分析をもとに改善案を考えます。5 件の回答がうまくいっていない原因は、端的に言えば検索がうまくいっておらず、とってきてほしい情報をとってこれていないことでした。これに対して改善案とその所感を挙げます。
- ハイブリッド検索を用いる
→ 検索結果を見ていると、キーワード検索を足すことでうまくいくケースではなさそうです。 - チャンキングを工夫する
→ やってみないとわからないですが、あまり関係ないように見えます。 - 検索数を増やす
→ 検索で得られている情報がそもそも少ないです。現在検索取得数を最大 5 件に設定していますが、ほとんどのケースで検索で得られる情報が 5 よりも少なかったので、効果はなさそうです。
※ 検索エンジンごと変えればこの性質が変わる可能性はありますが、少し面倒です。 - メタデータフィルタリング
→ 検索数が多ければフィルタリングが効きますが、そもそもが少ないので効果はなさそうです。 - 質問文からクエリを生成する部分を工夫する
→ うまくいくかもしれないですが、未知数です。どのようにクエリ生成させればいいかのアイデアがありません。 - Agentic RAG を導入する
→ うまくいくかもしれないですが、未知数です。複雑度が上がるので、もう少しシンプルなソリューションから考えたいです。
いくつか候補はありますが、これという解決方法ではありません。
ここで、人間が情報を探すときと同様に、2 ステップで探すのはどうだろうかというアイデアを思いつきました。
- まず目次から関連しそうな場所を見つける
- 目次の中から正確な関連情報を見つける
これを RAG に置き換えると、このようになります
- 目次を LLM に渡して関連しそうなページ番号を推測させる
- そのページの中身全部 LLM に渡して回答を作成してもらう
最近の LLM はかなり長い文章を入力することができますし、その中から必要な情報だけを抽出する能力も非常に高いです。そこで、関連しそうな文章を全部 LLM に入れて答えを探させてしまうという方法がうまくいくのではないかと考えました。
チャンキング検索の懸念点
ここで、RAG の最も典型的な手法として知られている「文章を小さく分割 (チャンキング) して、検索エンジンで検索して、LLM に渡す」という手法の懸念点を考えてみます。
1. 「文脈を喪失する」
例えば、次のような文章を考えてみましょう。この文章は、ある会社の有給休暇の規定に関する文章です。正社員の場合と、非正規雇用社員の場合で、場合分けして記述されています。
A社の有給休暇の規定に関する文章
1. 正社員の場合
....
**付与日数**
- 入社後6ヶ月継続勤務し、全労働日の8割以上出勤した場合、10日の年次有給休暇を付与します。
....
2. 非正規雇用社員の場合
**対象者**
パートタイム社員、契約社員、アルバイト等の非正規雇用社員も、法令に基づき年次有給休暇を取得できます。
....
**付与日数**
週所定労働日数または年間所定労働日数に応じて、以下のとおり付与します。
.....
この文章をチャンキングしようとすると次のようになる可能性があります。このようになった場合、「付与日数」の情報はチャンク2とチャンク4に入っていますが、どちらが正社員の情報かわからなくなってしまいます。
チャンク 1
1. 正社員の場合
....
チャンク 2
....
**付与日数**
- 入社後6ヶ月継続勤務し、全労働日の8割以上出勤した場合、10日の年次有給休暇を付与します。
....
チャンク 3
2. 非正規雇用社員の場合
**対象者**
パートタイム社員、契約社員、アルバイト等の非正規雇用社員も、法令に基づき年次有給休暇を取得できます。
....
チャンク 4
....
**付与日数**
週所定労働日数または年間所定労働日数に応じて、以下のとおり付与します。
.....
2. 網羅するタスクに弱い
例えば「Amazon Bedrock で使える Claude のモデルを全て挙げてください」というタスクを今回の RAG に依頼することを考えます。検索エンジンはこの質問に関連する情報を取ってきて LLM が回答するでしょうが、その情報で全てのモデルが網羅されているかどうかはわかりません。実際、結果の分析ステップで見たうまくいっていない回答の #1 と #2 は、一部の情報は答えられているが、網羅できていない回答になってしまいました。
このようなタスクにはチャンクを検索するという手法は合わないことがありそうです。
Step 5. 目次 RAG を試してみる
この思いついた手法(目次 RAG と名付けてみました)を試してみましょう。モデルは全て Claude 3.5 Sonnet v2 を用いています。流れは以下の通りです。
- 目次を LLM に渡して、ユーザーの質問に関連しそうなページ番号を推測させる
- そのページの中身全部 LLM に渡して回答を作成してもらう

1 : 目次を LLM に渡して関連しそうなページ番号を推測させる
AWS の開発者向けドキュメントには次のような目次がついています。これを渡して、ユーザーの質問がどこに該当するかを LLM に推測させます。
# What is Amazon Bedrock?: 1
- What can I do with Amazon Bedrock?: 1
- How do I get started with Amazon Bedrock?: 2
- Amazon Bedrock pricing: 3
- Key terminology: 3
# Getting started: 6
- Request access to an Amazon Bedrock foundation model: 9
- (Optional tutorials) Explore Amazon Bedrock features through the console or API: 10
- Getting started in the console: 10
- Explore the text playground: 11
- Explore the image playground: 11
- Getting started with the API: 12
- Install the AWS CLI or an AWS SDK: 12
- Get credentials to grant programmatic access to a user: 13
- Try out some Amazon Bedrock API requests: 14
- Run examples with the AWS CLI: 15
- Run examples with the AWS SDK for Python (Boto3): 17
- Run examples with a SageMaker AI notebook: 21
- Working with AWS SDKs: 24
# Access foundation models: 26
- Grant permissions to request access to foundation models: 26
- Add or remove access to foundation models: 29
利用したプロンプトはこのようなものです。何度か試行錯誤して、この形に落ち着きました。
<table_of_contents>
{{table_of_contents}}
</table_of_contents>
Bedrockのドキュメントの目次を渡しています。目次は# hogehogeが大項目、- fugafugaが小項目を表しています。
この目次を参考にユーザーのBedrockに関する質問がドキュメントのどの部分に該当するかを予想して、その範囲の最初と最後のページ番号を出力してください。
1. 目次を丁寧に読み、ユーザーの質問に該当しそうな項目の候補を全て挙げます
2. 適切な情報が含まれている可能性が高い順に候補をランクづけします
3. 合計のページ数が30ページ以内に収まるように、最終的なページの範囲を選定します
<output_format>
[[<範囲の最初のページ番号>, <範囲の最後のページ番号>],...]
</output_format>
<output_example>
[[2-10], [150-170]]
</output_example>
出力の前に<thinking>内でstep-by-stepで思考してください。
選んだ理由を<reason>内でで説明してください。
回答は<response>で囲んでください。
<question>
{{question}}
</question>
例えば、「 Bedrock ガードレールではどのようなフィルタリングができますか ?」の質問を入れてみるとこのような出力になります。必要な部分を適切に選定できている感じがします。この例では 20 ページで約 36,000 文字でした。
<thinking>
1. 関連しそうな項目を探す:
- "Stop harmful content in models using Amazon Bedrock Guardrails": 510-604
- "Components of a guardrail": 515
- "Content filters": 516
- "Filter classification and blocking levels": 518
- "Filter strength": 518
- "Sensitive information filters": 522
- "Word filters": 527
- "Block images with image content filter": 534
2. ランク付け:
1位: 510-604の範囲 - ガードレールの全体像と各フィルタリング機能の詳細説明
2位: 515-534の範囲 - フィルタリングの具体的な機能説明
3. ページ数の制限を考慮:
- 510-604は94ページあり、30ページの制限を超えています
- 515-534は19ページで、フィルタリング機能の核心部分をカバーしています
最適な範囲として515-534を選択します。
</thinking>
<reason>
515-534のページ範囲を選んだ理由:
1. ガードレールのフィルタリング機能に特化した説明が含まれている
2. コンテンツフィルター、センシティブ情報フィルター、ワードフィルター、画像フィルターなど、具体的なフィルタリング方法が網羅されている
3. 30ページの制限内に収まっている
4. フィルタリングの分類、ブロックレベル、強度など、実装に必要な技術的な詳細も含まれている
</reason>
<response>
[[515, 534]]
</response>
2 . そのページの中身を全部 LLM に渡して回答を作成してもらう
関係ない情報を含む数万数万文字という大量のドキュメントを入れても、質問に答えられるということをいくつかの質問で確認しました。プロンプトは極限までシンプルにしました。
<context>
{{context}}
</context>
コンテキストを参考にユーザーの質問に回答してください。質問への回答以外は出力しないでください。
コンテキストの内容から回答できない場合は、「与えられた情報からは回答できません」と回答してください
<question>
{{question}}
</question>
ステップ 1 、 ステップ 2 ともにいくつかの質問でうまく回答できていることが確認できたので、全質問で実行と評価を回してみます。
結果 :
2 点 : 17/18 (94%)
1 点 : 0/18
0 点 : 1/18
先ほどの実験よりかなり結果が良くなっています。
余談 :
実は、LLM-as-a-Judge の評価結果では 1 点の回答が 3 つあったのですが、私が目で見て確認したところ、正解 (2 点) として問題ないと判断をしました。私が評価のために設定したプロンプトだと LLM の評価が厳しすぎて、正解に含まれている要素が1つでも抜けていたら 1 点にしてしまっています。しかし、重要な要素が含まれていれば正解としていいというケースもありますので、この評価方法は改善の余地があります。
Step 6. 考察
唯一 0 点となってしまった質問は「Claude 3.5 sonnetのモデル ID はなんですか ?」です。これは逆に Amazon Bedrock Knowledge Bases を用いた手法では正解できていた質問ですが、今回の手法では回答が難しい問題です。
というのも目次が次の画像のようになっているのですが、正解に必要な情報は “Supported foundation models” の項目にあります。しかし、何も知らない人が見たらこの項目にモデル ID の情報があると気づくことは難しいでしょう。実際 LLM は 152 ページの “Anthropic Claude models” と 273 ページの “Anthropic Claude3 models” の項目に答えがあると判断しています。
このように、目次から判別が難しい場所に情報が入っているケースは不得意なようです
ベクトル検索と両方を利用することで、双方のメリットを活かして、今回の評価データセットでも満点を取れるかもしれません。

このようにきれいな目次があるドキュメントでしか使えないというのがこの手法の限界です。技術ドキュメントは綺麗な目次が用意されていることが多い上に、正しい情報が求められるため、この手法が使えるケースが多いかもしれません。また、私は専門外ですが、法律・医療・金融なんかのきちんとした文章にも適用できるかもしれません。
また、レイテンシーが大きいというのもこの手法のデメリットです。LLM が 2 段階になっていることと、インプットの情報量が多くなっていることがその原因です。
逆に、この手法のいいところは、LLM の進化のスピードに合わせて手法が改善されていく可能性があることです。最近は LLM の進化のスピードが早いので、新しいモデルに切り替えるだけで性能が改善される可能性があるのはメリットです。
まとめ
長々と書いてきましたが、本記事で伝えたいことは2つです。
- RAG の改善はまず結果の分析からしましょう。
- ベクトル検索やチャンキング検索には限界もあるため、柔軟に手法を選びましょう。
本記事で提案している目次 RAG については、まだまだ少数のデータでしか検証していないですし、すでに限界も見えてきています。この手法の有効性を示したいわけではありません。 RAG の改善の過程をお見せして、場合によってはチャンキング検索に囚われなくてもいいということを示すことが本記事の目的でした。
この連載の続きで、もっといろいろな手法を試していく予定です。乞うご期待 !!
筆者プロフィール

近藤 健二郎 (Kondo Kenjiro / @yakitori_eng)
アマゾンウェブサービスジャパン 合同会社
AI/ML スペシャリスト ソリューションアーキテクト
スノーボード、検索、レコメンドが好き。
最近は RAG にハマっている。
冬はスノーボードとその後の温泉のことのみ考えている。
AWS を無料でお試しいただけます