LangChain系列——中间件(Middleware)

文章目录


一、中间件概述

在 create_agent() 的底层运行机制中,有几个重要的组件,分别是:

  • 模型(Model) :Agent 的"大脑",负责理解任务与决策推理。
  • 工具(Tools) :Agent 的"手脚",执行模型自己做不到的外部操作。
  • 系统提示词(System Prompt) :Agent的"角色",告诉模型该怎么想、参考什么上下文。
  • 中间件(Middleware) :Agent的"中枢",在执行流程的关键节点进行拦截、控制和增强。

什么是中间件

Middleware(中间件),简单说就是Agent 执行过程中的钩子函数,是 LangChain 1.x 的"王牌"工程化能力。

添加中间件之后的Agent架构:

为什么需要中间件

总结:

中间件的分类

链接:https://docs.langchain.com/oss/python/langchain/middleware/overview

和模型供应商无关的内置中间件分类

LangChain提供的和模型供应商无关的内置中间件分为六个类别

类型1:成本与资源控制类

核心目标:控成本、控配额、避免无限调用

这类中间件主要解决" Agent太贵、太能跑、停不下来 "的问题。

包含:

  • Model call limit:限制模型调用次数,防止一次任务反复请求 LLM,导致费用失控
  • Tool call limit:限制工具调用次数,避免 Agent 无限试错、死循环调工具
  • Summarization:在上下文快满时自动总结历史,减少 token 消耗
  • Context editing:裁剪上下文、清理工具调用痕迹,本质上也是为了节省上下文成本

业务场景理解:

适合生产环境的成本治理、配额治理、长会话优化、SaaS 产品控费。

类型2:稳定性与容错保障类

核心目标:保证服务不中断、失败后尽量自动恢复

这类中间件主要解决" 调用失败怎么办、模型挂了怎么办、工具超时怎么办 "。

包含:

  • Model fallback:主模型失败时切换备用模型
  • Model retry:模型调用失败后自动重试
  • Tool retry:工具调用失败后自动重试
    业务场景理解:
    适合线上生产系统,尤其是多模型、多工具依赖的 Agent。
    本质上是在做 高可用、容灾、鲁棒性建设。

类型3:安全与合规风控类

核心目标:让 Agent 可控、可审、合规

这类中间件主要解决" Agent乱执行、泄露敏感信息、做危险操作 "的问题。

包含:

  • Human-in-the-loop:在关键工具调用前暂停,等人工审批
  • PII detection:检测和处理个人敏感信息
  • Model call limit / Tool call limit:某种意义上也可归到风控,因为它能防止异常滥用
    业务场景理解:
    适合企业内部系统、客服系统、审批流、数据查询类 Agent。
    尤其是涉及:发邮件、调数据库、调财务/人事系统、导出敏感信息、执行外部动作等

类型4:决策增强与智能编排类

核心目标:提升 Agent 的决策质量和任务拆解能力

这类中间件主要解决" Agent不够聪明、不会规划、不会先筛工具 "的问题。

包含:

  • To-do list:给 Agent 增加任务规划、分步骤执行和状态跟踪能力
  • LLM tool selector:当工具太多时,用子模型筛选最相关的几个工具交给主模型
  • Subagent:允许生成子Agent,把复杂任务拆给不同角色处理
    业务场景理解:
    适合复杂任务流,比如:研究型 Agent、多步骤分析、报告生成、多角色协作、长链路任务编排等。
    这类本质上是在增强 Agent的"脑子"与"组织能力"。

类型5:执行能力扩展类

核心目标:给 Agent 更多"手脚"

这类中间件主要解决" Agent只能聊天,不能真正操作环境 "的问题。

包含:

  • Shell tool:给 Agent 持久 shell,会执行命令
  • File search:给 Agent 文件搜索能力,能做 Glob/Grep
  • Filesystem:给 Agent 文件系统读写与长期存储能力
    业务场景理解:
    适合工程 Agent、代码 Agent、本地自动化 Agent、运维 Agent。
    本质上是把 Agent 从"纯推理"扩展成"能操作环境的执行体"。

类型6:开发调试与测试辅助类

核心目标:方便开发、测试、验证 Agent 行为

这类中间件主要不是直接服务业务,而是服务于 研发和调试阶段 。

包含:

  • LLM tool emulator:用 LLM 模拟工具执行,便于测试(最典型)
  • Summarization:有时也可辅助调试长会话表现
  • Context editing:可用于测试上下文裁剪效果
  • Human-in-the-loop:也常用于调试高风险步骤
    业务场景理解:
    适合开发阶段快速验证流程、做 mock、减少真实工具依赖。

二、常用内置中间件的使用

LangChain 1.0 提供了 16 个预置中间件,开箱即用。

SummarizationMiddleware中间件

参数说明

举例1:测试trigger、keep参数

py 复制代码
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
import os

