LangGraph 短期记忆治理策略之 消息删除(delete) 和 摘要(summarize):分别在模型调用后控制消息总数,以及通过摘要压缩历史消息以节省 token。
| 对比维度 | 裁剪(trim) | 删除(delete) |
|---|
| 触发时机 | @before_model — 模型调用前 | @after_model — 模型调用后 |
| 控制对象 | 传入模型的上下文消息数量 | state 中存储的消息总数 |
| 处理方式 | RemoveMessage 全部旧消息 + 添加裁剪后列表 | 仅 RemoveMessage 超出部分的旧消息 |
| 目的 | 减少给模型的消息量 | 控制状态存储的消息上限 |
- 使用
SummarizationMiddleware(LangGraph 内置中间件) - 双模型架构:外层 Agent 模型 + 内层摘要模型(更小更便宜)
- 超过 token 阈值时触发:旧消息 → 摘要文本,近期消息 → 保留完整
- 通过
keep 参数控制保留最近多少条完整消息
- 墓碑标记(Tombstone):不物理删除消息,而是追加一条"该消息已移除"的标记
- 后续加载 state 时,原始消息 + 墓碑标记 → 对外表现"已被删除"
- 消息实际仍存在于历史消息池中
对话进行中...
│
▼ state["messages"] = 8 条(超出上限 5)
┌─────────────────────────────────┐
│ @after_model 中间件触发 │
│ ┌─────────────────────────────┐│
│ │ messages = state["messages"]││
│ │ if len(messages) > 5: ││
│ │ to_delete = len - 5 ││
│ │ 取前 to_delete 条的消息 ││
│ │ 的 id ││
│ │ return {"messages": [ ││
│ │ RemoveMessage( ││
│ │ id=msg.id ││
│ │ ) for msg in ││
│ │ messages[:to_delete]││
│ │ ]} ││
│ └─────────────────────────────┘│
└─────────────────────────────────┘
│
▼
state 中只剩最近 5 条消息
┌──────────────────────────────────────────────┐
│ SummarizationMiddleware │
│ │
│ token_count > 阈值 (如 100) ? │
│ │ │
│ YES ├──► 旧消息 → 内层摘要模型 → 摘要文本 │
│ │ 最近 2 条 → 保留完整 │
│ │ │
│ NO └──► 不做处理,保持原样 │
│ │
│ 结果: state["messages"] = [摘要, msg_n, msg_n+1]│
└──────────────────────────────────────────────┘
from langgraph.graph import StateGraph
from langgraph.prebuilt.chat_agent_executor import AgentState
from langchain_core.messages import RemoveMessage
from typing import Optional
def delete_old_messages(state: AgentState, runtime) -> Optional[dict]:
"""消息删除中间件:模型调用后,保持消息总数不超过 5 条"""
messages = state["messages"]
if len(messages) <= 5:
return None
to_delete = len(messages) - 5
return {
"messages": [
RemoveMessage(id=messages[i].id)
for i in range(to_delete)
]
}
from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint.memory import InMemorySaver
outer_model = ChatOpenAI(model="gpt-4o", temperature=0)
inner_model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
from langgraph.prebuilt.chat_agent_executor import SummarizationMiddleware
summarization_middleware = SummarizationMiddleware(
model=inner_model,
max_tokens=100,
keep=2,
prompt="请用中文简洁地总结以下对话内容:"
)
agent = create_react_agent(
model=outer_model,
tools=[],
checkpointer=InMemorySaver(),
middleware=[summarization_middleware]
)
config = {"configurable": {"thread_id": "11"}}
response1 = agent.invoke(
{"messages": [HumanMessage(
content="我叫张三,今年30岁,住在北京,在一家互联网公司做工程师..."
)]},
config=config
)
response2 = agent.invoke(
{"messages": [HumanMessage(content="请总结一下我的信息")]},
config=config
)
final_state = agent.get_state(config)
from rich import print as rprint
rprint(final_state.values["messages"])
- 删除 ≠ 裁剪:删除在
@after_model 执行,裁剪在 @before_model 执行,两者时机和目的不同 - 删除后 AI 回复的边界情况:如果 AI 的回复没有提及用户姓名等信息,后续对话可能"遗忘"用户身份 — 依赖 AI 回复时的自然语言包含了关键信息
- 删除数量计算:
to_delete = len(messages) - 5,然后取前 to_delete 条的 id 做 RemoveMessage
- 摘要会丢失信息吗? — 会有一定丢失,但姓名、关键事实等重要信息通常会被保留;配合
keep 参数保留最近完整消息,对大多数场景足够 - max_tokens 阈值如何设置? — 参考模型上下文窗口(如 4K),设为稍小于窗口值,预留余量给工具调用和系统提示;不是拍脑袋定
- 摘要成本高吗? — 使用便宜的轻量模型(如 gpt-4o-mini)做摘要,成本可控
- 摘要触发频率 — 需关注:频繁触发应提高 token 阈值;从不触发应降低阈值或检查是否消息确实很短
- 两个模型别混淆:外层 model 做对话,内层 model(model_in)做摘要,两者可不同
- 基于
@before_model 或 @after_model 中间件,内部逻辑完全自定义 - 可按消息类型(System / Human / AI / Tool)过滤
- 可按时间窗口过滤
- 可按关键词、重要性评分等过滤
| 策略 | 时机 | 适用场景 |
|---|
| 裁剪 (trim) | @before_model | 控制每次请求的 token 量 |
| 删除 (delete) | @after_model | 控制 state 存储的消息总数 |
| 摘要 (summarize) | 超阈值时 | 保留语义信息 + 节省 token |
- 组合使用:摘要 + 裁剪,摘要处理旧消息 + 裁剪控制传入 token
- 模型选择:摘要模型用便宜的轻量模型(如 gpt-4o-mini / qwen-turbo),对话模型用强模型
- 阈值调优:根据实际对话长度分布,监控 token 使用量和摘要触发频率来做调整
- State 不止 messages:state 中还包含
structured_response(结构化输出)、jump_to(中间件跳转)等字段
- 2.5 节提到 state 的理解:除了
messages,还有 structured_response、jump_to 字段,在工具调用和中间件跳转中用到