docs
This guide covers using middleware to extend agent behavior with composable, reusable components.
Middleware provides a way to add cross-cutting concerns to agents:
import { createAgent, humanInTheLoopMiddleware, tool } from "langchain";
import { z } from "zod";
const sendEmail = tool(
async ({ to, subject, body }) => {
await emailService.send({ to, subject, body });
return `Email sent to ${to}`;
},
{
name: "send_email",
description: "Send an email",
schema: z.object({
to: z.string().email(),
subject: z.string(),
body: z.string(),
}),
}
);
const agent = createAgent({
model: "openai:gpt-4o",
tools: [sendEmail],
middleware: [
humanInTheLoopMiddleware({ interruptOn: "tools" }),
],
checkpointer: myCheckpointer, // Required for interrupts
});
// Agent will pause before calling tools
const result = await agent.invoke(
{ messages: [{ role: "user", content: "Send email to john@example.com" }] },
{ configurable: { thread_id: "thread-1" } }
);
// Resume after human approval
await agent.invoke(
{ messages: [] },
{ configurable: { thread_id: "thread-1", /* decision */ } }
);import { createAgent, piiMiddleware } from "langchain";
const agent = createAgent({
model: "openai:gpt-4o",
tools: [],
middleware: [
piiMiddleware({
builtInTypes: ["email", "credit_card", "ip"],
strategy: "redact", // or "mask", "hash", "remove"
}),
],
});import { createAgent, toolRetryMiddleware } from "langchain";
const agent = createAgent({
model: "openai:gpt-4o",
tools: [unreliableTool],
middleware: [
toolRetryMiddleware({
maxRetries: 3,
initialDelay: 1000,
backoffMultiplier: 2,
retryableErrors: [/timeout/i, /network/i],
}),
],
});import { createAgent, modelRetryMiddleware } from "langchain";
const agent = createAgent({
model: "openai:gpt-4o",
tools: [],
middleware: [
modelRetryMiddleware({
maxRetries: 3,
initialDelay: 1000,
maxDelay: 10000,
backoffMultiplier: 2,
}),
],
});import { createAgent, summarizationMiddleware } from "langchain";
const agent = createAgent({
model: "openai:gpt-4o",
tools: [],
middleware: [
summarizationMiddleware({
threshold: 4000, // Token threshold
model: "openai:gpt-4o-mini", // Model for summarization
}),
],
});import { createAgent, toolCallLimitMiddleware } from "langchain";
const agent = createAgent({
model: "openai:gpt-4o",
tools: [tool1, tool2],
middleware: [
toolCallLimitMiddleware({
threadLimit: 10, // Max per thread
runLimit: 5, // Max per run
}),
],
});import {
createAgent,
humanInTheLoopMiddleware,
piiMiddleware,
toolCallLimitMiddleware,
toolRetryMiddleware,
} from "langchain";
const agent = createAgent({
model: "openai:gpt-4o",
tools: [tools],
middleware: [
// Order matters! Middleware executes in array order
piiMiddleware({ builtInTypes: ["email", "credit_card"] }),
toolCallLimitMiddleware({ runLimit: 5 }),
toolRetryMiddleware({ maxRetries: 3 }),
humanInTheLoopMiddleware({ interruptOn: "tools" }),
],
checkpointer: myCheckpointer,
});import { createMiddleware, createAgent } from "langchain";
const loggingMiddleware = createMiddleware({
name: "logging",
// Wrap tool calls
wrapToolCall: async (request, handler, runtime) => {
console.log(`Calling tool: ${request.toolName}`);
console.log(`Arguments:`, request.args);
const result = await handler(request);
console.log(`Result:`, result);
return result;
},
});
const agent = createAgent({
model: "openai:gpt-4o",
tools: [],
middleware: [loggingMiddleware],
});import { createMiddleware } from "langchain";
import { z } from "zod";
const sessionMiddleware = createMiddleware({
name: "session",
stateSchema: z.object({
sessionId: z.string(),
startTime: z.number(),
toolCallCount: z.number().default(0),
}),
beforeAgent: async (state, runtime) => {
return {
...state,
sessionId: crypto.randomUUID(),
startTime: Date.now(),
};
},
wrapToolCall: async (request, handler, runtime) => {
const result = await handler(request);
// State is automatically updated
return result;
},
afterAgent: async (state, runtime) => {
const duration = Date.now() - state.startTime;
console.log(`Session ${state.sessionId} completed in ${duration}ms`);
console.log(`Tool calls: ${state.toolCallCount}`);
return state;
},
});import { createMiddleware } from "langchain";
import { z } from "zod";
const analyticsMiddleware = createMiddleware({
name: "analytics",
contextSchema: z.object({
userId: z.string(),
requestId: z.string(),
}),
afterModel: async (state, runtime) => {
// Access read-only context
const { userId, requestId } = runtime.context;
await analytics.track({
userId,
requestId,
event: "model_call_completed",
messageCount: state.messages.length,
});
return state;
},
});
// Use with context
const result = await agent.invoke(
{ messages: [...] },
{
context: {
userId: "user-123",
requestId: "req-456",
},
}
);import { createMiddleware, tool } from "langchain";
import { z } from "zod";
const cacheTool = tool(
async ({ key }) => {
const value = await cache.get(key);
return value || "Not found";
},
{
name: "get_cache",
description: "Get value from cache",
schema: z.object({
key: z.string(),
}),
}
);
const cacheMiddleware = createMiddleware({
name: "cache",
tools: [cacheTool], // Additional tools provided by middleware
});createMiddleware({
name: "my_middleware",
// Before agent starts
beforeAgent: async (state, runtime) => {
// Modify state before agent runs
return state;
},
// Before model call
beforeModel: async (state, runtime) => {
// Modify state before model invocation
return state;
},
// After model call
afterModel: async (state, runtime) => {
// Process model response
return state;
},
// After agent completes
afterAgent: async (state, runtime) => {
// Final processing
return state;
},
// Wrap tool calls
wrapToolCall: async (request, handler, runtime) => {
// Pre-processing
const result = await handler(request);
// Post-processing
return result;
},
// Wrap model calls
wrapModelCall: async (state, handler, runtime) => {
// Pre-processing
const newState = await handler(state);
// Post-processing
return newState;
},
});beforeAgent - Runs once at startbeforeModel - Before model callwrapModelCall - Around model callafterModel - After model callwrapToolCall - Around each tool callafterAgent - Runs once at endconst conditionalMiddleware = createMiddleware({
name: "conditional",
wrapToolCall: async (request, handler, runtime) => {
// Only apply to specific tools
if (request.toolName === "dangerous_tool") {
// Add extra validation
if (!validateInput(request.args)) {
throw new Error("Invalid input");
}
}
return handler(request);
},
});const metricsMiddleware = createMiddleware({
name: "metrics",
wrapModelCall: async (state, handler, runtime) => {
const start = Date.now();
try {
const result = await handler(state);
const duration = Date.now() - start;
await metrics.record({
type: "model_call",
duration,
success: true,
});
return result;
} catch (error) {
const duration = Date.now() - start;
await metrics.record({
type: "model_call",
duration,
success: false,
error: error.message,
});
throw error;
}
},
});const aclMiddleware = createMiddleware({
name: "acl",
contextSchema: z.object({
userId: z.string(),
role: z.enum(["admin", "user"]),
}),
wrapToolCall: async (request, handler, runtime) => {
const { role } = runtime.context;
// Check permissions
const requiredRole = getToolPermission(request.toolName);
if (!hasPermission(role, requiredRole)) {
return "Error: Insufficient permissions";
}
return handler(request);
},
});See Middleware Overview and Built-in Middleware Catalog for more details.