コンテキストエンジニアリングとは、言語モデルが答えるときに目にするすべてのものを決める作業です。入力する質問だけでなく、そのまわりの指示、そこに差し込まれる文書やツールの結果、ユーザーについて保存されたメモリ、それまでの会話も含みます。そのすべてが1つのコンテキストウィンドウを共有し、モデルはそのテキストだけから答えます。この用語は、コンテキストの大半を自動で組み立てるエージェント型の AI 製品が増えた2025年に広まりました。
プロンプトエンジニアリングは、主に依頼をどう言葉にするかを扱います。コンテキストエンジニアリングは、呼び出しのたびにモデルの目の前に何を、どの順番で置くべきかを扱います。
プロンプトからコンテキストへ
チャットアプリでは、コンテキストの大半を自分で書きます。アプリがシステムプロンプトと履歴を加え、残りはあなたが加えます。アプリケーションではこのバランスが逆転します。ユーザーが1文入力すると、モデルのまわりのコードが、指示、ユーザーのプロフィール、検索で見つけたヘルプ記事3本、使えるツールの一覧、直前のツール呼び出しの出力を加えます。ユーザーの1文は、モデルが読むもののごく一部かもしれません。
そうしたシステムの答えが悪いとき、言い回しを直して解決することはまれです。よくある原因は、モデルが間違った素材を持っていたことです。足りない事実、古いツールの結果、検索では関係ありそうに見えた無関係な文書などです。
コンテキストウィンドウに入るもの
AI アプリケーションの典型的な呼び出しには、次のものの一部またはすべてが、おおよそこの順番で含まれます。
- システムの指示:役割、ルール、出力形式。たいていアプリ全体で固定です。システムプロンプトを参照してください。
- ツールの定義:モデルが呼び出せるツールの名前、説明、パラメータ。
- 例:期待されるふるまいを示す、入力と出力のサンプルをいくつか。
- メモリ:ユーザーのプラン、言語、好みなど、以前のセッションから保存された事実。
- 検索で取ってきた文書:この質問のためにナレッジベースを検索して見つけた箇所(検索拡張生成、RAG)。
- 会話履歴:それまでのターン。そのままか、要約した形で。
- ツールの結果:ReAct のループのように、このタスクの中で行った検索、コードの実行、API 呼び出しの出力。
- 現在のメッセージ:ユーザーが今尋ねたこと。
下のブロックは、サポートアシスタントのために組み立てたコンテキストの1例です。パートを1つずつオフにしてみてください。コンテキストのパートがないと、モデルは顧客のプランを知ることができません。入力のパートがないと製品についての事実がなく、制約に従って、推測せずにそう伝えます。
Dana さん、オフラインモードは Pro プランの機能で、現在のアカウントは Free プランのため、今のところご利用いただけません。Pro なら、フライト中に作成したメモはスマートフォンが再接続したときに自動で同期されます。1点ご注意いただきたいのは、20 MB を超える添付ファイルはオフラインではご利用いただけないことです。
正しい答えが2つの情報源を組み合わせることで成り立っている点に注目してください。メモリ(Free プラン)と文書(オフラインモードは Pro のみ)です。どちらか一方だけでは足りず、これはよくあることです。コンテキストエンジニアリングの大部分は、互いを必要とする部品がそろって届くようにすることです。
コンテキストがうまくいかない4つのパターン
- 情報が足りない。モデルは穴をもっともらしい推測で埋め、多くのハルシネーションはそこから生まれます。事実を加えるか、事実がないときにどうするかをモデルに伝えてください。
- 素材が多すぎる。無関係な段落の1つ1つがトークンを消費し、注意を奪い合います。Liu らの2023年の論文「Lost in the Middle: How Language Models Use Long Contexts」は、試したモデルが、長い入力の中ほどにある情報より、最初か最後にある情報をより確実に使うことを見つけました。新しいモデルは長い入力をもっとうまく扱いますが、それでもマニュアル全体を貼り付けるより、質問に答える数か所を送るほうが安く、モデルにとっても使いやすくなります。
- 情報が古い。10ステップ前のツールの結果が、その後変わったファイルや残高を表していることがあります。両方の版が見えていると、モデルは古いほうを使うかもしれません。
- 矛盾している。2つの文書の内容が食い違っている、あるいはメモリとユーザーの言うことが違う。「ユーザーの最新のメッセージが保存されたメモリより優先される」のように、どの情報源を優先するかをモデルに伝えてください。
コンテキストの並べ方
順番は結果とコストの両方を変えます。
- 変わらない部分を先に。 システムの指示、ツールの定義、固定の参考資料は、呼び出しのあいだでほとんど変わりません。いくつかの API 提供元はプロンプトキャッシュを提供していて、入力の同一の冒頭部分の処理を再利用するので、変わらない前半部分があれば、繰り返しの呼び出しが安く速くなります。
- 長い素材は質問の前に。 長い文書や大量の抜粋は、素材を先に置き、質問と最後の指示をそのあとに置きます。たとえば Anthropic のプロンプトのガイドは、長い入力にはこの順番をすすめており、モデルが書きはじめる直前に質問が来るようにしています。
- すべての部分にラベルを付ける。 各情報源を
<document>、<memory>、<tool_result>のようなタグで、情報源の名前とともに囲みます。ラベルがあれば、モデルはデータと指示を見分けられ、答えがどこから来たかを引用させることもできます。形式は区切り文字と XML タグで扱っています。
長いコンテキストを削る
チャットは毎ターン履歴全体を送り直すので、長いセッションは何かを削らなければならなくなるまで膨らみ続けます。アプリによって、古いメッセージを要約するか削るか、新しいチャットを始めるよう求めます。意図して削るほうが良い結果が得られます。
- 直近の数ターンは一字一句そのまま残し、古いターンは要約に置き換える。
- ツールの結果は、使い終わったら、何がわかったかを1行で書いたメモに置き換える。
- どの要約にも正確な識別子を残す。ファイル名、関数名、ID、エラーメッセージ、数値。履歴では
settings.prod.yamlと書いていたところを「設定ファイル」と要約すると、いちばん重要だった細部が失われます。 - チャットアプリで会話が脱線してきたら、引き継ぎメモを持って新しいチャットを始める。下のプロンプトがそれを書きます。
目的:会員登録フォームの手書きのチェックを、ブラウザとサーバーの両方で使う共通のバリデーションスキーマ1つに置き換える。
決めたこと
- スキーマのファイルは
signupSchema.tsの1つにし、SignupForm.tsxとapi/signup.tsの両方から読み込む。2つのルールがずれないようにするため。 - エラーメッセージは一字一句変えない。ヘルプセンターで引用しているため。
現在の状態
- ブラウザ側のバリデーションはスキーマを使っていて、
SignupForm.test.tsxは通っている。 - サーバー側はまだ
api/signup.tsの中の古いvalidateSignup()を呼んでいる。
正確な詳細:パスワードは8文字以上で数字を1つ含むこと。メールのエラー文言:「有効なメールアドレスを入力してください。」
次のステップ:validateSignup() をスキーマに置き換え、API のテストを実行する。
未解決の疑問点:登録済みのメールアドレスには 409 と 400 のどちらを返すべきか?
セッションをまたぐメモリ
メモリとは、会話より長く生き残るコンテキストです。あるセッションの終わりにストレージに書き込まれ、次のセッションで読み込まれる事実です。チャットアプリにも、保存されたメモリや、プロジェクト内のすべてのチャットに加えられるプロジェクトの指示のような形でこれがあります。自分のアプリケーションでは、メモリはコードが読み込んで差し込むテーブルやメモのファイルです。役に立つ状態に保つルールが2つあります。会話の記録ではなく、変わらない事実(プラン、言語、好みの技術スタック)を保存すること。そして、今のタスクに関係するものだけを読み込むこと。メモリはほかのすべてのものと同じ場所を奪い合うからです。
コードでコンテキストを組み立てる
アプリケーションでは、コンテキストエンジニアリングはふつうのコードです。Anthropic の Python SDK を使ったこのスケッチは、固定のルールとメモリをシステムプロンプトに入れ、直近の履歴だけを残し、ラベルを付けた文書を質問の前に置いています。
import anthropic
client = anthropic.Anthropic()
MODEL = "your-model-id" # e.g. from your provider's model list
def build_context(question, docs, history, memory, max_messages=6):
documents = "\n".join(
f'<document source="{d["source"]}">\n{d["text"]}\n</document>' for d in docs
)
system = (
"You are the support assistant for Acme Notes. Answer only from the documents. "
"If they do not cover the question, say so.\n"
f"<memory>\n{memory}\n</memory>"
)
# history holds complete user/assistant pairs, so an even slice starts with a user turn
recent = history[-max_messages:]
user = f"<documents>\n{documents}\n</documents>\n\n{question}"
return system, recent + [{"role": "user", "content": user}]
# question, docs, history and memory come from your application
system, messages = build_context(question, docs, history, memory)
response = client.messages.create(model=MODEL, max_tokens=1024, system=system, messages=messages)
print(response.content[0].text)
この関数の中の判断の1つ1つ(どの文書を入れるか、メッセージをいくつ残すか、メモリをどこに置くか)がコンテキストエンジニアリングの選択であり、言い回しの変更を試すのと同じように、どれも本物の質問で試す価値があります。
よくある質問
コンテキストエンジニアリングとは何ですか?
コンテキストエンジニアリングとは、言語モデルが1回の呼び出しで受け取るすべてのものを選び、並べ、削る作業です。システムの指示、例、検索で取ってきた文書、ツールの定義と結果、保存されたメモリ、会話履歴、ユーザーのメッセージが含まれます。モデルはそのテキストだけから答えるので、何が入っていて何が抜けているかが、答えの質を決めます。
コンテキストエンジニアリングとプロンプトエンジニアリングの違いは何ですか?
プロンプトエンジニアリングは主に指示の言い回しを扱います。コンテキストエンジニアリングは入力全体を扱い、その多くは人が入力するのではなくコードが組み立てます。どの文書を検索して取ってくるか、どのツールの結果を残すか、履歴をどれだけ入れ、どの順番にするか。チャットではコンテキストの大半を自分で書きますが、アプリやエージェントでは、その大半をモデルのまわりのシステムが選びます。
コンテキストは多ければ多いほど良いのですか?
いいえ。無関係な素材や古い素材は重要な部分と競い合い、トークンを消費し、現在の状態と矛盾することもあります。長い入力についての研究では、長いコンテキストの中ほどに置かれた情報をモデルが見落とすことがあるとわかっています。タスクに必要なものを入れ、ラベルを付け、もう必要なくなったものは削ってください。
長いチャットがだんだん悪くなるのはなぜですか?
会話全体が毎ターン送り直されるので、古い間違い、捨てたアイデア、置き換えられたコードがコンテキストに残り、返答に影響し続けます。チャットがコンテキストウィンドウを超えると、アプリは古いメッセージを削るか要約するしかありません。決定事項と現在の状態の短い要約を持って新しいチャットを始めるほうが、続けるよりうまくいくことがよくあります。
コンテキストエンジニアリングにおける RAG とは何ですか?
RAG(検索拡張生成)とは、質問に関係する箇所を自分たちの文書から検索し、モデルが答える前にコンテキストに入れることです。コンテキストエンジニアリングの主な道具の1つで、モデルは学習からは知りえない最新の具体的な事実を得られ、その箇所だけから答えるよう指示することもできます。