进阶实战!掌握 LangChain V1.0 八大中间件:从调用限制到上下文优化的全流程实战

机智流 2025-11-06 22:14

进阶实战!掌握 LangChain V1.0 八大中间件:从调用限制到上下文优化的全流程实战图1

 

> 作者:李剑锋

前言

在上节课中()我们已经介绍了中间件的基本概念以及插入点位置,并且还介绍了SummarizationMiddleware 和 HumanInTheLoopMiddleware 两个重要的中间件。那这节课我们继续接着上节课的内容,继续讲解剩下的内置中间件!

前期准备

代码准备

首先我们需要把我们上上节课的代码搬过来使用:

from langchain_community.chat_models import ChatTongyi
import os
llm = ChatTongyi(api_key=os.environ.get("DASHSCOPE_API_KEY"), model="qwen-max")

from langchain_community.agent_toolkits.load_tools import load_tools
tools = load_tools(["arxiv"])

from langgraph.checkpoint.memory import InMemorySaver 

memory = InMemorySaver()

from langchain.agents import create_agent

agent = create_agent(model=llm, 
                     tools=tools, 
                     system_prompt="You are a helpful assistant"
                     checkpointer=memory)

result1 = agent.invoke({"messages": [{"role""user""content""请使用 arxiv 工具查询论文编号 1605.08386"}]}, config={"configurable": {"thread_id""user_1"}})
print(result1["messages"][-1].content)

后续的内容我们都会在这个代码的基础上去添加相关的一些中间键。

环境准备

除此之外,我们也要和上节课一样配置好对应的环境信息:

pip install -U langchain langgraph langchain-community dashscope arxiv

然后也是到阿里云获取一下 API_Key,并且最好将其设置到环境变量之中(DASHSCOPE_API_KEY)。前面的课程里我们都有提到过,大家可以到文章中获取一下。

那获取到了密钥后,只要能够成功运行以下代码就代表已准备完成:

from langchain_community.chat_models import ChatTongyi
from langchain_core.messages import HumanMessage
import os

# 初始化模型
llm = ChatTongyi(api_key=os.environ.get("DASHSCOPE_API_KEY"))

# 发送消息
response = llm.invoke([HumanMessage(content="你好,请用一句话介绍一下你自己。")])

print(response.content)

准备好后,我们就可以来开始正式的实战环节了。

内置中间件详解

ModelCallLimitMiddleware —— 限制模型调用次数

简介

在一个智能体执行循环(Agent Loop)中,模型会多次被调用:

  • 一次决定“要不要用工具”;
  • 一次执行后判断“是否需要再次调用”;
  • 再次思考结果、继续调用……

在 ReAct-style agent 中,如果 LLM 的输出不够理性,可能会进入:输入→ 模型思考 → 工具调用 → 模型思考 → 工具调用 → 模型思考... 这种循环,轻则多花钱,重则“跑爆上下文”。

因此,ModelCallLimitMiddleware 的作用就是限定模型调用次数,一旦达到上限,就让 Agent 自动停止或报错。

参数信息

那这个中间键其实比较简单,其主要参数如下:

参数名
类型
默认值
说明
thread_limit
int
None
同一会话线程中的最大模型调用次数(例如持续会话)
run_limit
int
None
单次执行循环的最大模型调用次数(例如单次 invoke)
exit_behavior
str
"end"
达到限制后的处理方式:
  1. "end":优雅结束并返回提示
  2. "error":直接抛出异常停止运行 |

使用示例

假如我们想要设置单次任务最高的次数那就设置 run_limit ,假如是希望多轮对话里限制模型的使用次数,那就需要使用 thread_limit 来进行设置。比如我们定义了下面这样的参数:

from langchain.agents.middleware import ModelCallLimitMiddleware
ModelCallLimitMiddleware(thread_limit=10, run_limit=3)

然后我们连续调用三次模型:

agent.invoke({"messages": [{"role""user""content""请使用 arxiv 工具查询论文编号 1605.08386"}]}, config={"configurable": {"thread_id""user_1"}})
agent.invoke({"messages": [{"role""user""content""请使用 arxiv 工具查询论文编号 1605.08386"}]}, config={"configurable": {"thread_id""user_1"}})
agent.invoke({"messages": [{"role""user""content""请使用 arxiv 工具查询论文编号 1605.08386"}]}, config={"configurable": {"thread_id""user_1"}})

