创见博客
LangChain 中间件(Middleware)入门:是什么、有什么用、有哪些分类
七崽爱吃小饼干2026/10/15阅读 0专栏 LangChain 开发实战

前面几篇讲了工具、结构化输出、以及把二者串起来的 createAgent。到这里,一个 Agent 的基本骨架就齐了。但真实项目里,我们往往还想在 Agent 的循环里「插一脚」——记录日志、改写提示词、给工具加重试、限制调用次数、拦截敏感信息。中间件(Middleware) 就是官方为此留出的机制。这篇讲清楚它是什么、能做什么、有哪些分类;具体常用中间件的用法,留到后面的文章逐个演示。

一、从 Agent 循环说起

Agent 的本质是一个循环(见 demo-12):

用户提问
  -> 调用模型(模型可能输出 tool_calls)
  -> 执行工具,把结果塞回消息历史
  -> 再调用模型
  -> 重复,直到模型不再调用工具

问题来了:如果我们想在这个循环的某些环节加点东西,怎么办?

  • 想在每次调用模型前记录日志?
  • 想让系统提示词根据当前状态动态变化?
  • 想给工具调用加上失败重试?
  • 想限制模型最多调用几次,防止死循环烧钱?
  • 想在把消息发给模型前,把手机号、邮箱脱敏?

这些需求有一个共同点:它们和具体业务无关,但几乎每个 Agent 都可能需要。 这类「横切关注点」如果都塞进工具函数或系统提示词里,代码会变得又乱又难维护。

二、什么是中间件

中间件是挂在 Agent 循环上的可插拔拦截器。 它在循环的各个关键节点暴露钩子(hook),你写一个中间件挂上去,就能在对应节点介入,而完全不用碰 createAgent 的内部实现。

js
import { createAgent, createMiddleware } from "langchain";

const myMiddleware = createMiddleware({
  name: "MyMiddleware",
  beforeModel: (state, runtime) => {
    console.log("准备调用模型,当前消息数:", state.messages.length);
  },
});

const agent = createAgent({
  model,
  tools: [...],
  middleware: [myMiddleware],   // 挂上中间件
});

一句话:中间件让「定制 Agent 行为」从「改源码」变成「加一个插件」。

三、能挂在哪:生命周期钩子

一个中间件可以挂六种钩子,正好对应 Agent 循环的几个关键点:

钩子触发时机典型用途
beforeAgent整个 Agent 运行「前」执行一次初始化、打点
beforeModel「每次」调用模型前改状态、动态拼提示词
afterModel模型返回后、工具执行前修正 tool_calls 参数
wrapModelCall「包裹」模型调用改请求、重试、降级、后处理响应
wrapToolCall「包裹」工具执行参数校验、鉴权、缓存、重试、改结果
afterAgent整个 Agent 运行「后」执行一次汇总、清理

其中的钩子分成两类,理解这点很关键:

  • 节点式钩子(node-style):beforeAgent / beforeModel / afterModel / afterAgent。它们在固定节点顺序执行,适合做日志、校验、状态更新。
  • 包裹式钩子(wrap-style):wrapModelCall / wrapToolCall。它们包裹住真正的调用,你决定要不要调用、调用几次(0 次=短路,1 次=正常,多次=重试),适合做重试、缓存、降级、转换。

除了钩子,中间件还能注册一些「资产」:

能力作用
stateSchema自己的状态字段,跨多次调用持久化
contextSchema只读的运行时上下文,不持久化
tools给 Agent 追加工具(例如任务清单中间件会加一个 write_todos)
streamTransformers注册流式转换器

四、动手:钩子是按什么顺序触发的

理论说了这么多,不如跑一遍看顺序。下面这个中间件什么都不做,只在每个钩子里打印一行——它不是常用中间件,只是把「钩子图」真实地跑出来:

js
import { createMiddleware, createAgent, tool } from "langchain";
import { z } from "zod";

const tracer = createMiddleware({
  name: "Tracer",
  beforeAgent:  () => console.log("beforeAgent"),
  beforeModel:  () => console.log("beforeModel"),
  afterModel:   () => console.log("afterModel"),
  afterAgent:   () => console.log("afterAgent"),
  wrapModelCall: async (request, handler) => {
    console.log("wrapModelCall -> 进入");
    const response = await handler(request);
    console.log("wrapModelCall <- 返回");
    return response;
  },
  wrapToolCall: async (request, handler) => {
    console.log(`wrapToolCall -> 进入工具 ${request.toolCall.name}`);
    const result = await handler(request);
    console.log("wrapToolCall <- 工具执行完毕");
    return result;
  },
});

const getWeather = tool(({ city }) => `${city} 今天晴,气温 25℃`, {
  name: "get_weather",
  description: "查询指定城市今天的天气",
  schema: z.object({ city: z.string() }),
});

const agent = createAgent({
  model,
  tools: [getWeather],
  middleware: [tracer],
});

await agent.invoke({ messages: [{ role: "user", content: "北京今天天气怎么样?" }] });

输出(模型调了 2 次、工具 1 次):

beforeAgent
  beforeModel
  wrapModelCall -> 进入
  wrapModelCall <- 返回          ← 第 1 次模型调用:决定要调 get_weather
  afterModel
  wrapToolCall  -> 进入工具 get_weather
  wrapToolCall  <- 工具执行完毕
  beforeModel
  wrapModelCall -> 进入
  wrapModelCall <- 返回          ← 第 2 次模型调用:根据结果生成最终回答
  afterModel
afterAgent

把这张输出和第一节的 Agent 循环对照,就能直观看到:中间件的钩子,精确地分布在循环的每个环节。

多个中间件同时存在时的执行顺序规则:before_* 按注册顺序正序执行,after_* 逆序执行,wrap_* 像洋葱一样嵌套(第一个中间件包住所有后面的)。

