| name | langchain-context-engineering |
| description | Engineer reliable LangChain agents by controlling model context, tool context, and life-cycle context. Use when building or debugging agents with dynamic prompts, message injection, tool filtering, model selection, structured output, or conversation summarization. Covers state (short-term), store (long-term), and runtime context (static config) patterns using middleware. |
Documentation Index
Fetch the complete documentation index at: https://docs.langchain.com/llms.txt
Use this file to discover all available pages before exploring further.
Context Engineering in LangChain Agents
Core premise: Agent failures are almost always caused by the wrong context reaching the LLM — not a capability gap. Context engineering is passing the right information and tools in the right format.
Data Sources
| Source | Also known as | Scope | Examples |
|---|
state | Short-term memory | Conversation | Messages, uploaded files, auth status |
store | Long-term memory | Cross-conversation | User preferences, memories, history |
runtime context | Static config | Conversation | User ID, API keys, permissions, env |
Three Context Types
| Type | Controls | Transient or Persistent |
|---|
| Model context | What goes into each model call | Transient |
| Tool context | What tools read/write | Persistent |
| Life-cycle context | What happens between steps | Persistent |
Model Context
All model context is modified via middleware. Use @dynamic_prompt for system prompt, @wrap_model_call for messages/tools/model/response format.
System Prompt — @dynamic_prompt
from langchain.agents.middleware import dynamic_prompt, ModelRequest
@dynamic_prompt
def adaptive_prompt(request: ModelRequest) -> str:
user_role = request.runtime.context.user_role
user_prefs = request.runtime.store.get(("prefs",), request.runtime.context.user_id)
msg_count = len(request.messages)
base = "You are a helpful assistant."
if user_role == "admin":
base += "\nYou have admin access."
if msg_count > 10:
base += "\nBe extra concise — long conversation."
return base
Messages — @wrap_model_call
Transient injection (does not persist to state):
from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse
from typing import Callable
@wrap_model_call
def inject_context(request: ModelRequest, handler: Callable) -> ModelResponse:
uploaded_files = request.state.get("uploaded_files", [])
if uploaded_files:
file_summary = "\n".join(f"- {f['name']}: {f['summary']}" for f in uploaded_files)
messages = [*request.messages, {"role": "user", "content": f"Files available:\n{file_summary}"}]
request = request.override(messages=messages)
return handler(request)
Tools — dynamic filtering
@wrap_model_call
def filter_tools(request: ModelRequest, handler: Callable) -> ModelResponse:
user_role = request.runtime.context.user_role
if user_role == "viewer":
tools = [t for t in request.tools if t.name.startswith("read_")]
request = request.override(tools=tools)
return handler(request)
Model — dynamic selection
from langchain.chat_models import init_chat_model
large = init_chat_model("claude-sonnet-4-6")
standard = init_chat_model("gpt-5.4")
budget = init_chat_model("gpt-5.4-mini")
@wrap_model_call
def select_model(request: ModelRequest, handler: Callable) -> ModelResponse:
cost_tier = request.runtime.context.cost_tier
model = large if cost_tier == "premium" else budget if cost_tier == "budget" else standard
return handler(request.override(model=model))
Response Format — dynamic schema
from pydantic import BaseModel, Field
class SimpleResponse(BaseModel):
answer: str = Field(description="Brief answer")
class DetailedResponse(BaseModel):
answer: str
reasoning: str
confidence: float = Field(description="0-1")
@wrap_model_call
def select_format(request: ModelRequest, handler: Callable) -> ModelResponse:
schema = DetailedResponse if len(request.messages) >= 3 else SimpleResponse
return handler(request.override(response_format=schema))
Tool Context
Reading context in tools
from langchain.tools import tool, ToolRuntime
@tool
def fetch_user_data(query: str, runtime: ToolRuntime) -> str:
"""Fetch data for the current user."""
user_id = runtime.context.user_id
api_key = runtime.context.api_key
auth = runtime.state.get("authenticated")
prefs = runtime.store.get(("prefs",), user_id)
...
Writing to state via Command
from langgraph.types import Command
@tool
def authenticate_user(password: str, runtime: ToolRuntime) -> Command:
"""Authenticate the user and persist status."""
success = verify_password(password)
return Command(update={"authenticated": success})
Writing to store (persists across conversations)
@tool
def save_preference(key: str, value: str, runtime: ToolRuntime) -> str:
"""Save a user preference for future sessions."""
user_id = runtime.context.user_id
existing = runtime.store.get(("prefs",), user_id)
prefs = existing.value if existing else {}
prefs[key] = value
runtime.store.put(("prefs",), user_id, prefs)
return f"Saved {key} = {value}"
Life-cycle Context (Middleware)
Built-in: Conversation summarization
Permanently replaces old messages with a summary in state:
from langchain.agents.middleware import SummarizationMiddleware
agent = create_agent(
model="gpt-5.4",
tools=[...],
middleware=[
SummarizationMiddleware(
model="gpt-5.4-mini",
trigger={"tokens": 4000},
keep={"messages": 20},
),
],
)
Registering middleware
from langchain.agents import create_agent
agent = create_agent(
model="gpt-5.4",
tools=[...],
middleware=[adaptive_prompt, inject_context, filter_tools, select_model],
context_schema=Context,
store=InMemoryStore(),
)
Key Rules
@dynamic_prompt — system prompt only; receives ModelRequest, returns str
@wrap_model_call — messages, tools, model, response format; must call handler(request) and return result
request.override(...) — creates a modified copy; never mutates in place
request.messages — shortcut for request.state["messages"]
- Transient vs persistent:
wrap_model_call changes are per-call only; Command updates and store.put() persist
context_schema — pass a dataclass; access via request.runtime.context.field or runtime.context.field in tools
store required — must be passed to create_agent to use runtime.store
Related Resources
- Middleware guide:
/oss/python/langchain/middleware
- Tools guide:
/oss/python/langchain/tools
- Agents guide:
/oss/python/langchain/agents
- Context concepts:
/oss/python/concepts/context
- Memory patterns:
/oss/python/concepts/memory