那执行的具体情况如下:

执行轮次
模型调用次数(本轮)
模型调用次数(累计)
说明
第 1 次执行
3 次
3 次
未超出限制 ✅
第 2 次执行
3 次
6 次
未超出限制 ✅
第 3 次执行
3 次
9 次
未超出限制 ✅
第 4 次执行
3 次
12 次
超出 thread_limit=10 🚫 → 整个线程中断

所以可以看到:

  • run_limit 是每一轮内部的安全阀;
  • thread_limit 是全局线程级别的安全阀。

ToolCallLimitMiddleware —— 限制工具调用次数

简介

类似的,除了有模型调用的限制以外,还有一个就是限制工具调用的次数。在实际智能体(Agent)执行循环中,工具调用(Tool Calls)往往是最昂贵或最不确定的环节:

  • 有的工具访问外部 API(比如搜索、数据库查询);
  • 有的工具运行时间长(比如爬虫、OCR、代码执行);
  • 如果模型逻辑出错,可能疯狂调用工具,导致成本暴涨或资源占满

这也是为什么 LangChain 提供了 ToolCallLimitMiddleware ,通过设置调用次数上限,防止 Agent 过度调用工具或陷入无穷循环。

参数信息

该中间键具体的参数如下:

参数名
类型
默认值
说明
tool_name
str
None
限制目标工具名,不填则作用于所有工具
thread_limit
int
None
当前对话线程中(跨多轮)工具调用总上限
run_limit
int
None
单次执行内(一次 .invoke())的调用上限
exit_behavior
str
"end"
达到上限后的处理方式:1.  "end" → 优雅结束2.  "error" → 抛出异常停止执行
参数名
类型
默认值
说明
tool_name
str
None
限制目标工具名,不填则作用于所有工具
thread_limit
int
None
当前对话线程中(跨多轮)工具调用总上限
run_limit
int
None
单次执行内(一次 .invoke())的调用上限
exit_behavior
str
"end"
达到上限后的处理方式:
  1. "end" → 优雅结束
  2. "error" → 抛出异常停止执行 |

使用示例

根据具体的参数,我们也可以知道,假如我们希望限制总工具的调用次数,可以通过这样的参数设置:

from langchain.agents.middleware import ToolCallLimitMiddleware

# 限制全局工具调用
global_limiter = ToolCallLimitMiddleware(thread_limit=20, run_limit=10)

假如我们希望限制某个特定工具,我们可以添加一个 tool_name 去针对该工具。

from langchain.agents.middleware import ToolCallLimitMiddleware

# 单独限制某个工具
search_limiter = ToolCallLimitMiddleware(
    tool_name="search_tool",  # 工具名
    thread_limit=5,           # 整个会话最多调用 5 次
    run_limit=3,              # 单轮执行最多调用 3 次
    exit_behavior="end",      # 达到上限后终止 agent
)

假如我们想要对多个工具进行限制的话,那我们可以制作多个中间键,然后放入到 create_agent 中:

from langchain.agents import create_agent
from langchain.agents.middleware import ToolCallLimitMiddleware

search_limit = ToolCallLimitMiddleware(
    tool_name="search_web",
    run_limit=3,
    thread_limit=10
)

translate_limit = ToolCallLimitMiddleware(
    tool_name="translate",
    run_limit=2
)

summarize_limit = ToolCallLimitMiddleware(
    tool_name="summarize",
    run_limit=1,
    exit_behavior="error"# 超出时报错
)

agent = create_agent(
    model=llm,
    tools=[search_web, translate, summarize],
    middleware=[search_limit, translate_limit, summarize_limit],
)

当多个中间件都存在时:

  1. 每次调用工具前 → LangChain 会依次执行这些中间件。
  2. 如果任何一个返回 {"jump_to": "end"} 或抛出异常 → 整个工具调用被终止。
  3. 否则 → 调用继续执行。

但需要注意的是放在列表越前的 middleware 会越早触发限制。通常先放“全局限制”,再放“具体工具限制”。比如:

