目录
- [1. 引言](#1. 引言)
- [2. 核心问题:为什么需要调用限制中间件?](#2. 核心问题:为什么需要调用限制中间件?)
- [3. 实现原理](#3. 实现原理)
- [4. 实践示例](#4. 实践示例)
- [5. 对比与使用场景](#5. 对比与使用场景)
- [6. 总结](#6. 总结)
1. 引言
在构建基于大语言模型(LLM)的应用时,我们常常面临两个棘手的挑战:
- 模型调用成本不可控:每次 invoke、stream 都产生费用,而流程中的重试、循环、复杂链可能让调用次数远超预期。
- 工具调用陷入死循环:Agent 在任务未解决时反复调用同一个工具,不仅消耗 Token,还可能触发 API 的并发限制。
LangChain 从 0.3 版本开始推出 内置中间件(Built-in Middleware) 体系,其中 ModelCallLimitMiddleware 和 ToolCallLimitMiddleware 专门针对上述问题提供了开箱即用的限额控制能力。本文结合官方文档(Built-in Middleware)与真实场景,从问题根源、实现原理、代码示例到对比选型,深入探讨这两个中间件的设计思想与最佳实践。
关键词:LangChain,中间件,调用限制,ModelCallLimitMiddleware,ToolCallLimitMiddleware,成本控制,Agent 稳定性
2. 核心问题:为什么需要调用限制中间件?
2.1 模型调用次数的隐形爆炸
一个简单的 RAG 检索链可能只调用一次模型,但在 Agent 场景下,模型会反复调用工具、评估结果、再规划。典型的 ReAct 循环可能如下:
- 用户问:"分析XX公司的季度报告"→ 模型调用文档搜索工具。
- 搜索结果不理想 → 模型调用摘要工具。
- 摘要后仍然不清楚 → 模型再次搜索,进入重试循环。
如果没有限制,一次对话可能产生数十次模型调用,带来严重的成本和时间开销。
2.2 工具调用的无限循环风险
更危险的是,Agent 可能对某个工具返回的结果"不满意",持续调用直到命中 API 限流或耗尽预算。例如:调用天气 API 时收到错误码,Agent 尝试重新调用;调用数据库查询工具时 SQL 错误,Agent 反复修正,但每次修正仍出错。
传统解决方案的缺陷:
- 在链或 Agent 外层手写一个计数器,代码侵入性强,难以复用。
- 硬编码 in/after 逻辑容易破坏中间件的洋葱模型,让代码耦合度升高。
因此,LangChain 提供了 声明式、可插拔的中间件,在不修改模型或工具实现的前提下,为任意 Runnable 添加调用限额。
3. 实现原理
两个中间件都遵循 LangChain Middleware 的 拦截器模式(Interceptor Pattern):
用户请求 → Middleware.before_xxx → 实际模型/工具调用 → Middleware.after_xxx → 返回结果
3.1 ModelCallLimitMiddleware 核心机制
位置 :langchain_core.middleware.ModelCallLimitMiddleware
参数:
limit: int-- 允许的最大调用次数。on_limit: Optional[Callable]-- 超出限制时的自定义处理函数,默认抛出ModelRateLimitError。reset_period: Optional[timedelta]-- 计数自动重置的时间窗口,适用于周期配额场景。
工作流程:
- 中间件维护一个内部计数器
_call_count(线程安全采用asyncio.Lock保护异步上下文)。 - 每次
before_model钩子触发时检查计数,若 ≥ limit 则调用on_limit。 after_model钩子成功后递增计数。- 若设置了
reset_period,后台任务或调用时按时间窗口重置计数器。
为什么选择中间件而不是简单的包装器?
- 中间件可以 链式组合 :
AuthMiddleware | RateLimitMiddleware | actual_model,各司其职。 - 对于复杂的 LangGraph 节点,中间件会自动传播到内层的每次调用,无需逐节点手动注入。
3.2 ToolCallLimitMiddleware 核心机制
位置 :langchain_core.middleware.ToolCallLimitMiddleware
与模型中间件类似,但它拦截的是 工具调用 而非模型调用。
- 计数器基于工具的每次执行(
tool.invoke/tool.ainvoke)。 - 同样支持
limit、on_limit、reset_period。 - 在 Agent 场景下与模型中间件配合,可以实现 双层保护:外层限制模型思考次数,内层限制工具执行次数。
值得一提的是,LangGraph 内部已经提供了 max_turns 参数来限制 Agent 的最大步数,但 ToolCallLimitMiddleware 的优势在于:
- 可以更细粒度地针对某个工具(而非整个 Agent)进行限制。
- 可以自定义超限后的降级策略(例如返回默认值),而不是直接中断图执行。
4. 实践示例
4.1 限制 LLM 调用次数
python
from langchain.agents import create_agent
from langchain.agents.middleware import ModelCallLimitMiddleware
from langgraph.checkpoint.memory import InMemorySaver
agent = create_agent(
model="gpt-5.5",
checkpointer=InMemorySaver(), # Required for thread limiting
tools=[],
middleware=[
ModelCallLimitMiddleware(
thread_limit=10,
run_limit=5,
exit_behavior="end",
),
],
)
参数配置说明:

4.2 限制工具调用次数(结合 Agent)
以下示例创建一个简单的 Agent,并限制其最多调用search工具 5 次,最多总调用工具次数是20次,单轮对话限制其最多调用search工具3 次,最多总调用工具次数是10次。
python
from langchain.agents import create_agent
from langchain.agents.middleware import ToolCallLimitMiddleware
agent = create_agent(
model="gpt-5.5",
tools=[search_tool, database_tool],
middleware=[
# Global limit
ToolCallLimitMiddleware(thread_limit=20, run_limit=10),
# Tool-specific limit
ToolCallLimitMiddleware(
tool_name="search",
thread_limit=5,
run_limit=3,
),
],
)
参数配置说明:

4.3 双层限制实战
为了同时控制预算和防止工具滥用,可以串联两个中间件:
python
from langchain.agents import create_agent
from langchain.agents.middleware import ModelCallLimitMiddleware
from langgraph.checkpoint.memory import InMemorySaver
agent = create_agent(
model="gpt-5.5",
checkpointer=InMemorySaver(), # Required for thread limiting
tools=[],
middleware=[
ModelCallLimitMiddleware(
thread_limit=15,
run_limit=10,
exit_behavior="end",
),
ToolCallLimitMiddleware(thread_limit=10, run_limit=10),
],
)
此时:
- 模型总共最多被调用 15 次(包含思考和最终回答)。
- 工具最多被执行 10 次,确保不会被某个循环耗尽所有配额。
5. 对比与使用场景
| 维度 | ModelCallLimitMiddleware | ToolCallLimitMiddleware |
|---|---|---|
| 拦截对象 | LLM 的 invoke / ainvoke / stream 调用 | 工具的 invoke / ainvoke 调用 |
| 计数单位 | 模型调用次数 | 工具执行次数 |
| 典型应用 | 预算控制,防止粗暴重试 | 防止 Agent 陷入工具循环 |
| 默认超限行为 | ModelRateLimitError |
ToolRateLimitError |
| 复位能力 | 支持 reset_period 周期性清零 |
支持 reset_period |
| 独立性 | 于模型相关,不影响工具 | 于工具相关,不影响模型 |
| 组合使用 | 可串接在同一个模型上,形成双重限制 |
选型建议:
- 如果您的应用是直接的 LangChain 链或 RAG 流程,绝大多数开销来自 LLM 调用,优先使用
ModelCallLimitMiddleware。 - 如果您在使用 Agent、尤其是自定义工具较多且可能出现死循环时,务必添加
ToolCallLimitMiddleware。 - 在生产环境中,推荐 同时部署两个中间件 ,并为
ToolCallLimitMiddleware设置一个比模型限额更小的数值,确保工具问题不会耗尽所有预算。
6. 总结
LangChain 的 ModelCallLimitMiddleware 与 ToolCallLimitMiddleware 是两个轻量但极具实用价值的内置中间件,它们将"调用保护"从业务代码中解耦出来,以声明式的方式融入任何 Runnable 流水线。
核心价值:
- 无侵入 :不改动现有模型或工具代码,一行
|操作即可引入。 - 可组合:支持洋葱模型堆叠,结合 PII、HITL等中间件形成完善治理。
建议读者在实践中建立 "默认有限" 的意识:在启动任何 Agent 或长时间运行的链之前,首先通过中间件设定合理的调用上限,并设计好降级或告警策略------这不仅能守护预算,更是一道保障系统稳定性的重要防线。