langchain 中间件

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 LangChain

1.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/Grep
  • Filesystem:给 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 , 放到消息列表最开始的位置

参数说明如下:

参数1:model --- 用于生成摘要的模型

可以是模型名称也可以是模型对象,如果传递的是模型名称,底层会调用 init_chat_model 初始化模型

参数2:trigger --- 摘要触发条件

是一个列表,每个元素对应一个条件,当任一条件满足时,触发摘要:

  • tokens:token 的数量,历史 token 的累计数量达到该值触发摘要
  • messages :历史消息数量,历史消息条数达到该值触发摘要
  • fraction :上下文长度比例,历史 token 的累计数量达到模型的 max_input_tokens*fraction 触发摘要

注意:如果条件包含 fraction ,要求模型的 profile 包含 max_input_tokens

参数3:keep --- 摘要时保留的原始消息

支持三种条件,但和 trigger 不同,keep同一时间只接收一种条件

  • tokens:摘要时保留的token数量
  • messages :摘要时保留的历史消息条数
  • fraction:摘要时保留 max_input_tokens*fraction 个 token

参数4:token_counter --- 统计token数量的函数

默认使用 LangChain 提供的 count_tokens_approximately ,一般不用更改

参数5:summary_prompt --- 摘要时的自定义提示词

该提示词需要包含 {messages} 占位符,使得历史消息列表可以被插入;不指定则使用内置提示词

参数6:trim_token_to_summarize --- 摘要时历史消息的最大token数

如果历史消息 token 数大于该值,则会被裁剪;默认为" 4000 "

如果 trigger 用 token 作为度量,调大触发阈值时,当前配置项应相应调整,否则会丢失信息

案例1:

输出结果如下:

案例2:测试 summary_prompt 参数

输出结果如下:

2.2 HumanInTheLoopMiddleware 中间件

HumanInTheLoopMiddleware(人在环中间件、人工审核中间件) :在工具调用前中断Agent运行,等待用户对工具调用请求决策:

  • approve(同意执行)
  • edit(编辑调用配置后执行)
  • reject(拒绝执行)

参数1:interrupt_on --- 工具名和对应的中断策略

策略可以是 True 、False 或 InterruptOnConfig 对象,精细控制决策选项

  1. True 表示需要中段,其所有决策 (approve, edit, reject) 都可以选择

  2. False 表示不中断,即无需审批即可执行

  3. InterruptOnConfig 是一个 TypedDict 的子类,可以用字典直接赋值;支持的Key有:

  • allowed_decisions :精细控制中断后允许的决策
  • description :特定工具的中断描述信息,优先级高于 description_prefix,后者会更改所有工具中断的描述

参数2:description_prefix --- 自定义中断描述

默认为 "Tool execution requires approval"

案例:

注意:本例需要从中断的位置让 Agent 继续运行,这就需要用到短期记忆,这里先使用即可

创建 Agent 时: 通过 checkpointer参数启用短期记忆

调用 Agent 时: 通过传递相同的 config加载记忆

结果大致如下:

案例2:指明工具调用请求决策

最终大致输出结果如下:

2.3 PIIMiddleware 中间件

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

参数说明如下:

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

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

  • email:电子邮箱地址
  • credit_card:信用卡号
  • url:网址
  • mac_address :设备MAC地址
  • ip :IP地址

参数2:strategy --- 处理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 信息, 直接抛出异常。适合对隐私要求极高、绝不允许泄露任何敏感信息的场景

参数3:detector --- 自定义PII检测函数或者正则表达式

如果没有提供则使用内置的检测函数(一般不用手动修改)

LangChain 为每种 PII 信息定制了专门的检测函数,相关源码如下:

参数4:apply_to_input --- 是否在调用模型前检测

默认为 True

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

默认为 False

参数6:apply_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 的工作目录
  1. 然后执行:
  1. 输出结果如下:

分析:

  1. pytest 会扫描目录下所有以 test_ 开头或以 _test 结尾的文件,视为测试文件
  2. 然后执行测试文件中所有以 test 开头的函数
  3. 执行出错会打印到控制台,如上所示
  4. 测试函数的逻辑是调用 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(override=True)
