メインコンテンツへスキップ

Lost in the Middle — LLMの位置バイアスを克服するプロンプト設計

Lost in the Middle — LLMの位置バイアスを克服するプロンプト設計のアイキャッチ画像
目次

TL;DR


1. Lost in the Middle問題とは

LLMの位置バイアス

LLMを使ったアプリケーション開発で、「プロンプトに書いた指示が無視される」という経験はないでしょうか。特に、システムプロンプトが数百行に及ぶ複雑なアプリケーションでは、この問題が頻繁に発生します。

これはLost in the Middleと呼ばれる現象です。Liu et al. (2023) の研究 “Lost in the Middle: How Language Models Use Long Contexts” で体系的に報告されました。

この研究の核心的な発見は、LLMがU字型の性能曲線を示すことです。

性能

 │  ★                                    ★
 │   ★                                 ★
 │    ★★                            ★★
 │      ★★★                     ★★★
 │         ★★★★★★★★★★★★★

 └──────────────────────────────────────► 情報の位置
   先頭        中間(性能低下)        末尾

具体的な数値として、複数の文書を参照して質問に回答させるタスクにおいて、中間に配置された情報の利用性能は先頭・末尾と比較して30%以上低下するケースが報告されています(Liu et al., 2023。低下幅はモデルやタスクにより異なります)。

なぜ中間の情報が失われるのか — RoPEの長期減衰効果

この現象の主要な原因の一つは、多くのLLMで採用されているRoPE(Rotary Position Embedding)の長期減衰効果にあると考えられています。なお、位置バイアスはRoPEだけでなく、因果的注意マスク——各トークンが自分より後ろのトークンを参照できないよう制限する三角行列状のマスク——の構造や、学習データにおける位置分布の偏りなど、複数の要因が複合的に関与していると指摘されています。

RoPEは、トークン間の相対位置に基づいて注意(Attention)の強度を調整する仕組みです。通常のTransformerは「このトークンが入力の何番目にあるか」を絶対位置として記憶しますが、RoPEは代わりに「2つのトークンがどれだけ離れているか」という相対的な距離を、ベクトルの回転角度として表現します。この回転角度は距離が離れるほど大きくなり、結果として遠いトークン同士の注意スコアが自然に減衰します。

プロンプトの先頭の指示は直近の生成トークンからは「遠い」位置にあります。しかし、因果的言語モデル(causal language model)——つまり左から右へ1トークンずつ順に生成し、各トークンはそれより前のトークンしか参照できないモデル——の特性により、先頭のトークンは後続の全てのトークンの処理に繰り返し参照されます。この累積的な影響により、結果的に先頭と末尾の情報は強く保持されます。

一方で、中間のトークンは先頭ほどの累積的な影響力を持たず、かつ末尾ほど生成トークンに近くもないため、いわば「注意の谷間」に陥ります。


2. 実務で遭遇する具体例

どのくらいの長さで問題になるのか

最新のモデルでは、数十行程度のプロンプトでこの問題が顕在化することはほぼありません。筆者の体感では、数百行規模のシステムプロンプト——たとえばRAGで大量のコンテキストを注入するケースや、複雑なルールセットを持つエージェント型アプリケーション——で初めて影響が目立ち始めます。

この問題は最新モデルでも解消されていません。Modarressi et al. (2025) の研究 “NoLiMa: Long-Context Evaluation Beyond Literal Matching”(ICML 2025)では、GPT-4oやGemini 1.5 Pro、Claude 3.5 Sonnetを含む13モデル中11モデルが、32Kトークンのコンテキストにおいて短文時のベースライン性能の50%未満にまで低下しました。その後の追加評価ではGPT-4.1やGemini 2.5 Flashでも同様の傾向が確認されています。

また、Chroma Researchの研究 “Context Rot: How Increasing Input Tokens Impacts LLM Performance”(2025年7月)では、18モデルを対象に長文コンテキストでの性能劣化が検証されています。興味深いのはモデルファミリーごとに劣化パターンが異なる点で、GPTモデルは誤った回答を自信を持って返す(ハルシネーション)傾向が強いのに対し、Claudeモデルは不確実な場合に回答を控える(棄権する)傾向が見られました。なお、同研究ではLiu et al.が報告したU字型の検索パターンは一貫しては観察されなかったとも報告されており、位置バイアスの現れ方はタスクやモデルによって異なる可能性があります。

