LLM Frontline
開発・エージェント

RAGのクエリ書き換え(query rewriting)|マルチクエリ・分解・HyDEの使い分けと評価

ミナト・開発・API担当
・ 約22分で読めます
RAGのクエリ書き換え(query rewriting)|マルチクエリ・分解・HyDEの使い分けと評価

RAGの精度が出ないとき、埋め込みモデルやチャンク分割、リランカーに手を入れる前に、もう一つ見るべき場所があります。検索に投げている「質問そのもの」です。人が入力する質問は、言葉足らずだったり、指示語で前の話を受けていたり、社内文書では使われない言い回しだったりします。そのまま検索に流せば外れて当然です。クエリ書き換え(query rewriting / query transformation)は、検索の前段でクエリを整える工夫の総称です。代表的な手法の向き不向き、追加で払うレイテンシとコスト、そして「入れたら悪化した」を検知する評価のやり方までを整理します。

なぜ生クエリのままでは外すのか

RAGの検索は質問と文書の距離を測る処理なので、質問の書かれ方に結果が強く左右されます。実際のユーザー入力には、検索の相手として不利な性質がいくつも混ざっています。言葉が足りない(「あれ、いくらでしたっけ」)、語彙がズレる(ユーザーは「解約」、規程は「契約の終了手続」)、複数の問いが一文に詰まっている(「AとBの違いと、Bを選んだ場合の申請手順」)、指示語が前の発話を指す(「その条件は?」)といった具合です。どれも、そのままでは1本のクエリとして成立しません。

LangChainのドキュメントでは、2ステップRAGとエージェント型RAGの中間に位置づけられるHybrid RAGパターンの典型的な構成要素として「クエリの改善(query enhancement)」が挙げられ、曖昧なクエリの書き換え、複数のバリエーションの生成、追加の文脈によるクエリの拡張という3つが示されています。RAG一般の構成要素として書かれているわけではありませんが、質問を直すこと自体を検索パイプラインの一段として扱う設計例です。

「検索器を替えても効かなかったのに、質問文をこちらで整えてから投げたら急に当たるようになった」という経験は珍しくありません。原因が検索側ではなく入力側にあることもあります。
社内ナレッジ検索を運用するエンジニア

書き換えはパイプラインのどこに入るのか

RAGの基本形は、質問を受けて検索し(retrieve)、取れた文書を根拠に生成する(read/generate)という流れです。クエリ書き換えは、この検索の手前にもう一段を挿し込みます。arXivの「Query Rewriting for Retrieval-Augmented Large Language Models」(arXiv:2305.14283)は、この構成をretrieve-then-readに対するRewrite-Retrieve-Readとして定式化し、入力テキストと必要な知識との間には必ずギャップがあるとして、検索クエリ自体の適応に注目しました。同論文はさらに、ブラックボックスのLLMリーダーに合わせて小型言語モデルをリライタとして用意し、リーダーからのフィードバックで強化学習する構成も提案しています。

実務では学習済みのリライタを用意するところまでは作り込まず、汎用LLMへのプロンプト1回で書き換えることが多いはずです。重要なのは位置づけで、クエリ書き換えは他の改善策と役割が違います。

レイヤー何を直すか代表的な手段
索引作成(pre-index)検索される側の形チャンク分割、メタデータ付与、パース
クエリ変換(pre-retrieval)検索に投げる入力書き換え、マルチクエリ、分解、HyDE
取得(retrieval)拾い方ベクトル検索、BM25、ハイブリッド検索
取得後(post-retrieval)並べ替えと絞り込みリランキング、重複排除、圧縮

Weaviateの解説では、クエリ書き換えは埋め込みモデルやLLMのファインチューニングと比べて投資が小さく見返りの大きい手法に分類されています。既存の索引や検索器に触らず、前段を1つ足すだけで試せるのが利点です。

あわせて読みたい

RAGとは何か|仕組み・向き不向き・導入判断の考え方

代表的な6つの手法

1. 書き換え(明確化・専門用語への正規化)

もっとも素直なやり方です。ユーザーの入力を1回LLMに通し、曖昧さを取り除き、対象コーパスで実際に使われている用語に寄せたクエリを1本作ります。「解約したい」を「契約の解除手続き 中途解約 違約金」のように索引側の語彙へ翻訳するイメージで、誤字の訂正や同義語の追加もここに含まれます。

商用の検索サービスにも同種の機能があります。Azure AI Searchのクエリ書き換え機能は、検索サービスがクエリを生成モデルに送って代替クエリを作らせ、元のクエリと書き換えたクエリの両方を使って結果を取得する仕組みです(公式ドキュメント上はプレビュー段階と明記されています)。誤字の訂正と同義語によるクエリ拡張が目的として明記され、"queryRewrites":"generative|count-5"のように書き換え数を指定し(1から10)、セマンティックランカーの有効化とqueryLanguageの指定が前提になっています。前提条件としてクエリ書き換えを提供するリージョンのAzure AI Searchであることが求められ、利用できるリージョンは限られます。公式ドキュメントのREST例もプレビューのapi-versionを指定しています。

2. マルチクエリ(複数の言い換えを並列検索して統合)

1本に絞らず、言い換えを複数生成して並列に検索し、結果を1つのリストへ統合します。各クエリが元の質問を別々の角度から捉えるため、単一のクエリでは届かなかった文書が拾える確率が上がります。

統合にはRRF(Reciprocal Rank Fusion)がよく使われます。RAG-Fusionを扱った論文(arXiv:2402.03367)は、複数クエリの生成とRRFによる統合を組み合わせる手法として整理しています。同論文は単一企業(Infineon)の製品情報検索を対象に回答をaccuracy/relevance/comprehensivenessの観点で人手評価した事例報告であり、ベンチマークによる定量評価ではありませんが、生成されたクエリが元の質問をさまざまな観点から文脈づけることで正確で網羅的な回答が得られた一方、生成クエリと元の質問の関連が不十分な場合には回答が本題から逸れることがあった、と報告しています。恩恵と副作用がセットで出る手法です。

メモ

RRFは順位だけを使うためスコアのスケールに依存しません。ハイブリッド検索の統合と同じ性質で、マルチクエリにもそのまま流用できます。詳細は下の記事で扱っています。

あわせて読みたい

ハイブリッド検索とは|キーワード検索(BM25)とベクトル検索を組み合わせてRAGの取りこぼしを減らす

3. クエリ分解(サブクエリ化)

1つの質問に複数の問いが含まれている場合、それを個別に答えられるサブクエリへ割ります。LlamaIndexのクエリ変換のドキュメントでは、初期クエリをより答えやすい形へ変換する単段の分解と、前の回答を踏まえて次のサブ質問を作る多段の分解が示されています。クラス名とコード例まで示されているのは多段側で、StepDecomposeQueryTransformをMultiStepQueryEngineに渡す構成です(単段の分解は概要の箇条書きで名前が挙がるだけです)。Sub Question Query Engineは、複雑なクエリを複数のサブ質問と実行先のクエリエンジンに分解し、すべて実行してから応答を合成する仕組みです。別ページのQ&A patternsでは、この仕組みは任意のサブクエリを任意のツールに対して実行できるため、複数文書にまたがる比較・対比のクエリに特に適していると位置づけられています。

マルチクエリとの違いは目的です。マルチクエリは同じ問いの言い換えを並べて再現率を上げますが、分解は異なる問いに分けて答えを集めてから統合します。「AとBを比較して」は言い換えを増やしても解けず、分けて引く必要があります。

4. HyDE(仮想文書を生成して埋め込む)

質問は短い疑問文、文書は説明的な平叙文で、そもそも形が違います。この非対称を埋めるのがHyDE(Hypothetical Document Embeddings)で、質問に対してまずLLMに「答えらしき仮想の文書」を書かせ、その仮想文書を埋め込んで近傍検索します。提案論文(arXiv:2212.10496)は、生成された文書が関連性のパターンを捉える一方で実在せず誤った詳細を含みうることを認めたうえで、エンコーダの密なボトルネックが作り話をふるい落とし、生成物を実在のコーパスに接地させると説明しています。

