AKARI Tech Blog

燈株式会社のエンジニア・開発メンバーによる技術ブログです

Context Engineeringってなんで流行ってるの?

こんばんは!Tech blogの時間がやってまいりました。
DX Solution 事業本部 Dev の許が担当いたします。

最近はGPT-5やらgpt-ossやら、OpenAIが立て続けに大きな発表をしましたね!
特にgpt-ossはOpenAIが6年ぶりに公開するオープンウェイトなモデルということもあり、OpenAIがオープンなモデルを出したことが話題になりました!


ありがとう、アルトマン……!


今年に入ってからLLMもどんどん進化してきて、いまではベンチマークの結果で人間を超えることも当たり前になってきていますね。
そうなるとLLMが十分に賢いことを前提となり、「如何にして適切な情報を渡してやれるか」というのが今後の焦点になってくると考えられます。


そんな中で流行したワードとして、「Context Engineering(コンテキストエンジニアリング)」があります。
いままでもRAGやprompt engineeringなど、LLMに追加の情報を与える手法がいろいろありましたが、それらをひっくるめて「LLMの判断に必要なコンテキストを渡すこと」を再定義したものになります。


なんでいまContext Engineeringなの?

Prompt Engineeringってなんか微妙じゃね?

『Context Engineering』という言葉は、ChatGPTが世に出て間もない頃から、すでに現在のような意味で使い始める人がいました。

この時期だと主に「Prompt Engineeringという単語って微妙じゃね?」という意見が目立ちます。

というのも、プロンプトエンジニアリングだけだと、

  • LLMのモデルへの依存性が高く、知識資産の陳腐化が早い
  • 特定のユースケースや入力前提に最適化されがちで、再利用性が低い
  • 「文章の最初/最後に重要なことを書くべき」など、表層的な文字列最適化に偏りがちで、かつ「良くなった/悪くなった」の評価も曖昧

といった欠点があり、LLMの性能を引き出すエンジニアリング全体をカバーできないからですね。
そのため、「プロンプトエンジニアリング」だけではLLMの性能を最大限引き出すための設計をカバーしきれない、コンテキスト全体を設計しLLMに渡す「コンテキストエンジニアリング」といったワードの方が良い、という意見ですね。


さらに、ChatGPTやClaude、GeminiのモデルなどのLLMのモデルの性能が上がるにつれてシステムプロンプトが洗練されていき、ユーザーがそこまでプロンプトを工夫しなくても高品質な応答が得られるようになりました。
結果として、かつて重要視されていたプロンプトエンジニアリングの必要性は低下していき、RAGなどの「外部情報をいかにして与えてやるか」といった技術にフォーカスがあたっていきました。

The Era of AI Agents

LLMの性能が上げるにつれて、「如何にして人間のやることを代替させるか」「LLMが自律的に行動するにはどうすればいいか」という動きが目立つようになりました。

  • Cursor、Cline、Devin、Claude Codeといったコーディングエージェント
  • Deep Researchといったリサーチエージェント
  • 2025年3月に話題になったManusなどの汎用エージェント

など、昨今はあらゆるところでAIエージェントの話題を耳にするようになりましたね。


そしてAIエージェントの設計に関しては、LLMへの入力情報を如何にして集めるかが非常に重要になってきます。
そうなるとAIエージェントの設計に必要な要素をひっくるめてまとめたくなりますよね。
ちょうどこれまでのLLMを使った開発や設計の知見も溜まっていたため、これらの概念が一つの単語に集約されていきました。

それが「Context Engineering」だった、というわけです。

主に技術者や開発者コミュニティで話題になった後、2025年6月頃にShopify CEOのツイートがバズったのを区切りに、様々な著名人が「Context Engineering」について言及し、LangChainLlamaIndexといったコミュニティでもこれまでの概念をまとめ直す動きが見られました。


結果として「Context Engineering」がここ最近のホットなワードとなっています。

Context Engineeringってどんなものがあるの?

いろいろな定義

「Context Engineering」は現在概念の再整理中なため、みんななんとなく同じようなことを指して「Context Engineering」と言っていますが、細かいところの定義がまだ異なっています。

コンテキストエンジニアリングを調べたサーベイ論文A Survey of Context Engineering for Large Language Models がまとめた図が下記となります。

Context Engineeringの進化の過程

わお。こういった俯瞰図を見ると、これまでの進化の過程が思い返されてちょっと感動してきますね。

LlamaIndexやLangChainといったコミュニティではどのように定義されているのでしょうか。