middleware=[
    ToolCallLimitMiddleware(run_limit=10),  # 全局
    ToolCallLimitMiddleware(tool_name="search", run_limit=3),  # 局部
]

ModelFallbackMiddleware —— 模型容灾与降级策略

简介

在实际部署智能体(Agent)时,经常会遇到这些情况:

  • 主模型(如 GPT-4o)暂时不可用或返回错误;
  • 模型超时、API 限额、或延迟太高;
  • 想节省成本,用小模型在轻任务中“兜底”。

所以 LangChain 官方提供了一个内置的中间键 ModelFallbackMiddleware ,在主模型失败时自动切换备用模型,保持任务不中断。

参数信息

该中间键具体的参数如下:

参数名
类型
说明
first_model
str / BaseChatModel
第一个备用模型
*additional_models
str / BaseChatModel
后续备用模型

使用示例

比如我们可以这样来进行设置:

from langchain.agents import create_agent
from langchain.agents.middleware import ModelFallbackMiddleware

agent = create_agent(
    model="openai:gpt-4o",  # 主模型
    tools=[...],
    middleware=[
        ModelFallbackMiddleware(
            ChatOpenAI(model="gpt-4o-mini", temperature=0.5),
            ChatOpenAI(model="gpt-3.5-turbo", temperature=0.3),
            ChatTongyi(model="qwen-max")
        )
    ]
)

这个情况下,其执行顺序为:

尝试 GPT-4o
  ↓ 若失败
尝试 gpt-4o-mini
  ↓ 若失败
尝试 gpt-3.5-turbo
  ↓ 若失败
尝试 qwen-max
  ↓ 若全部失败 → 抛异常

所以这个方式可以帮助我们避免异常情况的发生!

