LangChain 中间件(Middleware)

一、简介

LangChain 1.0 引入了正式的中间件(Middleware) 概念,提供了一套声明式、可组合的 API,用于在 Runnable 执行流程中插入横切逻辑。与早期版本中依赖 RunnableLambda、回调或手动包装不同,1.0 的中间件是一等公民,通过 @middleware 装饰器和 Middleware 类,能够以更简洁、类型安全的方式实现日志、重试、限流、缓存、输入验证等功能。

1.1 为什么需要中间件?

Agent 具备自主推理、多步工具调用能力,但也带来三大核心问题:

  1. 不可控性:无法干预模型何时调用工具、如何生成提示词
  2. 安全风险:敏感操作无审核、隐私数据泄露、幻觉输出
  3. 资源浪费:Token 超限、重复调用、死循环、成本失控

中间件的核心价值:

  • 流程拦截与修改(输入 / 输出 / 状态)
  • 动态提示词管理、上下文压缩
  • 工具权限控制、敏感操作审核
  • 日志、监控、成本统计
  • 重试、降级、限流、缓存
  • 安全护栏(PII 脱敏、内容审核)

直接在每个链或 Agent 中重复实现这些功能会导致代码冗余。中间件模式允许将这些横切逻辑独立封装,并以声明方式应用到多个组件上。

1.2 核心概念

在 LangChain 1.0 中,所有可执行组件(模型、提示模板、解析器、Agent、链等)都实现了 Runnable 接口。中间件本质上是一个 Runnable 包装器,它接受一个 Runnable 并返回一个新的 Runnable,在调用前后插入自定义行为。

与旧版的主要区别:

方面 1.0 之前的通用模式 1.0 正式中间件 API
定义方式 RunnableLambda、继承 RunnableBinding @middleware 装饰器或 Middleware 类
组合 手动管道 | MiddlewareChain 或自动组合
配置传递 通过 RunnableConfig 手动处理 内置支持 config 参数和元数据
错误处理 自定义 try/except 中间件可统一捕获异常并重试/降级
官方支持 无标准模式 LangChain Core 原生支持

二、核心 API:@middleware 装饰器

LangChain 1.0 提供了 langchain_core.runnables.middleware 模块,其中核心是 @middleware 装饰器。

2.1 基础用法

@middleware 装饰器既可以用于异步函数,也可以用于同步函数。

python 复制代码
from langchain_core.runnables.middleware import middleware
from langchain_core.runnables import RunnableConfig
from langchain_openai import ChatOpenAI

@middleware
async def log_middleware(request, call_next):
    """记录输入和输出"""
    print(f"Input: {request['input']}")
    response = await call_next(request)  # 调用下层 Runnable
    print(f"Output: {response}")
    return response

model = ChatOpenAI()
wrapped_model = log_middleware(model)  # 直接包装

# 调用
result = await wrapped_model.ainvoke("Hello")
  • request:包含 input(用户输入)、config(RunnableConfig)、kwargs 等字段的字典。
  • call_next:异步函数,调用下一个中间件或最终的 Runnable。

2.2 带参数的中间件工厂

如果需要配置中间件(如重试次数),可以编写返回中间件装饰器的函数:

python 复制代码
def retry_middleware(max_retries: int = 3):
    @middleware
    async def _retry(request, call_next):
        last_exception = None
        for attempt in range(max_retries):
            try:
                return await call_next(request)
            except Exception as e:
                last_exception = e
                print(f"Attempt {attempt+1} failed: {e}")
        raise last_exception
    return _retry

model = ChatOpenAI()
wrapped = retry_middleware(5)(model)

2.3 使用 Middleware 类(面向对象方式)

除了装饰器,还可以继承 Middleware 类,实现更复杂的状态管理。

python 复制代码
from langchain_core.runnables.middleware import Middleware

class LoggingMiddleware(Middleware):
    async def __call__(self, request, call_next):
        print("Before")
        response = await call_next(request)
        print("After")
        return response

model = ChatOpenAI()
wrapped = LoggingMiddleware()(model)

2.4 组合多个中间件:MiddlewareChain

使用 MiddlewareChain 可以将多个中间件按顺序应用到同一个 Runnable 上。

python 复制代码
from langchain_core.runnables.middleware import MiddlewareChain

# 假设已有 log_middleware, retry_middleware, cache_middleware
middlewares = [log_middleware, retry_middleware(2), cache_middleware]
model = ChatOpenAI()
wrapped = MiddlewareChain(middlewares=middlewares, runnable=model)

三、核心执行流程与钩子

3.1 执行流程

中间件的执行基于洋葱模型(类似 Express.js 或 Starlette),遵循"后进先出"的顺序。当你添加多个中间件时,请求会依次穿过它们,到达核心的 LLM 调用,然后响应再反向穿过这些中间件。