近い発想に、few-shotプロンプトで擬似文書を生成して元のクエリに連結するquery2doc(arXiv:2303.07678)があります。同論文の要旨では、MS MARCOやTREC DLでBM25の性能が3%から15%改善したと報告されています。なお、LlamaIndexの公式ドキュメントに実装例があるのはHyDEのほうで、HyDEQueryTransformとして提供されています(同ドキュメントにquery2docに対応するモジュールの記載はありません)。

# 出典: LlamaIndex公式ドキュメント「Query Transformations」の例をもとに、一部を現行の推奨APIに合わせています
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.core.indices.query.query_transform.base import HyDEQueryTransform
from llama_index.core.query_engine import TransformQueryEngine

documents = SimpleDirectoryReader("../paul_graham_essay/data").load_data()
index = VectorStoreIndex.from_documents(documents)

hyde = HyDEQueryTransform(include_original=True)
query_engine = index.as_query_engine()
query_engine = TransformQueryEngine(query_engine, query_transform=hyde)
response = query_engine.query("what did paul graham do after going to RISD")

注意

HyDEはLLMに事実を書かせる手法です。モデルが知らない領域(社内の固有ルールや新しい製品仕様)では、もっともらしい嘘の仮想文書でそのまま検索してしまいます。上の例のinclude_original=True(既定値もTrue)のように、元クエリも併用して仮想文書だけに賭けない構成が安全側です。

5. 会話履歴を踏まえた指示語の解決

チャットUIでは、直近の発話を受けた省略や指示語が当たり前に出ます。「その場合の上限は?」を単独で検索しても意味がないため、会話履歴と最新メッセージから単体で成立する質問文(standalone question)を作り直してから検索します。LlamaIndexのCondense Question Modeは、会話の文脈と最後のメッセージから独立した質問を生成し、その凝縮した質問でクエリエンジンに問い合わせる2段構成として説明されています。

同ドキュメントは限界も明示しており、この方式は単純でナレッジベースに直接関係する質問には有効だが、常にナレッジベースへ問い合わせるため「さっき何を聞いた?」のようなメタな質問には答えづらい、とされています。会話の管理と検索の入力作りは別問題だという線引きが要ります。

6. メタデータフィルタの抽出

「2024年度の営業部の規程で」のような条件は、意味的な近さではなく構造化されたメタデータで絞るほうが確実です。自然文からフィルタ条件を抜き出し、検索クエリ本体とフィルタに分けて投げます。LlamaIndexのAuto-Retrievalでは、コレクションの構造化された説明と対応するメタデータフィルタを記述したVectorStoreInfoを渡し、自然文クエリからLLMがメタデータフィルタと適切なクエリ文字列(どちらも空になりうる)を推論して、そのクエリバンドルをベクトルDBに対して実行する、と説明されています。単純なtop-kの意味検索を超えた検索が可能になるという位置づけです。

他の5つと違い、この手法は「意味検索の当たり外れ」ではなく「そもそも検索対象を間違えている」問題に効きます。日付・部署・文書種別で母集団を切れるなら、書き換えを凝るより先に検討する価値があります。

手法の比較

手法効く場面追加のLLM呼び出し検索回数主な失敗モード
書き換え(明確化・用語正規化)語彙のズレ、言葉足らず、誤字1回1回意図の変質、固有名詞の欠落
マルチクエリ表現の揺れが大きい、再現率を上げたい1回(n本を一括生成)n回ノイズ増、統合コスト、逸脱したクエリ
クエリ分解複数の問い、比較、多段の質問1回以上(多段なら反復)サブクエリ数ぶん分解の失敗、レイテンシの膨張
HyDE質問と文書の形が非対称、ゼロショット領域1回(生成は長め)1回仮想文書のハルシネーション
会話履歴の解決チャットUI、指示語・省略1回1回文脈の取り違え、メタ質問に弱い
メタデータ抽出日付・部署・種別など条件が明確1回1回誤抽出で0件、過剰な絞り込み