筆者もGPT-4.1 miniで同様の現象を確認しており、モデルの世代が進んでも位置バイアスは緩和されつつあるが根本的には解消されていないのが現状です。

以下に示す例は構造を理解するために簡略化したものですが、実際にはこうしたセクションが何十個も並び、全体で数百行〜数千トークンに膨らんだときに問題が発生します。

システムプロンプトでの中間ルール無視

たとえば、以下のようなセクション構成を持つシステムプロンプトが数百行に及ぶ場合を考えます。

あなたはカスタマーサポートアシスタントです。   ← 先頭付近: 遵守される

## 基本ルール
- 丁寧な言葉遣いで応答してください
- ユーザーの名前を使って応答してください

... (数十セクションが続く)

## 応答フォーマット                               ← 中間に埋もれる
- 回答は3文以内にまとめてください
- 箇条書きを使用してください

## データ参照ガイド                               ← 中間に埋もれる
- 価格情報は必ずデータベースを参照すること

... (さらに多数のセクションが続く)

## 禁止事項                                       ← 末尾付近: 遵守される
- 競合他社の製品を推奨しないでください
- 個人情報を聞き出さないでください

先頭の「基本ルール」と末尾の「禁止事項」は遵守されるのに、中間に埋もれた「応答フォーマット」や「データ参照ガイド」のセクションが無視される——プロンプトが長くなるほど、このパターンに遭遇しやすくなります。

コード生成タスクでの要件欠落

コード生成タスクでも、要件セクションが多い場合に同様の問題が発生します。先頭の技術スタック指定や末尾のレスポンス形式は遵守されるのに、中間に記載したバリデーション要件やエラーハンドリング要件が丸ごと抜け落ちるケースです。プロンプト全体が短ければ問題にならないものの、コンテキストや例示が増えて全体が長くなると影響が出始めます。


3. 末尾チェックリストパターン

パターンの概要

Lost in the Middle問題に対する最も実践的な対策が、末尾チェックリストパターンです。プロンプトの末尾にチェックリスト形式で重要な指示を再掲し、LLMに「二重確認」を促します。

Before / After

以下は数百行のシステムプロンプトの構造を簡略化したものです。実際には各セクションがさらに詳細で、間に多くのルールやコンテキストが挟まります。

Before(中間の指示が埋もれる構成):

あなたはコードレビューアシスタントです。

## レビュー観点
...(5項目)

... (多数のセクション: コーディング規約、言語別ルール、例外ケース...)

## 出力フォーマット                    ← 中間に埋もれる
- 重要度を「高/中/低」で分類
- 各指摘に修正案のコード例を添付
- 影響範囲を明記

... (さらにセクションが続く)

## レビュー対象
{code}

After(末尾にチェックリストを追加):

あなたはコードレビューアシスタントです。

## レビュー観点
...(5項目)

... (多数のセクション: コーディング規約、言語別ルール、例外ケース...)

## 出力フォーマット
- 重要度を「高/中/低」で分類
- 各指摘に修正案のコード例を添付
- 影響範囲を明記

... (さらにセクションが続く)

## レビュー対象
{code}

---
## 出力前の最終チェックリスト           ← ここを追加
回答を出力する前に、以下の全項目を確認してください:
- [ ] 5つの観点全てに言及したか
- [ ] 各指摘に重要度(高/中/低)を付与したか
- [ ] 各指摘に修正案のコード例を添付したか
- [ ] 影響範囲を明記したか

末尾にチェックリストを配置することで、LLMは生成直前にこれらの条件を「再認識」します。U字型性能曲線の特性を逆手に取り、末尾という最も注意が向きやすい位置に確認項目を置くわけです。

筆者の経験では、このパターンを導入してから中間部の指示が無視される頻度は体感的にかなり減りました。特に「出力フォーマット」のようなプロンプト中間部に記載しがちな指示に対して効果を感じています。

実体験: 構造化JSON出力での改善

筆者が実際にこの問題に直面したのは、LangChainでOpenAIモデルを使い、ユーザーの自由入力テキストから構造化されたJSONを抽出するタスクでした。LangChainの with_structured_output を使用しており、Pydanticモデルの Field(description=...) でスキーマとフィールドの説明を定義しつつ、プロンプト側にも各フィールドの抽出ルール(必須/任意の区別、デフォルト値、フォーマット指定など)を別途記載する構成です。