bash 复制代码
请求 → Middleware A (pre) → Middleware B (pre) → 原始 LLM 调用 → 
Middleware B (post) → Middleware A (post) → 响应
  • 前处理(before):修改输入、校验、过滤、增强 Prompt
  • 后处理(after):解析输出、脱敏、日志、格式化
  • 包裹(wrap):完全接管执行,可重试、短路、缓存、替换模型

3.2 核心钩子(Hooks)

  1. 节点式钩子(顺序执行)

    钩子 执行时机 入参 返回值 典型用途
    before_agent Agent 启动前(单次) inputs: dict dict 初始化状态、验证输入、加载记忆
    before_model 每次模型调用前 messages: List[BaseMessage] List[BaseMessage] 裁剪消息、动态提示、注入上下文
    after_model 模型返回后 response: BaseMessage BaseMessage 审查输出、统计 Token、修改响应
    after_agent Agent 结束后(单次) output: dict dict 清理资源、生成报告、格式化结果
  2. 包装式钩子(洋葱嵌套)

    钩子 作用对象 入参 返回值 典型用途
    wrap_model_call 模型调用 model_call: Callable, messages BaseMessage 重试、缓存、限流、模型降级
    wrap_tool_call 工具执行 tool_call: Callable, tool_input Any 权限校验、超时、审计、脱敏

3.3 更细粒度的钩子示例

python 复制代码
from langchain.middleware import BaseMiddleware
from langchain_core.messages import HumanMessage

class CustomMiddleware(BaseMiddleware):
    async def pre_process(self, request):
        # 修改输入:统一转为字符串
        if isinstance(request.input, dict):
            request.input = str(request.input)
        return request

    async def wrap_llm_call(self, handler):
        # 对 LLM 调用增加重试逻辑
        for attempt in range(3):
            try:
                return await handler()  # 实际调用 LLM
            except Exception as e:
                if attempt == 2:
                    raise
        return None

    async def post_process(self, request, response):
        # 过滤敏感词
        response.output = response.output.replace("badword", "***")
        return response

    async def on_error(self, request, error):
        print(f"调用失败: {error}")
        return "默认回复"  # 可以返回备用响应

3.4 执行顺序详解

当调用 agent.invoke("hello") 时,LangChain 内部构建一个中间件栈:

  1. 最先添加的中间件位于最外层(最后执行 pre,最先执行 post)
  2. 执行流程:
    • 进入 Middleware1.pre_process → 进入 Middleware2.pre_process → ... → 到达 LLM 调用
    • LLM 返回 → 退出 MiddlewareN.post_process → ... → 退出 Middleware1.post_process

示例验证:

python 复制代码
class A(BaseMiddleware):
    async def pre_process(self, request):
        print("A pre")
        return request
    async def post_process(self, request, response):
        print("A post")
        return response

class B(BaseMiddleware):
    async def pre_process(self, request):
        print("B pre")
        return request
    async def post_process(self, request, response):
        print("B post")
        return response

agent = create_agent(...)
agent.add_middleware(A())
agent.add_middleware(B())
await agent.invoke("test")

# 输出:
# A pre
# B pre
# (实际 LLM 调用)
# B post
# A post

四、内置中间件

LangChain 1.0 提供官方内置中间件,覆盖安全、上下文、可靠性三大场景,直接导入即可使用。

4.1 RetryMiddleware

自动重试失败调用,支持指数退避。

python 复制代码
from langchain_core.runnables.middleware import RetryMiddleware

retry = RetryMiddleware(max_retries=3, backoff_factor=1.0)
model = ChatOpenAI()
robust_model = retry(model)

4.2 RateLimiterMiddleware

基于令牌桶算法的速率限制。

python 复制代码
from langchain_core.runnables.middleware import RateLimiterMiddleware

limiter = RateLimiterMiddleware(requests_per_second=10)
model = limiter(ChatOpenAI())

4.3 CacheMiddleware

简单缓存中间件,支持内存和 Redis 后端。

python 复制代码
from langchain_core.runnables.middleware import CacheMiddleware
from langchain_core.caches import InMemoryCache

cache = CacheMiddleware(cache=InMemoryCache())
model = cache(ChatOpenAI())

4.4 TracingMiddleware

自动集成 LangSmith 或自定义追踪器。

python 复制代码
from langchain_core.runnables.middleware import TracingMiddleware

tracer = TracingMiddleware(project_name="my_project")
model = tracer(ChatOpenAI())

4.5 PIIRedactionMiddleware

自动检测并屏蔽手机号、邮箱、身份证、银行卡等 PII 数据,支持自定义正则模式。

