コンテキストエンジニアリングを極める:プロダクションAIのための、プロンプトを超えた手法

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

プロンプトからコンテキストエンジニアリングへの転換

初めてLLM(大規模言語モデル)を活用した機能を開発したとき、私は「秘策」とは単に巧妙なプロンプトを書くことだと思っていました。「専門家」や「簡潔に」といった形容詞にこだわり、モデルが期待通りに動くよう何時間も費やしたものです。しかし、ローカルのスクリプトから、5,000人のアクティブユーザーを抱える本番環境へと移行したとき、すべてが変わりました。突然、モデルは長い会話の流れを見失い、存在しない機能を捏造(ハルシネーション)し、APIの請求額はわずか1週間で400%も急騰したのです。

その失敗から、私は厳しい教訓を学びました。プロンプトエンジニアリングは氷山の一角に過ぎないということです。信頼性の高いソフトウェアを構築するには、コンテキストエンジニアリングが必要です。これは、モデルの限られたメモリに入力されるあらゆるデータを戦略的に管理することを指します。私の経験上、このスキルを習得しているかどうかが、脆弱なデモ版と堅牢なエンタープライズグレードのAIアプリケーションを分ける境界線となります。

手法の比較:素朴なプロンプト vs コンテキストエンジニアリング

本番環境では、通常2つのデータフロー処理方法が見られます。一つは設定は簡単ですが維持費が高く、もう一つはより多くのコードが必要ですが一貫した結果をもたらします。

1. 素朴なアプローチ(追加して祈る)

ほとんどの開発者はここから始めます。ユーザーの入力を受け取り、直近の10件のチャットメッセージを付け加えてAPIに送信します。フィルタリングや優先順位付けは行わず、何が重要かの判断は完全にモデル任せです。これはしばしば「コンテキストの詰め込み(context stuffing)」を招き、3日前の無関係な詳細によってモデルが混乱する原因となります。

2. コンテキストエンジニアリング・アプローチ

この手法では、コンテキストウィンドウを、低レイヤープログラミングにおけるRAMの管理と同じように、貴重なリソースとして扱います。構造化されたテンプレート、動的なプルーニング(削減)、そして多層的な指示を使用します。テキストの乱雑な塊ではなく、クリーンで優先順位付けされた会話のマップをモデルに提供します。

特徴 素朴なアプローチ コンテキストエンジニアリング
組織化 非構造化テキストブロック 構造化されたXMLまたはMarkdownタグ
メモリ 直近N個のメッセージ(固定) 要約および優先順位付けされた履歴
ハルシネーション 高(エラー率 10-15%) 低(グラウンディングにより2%未満)
コスト 無駄が多く予測不能 トークン予算管理により最適化

現実的なトレードオフ

エンジニアリングにおいて「完璧な」解決策が見つかることは稀であり、重要なのは適切なトレードオフを選択することです。コンテキストエンジニアリングも例外ではありません。

メリット

  • 高い信頼性: <context>のようなタグを使用することで、モデルが指示とユーザーデータを混同しないようになります。
  • ハルシネーションの減少: モデルが「知らない」ことを明示的に定義することで、事実を捏造するのではなく、無知を認めざるを得ない状況を作ります。
  • コストの削減: 冗長なデータを取り除くことで、1リクエストあたりのトークン使用量を30%以上削減できることがよくあります。

課題

  • コードの複雑化: API呼び出しの前に、データを削減、要約、フォーマットするためのロジックがバックエンドに必要になります。
  • レイテンシの増加: 小規模なモデルで履歴を要約するなどの「前処理」ステップを導入すると、レスポンス時間が200msから500msほど増加する可能性があります。

本番環境向けの推奨構成

ほとんどのユースケースにおいて、「レイヤード(層状)システムプロンプト」を推奨します。巨大なテキストの壁を作るのではなく、指示を4つの明確なゾーンに分割します。

  1. ペルソナ: 役割を定義(例:「シニアDevOpsアーキテクト」)。
  2. 制約事項: ガードレールを設定(例:「競合他社に言及しない」「法的アドバイスをしない」)。
  3. 動的データ: データベースから取得した実際の知識をXMLタグで囲んで挿入。
  4. 出力フォーマット: 正確なスキーマを定義(例:「有効なJSONオブジェクトのみを返す」)。