ヒント

6つは排他ではありません。実務では「会話履歴の解決 -> メタデータ抽出 -> 書き換え」のように直列で組むことが多く、LLM呼び出しが積み上がります。全部盛りにせず1つずつ足して測るほうが、切り分けが楽です。

追加レイテンシとコストをどう見積もるか

対価はほぼ必ずレイテンシとして表れます。検索の手前にLLM呼び出しが1回入るので、その生成時間がまるごと体感時間に乗ります。HyDEのように段落単位の文章を書かせる手法は出力トークンが多く、待ち時間が伸びます。

マルチクエリと分解では、検索そのものの回数も増えます。5本のクエリを投げれば検索回数は5倍になり、統合前の候補件数も膨らみます。並列化すれば壁時計時間は抑えられますが、レート制限に当たりやすくなり、費用は減りません。

コスト面では、書き換え用のLLMを本体の生成モデルと分けられるかが分かれ目です。書き換えは分類や整形に近い軽い仕事なので小さく速いモデルで足りることが多く、回答生成と同じ大型モデルを使い回すと費用対効果は悪くなりがちです。

注意

対話UIでは、書き換えのぶんだけ最初の1文字が出るまでの時間が遅れます。生成のストリーミングで見かけのレイテンシを隠せても、検索前の処理は隠せません。応答の速さが体験を左右する用途では、精度の伸びと遅延の増加を並べて判断してください。

あわせて読みたい

LLMアプリの「遅い」を設計で潰す|TTFT・生成速度・総所要時間を分けて考えるレイテンシ設計

失敗モードを先に知っておく

クエリ書き換えは壊れ方に癖があり、先に知っておくと検知の仕組みを用意できます。

1つ目は意図の変質です。LLMが質問を勝手に一般化したり前提を足したりして、聞きたかったこととズレた検索が走ります。「返品できるか」が「返品ポリシーの概要」に書き換わり、個別条件の記載を外す、といった具合です。

2つ目は固有名詞や識別子の破損です。Azure AI Searchのドキュメントには、書き換えられたクエリは元のクエリが持っていた正確な語をすべて含むとは限らず、一意の識別子や製品コードの完全一致を必要とする具体的なクエリでは検索結果に影響しうる、と明記されています。型番やエラーコードを含む質問は、最も価値のある語が消える危険があります。

3つ目はハルシネーションによる誘導で、HyDEやquery2doc系で特に注意が要ります。仮想文書に含まれた架空の用語や数値が埋め込みに反映され、無関係な文書群を上位に引き寄せることがあります。

そして厄介なことに、これらの失敗は静かに起きます。検索は0件ではなく「それらしい別の文書」を返し、LLMはそれを根拠にもっともらしい回答を作ります。評価をしていなければ気づけません。

メモ

検知の受け皿は先に作っておくほうが安上がりです。書き換え前後のクエリをログに残す、元クエリの固有名詞や型番が書き換え後にも残っているか機械的に検査する、失敗したら元クエリへ戻す。この3つがあるだけで、静かな劣化にかなり気づけるようになります。

使うべきでない場面

効果がわかっている手法でも、常時オンが正解とは限りません。次の条件では、入れないほうが良い結果になることがあります。

まず、クエリが最初から短く明確な場合です。「有給休暇の付与日数」のようにキーワードが揃っている質問は、書き換えても情報が増えず、余計な語が混ざってノイズになります。書き換えは質問が不完全なときに効く手法です。対象が単一ドメインで語彙が揃っている場合も同様で、索引側とユーザー側の語彙が近ければ埋めるべき溝がありません。

レイテンシ要件が厳しい場合は、検索前のLLM呼び出しそのものが許容できないことがあります。入力補完のように応答速度が体験の中心にある用途では、精度より速さを取る判断が妥当です。完全一致が本質の検索も同様で、識別子や型番で1件を特定したい問い合わせは書き換えのリスクが利得を上回りやすく、ハイブリッド検索でキーワード側を厚くするほうが素直です。

