🎯 课程主题
理解多个中间件组合使用时的执行顺序——洋葱模型(由外向内逐层进入,由内向外逐层退出)。
📝 核心知识点
1. middleware 参数是列表
- 概念说明:
create_agent的middleware参数接收一个列表,可以同时传入多个中间件。 - 关键细节:前面各节测试时通常只传入一个中间件,看不出顺序;实际开发中多个中间件组合时,顺序非常关键。
2. 洋葱模型执行顺序
- 概念说明:多个中间件的执行遵循洋葱模型——进入时按列表顺序
[1, 2, 3]依次执行before_*钩子,退出时按逆序[3, 2, 1]依次执行after_*钩子。 - 关键细节:即:进 1→2→3,出 3→2→1。
请求进入 ──► before_1 ──► before_2 ──► before_3 ──► [核心模型调用] 响应退出 ◄── after_1 ◄── after_2 ◄── after_3 ◄──
3. 顺序的意义与实战建议
- 概念说明:先执行的中间件应处理"前置"逻辑,后执行的中间件做"收尾"工作。
- 关键细节:如典型顺序——先修剪消息(
TrimMessagesMiddleware)→ 再做摘要(SummarizationMiddleware)→ 最后记录日志,这样的排列符合业务逻辑的先后依赖关系。
4. 自定义中间件演示顺序
- 概念说明:通过自定义三个中间件,在每个中间件中实现
before_model和after_model钩子,打印日志来直观观察执行顺序。 - 关键细节:交换列表中的顺序会直接改变执行次序,例如
[3, 1, 2]→ 进入 3→1→2,退出 2→1→3。
🏗️ 架构与工作流
middleware = [middleware_1, middleware_2, middleware_3]
┌──────────────────────────────────────────┐
│ Agent 执行流程 │
│ │
│ before_model(1) │
│ └─► before_model(2) │
│ └─► before_model(3) │
│ └─► ★ 模型调用 ★ │
│ ◄── after_model(3) │
│ ◄── after_model(2) │
│ ◄── after_model(1) │
└────────────────────────────────────────────┘
middleware 列表中的顺序就是"包裹层"从外到内的顺序
💻 代码实战
from langchain.agents import create_agent
from langchain.agents.middleware import AgentMiddleware
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
model = ChatOpenAI(model="gpt-4o")
# 自定义中间件1
class Middleware1(AgentMiddleware):
def before_model(self, state, runtime):
print("[Middleware 1] before_model 执行")
return None
def after_model(self, state, runtime):
print("[Middleware 1] after_model 执行")
return None
# 自定义中间件2
class Middleware2(AgentMiddleware):
def before_model(self, state, runtime):
print("[Middleware 2] before_model 执行")
return None
def after_model(self, state, runtime):
print("[Middleware 2] after_model 执行")
return None
# 自定义中间件3
class Middleware3(AgentMiddleware):
def before_model(self, state, runtime):
print("[Middleware 3] before_model 执行")
return None
def after_model(self, state, runtime):
print("[Middleware 3] after_model 执行")
return None
# 组合多个中间件(顺序关键!)
agent = create_agent(
model=model,
tools=[],
middleware=[
Middleware1(), # 最外层
Middleware2(), # 中间层
Middleware3(), # 最内层(最靠近模型)
],
)
response = agent.invoke({
"messages": [HumanMessage(content="你好")]
})
# 输出顺序:
# [Middleware 1] before_model 执行 ← 进入:1
# [Middleware 2] before_model 执行 ← 进入:2
# [Middleware 3] before_model 执行 ← 进入:3
# [Middleware 3] after_model 执行 ← 退出:3
# [Middleware 2] after_model 执行 ← 退出:2
# [Middleware 1] after_model 执行 ← 退出:1
⚠️ 常见问题与避坑指南
- 切勿随意排列中间件顺序:比如日志中间件如果放在
ContextEditingMiddleware之前,则日志记录的是裁剪前的完整状态;放在之后则记录裁剪后的状态。 - 洋葱模型并非 LangChain 独有,与 Web 框架(如 Express/Koa)的中间件机制一致,有相关经验的可以类比理解。
- 如果某个中间件的
after_*钩子依赖前一个中间件的处理结果,则必须确保该中间件在列表中的位置符合依赖关系。
💡 个人总结与延伸
LangChain Agent 的中间件采用了经典的洋葱模型,middleware 列表的书写顺序决定了执行的"包裹层级"。理解这个模型对调试和组合中间件至关重要——数据流从外到内依次经过每个中间件的 before 阶段,然后从内到外依次经过 after 阶段。实际开发中应先在纸上画出期望的执行顺序,再对应排列 middleware 列表。