フィールド数が多くなるにつれて、プロンプト中間部に記載した特定フィールドの抽出精度が目に見えて落ちていきました。先頭付近に書いたフィールドのルールと、末尾付近のルールは問題なく適用されるのに、中間に埋もれたフィールドだけが null や不正確な値で返ってくる——まさにU字型曲線の通りの挙動です。

この問題に対して、プロンプト末尾(ユーザー入力の後)にリマインダーを追加したところ、該当フィールドの抽出精度が明確に改善しました。

from langchain_core.prompts import ChatPromptTemplate
from pydantic import BaseModel, Field

# Pydanticスキーマ定義(descriptionもLLMに渡される)
class TaskSchema(BaseModel):
    category: str = Field(description="定義済みカテゴリから選択")
    priority: int = Field(description="1-5の整数")
    due_date: str | None = Field(description="ISO 8601形式の日付、不明ならnull")

prompt = ChatPromptTemplate.from_messages([
    ("system", """あなたはタスク情報を抽出するアシスタントです。
ユーザーの入力から構造化データを抽出してください。"""),
    ("human", """{user_input}

## 出力前の確認
以下のフィールドが正しく抽出されていることを確認してから出力してください:
- "category": 必ず定義済みカテゴリから選択すること(推測しない)
- "priority": 1-5の整数であること
- "due_date": ISO 8601形式であること(入力に日付がなければnull)"""),
])

structured_llm = llm.with_structured_output(TaskSchema)
messages = prompt.format_messages(user_input=user_input)
result = structured_llm.invoke(messages)

この末尾リマインダーの追加で注目すべきは、他のフィールド(もともと正しく抽出できていたもの)の出力にほとんど影響がなかった点です。同じ指示をプロンプト中間部の該当フィールド定義を強調する形で追記した場合は、周辺フィールドの抽出にも微妙なブレが生じることがありましたが、末尾への配置ではそういった副作用がほぼ見られませんでした。末尾チェックリストは、既存の出力を壊さずにピンポイントで弱い箇所を補強できるという利点があります。

ここで一つ注意点があります。プロンプト側の抽出ルールを修正したとき、Pydanticモデルの Field(description=...) も合わせて更新しないと、プロンプトとスキーマ定義で矛盾が生じ、せっかくの修正の精度が落ちることがありました。with_structured_output はスキーマの description もLLMに渡すため、プロンプトとスキーマの両方を同期して更新する必要があります。地味ですが実務では見落としやすいポイントです。

なお、LangChainにおけるドメイン固有知識のプロンプト注入に関しては、LangChainブログの記事 “Incorporating domain specific knowledge in SQL-LLM solutions” でも、静的なプロンプトではなく動的にFew-shot exampleを検索して注入するアプローチが紹介されています。

A more powerful approach is to have a robust dataset of good examples, and dynamically include those which are relevant to the user question.

(より強力なアプローチは、良質なexampleの堅牢なデータセットを用意し、ユーザーの質問に関連するものを動的に含めることである。)

具体的には、ベクトルデータベースを活用したカスタムRetriever Toolを作成し、ユーザーの質問に意味的に類似したexampleを動的に取得する方法が示されています。フィールド数が多い構造化出力タスクでは、全ルールを静的に並べるよりも、入力内容に応じて関連するフィールドの抽出ルールを動的に選択・配置する方がLost in the Middleの影響を受けにくいかもしれません。

末尾チェックリストが効きにくいケース

ただし、この手法にも限界があります。

実装上のポイント

末尾チェックリストパターンを効果的に使うためのポイントをまとめます。

1. チェックリストは「本文の繰り返し」ではなく「確認事項」として書く
   - NG: 同じ文章をそのままコピー
   - OK: 確認すべきポイントを簡潔にリスト化

2. 項目数は5〜10個に絞る
   - 多すぎると逆効果(チェックリスト自体のLost in the Middle)

3. 「回答の前に確認してください」と明示する
   - LLMに確認プロセスを促す

4. 最も見落とされやすい項目を優先する
   - 全てを再掲するのではなく、実績として漏れやすい指示を重点的に

4. その他のアプローチ

末尾チェックリストは手軽で効果的ですが、プロンプトの構造自体を改善するアプローチや、RAGパイプラインなどプロンプト外の仕組みで対策するアプローチもあります。

サンドイッチ戦略