python 复制代码
from langchain.agents.middleware import PIIRedactionMiddleware

middleware = [
    PIIRedactionMiddleware(
        patterns=["phone", "email", "ssn"],  # 内置模式
        custom_patterns={"order_id": r"\d{10}"}  # 自定义模式
    )
]

4.6 HumanInTheLoopMiddleware

敏感工具调用前需人工确认(如发送邮件、删除数据),支持指定工具白名单。

python 复制代码
from langchain.agents.middleware import HumanInTheLoopMiddleware

middleware = [
    HumanInTheLoopMiddleware(
        tool_names=["send_email", "delete_data"],  # 需审核的工具
        approve_message="确认执行此操作?"  # 自定义提示
    )
]

4.7 SummarizationMiddleware

对话过长时自动摘要历史,避免 Token 超限,支持自定义摘要模型与阈值。

python 复制代码
from langchain.agents.middleware import SummarizationMiddleware

middleware = [
    SummarizationMiddleware(
        llm=ChatOpenAI(model="gpt-3.5-turbo"),  # 摘要用轻量模型
        max_tokens_before_summary=1500,  # 触发摘要的 Token 阈值
        summary_prompt="请用100字内摘要对话历史"  # 自定义提示
    )
]

4.8 MessageLimitMiddleware

限制历史消息数量,防止无限循环。

python 复制代码
from langchain.agents.middleware import MessageLimitMiddleware

middleware = [
    MessageLimitMiddleware(max_messages=30)  # 最多保留30条消息
]

4.9 ToolRetryMiddleware

工具调用失败自动指数退避重试。

python 复制代码
from langchain.agents.middleware import ToolRetryMiddleware

middleware = [
    ToolRetryMiddleware(
        max_retries=2,  # 最大重试次数
        retry_delay=1.0  # 初始延迟(秒)
    )
]

五、自定义中间件

5.1 基础自定义(节点式钩子)

继承 BaseAgentMiddleware,重写需要的钩子方法。

示例:日志中间件

python 复制代码
class LoggingMiddleware(BaseAgentMiddleware):
    def before_agent(self, inputs: dict, **kwargs) -> dict:
        print(f"[Agent 启动] 输入:{inputs}")
        return inputs  # 可修改输入

    def before_model(self, messages: List[BaseMessage], **kwargs) -> List[BaseMessage]:
        print(f"[模型调用前] 消息数:{len(messages)}")
        return messages  # 可修改消息

    def after_model(self, response: BaseMessage, **kwargs) -> BaseMessage:
        print(f"[模型返回] 内容:{response.content[:50]}...")
        return response  # 可修改响应

    def after_agent(self, output: dict, **kwargs) -> dict:
        print(f"[Agent 结束] 输出:{output}")
        return output  # 可修改输出

5.2 高级自定义(包装式钩子)

重写 wrap_model_call/wrap_tool_call,实现重试、缓存等逻辑。

示例:模型重试中间件

python 复制代码
class ModelRetryMiddleware(BaseAgentMiddleware):
    def __init__(self, max_retries: int = 2):
        self.max_retries = max_retries

    def wrap_model_call(
        self,
        model_call: Callable[[List[BaseMessage]], BaseMessage],
        messages: List[BaseMessage],
        **kwargs
    ) -> BaseMessage:
        for attempt in range(self.max_retries + 1):
            try:
                print(f"[重试] 第 {attempt+1} 次调用模型")
                return model_call(messages)
            except Exception as e:
                if attempt == self.max_retries:
                    raise e
                print(f"[重试失败] {e},1秒后重试...")
                import time
                time.sleep(1)
        return model_call(messages)

5.3 状态管理(跨中间件通信)

通过 kwargs["state"] 访问 / 修改全局状态,实现数据共享。

python 复制代码
def before_model(self, messages: List[BaseMessage], **kwargs) -> List[BaseMessage]:
    state = kwargs["state"]
    # 记录模型调用次数
    state["model_calls"] = state.get("model_calls", 0) + 1
    # 注入状态到消息
    messages.append({"role": "system", "content": f"已调用模型{state['model_calls']}次"})
    return messages

5.4 提前终止(Early Exit)

在 before_model 中返回 {"jumpTo": "end"} 直接终止 Agent。

python 复制代码
def before_model(self, messages: List[BaseMessage], **kwargs):
    if len(messages) > 50:
        return {
            "messages": [{"role": "assistant", "content": "对话过长,已终止"}],
            "jumpTo": "end"  # 强制结束
        }
    return messages

六、高级中间件模式

6.1 中间件与 LCEL

中间件可以直接应用于 LCEL 链,因为整个链本身也是一个 Runnable。

python 复制代码
prompt = ChatPromptTemplate.from_template("Tell me a joke about {topic}")
chain = prompt | model | StrOutputParser()

