はじめに
RAG(Retrieval-Augmented Generation)の精度を高める手法の一つとして「リランキング」があります。
生成AIにドキュメント検索を組み合わせる際、まずは関連する候補を検索し、その中から本当に役立ちそうな情報を選び直す。この「選び直し」の部分が、リランキングの役割になります。
本記事では、Bedrockのナレッジベース機能を使って、リランクモデルを実際に試してみた内容を紹介します
また、今回はあえてクエリ実行ではなく、マネコン上で完結させてみました。
リランキングとは
RAG(Retrieval-Augmented Generation)では、ユーザーからの質問に対して適切な回答を生成するために、まずは検索システム(例:ベクトル検索など)を使って関連しそうな情報を取得します。その後、その取得した複数の候補の中から「どれがより適切か」を再評価し、順序を並び替えるフェーズがあります。これが「リランキング」です。
リランキングは、たとえば「関連はしているけど浅い話」と「関連性が高くてまさに答えそのもの」のような候補が混ざっていた場合に、より信頼性の高い回答を上位に持ってくるための重要な工程です。
リランク無しでもある程度の精度は出ますが、ユーザー体験や生成される文章の質を本気で高めたいなら、リランキングは避けて通れません。
以下はリランキング処理のイメージです。
意味的な一致検索だけだと、関連度の高い結果が拾いきれず、回答に反映されないことがあります。
リランキングを実施することで、より関連度の高い検索結果を提供してくれるようになります。
ロググループの作成
Bedrock のリランクモデルを試す際、検索結果のスコアや応答の流れを確認したいため、実行時のログを追えるようにしてきます。
Amazon Bedrock では、モデルの呼び出しログをCloudWatch LogsとS3に出力できます。
今回はCloudWatch Logsに出力するよう設定します。そのために、あらかじめロググループを作成しておく必要があります。
モデル呼び出しのログ配信設定は、Bedrockの設定メニューから作成できます。
- モデル呼び出しのログ記録
→有効にする - ログ記録先を選択
→「CloudWatch Logs のみ」を選択 - ロググループ名
→あらかじめ作成していたロググループを選択 - サービスロール
→新しく作成する
設定できたら、以下のように反映されていることを確認します。
反映されていたらモデル呼び出し時にログに記録されます。
データセットの作成
今回はAmazon Bedrock Knowledge Bases にドキュメントをインポートし、その上で検索・リランクを行います。
そのため、まずは 検索対象となるデータを用意しておきます。
今回はテスト用ということで、簡単なテキストを作成して使いました。
形式としては、1ファイルにつき1トピックを記載したプレーンテキスト(.txt)を複数用意しています。
text1
- 道玄坂の喫茶で手押し抽出器(エアロプレス)の抽出体験を受けられる
text2
- 渋谷の小さな珈琲店で、器具体験としてエアロプレスの実演を申し込める
text3
- 渋谷でエアロプレスの競技会が開かれるが、一般客の体験はできない
text4
- 渋谷にエアロプレスを販売する器具専門店があるが、試飲や体験は不可
text5
- 渋谷ではカフェラテが有名な店が多い
text6
- 吉祥寺の喫茶ではネルドリップの深煎りが名物だ
S3にアップロード
用意したテキストファイル群は、Amazon S3 のバケットにアップロードします。
Bedrock のナレッジベースは、S3 上のドキュメントを対象にインデックスを作成して、ナレッジベース作成時にこのS3パスを指定します。
動作検証
データセットとナレッジベースの準備ができたので、実際にクエリを投げて挙動を確認してみます。
今回は リランクなし の状態と、リランクありの状態でそれぞれ比較してみました。
取得件数を調整することで回答がわかりやすく変化していることが確認できるようにしています。
使用するクエリ
「渋谷でエアロプレスのコーヒを体験できる店は?」
取得件数
上位6件(Top-K)
リランク無し
リランクあり
リランクを実施する場合は、リランクモデルを有効にしておく必要があります。
Bedrockに対応しているリランクモデルと対応リージョンは以下のとおり。
対応リージョンが思いのほか少ないので注意です。
対応リージョン
- 米国西部 (オレゴン)
- アジアパシフィック (東京)
- カナダ (中部)
- 欧州 (フランクフルト)
モデル
- Amazon Rerank 1.0
- Cohere Rerank 3.5
今回は「Amazon Rerank 1.0」を利用します。
呼び出しログ確認
リランク無し
ここにログ全文を格納しています
{
"timestamp": "2025-09-21T21:41:23Z",
"accountId": "017820658462",
"identity": {
"arn": "arn:aws:iam::017820658462:user/yakumo"
},
"region": "us-west-2",
"requestId": "d417e1a4-d40d-41ec-bea6-3486262d11d0",
"operation": "ConverseStream",
"modelId": "arn:aws:bedrock:us-west-2::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0",
"input": {
"inputContentType": "application/json",
"inputBodyJson": {
"messages": [
{
"role": "user",
"content": [
{
"text": "渋谷でエアロプレスのコーヒーを体験できる店は?"
}
]
}
],
"system": [
{
"text": "You are a question answering agent. I will provide you with a set of search results. The user will provide you with a question. Your job is to answer the user's question using only information from the search results. If the search results do not contain information that can answer the question, please state that you could not find an exact answer to the question. Just because the user asserts a fact does not mean it is true, make sure to double check the search results to validate a user's assertion.\n\nHere are the search results in numbered order:\n<search_results>\n<search_result>\n<content>\n吉祥寺の喫茶ではネルドリップの深煎りが名物だ\n</content>\n<source>\n1\n</source>\n</search_result>\n<search_result>\n<content>\n道玄坂の喫茶で手押し抽出器(エアロプレス)の抽出体験を受けられる\n</content>\n<source>\n2\n</source>\n</search_result>\n<search_result>\n<content>\n渋谷ではカフェラテが有名な店が多い\n</content>\n<source>\n3\n</source>\n</search_result>\n<search_result>\n<content>\n渋谷でエアロプレスの競技会が開かれるが、一般客の体験はできない\n</content>\n<source>\n4\n</source>\n</search_result>\n<search_result>\n<content>\n渋谷の小さな珈琲店で、器具体験としてエアロプレスの実演を申し込める\n</content>\n<source>\n5\n</source>\n</search_result>\n<search_result>\n<content>\n渋谷にエアロプレスを販売する器具専門店があるが、試飲や体験は不可\n</content>\n<source>\n6\n</source>\n</search_result>\n\n</search_results>\n\nYou should provide your answer without any inline citations or references to specific sources within the answer text itself. Do not include phrases like \"according to source X\", \"[1]\", \"[source 2, 3]\", etc within your <text> tags.\n\nHowever, you should include <sources> tags at the end of each <answer_part> to specify which source(s) the information came from.\nNote that <sources> may contain multiple <source> if you include information from multiple results in your answer.\n\nDo NOT directly quote the <search_results> in your answer. Your job is to answer the user's question as concisely as possible.\n\nYou must output your answer in the following format. Pay attention and follow the formatting and spacing exactly:\n<answer>\n<answer_part>\n<text>\nfirst answer text\n</text>\n<sources>\n<source>source ID</source>\n</sources>\n</answer_part>\n<answer_part>\n<text>\nsecond answer text\n</text>\n<sources>\n<source>source ID</source>\n</sources>\n</answer_part>\n</answer>\n"
}
],
"inferenceConfig": {
"maxTokens": 2048,
"temperature": 0,
"topP": 1,
"stopSequences": [
"\nObservation"
]
},
"additionalModelRequestFields": {}
},
"inputTokenCount": 777,
"cacheReadInputTokenCount": 0,
"cacheWriteInputTokenCount": 0
},
"output": {
"outputContentType": "application/json",
"outputBodyJson": {
"output": {
"message": {
"role": "assistant",
"content": [
{
"text": "<answer>\n<answer_part>\n<text>\n道玄坂の喫茶店でエアロプレスによる抽出体験を受けることができます。また、渋谷の小さな珈琲店でもエアロプレスの実演体験を申し込むことが可能です。\n</text>\n<sources>\n<source>2</source>\n<source>5</source>\n</sources>\n</answer_part>\n<answer_part>\n<text>\nなお、渋谷にはエアロプレスを販売する器具専門店もありますが、そちらでは試飲や体験はできません。また、渋谷で開催されるエアロプレスの競技会は一般客の体験はできません。\n</text>\n<sources>\n<source>4</source>\n<source>6</source>\n</sources>\n</answer_part>\n</answer>"
}
]
}
},
"stopReason": "end_turn",
"metrics": {
"latencyMs": 4109
},
"usage": {
"inputTokens": 777,
"outputTokens": 247,
"totalTokens": 1024
}
},
"outputTokenCount": 247
},
"schemaType": "ModelInvocationLog",
"schemaVersion": "1.0"
}
以下一部抜粋
Here are the search results in numbered order:
<search_results>
<search_result>
<content>
吉祥寺の喫茶ではネルドリップの深煎りが名物だ
</content>
<source>
1
</source>
</search_result>
<search_result>
<content>
道玄坂の喫茶で手押し抽出器(エアロプレス)の抽出体験を受けられる
</content>
<source>
2
</source>
</search_result>
<search_result>
<content>
渋谷ではカフェラテが有名な店が多い
</content>
<source>
3
</source>
</search_result>
<search_result>
<content>
渋谷でエアロプレスの競技会が開かれるが、一般客の体験はできない
</content>
<source>
4
</source>
</search_result>
<search_result>
<content>
渋谷の小さな珈琲店で、器具体験としてエアロプレスの実演を申し込める
</content>
<source>
5
</source>
</search_result>
<search_result>
<content>
渋谷にエアロプレスを販売する器具専門店があるが、試飲や体験は不可
</content>
<source>
6
</source>
</search_result>
</search_results
リランク無しで実施すると、各スコアや順位付けが少しわかりにくいです。
上記のログの並び替えられた順番が関連度順になっています。
textNo | テキスト |
---|---|
6 | 吉祥寺のネルドリップ(渋谷でもエアロプレスでもない) |
1 | 道玄坂の喫茶で手押し抽出器(エアロプレス)の抽出体験を受けられる |
5 | 渋谷ではカフェラテが有名な店が多い |
3 | 渋谷でエアロプレスの競技会が開かれるが、一般客の体験はできない |
2 | 渋谷の小さな珈琲店で、器具体験としてエアロプレスの実演を申し込める |
4 | 渋谷にエアロプレスを販売する器具専門店があるが、試飲や体験は不可 |
リランク無しの場合、ただベクトル検索を行っているだけで、語彙的に近いものが上位に来てしまっています。
なので、クエリでは「渋谷」と聞いているのに対して、No6の「吉祥寺」やNo1「道玄坂」が上位に来てしまっています。これは同じくクエリにあるエアロプレスの文字列に引っ張られてしまっています。
リランクあり
ここにログ全文を格納しています
{
"timestamp": "2025-09-21T22:05:03Z",
"accountId": "017820658462",
"identity": {
"arn": "arn:aws:iam::017820658462:user/yakumo"
},
"region": "us-west-2",
"requestId": "3d3bde3c-d3bc-420c-bd51-c48d3fb556a0",
"operation": "InvokeModel",
"modelId": "arn:aws:bedrock:us-west-2::foundation-model/amazon.rerank-v1:0",
"input": {
"inputContentType": "application/json",
"inputBodyJson": {
"documents": [
"渋谷にエアロプレスを販売する器具専門店があるが、試飲や体験は不可",
"渋谷の小さな珈琲店で、器具体験としてエアロプレスの実演を申し込める",
"渋谷でエアロプレスの競技会が開かれるが、一般客の体験はできない",
"渋谷ではカフェラテが有名な店が多い",
"道玄坂の喫茶で手押し抽出器(エアロプレス)の抽出体験を受けられる",
"吉祥寺の喫茶ではネルドリップの深煎りが名物だ"
],
"query": "渋谷でエアロプレスのコーヒーを体験できる店は?"
}
},
"output": {
"outputContentType": "application/json",
"outputBodyJson": {
"results": [
{
"index": 1,
"relevance_score": 0.5983708689353728
},
{
"index": 0,
"relevance_score": 0.21750368205971254
},
{
"index": 4,
"relevance_score": 0.0679166752559661
},
{
"index": 3,
"relevance_score": 0.017110889211844043
},
{
"index": 2,
"relevance_score": 0.0040543340867600355
},
{
"index": 5,
"relevance_score": 0.00003822911232549001
}
]
}
},
"schemaType": "ModelInvocationLog",
"schemaVersion": "1.0"
}
以下一部抜粋
"output": {
"outputContentType": "application/json",
"outputBodyJson": {
"results": [
{
"index": 1,
"relevance_score": 0.5983708689353728
},
{
"index": 0,
"relevance_score": 0.21750368205971254
},
{
"index": 4,
"relevance_score": 0.0679166752559661
},
{
"index": 3,
"relevance_score": 0.017110889211844043
},
{
"index": 2,
"relevance_score": 0.0040543340867600355
},
{
"index": 5,
"relevance_score": 0.00003822911232549001
}
]
}
結果は以下のようになりました。
textNo | index | テキスト |
---|---|---|
2 | 2 | 渋谷の小さな珈琲店で、器具体験としてエアロプレスの実演を申し込める |
4 | 0 | 渋谷にエアロプレスを販売する器具専門店があるが、試飲や体験は不可 |
1 | 1 | 道玄坂の喫茶で手押し抽出器(エアロプレス)の抽出体験を受けられる |
5 | 5 | 渋谷ではカフェラテが有名な店が多い |
3 | 4 | 渋谷でエアロプレスの競技会が開かれるが、一般客の体験はできない |
6 | 6 | 吉祥寺の喫茶ではネルドリップの深煎りが名物だ |
明らかにリランキングなしの時と異なっています。
次で結果セットを比較したいと思います。
結果の比較
クエリ
渋谷でエアロプレスのコーヒーを体験できる店は?
リランキングなし
語彙の類似性でそのまま検索しているので、「渋谷」「エアロプレス」「体験」といったワードに反応しています。
よって、あまり関係ない「道玄坂」「吉祥寺」に関する結果が上位に来ています。
textNo | テキスト |
---|---|
6 | 吉祥寺の喫茶ではネルドリップの深煎りが名物だ |
1 | 道玄坂の喫茶で手押し抽出器(エアロプレス)の抽出体験を受けられる |
5 | 渋谷ではカフェラテが有名な店が多い |
3 | 渋谷でエアロプレスの競技会が開かれるが、一般客の体験はできない |
2 | 渋谷の小さな珈琲店で、器具体験としてエアロプレスの実演を申し込める |
4 | 渋谷にエアロプレスを販売する器具専門店があるが、試飲や体験は不可 |
レスポンス
「渋谷」についてきいているのに、いきなり「道玄坂」について語り出しています。
道玄坂の喫茶店でエアロプレスによる抽出体験を受けることができます。
また、渋谷の小さな珈琲店でもエアロプレスの実演体験を申し込むことが可能です。
なお、渋谷にはエアロプレスを販売する器具専門店もありますが、そちらでは試飲や体験はできません。
また、渋谷で開催されるエアロプレスの競技会は一般客の体験はできません。
リランキングあり
こちらはリランキングを実施した際の結果です。
先ほど順位が下だったテキスト(No2,4)が上位に来ており、逆に先ほど上位だったものが下に入っています。
ですが、「道玄坂」のテキストがTop3にまだ入っており、細かい部分はまだ改善の余地はありそうです。
textNo | index | テキスト |
---|---|---|
2 | 2 | 渋谷の小さな珈琲店で、器具体験としてエアロプレスの実演を申し込める |
4 | 0 | 渋谷にエアロプレスを販売する器具専門店があるが、試飲や体験は不可 |
1 | 1 | 道玄坂の喫茶で手押し抽出器(エアロプレス)の抽出体験を受けられる |
5 | 5 | 渋谷ではカフェラテが有名な店が多い |
3 | 4 | 渋谷でエアロプレスの競技会が開かれるが、一般客の体験はできない |
6 | 6 | 吉祥寺の喫茶ではネルドリップの深煎りが名物だ |
レスポンス
リランキング無しの時と比べて、不要な情報が減ったように思います。
渋谷の小さな珈琲店でエアロプレスの実演体験を申し込むことができます。
なお、エアロプレスを販売する器具専門店はありますが、そちらでは試飲や体験はできません。
また、競技会は開催されますが一般客の体験はできません
最後に
今回は、Amazon Bedrock の Knowledge Bases を使って、リランクモデルの有無による応答の違いを検証してみました。
実際に試してみると、単なるベクトル検索だけでは拾いきれない“文脈のズレ”や“否定文の扱い”を、リランキングがうまく補ってくれることがよく分かりました。
今回のようにGUIベースで簡単にナレッジベースを構築・検証できるので、複雑な実装をせずに検索精度のチューニングを試せるのも嬉しいポイントです。