エージェント側でクエリ制御をしている場合も、役割が二重になります。Agentic RAGではクエリ理解・計画やルーティングをエージェント自身が担うため、前段にさらに固定の書き換えを挟むと判断が競合します。どちらのレイヤーでクエリを決めるかを先に決めてください。

あわせて読みたい

Agentic RAG(エージェンティックRAG)とは|1回検索で終わる従来RAGを、計画・再検索・自己反省で賢くする

効果を数字で確かめる

クエリ書き換えは検索の入力を変える処理なので、評価も検索段で行うのが筋です。生成の品質だけを見ていると、書き換えの寄与が回答生成の揺らぎに埋もれます。用意するのは、想定質問と正解文書(またはチャンクID)の対応表です。書き換えなし・ありの2条件で同じ検索器を回し、次の指標を並べます。

  • Hit Rate(命中率): 上位k件に正解が1件でも入っているか。まず「拾えているか」を見る指標です。
  • Recall@k: 正解が複数ある場合に、上位k件で何割を拾えたか。マルチクエリや分解のように網羅を狙う手法の効果はここに出ます。
  • nDCG@k: 関連度に濃淡があるときの並び順の質。上位ほど重く配点するため、正解が上に来たかを測れます。

LlamaIndexには検索段の評価モジュールがあり、公式ドキュメントでは指標名を指定して評価器を作る形が示されています。llama-index-coreに登録されている指標はhit_rate/mrr/precision/recall/ap/ndcgで、上の3指標もここから指定できます。

# 出典: LlamaIndex公式ドキュメント「Usage Pattern (Retrieval)」の例をもとに、本文の指標に合わせて指標名を差し替えています
from llama_index.core.evaluation import RetrieverEvaluator

retriever_evaluator = RetrieverEvaluator.from_metric_names(
    ["hit_rate", "recall", "ndcg"], retriever=retriever
)

retriever_evaluator.evaluate(
    query="query", expected_ids=["node_id1", "node_id2"]
)

コツは2つです。1つは、変える要素を一度に1つへ絞ること。書き換え手法と取得件数を同時に動かすと、どちらが効いたのかわからなくなります。もう1つは、平均値だけを見ないこと。改善した件数と悪化した件数を別々に数えると、平均が横ばいでも中身が入れ替わっている状況に気づけます。

本番では書き換え自体をA/Bの対象にします。トラフィックを分け、あり群となし群で根拠のクリック率・回答の採用率・追加質問の発生率・レイテンシを並べます。オフライン評価の結論が本番の質問分布で成り立つとは限らないため、オフラインで候補を絞りオンラインで確定します。

この記事のクラス名・パラメータは執筆時点の各公式ドキュメントと論文の記載に基づきます。フレームワークのAPIやマネージドサービスの機能(提供状況・プレビュー可否・パラメータ名)は更新されるため、実装前に必ず現行の公式ドキュメントで確認してください。論文で報告された改善幅はそのデータセット・検索器・設定でのものであり、自社データでそのまま再現するとは限りません。

導入の進め方

  1. 1

    失敗クエリを集めて評価セットを作る

    本番ログから外している質問を実物で集め、原因の内訳(言葉足らず/語彙のズレ/複数の問い)を見ます。そのうえで正解文書を紐づけ、Hit Rate・Recall@k・nDCGを計算できる状態にします。
  2. 2

    いちばん軽い手法から1つだけ入れる

    多くの場合は単純な書き換え(明確化と用語正規化)が最初の候補で、会話型UIなら指示語の解決を先に入れます。同時に複数を足さず、寄与を切り分けます。
  3. 3

    元クエリとの併用を試し、レイテンシとコストも記録する

    書き換え後のクエリだけに賭けず、元クエリの結果と統合する構成を試します。精度指標と一緒に、p50/p95のレイテンシと1クエリあたりのトークン消費も同じ表に記録します。Azure AI Searchが元クエリと書き換えクエリの両方で結果を取得しているのと同じ考え方です。
  4. 4

    悪化したクエリを個別に読み、A/Bで確定する

    平均で判断せず、悪化した質問の書き換え結果を目で見ます。そのうえで残った候補を本番トラフィックの一部に当て、フォールバック経路を用意して段階的に広げます。

