LLMアプリケーションを作っていると、次のような発想になりがちです。
コンテキストウィンドウが大きいモデルを使えば、とりあえず大量のドキュメントを詰め込めばよいのでは?
しかし、実際にはそう単純ではありません。
LLMには、長い入力の中に含まれる情報を均等に扱えないことがあります。特に、重要な情報が入力の真ん中付近にあると、モデルがそれをうまく利用できず、回答精度が落ちることがあります。
この現象は、論文 “Lost in the Middle: How Language Models Use Long Contexts” で詳しく検証され、一般に Lost in the Middle と呼ばれています。
この記事では、Lost in the Middleの概要、なぜRAG設計で問題になるのか、そして実務で取れる対策を整理します。
Lost in the Middleとは
Lost in the Middleとは、LLMが長いコンテキストを処理するときに、入力の先頭や末尾にある情報は比較的利用しやすい一方で、中央付近にある情報を見落としやすい現象です。
イメージとしては、次のような状態です。
[重要情報] ................................. → 比較的読まれやすい
................. [重要情報] ............... → 見落とされやすい
................................. [重要情報] → 比較的読まれやすい
つまり、コンテキストウィンドウが大きいことと、その中の情報をモデルが均等に使えることは別問題です。
論文で検証されたこと
論文では、主に次の2種類のタスクで検証されています。
1. Multi-document Question Answering
複数のドキュメントをLLMに与え、その中から質問に答えるタスクです。
RAGの実務に近い設定です。
例えば、次のような入力をLLMに渡します。
Document 1: ...
Document 2: ...
Document 3: ...
Document 4: ...
Document 5: ...
Question: xxxについて教えてください
このとき、正解に必要な情報がどのDocumentにあるかを変えて、回答精度の変化を観察します。
結果として、正解情報が入力の先頭または末尾にあると性能が高く、中央付近にあると性能が低下する傾向が確認されました。
2. Key-value Retrieval
大量のkey-valueペアを与えて、特定のkeyに対応するvalueを答えさせるタスクです。
例:
key001: apple
key002: orange
key003: banana
...
key999: grape
Question: key512のvalueは?
このタスクでも、探すべきkey-valueがコンテキストのどこに置かれるかによって、正答率が変わります。
単純な検索に見えるタスクでも、中央にある情報は取り出しにくくなることが示されています。
なぜRAGで問題になるのか
RAGでは、検索で取得したチャンクをLLMのプロンプトに詰め込みます。
典型的には次のような構成になります。
System Prompt
User Question
Retrieved Context 1
Retrieved Context 2
Retrieved Context 3
Retrieved Context 4
Retrieved Context 5
Instruction
ここで問題になるのが、取得したチャンクの並び順です。
仮に、最も重要なチャンクが3番目や4番目に配置されていると、プロンプト全体の中では中央付近に埋もれる可能性があります。
その結果、次のような問題が起きます。
- 検索では正しい文書を取得できているのに、LLMが回答に使わない
- コンテキストには答えがあるのに「分かりません」と返す
- 重要でないチャンクを優先して回答してしまう
- 回答の根拠が曖昧になる
- 長いプロンプトにしたのに精度が上がらない
これは、RAGにおいて非常に厄介です。
なぜなら、問題の原因が「検索失敗」なのか「LLMが中央の情報を使えていない」のかを切り分けにくいからです。
よくある誤解:長いコンテキストなら安心?
最近のLLMは、非常に大きなコンテキストウィンドウを持つものが増えています。
しかし、Lost in the Middleを踏まえると、次のように考えるべきです。
コンテキストに入る = モデルが正しく使える、ではない
例えば、128k tokensのコンテキストに文書を大量投入できたとしても、中央に置かれた重要情報をモデルが確実に利用してくれるとは限りません。
そのため、RAG設計では「どれだけ入るか」だけではなく、次の観点が重要になります。
- どの情報を入れるか
- どの順番で入れるか
- どの情報を先頭・末尾に置くか
- どの程度まで要約・圧縮するか
- 回答前に根拠を再確認させるか
実務で取れる対策
ここからは、RAGやLLMアプリケーション設計で使える実践的な対策を整理します。
対策1:重要な情報を先頭または末尾に置く
Lost in the Middleを考えると、最も単純で効果的な対策は、重要な情報をプロンプトの中央に埋めないことです。
例えば、検索スコアが最も高いチャンクを先頭に配置します。
# 悪い例
Context A: 関連度 中
Context B: 関連度 低
Context C: 関連度 高
Context D: 関連度 中
Context E: 関連度 低
# 改善例
Context C: 関連度 高
Context A: 関連度 中
Context D: 関連度 中
Context B: 関連度 低
Context E: 関連度 低
また、重要情報を先頭と末尾に重複配置する方法もあります。
Important Summary:
- 今回の質問に最も関係する事実: xxx
- 根拠文書: Document C
Retrieved Contexts:
...
Final Reminder:
- 回答ではImportant Summaryと根拠文書を優先すること
同じ情報を無制限に重複させるのはよくありませんが、重要な根拠を先頭または末尾に明示するのは実務上有効です。
対策2:検索結果をそのまま詰め込まない
RAGでありがちな実装は、ベクトル検索の上位k件をそのままプロンプトに入れる方法です。
docs = retriever.search(query, top_k=10)
context = "\n\n".join([doc.text for doc in docs])
しかし、この実装では次の問題が起きます。
- top_kが大きいほどノイズが増える
- 重要チャンクが中央に埋もれる
- 似ているが答えに不要な文書が混ざる
- LLM側の負荷が増える
そのため、検索結果をそのまま使うのではなく、LLMに渡す前に並べ替え・圧縮・フィルタリングする工程を入れるべきです。
docs = retriever.search(query, top_k=20)
reranked_docs = reranker.rerank(query, docs)
selected_docs = reranked_docs[:5]
context = build_context(selected_docs)
重要なのは、検索のtop_kとLLMへ渡す件数を分けて考えることです。
対策3:Rerankerを使う
ベクトル検索は便利ですが、必ずしも質問に対して最も答えやすい順番で文書を返すとは限りません。
そこで、Rerankerを使って検索結果を再評価します。
構成例:
User Query
↓
Vector Search: top_k=30
↓
Reranker: top_n=5
↓
Prompt Builder
↓
LLM
Rerankerを使うことで、単に意味的に近い文書ではなく、質問への回答により有用な文書を上位に配置しやすくなります。
特に、業務文書、FAQ、仕様書、議事録、障害報告書などでは有効です。
対策4:コンテキストを要約してから渡す
大量のチャンクをそのまま渡すのではなく、質問に関係する部分だけを抽出・要約してからLLMに渡す方法もあります。
Raw Documents
↓
Question-focused Summarization
↓
Condensed Context
↓
Answer Generation
例えば、次のような中間処理を入れます。
以下の文書から、ユーザーの質問に答えるために必要な事実だけを抽出してください。
推測はせず、文書内の記述だけを箇条書きにしてください。
その結果を、最終回答用のLLMに渡します。
この方法のメリットは、プロンプト全体を短くできることです。
ただし、要約段階で重要情報が落ちるリスクもあるため、必要に応じて元文書への参照IDを残すべきです。
対策5:チャンクサイズとオーバーラップを見直す
Lost in the Middleは、プロンプト全体だけでなく、チャンク内部でも問題になる可能性があります。
1つのチャンクが長すぎると、重要な文がチャンクの中央に埋もれます。
Chunk:
- 前置き
- 背景説明
- 補足
- 重要な結論
- 例外条件
- 関連情報
この場合、検索にはヒットしていても、LLMが重要な結論を使いにくくなる可能性があります。
チャンク設計では、次の点を意識します。
- 1チャンクに複数の論点を詰め込みすぎない
- 見出し単位・セクション単位で分割する
- 結論や定義をチャンクの先頭に寄せる
- 必要に応じてオーバーラップを入れる
- メタデータにタイトル・章・日付・カテゴリを持たせる
Markdown文書であれば、見出し単位で分割するのが実務上扱いやすいです。
def split_by_heading(markdown: str) -> list[str]:
chunks = []
current = []
for line in markdown.splitlines():
if line.startswith("# ") or line.startswith("## "):
if current:
chunks.append("\n".join(current))
current = []
current.append(line)
if current:
chunks.append("\n".join(current))
return chunks
対策6:プロンプトに「根拠確認」のステップを入れる
回答生成時に、いきなり答えを出させるのではなく、先に根拠を確認させる方法があります。
例:
次の手順で回答してください。
1. 質問に関係する根拠文をContextから抽出する
2. 抽出した根拠文のIDを列挙する
3. 根拠文だけを使って回答する
4. Contextに根拠がない場合は「文書内では確認できません」と答える
このようにすると、LLMがコンテキスト全体を漫然と読むのではなく、回答前に関連箇所を探す動きになりやすくなります。
ただし、これだけでLost in the Middleが完全に解消するわけではありません。
プロンプト上の工夫と、検索・並び替え・圧縮の設計を組み合わせることが重要です。
対策7:評価データを作って位置バイアスを測る
RAGアプリケーションでは、通常の正答率だけでなく、情報の位置による性能差も評価するとよいです。
例えば、同じ質問・同じ根拠文を使い、根拠文の配置だけを変えます。
Case A: 根拠文を先頭に置く
Case B: 根拠文を中央に置く
Case C: 根拠文を末尾に置く
そのうえで、回答精度や根拠IDの一致率を比較します。
簡単な評価コードのイメージです。
def build_prompt(contexts, question):
return f"""
以下のContextだけを使って質問に答えてください。
Context:
{contexts}
Question:
{question}
"""
def place_relevant_doc(position, relevant_doc, distractors):
if position == "head":
docs = [relevant_doc] + distractors
elif position == "middle":
mid = len(distractors) // 2
docs = distractors[:mid] + [relevant_doc] + distractors[mid:]
elif position == "tail":
docs = distractors + [relevant_doc]
else:
raise ValueError("position must be head, middle, or tail")
return "\n\n".join(docs)
評価観点は、次のようにします。
- answer_exact_match
- answer_contains_expected_fact
- cited_document_id_match
- hallucination_rate
- unknown_answer_rate
本番RAGでは、モデル変更、チャンク設計変更、top_k変更、Reranker導入のたびに、この評価を回すと安全です。
RAGプロンプトの改善例
最後に、Lost in the Middleを考慮したプロンプト構成例を示します。
あなたは社内ドキュメントに基づいて回答するアシスタントです。
必ずContextに含まれる情報だけを使って回答してください。
# 最重要根拠
{most_relevant_context}
# 質問
{user_question}
# 補足Context
{other_contexts}
# 回答ルール
- 最重要根拠を優先して確認する
- 補足Contextは必要な場合のみ参照する
- 回答には根拠となるDocument IDを含める
- Contextに根拠がない場合は「文書内では確認できません」と答える
ポイントは、最重要情報を中央に置かないことです。
また、質問をContextの前に置くか後に置くかはモデルやタスクによって差が出るため、実際には評価で決めるのが安全です。
実務でのチェックリスト
RAGの回答精度が不安定なときは、次の観点を確認するとよいです。
[ ] 検索結果の上位に正解文書が含まれているか
[ ] 正解文書がプロンプト内のどこに配置されているか
[ ] top_kを増やしすぎていないか
[ ] ノイズ文書が多すぎないか
[ ] Rerankerを使っているか
[ ] チャンクが長すぎないか
[ ] チャンクの中央に重要情報が埋もれていないか
[ ] 回答時に根拠IDを出させているか
[ ] 位置を変えた評価データで検証しているか
まとめ
Lost in the Middleは、LLMが長いコンテキストを常に均等に利用できるわけではないことを示す重要な現象です。
RAGや長文処理を設計するうえでは、単に「大きなコンテキストに全部入れる」のではなく、次のような設計が必要です。
- 重要情報を先頭または末尾に配置する
- 検索結果をそのまま詰め込まない
- Rerankerで並び替える
- 不要な文書を削る
- 質問に関係する情報だけを要約・抽出する
- チャンクサイズを適切に保つ
- 位置バイアスを評価する
LLMアプリケーションの品質は、モデル選定だけで決まりません。
どの情報を、どの順序で、どの粒度で渡すか。
このプロンプト設計とRAGパイプライン設計が、実務では非常に重要になります。
参考文献
- Nelson F. Liu et al., “Lost in the Middle: How Language Models Use Long Contexts”
- TACL: Transactions of the Association for Computational Linguistics
- arXiv: 2307.03172