LangChain 应用开发(十二):Agent 中间件与内置中间件

目录

[一、认识 Agent Middleware](#一、认识 Agent Middleware)

[1. 什么是中间件](#1. 什么是中间件)

二、中间件的工作机制

[1. Agent 生命周期与 Hook 介入点](#1. Agent 生命周期与 Hook 介入点)

[2. 多个 Middleware 的执行](#2. 多个 Middleware 的执行)

[3. 中间件的分类](#3. 中间件的分类)

三、SummarizationMiddleware

[1. 工作原理](#1. 工作原理)

[2. 参数说明](#2. 参数说明)

[3. 配置自动摘要中间件](#3. 配置自动摘要中间件)

四、HumanInTheLoopMiddleware

[1. 参数说明](#1. 参数说明)

[2. 配置 HITL 中间件与捕获中断请求](#2. 配置 HITL 中间件与捕获中断请求)

[五、PIIMiddleware 与 TodoListMiddleware](#五、PIIMiddleware 与 TodoListMiddleware)

[1. PIIMiddleware](#1. PIIMiddleware)

[2. 代码案例](#2. 代码案例)

[3. TodoListMiddleware](#3. TodoListMiddleware)

[六、其他常用内置 Middleware](#六、其他常用内置 Middleware)

[1. 核心内置 Middleware 盘点](#1. 核心内置 Middleware 盘点)

[2. 内置中间件选型](#2. 内置中间件选型)

总结


一、认识 Agent Middleware

在前两篇文章里,我们使用 system_prompt 约束智能体行为,response_format 限定输出,同时通过 stream_mode 掌握流式交互效果

然而在应用开发中,很多需求超出了这些基础配置参数的能力范畴:

  • 模型调用前:如何根据当前用户权限动态修改 Prompt 或注入额外凭证?

  • Tool 调用前/后:如何对敏感工具的参数进行校验,或将工具执行结果自动记录到日志审计系统?

  • 长对话上下文:当对话轮数过多触发 Token 窗口溢出时,如何实现自动摘要与历史裁剪?

  • 数据安全:如何拦截用户输入的身份证号、手机号,并在送入大模型前自动脱敏?

这些无法通过简单 Prompt 搞定的横向控制逻辑,正需要 Agent Middleware(中间件) 发挥作用


1. 什么是中间件

Middleware(中间件)是一种生命周期拦截机制。 它允许开发者在 Agent 运行的关键节点(如 Model 调用前后、Tool 执行前后、State 改变时)植入自定义的逻辑代码,从而对数据包进行审查、改写或中断控制

Agent 自身负责做业务,而 Middleware 负责把关

二、中间件的工作机制

理解中间件的机制,核心在于厘清两个概念:Middleware(中间件)Hook(钩子)

  • Middleware 是整体机制与逻辑载体:它是一个具体的类或功能模块,里面编写了我们希望执行的拦截逻辑

  • Hook 是 Middleware 介入生命周期的具体点位:它是 Agent 运行流程中预留的 "插座",决定了中间件的代码到底在哪个瞬间被触发


1. Agent 生命周期与 Hook 介入点

在 Agent 的 ReAct 循环中,中间件通过挂载在不同的 Hook 点位,实现对全流程的动态监管:


2. 多个 Middleware 的执行

当我们在 Agent 中同时配置了多个 Middleware 时,它们的执行顺序遵循经典的洋葱圈模型:

复制代码
请求进入 ──> [ Middleware A (前置) ] ──> [ Middleware B (前置) ] ──> [ 核心执行 (Model/Tool) ]
                                                                       │
输出返回 <── [ Middleware A (后置) ] <── [ Middleware B (后置) ] <───┘

按顺序注册的中间件,在前置 Hook 阶段按照 A -> B 顺序正向执行;而在后置 Hook 阶段则按照 B -> A 顺序逆向剥离


3. 中间件的分类

可以按照中间件在实际业务中解决的工程问题进行分类:

复制代码
Agent Middleware
│
├── 1. 上下文管理 (Context Management)
│    └── 核心解决:Token 溢出与历史冗余(如 SummarizationMiddleware)
│
├── 2. 人工干预与控制 (Human-in-the-loop)
│    └── 核心解决:高危操作防护与人工审批(如 HumanInTheLoopMiddleware)
│
├── 3. 安全与合规治理 (Security & Privacy)
│    └── 核心解决:敏感信息泄露与 PII 拦截(如 PIIMiddleware)
│
├── 4. 能力与任务扩展 (Capability Extension)
│    └── 核心解决:长链条任务拆解与显式规划(如 TodoListMiddleware)
│
└── 5. 稳定性与容错 (Resilience)
     └── 核心解决:网络波动与 API 重试(如 Retry 相关中间件)

三、SummarizationMiddleware

在多轮对话或复杂长链条任务中,Agent 极易遇到上下文窗口溢出。随着对话轮数的累积,系统性能与经济成本会迅速恶化

复制代码
HumanMessage ──► AIMessage ──► HumanMessage ──► AIMessage ...
    │
    ▼
上下文膨胀 ──► Token 消耗指数级上升 ──► 触发模型 Token 上限 ──► 响应变慢且成本暴涨

SummarizationMiddleware (自动摘要中间件) 正是为此而生


1. 工作原理

SummarizationMiddleware 会在 Agent 执行周期的 Before Model 阶段监视当前状态流。一旦发现历史消息触及预设阈值,它就会在后台自动介入:

这样既通过摘要保护了 Agent 对早期事实的记忆,又解放了上下文窗口压力


2. 参数说明

1. model --- 用于摘要的模型

指定负责生成摘要的模型。支持直接传入模型对象或字符串模型名称(如 "gpt-4o-mini")。如果传递的是模型名称,底层会自动调用 init_chat_model 完成初始化。在实际生产中,通常建议传入一个更便宜、速度更快的小模型来降低摘要成本

2. trigger --- 摘要触发条件

传入一个包含条件元组的列表,当列表中任意一个条件满足时,即刻触发摘要逻辑:

  • **("tokens", N):**历史消息累计的 Token 数达到 N 时触发

  • **("messages", N):**历史消息的总条数达到 N 时触发

  • **("fraction", ratio):**历史 Token 占模型最大上下文窗口的比例达到 max_input_tokens * ratio 时触发

如果使用 fraction 条件,要求模型的配置 profile 中必须包含 max_input_tokens。若某些模型的 profile 为空(如部分 DeepSeek 模型),需手动配置补充上下文长度(例如 DeepSeek 模型的上下文长度通常为 128K)

3. keep --- 摘要时保留的原始消息

决定完成摘要后,保留多少最新的原始上下文不被压缩**。与 trigger 不同,keep 同一时间只接收一种条件设定:**

  • **("tokens", N):**摘要时保留最新的 N 个 Token

  • **("messages", N):**摘要时保留最新的 N 条历史消息

  • **("fraction", ratio):**摘要时保留 max_input_tokens * ratio 个 Token

4. token_counter --- 统计 Token 数量的函数

统计上下文 Token 的自定义函数,默认使用 LangChain 提供的 count_tokens_approximately,一般无需更改

原理:该函数通过先统计文本字符数,除以单 Token 的估算字符数转换为粗略 Token数,再叠加额外开销进行近似计算

5. summary_prompt --- 自定义摘要提示词

用于定制生成摘要时的 Prompt 模板。该提示词中必须包含 {messages} 占位符,框架会将待压缩的历史消息列表自动注入其中。若不指定,则使用内置的标准摘要提示词

6. trim_token_to_summarize --- 摘要时历史消息的最大 Token 数限制

单次送入摘要模型的历史消息 Token 上限,默认值为 "4000"。如果待摘要的历史消息总 Token 数超过该值,超出的部分会被提前裁剪。

如果 trigger 中设置了较大的 Token 触发阈值,必须同步调大该参数,否则长历史消息在生成摘要前就会丢失信息


3. 配置自动摘要中间件

下面是一个完整的实战示例。我们构建一个包含 6 条历史消息的上下文,并为 Agent 挂载中间件,观察它是如何自动触发摘要并保留最新消息的:

python 复制代码
# 1. 准备模型对象
model = ChatOpenAI(
    api_key=DEEPSEEK_API_KEY,
    base_url=DEEPSEEK_BASE_URL,
    model="deepseek-v4-flash",
    extra_body={
        "thinking": {"type": "disabled"}
    },
    profile={"max_input_tokens": 4000}
)

# 2. 模拟一组达到触发条件的多轮对话上下文(共 6 条消息)
messages = [
    SystemMessage("你是个非常友好的AI助手"),
    HumanMessage("你好啊,我是老王,你是谁?"),
    AIMessage("你好老王,我是小王"),
    HumanMessage("好的小王,很高兴认识你"),
    AIMessage("你高兴得太早了"),
    HumanMessage("呵呵,你什么意思")
]

# 3. 创建挂载了 SummarizationMiddleware 的 Agent
agent = create_agent(
    model=model,
    middleware=[
        SummarizationMiddleware(
            model=model,
            # 满足任意一个条件即触发摘要:Token>100 或 消息数>=6 或 占比>0.001
            trigger=[
                ("tokens", 100),
                ("messages", 6),
                ("fraction", 0.001)
            ],
            # 摘要完成后,仅保留最新 2 条消息,其余压缩为 Summary
            keep=("messages", 2)
        )
    ]
)

# 4. 执行 Agent 观察最终消息列表结构
response = agent.invoke({"messages": messages})

# 打印压缩与响应后的上下文消息
for msg in response["messages"]:
    msg.pretty_print()

输出示例:

通过这套机制,旧的对话记录被高效提炼为系统摘要,最新消息被保留,Agent 既记住了 "用户是老王",又避免了随着对话深入而导致的上下文爆仓问题

四、HumanInTheLoopMiddleware

在真实工程落地中,自主 Agent ≠ 无监管放任。对于查询天气、检索新闻等只读操作,尽可以放手让 Agent 自主决策;但对于具有真实副作用的操作(如发送邮件、划转资金、修改数据库),一旦 Agent 发生幻觉或误判,造成的损失往往不可逆

HumanInTheLoopMiddleware (人在环中间件) 正是解决这一痛点的防线。它可以在 Tool 执行前强行中断 Agent 进程,抛出等待审批的中断请求,由人工确认后再继续执行


1. 参数说明

配置 HumanInTheLoopMiddleware 时,主要通过 interrupt_on 和 description_prefix 精细化控制每个 Tool 的中断策略与提示信息:

1. interrupt_on --- 工具名和中断策略的映射

传入一个字典,实现工具上的精细控制。策略值支持 True、False 或 InterruptOnConfig 字典对象:

python 复制代码
interrupt_on = {
    "get_weather": True,
    "read_email_tool": False,
    "send_email_tool": {
        "allowed_decisions": ["approve", "reject"],
        "description": "发送邮件中断啦"
    }
}
  • True:表示开启中断,且允许所有决策动作(approve 批准、edit 编辑、reject 拒绝)。等价于:

    python 复制代码
    "get_weather": {
        "allowed_decisions": ["approve", "edit", "reject"]
    }
  • False :表示不中断,即该工具无需审批,直接放行

  • InterruptOnConfig:是一个 TypedDict 子类,可以通过字典形式赋值,支持的 Key 包括:

    1. allowed_decisions:控制中断后允许的决策列表(如只允许 "approve", "reject"

    2. description:特定工具专属的中断描述信息。优先级高于 description_prefix,配置后会覆盖该工具的全局前缀描述

2. description_prefix --- 自定义中断描述

用于设定全局的中断描述前缀,默认值为 "Tool execution requires approval"。如果某个 Tool 内部没有独立定义 description,则会统一复用该前缀描述


2. 配置 HITL 中间件与捕获中断请求

下面演示四个不同 Tool 的拦截配置,并展示如何在 Agent 触发中断后提取 interrupt 和 action_requests 信息:

python 复制代码
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langchain_core.tools import tool
from langgraph.checkpoint.memory import InMemorySaver

@tool
def get_weather(city: str) -> str:
    """查询指定城市天气"""
    return f"{city}今天天气不错"

@tool
def send_email(recipient: str, body: str) -> str:
    """发送邮件"""
    print(">>> 真正执行发送邮件")
    return f"邮件已发送给 {recipient}"

agent = create_agent(
    model=model,
    tools=[get_weather, send_email],
    checkpointer=InMemorySaver(),
    middleware=[
        HumanInTheLoopMiddleware(
            interrupt_on={
                "get_weather": False,   # 直接执行
                "send_email": {
                    "allowed_decisions": ["approve", "reject"]
                },
            }
        )
    ],
)

config = {
    "configurable": {
        "thread_id": "demo-1"
    }
}

response = agent.invoke(
    {
        "messages": [
            {
                "role": "user",
                "content": (
                    "查询北京天气,并给 test@example.com "
                    "发送一封内容为"今天天气不错"的邮件"
                ),
            }
        ]
    },
    config=config,
)

print(response.get("__interrupt__", []))

输出示例:

创建 Agent 时通过 checkpointer 参数启用了短期记忆,在调用时通过传递相同的 config 加载记忆。记住固定用法即可

五、PIIMiddleware 与 TodoListMiddleware

除了上下文窗口管理与人工风险控制外,数据合规治理复杂任务规划也是决定系统能否安全、稳定落地的能力。LangChain 提供了 PIIMiddleware 与 TodoListMiddleware,分别应对这两类需求


1. PIIMiddleware

提示词无法从根本上保障数据合规。一旦用户在对话中传入手机号、身份证或信用卡,大模型依然可能将其记录、回显甚至通过日志泄漏。PIIMiddleware 拦截在模型调用的前置与后置阶段,确保敏感数据在进入模型前被自动脱敏,防止隐私泄露

核心处理策略(strategy)

PIIMiddleware 支持四种数据处理策略:

  • redact(默认):直接将敏感信息替换为占位符(如 REDACTED_EMAIL)。适用于日志合规与通用审计

  • mask:对敏感数据进行掩码局部隐藏(如 ****-****-****-1234)。适用于需要对人类保留部分可读性的场景

  • block:一旦检测到敏感字段,立即抛出 PIIDetectionError 异常。适用于高敏感度的场景

  • hash:将敏感信息替换为确定性的哈希指纹。在保持匿名化的同时保留关联标识,适用于数据分析与调试

主要配置参数

  • pii_type:要检测的敏感信息类型。支持内置类型("email", "credit_card", "ip", "mac_address", "url")或自定义类型名称

  • strategy:选择上述处理策略之一

  • detector:自定义检测逻辑。可传入正则表达式字符串(如匹配 API Key)或自定义检测函数

  • apply_to_input:是否在调用模型前检测(默认为 True)

  • apply_to_output:是否在模型调用后检测(默认为False)

  • apply_to_tool_results:是否对 Tool 的执行返回结果实施 PII 脱敏(默认为 False)


2. 代码案例

python 复制代码
from rich import print as rprint

# 构建针对不同敏感信息的安全防线
agent = create_agent(
    model=model,
    middleware=[
        # 邮箱地址自动替换为 [REDACTED_EMAIL]
        PIIMiddleware(pii_type="email", strategy="redact"),
        # 信用卡号自动隐藏前段,只留尾数
        PIIMiddleware(pii_type="credit_card", strategy="mask"),
        # 通过正则检测 API Key,一旦发现直接阻断请求
        PIIMiddleware(
            pii_type="api_key",
            detector=r"sk-[a-zA-Z0-9]{32}",
            strategy="block"
        )
    ]
)

response = agent.invoke({
    "messages": [HumanMessage("""
    帮我向 156168188@qq.com 发送一封邮件
    同时查看银行卡号: 5105-1051-0510-5100 的余额
    """)]
})

rprint(response["messages"][0])

输出示例:


3. TodoListMiddleware

面对多步骤任务时,普通 Agent 极易发生漏步、无序执行或执行中断后无法恢复的问题

TodoListMiddleware 显式赋予了 Agent 任务规划与进度跟踪能力。它通过自动拦截 Agent 的思考链路,引导其将大目标拆解为清晰的子任务列表,并按 pending(待办)、in_progress(进行中)、completed(已完成)三种状态动态更新执行进度。它不仅是输出控制,更是帮助复杂 Agent 显式维护状态与记忆的基础设施

工作原理与使用约定

  • 自动工具注入:只需挂载 TodoListMiddleware()(无需额外配置复杂参数),中间件会自动向 Agent 注入 write_todos 管理工具

  • 依赖 Checkpointer:由于任务清单状态需要在多轮推导和工具调用间持续同步,必须为 Agent 配置 checkpointer(如 InMemorySaver())

  • 驱动推导循环:Agent 在接收复杂任务时会自动判定并调用 write_todos 初始化 Task 清单,每完成一步自动更新状态,避免步骤遗漏

参数说明:

  • **system_prompt:**自定义指导 todo 列表使用的提示词,不提供则使用内置提示词,通常不必提供

  • **tool_description:**自定义 write_tools 工具描述,不提供则使用内置描述,通常不必提供

代码案例:

python 复制代码
from rich import print as rprint

@tool
def search_web(query: str) -> str:
    """搜索资料"""
    return f"已搜索:{query}"


@tool
def write_summary(topic: str) -> str:
    """生成主题总结"""
    return f"已生成 {topic} 的总结"


agent = create_agent(
    model=model,
    tools=[search_web, write_summary],
    middleware=[
        TodoListMiddleware()
    ],
)

result = agent.invoke(
    {
        "messages": [{
            "role": "user",
            "content": (
                "请使用 Todo 列表规划并完成以下任务:"
                "1. 搜索 LangChain;"
                "2. 搜索 LangGraph;"
                "3. 总结 LangChain 用途;"
                "4. 总结 LangGraph 用途;"
                "5. 比较两者差异;"
                "6. 给出最终结论。"
            )
        }]
    }
)

rprint(result["todos"])

输出示例:

TodoListMiddleware 并不会强制 Agent 每次都生成 Todo,而是向 Agent 注入 write_todos 工具和对应提示词。只有当模型判断任务足够复杂时才会调用该工具;是否实际生效,可以通过消息中的 write_todos 工具调用或最终的 result"todos" 字段观察

六、其他常用内置 Middleware

LangChain 预置了丰富的内置中间件,将弹性容错、成本控制、上下文管理等非功能性需求封装为即插即用的模块


1. 核心内置 Middleware 盘点

  • 模型与工具容错(Fault Tolerance)

    • **ModelRetryMiddleware / ToolRetryMiddleware:**自动拦截模型网络波动或 Tool 执行报错,支持指数退避、随机抖动及重试上限控制。可在耗尽重试后将错误文本返还给模型自行修正

    • ModelFallbackMiddleware:当主模型触发限流或服务崩溃时,自动降级切换至备用模型

    • ToolErrorMiddleware:捕获工具未处理的 Exception 并转化为标准的 ToolMessage 错误日志,避免系统直接溃败中断

  • 成本控制与上下文管理

    • SummarizationMiddleware:监控历史对话 Token 消耗,当触发阈值时,自动将早期上下文压缩为摘要,保障长对话不超限

    • ModelCallLimitMiddleware / ToolCallLimitMiddleware:支持在运行级或线程级限制模型及工具的最高调用次数,防范 Agent 陷入推导死循环导致账单失控

    • ContextEditingMiddleware:动态裁剪或清空历史交互日志中的冗余 Tool 返回值

  • 能力扩展与高效路由

    • **LLMToolSelector / ProviderToolSearch:**在大规模工具集场景下,预先通过轻量模型或服务端检索过滤候选 Tool,提升路由准确度并节省 Prompt Token

    • **FilesystemMiddleware / ShellToolMiddleware / SubagentMiddleware:**为 Agent 注入文件持久化存储、Shell 命令行交互以及动态派生子 Agent 协助执行的能力


2. 内置中间件选型

中间件名称 分类 解决的痛点 应用场景
HumanInTheLoopMiddleware 业务风控 高危操作产生不可逆副作用 资金划转、发邮件、写 / 删数据库
PIIMiddleware 安全合规 用户敏感隐私泄露至外部 LLM 手机号、身份证、API Key 脱敏
TodoListMiddleware 规划决策 复杂长流程任务漏步、无序 多步骤 ETL、自动化工作流
ModelRetryMiddleware / ToolRetryMiddleware 弹性容错 网络抖动或接口偶发性报错 生产环境高可用保障
ModelFallbackMiddleware 弹性容错 主模型 API 服务不可用 异构大模型服务降级
SummarizationMiddleware 上下文管理 上下文超出 Token 限制 / 成本高昂 多轮客服、长文本助手
ModelCallLimitMiddleware / ToolCallLimitMiddleware 成本治理 Agent 陷入死循环耗尽预算 复杂自主推理任务兜底

中间件机制解耦了 Agent 的业务推导逻辑与非功能性控制逻辑。开发人员无需改动 LLM 提示词或工具内部代码,只需组合不同的 Middleware,即可快速构建出具备安全防护、自我修复与高效规划能力的高可用 Agent 应用

总结

本章正式进入 LangChain 的 Agent Middleware 中间件机制,了解了中间件在 Agent 运行生命周期中的作用,以及 Middleware 与 Hook 钩子之间的关系。通过中间件,我们可以在不修改 Agent 核心逻辑的情况下,为其增加上下文管理、人工审批、隐私保护和任务规划等能力

随后,我们重点学习了 SummarizationMiddleware、HumanInTheLoopMiddleware、PIIMiddleware 和 TodoListMiddleware 等常用内置中间件,理解了它们各自解决的问题、基本配置方式和典型应用场景,并对 LangChain 提供的其他 Middleware 能力有了整体认识

下一篇将进一步学习 自定义 Middleware,深入 Agent 的执行生命周期与各种 Hook,掌握如何根据实际业务需求编写自己的中间件,从 "使用现成能力" 进一步走向 "扩展 Agent 运行机制"

相关推荐
智圣新创0141 分钟前
存量数字化校园效能升级:智圣新创数据治理及一表通平台实现填报减负与数据价值双升
大数据·人工智能·物联网
课件帮43 分钟前
教师数字分身+AI协同备课:2026课件帮如何重构教学内容生产?
人工智能·重构
zx_741484811 小时前
【Python 入门】Python 正则表达式零基础讲解:基础语法 + 元字符 + 常用案例
python·正则表达式
cxr8281 小时前
skills迁移备份
人工智能·智能体
俊哥V1 小时前
AI一周事件 · 2026年8月12日—8月25日
人工智能·ai
prog_61031 小时前
【笔记】用cursor手搓cursor(九)
人工智能·笔记·大语言模型·agent
weixin199701080161 小时前
《大促护航:电商API限流与降级,双11峰值500万调用的架构复盘》(附Python源码)
python·架构·wpf
ai小陈1 小时前
PyTorch多GPU分布式训练实战:从单卡脚本迁移到DDP
服务器·人工智能·pytorch·分布式·深度学习·ai·gpu算力
sdzhyt1 小时前
从劳动仲裁看智能体如何真正走进政务一线?
大数据·人工智能