api_key = os.getenv("BAILIAN_API_KEY")
base_url = os.getenv("BAILIAN_API_BASE")

# 2.初始化模型
llm = init_chat_model(
    model_provider="openai",  # 模型提供商
    model="deepseek-v4.1-flash",  # 模型名称
    api_key=api_key,  # API Key
    base_url=base_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(), key=lambda 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(encoding="utf-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, encoding="utf-8")
    return f"已写入文件: {path}"

@tool
def run_tests() -> str:
    """
    在工作区运行 pytest -q,并返回输出。不接收任何参数,返回格式为
    returncode=0|1
    STDOUT:
    STDERR:
    """
    
    try:
        result = subprocess.run(
        ["pytest", "-q"],
        cwd=str(WORKSPACE),
        capture_output=True,
        text=True,
        timeout=20,
        )
        return (
            f"returncode={result.returncode}\n\n"
            f"STDOUT:\n{result.stdout}\n\n"
            f"STDERR:\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(
    model=llm,
    # 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:

案例2:

3.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 函数(钩子函数) :在某个既定流程的特定时机,被框架、系统或主程序自动调用的扩展函数

核心特点:

  1. 不是主动在业务代码里随便调用的,而是当流程运行到某个"钩子点"时,系统自动触发
  2. **依附于一个更大的执行流程;**比如"请求开始前""模型调用前""任务结束后""异常发生时"等
  3. 作用是在不改主流程源码的前提下插入自己的逻辑。例如做日志 、 鉴权 、 修改输入 、 拦截输出 、 清理资源等

LangChain 的中间件作用在 Agent 架构中,后者是基于 LangGraph 构建的流程图

如下列出了六个hook 函数(钩子函数):

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

5.2 LangChain 的 hook 函数分类

官方将六个钩子函数按照风格分为两类

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

它们在流程的特定节点运行,适合顺序逻辑,如记录日志、验证

包括

  • before_agent:在Agent开始运行之前执行
  • before_model:在模型调用之前执行
  • after_model:在模型调用之后执行
  • after_agent:在Agent流程全部完成后执行

类型2:Wrap-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 messages)
  • PII 脱敏
  • 输入验证
  • 条件路由
  1. 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" :结束 Agent
  • "tools" :跳到工具节点
  • 其他自定义节点

5.3.5 装饰器参数:can_jump_to

这里就涉及到 Node-style 的四个 hook 函数可以接收额外参数 can_jump_to

钩子函数可以改变 Agent 正常的运行轨迹 。比如:发现上下文窗口溢出,直接跳转至结尾,提前终止整个Agent

can_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时传递中间件的顺序

案例:

相关推荐
Be for thing3 小时前
PyTorch自动求导Autograd详解|requires_grad、backward、grad、梯度清零
pytorch·学习·1024程序员节
syagain_zsx3 小时前
ODB 与 MySQL:C++ 里的 ORM 实践(类即表、事务铁律与查询封装)
开发语言·数据库·c++·mysql·工具库·1024程序员节
小嘉丶学长3 小时前
Day08 · 字符串(二)
服务器·开发语言·python·学习·1024程序员节
4SAPI4 小时前
电商全链路内容自动化生成大模型 API 生产选型指南:4SAPI、硅基流动与 OpenRouter 的架构权衡
运维·人工智能·架构·自动化·1024程序员节
深圳老胡4 小时前
STM32F103 硬件 SPI 与软件 IO 模拟 SPI 最高速度差异详解
笔记·stm32·单片机·代码规范·1024程序员节
liudachu4 小时前
Agent 开发常见名词说明与类比
python·langchain
CappuccinoRose4 小时前
滚动交互体系
前端·javascript·交互·html5·1024程序员节
画中有画4 小时前
论可观测性架构在分布式系统中的应用
1024程序员节
惊讶的猫5 小时前
慢查询和排查方式(查询日志 + 分析工具 + EXPLAIN)
mysql·1024程序员节