導入
検索がうまくいかないとき、原因は文書側だけでなく質問の書き方にあることも多いです。ユーザーが打ち込んだそのままの質問が、検索にとって都合の良い形とは限りません。そこでLLM自身に質問側を整えさせてから検索する、という発想のテクニックがあります。
説明
- クエリ拡張(Query Expansion):元の質問に同義語や関連語を足してから検索します。「有給休暇」で検索するなら「年次有給休暇 有給 休暇制度」のように語彙を増やし、語彙ミスマッチによる取りこぼしを減らします。
- Multi-Query:1つの質問をLLMに複数の言い換えとして生成させ、それぞれで検索した結果をまとめて統合します。1回の検索では拾いきれない文書も、視点を変えた複数回の検索なら拾える可能性が上がります。
- HyDE(Hypothetical Document Embeddings):質問に対して「もしこの質問に完璧に答えるとしたら、どんな文章になるか」という仮の答えをLLMに書かせ、その仮の答えのベクトルで検索します。質問文と回答文は言い回しが違うことが多いですが、「答えは答えに似ている」という発想で、仮の答え同士の近さを使うことで検索精度を上げます。
- query rewriting(クエリの書き換え):第4章レッスン17で扱った「会話の文脈を踏まえて質問を独立した形に畳む」処理も、広い意味ではクエリ変換の一種です。「それについてもっと詳しく」のような文脈依存の質問を、単独で検索できる形に直してから検索に渡します。
flowchart TD q["元の質問"] --> exp["クエリ拡張<br/>同義語・関連語を追加"] q --> multi["Multi-Query<br/>複数の言い換えを生成"] q --> hyde["HyDE<br/>仮の答えをLLMに書かせる"] q --> rewrite["query rewriting<br/>会話の文脈を1つのクエリに畳む"] exp --> search["検索"] multi --> search hyde --> search rewrite --> search search --> merge["結果をまとめて統合し生成へ"]
Multi-Queryの考え方を、擬似コードで示します(実行はしません)。
# Multi-Query の擬似コード(掲示のみ)
def multi_query_search(question, llm, retriever, n=3):
# ① LLMに question の言い換えを n 個作らせる
variants = llm.generate_variants(question, n=n)
variants.append(question) # 元の質問も忘れずに含める
# ② それぞれの言い換えで検索し、結果をまとめる
all_hits = []
for q in variants:
all_hits.extend(retriever.search(q, k=3))
# ③ 重複を除いて返す(同じ文書が複数の言い換えでヒットすることも多い)
return list(dict.fromkeys(all_hits))
いずれの手法も、共通しているのは「検索する前に、LLMをもう1回使って質問側を整える」という点です。LLM呼び出しの回数は増えるぶんコストと時間がかかりますが、検索の取りこぼしを減らす効果は大きく、実務のRAGでもよく組み込まれています。
まとめ
- クエリ拡張は同義語・関連語を足して語彙ミスマッチを減らす。
- Multi-Queryは質問を複数の言い換えに増やし、それぞれの検索結果を統合する。
- HyDEは「仮の答え」を作ってそのベクトルで検索する、「答えは答えに似ている」という発想。
- query rewritingは会話の文脈を独立したクエリに畳む処理(第4章レッスン17の続き)。