PIIMiddleware —— 隐私信息检测与脱敏(before_modelafter_model

简介

在很多实际的应用场景里,比如企业、教育或医疗等场景中,AI 模型输入/输出中往往包含个人隐私信息(PII),例如:姓名、手机号、邮箱、身份证号、银行卡号、API Key 等。

这些信息其实我们是不希望输入给模型或者模型输出出来的。因此在 LangChain 提供了一个中间键 PIIMiddleware ,其能够通过正则表达式检测的方式,在模型调用前后自动检测并处理敏感信息,从而确保系统在生成、存储、展示时都符合法律与合规要求(如 GDPR、个人信息保护法等)。

完整参数信息

那我们可以看看这个中间键的主要参数:

参数名
类型
默认值
说明
pii_type
str
必填
要检测的隐私类型(内置或自定义)
strategy
str
"redact"
检测后采取的策略:
  1. "block" → 直接报错阻止
  2. "redact" → 替换为 [REDACTED_TYPE]
  3. "mask" → 局部遮盖,如 ****1234
  4. "hash" → 转换为哈希字符串 | | detector | str/regex/function | None | 自定义检测规则(正则或函数) | | apply_to_input | bool | True | 是否在模型调用前检查输入 | | apply_to_output | bool | False | 是否在模型输出后检查响应 | | apply_to_tool_results | bool | False | 是否在工具结果中检查 |

pii_type  参数详解

在这些参数里, pii_type 是必须填写的内容。LangChain 官方内置了五种隐私的检测类型,分别是:

内置类型名
检测目标
示例
"email"
邮箱地址
alice@example.com
"credit_card"
信用卡/银行卡号(支持 Luhn 校验)
4111-1111-1111-1111
"ip"
IPv4 或 IPv6 地址
192.168.1.12
"mac_address"
设备 MAC 地址
00:1A:2B:3C:4D:5E
"url"
含 http(s) 或裸域名的网址
https://openai.com

内置的方法我们只需要去输入类型、选择策略(strategy)以及作用的位置(模型前后及工具)就可以使用了。

但假如我们要自定义的话,我们需要输入一个名称,并且还需要设置 detector 的方法,这样才能够正常的运行。假如用了内置的名称但同时设置了 detector 方法,优先使用 detector 方法。

我们可以参考以下方法来进行设计:

from langchain.agents.middleware import PIIMiddleware

# 检测并屏蔽手机号
pii_phone = PIIMiddleware(
    "phone",                               # 自定义名字,可随意
    detector=r"\b1[3-9]\d{9}\b",           # 正则表达式(匹配中国手机号)
    strategy="mask",                       # 处理策略
    apply_to_input=True                    # 检查输入
)

这里我们设置了正则表达式的方法作为 detector ,这个时候当我们输入的是:

我的手机号是13812345678

应该输出:

我的手机号是****1234

strategy  参数详解

那这里我再详细的说一下这个 strategy 里的四个策略,比如说我们设置了邮箱(email)或者信用卡(credit_card)这个类型,然后我们在输入的时候传入了:

My email is alice@example.com and my card is 4111-1111-1111-5678.

这句话,不同的策略会有不同的应对方式:

策略
处理后输出
效果说明
"block"
❌ 报错:Detected PII (email) — Request blocked
直接抛出 PIIDetectionError,Agent 停止运行
"redact"
My email is [REDACTED_EMAIL] and my card is [REDACTED_CREDIT_CARD].
敏感数据完全替换为标签,模型还能读懂语义
"mask"
My email is a****@example.com and my card is --****-5678.
部分隐藏,用户仍可识别部分信息
"hash"
My email is and my card is .
转换为哈希值,可用于统计去重,但不能反查

通过这个例子我们就能够很明显的看出来到底程序内部是如何实现替换操作的。

传入位置详解

在传入的位置中,LangChain 提供了三个可筛查信息的位置:

参数
触发时机
作用对象
Hook位置
apply_to_input 模型调用前
用户输入(HumanMessage)
before_model
apply_to_output 模型调用后
模型生成的输出(AIMessage)
after_model
apply_to_tool_results 工具调用返回后
工具返回的结果(ToolMessage)
before_model

这里我们会发现,第三个 apply_to_tool_results 和第一个 apply_to_input ,两个 hook 所作用的位置都是在 before_model 。但是由于作用的对象不同,一个是 HumanMessage,另一个是 ToolMessage。所以作用的位置一个是在 request 到 model 的位置,另一个是在 observation 到 model 的位置,所以两者还是有区别的。

进阶实战!掌握 LangChain V1.0 八大中间件:从调用限制到上下文优化的全流程实战图2

这里我们从某种程度上也能够看到,即便是同一个钩子,作用的位置也是有区别的。大部分情况我们使用 before_model 的时候,就是默认两个位置都会进行审查,只是在这个方法里会有更精细化的划分。后期我们讲解 LangGraph 的时候,我们可以更精细化的对每个组件的位置进行控制。

LLMToolSelectorMiddleware —— 模型驱动的智能工具筛选器

简介

当一个 Agent 拥有很多工具(10 个以上)时,模型每次推理都需要在所有工具说明之间“思考”,这样不仅浪费 token,还容易选错。

所以 LLMToolSelectorMiddleware 的作用就是在主模型调用前,让一个小模型(或更快的模型)先分析用户意图 → 从众多工具中选出相关的几个。

最终,Agent 的系统提示中只保留这些“相关工具”的定义,从而实现 更快、更准、更省钱 的调用。这在一定程度上也算是 Context Engineering (上下文工程)的实践。

参数信息

该方法的参数信息如下所示:

参数名
类型
默认值
说明
model str BaseChatModel
system_prompt str
内置模板
可以自定义提示,控制工具筛选逻辑
max_tools int
无限制
限定最多保留的工具数量
always_include list[str]
指定永远包含的工具名(比如 search、logging)

使用示例

那我们看到,像是 qwen 系列当中,也有比较强的模型,比如 qwen-max 。当然也有一些相对弱一些的模型,比如 qwen-turbo 或者 qwen-plus 。那这个时候对于一些简单的判断类的任务,交给 turbo 模型就已经能够完成任务了,所以我们可以像下面一样进行设置。

from langchain.agents import create_agent
from langchain.agents.middleware import LLMToolSelectorMiddleware

agent = create_agent(
    model=ChatTongyi(model="qwen-max"),  # 主模型
    tools=[weather_tool, search_tool, math_tool, email_tool],
    middleware=[
        LLMToolSelectorMiddleware(
            model=ChatTongyi(model="qwen-turbo"),  # ✅ 选择辅助模型用于筛选
            max_tools=2,                 # 最多保留 2 个
            always_include=["search"],   # 某些关键工具始终保留
        ),
    ],
)

虽然这里我们没有传入 system_prompt,但是在 LangChain 内部本身就提供了系统提示词,即便我们不传入也能够很好的完成任务。具体的提示词如下:

 "Your goal is to select the most relevant tools for answering the user's query."

假如我们加了 max_tools,系统提示词会加一句:

if self.max_tools is not None:
    system_message += (
        f"\nIMPORTANT: List the tool names in order of relevance, "
        f"with the most relevant first. "
        f"If you exceed the maximum number of tools, "
        f"only the first {self.max_tools} will be used."
    )

所以实际的提示词可能如下所示:

System:
Your goal is to select the most relevant tools for answering the user's query.
IMPORTANT: List the tool names in order of relevance, with the most relevant first.
If you exceed the maximum number of tools, only the first 3 will be used.

同时工具列表也会自动提取工具信息并生成 schema 传给模型,比如:

{
  "type""object",
"properties": {
    "tools": {
      "type""array",
      "items": {
        "enum": ["weather_tool""math_tool""news_tool"],
        "description""Tools to use. Place the most relevant tools first."
      }
    }
  },
"required": ["tools"]
}

那模型也会根据要求返回其所选择的工具信息:

{"tools": ["weather_tool""math_tool"]}

假如有 always_include 中的工具会自动加入到最终工具列表里,比如传给主模型的时候就会变成:

{"tools": ["weather_tool""math_tool""search"]}

ToolRetryMiddleware —— 工具调用自动重试机制

简介

在工具调用的过程中,我们常常还会遇到一些外部的问题,比如网络连接失败、数据库连接失败、模型执行有问题等等。那在工具调用(Tool call)失败时,如何让 Agent 不会立即崩溃或返回错误,这就成了急切需要解决的问题。

所以这个时候,ToolRetryMiddleware 就给出了一个方案,不仅仅可以在调用工具的时候重复多次,还能够设置延时、指数回退(exponential backoff)、随机扰动(jitter)等机制。从而让智能体在调用 不稳定外部服务(例如网络请求、搜索接口、数据库API)时更加鲁棒(robust)。

参数信息

那该工具详细的工具信息如下:

参数名
类型
默认值
说明
max_retries
int
2
最大重试次数(不含初次调用)
tools
list[str
BaseTool]
None
retry_on
tuple[type[Exception]] 或 callable
(Exception,)
指定哪些异常会触发重试
on_failure "return_message"
 / "raise" / callable
"return_message"
所有重试失败后的处理方式
backoff_factor
float
2.0
每次重试等待时间乘倍数(指数退避)
initial_delay
float
1.0
初始延迟秒数
max_delay
float
60.0
最大等待间隔
jitter
bool
True
是否添加 ±25% 随机扰动避免同时重试

retry_on 参数详解

我们可以看到,其类型要求的格式是传入一个元组 tuple[type[Exception], ...],或者是一个 Callable 的列表 Callable[[Exception], bool] 。默认情况下,是遇到任何异常都进行尝试 (Exception,) 。

看上面的内容可能还有点不能理解,我们来看看具体的例子。一种是指定类型的异常(元组),比如:

from requests.exceptions import Timeout, ConnectionError

ToolRetryMiddleware(retry_on=(Timeout, ConnectionError))

那这种情况下,只有在网络超时、连接错误时重试。

当然我们可以来自定义判断函数(作为 runnable 对象)来传入:

def retry_on_server_error(exc: Exception) -> bool:
    return hasattr(exc, "status_code"and 500 <= exc.status_code < 600

ToolRetryMiddleware(retry_on=retry_on_server_error)

那这个时候就会仅对服务端 5xx 错误重试,不对 4xx 客户端错误重试。

on_failure 参数详解

这个参数决定了在工具调用出错以后,并且所有重试失败之后要做的事情。那在参数中给出了三种可能 "return_message" | "raise" | Callable[[Exception], str] 。

默认情况下就是 "return_message" ,就是把错误信息当做工具返回的结果 Observation 返回给到模型,让模型知道这个工具没办法调用。也就是说 返回的内容会被包装成一个 ToolMessage

return ToolMessage(
    content=content,
    tool_call_id=tool_call_id,
    name=tool_name,
    status="error",
)

又比如选择了 "raise"  的话,那就是直接抛出异常终止 Agent 了。这种一般适用于高风险操作(如转账、删除文件)等操作。

最后的 callable 其实就是自己去定义报错后返回的信息。然后再把这部分信息作为 ToolMessage 里的 content (和前面 "return_message"  类似)返回给模型。

def format_error(e: Exception) -> str:
    return "数据库暂时不可用,请稍后重试。"

ToolRetryMiddleware(
    max_retries=3,
    on_failure=format_error
)

ToolMessage(
    content="数据库暂时不可用,请稍后重试。",
    name="search_database",
    tool_call_id="tool_call_123",
    status="error",
)

initial_delay & backoff_factor & max_delay 参数详解

initial_delay  其实表达的是在首次重试前的等待秒数,默认是等待 1 秒。而 backoff_factor  指数退避倍数。每次重试间隔时间按倍数增长,默认是两倍。

也就是说在默认情况下,第一次失败后会等 1 秒,第二次失败后等 2 秒,第三次失败后会等 4 s 这样。

当然假如 backoff_factor  等于 0 的话,那就是恒定按照 initial_delay  的时间进行重置。

那 max_delay  其实就是最大等待时间上限,防止指数退避过长。默认是 60s 这样。

jitter 参数详解

jitter(中文一般叫“随机抖动”或“随机扰动”)在分布式系统和中间件里是一个非常经典的概念,在 ToolRetryMiddleware 中主要用于“随机化每次重试的等待时间”,防止所有 Agent 在同一时间点同时发起重试请求,从而造成“雪崩”。

比如默认情况我们开启就会设置 jitter 为 25%,那假如等待时间是 4 秒的话,那实际的等待时间可能在 3.0 秒到 5.0 秒之间浮动。

假设有 100 个智能体(Agent),它们都在调用同一个外部接口。如果这个接口临时宕机了,那么所有智能体都同时报错。这样会导致 “流量雪崩”(thundering herd)——外部接口刚恢复,又瞬间被几百个 agent 同时打爆。

假如我们加上了 gitter 的话就会变成:

第 1 次失败:每个 agent 在 1s ± 0.25s 随机重试
第 2 次失败:每个 agent 在 2s ± 0.5s 随机重试
第 3 次失败:每个 agent 在 4s ± 1s 随机重试

这样不同 agent 的重试时刻被分散开,整个系统的请求量更平滑,恢复更快,也不容易触发限流。所以在实际生产的过程中还是非常实用的,当然假如是自己本地测试那开不开都无所谓。

使用示例

比如下面的基础用法示例(需要注意部分输入要求的是浮点数):

from langchain.agents import create_agent
from langchain.agents.middleware import ToolRetryMiddleware

agent = create_agent(
    model=ChatTongyi(model="qwen-max"),
    tools=[search_tool, database_tool],
    middleware=[
        ToolRetryMiddleware(
            max_retries=3,       # 最多重试 3 次
            backoff_factor=2.0,  # 指数退避倍数
            initial_delay=1.0,   # 首次延迟 1 秒
            max_delay=60.0,      # 最大等待时间 60 秒
            jitter=True,         # 添加随机扰动,防止雪崩重试
        ),
    ],
)

执行逻辑类似于:

┌────────────────────────────┐
│  wrap_tool_call(request)   │
└─────────────┬──────────────┘
              ▼
        ┌─────────────┐
        │ try handler │
        └──────┬──────┘
               │ success
               ▼
        Tool 执行成功 ✅
               │
               └──→ 返回结果
               │
        ❌ 失败(异常)
               ▼
        进入重试逻辑(延时+计数)
               ▼
        超过 max_retries?
         ├─ 否 → 继续 retry
         └─ 是 → 触发 on_failure

LLMToolEmulator —— 用模型“假装”执行工具(wrap_tool_call

简介

有些时候,在还没有实现具体的工具逻辑的时候,但是这个工具又非常重要的时候,我们可以通过用 LLM 来模拟工具的执行结果(tool emulation)。这样就可以让整体流程先顺利进行,后面再补充相对应的逻辑。

比如当 agent 决定调用某个工具时(如 search_database()),这个中间件会拦截掉真正的工具调用,然后由另一个 LLM 生成“模拟的工具返回结果”。

参数详情

参数名
类型
默认值
作用说明
tools list[strBaseTool] None None
  1. None → 所有工具都被模拟
  2. ["search", "weather_api"] → 只模拟指定工具 - [] → 不模拟任何工具 | | model | str | BaseChatModel | "anthropic:claude-3-5-sonnet-latest" | 指定用于生成模拟结果的模型(LLM),可以是模型 ID 或实例。建议使用便宜的小模型(如 "openai:gpt-4o-mini")来降低成本。 |

使用示例

当我们希望所有的工具都用 LLM 进行模拟,我们可以这样写入(tools 参数里传入 None):

from langchain.agents import create_agent
from langchain.agents.middleware import LLMToolEmulator

agent = create_agent(
    model=ChatTongyi(model="qwen-max"),
    tools=[get_weather, search_database, send_email],
    middleware=[
        LLMToolEmulator(
            model = ChatTongyi(model="qwen-turbo")
        ),  # 所有工具调用都由 LLM 模拟执行
    ],
)

# 调用时,模型不会真的去查数据库或发邮件
result = agent.invoke({"messages": [{"role""user""content""查一下广州的天气"}]})

假如我们要关闭这个功能的话,我们要传入的就是空的列表:

from langchain.agents import create_agent
from langchain.agents.middleware import LLMToolEmulator

agent = create_agent(
    model=ChatTongyi(model="qwen-max"),
    tools=[get_weather, search_database, send_email],
    middleware=[
        LLMToolEmulator(
            tools = []
            model = ChatTongyi(model="qwen-turbo")
        ),  # 所有工具调用都由 LLM 模拟执行
    ],
)

假如要针对某个工具的话,就在列表里写入内容:

from langchain.agents import create_agent
from langchain.agents.middleware import LLMToolEmulator

agent = create_agent(
    model=ChatTongyi(model="qwen-max"),
    tools=[get_weather, search_database, send_email],
    middleware=[
        LLMToolEmulator(
            tools = [get_weather]
            model = ChatTongyi(model="qwen-turbo")
        ),  # 所有工具调用都由 LLM 模拟执行
    ],
)

这个时候 Agent 的流程仍然是“思考 → 调用工具 → 得到结果”,但结果其实是由 LLM虚构的输出,例如:

工具调用:get_weather(city="广州")
→ 模拟输出:"广州当前气温27°C,晴天,微风。"

ContextEditingMiddleware—上下文编辑器

简介

对于长时间运作的 Agent 而言,其上下文可能会非常长。过长的上下文不仅会导致成本的暴增,并且也可能会让大模型产生严重的幻觉问题。因此 LangChain 的 ContextEditingMiddleware 是一种 “上下文清理器”,当对话变得太长(token 多、工具历史太多)时,自动触发对上下文的“编辑(editing)”操作。

这些操作包括:

  • 清除旧的工具调用记录;
  • 删除失败的 tool 结果;
  • 替换过期内容为占位符;
  • 自定义保留/清理策略。

下面我们就具体的来看看实现的方式。

参数信息

该工具具体参数信息如下:

参数名
类型
默认值
作用
什么时候调
edits Iterable[ContextEdit] [ClearToolUsesEdit()]
要执行的上下文编辑策略列表(可以包含多个策略,按顺序依次执行)
想自定义清理规则、保留哪些消息、如何裁剪时修改
token_count_method "approximate"
 / "model"
"approximate"
用什么方式计算当前对话 token 数:近似计数还是模型精确计数
生产高严谨度 → "model";调试高性能 → "approximate"

那在内置的 ClearToolUsesEdit 中又有一些具体的清理策略参数:

参数名
类型
默认值
作用
常见用法
trigger int 100_000
当上下文 token 数超过这个阈值时才触发清理
把它设成你的“别再胖下去”的拐点,比如 4000、8000
clear_at_least int 0
每次运行时至少要回收多少 token,达到这个后就可以停
想控制“清多少就够”,比如至少释放 1000 tokens
keep int 3
永远保留最近 N 次工具结果,不动它们
防止把刚刚用过的工具结果清掉
clear_tool_inputs bool False
除了清理工具输出,还要不要把工具的入参也清掉
处理含隐私/敏感参数的场景时设为 True
exclude_tools Sequence[str] ()
哪些工具的结果绝对不能清理
权限校验、用户信息、合规模块的输出
placeholder str "[cleared]"
被清理的 ToolMessage 会用这个占位文本替代内容
可自定义更友好的人类可读文案,比如"[历史记录已清除]"

所以在默认的情况下,只有超过 10w 的 token 后,才会开始触发清理的任务。

使用示例

假如我们希望定期的清理掉一些旧工具的调用信息,我们可以:

ContextEditingMiddleware(
    edits=[
        ClearToolUsesEdit(
            trigger=2000,   # 超过2000token清理
            keep=2,         # 保留最近2次
            placeholder="[旧结果已清理]"
        )
    ]
)

所以当上下文 token 超过 2000 时:

  • 仅保留最近 2 条工具调用;
  • 旧记录被替换为 [旧结果已清理]

又比如我们希望某些特定工具的信息不被清理,同时清理掉别的工具信息的话,我们可以加上:

ContextEditingMiddleware(
    edits=[
        ClearToolUsesEdit(
            exclude_tools=["summarize""search"],  # 不清理这些
            clear_tool_inputs=True
        )
    ]
)

这会清除其他工具调用,但保留 summarize 和 search 的上下文记录。

总结

通过本节课的学习,我们完整地了解了 LangChain 内置的八大中间件机制,每一个都在智能体的生命周期中承担着不同的“守门人”或“增强器”角色。它们分别从性能、稳定性、安全性与智能化等多个维度出发,为 Agent 的执行过程提供了精细的控制与优化手段:

  • ModelCallLimitMiddleware / ToolCallLimitMiddleware —— 负责为模型与工具调用设定“安全阈值”,防止无限循环与资源浪费;
  • ModelFallbackMiddleware —— 在主模型出错或超时时自动降级,确保服务稳定运行;
  • PIIMiddleware —— 自动检测并脱敏隐私信息,保障数据合规与安全;
  • LLMToolSelectorMiddleware —— 利用小模型智能筛选工具,显著降低 token 成本与错误率;
  • ToolRetryMiddleware —— 提供重试与退避机制,让外部工具调用更稳健、更抗波动;
  • LLMToolEmulator —— 在开发早期用模型模拟工具行为,实现“无痛调试”;
  • ContextEditingMiddleware —— 动态裁剪上下文内容,防止上下文过长带来的性能问题。

从整体上看,这些中间件的核心思想是让 Agent 运行更智能、更高效、更安全、更可控。 在接下来的课程中,我们还会继续结合 LangGraph 的节点与状态机制,深入探讨如何在图式智能体中实现更细粒度的中间件控制与上下文管理,让我们的智能体真正具备“工程化运行”的能力。

-- 完 --

声明:内容取材于网络,仅代表作者观点,如有内容违规问题,请联系处理。 
AI
more
夸克 AI 眼镜 S1 体验:超越 Meta 的决心,以及方法论
芯片,被AI “截胡”
1500 万围观的「疯狂动物城」游戏,Nano Banana Pro+可灵保姆级教程带你复刻
英特尔聚合生态力量,共塑AI NAS新格局:酷睿Ultra驱动智慧本地算力与存储创新
基于文本AI的终结?Agent协作可直接「复制思维」,Token效率暴涨
面积缩小45%、速率翻倍!时创意双新品引爆AI终端存储升级浪潮
雇佣过目即忘的天才:为什么企业级AI总在浪费钱?
iPhone Air保值率崩盘!二手价格直接砍半,苹果史上最不保值机型?
北京AI芯片四强出炉,非GPU阵营占三席!国产算力格局要变天?
AI赋能千行百业  “制造”迈上“智造”新台阶
Copyright © 2025 成都区角科技有限公司
蜀ICP备2025143415号-1
  
川公网安备51015602001305号