Customizing Agents with Middleware
Explore how to customize LangChain agents using middleware to extend default behaviors such as retrying failed tool calls, summarizing message history, logging model latency, and pausing for human approval. This lesson helps you integrate reusable middleware hooks into create_agent for more robust and flexible AI agent workflows.
We'll cover the following...
- Why middleware?
- Node-style hooks: before_model and after_model
- Wrap-style hooks: wrap_model_call and wrap_tool_call
- A built-in alternative: ToolRetryMiddleware and ModelRetryMiddleware
- SummarizationMiddleware: Keeping conversations short
- HumanInTheLoopMiddleware: Pausing for approval
- Putting it together
- What’s next?
create_agent constructs the model-and-tools loop that we previously implemented manually: invoke the model, execute any requested tools, and continue until the model returns no further tool calls. By default, the agent follows this standard loop. Middleware lets us extend it to log model calls, automatically retry transient tool failures, summarize or trim message history before it exceeds context limits, and pause configured tool calls for human review. A custom StateGraph could also implement these behaviors, but middleware lets us add them directly to the agent created by create_agent.
Why middleware?
Middleware exposes hooks at specific stages of the agent loop, allowing us to inspect, modify, or wrap model and tool execution. It provides an alternative to using the default behavior unchanged or implementing a custom StateGraph. We can retain create_agent while adding the required behavior through reusable middleware.
We pass middleware to create_agent as a list:
graph = create_agent(model="openai:gpt-5",tools=[get_weather],middleware=[SomeMiddleware()],)
Custom middleware supports two hook styles: node-style hooks that run at specific lifecycle points, such as before or after the agent or model, and wrap-style hooks that run around each model or tool call and control how the underlying handler executes.
Node-style hooks: before_model and after_model
before_model runs right before the model is called; after_model runs right after. The simplest way to write one is with a decorator:
from langchain.agents.middleware import before_model@before_modeldef log_before_model(state, runtime):print(f"About to call the model with {len(state['messages'])} messages")
Line 1: We import the
before_modeldecorator fromlangchain.agents.middleware.Lines 3–5: The decorated function receives the current
state(the same dict-like state our hand-built nodes received) and aruntimeobject, and ...