LangChain Module 8: Middleware, Limits, and Human Approval
Edited by Dev Kilaru, Senior Data Analyst. How we make our content
Introduction
Middleware lets you plug logic into the agent loop without rewriting it: condense history, ask a human before a risky action, limit how many calls an agent can make, or log what is happening. This module introduces the built-in options and shows how to write a simple hook of your own.
Prerequisites
- Modules 4 and 5 completed
Step-by-Step Guide
Step 1: Attach middleware to an agent
Pass a list to the middleware argument of create_agent. Middleware runs around the model and tool calls, and the order of the list matters when pieces interact, such as tool retries and tool error handling.
from langchain.agents import create_agent
from langchain.agents.middleware import SummarizationMiddleware
agent = create_agent(
model=model,
tools=[...],
middleware=[SummarizationMiddleware(model="anthropic:claude-haiku-5-5",
trigger=("tokens", 4000),
keep=("messages", 20))],
)Step 2: Know what is built in
LangChain ships middleware for tool retries and tool errors, model retries and fallbacks, summarization, human approval, limits on model calls and tool calls, PII detection, to-do lists, tool selection, and context editing. Check the built-in middleware page for the exact class names and options in your version before using them.
Step 3: Require human approval for risky tools
HumanInTheLoopMiddleware pauses the agent before the tools you list and lets a person approve, edit, or reject the call. It needs a checkpointer, because the agent must save its state while it waits. Setting a tool to False lets it run without approval.
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langgraph.checkpoint.memory import InMemorySaver
agent = create_agent(
model=model,
tools=[send_email, read_email],
checkpointer=InMemorySaver(),
middleware=[
HumanInTheLoopMiddleware(
interrupt_on={
"send_email": {"allowed_decisions": ["approve", "edit", "reject"]},
"read_email": False,
}
)
],
)Step 4: Resume a paused run
When an approval is pending, the run stops and reports the interrupt. You resume it with the same thread_id by sending a decision back using LangGraph's Command(resume=...), which Module 6 of the LangGraph course explains in depth. The exact shape of the decision payload for this middleware is in the human-in-the-loop documentation, so follow it for your version.
Step 5: Write a simple custom hook
Decorators such as before_model create middleware from a function. The function receives the agent state and a runtime, and returns a dictionary of state changes, or None to change nothing. This one only logs, which is a safe way to start.
from typing import Any
from langchain.agents import AgentState
from langchain.agents.middleware import before_model
from langgraph.runtime import Runtime
@before_model
def log_message_count(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
print("Messages going to the model:", len(state["messages"]))
return None
agent = create_agent(model=model, tools=[...], middleware=[log_message_count])FAQs
- When should I use middleware instead of changing the prompt?
- Use middleware for behavior that must hold regardless of what the model says: limits, approvals, redaction, and logging. Prompts are suggestions, but middleware is enforced in code.
- Does human approval require a database?
- It requires a checkpointer. InMemorySaver is fine for testing, but production needs a persistent one so a pending approval survives restarts.
Summary
Middleware adds enforced behavior to the agent loop. Use summarization for long chats, human approval for risky tools, limits and retries for reliability, and small decorator hooks for custom logic.
Comments
Log in or create a free account to join the discussion.