langchain 中间件
发布时间:2026/10/27 10:24:25 锦皓数字建站

1. 中间件概述在create_agent( )的底层运行机制中有几个重要的组件分别是模型(Model)Agent 的“大脑”负责理解任务与决策推理工具(Tools)Agent 的“手脚”执行模型自己做不到的外部操作系统提示词(System Prompt)Agent 的 “角色”告诉模型该怎么想、参考什么上下文中间件(Middleware)Agent 的 “中枢”在执行流程的关键节点进行拦截、控制和增强语法如下1.1 什么是中间件Middleware(中间件)是 Agent 执行过程中的钩子函数钩子框架或系统在某些关键执行点暴露的扩展接口开发者可以“挂上”自己的逻辑在那些点上插入、修改或替换行为而无需改变主流程代码就像在流水线上某个环节设置了一个“检查点”或 “插入器”借助中间件开发者可以高度定制和控制 Agent 运行的每一个环节是处理 Agent 生命周期的标准方式没有中间件的 agent 框架添加中间件之后的Agent架构在 LangChain 的 Agent 执行循环中比如 “模型调用前”、“模型调用后”、“工具调用前后” 设置一些钩子 hooks可以在不改 Agent 主体逻辑的情况下实现策略与治理1.2 为什么需要中间件如果没有中间件Agent 的执行流程通常比较直接用户输入 → 拼接提示词/消息 → 调用模型 → 如有需要调用工具 → 返回结果这种方式只适用于简单需求一旦进入真实项目往往会遇到很多额外需求这些需求有一个共同特点它们不是 Agent 的核心业务逻辑但又会影响 Agent 的执行过程这些逻辑又不能全部直接写进主流程会造成主程序混乱代码难以复用等问题中间件的价值在于把这些与业务无关、但与执行过程强相关的横切逻辑从 Agent 主流程中分离出来。让 Agent 主体代码聚焦业务 而借助中间件实现“拦截流程、修改流程、增强流程”目前LangChain 1.x 的中间件能实现如下功能✅ 日志与分析 - 追踪行为、调试、性能监控✅ 转换 - 修改提示词、工具选择、输出格式✅ 容错 - 重试、降级、早期终止✅ 安* - 限流、守护规则、PII 检测1.3 中间件的分类分为自定义中间件允许开发者自定义从而实现更加灵活的 Agent 行为管理内置中间件LangChain 实现并提供的模型供应商定制的中间件依赖于特定模型服务的实现和模型供应商无关的中间件LangChain提供的与供应商无关的中间件如下链接Middleware overview - Docs by LangChain1.4 和模型供应商无关的内置中间件分类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 可控、可审、合规包含Human-in-the-loop人在环在关键工具调用前暂停等人工审批PII detection检测和处理个人敏感信息业务场景理解适合企业内部系统、客服系统、审批流、数据查询类 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/GrepFilesystem给 Agent 文件系统读写与长期存储能力业务场景理解适合工程 Agent、代码 Agent、本地自动化 Agent、运维 Agent本质上是把 Agent 从“纯推理”扩展成“能操作环境的执行体”类型6开发调试与测试辅助类核心目标方便开发、测试、验证 Agent 行为这类中间件主要不是直接服务业务而是服务于研发和调试阶段包含LLM tool emulator用 LLM 模拟工具执行便于测试最典型Summarization有时也可辅助调试长会话表现Context editing可用于测试上下文裁剪效果Human-in-the-loop也常用于调试高风险步骤业务场景理解适合开发阶段快速验证流程、做 mock、减少真实工具依赖2. 常用内置中间件的使用2.1 SummarizationMiddleware 中间件作用对历史消息列表进行摘要总结达到压缩上下文的效果原理在达到触发条件时调用大模型对历史消息进行摘要将摘要的结果作为HumanMessage 放到消息列表最开始的位置参数说明如下参数1model — 用于生成摘要的模型可以是模型名称也可以是模型对象如果传递的是模型名称底层会调用init_chat_model初始化模型参数2trigger — 摘要触发条件是一个列表每个元素对应一个条件当任一条件满足时触发摘要tokenstoken 的数量历史 token 的累计数量达到该值触发摘要messages历史消息数量历史消息条数达到该值触发摘要fraction上下文长度比例历史 token 的累计数量达到模型的 max_input_tokens*fraction 触发摘要注意如果条件包含 fraction 要求模型的 profile 包含max_input_tokens参数3keep — 摘要时保留的原始消息支持三种条件但和 trigger 不同keep同一时间只接收一种条件tokens摘要时保留的token数量messages摘要时保留的历史消息条数fraction摘要时保留 max_input_tokens*fraction 个 token参数4token_counter — 统计token数量的函数默认使用 LangChain 提供的 count_tokens_approximately 一般不用更改参数5summary_prompt — 摘要时的自定义提示词该提示词需要包含{messages} 占位符使得历史消息列表可以被插入不指定则使用内置提示词参数6trim_token_to_summarize — 摘要时历史消息的最大token数如果历史消息 token 数大于该值则会被裁剪默认为 4000 如果 trigger 用 token 作为度量调大触发阈值时当前配置项应相应调整否则会丢失信息案例1输出结果如下案例2测试 summary_prompt 参数输出结果如下2.2 HumanInTheLoopMiddleware 中间件HumanInTheLoopMiddleware人在环中间件、人工审核中间件在工具调用前中断Agent运行等待用户对工具调用请求决策approve同意执行edit编辑调用配置后执行reject拒绝执行参数1interrupt_on — 工具名和对应的中断策略策略可以是True、False或InterruptOnConfig对象精细控制决策选项1. True 表示需要中段其所有决策 (approve, edit, reject) 都可以选择2. False 表示不中断即无需审批即可执行3. InterruptOnConfig 是一个TypedDict 的子类可以用字典直接赋值支持的Key有allowed_decisions精细控制中断后允许的决策description特定工具的中断描述信息优先级高于 description_prefix后者会更改所有工具中断的描述参数2description_prefix — 自定义中断描述默认为 Tool execution requires approval案例注意本例需要从中断的位置让 Agent 继续运行这就需要用到短期记忆这里先使用即可创建 Agent 时通过checkpointer参数启用短期记忆调用 Agent 时通过传递相同的config加载记忆结果大致如下案例2指明工具调用请求决策最终大致输出结果如下2.3 PIIMiddleware 中间件PII 中间件用于检测和处理对话中的个人身份信息Personally Identifiable InformationPII支持自定义处理策略参数说明如下参数1pii_type — 检测的 PII 数据类型可以是内置类型或自定义类型自定义类型有email电子邮箱地址credit_card信用卡号url网址mac_address设备MAC地址ipIP地址参数2strategy — 处理PII信息的策略支持四种选项redact将检测到的 PII 信息用字符串 [REDACTED_[PII_TYPE]] 替换其中的 PII_TYPE是上面提到的具体类型比如 [REDACTED_EMAIL] 、 [REDACTED_CREDIT_CARD] 这样的标签。完全 “擦除/隐藏” 真实内容适合日志清洗、合规需求、公开输出时隐藏敏感内容mask用 *** 将 PII 信息的前面一部分信息遮蔽比如信用卡号可能变成 ****-****- ****-1234 只保留最后几位/部分可见邮箱可能保留域名部分 隐藏用户名的一部分等 — 既隐藏大部分敏感信息又保留了一点“可辨识性”比如账号后四位、域名等适合用户服务界面 / 前端显示 / 需要部分可识别但不泄露完整敏感内容的场景hash用检测到的 PII 信息的哈希值替代原值比如email_hash:a1b2c3d4适合 analytics、调试 (debug)、统计分析、匿名追踪等场景block如果检测到 PII 信息 直接抛出异常。适合对隐私要求极高、绝不允许泄露任何敏感信息的场景参数3detector — 自定义PII检测函数或者正则表达式如果没有提供则使用内置的检测函数一般不用手动修改LangChain 为每种 PII 信息定制了专门的检测函数相关源码如下参数4apply_to_input — 是否在调用模型前检测默认为 True参数5apply_to_output — 是否在模型调用后检测默认为 False参数6apply_to_tool_results — 是否在工具调用后检测其输出默认为False通常只在模型调用前检测因为 PII 检测的主要目的是避免将敏感信息发送给模型服务导致信息泄露案例如下使用内置检测器输出内容如下案例自定义检测器 / 检测函数接着输出结果如下2.4 TodoListMiddleware中间件TodoListMiddleware 中间件赋予了 Agent任务规划和追踪进度的能力可以应对复杂的多步任务典型应用场景任务链路长、步骤多且有严格的先后依赖关系需要在前端 UI 界面实时展示 Agent 的 “思考与执行进度”To-do list 的创建和维护是通过调用 write_todos 工具实现的参数说明如下1. system_prompt — 自定义指导 todo 列表使用的提示词不提供则使用内置提示词通常不必提供2. tool_description — 自定义 write_tools 工具的描述信息不提供则使用内置描述通常不必提供案例设计一个较复杂任务1. 任务目标扫描工作目录测试并修复工作区下的my_add.py文件2. 工具列表为了实现上述任务提供一系列工具list_files扫描工作目录列出其中的所有文件read_file扫描指定文件返回文件内容write_file向指定文件写入内容run_tests运行测试底层基于 pytest 实现3.代码3.1 环境准备在项目根目录下创建todo_workspace作为工作空间在该目录下创建my_add.py写入以下内容在该目录下创建test_my_add.py写入以下内容测试工具是基于 pytest 实现的pytest 用法如下1.在PyCharm中打开终端依次执行命令切换到 todo_workspace 的工作目录2. 然后执行3. 输出结果如下分析pytest 会扫描目录下所有以 test_ 开头或以 _test 结尾的文件视为测试文件然后执行测试文件中所有以 test 开头的函数执行出错会打印到控制台如上所示测试函数的逻辑是调用 my_add.py 中的 add 函数得不到符合预期的结果则抛出异常3.2 业务代码# 模型初始化 from langchain.chat_models import init_chat_model from dotenv import load_dotenv import os from rich import print as rprint from langchain_core.messages import SystemMessage, HumanMessage, AIMessage # 1.加载配置信息 load_dotenv(overrideTrue) api_key os.getenv(BAILIAN_API_KEY) base_url os.getenv(BAILIAN_API_BASE) # 2.初始化模型 llm init_chat_model( model_provideropenai, # 模型提供商 modeldeepseek-v4.1-flash, # 模型名称 api_keyapi_key, # API Key base_urlbase_url, # API Base URL ) # 提供工具列表 from langchain.tools import tool from pathlib import Path import subprocess WORKSPACE Path(../todo_workspace) tool def list_files(path: str .) - str: 列出工作区指定目录下的文件和子目录, path 只能是相对路径 Args: path: 工作区下的相对路径一定指向目录默认为., 表示工作区根路径, 不能访问工作区外的目录 target (WORKSPACE / path).resolve() workspace_root WORKSPACE.resolve() if not str(target).startswith(str(workspace_root)): return 错误只允许访问工作区内的目录。 if not target.exists(): return f错误目录不存在: {path} if not target.is_dir(): return f错误不是目录: {path} items sorted(target.iterdir(), keylambda p: (p.is_file(),p.name.lower())) if not items: return f目录为空: {path} lines [] for item in items: rel item.relative_to(workspace_root) kind [DIR] if item.is_dir() else [FILE] lines.append(f{kind} {rel.as_posix()}) return \n.join(lines) tool def read_file(path: str) - str: 读取工作区中的文本文件内容。path 只能是相对路径。 Args: path: 工作区内的文件名 file_path (WORKSPACE / path).resolve() if not str(file_path).startswith(str(WORKSPACE.resolve())): return 错误只允许读取工作区内的文件。 if not file_path.exists(): return f错误文件不存在: {path} return file_path.read_text(encodingutf-8) tool def write_file(path: str, content: str) - str: 写入工作区中的文本文件。path 只能是相对路径 Args: path: 工作区内的文件名 content: 写入文件的内容 file_path (WORKSPACE / path).resolve() if not str(file_path).startswith(str(WORKSPACE.resolve())): return 错误只允许写入工作区内的文件。 file_path.write_text(content, encodingutf-8) return f已写入文件: {path} tool def run_tests() - str: 在工作区运行 pytest -q并返回输出。不接收任何参数返回格式为 returncode0|1 STDOUT: STDERR: try: result subprocess.run( [pytest, -q], cwdstr(WORKSPACE), capture_outputTrue, textTrue, timeout20, ) return ( freturncode{result.returncode}\n\n fSTDOUT:\n{result.stdout}\n\n fSTDERR:\n{result.stderr} ) except Exception as e: return f运行测试失败: {e} from langchain.agents import create_agent from langchain.agents.middleware import TodoListMiddleware from langchain.messages import HumanMessage from rich import print as rprint # 1. 初始化 Agent agent create_agent( modelllm, # write_todos 等工具TodoListMiddleware 需要配合这些工具使用 tools[list_files, read_file, write_file, run_tests], # 引入 Todo 列表中间件 middleware[TodoListMiddleware()], system_prompt( 你是一个代码修复助手。遇到多步骤任务时先使用 write_todos 制定待办事项 然后读取文件、修复代码并运行测试。工作全部在工作区下进行。 ), ) # 2. 使用invoke进行同步调用 print(正在执行 Agent 任务...) final_state agent.invoke( { messages: [ HumanMessage(content请测试并修复工作区下 my_add.py 文件中的代码) ] } ) rprint(final_state)结果如下3. 其他内置中间件3.1 ModelCallLimitMiddleware中间件限制模型调用次数避免无限循环控制调用成本案例整个会话限制——优雅退出案例整个会话限制——抛异常案例单次调用限制——优雅退出案例单次调用限制——抛异常3.2 ToolCallLimitMiddleware中间件限制工具调用次数可以限制所有工具调用的总次数也可以限制特定工具的调用次数退出行为有三种模式error直接抛异常end结束整个会话continue继续运行 Agent这是默认行为此时 Agent 会将工具调用超出限制的信息传递给模型后者自主决定后续行为如果模型能力不足可能导致死循环为了避免这种情况我们实现的 fake server 会以 20% 的概率输出正确响应从而能终止循环案例整个会话限制——优雅结束案例整个会话限制——抛异常案例单次调用限制——继续运行3.3 ModelFallbackMiddleware中间件用于故障转移当主模型无法访问时启用备用模型案例模型初始化时定义备用模型案例创建 agent 时定义备用模型3.4 LLMToolSelectorMiddleware中间件智能工具筛选 当工具太多时用子模型筛选最相关的几个工具参数model用于工具筛选的子模型max_tools限定可以调用的工具总数always_include指定的工具不被计数案例1案例23.5 ToolRetryMiddleware中间件基于指数退避算法设置工具调用失败时的重试策略3.6 ModelRetryMiddleware中间件模型调用失败时重试策略和工具调用的重试一样都是基于指数退避算法案例继续运行案例抛异常3.7 LLMToolEmulator中间件某些情况下工具尚未开发完成我们希望先测试工具调用可以用LLM tool emulator模拟工具案例3.8 ContextEditingMiddleware中间件上下文编辑中间件该中间件提供了上下文管理的一种方式通过更改发送给模型的消息列表来控制成本案例3.9 FilesystemFileSearchMiddleware中间件基于系统的 Glob 和 Grep 检索工具为 Agent 赋予本地文件搜索和分析的能力Glob 根据文件路径检索Grep 根据文件内容检索3.10 Shell tool中间件为 Agent 提供一个可以执行命令的 Shell 环境3.11 Filesystem中间件这是源自 deepagents基于LangChain的另一个框架的中间件内置了四个工具分别用于查看目录、读文件、写文件和改文件4. 多个中间件组合及执行顺序Middleware 可以叠加使用其中中间件的书写顺序非常重要案例5. 自定义中间件某些复杂场景下官方内置的中间件不能完全满足需求此时可以通过实现 LangChain 暴露的中间件hook 函数构建自定义中间件注意尽量使用内置中间件5.1 什么是 hook 函数钩子函数hook 函数钩子函数在某个既定流程的特定时机被框架、系统或主程序自动调用的扩展函数核心特点不是主动在业务代码里随便调用的而是当流程运行到某个“钩子点”时系统自动触发依附于一个更大的执行流程比如“请求开始前”“模型调用前”“任务结束后”“异常发生时”等作用是在不改主流程源码的前提下插入自己的逻辑。例如做日志 、 鉴权 、 修改输入 、 拦截输出 、 清理资源等LangChain 的中间件作用在 Agent 架构中后者是基于 LangGraph 构建的流程图如下列出了六个hook 函数钩子函数无论是官方内置中间件、自定义中间件、还是便捷装饰器中间件通常都是通过实现其中的 一个或多个 hook 来生效的5.2 LangChain 的 hook 函数分类官方将六个钩子函数按照风格分为两类类型1Node-style hooks(节点风格钩子)它们在流程的特定节点运行适合顺序逻辑如记录日志、验证包括before_agent在Agent开始运行之前执行before_model在模型调用之前执行after_model在模型调用之后执行after_agent在Agent流程全部完成后执行类型2Wrap-style hooks(包装风格钩子)它们在模型或工具调用前后运行适合控制流如重试、回退、缓存包括wrap_model_call (包裹模型调用)wrap_tool_call (包裹工具调用)5.3 Node-style hooks函数用法支持两种用法装饰器是函数式挂载把一个hook快速挂载到Agent的某个节点类写法是对象化中间件把中间件封装为一个可配置、可复用、可扩展的组件5.3.1 基本用法1.基于装饰器实现输出结果如下2.基于类实现关键规则1.必须继承 AgentMiddleware← 这个固定2.方法名固定 ( before_model , after_model )← 这个固定3.类名随意← 这个不固定案例输出如下1. before_model 通常的场景消息修剪trim messagesPII 脱敏输入验证条件路由2. after_model 通常的场景输出验证格式化响应统计信息状态更新5.3.2 两种方法的统一装饰器是一种便捷的写法其底层会基于我们重写的方法构造一个 AgentMiddleware 子类的实例5.3.3 参数说明Node-style hooks 函数有两个参数state是一个 AgentState 实例维护 Agent 运行过程中的状态这类状态会随着 Agent 的运行而发生变化包括消息列表runtime是一个 Runtime 实例维护 Agent 运行过程中的上下文环境包括上下文 、 长期记忆等5.3.4 返回值说明返回 None不修改状态不修改Agent状态返回字典更新状态返回 {jump_to: ...}控制流程jump_to 目标__end__ 结束 Agenttools 跳到工具节点其他自定义节点5.3.5 装饰器参数can_jump_to这里就涉及到 Node-style 的四个 hook 函数可以接收额外参数 can_jump_to钩子函数可以改变 Agent 正常的运行轨迹 。比如发现上下文窗口溢出直接跳转至结尾提前终止整个Agentcan_jump_to 决定了钩子函数可以直接跳转至流程的哪些位置可取值如下end跳转至 Agent 流程末尾或第一个 after_agent 钩子直接终止整个流程tools跳转至工具节点model跳转至模型节点或第一个before_model钩子1. 基于装饰器实现2. 基于类实现和基于装饰器实现的关键区别在于需要引入额外的装饰器hook_config为can_jump_to传参5.4 Wrap-style hooks 函数用法5.4.1 基本用法1. wrap_model_call① 基于装饰器实现同时在模型调用前后做事所以命名为 wrap_model_call wrap 意为包裹案例② 基于类实现2. wrap_tool_call同时在工具调用前后做事所以命名为 wrap_tool_call① 基于装饰器实现② 基于类实现5.4.2 两种方式的统一装饰器方法底层也会创建一个 AgentMiddleware 的实例5.4.3 参数说明request被封装的请求对象可以是模型或工具调用请求handler处理器用于处理请求并返回调用结果5.5 装饰器和类的选择情况1中间件只用一个钩子函数推荐用装饰器需要多个钩子函数推荐类写法当一个中间件只需要实现一个钩子函数时直接使用装饰器最简单当一个中间件需要实现多个钩子函数时类写法更合适即装饰器写法适合把单个 hook 快速挂到 agent 生命周期的某个节点上类写法更适合把多个 hook 组织为一个完整的中间件组件当中间件同时涉及 before_model 、 after_model 等多个钩子时虽然装饰器工厂也能实现但类写法在结构表达、配置归属、可维护性上更好情况2复杂配置推荐用类实现装饰器当然也可以通过函数闭包传递参数但在自省运行时类型校验、调试等方面天然不如类写法方便情况3跨项目复用推荐用类写法如果希望中间件成为一个可实例化、可封装、可测试的组件类写法更加合适因为这些本就是类擅长 的场景装饰器的闭包也能实现但使用不友好总结如下装饰器写法和类写法都能实现 middleware hook本质上只是两种定义中间件的方式并不是能力上完全割裂的两套机制底层实现是统一的一般来说装饰器写法更适合单个 hook、逻辑简单、快速原型的场景类写法更适合多个 hook 组合、复杂配置、需要同时提供同步 / 异步实现、以及更强复用与可测试性的场景5.6 hook函数执行顺序before_* 钩子函数从前到后执行after_* 钩子函数从后往前执行wrap_* 钩子函数洋葱架构前面的包裹后面的外层的先执行这里的顺序并非定义顺序而是创建Agent时传递中间件的顺序案例
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。