3.8 ContextEditingMiddleware中间件
1.实验组-启用上下文编辑
上下文编辑中间件,该中间件提供了上下文管理的一种方式。
通过更改发送给模型的消息列表来控制成本。
注意:不会更改消息列表。因此我们只能通过token用量来推测是否对消息列表进行了裁剪。
from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain.agents.middleware import (
ContextEditingMiddleware,
ClearToolUsesEdit,
)
from langchain.messages import HumanMessage, AIMessage
from langgraph.checkpoint.memory import InMemorySaver
from langchain.tools import tool
load_dotenv()
count = 0
@tool
def get_weather(city: str):
"""查询指定城市天气"""
global count
return (
f"当前是第 {count} 次调用工具,{city}今天天气晴朗"
f"天气非常好,北风,非常适合出行,盼望着,盼望着,"
f"春天来了。我喜欢春天,你喜欢吗,天气真的很不错"
f"万里无云,天气晴朗,春和景明,哈哈哈哈哈哈,这是凑字数的"
f"真不错,天气非常好,适合出行,这里 token 挺多的"
f"可以出门玩,可以跑步,钓鱼,爬山,一切都很好哈哈哈"
)
agent = create_agent(
model="deepseek:deepseek-v4-pro",
tools=[get_weather],
middleware=[
ContextEditingMiddleware(
edits=[
ClearToolUsesEdit(
trigger=50,
keep=0,
),
],
),
],
checkpointer=InMemorySaver(),
)
config = {
"configurable": {
"thread_id": "1",
}
}
for i in range(3):
print(
"=" * 30,
f"当前是第 {i + 1} 轮调用",
"=" * 30,
)
count = i + 1
response = agent.invoke(
{
"messages": [
HumanMessage(
f"第 {i + 1} 次询问:"
"今天北京天气如何,一句话回答"
)
]
},
config=config,
)
print("---- 本次返回的 messages ----")
for msg in response["messages"]:
# msg.pretty_print()
if isinstance(msg, AIMessage):
if not msg.tool_calls:
print(f"本次 token 用量:{msg.usage_metadata}")
说明
ContextEditingMiddleware 的价值:大模型多轮对话时,如果频繁调用产生大量文本的工具 (如代码执行、网页爬取),历史记录会急剧膨胀。这个中间件就像一个“上下文抽脂手术”,在不 影响当前对话的前提下,自动在后台删掉之前沉淀的工具调用废话,从而极大地节省 Token 费用 并防止超出模型最大上下文窗口(Context Window)。
InMemorySaver :它在内存中开辟了一个空间。第二轮和第三轮提问时,Agent 能通过 thread_id 自动找回前几轮的记忆。

2. 对照组-不裁剪上下文
agent = create_agent(
model="deepseek:deepseek-v4-pro",
tools=[get_weather],
middleware=[],
checkpointer=InMemorySaver(),
)

可以观察到,对照组的input_tokens明显大于实验组。
3.9 FilesystemFileSearchMiddleware中间件
基于系统的Glob和Grep检索工具,为Agent赋予本地文件搜索和分析的能力。
Glob根据文件路径检索
Grep根据文件内容检索
from langchain.agents import create_agent
from langchain.agents.middleware import FilesystemFileSearchMiddleware
from langchain.messages import HumanMessage
from dotenv import load_dotenv
load_dotenv(verbose=True)
agent = create_agent(
model="deepseek:deepseek-v4-pro",
tools=[], # 自动添加 Glob 和 Grep 工具
middleware=[
FilesystemFileSearchMiddleware(
root_path="../todo_workspace", # 搜索目录
# 【可选】限制搜索的文件后缀,防止模型读取非代码或无关文件
# allowed_extensions=[".py", ".ipynb", ".js", ".md"], # 允许的文件类型
# 是否启用 ripgrep 搜索引擎:
# 设为 True 可以获得比原生 Grep 更快的性能(前提是系统已安装 ripgrep)
use_ripgrep=True,
# 单个文件的最大读取限制(单位 MB):
# 防止读取超大型日志或二进制文件导致 OOM
max_file_size_mb=10,
),
],
)
result = agent.invoke({
"messages": [
HumanMessage("找到包含 add 函数的 Python 或 Jupyter 文件")
]
})
for msg in result["messages"]:
msg.pretty_print()
4、多个中间件组合及执行顺序
问题:Middleware 可以叠加使用,那么多个中间件书写顺序重要吗?
非常重要!比如:
middleware=[
TrimmerMiddleware(), # 1. 先修剪消息
SummarizationMiddleware(), # 2. 再摘要
LoggingMiddleware(), # 3. 最后记录日志
]
比如:
agent = create_agent(
model=model,
tools=[get_weather, get_news],
middleware=[
PIIMiddleware(strategy="redact"), # 1. 最先检查 PII
ModelCallLimitMiddleware(run_limit=10), # 2. 限制调用次数
SummarizationMiddleware(max_tokens_before_summary=500), # 3. 总结历史
ToolRetryMiddleware(max_retries=3), # 4. 重试工具
],
)
举例:
from langchain.agents.middleware import AgentMiddleware
class Middleware1(AgentMiddleware):
def before_model(self, state, runtime):
print("[中间件1] before_model")
return None
def after_model(self, state, runtime):
print("[中间件1] after_model")
return None
class Middleware2(AgentMiddleware):
def before_model(self, state, runtime):
print("[中间件2] before_model")
return None
def after_model(self, state, runtime):
print("[中间件2] after_model")
return None
class Middleware3(AgentMiddleware):
def before_model(self, state, runtime):
print("[中间件3] before_model")
return None
def after_model(self, state, runtime):
print("[中间件3] after_model")
return None
agent = create_agent(
model="deepseek:deepseek-v4-pro",
tools=[],
middleware=[
Middleware1(),
Middleware2(),
Middleware3(),
],
)
print("\n执行一次调用,观察顺序:")
agent.invoke({
"messages": [
{
"role": "user",
"content": "测试",
}
]
})
print("\n关键点:")
print(" - before_model: 正序执行(1→2→3)")
print(" - after_model: 逆序执行(3→2→1)")
print(" - 类似洋葱模型:1→2→3→模型→3→2→1")
执行一次调用,观察顺序:
[中间件1] before_model
[中间件2] before_model
[中间件3] before_model
[中间件3] after_model
[中间件2] after_model
[中间件1] after_model关键点:
- before_model: 正序执行(1→2→3)
- after_model: 逆序执行(3→2→1)
- 类似洋葱模型:1→2→3→模型→3→2→1
3. 分析
Middleware1.before_model↓ 正序Middleware2.before_model↓Middleware3.before_model↓- [模型调用]
Middleware3.after_model↑ 逆序Middleware2.after_model↑Middleware1.after_model↑
类似洋葱模型:外层先进后出

1096

被折叠的 条评论
为什么被折叠?