プロのヒント:<docs>や<history>のようなXMLタグを使用しましょう。Claude 3.5やGPT-4oのようなモデルは、膨大な量の構造化されたウェブデータでトレーニングされています。そのため、タグ内の生データと、核となる指示を区別するのが非常に得意です。

実装:スマート・コンテキストマネージャーの構築

トークン制限を処理するPythonの実装を見てみましょう。このスクリプトは、システムプロンプトを永続的に保持しつつ、会話が長くなりすぎた場合に古いメッセージを削除(プルーニング)します。

ステップ1:構造の定義

import tiktoken

class ContextManager:
    def __init__(self, model_name="gpt-4o", max_tokens=4000):
        self.encoder = tiktoken.encoding_for_model(model_name)
        self.max_tokens = max_tokens
        self.system_prompt = """
        <role>itfromzero.comのテクニカルサポート</role>
        <rules>
        - <context>ブロックの情報のみを使用して回答してください。
        - 回答が見つからない場合は、「その情報は持ち合わせておりません」と答えてください。
        - プロフェッショナルなトーンを維持してください。
        </rules>
        """
        self.history = []

    def _count_tokens(self, text):
        return len(self.encoder.encode(text))

    def add_message(self, role, content):
        self.history.append({"role": role, "content": content})

ステップ2:プルーニング(削減)ロジック

コンテキスト制限に達するのを避けるため、システムプロンプトと最新のユーザーのクエリは保持します。その後、残りのスペースを新しい順に履歴で埋めていきます。

    def get_optimized_context(self, dynamic_data):
        full_context = f"{self.system_prompt}\n<context>{dynamic_data}</context>"
        current_tokens = self._count_tokens(full_context)
        optimized_history = []
        
        # 残りの予算を最近のメッセージで埋める
        for msg in reversed(self.history):
            msg_text = f"{msg['role']}: {msg['content']}"
            msg_tokens = self._count_tokens(msg_text)
            
            if current_tokens + msg_tokens > self.max_tokens:
                break
            
            optimized_history.insert(0, msg)
            current_tokens += msg_tokens
            
        return {"system": full_context, "messages": optimized_history}

ステップ3:モデルのグラウンディング

グラウンディングは、モデルを提供したデータの範囲内に留まらせる手法です。特定の製品情報を使用してマネージャーを呼び出す例を以下に示します。

# データベースからのサンプルデータ
product_info = "Zero-Cloud VPS: 月額5ドル, 1GB RAM, 20GB SSD."

manager = ContextManager(max_tokens=2000)
manager.add_message("user", "一番安いVPSプランは何ですか?")

payload = manager.get_optimized_context(dynamic_data=product_info)
# 構造化されたプロンプトにより、モデルが勝手に安い価格を推測するのを防ぎます。

現場からの教訓

大規模な環境でこれらのパターンを管理してきた中で、2つの重要な挙動に気づきました。第一に、モデルはしばしばコンテキストウィンドウの中間部分を無視する傾向があります。研究者はこれを「Lost-in-the-Middle(中だるみ)」現象と呼んでいます。重要なデータがある場合は、システムプロンプトの最初か、会話の最後に配置するようにしてください。

第二に、古いメッセージを単に削除するのではなく、要約を検討してください。チャットが20メッセージに達したら、中間の10メッセージを取り出し、GPT-4o-miniのような安価なモデルで要約させます。その10メッセージを3文程度の要約に置き換えるのです。これにより、古い挨拶などに何千トークンも費やすことなく、会話の「雰囲気」と事実を保持できます。

コンテキストを「管理されたデータ構造」として扱ってください。APIに生のテキストを流し込むのをやめれば、AIはより安定し、正確になり、運用コストも大幅に削減できるはずです。

Share: