🎯 课程主题
详解 SummarizationMiddleware 的配置与使用,通过自动摘要历史消息实现上下文压缩,有效控制 Token 消耗成本。
📝 核心知识点
1. SummarizationMiddleware 概述
- 概念说明:属于"成本与资源控制类"中间件,在触发条件满足时调用大模型对历史消息进行摘要,将摘要结果作为
HumanMessage放到消息列表最前面,从而压缩上下文、节省 Token 消耗。 - 关键细节:适用于长会话场景,当对话轮次过多、消息列表过长时自动压缩历史信息。
2. 核心参数详解
| 参数 | 类型 | 说明 |
|---|---|---|
model | str 或 BaseChatModel | 用于摘要的模型,可以是模型名称字符串(底层自动调用 init_chat_model)或模型实例 |
trigger | list | 触发器列表(元组),每个元素为一个触发条件,满足任意一个即触发摘要。支持三种条件:("tokens", N)、("messages", N)、("fraction", 0~1) |
keep | dict | 摘要后保留的原始消息数量,支持 {"messages": N}、{"tokens": N} 或 {"fraction": N},三选一 |
token_counter | callable | 统计 Token 数量的函数,一般使用默认值 |
summary_prompt | str | 自定义摘要提示词,必须包含 {messages} 占位符,用于插入待摘要的历史消息列表 |
max_tokens | int | 摘要时历史消息的最大 Token 数限制 |
3. 触发机制详解
("tokens", N):当历史消息总 Token 数达到 N 时触发("messages", N):当历史消息条数达到 N 条时触发("fraction", 0.001):当历史 Token 数占模型max_input_tokens的比例达到指定值时触发(需模型 profile 中包含max_input_tokens参数,否则需手动指定)
🏗️ 架构与工作流
Agent 执行 → 消息列表累积
│
▼
┌─────────────────────────────────┐
│ trigger 条件检查(满足任一触发) │
│ ├── tokens >= 阈值 │
│ ├── messages >= 阈值 │
│ └── fraction * max_input_tokens │
└─────────────────────────────────┘
│ 触发
▼
┌─────────────────────────────────┐
│ 1. 保留最近 keep 条原始消息 │
│ 2. 对更早的消息调用摘要模型 │
│ 3. 摘要结果作为 HumanMessage │
│ 插入到消息列表最前面 │
└─────────────────────────────────┘
│
▼
继续 Agent 执行(上下文已压缩)
💻 代码实战
from langchain_deepseek import ChatDeepSeek
from langgraph.prebuilt import create_react_agent
from langchain_core.messages import HumanMessage, AIMessage
# ==================== 准备模型 ====================
model = ChatDeepSeek(model="deepseek-v4-flash")
# ==================== 准备测试消息列表 ====================
# 模拟一段较长的对话历史
messages = [
HumanMessage(content="你好,我想了解一下你们的服务"),
AIMessage(content="您好!我们提供AI智能助手服务,有什么可以帮您的?"),
HumanMessage(content="能帮我写一段Python代码吗?"),
AIMessage(content="当然可以,请问您需要什么样的Python代码?"),
HumanMessage(content="我想写一个快速排序算法"),
AIMessage(content="好的,以下是快速排序的Python实现:\ndef quicksort(arr):..."),
HumanMessage(content="谢谢!那再帮我看看这个代码的性能如何优化"),
AIMessage(content="快速排序的时间复杂度是O(n log n),优化可以从以下几个方面..."),
HumanMessage(content="你高兴的太早了"),
AIMessage(content="呵呵,你什么意思?"),
HumanMessage(content="再问一个问题,机器学习中什么是过拟合?"),
]
# ==================== 举例一:基础使用(trigger + keep)====================
agent = create_react_agent(
model=model,
tools=[],
middleware=[
SummarizationMiddleware(
model=model, # 用于摘要的模型
trigger=[ # 触发器:满足任意一个即触发
("tokens", 100), # Token数达100触发
("messages", 6), # 消息数达6条触发
("fraction", 0.001), # Token占max_input_tokens的0.1%触发
],
keep={"messages": 2}, # 摘要后保留最近2条原始消息
)
]
)
# 需要在模型 profile 中指定 max_input_tokens(fraction 需要此参数)
model_with_profile = ChatDeepSeek(
model="deepseek-v4-flash",
profile={"max_input_tokens": 128000} # 128K tokens
)
result = agent.invoke({"messages": messages})
for msg in result["messages"]:
msg.pretty_print()
# 输出结构:
# HumanMessage (摘要结果,放在最前面)
# "以下是历史消息摘要: ..."
# AIMessage (keep保留的最近第2条)
# HumanMessage (keep保留的最近第1条)
# AIMessage (最终回复)
# ==================== 举例二:自定义 summary_prompt ====================
agent_with_prompt = create_react_agent(
model=model_with_profile,
tools=[],
middleware=[
SummarizationMiddleware(
model=model,
trigger=[
("messages", 6),
],
keep={"messages": 2},
# 自定义摘要提示词,{messages} 占位符会被历史消息替换
summary_prompt=(
"对历史消息进行摘要:\n"
"消息列表如下:\n"
"{messages}\n\n"
"请用中文简洁地总结上述对话的核心内容。"
),
)
]
)
result = agent_with_prompt.invoke({"messages": messages})
for msg in result["messages"]:
msg.pretty_print()
# 此时摘要结果的 HumanMessage 将使用自定义的中文提示词生成
⚠️ 常见问题与避坑指南
- 必须指定
max_input_tokens:当trigger中使用("fraction", ...)时,如果模型 profile 中没有max_input_tokens,会报错。需要显式设置profile={"max_input_tokens": 128000}。 trigger是OR 关系(满足任意一个即触发),keep是三选一(只能设置一种保留方式)。summary_prompt中必须包含{messages}占位符,否则无法将待摘要的消息列表插入。- 摘要模型可以与主 Agent 模型不同——可以用更轻量的模型做摘要以进一步节省成本。
- 摘要结果始终以
HumanMessage形式插入到消息列表最开头,这是固定的行为。
💡 个人总结与延伸
SummarizationMiddleware 是长会话场景下控制 Token 成本的利器,核心逻辑是"触发条件 → 调用模型摘要 → 替换历史消息"。它体现了中间件的典型模式:在主流程(Agent 对话)的某个节点(消息列表过长时)拦截并增强(自动压缩)。实际项目中,可以结合 trigger 的参数灵活调整摘要的触发频率,在成本与对话质量之间找到平衡。