load_dotenv(override=True)

DASHSCOPE_API_KEY=os.getenv("DASHSCOPE_API_KEY")
DASHSCOPE_BASE_URL=os.getenv("DASHSCOPE_BASE_URL")

custom_profile = {
    "max_input_tokens": 128_000
}

model = init_chat_model(
    model="openai:qwen-plus",   # 底层调用的是ChatOpenAI
    profile=custom_profile,
    api_key=DASHSCOPE_API_KEY,
    base_url=DASHSCOPE_BASE_URL
)

from langchain.agents import create_agent
from langchain.agents.middleware import SummarizationMiddleware
from langchain.messages import SystemMessage, HumanMessage, AIMessage

messages = [
    SystemMessage("你是个非常友好的AI助手"),
    HumanMessage("你好啊,我是老王,你是谁?"),
    AIMessage("你好老王,我是小王"),
    HumanMessage("好的小王,很高兴认识你"),
    AIMessage("你高兴得太早了"),
    HumanMessage("呵呵,你什么意思")
]
agent = create_agent(
    model=model,
    middleware=[
        SummarizationMiddleware(
            model=model,
            trigger=[
                ("tokens", 100),
                ("messages", 6),
                ("fraction", 0.001)
            ],
            keep=("messages", 2)
            )
    ]
)
response = agent.invoke({
    "messages": messages
})
for msg in response["messages"]:
    msg.pretty_print()

举例2:测试summary_prompt参数

HumanInTheLoopMiddleware中间件

HumanInTheLoopMiddleware(人在环中间件、人工审核中间件)在 工具调用前 中断Agent运行,等待用户对工具调用请求决策。可选的决策有: approve(同意执行) 、 edit(编辑调用配置后执行) 、 reject(拒绝执行) 。

参数说明

参数1:interrupt_on ---工具名和中断策略的映射
参数2:description_prefix ---自定义中断描述

默认为 "Tool execution requires approval" ,下面的举例可以看到效果

举例过程1:调用前中断

举例过程2:指明工具调用请求决策

北京要改为上海

PIIMiddleware中间件

敏感信息保护。

PII中间件用于检测和处理对话中的个人身份信息(Personally Identifiable Information,PII),支持自定义处理策略。

参数说明

参数1:pii_type ---检测的PII数据类型

可以是内置类型或自定义类型,内置类型有:

参数2:strategy ---处理PII信息的策略
参数3:detector ---自定义 PII检测函数 或者 正则表达式
参数4:apply_to_input ---是否在调用模型前检测

默认为True

参数5:apply_to_output ---是否在模型调用后检测

默认为False。

参数6:apply_to_tool_results ---是否在工具调用后检测其输出

默认为False。

通常我们 只在模型调用前 检测。因为PII检测的主要目的是避免将敏感信息发送给模型服务导致信息泄露

举例1:使用内置检测器

举例2:自定义检测器/函数

TodoListMiddleware中间件

参数说明

案例设计

代码

分析

为了让 TodoListMiddleware 生效,Agent、工具和中间件三者之间必须满足特定的协同契约:

三、其它内置中间件

ModelCallLimitMiddleware中间件

限制模型调用次数,避免无限循环,控制调用成本。

举例1:整个会话限制-优雅退出

举例2:整个会话限制-抛异常

举例3:单次调用限制-优雅退出

需要fake-server重复触发工具调用,代码如下

举例4:单次调用限制-抛异常

ToolCallLimitMiddleware中间件

限制工具调用次数,可以 限制所有工具 调用的总次数,也可以 限制特定工具 的调用次数。

举例1:整个会话限制-优雅结束

ModelFallbackMiddleware中间件

用于故障转移,当主模型无法访问时,启用备用模型

或者

LLMToolSelectorMiddleware中间件

智能工具筛选。

当工具太多时,用子模型筛选最相关的几个工具。

ToolRetryMiddleware中间件

基于指数退避算法,设置工具调用失败时的重试策略。

举例1:带抖动

举例2:无抖动

如果你把 backoff_factor = 0,就意味着不使用指数增长,重试之间始终用固定的 initial_delay。

为什么要引入 Jitter(抖动)?关闭它会有什么问题?

在单用户、单并发的测试环境下,关闭 jitter没有任何副作用,甚至能让等待时间非常规律、可预测。

但在高并发的生产环境中,关闭 jitter会引发灾难性的 "惊群效应(Thundering Herd Problem)" :

  • 没有 Jitter 的惨剧( jitter=False ):
    假设某刻天气 API 服务突然宕机了 1 秒。此时刚好有 1000 个用户同时发起了查询。因为这 1000个请求同时失败,并且它们都严格死板地等待 1 秒、2 秒、4 秒......
    这意味着,在第 1 秒、第 2 秒、第 4 秒的那个精准的时间点上,这 1000 个请求会整整齐齐地再次同时轰炸服务器。刚刚复活的服务器瞬间又被这波整齐的峰值流量压垮,形成恶性循环。

  • 引入 Jitter 的优势( jitter=True ):

    通过给重试时间加上随机性,这 1000 个请求会在 01秒和12的区间内均匀地错开(削峰填谷)。流量被平摊到了整条时间轴上,服务器就能轻松地分批处理完这些请求。

