demo-14 讲了中间件是什么、怎么挂;demo-15 动手演示了 4 个最常用的(摘要、人在环、PII、任务清单)。这一篇把剩下 12 个内置中间件各跑一个最小示例,最后再介绍 3 个厂商专属的。每种的用法都高度统一——import 进来,放进 createAgent({ middleware: [...] })。
代码基于 JavaScript 版
langchainv1,模型用 DeepSeek。为便于理解,把 12 个按用途分成六组。
一、可靠性:失败怎么办
1. toolRetryMiddleware —— 工具重试
工具调用失败时自动重试(指数退避),适合调用外部 API、网络工具等。
toolRetryMiddleware({
maxRetries: 3, // 重试次数(默认 2)
backoffFactor: 2.0, // 退避倍数:等待 = initialDelayMs * factor^retry
initialDelayMs: 1000, // 首次重试等待
retryOn: (err) => true, // 只重试哪些错误
onFailure: "continue", // 重试耗尽后:continue(交还给模型)/ error(抛出)/ 函数
tools: ["search"], // 只对指定工具生效(省略则全部)
});
示例:造一个「前两次失败、第三次成功」的工具,看它最终成功:
let calls = 0;
const flakyTool = tool(({ q }) => {
calls += 1;
if (calls < 3) throw new Error("临时失败");
return `第 ${calls} 次调用成功`;
}, { name: "flaky_tool", description: "不稳定的工具", schema: z.object({ q: z.string() }) });
const agent = createAgent({
model, tools: [flakyTool],
middleware: [toolRetryMiddleware({ maxRetries: 3, backoffFactor: 0, initialDelayMs: 1 })],
});
// 工具最终返回:"第 3 次调用成功",实际被调用 3 次
2. modelRetryMiddleware —— 模型重试
和工具重试对称,作用于模型调用失败:网络抖动、限流等。
modelRetryMiddleware({ maxRetries: 3, onFailure: "continue" });
演示时用一个自定义模型模拟「前两次抛错、第三次成功」:
class FlakyChatModel extends BaseChatModel {
constructor(fields) { super(fields); this.attempts = 0; this.failures = fields.failures; }
_llmType() { return "flaky"; }
bindTools() { return this; }
async _generate() {
this.attempts += 1;
if (this.attempts <= this.failures) throw new Error("模拟服务不稳定");
const text = `成功!模型共被调用 ${this.attempts} 次。`;
return { generations: [{ text, message: new AIMessage(text) }] };
}
}
const flaky = new FlakyChatModel({ failures: 2 });
const agent = createAgent({
model: flaky, tools: [],
middleware: [modelRetryMiddleware({ maxRetries: 3, backoffFactor: 0, initialDelayMs: 1 })],
});
// 最终回答:"成功!模型共被调用 3 次。"
3. modelFallbackMiddleware —— 模型降级
主模型失败时,按顺序自动尝试备用模型。适合容灾、成本优化、多厂商冗余。
const agent = createAgent({
model: "deepseek:no-such-model-xyz", // 主模型(必然失败)
tools: [],
middleware: [modelFallbackMiddleware("deepseek:deepseek-chat")], // 备用模型,可写多个
});
// 主模型失败后,由备用模型正常回答
4. toolErrorMiddleware —— 工具错误兜底
把工具抛出的异常转成一条 error ToolMessage 交还给模型,而不是让整个 Agent 崩掉。它只「兜底」,不负责重试(重试交给 toolRetry)。
toolErrorMiddleware({
onError: (error, request) => {
if (error instanceof Error) {
// 只暴露错误类型,不泄露原始信息
return `工具 ${request.toolCall.name} 执行失败(${error.name}),请稍后重试。`;
}
// 不返回 = 让异常继续向上抛
},
tools: [...], // 可选,限定工具
});
让模型看到错误后,它有机会换个方式重试或给用户一个交代。
二、限流边界:别过头
5. toolCallLimitMiddleware —— 限制工具调用次数
限制工具的调用次数,防止狂调昂贵 API、死循环。runLimit 是单次调用内,threadLimit 是整个会话(需 checkpointer)。
toolCallLimitMiddleware({
toolName: "search", // 省略则限制全部工具
runLimit: 1, // 单次调用最多 1 次
threadLimit: 20, // 整个会话最多 20 次
exitBehavior: "continue", // continue(拦截并返回 error 消息)/ error(抛错)/ end(停止)
});
示例:runLimit: 1、用户一次问三个城市,超出的调用被拦截:
[error] "Tool call limit exceeded. Do not make additional tool calls."
[error] "Tool call limit exceeded. Do not make additional tool calls."
[success] "北京 晴 25℃"
6. modelCallLimitMiddleware —— 限制模型调用次数
限制模型调用次数,exitBehavior 默认 "end"(优雅收尾),也可设 "error" 抛异常。
modelCallLimitMiddleware({ runLimit: 1, threadLimit: 10, exitBehavior: "end" });
示例:runLimit: 1 时,超出后最后一条消息变成:
"Model call limits exceeded: run level call limit reached with 1 model calls"
三、上下文管理
7. contextEditingMiddleware —— 清理旧工具结果
上下文变长时,清理较早的工具调用结果以腾出空间(保留最近几条),比整体摘要更轻量。默认策略是 ClearToolUsesEdit。
import { contextEditingMiddleware, ClearToolUsesEdit } from "langchain";
contextEditingMiddleware({
edits: [
new ClearToolUsesEdit({
trigger: { tokens: 100000 }, // 触发条件(也可 messages / fraction)
keep: { messages: 3 }, // 保留最近几条工具结果
clearToolInputs: false, // 是否同时清空工具调用参数
excludeTools: [], // 不清理的工具
placeholder: "[cleared]", // 被清理后的占位符
}),
],
});
示例:把阈值调低,工具结果被替换:
工具消息内容已被清理为:"[cleared]"
四、动态行为
8. dynamicSystemPromptMiddleware —— 动态系统提示词
在每次调用模型前,根据运行时状态 / 上下文动态生成系统提示词。
const mw = dynamicSystemPromptMiddleware(
(state, runtime) => `你是助手,用户所在区域是 ${runtime.context.region ?? "未知"}。先复述这个区域。`
);
const agent = createAgent({
model, tools: [],
contextSchema: z.object({ region: z.string().optional() }),
middleware: [mw],
});
const res = await agent.invoke(
{ messages: [{ role: "user", content: "你好" }] },
{ context: { region: "EU" } }
);
// 回答里会带上 "EU"
五、工具治理
9. llmToolSelectorMiddleware —— 工具太多先筛选
工具很多(10+)时,先用一个(通常更小的)模型挑出与当前问题最相关的几个,再交给主模型,减少 Token、提升准确率。
llmToolSelectorMiddleware({
model, // 选择用的模型,默认主模型
maxTools: 1, // 最多选几个
alwaysInclude: ["search"], // 永远保留的工具
});
示例:注册 4 个工具、maxTools: 1,用一层自定义中间件打印主模型实际看到的工具:
主模型实际看到的工具:get_weather
(这里问的是天气,get_weather 被选中,其余 3 个被过滤掉。)
10. toolEmulatorMiddleware —— 用 LLM 模拟工具
测试时不真正执行工具,而是让 LLM 编一个「像模像样」的结果,适合还没接好外部依赖时开发。
toolEmulatorMiddleware({ tools: ["get_weather"], model });
示例中真实工具函数不会被调用,工具消息由 LLM 生成:
工具消息(由 LLM 模拟生成):{ "city":"北京","temperature":18,"condition":"多云", ... }
真实工具函数是否被调用:false
11. piiRedactionMiddleware —— 请求脱敏 + 响应还原
和 demo-15 的 piiMiddleware 不同:它在发给模型的请求里把 PII 抹成标记,但在模型响应 / 工具调用里把标记还原回原值,保证工具仍能拿到真实数据。
piiRedactionMiddleware({ rules: { ssn: /\b\d{3}-\d{2}-\d{4}\b/g } });
示例:用户消息里有 SSN,工具最终仍收到原文:
工具实际收到的 SSN(已从脱敏标记还原):"123-45-6789"
注意:该中间件已标记 deprecated,官方建议用
piiMiddleware。
六、厂商能力
12. providerToolSearchMiddleware —— 服务端工具搜索
把一部分工具「藏」在厂商的服务端工具搜索里,模型按需搜索发现,减少上下文占用。依赖厂商能力(Anthropic Claude Sonnet 4+/Opus 4+ 或 OpenAI gpt-5.4+),DeepSeek 不支持。
import { providerToolSearchMiddleware } from "langchain";
import { ChatAnthropic } from "@langchain/anthropic";
const agent = createAgent({
model: new ChatAnthropic({ model: "claude-sonnet-4-5" }),
tools: [getWeather, lookupOrder],
middleware: [
providerToolSearchMiddleware({ searchableTools: [lookupOrder] }),
],
});
附:3 个厂商专属中间件
这三个只在对应厂商上生效:
| 中间件 | 厂商 | 作用 |
|---|---|---|
anthropicPromptCachingMiddleware | Anthropic | 给提示词加缓存,降低重复前缀的成本与延迟 |
bedrockPromptCachingMiddleware | AWS Bedrock | 同上,针对 Bedrock Converse |
openAIModerationMiddleware | OpenAI | 用 OpenAI 审核接口拦截违规内容 |
anthropicPromptCachingMiddleware({ ttl: "5m" });
bedrockPromptCachingMiddleware();
openAIModerationMiddleware({ model, exitBehavior: "end" });
Python 对应写法
名字与 JS 基本一致(首字母大写),定义在 langchain.agents.middleware:
from langchain.agents import create_agent
from langchain.agents.middleware import (
ToolRetryMiddleware,
ModelRetryMiddleware,
ModelFallbackMiddleware,
ToolErrorMiddleware,
ToolCallLimitMiddleware,
ModelCallLimitMiddleware,
ContextEditingMiddleware,
LLMToolSelectorMiddleware,
ToolEmulatorMiddleware,
ProviderToolSearchMiddleware,
)
agent = create_agent(
model="gpt-5.5",
tools=[...],
middleware=[
ToolRetryMiddleware(max_retries=3, on_failure="continue"),
ToolCallLimitMiddleware(run_limit=10),
ModelCallLimitMiddleware(run_limit=5, exit_behavior="end"),
LLMToolSelectorMiddleware(max_tools=3),
],
)
动态提示词在 Python 里常用装饰器 @dynamic_prompt:
from langchain.agents.middleware import dynamic_prompt, ModelRequest
@dynamic_prompt
def add_context(request: ModelRequest) -> str:
return f"你是助手,用户区域:{request.runtime.context.get('region', '未知')}"
参数命名规律与 demo-15 一致:JS 驼峰 → Python 下划线(maxRetries ↔ max_retries、exitBehavior ↔ exit_behavior)。
小结
| 分组 | 中间件 |
|---|---|
| 可靠性 | toolRetry / modelRetry / modelFallback / toolError |
| 限流边界 | toolCallLimit / modelCallLimit |
| 上下文 | contextEditing |
| 动态行为 | dynamicSystemPrompt |
| 工具治理 | llmToolSelector / toolEmulator / piiRedaction(已废弃) |
| 厂商能力 | providerToolSearch(需 Anthropic/OpenAI) |
记忆规律:
- 名字带
retry/fallback/error的,管「失败怎么办」; - 带
limit的,管「别过头」; - 带
selector/emulator的,管「工具怎么用」; - 需要跨轮状态或中断的中间件,记得配
checkpointer。
结合 demo-15 的四个,LangChain 内置中间件这套「可插拔的横切能力」就基本铺满了——按需挑选、自由叠加即可。