LlamaIndexではコンテキストとはなにか?を下記のように説明しています。

  • The system prompt/instruction
  • The user input
  • Short term memory or chat history
  • Long-term memory
  • Information retrieved from a knowledge base
  • Tools and their definitions
  • Responses from tools
  • Structured Outputs
  • Global State/Context (llamaindexにおける、外部のscratchpad)

www.llamaindex.ai



LangChainでもContext Engineeringの具体例を記載しています。

  • 1. Prompt Engineering (プロンプト設計)
  • 2. Short term memory (短期メモリ)
  • 3. Long term memory (長期メモリ)
  • 4. Retrieval (外部知識検索)
  • 5. Tool use (ツールの使用)

blog.langchain.com


また実際にどのようにContext Engineeringを行うかを4ステップに分けて解説しています。




6. Context Engineeringのやり方 (コンテキスト管理)

  • Write Context
    • Scratchpads (外部のメモ帳)
    • Memories (複数の部分で保持するメモリ)
    • State (エージェント内部の状態)
  • Select Context (コンテキスト選択)
    • Scratchpads
    • Memories
    • Tools
    • Knowledge (RAGなど)
  • Compressing Context (コンテキスト圧縮)
    • Context Summarization
    • Context Trimming
  • Isolating Context (コンテキスト分割)
    • Multi-agent
    • Context Isolation with Environments
    • State



blog.langchain.com



せっかくなのでLangchain&LangGraphのコード例といっしょに見ていきましょうか。

Context Engineeringのコード例

1. プロンプト設計 (System prompt, User input, Prompt Engineering)

毎度おなじみ、システムプロンプトとユーザープロンプトの分割。
プロンプトエンジニアリングもここで行う。

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.messages import SystemMessage

template = ChatPromptTemplate.from_messages([
    ("system", "あなたは親切なアシスタントです。"),
    ("human", "{user_input}")
])
prompt_value = template.invoke({"user_input": "今日の天気は?"})
print(prompt_value)
2. 短期メモリ(Short term memory, Chat history

直近の会話履歴を保持し、モデルに現在の文脈として提供する機構。

from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import StateGraph, MessagesState, START
from langchain_core.messages import SystemMessage, HumanMessage
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_openai import ChatOpenAI

# checkpointer(短期メモリ用)
checkpointer = InMemorySaver()

# グラフにテンプレートを定義
prompt = ChatPromptTemplate.from_messages([
    ("system", "あなたは親切なアシスタントです。"),
    MessagesPlaceholder("messages")
])

# (ノード定義など省略)
builder = StateGraph(state_schema=MessagesState)
graph = builder.compile(checkpointer=checkpointer)

# 会話スレッドの流れを invoke によって維持
graph.invoke(
    {"messages": [{"role": "user", "content": "こんにちは。"}]},
    {"configurable": {"thread_id": "thread-123"}}
)
graph.invoke(
    {"messages": [{"role": "user", "content": "昨日の話を覚えてる?"}]},
    {"configurable": {"thread_id": "thread-123"}}
)
3. 長期メモリ(Long term memory)

ユーザ設定や過去対話など、セッションを跨いで保持・活用する記憶。

from langchain_openai import OpenAIEmbeddings
from langchain_milvus import Milvus
from langchain.memory import VectorStoreRetrieverMemory
from langchain.chains import ConversationChain
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.documents import Document

# Embeddings と Milvus の初期化
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
URI = "./milvus_example.db"
vector_store = Milvus(
    embedding_function=embeddings,
    connection_args={"uri": URI},
)
# documentから生成する場合
vector_store = Milvus.from_documents(
    [Document(page_content="初回の会話内容")],
    embeddings,
    collection_name="chat_memory",
    connection_args={"uri": URI},
)

# retriever の作成と長期メモリの設定
retriever = vector_store.as_retriever(search_kwargs={"k": 1})
memory = VectorStoreRetrieverMemory(retriever=retriever)

# 組み込み
chain = ConversationChain(
    llm=ChatOpenAI(),
    prompt=PromptTemplate(template="履歴:\n{history}\nUser: {input}", input_variables=["history", "input"]),
    memory=memory,
    verbose=True
)

# 初期保存と会話テスト
memory.save_context({"input": "私の名前はボブです。"}, {"output": "了解です。"})
print(chain.predict(input="私の名前を覚えてる?"))

4. 外部知識検索(Information retrieved from a knowledge base)

外部ドキュメントやベクトルDBから関連情報を検索し、文脈に追加して応答の事実性や精度を高める手法。
RAGなど。

from langchain_community.document_loaders import WebBaseLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai.embeddings import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_openai.chat_models import ChatOpenAI

# ドキュメントを取得し、分割
loader = WebBaseLoader("https://lilianweng.github.io/posts/2023-06-23-agent/")
docs = loader.load()
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=0)
chunks = text_splitter.split_documents(docs)

