128Kの限界を超えて:実用的なアプリケーションのための長文コンテキストLLMの最適化

AI tutorial - IT technology blog
AI tutorial - IT technology blog

RAGからネイティブな長文コンテキストへの転換

昨年、私のチームはある壁に突き当たりました。500ページに及ぶコンプライアンス文書(約35万トークンの難解な法律用語)の自動監査システムを構築していた時のことです。当時は標準的なRAG(検索拡張生成)を使用していました。テキストをチャンク化し、埋め込みベクトルを作成して、上位k個のスニペットを抽出していました。基本的な事実の確認には機能しましたが、12ページの責任条項と480ページの免責事項を関連付ける必要がある場面では、LLMは正しく機能しませんでした。

Gemini 1.5 Pro(200万コンテキスト)やClaude 3.5 Sonnet(20万コンテキスト)の登場により、状況は一変しました。適切なチャンクを探すのをやめ、ドキュメント全体をモデルに投入し始めたのです。しかし、コンテキストウィンドウが大きくなれば、それだけ問題も大きくなります。単に100万トークンをプロンプトに流し込むだけでは、正しい回答は保証されません。ここでは、コストを抑えつつ精度を落とさずに、これら大規模なコンテキストを管理する方法を紹介します。

大規模ドキュメントのためのアーキテクチャパターン

データが10万トークンを超える場合、一般的に3つの道があります。選択を誤ると、莫大な請求が発生するか、モデルがハルシネーション(幻覚)を起こすことになります。

1. 従来のRAG(低コストオプション)

これは、スニペットを取得するためにベクトルデータベースに依存する方法です。非常に安価で、多くの場合、1クエリあたり0.01ドル未満で済みます。しかし、LLMはドキュメントのごく一部しか見ることができません。「グローバルな認識」に欠けているため、本全体を要約したり、章をまたがる矛盾を見つけたりすることはできません。

2. ネイティブな長文コンテキスト(ゴールドスタンダード)

すべての単語をプロンプトに投入します。LLMは全体像を把握できます。複雑な推論には最適ですが、低速です。GPT-4oで20万トークンのドキュメントを処理すると、1メッセージあたり1ドル以上のコストがかかり、応答に30秒かかることもあります。

3. Context Caching(ハイブリッドな最適解)

AnthropicやGoogleなどのプロバイダーは、サーバー上でドキュメントを「凍結」できる機能を提供しています。テキストの処理に対して一度だけ料金を支払います。その後の質問は、約90%安くなり、80%速くなります。私の本番環境でのテストでは、マルチターンのチャットにおいて20秒の待ち時間が4秒のレスポンスに短縮されました。

トレードオフ:コスト vs パフォーマンス

トークンを増やせば常に良い結果が得られるという罠に陥らないでください。私の経験では、コンテキストウィンドウが一杯になるずっと前に、LLMのパフォーマンスが頭打ちになることがよくあります。

  • 従来のRAG: 高速で低コストですが、ストローを通して壁画を見ているようなものです。文脈(コンテキスト)を見落とします。
  • ネイティブな長文コンテキスト: 驚異的な推論能力を持ちますが、「Lost in the Middle(中央での喪失)」現象に直面します。モデルは、長いプロンプトの中央に埋もれた事実を忘れてしまうことがよくあります。
  • Context Caching: リピーターにとって最適です。特定のベンダーのAPIに縛られることになりますが、レイテンシを大幅に削減できます。

本番環境向けの戦略

エンタープライズ向けアプリでは、2層コンテキスト戦略を採用しています。10万トークン以上のモデルとContext Cachingを組み合わせます。モデルにすぐに「針(特定の情報)」を探させるのではなく、まず「ドキュメントマップ」を生成させます。このマップが、最終的なクエリを正しいセクションへと導きます。

2,000ページ以上(100万トークン超)を扱う場合は、Long-Context RAGを使用します。500トークンのスニペットではなく、5,000トークンの「メガチャンク」を取得します。これにより、コストを抑えつつ、論理的な流れを維持するのに十分な周辺情報をモデルに提供できます。

「Needle In A Haystack」(NIAH)による精度テスト

モデルが作り話をしていないことをどうやって証明しますか?Needle In A Haystack(干し草の山の中の針)テストを使用します。10万語の財務報告書(干し草の山)の真ん中に、「CEOの好きな色は藤色です」といったランダムな事実(針)を隠します。そして、モデルにそれを見つけるよう指示します。

128Kのウィンドウを謳っている多くのモデルが、ドキュメントの40%から70%の深さに「針」を置くと失敗し始めることがわかりました。精度は、ドキュメントの冒頭では99%であっても、中間部では65%まで低下することがあります。

コード:独自のNIAHテストを実行する

Pythonとtiktokenを使用して、これを自動化できます。このスクリプトは、特定の深さに事実を配置し、モデルがそれをまだ「認識」できるかどうかを確認します。

import tiktoken

def create_haystack(base_text, needle, depth_percent, model_name="gpt-4o"):
    encoder = tiktoken.encoding_for_model(model_name)
    tokens = encoder.encode(base_text)
    
    # 挿入箇所の計算
    insert_at = int(len(tokens) * (depth_percent / 100))
    
    # 針(特定の事実)を注入
    full_context = tokens[:insert_at] + encoder.encode(f"\n{needle}\n") + tokens[insert_at:]
    return encoder.decode(full_context)

# クイックテスト:秘密の情報を60%の深さに配置
secret = "サーバーのパスワードは 'Blue-Monkey-99' です。"
big_doc = "標準的な企業のダミーテキスト... " * 2000
prompt = create_haystack(big_doc, secret, 60)

# サイズを確認するためにトークン数をカウント
print(f"コンテキストサイズ: {len(encoder.encode(prompt))} トークン")

苦労して得た最適化のヒント

  1. 正確なトークナイザーを使用する: トークン数を推測してはいけません。OpenAIとAnthropicではロジックが異なります。わずか5%の計算ミスが、APIによってドキュメントの末尾が切り捨てられる原因になります。
  2. Temperatureをゼロにする: 抽出タスクでは temperature=0 に設定します。モデルにはクリエイティブな作家ではなく、文字通りの司書であってほしいからです。
  3. プロンプトエンジニアリング: どこを探すべきかをモデルに正確に伝えます。「回答する前に、テキスト全体を徹底的にスキャンしてください」と付け加えるだけで、私のベンチマークでは抽出率が15%向上しました。

レスポンスの高速化

LLMがドキュメントを読み終えるまで30秒待たせるのは、ユーザー体験を損ないます。ストリーミングは必須です。これにより、モデルが残りのデータを処理している間に、ユーザーは回答の最初の数語を見ることができます。Google Geminiを使用している場合、再利用されるクエリに対してTime-to-First-Token (TTFT) を短縮するには、彼らのContext Caching APIが最適なツールです。

# Google Geminiのキャッシング例
from google.generativeai import caching
import datetime

# ドキュメントを1時間キャッシュして、クエリコストを90%節約する
file_cache = caching.CachedContent.create(
    model='models/gemini-1.5-pro-001',
    contents=[large_document_string],
    ttl=datetime.timedelta(hours=1),
)

長文コンテキストの管理は、単に大きなバケツを持つことだけではありません。それをどう満たすかを知ることが重要です。NIAHテストを使用してモデルの限界点を見つけ、キャッシュを実装してコストを節約することで、大規模なデータセットを外科的な精度で処理する AIツールを構築できます。

Share: