一、简介
LangChain 1.0 引入了正式的中间件(Middleware) 概念,提供了一套声明式、可组合的 API,用于在 Runnable 执行流程中插入横切逻辑。与早期版本中依赖 RunnableLambda、回调或手动包装不同,1.0 的中间件是一等公民,通过 @middleware 装饰器和 Middleware 类,能够以更简洁、类型安全的方式实现日志、重试、限流、缓存、输入验证等功能。
1.1 为什么需要中间件?
Agent 具备自主推理、多步工具调用能力,但也带来三大核心问题:
- 不可控性:无法干预模型何时调用工具、如何生成提示词
- 安全风险:敏感操作无审核、隐私数据泄露、幻觉输出
- 资源浪费:Token 超限、重复调用、死循环、成本失控
中间件的核心价值:
- 流程拦截与修改(输入 / 输出 / 状态)
- 动态提示词管理、上下文压缩
- 工具权限控制、敏感操作审核
- 日志、监控、成本统计
- 重试、降级、限流、缓存
- 安全护栏(PII 脱敏、内容审核)
直接在每个链或 Agent 中重复实现这些功能会导致代码冗余。中间件模式允许将这些横切逻辑独立封装,并以声明方式应用到多个组件上。
1.2 核心概念
在 LangChain 1.0 中,所有可执行组件(模型、提示模板、解析器、Agent、链等)都实现了 Runnable 接口。中间件本质上是一个 Runnable 包装器,它接受一个 Runnable 并返回一个新的 Runnable,在调用前后插入自定义行为。
与旧版的主要区别:
| 方面 | 1.0 之前的通用模式 | 1.0 正式中间件 API |
|---|---|---|
| 定义方式 | RunnableLambda、继承 RunnableBinding | @middleware 装饰器或 Middleware 类 |
| 组合 | 手动管道 | | MiddlewareChain 或自动组合 |
| 配置传递 | 通过 RunnableConfig 手动处理 | 内置支持 config 参数和元数据 |
| 错误处理 | 自定义 try/except | 中间件可统一捕获异常并重试/降级 |
| 官方支持 | 无标准模式 | LangChain Core 原生支持 |
二、核心 API:@middleware 装饰器
LangChain 1.0 提供了 langchain_core.runnables.middleware 模块,其中核心是 @middleware 装饰器。
2.1 基础用法
@middleware 装饰器既可以用于异步函数,也可以用于同步函数。
python
from langchain_core.runnables.middleware import middleware
from langchain_core.runnables import RunnableConfig
from langchain_openai import ChatOpenAI
@middleware
async def log_middleware(request, call_next):
"""记录输入和输出"""
print(f"Input: {request['input']}")
response = await call_next(request) # 调用下层 Runnable
print(f"Output: {response}")
return response
model = ChatOpenAI()
wrapped_model = log_middleware(model) # 直接包装
# 调用
result = await wrapped_model.ainvoke("Hello")
- request:包含 input(用户输入)、config(RunnableConfig)、kwargs 等字段的字典。
- call_next:异步函数,调用下一个中间件或最终的 Runnable。
2.2 带参数的中间件工厂
如果需要配置中间件(如重试次数),可以编写返回中间件装饰器的函数:
python
def retry_middleware(max_retries: int = 3):
@middleware
async def _retry(request, call_next):
last_exception = None
for attempt in range(max_retries):
try:
return await call_next(request)
except Exception as e:
last_exception = e
print(f"Attempt {attempt+1} failed: {e}")
raise last_exception
return _retry
model = ChatOpenAI()
wrapped = retry_middleware(5)(model)
2.3 使用 Middleware 类(面向对象方式)
除了装饰器,还可以继承 Middleware 类,实现更复杂的状态管理。
python
from langchain_core.runnables.middleware import Middleware
class LoggingMiddleware(Middleware):
async def __call__(self, request, call_next):
print("Before")
response = await call_next(request)
print("After")
return response
model = ChatOpenAI()
wrapped = LoggingMiddleware()(model)
2.4 组合多个中间件:MiddlewareChain
使用 MiddlewareChain 可以将多个中间件按顺序应用到同一个 Runnable 上。
python
from langchain_core.runnables.middleware import MiddlewareChain
# 假设已有 log_middleware, retry_middleware, cache_middleware
middlewares = [log_middleware, retry_middleware(2), cache_middleware]
model = ChatOpenAI()
wrapped = MiddlewareChain(middlewares=middlewares, runnable=model)
三、核心执行流程与钩子
3.1 执行流程
中间件的执行基于洋葱模型(类似 Express.js 或 Starlette),遵循"后进先出"的顺序。当你添加多个中间件时,请求会依次穿过它们,到达核心的 LLM 调用,然后响应再反向穿过这些中间件。
bash
请求 → Middleware A (pre) → Middleware B (pre) → 原始 LLM 调用 →
Middleware B (post) → Middleware A (post) → 响应
- 前处理(before):修改输入、校验、过滤、增强 Prompt
- 后处理(after):解析输出、脱敏、日志、格式化
- 包裹(wrap):完全接管执行,可重试、短路、缓存、替换模型
3.2 核心钩子(Hooks)
-
节点式钩子(顺序执行)
钩子 执行时机 入参 返回值 典型用途 before_agent Agent 启动前(单次) inputs: dict dict 初始化状态、验证输入、加载记忆 before_model 每次模型调用前 messages: List[BaseMessage] List[BaseMessage] 裁剪消息、动态提示、注入上下文 after_model 模型返回后 response: BaseMessage BaseMessage 审查输出、统计 Token、修改响应 after_agent Agent 结束后(单次) output: dict dict 清理资源、生成报告、格式化结果 -
包装式钩子(洋葱嵌套)
钩子 作用对象 入参 返回值 典型用途 wrap_model_call 模型调用 model_call: Callable, messages BaseMessage 重试、缓存、限流、模型降级 wrap_tool_call 工具执行 tool_call: Callable, tool_input Any 权限校验、超时、审计、脱敏
3.3 更细粒度的钩子示例
python
from langchain.middleware import BaseMiddleware
from langchain_core.messages import HumanMessage
class CustomMiddleware(BaseMiddleware):
async def pre_process(self, request):
# 修改输入:统一转为字符串
if isinstance(request.input, dict):
request.input = str(request.input)
return request
async def wrap_llm_call(self, handler):
# 对 LLM 调用增加重试逻辑
for attempt in range(3):
try:
return await handler() # 实际调用 LLM
except Exception as e:
if attempt == 2:
raise
return None
async def post_process(self, request, response):
# 过滤敏感词
response.output = response.output.replace("badword", "***")
return response
async def on_error(self, request, error):
print(f"调用失败: {error}")
return "默认回复" # 可以返回备用响应
3.4 执行顺序详解
当调用 agent.invoke("hello") 时,LangChain 内部构建一个中间件栈:
- 最先添加的中间件位于最外层(最后执行 pre,最先执行 post)
- 执行流程:
- 进入 Middleware1.pre_process → 进入 Middleware2.pre_process → ... → 到达 LLM 调用
- LLM 返回 → 退出 MiddlewareN.post_process → ... → 退出 Middleware1.post_process
示例验证:
python
class A(BaseMiddleware):
async def pre_process(self, request):
print("A pre")
return request
async def post_process(self, request, response):
print("A post")
return response
class B(BaseMiddleware):
async def pre_process(self, request):
print("B pre")
return request
async def post_process(self, request, response):
print("B post")
return response
agent = create_agent(...)
agent.add_middleware(A())
agent.add_middleware(B())
await agent.invoke("test")
# 输出:
# A pre
# B pre
# (实际 LLM 调用)
# B post
# A post
四、内置中间件
LangChain 1.0 提供官方内置中间件,覆盖安全、上下文、可靠性三大场景,直接导入即可使用。
4.1 RetryMiddleware
自动重试失败调用,支持指数退避。
python
from langchain_core.runnables.middleware import RetryMiddleware
retry = RetryMiddleware(max_retries=3, backoff_factor=1.0)
model = ChatOpenAI()
robust_model = retry(model)
4.2 RateLimiterMiddleware
基于令牌桶算法的速率限制。
python
from langchain_core.runnables.middleware import RateLimiterMiddleware
limiter = RateLimiterMiddleware(requests_per_second=10)
model = limiter(ChatOpenAI())
4.3 CacheMiddleware
简单缓存中间件,支持内存和 Redis 后端。
python
from langchain_core.runnables.middleware import CacheMiddleware
from langchain_core.caches import InMemoryCache
cache = CacheMiddleware(cache=InMemoryCache())
model = cache(ChatOpenAI())
4.4 TracingMiddleware
自动集成 LangSmith 或自定义追踪器。
python
from langchain_core.runnables.middleware import TracingMiddleware
tracer = TracingMiddleware(project_name="my_project")
model = tracer(ChatOpenAI())
4.5 PIIRedactionMiddleware
自动检测并屏蔽手机号、邮箱、身份证、银行卡等 PII 数据,支持自定义正则模式。
python
from langchain.agents.middleware import PIIRedactionMiddleware
middleware = [
PIIRedactionMiddleware(
patterns=["phone", "email", "ssn"], # 内置模式
custom_patterns={"order_id": r"\d{10}"} # 自定义模式
)
]
4.6 HumanInTheLoopMiddleware
敏感工具调用前需人工确认(如发送邮件、删除数据),支持指定工具白名单。
python
from langchain.agents.middleware import HumanInTheLoopMiddleware
middleware = [
HumanInTheLoopMiddleware(
tool_names=["send_email", "delete_data"], # 需审核的工具
approve_message="确认执行此操作?" # 自定义提示
)
]
4.7 SummarizationMiddleware
对话过长时自动摘要历史,避免 Token 超限,支持自定义摘要模型与阈值。
python
from langchain.agents.middleware import SummarizationMiddleware
middleware = [
SummarizationMiddleware(
llm=ChatOpenAI(model="gpt-3.5-turbo"), # 摘要用轻量模型
max_tokens_before_summary=1500, # 触发摘要的 Token 阈值
summary_prompt="请用100字内摘要对话历史" # 自定义提示
)
]
4.8 MessageLimitMiddleware
限制历史消息数量,防止无限循环。
python
from langchain.agents.middleware import MessageLimitMiddleware
middleware = [
MessageLimitMiddleware(max_messages=30) # 最多保留30条消息
]
4.9 ToolRetryMiddleware
工具调用失败自动指数退避重试。
python
from langchain.agents.middleware import ToolRetryMiddleware
middleware = [
ToolRetryMiddleware(
max_retries=2, # 最大重试次数
retry_delay=1.0 # 初始延迟(秒)
)
]
五、自定义中间件
5.1 基础自定义(节点式钩子)
继承 BaseAgentMiddleware,重写需要的钩子方法。
示例:日志中间件
python
class LoggingMiddleware(BaseAgentMiddleware):
def before_agent(self, inputs: dict, **kwargs) -> dict:
print(f"[Agent 启动] 输入:{inputs}")
return inputs # 可修改输入
def before_model(self, messages: List[BaseMessage], **kwargs) -> List[BaseMessage]:
print(f"[模型调用前] 消息数:{len(messages)}")
return messages # 可修改消息
def after_model(self, response: BaseMessage, **kwargs) -> BaseMessage:
print(f"[模型返回] 内容:{response.content[:50]}...")
return response # 可修改响应
def after_agent(self, output: dict, **kwargs) -> dict:
print(f"[Agent 结束] 输出:{output}")
return output # 可修改输出
5.2 高级自定义(包装式钩子)
重写 wrap_model_call/wrap_tool_call,实现重试、缓存等逻辑。
示例:模型重试中间件
python
class ModelRetryMiddleware(BaseAgentMiddleware):
def __init__(self, max_retries: int = 2):
self.max_retries = max_retries
def wrap_model_call(
self,
model_call: Callable[[List[BaseMessage]], BaseMessage],
messages: List[BaseMessage],
**kwargs
) -> BaseMessage:
for attempt in range(self.max_retries + 1):
try:
print(f"[重试] 第 {attempt+1} 次调用模型")
return model_call(messages)
except Exception as e:
if attempt == self.max_retries:
raise e
print(f"[重试失败] {e},1秒后重试...")
import time
time.sleep(1)
return model_call(messages)
5.3 状态管理(跨中间件通信)
通过 kwargs["state"] 访问 / 修改全局状态,实现数据共享。
python
def before_model(self, messages: List[BaseMessage], **kwargs) -> List[BaseMessage]:
state = kwargs["state"]
# 记录模型调用次数
state["model_calls"] = state.get("model_calls", 0) + 1
# 注入状态到消息
messages.append({"role": "system", "content": f"已调用模型{state['model_calls']}次"})
return messages
5.4 提前终止(Early Exit)
在 before_model 中返回 {"jumpTo": "end"} 直接终止 Agent。
python
def before_model(self, messages: List[BaseMessage], **kwargs):
if len(messages) > 50:
return {
"messages": [{"role": "assistant", "content": "对话过长,已终止"}],
"jumpTo": "end" # 强制结束
}
return messages
六、高级中间件模式
6.1 中间件与 LCEL
中间件可以直接应用于 LCEL 链,因为整个链本身也是一个 Runnable。
python
prompt = ChatPromptTemplate.from_template("Tell me a joke about {topic}")
chain = prompt | model | StrOutputParser()
wrapped_chain = log_middleware(chain)
result = wrapped_chain.invoke({"topic": "chickens"})
中间件会拦截整个链的输入和输出。如果需要针对链中的特定步骤(如仅模型调用)应用中间件,只需包装那个子 Runnable 即可:
python
model_with_middleware = retry_middleware()(model)
chain = prompt | model_with_middleware | parser
6.2 访问和修改 RunnableConfig
中间件可以读取或修改请求中的 config,用于传递上下文(如 request ID、用户身份)。
python
@middleware
async def add_request_id(request, call_next):
config = request.get("config") or RunnableConfig()
if "metadata" not in config:
config["metadata"] = {}
config["metadata"]["request_id"] = str(uuid.uuid4())
request["config"] = config
return await call_next(request)
6.3 条件性跳过中间件
根据输入动态决定是否执行后续逻辑:
python
@middleware
async def conditional_cache(request, call_next):
input_text = request["input"]
if len(input_text) > 100:
# 长文本不缓存,直接调用
return await call_next(request)
else:
# 短文本走缓存逻辑
return await cache_middleware(request, call_next)
6.4 异常处理与降级
中间件可以捕获异常并返回降级结果:
python
@middleware
async def fallback_middleware(request, call_next):
try:
return await call_next(request)
except Exception as e:
print(f"Primary failed: {e}, using fallback")
fallback_model = ChatOpenAI(model="gpt-3.5-turbo")
return await fallback_model.ainvoke(request["input"])
七、最佳实践
-
优先使用内置中间件:RetryMiddleware、RateLimiterMiddleware 等已经经过充分测试。
-
保持中间件幂等:重试时不应产生副作用。
-
异步优先:尽管中间件支持同步,但为了性能建议使用 async 定义。
-
避免修改输入原对象:如果需要修改输入,应拷贝一份,防止影响其他中间件。
-
中间件顺序:注意中间件列表的顺序,例如日志应该在最外层,重试应靠近内部。
bash1. 安全类(脱敏、审核)→ 第一道防线 2. 上下文类(摘要、裁剪)→ 优化输入 3. 可靠性类(重试、限流)→ 保障稳定 4. 监控类(日志、统计)→ 最后观测 -
测试中间件:使用模拟的 call_next 单元测试中间件逻辑。
-
性能优化
- 摘要用轻量模型(如 gpt-3.5-turbo),减少成本
- 避免在 before_model/after_model 中执行耗时操作
- 用 wrap_model_call 实现缓存,减少重复调用
企业标配中间件组合:
python
middleware = [
# 1. 安全
PIIRedactionMiddleware(),
ContentSafetyMiddleware(),
# 2. 上下文
SummarizationMiddleware(llm=ChatOpenAI(model="gpt-3.5-turbo")),
MessageLimitMiddleware(max_messages=30),
# 3. 可靠
ModelRetryMiddleware(max_retries=2),
ToolRetryMiddleware(max_retries=2),
HumanInTheLoopMiddleware(tool_names=["send_email", "delete_data"]),
# 4. 监控
LoggingMiddleware(),
TokenCounterMiddleware()
]