LangChain/Memory & Middleware
Advanced16 min

Custom Middleware

Build production middleware that intercepts model calls, gates tool execution, injects dynamic context, and writes state — using node-style hooks for sequential logic and wrap-style hooks when you need control over whether and how many times an operation runs.

Quick Reference

  • @before_model / @after_model — node-style hooks; return dict to update state or None to pass through
  • @wrap_model_call — wrap-style; you call handler(request) to execute the model
  • @wrap_tool_call — wrap-style around each tool execution
  • @dynamic_prompt — convenience hook that returns a system prompt string
  • request.override(model=, tools=, system_message=) — returns immutable modified copy
  • ExtendedModelResponse(model_response, command=Command(update={...})) — write state from wrap hooks
  • @before_model(can_jump_to=['end']) + return {'jump_to': 'end'} — exit agent loop early

When to Write Custom Middleware (and When Not To)

Before writing custom middleware, check whether a prebuilt covers your case — LangChain ships retry, fallback, PII, call limits, tool selection, summarization, and HITL out of the box. If prebuilt covers it, use it. Write custom middleware when you need cross-cutting behavior that doesn't fit a prebuilt, but would be wrong as a graph node because it must run on every model or tool call regardless of which node calls it.

SituationUse
Retry, fallback, PII redaction, call limitsPrebuilt middleware (ModelRetryMiddleware, PIIMiddleware, …)
Logic tied to a specific graph nodeGraph node — not middleware
Cross-cutting: log/validate every model callNode-style hook (@before_model / @after_model)
Control whether/how many times model runsWrap-style hook (@wrap_model_call)
Intercept every tool callWrap-style hook (@wrap_tool_call)
Multiple hooks + config at init timeClass-based AgentMiddleware subclass
Need to customize behavior?logging · retries · PII · routingPrebuilt covers it?(retry, PII, limits…)YESUse Prebuiltmiddleware.tsNOControl call itself?retry · cache · routeYESWrap-stylewrap_model_callNOMultiple hooks orconfig at init?YESClass-basedAgentMiddlewareNONode-style hook@before_model / @after_model

prebuilt first → wrap if you control execution → class if you need state/config → node-style otherwise