五、中间件有什么作用

官方给中间件的定位就是「横切关注点」,常见用途可以归为五类:

  1. 可观测:logging、埋点、调试,追踪 Agent 每一步在做什么;
  2. 改造请求:动态改写系统提示词、筛选/追加工具、格式化输出;
  3. 提升可靠性:失败重试、模型降级、错误兜底、提前终止;
  4. 控成本 / 防失控:限制模型调用次数、限制工具调用次数、限流;
  5. 安全合规:PII 检测与脱敏、内容审核、危险操作人工审批。

关键在于:把这些能力交给中间件统一处理,业务工具和系统提示词就能保持干净。

六、有哪些分类

LangChain 内置了一批开箱即用的中间件。按用途大致可分为以下几类(本篇只做分类介绍,具体用法放到后面的文章逐个演示):

A. 上下文管理——控制「喂给模型什么」

中间件作用
summarizationMiddleware对话太长时自动摘要,保留最近消息
contextEditingMiddleware清理旧的工具调用结果,腾出上下文
anthropicPromptCachingMiddleware / bedrockPromptCachingMiddleware给提示词加缓存,省钱提速

B. 可靠性 / 容错——让 Agent 更稳

中间件作用
toolRetryMiddleware工具调用失败自动重试(指数退避)
modelRetryMiddleware模型调用失败自动重试
modelFallbackMiddleware主模型失败时自动降级到备用模型
toolErrorMiddleware把工具抛的异常转成消息交还给模型
toolEmulatorMiddleware测试时用 LLM 模拟工具,不真正执行

C. 流程控制 / 循环干预——改变 Agent 循环怎么走

中间件作用
modelCallLimitMiddleware限制模型调用次数,防死循环 / 烧钱
toolCallLimitMiddleware限制工具调用次数(全局或指定工具)
humanInTheLoopMiddleware高风险工具执行前暂停,等人工审批
todoListMiddleware给 Agent 一个「任务清单」工具,做计划
llmToolSelectorMiddleware工具太多时,先用 LLM 挑出相关工具
providerToolSearchMiddleware把工具放到厂商的服务端「按需搜索」

D. 安全 / 合规——护栏与脱敏

中间件作用
piiMiddlewarePII 检测(block / redact / mask / hash)
piiRedactionMiddlewarePII 脱敏的专用封装
openAIModerationMiddlewareOpenAI 内容审核(provider 专用)

E. 动态行为——根据运行时状态改变行为

中间件作用
dynamicSystemPromptMiddleware按当前状态动态拼系统提示词

F. 能力扩展 / 厂商集成

  • 文件系统、子 Agent 等能力(来自 Deep Agents);
  • 各厂商专属中间件(Anthropic / AWS / OpenAI 的缓存、审核等)。

七、Python 对应写法

Python 侧的中间件模型与 JS 一致,只是用装饰器或类来定义。钩子名一一对应,只是换成下划线命名(before_model、wrap_model_call……)。

装饰器风格(单个钩子,简单):

python
from langchain.agents import create_agent
from langchain.agents.middleware import before_model, wrap_model_call, AgentState
from langgraph.runtime import Runtime
from typing import Any, Callable
from langchain.agents.middleware import ModelRequest, ModelResponse

@before_model
def log_before_model(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
    print(f"准备调用模型,当前消息数:{len(state['messages'])}")
    return None

@wrap_model_call
def retry_model(request: ModelRequest, handler: Callable[[ModelRequest], ModelResponse]) -> ModelResponse:
    for attempt in range(3):
        try:
            return handler(request)
        except Exception:
            if attempt == 2:
                raise

agent = create_agent(model="gpt-5.5", tools=[...], middleware=[log_before_model, retry_model])

类风格(多个钩子 / 需要配置):

python
from langchain.agents import create_agent
from langchain.agents.middleware import AgentMiddleware, AgentState
from langgraph.runtime import Runtime
from typing import Any

class LoggingMiddleware(AgentMiddleware):
    def before_model(self, state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
        print("before_model")
        return None

    def after_model(self, state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
        print("after_model")
        return None

agent = create_agent(model="gpt-5.5", tools=[...], middleware=[LoggingMiddleware()])

对应关系:

能力JSPython
定义方式createMiddleware({...})装饰器 / 继承 AgentMiddleware
节点钩子beforeAgent / beforeModel / afterModel / afterAgentbefore_agent / before_model / after_model / after_agent
包裹钩子wrapModelCall / wrapToolCallwrap_model_call / wrap_tool_call
注册状态stateSchemastate_schema
挂载createAgent({ middleware: [...] })create_agent(..., middleware=[...])

内置中间件的名字也一样,只是首字母大写:SummarizationMiddleware、HumanInTheLoopMiddleware、ModelCallLimitMiddleware、PIIMiddleware、TodoListMiddleware 等。

八、小结

  • 是什么:中间件是挂在 Agent 循环上的「钩子 / 拦截器」,用来处理横切关注点。不改 Agent 内部代码,就能定制行为。
  • 六个钩子:beforeAgent / beforeModel / wrapModelCall / afterModel / wrapToolCall / afterAgent;还能注册 state / context / tools / 流转换器。
  • 作用:可观测、改造请求、容错重试、限流控成本、安全合规。
  • 分类:上下文管理、可靠性、流程控制、安全合规、动态行为、能力扩展。

一句话:中间件 = Agent 的可插拔扩展点。 它把「日志、重试、限流、脱敏、审批」这些每个 Agent 都可能需要的通用能力,从业务逻辑里剥离出来,让 Agent 的骨架保持简洁。后续文章会挑其中最常用的几个,逐个演示用法。

评论
0/100