# Embeddings を生成し、FAISS に格納
embeddings = OpenAIEmbeddings()
vectorstore = FAISS.from_documents(documents=chunks, embedding=embeddings)

# LLM を初期化
llm = ChatOpenAI()
5. ツールの使用(Tool use, Tools and their definitions, Responses from tools)

関数呼び出しやAPIアクセスなど、外部ツールを文脈内に定義し、必要に応じてLLMが呼び出せるようにする機構。
MCPなどもここに分類。

from langchain_core.tools import tool
from langchain_openai import ChatOpenAI

#  ツール定義
@tool
def multiply(a: int, b: int) -> int:
    """Multiply two numbers."""
    return a * b

# モデル初期化
from langchain.chat_models import init_chat_model
model = init_chat_model(model="claude-3-5-haiku-latest")

# .bind_tools() によりツールをモデルに紐づける
model_with_tools = model.bind_tools([multiply])

# ユーザ入力を与えてツール呼び出しを試す
response = model_with_tools.invoke("What is 5 multiplied by 7?")

# ツール呼び出し情報を取得
print("Tool calls:", response.tool_calls)
6. コンテキスト管理(Global State/Context)

Scrachpad、memory、stateなどのコンテキストを管理。
LangChainでは先ほど説明した4ステップで色々実装している。

  • Write:クラッチパッドやStateオブジェクトへ文脈を保存し、文脈ウィンドウから外して記憶を保持
  • Select:必要な情報だけを文脈ウィンドウへ選択的に挿入
  • Compress:要約やトリミングにより文脈を圧縮しトークン消費を削減
  • Isolate:文脈を分割・モジュール化し、誤誘導や混乱を低減
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.store.memory import InMemoryStore
from langchain.openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage

# グラフと scratchpad の作成
state_store = InMemoryStore()
graph = StateGraph(MessagesState)
# ノード定義:Generate → Compress → Respond
def write_to_scratchpad(state: MessagesState) -> dict:
    return {"scratch": state["messages"]}  # スクラッチ保管

def compress_scratchpad(state: MessagesState) -> dict:
    msgs = state["scratch"]
    # Compress: 単純に最後の1メッセージだけ保持
    return {"compressed": msgs[-1] if msgs else ""}

def generate_response(state: MessagesState) -> dict:
    prompt = [
        SystemMessage(content="あなたは親切なアシスタントです。"),
        HumanMessage(content=state["compressed"])
    ]
    response = ChatOpenAI(temperature=0).invoke(prompt)
    return {"response": response.content}

# グラフにノードを追加
graph.add_node("write", write_to_scratchpad)
graph.add_node("compress", compress_scratchpad)
graph.add_node("respond", generate_response)
graph.add_edge(START, "write")
graph.add_edge("write", "compress")
graph.add_edge("compress", "respond")
graph.add_edge("respond", END)

# 実行
chain = graph.compile(checkpointer=state_store)
result = chain.invoke({"messages": [HumanMessage(content="こんにちは!")]})
print(result["response"])

まとめ

ここまででContext Engineeringが流行するまでの流れ、及び実装例はイメージできたでしょうか。
おそらく「普段使っていたあの技術も、Context Engineeringに組み込まれていたのか」と気づかれたのではないでしょうか。
そうです、RAGやtool useといったツールも、コンテキストを記録、選択、圧縮、分割してた各操作も、Context Engineeringの一部です。

筆者としても、正直プロンプトエンジニアリングという単語にはずっと違和感があったため、より本質を表す「コンテキストエンジニアリング」という言葉になって嬉しい限りです。
今回の記事は概念をさらうくらいに留めたので、いずれまたコンテキスト管理全体、特にコンテキスト圧縮などの手法について深堀りして紹介したいですね。

世はまさにAIエージェントの時代。
この時代のコンテキストをも飲み込んで、引き続き開発していきましょうか。

We’re Hiring!

燈では、LLMと対話するのが好きなAIエンジニアを募集しています!
興味がある方は、ぜひカジュアル面談でお話しましょう!


akariinc.co.jp