公司动态

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

📅 2026/8/27 20:22:29
LangChain 应用开发(十二):Agent 中间件与内置中间件
目录一、认识 Agent Middleware1. 什么是中间件二、中间件的工作机制1. Agent 生命周期与 Hook 介入点2. 多个 Middleware 的执行3. 中间件的分类三、SummarizationMiddleware1. 工作原理2. 参数说明3. 配置自动摘要中间件四、HumanInTheLoopMiddleware1. 参数说明2. 配置 HITL 中间件与捕获中断请求五、PIIMiddleware 与 TodoListMiddleware1. PIIMiddleware2. 代码案例3. TodoListMiddleware六、其他常用内置 Middleware1. 核心内置 Middleware 盘点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 模型的上下文长度通常为 128K3. keep — 摘要时保留的原始消息决定完成摘要后保留多少最新的原始上下文不被压缩。与 trigger 不同keep 同一时间只接收一种条件设定(tokens, N)摘要时保留最新的 N 个 Token(messages, N)摘要时保留最新的 N 条历史消息(fraction, ratio)摘要时保留 max_input_tokens * ratio 个 Token4. 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 挂载中间件观察它是如何自动触发摘要并保留最新消息的# 1. 准备模型对象 model ChatOpenAI( api_keyDEEPSEEK_API_KEY, base_urlDEEPSEEK_BASE_URL, modeldeepseek-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( modelmodel, middleware[ SummarizationMiddleware( modelmodel, # 满足任意一个条件即触发摘要Token100 或 消息数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 字典对象interrupt_on { get_weather: True, read_email_tool: False, send_email_tool: { allowed_decisions: [approve, reject], description: 发送邮件中断啦 } }True表示开启中断且允许所有决策动作approve 批准、edit 编辑、reject 拒绝。等价于get_weather: { allowed_decisions: [approve, edit, reject] }False表示不中断即该工具无需审批直接放行InterruptOnConfig是一个 TypedDict 子类可以通过字典形式赋值支持的 Key 包括allowed_decisions控制中断后允许的决策列表如只允许 [approve, reject]description特定工具专属的中断描述信息。优先级高于 description_prefix配置后会覆盖该工具的全局前缀描述2. description_prefix — 自定义中断描述用于设定全局的中断描述前缀默认值为 Tool execution requires approval。如果某个 Tool 内部没有独立定义 description则会统一复用该前缀描述2. 配置 HITL 中间件与捕获中断请求下面演示四个不同 Tool 的拦截配置并展示如何在 Agent 触发中断后提取 __interrupt__ 和 action_requests 信息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( modelmodel, tools[get_weather, send_email], checkpointerInMemorySaver(), 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: ( 查询北京天气并给 testexample.com 发送一封内容为“今天天气不错”的邮件 ), } ] }, configconfig, ) print(response.get(__interrupt__, []))输出示例创建 Agent 时通过 checkpointer 参数启用了短期记忆在调用时通过传递相同的 config 加载记忆。记住固定用法即可五、PIIMiddleware 与 TodoListMiddleware除了上下文窗口管理与人工风险控制外数据合规治理与复杂任务规划也是决定系统能否安全、稳定落地的能力。LangChain 提供了 PIIMiddleware 与 TodoListMiddleware分别应对这两类需求1. PIIMiddleware提示词无法从根本上保障数据合规。一旦用户在对话中传入手机号、身份证或信用卡大模型依然可能将其记录、回显甚至通过日志泄漏。PIIMiddleware 拦截在模型调用的前置与后置阶段确保敏感数据在进入模型前被自动脱敏防止隐私泄露核心处理策略strategyPIIMiddleware 支持四种数据处理策略redact默认直接将敏感信息替换为占位符如 [REDACTED_EMAIL]。适用于日志合规与通用审计mask对敏感数据进行掩码局部隐藏如 ****-****-****-1234。适用于需要对人类保留部分可读性的场景block一旦检测到敏感字段立即抛出 PIIDetectionError 异常。适用于高敏感度的场景hash将敏感信息替换为确定性的哈希指纹。在保持匿名化的同时保留关联标识适用于数据分析与调试主要配置参数pii_type要检测的敏感信息类型。支持内置类型email, credit_card, ip, mac_address, url或自定义类型名称strategy选择上述处理策略之一detector自定义检测逻辑。可传入正则表达式字符串如匹配 API Key或自定义检测函数apply_to_input是否在调用模型前检测默认为 Trueapply_to_output是否在模型调用后检测默认为Falseapply_to_tool_results是否对 Tool 的执行返回结果实施 PII 脱敏默认为 False2. 代码案例from rich import print as rprint # 构建针对不同敏感信息的安全防线 agent create_agent( modelmodel, middleware[ # 邮箱地址自动替换为 [REDACTED_EMAIL] PIIMiddleware(pii_typeemail, strategyredact), # 信用卡号自动隐藏前段只留尾数 PIIMiddleware(pii_typecredit_card, strategymask), # 通过正则检测 API Key一旦发现直接阻断请求 PIIMiddleware( pii_typeapi_key, detectorrsk-[a-zA-Z0-9]{32}, strategyblock ) ] ) response agent.invoke({ messages: [HumanMessage( 帮我向 156168188qq.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 工具描述不提供则使用内置描述通常不必提供代码案例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( modelmodel, 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] 字段观察六、其他常用内置 MiddlewareLangChain 预置了丰富的内置中间件将弹性容错、成本控制、上下文管理等非功能性需求封装为即插即用的模块1. 核心内置 Middleware 盘点模型与工具容错Fault ToleranceModelRetryMiddleware / ToolRetryMiddleware自动拦截模型网络波动或 Tool 执行报错支持指数退避、随机抖动及重试上限控制。可在耗尽重试后将错误文本返还给模型自行修正ModelFallbackMiddleware当主模型触发限流或服务崩溃时自动降级切换至备用模型ToolErrorMiddleware捕获工具未处理的 Exception 并转化为标准的 ToolMessage 错误日志避免系统直接溃败中断成本控制与上下文管理SummarizationMiddleware监控历史对话 Token 消耗当触发阈值时自动将早期上下文压缩为摘要保障长对话不超限ModelCallLimitMiddleware / ToolCallLimitMiddleware支持在运行级或线程级限制模型及工具的最高调用次数防范 Agent 陷入推导死循环导致账单失控ContextEditingMiddleware动态裁剪或清空历史交互日志中的冗余 Tool 返回值能力扩展与高效路由LLMToolSelector / ProviderToolSearch在大规模工具集场景下预先通过轻量模型或服务端检索过滤候选 Tool提升路由准确度并节省 Prompt TokenFilesystemMiddleware / 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 运行机制