中间件提供了一种更紧密地控制智能体内部行为的方式。
核心智能体循环包括调用模型、让其选择要执行的工具,然后在不再调用工具时结束:
中间件在每个步骤之前和之后暴露了钩子:
中间件能做什么?
通过将中间件传递给 @[create_agent] 来添加中间件:
import {
createAgent,
summarizationMiddleware,
humanInTheLoopMiddleware,
} from "langchain";
const agent = createAgent({
model: "openai:gpt-4o",
tools: [...],
middleware: [summarizationMiddleware, humanInTheLoopMiddleware],
});
内置中间件
LangChain 为常见用例提供了预构建的中间件:
在接近令牌限制时自动总结对话历史。
非常适合:
- 超过上下文窗口的长时间运行对话
- 具有广泛历史的多轮对话
- 需要保留完整对话上下文的应用
import { createAgent, summarizationMiddleware } from "langchain";
const agent = createAgent({
model: "openai:gpt-4o",
tools: [weatherTool, calculatorTool],
middleware: [
summarizationMiddleware({
model: "openai:gpt-4o-mini",
maxTokensBeforeSummary: 4000, // 在4000个令牌时触发摘要
messagesToKeep: 20, // 摘要后保留最后20条消息
summaryPrompt: "Custom prompt for summarization...", // 可选
}),
],
});
summaryPrefix
string
default:"## Previous conversation summary:"
摘要消息的前缀
人在回路
在执行工具调用之前暂停智能体执行,等待人工批准、编辑或拒绝。
非常适合:
- 需要人工批准的高风险操作(数据库写入、金融交易)
- 需要人工监督的合规工作流
- 使用人工反馈指导智能体的长时间运行对话
import { createAgent, humanInTheLoopMiddleware } from "langchain";
const agent = createAgent({
model: "openai:gpt-4o",
tools: [readEmailTool, sendEmailTool],
middleware: [
humanInTheLoopMiddleware({
interruptOn: {
// 发送邮件需要批准、编辑或拒绝
send_email: {
allowAccept: true,
allowEdit: true,
allowRespond: true,
},
// 自动批准读取邮件
read_email: false,
}
})
]
});
重要: 人在回路中间件需要一个检查点器来在中断期间维护状态。有关完整示例和集成模式,请参阅人在回路文档。
Anthropic 提示词缓存
通过缓存重复的提示词前缀来降低 Anthropic 模型的成本。
非常适合:
- 具有长且重复的系统提示词的应用
- 在多次调用中重用相同上下文的智能体
- 减少高流量部署的API成本
import { createAgent, HumanMessage, anthropicPromptCachingMiddleware } from "langchain";
const LONG_PROMPT = `
Please be a helpful assistant.
<Lots more context ...>
`;
const agent = createAgent({
model: "anthropic:claude-sonnet-4-latest",
prompt: LONG_PROMPT,
middleware: [anthropicPromptCachingMiddleware({ ttl: "5m" })],
});
// 缓存存储
await agent.invoke({
messages: [new HumanMessage("Hi, my name is Bob")]
});
// 缓存命中,系统提示词已缓存
const result = await agent.invoke({
messages: [new HumanMessage("What's my name?")]
});
缓存内容的存活时间。有效值:"5m" 或 "1h"
模型调用限制
限制模型调用次数以防止无限循环或过高成本。
非常适合:
- 防止失控智能体进行过多API调用
- 在生产部署上实施成本控制
- 在特定调用预算内测试智能体行为
import { createAgent, modelCallLimitMiddleware } from "langchain";
const agent = createAgent({
model: "openai:gpt-4o",
tools: [...],
middleware: [
modelCallLimitMiddleware({
threadLimit: 10, // 每个线程最多10次调用(跨运行)
runLimit: 5, // 每次运行最多5次调用(单次调用)
exitBehavior: "end", // 或 "error" 抛出异常
}),
],
});
达到限制时的行为。选项:"end"(优雅终止)或 "error"(抛出异常)
工具调用限制
限制特定工具或所有工具的调用次数。
非常适合:
- 防止对昂贵的外部API进行过多调用
- 限制网络搜索或数据库查询
- 对特定工具使用实施速率限制
import { createAgent, toolCallLimitMiddleware } from "langchain";
// 限制所有工具调用
const globalLimiter = toolCallLimitMiddleware({ threadLimit: 20, runLimit: 10 });
// 限制特定工具
const searchLimiter = toolCallLimitMiddleware({
toolName: "search",
threadLimit: 5,
runLimit: 3,
});
const agent = createAgent({
model: "openai:gpt-4o",
tools: [...],
middleware: [globalLimiter, searchLimiter],
});
要限制的特定工具。如果未提供,则限制适用于所有工具。
达到限制时的行为。选项:"end"(优雅终止)或 "error"(抛出异常)
模型回退
当主模型失败时自动回退到替代模型。
非常适合:
- 构建能够处理模型中断的弹性智能体
- 通过回退到更便宜的模型来优化成本
- 跨OpenAI、Anthropic等的提供商冗余
import { createAgent, modelFallbackMiddleware } from "langchain";
const agent = createAgent({
model: "openai:gpt-4o", // 主模型
tools: [...],
middleware: [
modelFallbackMiddleware(
"openai:gpt-4o-mini", // 出错时首先尝试
"anthropic:claude-3-5-sonnet-20241022" // 然后尝试这个
),
],
});
中间件接受可变数量的字符串参数,表示按顺序排列的回退模型:当主模型失败时按顺序尝试的一个或多个回退模型字符串modelFallbackMiddleware(
"first-fallback-model",
"second-fallback-model",
// ... 更多模型
)
PII检测
检测和处理对话中的个人身份信息。
非常适合:
- 具有合规要求的医疗和金融应用
- 需要清理日志的客户服务智能体
- 任何处理敏感用户数据的应用
import { createAgent, piiRedactionMiddleware } from "langchain";
const agent = createAgent({
model: "openai:gpt-4o",
tools: [...],
middleware: [
// 在用户输入中编辑电子邮件
piiRedactionMiddleware({
piiType: "email",
strategy: "redact",
applyToInput: true,
}),
// 掩码信用卡(显示最后4位数字)
piiRedactionMiddleware({
piiType: "credit_card",
strategy: "mask",
applyToInput: true,
}),
// 使用正则表达式的自定义PII类型
piiRedactionMiddleware({
piiType: "api_key",
detector: /sk-[a-zA-Z0-9]{32}/,
strategy: "block", // 如果检测到则抛出错误
}),
],
});
要检测的PII类型。可以是内置类型(email、credit_card、ip、mac_address、url)或自定义类型名称。
如何处理检测到的PII。选项:
"block" - 检测到时抛出错误
"redact" - 替换为 [REDACTED_TYPE]
"mask" - 部分掩码(例如 ****-****-****-1234)
"hash" - 替换为确定性哈希
自定义检测器正则表达式模式。如果未提供,则使用该PII类型的内置检测器。
为复杂的多步骤任务添加待办事项列表管理功能。
此中间件自动为智能体提供 write_todos 工具和系统提示词,以指导有效的任务规划。
import { createAgent, HumanMessage, todoListMiddleware } from "langchain";
const agent = createAgent({
model: "openai:gpt-4o",
tools: [
/* ... */
],
middleware: [todoListMiddleware()] as const,
});
const result = await agent.invoke({
messages: [new HumanMessage("Help me refactor my codebase")],
});
console.log(result.todos); // 带有状态跟踪的待办事项数组
无可用配置选项(使用默认值)。
LLM工具选择器
在调用主模型之前使用LLM智能选择相关工具。
非常适合:
- 具有许多工具(10+)且每个查询中大多数不相关的智能体
- 通过过滤不相关工具减少令牌使用
- 提高模型专注度和准确性
import { createAgent, llmToolSelectorMiddleware } from "langchain";
const agent = createAgent({
model: "openai:gpt-4o",
tools: [tool1, tool2, tool3, tool4, tool5, ...], // 许多工具
middleware: [
llmToolSelectorMiddleware({
model: "openai:gpt-4o-mini", // 使用更便宜的模型进行选择
maxTools: 3, // 限制为3个最相关的工具
alwaysInclude: ["search"], // 始终包含某些工具
}),
],
});
上下文编辑
通过修剪、总结或清除工具使用来管理对话上下文。
非常适合:
- 需要定期上下文清理的长时间对话
- 从上下文中移除失败的工具尝试
- 自定义上下文管理策略
import { createAgent, contextEditingMiddleware, ClearToolUsesEdit } from "langchain";
const agent = createAgent({
model: "openai:gpt-4o",
tools: [...],
middleware: [
contextEditingMiddleware({
edits: [
new ClearToolUsesEdit({ maxTokens: 1000 }), // 清除旧工具使用
],
}),
],
});
edits
ContextEdit[]
default:"[new ClearToolUsesEdit()]"
要应用的 ContextEdit 策略数组
@[ClearToolUsesEdit] 选项:
自定义中间件
通过实现在智能体执行流程中特定点运行的钩子来构建自定义中间件。
基于类的中间件
两种钩子风格
节点风格钩子
在特定执行点顺序运行。用于日志记录、验证和状态更新。
包装风格钩子
拦截执行,完全控制处理程序调用。用于重试、回退、缓存和转换。
节点风格钩子
在执行流程中的特定点运行:
beforeAgent - 在智能体启动前(每次调用一次)
beforeModel - 在每次模型调用前
afterModel - 在每次模型响应后
afterAgent - 在智能体完成后(每次调用最多一次)
示例:日志记录中间件
import { createMiddleware } from "langchain";
const loggingMiddleware = createMiddleware({
name: "LoggingMiddleware",
beforeModel: (state) => {
console.log(`About to call model with ${state.messages.length} messages`);
return;
},
afterModel: (state) => {
const lastMessage = state.messages[state.messages.length - 1];
console.log(`Model returned: ${lastMessage.content}`);
return;
},
});
示例:对话长度限制
import { createMiddleware, AIMessage } from "langchain";
const createMessageLimitMiddleware = (maxMessages: number = 50) => {
return createMiddleware({
name: "MessageLimitMiddleware",
beforeModel: (state) => {
if (state.messages.length === maxMessages) {
return {
messages: [new AIMessage("Conversation limit reached.")],
jumpTo: "end",
};
}
return;
},
});
};
包装风格钩子
拦截执行并控制何时调用处理程序:
wrapModelCall - 围绕每次模型调用
wrapToolCall - 围绕每次工具调用
您决定处理程序是调用零次(短路)、一次(正常流程)还是多次(重试逻辑)。
示例:模型重试中间件
import { createMiddleware } from "langchain";
const createRetryMiddleware = (maxRetries: number = 3) => {
return createMiddleware({
name: "RetryMiddleware",
wrapModelCall: (request, handler) => {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return handler(request);
} catch (e) {
if (attempt === maxRetries - 1) {
throw e;
}
console.log(`Retry ${attempt + 1}/${maxRetries} after error: ${e}`);
}
}
throw new Error("Unreachable");
},
});
};
示例:动态模型选择
import { createMiddleware, initChatModel } from "langchain";
const dynamicModelMiddleware = createMiddleware({
name: "DynamicModelMiddleware",
wrapModelCall: (request, handler) => {
// 根据对话长度使用不同的模型
const modifiedRequest = { ...request };
if (request.messages.length > 10) {
modifiedRequest.model = initChatModel("openai:gpt-4o");
} else {
modifiedRequest.model = initChatModel("openai:gpt-4o-mini");
}
return handler(modifiedRequest);
},
});
示例:工具调用监控
import { createMiddleware } from "langchain";
const toolMonitoringMiddleware = createMiddleware({
name: "ToolMonitoringMiddleware",
wrapToolCall: (request, handler) => {
console.log(`Executing tool: ${request.toolCall.name}`);
console.log(`Arguments: ${JSON.stringify(request.toolCall.args)}`);
try {
const result = handler(request);
console.log("Tool completed successfully");
return result;
} catch (e) {
console.log(`Tool failed: ${e}`);
throw e;
}
},
});
自定义状态模式
中间件可以使用自定义属性扩展智能体的状态。定义自定义状态类型并将其设置为 state_schema:
import { createMiddleware, createAgent, HumanMessage } from "langchain";
import * as z from "zod";
// 具有自定义状态要求的中间件
const callCounterMiddleware = createMiddleware({
name: "CallCounterMiddleware",
stateSchema: z.object({
modelCallCount: z.number().default(0),
userId: z.string().optional(),
}),
beforeModel: (state) => {
// 访问自定义状态属性
if (state.modelCallCount > 10) {
return { jumpTo: "end" };
}
return;
},
afterModel: (state) => {
// 更新自定义状态
return { modelCallCount: state.modelCallCount + 1 };
},
});
const agent = createAgent({
model: "openai:gpt-4o",
tools: [...],
middleware: [callCounterMiddleware] as const,
});
// TypeScript 强制执行必需的状态属性
const result = await agent.invoke({
messages: [new HumanMessage("Hello")],
modelCallCount: 0, // 由于有默认值,可选
userId: "user-123", // 可选
});
上下文扩展
上下文属性是通过可运行配置传递的配置值。与状态不同,上下文是只读的,通常用于在执行期间不改变的配置。
中间件可以定义必须通过智能体配置满足的上下文要求:
import * as z from "zod";
import { createMiddleware, HumanMessage } from "langchain";
const rateLimitMiddleware = createMiddleware({
name: "RateLimitMiddleware",
contextSchema: z.object({
maxRequestsPerMinute: z.number(),
apiKey: z.string(),
}),
beforeModel: async (state, runtime) => {
// 通过运行时访问上下文
const { maxRequestsPerMinute, apiKey } = runtime.context;
// 实现速率限制逻辑
const allowed = await checkRateLimit(apiKey, maxRequestsPerMinute);
if (!allowed) {
return { jumpTo: "END" };
}
return state;
},
});
// 上下文通过配置提供
await agent.invoke(
{ messages: [new HumanMessage("Process data")] },
{
context: {
maxRequestsPerMinute: 60,
apiKey: "api-key-123",
},
}
);
执行顺序
当使用多个中间件时,理解执行顺序很重要:
const agent = createAgent({
model: "openai:gpt-4o",
middleware: [middleware1, middleware2, middleware3],
tools: [...],
});
Before 钩子按顺序运行:
middleware1.before_agent()
middleware2.before_agent()
middleware3.before_agent()
智能体循环开始
middleware1.before_model()
middleware2.before_model()
middleware3.before_model()
Wrap 钩子像函数调用一样嵌套:
middleware1.wrap_model_call() → middleware2.wrap_model_call() → middleware3.wrap_model_call() → 模型
After 钩子按相反顺序运行:
middleware3.after_model()
middleware2.after_model()
middleware1.after_model()
智能体循环结束
middleware3.after_agent()
middleware2.after_agent()
middleware1.after_agent()
关键规则:
before_* 钩子:第一个到最后一个
after_* 钩子:最后一个到第一个(反向)
wrap_* 钩子:嵌套(第一个中间件包装所有其他)
智能体跳转
要从中间件提前退出,返回带有 jump_to 的字典:
import { createMiddleware, AIMessage } from "langchain";
const earlyExitMiddleware = createMiddleware({
name: "EarlyExitMiddleware",
beforeModel: (state) => {
// 检查某些条件
if (shouldExit(state)) {
return {
messages: [new AIMessage("Exiting early due to condition.")],
jumpTo: "end",
};
}
return;
},
});
可用的跳转目标:
"end":跳转到智能体执行的末尾
"tools":跳转到工具节点
"model":跳转到模型节点(或第一个 before_model 钩子)
重要: 当从 before_model 或 after_model 跳转时,跳转到 "model" 将导致所有 before_model 中间件再次运行。
要启用跳转,请使用 @hook_config(can_jump_to=[...]) 装饰您的钩子:
import { createMiddleware } from "langchain";
const conditionalMiddleware = createMiddleware({
name: "ConditionalMiddleware",
afterModel: (state) => {
if (someCondition(state)) {
return { jumpTo: "end" };
}
return;
},
});
最佳实践
- 保持中间件专注 - 每个应该做好一件事
- 优雅地处理错误 - 不要让中间件错误崩溃智能体
- 使用适当的钩子类型:
- 节点风格用于顺序逻辑(日志记录、验证)
- 包装风格用于控制流(重试、回退、缓存)
- 清楚地记录任何自定义状态属性
- 在集成之前独立地对中间件进行单元测试
- 考虑执行顺序 - 将关键中间件放在列表首位
- 尽可能使用内置中间件,不要重复造轮子 :)
动态选择工具
在运行时选择相关工具以提高性能和准确性。
好处:
- 更短的提示词 - 通过仅暴露相关工具来降低复杂性
- 更好的准确性 - 模型从更少的选择中正确选择
- 权限控制 - 根据用户访问权限动态过滤工具
import { createAgent, createMiddleware } from "langchain";
const toolSelectorMiddleware = createMiddleware({
name: "ToolSelector",
wrapModelCall: (request, handler) => {
// 基于状态/上下文选择一小部分相关工具
const relevantTools = selectRelevantTools(request.state, request.runtime);
const modifiedRequest = { ...request, tools: relevantTools };
return handler(modifiedRequest);
},
});
const agent = createAgent({
model: "openai:gpt-4o",
tools: allTools, // 所有可用工具需要预先注册
// 中间件可用于选择给定运行相关的较小子集。
middleware: [toolSelectorMiddleware],
});
Show 扩展示例:GitHub vs GitLab 工具选择
import * as z from "zod";
import { createAgent, createMiddleware, tool, HumanMessage } from "langchain";
const githubCreateIssue = tool(
async ({ repo, title }) => ({
url: `https://github.com/${repo}/issues/1`,
title,
}),
{
name: "github_create_issue",
description: "Create an issue in a GitHub repository",
schema: z.object({ repo: z.string(), title: z.string() }),
}
);
const gitlabCreateIssue = tool(
async ({ project, title }) => ({
url: `https://gitlab.com/${project}/-/issues/1`,
title,
}),
{
name: "gitlab_create_issue",
description: "Create an issue in a GitLab project",
schema: z.object({ project: z.string(), title: z.string() }),
}
);
const allTools = [githubCreateIssue, gitlabCreateIssue];
const toolSelector = createMiddleware({
name: "toolSelector",
contextSchema: z.object({ provider: z.enum(["github", "gitlab"]) }),
wrapModelCall: (request, handler) => {
const provider = request.runtime.context.provider;
const toolName = provider === "gitlab" ? "gitlab_create_issue" : "github_create_issue";
const selectedTools = request.tools.filter((t) => t.name === toolName);
const modifiedRequest = { ...request, tools: selectedTools };
return handler(modifiedRequest);
},
});
const agent = createAgent({
model: "openai:gpt-4o",
tools: allTools,
middleware: [toolSelector],
});
// 使用 GitHub 上下文调用
await agent.invoke(
{
messages: [
new HumanMessage("Open an issue titled 'Bug: where are the cats' in the repository `its-a-cats-game`"),
],
},
{
context: { provider: "github" },
}
);
关键点:
- 预先注册所有工具
- 中间件为每个请求选择相关子集
- 使用
contextSchema 进行配置要求
附加资源