最も重要な情報をプロンプトの先頭と末尾の両方に配置する方法です。

## 最重要ルール
出力は必ずJSON形式で返してください。

## コンテキスト情報
{大量のコンテキスト...}

## 補足情報
{追加のコンテキスト...}

## リマインダー: 出力は必ずJSON形式で返してください。

U字型曲線の両端、つまり性能が最も高い位置に重要な指示を配置するため、シンプルながら効果的です。ただし、「最重要」を1つに絞る必要があるため、複数の指示を同時に強調したい場合には不向きです。

XMLタグによる構造化とセクション分割

XMLタグやMarkdownのヘッダーを使ってプロンプトを明確にセクション分割し、LLMがパースしやすい構造にします。

Anthropicのプロンプトエンジニアリングチュートリアルでは、XMLタグによるデータと指示の分離が推奨されています。<sentences>...</sentences> のようなタグで入力データを明確に区切ることで、LLMがデータ領域と指示領域を区別しやすくなり、中間情報の見落としを軽減できる可能性があります。ただし、XMLタグ構造化は位置バイアスそのものを解消するものではなく、他の手法と組み合わせて使用することが推奨されます。

<system>
あなたはデータ分析アシスタントです。
</system>

<rules>
<rule priority="high">数値は必ず出典を明記すること</rule>
<rule priority="high">推測値には「推定」と明記すること</rule>
<rule priority="medium">グラフの説明には軸ラベルを含めること</rule>
</rules>

<context>
{分析対象のデータ}
</context>

<output_format>
{出力フォーマットの指定}
</output_format>

priority 属性を付与することで、LLMが重要度を判断する補助にもなります。構造化によって「どこに何が書かれているか」を明示的にすることで、中間部の情報が埋もれるリスクを下げる効果が期待できます。

RAGにおける戦略的ドキュメント配置

RAG(Retrieval-Augmented Generation)パイプラインでは、検索されたドキュメントの配置順序が回答精度に直結します。

def reorder_documents(docs: list[str], scores: list[float]) -> list[str]:
    """
    Lost in the Middle対策として、
    関連度の高いドキュメントを先頭と末尾に配置する。

    例: スコア順 [A(0.9), B(0.8), C(0.7), D(0.6), E(0.5)]
    結果: [A(0.9), C(0.7), E(0.5), D(0.6), B(0.8)]
           ^^^^^^                           ^^^^^^
           先頭に高スコア              末尾に高スコア
    """
    scored_docs = list(zip(docs, scores))
    scored_docs.sort(key=lambda x: x[1], reverse=True)

    head = []  # 先頭側(偶数インデックス: 1位, 3位, 5位...)
    tail = []  # 末尾側(奇数インデックス: 2位, 4位, 6位...)

    for i, (doc, score) in enumerate(scored_docs):
        if i % 2 == 0:
            head.append(doc)
        else:
            tail.append(doc)

    # 末尾側は逆順にして、高スコアが末尾端に来るようにする
    return head + tail[::-1]

この手法は、関連度スコアの高いドキュメントを先頭と末尾に配置し、中間にはスコアの低いドキュメントを置くことで、重要な情報が見落とされるリスクを軽減します。

情報位置と精度の定量的検証

Lost in the Middle問題は、Few-shot promptingの文脈でも影響を及ぼします。Anthropicのブログ記事 “Prompt engineering for Claude’s long context window” では、長文コンテキストからの情報検索精度を高めるテクニックとして、質問に関連する引用を先に抽出させる手法や、正しく回答されたQ&Aの例をプロンプトに追加する手法が定量的に検証されています。

自分のプロンプトで位置バイアスの影響度合いを確認したい場合、このようなベンチマークを参考に検証パイプラインを構築することが有効です。


5. まとめ

手法の比較

手法適用場面トークンコスト実装難易度
末尾チェックリストシステムプロンプト全般低(リスト追加分のみ)
サンドイッチ戦略最重要ルールが1つの場合低(1文の再掲)
XMLタグ構造化複数種類の情報を扱う場合中(タグ分の増加)
RAGドキュメント配置RAGパイプラインなし(並び替えのみ)

情報の配置に気を配る

末尾チェックリストで二重確認する

継続的にプロンプトの精度をモニタリングする


学んだこと


参考

ZSL
ZSL

AIエンジニア

生成AIを活用した開発ワークフローの研究・実践をしています。