よくある質問

クエリ書き換えとリランキングはどちらを先に入れるべきですか
直しどころが違うので、失敗の原因で決めます。まずHit Rateで正解が候補に拾えているかを確認し、拾えていないなら書き換え(または検索側)を、拾えているのに順位が低いならリランキングを検討する、という順序が素直です。
マルチクエリは何本くらい生成すればよいですか
一律の正解はありません。本数を増やすほど再現率は上がりやすい一方、検索回数と統合対象の候補が増えてレイテンシとコストが線形に近く伸びます。少ない本数から始め、Recall@kの伸びとレイテンシの増加を同じ表で見比べて決めてください。
HyDEは常に有効ですか
いいえ。質問と文書の形が大きく違う場面やゼロショット検索では効きますが、LLMが知らない社内固有の内容では、もっともらしい嘘の仮想文書を作って無関係な文書を引く危険があります。元のクエリも併用し、必ず評価セットで書き換えなしと比較してから採用してください。
書き換えで固有名詞が消えるのを防ぐ方法はありますか
完全な防止はできませんが、受け皿は多重にできます。プロンプトで型番・固有名詞・数値を変更しないよう明示する、書き換え後に元クエリの固有表現が残っているか機械的に検査して落ちていれば元クエリへ戻す、元クエリの結果も併走させて統合する、といった対策が現実的です。完全一致が重要ならキーワード側を厚くするハイブリッド構成も併せて検討してください。

まとめ

クエリ書き換え導入のチェックリスト

  • 失敗クエリの実物を集め、原因が入力側にあることを確認した
  • 想定質問と正解文書の評価セットでHit Rate/Recall@k/nDCGを計算できる状態にした
  • 書き換えなしを基準に、手法を1つずつ足して寄与を切り分けた
  • 精度指標とあわせてp50/p95レイテンシと1クエリあたりコストを記録した
  • 固有名詞の欠落を検査する仕組みと、元クエリへのフォールバック経路を用意した
  • 短く明確なクエリ・完全一致が本質・低レイテンシ厳守など、向かない場面を除外した

クエリ書き換えは、索引や検索器に手を入れずに試せる比較的軽い改善策です。ただし「質問を作り替える」性質上、良くする方向にも悪くする方向にも動き、その失敗はエラーにならず、それらしい回答として静かに出てきます。だからこそ、手法の選択そのものより、書き換えの有無を同じ評価セットで比較し、悪化したクエリを個別に読む運用のほうが効きます。検索段の評価軸を先に用意してから書き換えを足す。この順番を守れば、RAG改善の中でも見返りの読みやすい一手になります。

あわせて読みたい

LLMアプリの評価(eval)の作り方|「動いた気がする」で止めないための実務手順

出典・参考

この記事をシェア

関連する記事

開発・エージェント

ハイブリッド検索とは|キーワード検索(BM25)とベクトル検索を組み合わせてRAGの取りこぼしを減らす

ベクトル検索は言い換えや意味に強い一方で型番・固有名詞・略語の完全一致に弱く、BM25などのキーワード検索はその逆です。両方で候補を集めてRRF(Reciprocal Rank Fusion)などで統合するハイブリッド検索の仕組み、スコア統合の難しさとRRFがスケール非依存な理由、retrieve->fuse->rerankの三段構成、向く場面と運用コストを実務目線で整理します。

開発・エージェント

リランキング(Reranker)でRAGの検索精度を上げる|retrieve->rerankの二段構成

ベクトル検索で粗く多めに拾い、Cross-Encoder型リランカーで関連度を再スコアリングして上位を絞る二段構成(retrieve->rerank)を実務目線で整理します。bi-encoderとの違い、使いどころ、コストとレイテンシのトレードオフ、Hit Rate/MRR/nDCGでの評価、向かない場面までまとめます。