TypeScript framework for building LLM-powered applications with agents, tools, middleware, and model interoperability
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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.
docs