ModelRetryMiddleware中间件

举例1:继续运行

举例2:抛异常

LLMToolEmulator中间件

某些情况下,工具尚未开发完成,我们希望先测试工具调用,可以用LLM tool emulator模拟工具。

ContextEditingMiddleware中间件

上下文编辑中间件,该中间件提供了上下文管理的一种方式。

通过更改发送给模型的消息列表来控制成本。

注意:不会更改消息列表。因此我们只能通过token用量来推测是否对消息列表进行了裁剪。

实验组-启用上下文编辑

对照组-不裁剪上下文

可以观察到,对照组的input_tokens明显大于实验组

FilesystemFileSearchMiddleware中间件

基于系统的Glob和Grep检索工具,为Agent赋予本地文件搜索和分析的能力。

Shell tool中间件

为Agent提供一个可以执行命令的Shell环境。

Windows下无法测试。

Filesystem中间件

这是源自deepagents(基于LangChain的另一个框架)的中间件

内置了四个工具,分别用于查看目录、读文件、写文件和改文件。

Subagent中间件

也是来自deepagents的中间件

用于便捷地创建子Agent。

四、多个中间件组合及执行顺序

五、自定义中间件

什么是hook函数(钩子函数)

Hook 函数,中文常叫 钩子函数 ,指的是:在某个既定流程的特定时机,被框架、系统或主程序 自动调用 的扩展函数

无论是官方内置中间件、自定义中间件、还是下文提到的便捷装饰器中间件,通常都是通过实现其中的一个或多个hook来生效的。

LangChain的hook函数分类

类型1:Node-style hooks(节点风格钩子)

类型2:Wrap-style hooks(包装风格钩子)

Node-style hooks函数用法

基本用法

基于装饰器实现
基于类实现

使用场景

before_model 通常的场景
after_model 通常的场景

两种方法的统一

参数说明

返回值说明

jump_to 目标:

装饰器参数:can_jump_to

基于装饰器实现
基于类实现

和基于装饰器实现的关键区别在于:需要引入额外的装饰器 @hook_config 为 can_jump_to 传参。

Wrap-style hooks函数用法

基本用法

wrap_model_call

我们可以同时在模型调用前后做事,所以命名为 wrap_model_call ,wrap意为 包裹 。

场景1:重试逻辑
wrap_tool_call

我们可以同时在工具调用前后做事,所以命名为 wrap_tool_call 。

参数说明

装饰器和类的选择

情况1:中间件只用一个钩子函数,推荐用装饰器,需要多个钩子函数推荐类写法

情况2:复杂配置推荐用类实现

装饰器当然也可以通过函数闭包传递参数,但在自省(运行时类型校验)、调试等方面天然不如类写法

方便。

基于类的写法可以随时打印参数信息,而基于装饰器的闭包实现则难以做到。

情况3:跨项目复用推荐用类写法

如果希望中间件成为一个可实例化、可封装、可测试的组件,类写法更加合适,因为这些本就是类擅长的场景,装饰器的闭包也能实现,但使用不友好。

hook函数执行顺序(重要)

参考视频

https://www.bilibili.com/video/BV1rv7A6oEeP/?spm_id_from=333.788.videopod.episodes\&vd_source=0467ab39cc5ec5940fee22a0e7797575\&p=64

相关推荐
hyunbar5 小时前
LangChain 实战:中间件Summarization详解
langchain·agent·harness
归去来 兮5 小时前
LangChain从入门到精通
python·langchain·langgraph
沉下心来学鲁班6 小时前
初识DeepAgents搭建第一个智能体
人工智能·python·langchain
带鱼吃猫7 小时前
LangGraph入门:支持搜索的智能代理系统
人工智能·langchain
BreezeJiang8 小时前
给 AI 补记性,别只会把历史一股脑塞 prompt:截断、总结、检索三条路怎么选
langchain
染指11108 小时前
118.Agent-LangChain核心组件-StructuredOutPut结构化输出-工具策略(ToolStrategy )
人工智能·langchain·agent·agents
Darling噜啦啦12 小时前
别再用「死板 RAG」!手把手实现会思考、会纠错、会联网的 Agentic RAG(LangGraph 实战)
langchain·agent
海边捡石子12 小时前
用 Vue 3 + FastAPI 跑通 LangChain DeepAgent:任务规划、双轨 Skills,以及可复现的本地 Agent 工作台
langchain·aigc·agent
梅梅绵绵冰12 小时前
RAG知识库
langchain·llm·longchain4j