上回咱们学会了Agent智能代理,让AI能自主决策和调用工具。但你可能会想,Agent虽然智能,但有时候也需要"约束"和"控制"------比如安全检查、性能监控、错误处理等。
这就是Middleware(中间件)的作用!Middleware就像Agent的"控制塔",在Agent执行过程中进行拦截、监控和增强。
中间件:智能代理的"智能控制塔"
想象一下,你有一个能干的AI助手,它会自己思考、自己决策、自己执行。但有时候,我们需要给它加点"安全阀"和"监控器":
- 安全检查:防止助手访问不该访问的地方
- 性能监控:看看它执行得有多快
- 错误处理:出错了怎么办
- 流程控制:关键步骤需要人工审核
Middleware就是做这些事的!它就像给AI助手装上了各种"防护装备"和"监控设备"。
中间件的工作流程
为了更好地理解中间件如何工作,咱们先看看这个流程图:
是
否
用户请求
模型处理
是否需要工具?
调用工具
观察结果
返回结果
这个循环就是Agent的核心:模型思考→决定是否用工具→用工具→看结果→再思考。中间件就是在这个循环的各个关键节点上"插一手",进行监控和控制。
中间件的六大"插手点"
LangChain的中间件提供了6个关键时机让我们插手:

| 插手时机 | 触发时机 | 你能做什么 |
|---|---|---|
| before_agent | 整个Agent开始运行前 | 加载记忆、验证输入是否安全 |
| before_model | 每次问大模型前 | 更新提示词、精简对话历史 |
| wrap_model_call | 围绕每次模型调用 | 拦截并修改请求/响应 |
| wrap_tool_call | 围绕每次工具调用 | 拦截并修改工具执行 |
| after_model | 每次大模型回答后 | 检查回答是否安全、合规 |
| after_agent | 整个Agent运行完后 | 保存结果、清理资源 |
内置中间件:开箱即用的"防护装备"
LangChain已经为我们准备了一些现成的中间件,咱们直接就能用。
1. 个人信息保护中间件 (PIIMiddleware)
这个中间件会自动帮你隐藏敏感信息,比如邮箱、电话等:
python
import os
from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain.agents.middleware import PIIMiddleware
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage # 必须导入HumanMessage
load_dotenv()
# 设置代理(如果在需要代理的环境中)
os.environ["HTTP_PROXY"] = "http://127.0.0.1:7890"
os.environ["HTTPS_PROXY"] = "http://127.0.0.1:7890"
# 创建简单的订单查询工具
@tool
def query_order(user_info:str) ->str:
"""根据邮箱或手机号查询订单信息"""
if "@" in user_info:
return f"已查询邮箱 {user_info} 的订单信息:您有3个订单,总金额为¥599.00。"
else:
return f"已查询手机号 {user_info} 的订单信息:您有2个订单,总金额为¥299.00。"
# 创建带PII保护的Agent
agent = create_agent(
model="gpt-3.5-turbo",
tools=[query_order],
middleware=[
PIIMiddleware("email", strategy="mask",apply_to_input=True),
PIIMiddleware(
"phone_number",
detector=r"(?:\+?\d{1,3}[\s.-]?)?(?:\(?\d{2,4}\)?[\s.-]?)?\d{3,4}[\s.-]?\d{4}",
strategy="block"
)
]
)
def test_agent():
print("=== 测试PII中间件效果 ===")
# 测试邮箱
query1 = "我的邮箱是 john.doe@example.com,你能帮我查询一下订单信息吗?"
print(f"原始查询: {query1}")
try:
response = agent.invoke({"messages": [HumanMessage(content=query1)]})
print(f"Agent响应: {response['messages'][-1].content}")
except Exception as e:
print(f"错误: {str(e)}")
# 测试手机号
query2 = "我的手机号是13800138000,你能帮我查询一下订单信息吗?"
print(f"原始查询: {query2}")
try:
response = agent.invoke({"messages": [HumanMessage(content=query2)]})
print(f"Agent响应: {response['messages'][-1].content}")
except Exception as e:
print(f"错误: {str(e)}")
# 运行测试
if __name__ == "__main__":
test_agent()
运行效果
=== 测试PII中间件效果 ===
原始查询: 我的邮箱是 john.doe@example.com,你能帮我查询一下订单信息吗?
Agent响应: 已查询到邮箱 john.doe@****.com 的订单信息:您有3个订单,总金额为¥599.00。如需更多帮助,请告诉我。
原始查询: 我的手机号是13800138000,你能帮我查询一下订单信息吗?
错误: Detected 1 instance(s) of phone_number in text content
2. 对话摘要中间件 (SummarizationMiddleware)
当对话太长时,这个中间件会自动帮你总结:
python
import os
from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langchain.agents.middleware import SummarizationMiddleware
from langchain_core.tools import tool
from langgraph.checkpoint.memory import InMemorySaver
load_dotenv()
# 设置代理(如果在需要代理的环境中)
os.environ["HTTP_PROXY"] = "http://127.0.0.1:7890"
os.environ["HTTPS_PROXY"] = "http://127.0.0.1:7890"
# 创建工具
@tool
def read_email(subject: str) -> str:
"""读取指定主题的邮件内容"""
if subject == "重要通知":
return """邮件主题:重要通知
内容:尊敬的用户,我们在此通知您关于您账户的重要更新。首先,我们感谢您一直以来对我们服务的支持。根据最新的安全政策,我们将对所有用户账户进行一次全面的安全升级。这次升级将包括更强的密码要求、双因素认证选项以及更频繁的安全检查。我们建议您在收到此通知后的七天内登录您的账户,按照提示完成安全设置。如果您在过程中遇到任何问题,我们的客服团队将全天候为您提供帮助。您可以通过在线聊天、电子邮件或电话联系我们。请注意,为了保护您的账户安全,我们绝不会通过电子邮件或电话询问您的密码或其他敏感信息。任何此类请求都应视为可疑,并应立即向我们报告。我们致力于为您提供最安全、最可靠的服务体验。感谢您的理解与配合。"""
else:
return f"邮件主题:{subject}\n内容:这是一封测试邮件"
llm = ChatOpenAI(model="gpt-3.5-turbo")
# 创建检查点保存器
checkpointer = InMemorySaver()
# 创建带摘要中间件的Agent
agent = create_agent(
model=llm,
tools=[read_email],
middleware=[
SummarizationMiddleware(
model=llm,
trigger=[
("messages", 4), # 对话超过4轮后触发
("tokens", 1000) # 或总Token数超过1000后触发
],
keep=("messages", 2),
)
],
checkpointer=checkpointer,
)
def test_agent():
# 测试:长对话会自动触发摘要
print("=== 测试SummarizationMiddleware效果 ===\n")
# 使用固定的thread_id来保持对话连续性
config = {"configurable": {"thread_id": "test_user"}}
# 模拟多轮对话
queries = [
"读取主题为'重要通知'的邮件",
"这封邮件里提到了哪些安全升级措施?",
"如果我在设置过程中遇到问题,应该如何联系客服?",
"邮件中提到的安全检查频率是多少?",
]
for i,query in enumerate(queries):
print(f"--- 第{i+1}轮对话 ---")
print(f"用户: {query}")
# 调用Agent
response = agent.invoke(
{"messages": [{"role": "user", "content": query}]},
config=config
)
# 获取AI回复
ai_response = response["messages"][-1].content
print(f"助手: {ai_response}")
# 显示当前消息数量
current_messages = response["messages"]
print(f"当前消息数量: {len(current_messages)}")
# 检查是否有摘要生成
for msg in current_messages:
if hasattr(msg,'content') and 'summary' in str(msg.content).lower():
print("发现摘要消息:", msg.content)
if __name__ == "__main__":
test_agent()
运行效果
=== 测试SummarizationMiddleware效果 ===
--- 第1轮对话 ---
用户: 读取主题为'重要通知'的邮件
助手: 已成功读取主题为'重要通知'的邮件。邮件内容如下:
---
尊敬的用户,我们在此通知您关于您账户的重要更新。首先,我们感谢您一直以来对我们服务的支持。根据最新的安全政策,我们
将对所有用户账户进行一次全面的安全升级。这次升级将包括更强的密码要求、双因素认证选项以及更频繁的安全检查。我们建议
您在收到此通知后的七天内登录您的账户,按照提示完成安全设置。如果您在过程中遇到任何问题,我们的客服团队将全天候为您
提供帮助。您可以通过在线聊天、电子邮件或电话联系我们。请注意,为了保护您的账户安全,我们绝不会通过电子邮件或电话询
问您的密码或其他敏感信息。任何此类请求都应视为可疑,并应立即向我们报告。我们致力于为您提供最安全、最可靠的服务体验
。感谢您的理解与配合。
---
当前消息数量: 4
--- 第2轮对话 ---
用户: 这封邮件里提到了哪些安全升级措施?
助手: 这封邮件提到了以下安全升级措施:
1. 更强的密码要求
2. 双因素认证选项
3. 更频繁的安全检查
当前消息数量: 4
发现摘要消息: Here is a summary of the conversation to date:
邮件主题:重要通知
内容:尊敬的用户,我们在此通知您关于您账户的重要更新。首先,我们感谢您一直以来对我们服务的支持。根据最新的安全政策
,我们将对所有用户账户进行一次全面的安全升级。这次升级将包括更强的密码要求、双因素认证选项以及更频繁的安全检查。我
们建议您在收到此通知后的七天内登录您的账户,按照提示完成安全设置。如果您在过程中遇到任何问题,我们的客服团队将全天
候为您提供帮助。您可以通过在线聊天、电子邮件或电话联系我们。请注意,为了保护您的账户安全,我们绝不会通过电子邮件或
电话询问您的密码或其他敏感信息。任何此类请求都应视为可疑,并应立即向我们报告。我们致力于为您提供最安全、最可靠的服
务体验。感谢您的理解与配合。
--- 第3轮对话 ---
用户: 如果我在设置过程中遇到问题,应该如何联系客服?
助手: The email with the subject "Contact Customer Service" is just a test email and does not contain the information about security upgrade measures you mentioned. Would you like me to try reading another email to find the relevant information?
当前消息数量: 4
发现摘要消息: Here is a summary of the conversation to date:
The email mentions the following security upgrade measures:
- Stronger password requirements
- Two-factor authentication options
- More frequent security checks
--- 第4轮对话 ---
用户: 邮件中提到的安全检查频率是多少?
助手: The email with the subject "Contact Customer Service" is a test email and does not contain information about the security upgrade measures mentioned in the conversation summary. Would you like me to try reading another email for the relevant information?
当前消息数量: 4
发现摘要消息: Here is a summary of the conversation to date:
The email mentions the following security upgrade measures:
- Stronger password requirements
- Two-factor authentication options
- More frequent security checks
为什么会出现这样的效果?
SummarizationMiddleware通过自动触发摘要机制控制对话上下文长度:当消息数量超过阈值时,中间件会将旧对话压缩成摘要,配合keep=("messages", 2)参数保留最新的2条消息,加上1条摘要消息和可能的系统消息,使总数稳定在4条左右;这种设计在保留关键信息的同时,也会导致摘要过程中逐渐丢失细节,使助手无法回答关于历史对话的具体问题,但能确保对话在有限上下文中持续进行。
3. 人工审核中间件 (HumanInTheLoopMiddleware)
有些敏感操作需要真人批准才能执行:
python
import os
from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage
load_dotenv()
# 设置代理(如果在需要代理的环境中)
os.environ["HTTP_PROXY"] = "http://127.0.0.1:7890"
os.environ["HTTPS_PROXY"] = "http://127.0.0.1:7890"
# 创建工具
@tool
def read_email(subject: str) -> str:
"""读取指定主题的邮件内容"""
# 这里应该包含实际的邮件读取逻辑
return f"这是关于{subject}的邮件内容"
@tool
def send_email(to: str, subject: str, content: str) -> str:
"""发送邮件"""
# 这里应该包含实际的邮件发送逻辑
return f"成功发送邮件给{to},主题为{subject}"
# 创建LLM
llm = ChatOpenAI(temperature=0.7)
# 创建带人工审核中间件的Agent
agent = create_agent(
model=llm,
tools=[read_email, send_email],
middleware=[HumanInTheLoopMiddleware(
interrupt_on={
"send_email": { # 只有send_email工具需要审核
"allowed_decisions": ["approve", "edit", "reject"]
}
}
)]
)
# 测试
def test_agent():
# 测试:发送邮件时会暂停等待人工审核
print("=== 测试人工审核中间件 ===\n")
# 先测试一个不需要审核的操作
print("1. 测试读取邮件(不需要审核):")
response1 = agent.invoke({"messages": [HumanMessage(content="读取主题为'测试'的邮件")]})
print("回答:", response1["messages"][-1].content)
print()
# 然后测试需要审核的操作
print("2. 测试发送邮件(需要人工审核):")
response2 = agent.invoke({
"messages": [HumanMessage(content="发送邮件给alice@example.com,主题是'会议通知',内容是'明天下午3点开会'")]
})
print("回答:", response2["messages"][-1].content)
print()
if __name__ == "__main__":
test_agent()
运行效果
=== 测试人工审核中间件 ===
1. 测试读取邮件(不需要审核):
回答: 成功读取主题为'测试'的邮件内容。邮件内容为:'这是关于测试的邮件内容'。
2. 测试发送邮件(需要人工审核):
回答:
自定义中间件:打造专属"控制台"
如果内置中间件不够用,咱们还可以自己造!比如,创建一个购车主题的中间件,根据预算等级提供不同的车型推荐:
python
import os
from dotenv import load_dotenv
from dataclasses import dataclass
from typing import Callable
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langchain.agents.middleware import AgentMiddleware
from langchain.agents.middleware.types import ModelRequest, ModelResponse
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage
load_dotenv()
# 设置代理(如果在需要代理的环境中)
os.environ["HTTP_PROXY"] = "http://127.0.0.1:7890"
os.environ["HTTPS_PROXY"] = "http://127.0.0.1:7890"
# 定义购车上下文
@dataclass
class CarContext:
budget_range: str = "10-20万"
# 创建购车工具
@tool
def economy_car_recommendation() -> str:
"""经济型车型推荐"""
return """经济型车型推荐(10-20万预算):
1. 日产轩逸 - 省油舒适,空间宽敞
2. 丰田卡罗拉 - 可靠耐用,保值率高
3. 大众朗逸 - 德系品质,操控稳健
4. 本田思域 - 动力充沛,外观时尚"""
@tool
def mid_range_car_recommendation() -> str:
"""中档车型推荐"""
return """中档车型推荐(20-30万预算):
1. 本田雅阁 - 空间宽敞,配置丰富
2. 丰田凯美瑞 - 舒适安静,品质可靠
3. 大众帕萨特 - 商务大气,动力充沛
4. 凯迪拉克CT4 - 美式豪华,操控出色"""
@tool
def compare_models(model1:str, model2:str) -> str:
"""车型对比分析"""
return f"""{model1} vs {model2} 对比分析:
1. 价格对比:{model1}价格区间12-15万,{model2}价格区间18-22万
2. 空间对比:{model2}在后排空间和后备箱容积上更优
3. 油耗对比:{model1}百公里油耗约6.5L,{model2}约7.2L
4. 保值率:{model1}三年保值率约65%,{model2}约60%"""
# 自定义中间件: 根据预算等级调整可用工具
class CarBudgetMiddleware(AgentMiddleware):
def warp_model_call(
self,
model_request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse]
) -> ModelResponse:
# 获取预算范围
budget_range = request.runtime.context.budget_range
# 根据预算提供不同工具
if budget_range == "20-30万":
# 高预算:提供中档车型推荐
model_request.tools = [mid_range_car_recommendation, compare_models]
else:
# 低预算:提供经济型车型推荐
model_request.tools = [economy_car_recommendation, compare_models]
# 添加预算提示
budget_hint = f"\n[系统提示: 您的购车预算为{budget_range}]"
request.messages[-1].content += budget_hint
return handler(model_request)
# 创建LLM
llm = ChatOpenAI(model="gpt-3.5-turbo")
# 创建带自定义中间件的购车Agent
car_agent = create_agent(
model=llm,
tools=[economy_car_recommendation, mid_range_car_recommendation, compare_models],
middleware=[CarBudgetMiddleware()],
context_schema=CarContext
)
# 测试Agent
def test_agent():
# 测试:不同预算会有不同的车型推荐
print("=== 测试购车中间件(10-20万预算) ===\n")
response = car_agent.invoke({
"messages": [HumanMessage(content="推荐一款适合家庭使用的车")],
"runtime": {"context": CarContext(budget_range="10-20万")}
})
print("购车顾问建议:", response["messages"][-1].content)
print("\n=== 测试购车中间件(20-30万预算) ===\n")
response = car_agent.invoke({
"messages":[HumanMessage(content="推荐一款适合商务使用的车")],
"runtime": {"context": CarContext(budget_range="20-30万")}
})
print("购车顾问建议:", response["messages"][-1].content)
print("\n=== 测试购车中间件(对比车型) ===\n")
response = car_agent.invoke({
"messages":[HumanMessage(content="对比本田雅阁和丰田凯美瑞")],
"runtime": {"context": CarContext(budget_range="20-30万")}
})
print("购车顾问建议:", response["messages"][-1].content)
if __name__ == "__main__":
test_agent()
运行效果
=== 测试购车中间件(10-20万预算) ===
购车顾问建议: 我为您推荐以下适合家庭使用的中档车型:
1. 本田雅阁 - 空间宽敞,配置丰富
2. 丰田凯美瑞 - 舒适安静,品质可靠
3. 大众帕萨特 - 商务大气,动力充沛
4. 凯迪拉克CT4 - 美式豪华,操控出色
您可以根据个人偏好和需求选择适合您家庭的车型。
=== 测试购车中间件(20-30万预算) ===
购车顾问建议: 以下是适合商务使用的中档车型推荐:
1. 本田雅阁 - 空间宽敞,配置丰富
2. 丰田凯美瑞 - 舒适安静,品质可靠
3. 大众帕萨特 - 商务大气,动力充沛
4. 凯迪拉克CT4 - 美式豪华,操控出色
您可以选择其中一款来满足商务使用的需求。
=== 测试购车中间件(对比车型) ===
购车顾问建议: 经过对比分析,本田雅阁和丰田凯美瑞有以下区别:
1. 价格:本田雅阁价格更为亲民,而丰田凯美瑞定位更高档;
2. 空间:丰田凯美瑞在后排空间和后备箱容积方面优于本田雅阁;
3. 油耗:本田雅阁的油耗略低于丰田凯美瑞;
4. 保值率:本田雅阁的保值率略高于丰田凯美瑞。
根据个人需求和预算,可以选择适合自己的车型。
中间件的执行顺序
多个中间件会按照添加的顺序执行,形成一个"层层包裹"的结构:
用户请求
中间件1.before_agent
中间件2.before_agent
Agent处理
中间件2.after_agent
中间件1.after_agent
返回结果
所以顺序很重要!建议的排列顺序是:
- 最外层:隐私保护、安全检查
- 中间层:性能优化、对话管理
- 最内层:业务逻辑、个性化定制
总结
通过今天的学习,咱们掌握了Middleware中间件的核心用法。中间件让Agent从"能干的助手"变成了"可靠、可控的助手"。
在下一章中,我们将学习LangChain的调试和监控技巧,了解如何深入洞察AI的执行过程,让开发更加高效!