wrapped_chain = log_middleware(chain)
result = wrapped_chain.invoke({"topic": "chickens"})

中间件会拦截整个链的输入和输出。如果需要针对链中的特定步骤(如仅模型调用)应用中间件,只需包装那个子 Runnable 即可:

python 复制代码
model_with_middleware = retry_middleware()(model)
chain = prompt | model_with_middleware | parser

6.2 访问和修改 RunnableConfig

中间件可以读取或修改请求中的 config,用于传递上下文(如 request ID、用户身份)。

python 复制代码
@middleware
async def add_request_id(request, call_next):
    config = request.get("config") or RunnableConfig()
    if "metadata" not in config:
        config["metadata"] = {}
    config["metadata"]["request_id"] = str(uuid.uuid4())
    request["config"] = config
    return await call_next(request)

6.3 条件性跳过中间件

根据输入动态决定是否执行后续逻辑:

python 复制代码
@middleware
async def conditional_cache(request, call_next):
    input_text = request["input"]
    if len(input_text) > 100:
        # 长文本不缓存,直接调用
        return await call_next(request)
    else:
        # 短文本走缓存逻辑
        return await cache_middleware(request, call_next)

6.4 异常处理与降级

中间件可以捕获异常并返回降级结果:

python 复制代码
@middleware
async def fallback_middleware(request, call_next):
    try:
        return await call_next(request)
    except Exception as e:
        print(f"Primary failed: {e}, using fallback")
        fallback_model = ChatOpenAI(model="gpt-3.5-turbo")
        return await fallback_model.ainvoke(request["input"])

七、最佳实践

  1. 优先使用内置中间件:RetryMiddleware、RateLimiterMiddleware 等已经经过充分测试。

  2. 保持中间件幂等:重试时不应产生副作用。

  3. 异步优先:尽管中间件支持同步,但为了性能建议使用 async 定义。

  4. 避免修改输入原对象:如果需要修改输入,应拷贝一份,防止影响其他中间件。

  5. 中间件顺序:注意中间件列表的顺序,例如日志应该在最外层,重试应靠近内部。

    bash 复制代码
    1. 安全类(脱敏、审核)→ 第一道防线
    2. 上下文类(摘要、裁剪)→ 优化输入
    3. 可靠性类(重试、限流)→ 保障稳定
    4. 监控类(日志、统计)→ 最后观测
  6. 测试中间件:使用模拟的 call_next 单元测试中间件逻辑。

  7. 性能优化

    • 摘要用轻量模型(如 gpt-3.5-turbo),减少成本
    • 避免在 before_model/after_model 中执行耗时操作
    • 用 wrap_model_call 实现缓存,减少重复调用

企业标配中间件组合:

python 复制代码
middleware = [
    # 1. 安全
    PIIRedactionMiddleware(),
    ContentSafetyMiddleware(),
    # 2. 上下文
    SummarizationMiddleware(llm=ChatOpenAI(model="gpt-3.5-turbo")),
    MessageLimitMiddleware(max_messages=30),
    # 3. 可靠
    ModelRetryMiddleware(max_retries=2),
    ToolRetryMiddleware(max_retries=2),
    HumanInTheLoopMiddleware(tool_names=["send_email", "delete_data"]),
    # 4. 监控
    LoggingMiddleware(),
    TokenCounterMiddleware()
]
相关推荐
Cha0DD2 小时前
【由浅入深探究langchain】第二十集-SQL Agent+Human-in-the-loop
人工智能·python·ai·langchain
Cha0DD2 小时前
【由浅入深探究langchain】第十九集-官方的SQL Agent示例
人工智能·python·ai·langchain
Barkamin10 小时前
LangChain简单介绍
langchain
小李云雾11 小时前
零基础-从ESS6基础到前后端联通实战
前端·python·okhttp·中间件·eclipse·html·fastapi
百年੭ ᐕ)੭*⁾⁾13 小时前
Chroma简单上手
人工智能·语言模型·langchain·chroma·rag
Roselind_Yi15 小时前
【吴恩达2026 Agentic AI】面试向+项目实战(含面试题+项目案例)-2
人工智能·python·机器学习·面试·职场和发展·langchain·agent
JaydenAI15 小时前
[RAG在LangChain中的实现-04]常用的向量存储和基于向量存储的检索器
python·langchain·ai编程
Roselind_Yi15 小时前
【吴恩达2026 Agentic AI】面试向+项目实战(含面试题+项目案例)-1
人工智能·python·面试·职场和发展·langchain·gpt-3·agent
人间打气筒(Ada)17 小时前
go:如何实现接口限流和降级?
开发语言·中间件·go·限流·etcd·配置中心·降级