LangChain LangGraph
Module 1 Module 2 Module 3
Module 3 · 中间件与 HITL
1 3.2 Managing Msgs
2 3.3 HITL
3 3.4 Dyn Models
4 3.4 Dyn Prompts
5 3.4 Dyn Tools
6 3.5 Email Agent
SUM Module Summary
HomeLangChainModule 3 · 中间件与 HITL3.2 Managing Msgs
📓 Notebook 📖 Explained
LANGCHAIN MODULE 3 · LESSON 3.2

管理消息历史
Summarization · Trim · RemoveMessage

本节解决长对话里的上下文膨胀问题:用 SummarizationMiddleware 压缩旧消息,用 before_agent middleware 删除不该继续进入模型的消息。

1 为什么需要管理 messages

Agent 的 state 里最核心的字段通常是 messages。对话越长,消息越多,成本、延迟和上下文污染都会上升。Module 3 的第一步就是学会控制消息历史。

问题表现本课方案
上下文过长token 超限或调用变慢SummarizationMiddleware 总结旧消息
无用工具消息模型看到噪声输出后回答跑偏RemoveMessage 删除指定消息
多轮记忆混乱旧信息和新问题互相干扰只保留必要消息和摘要
核心原则

不是所有历史消息都值得一直保留。需要保留的是“当前任务仍然需要的上下文”,而不是完整 transcript。

2 用 SummarizationMiddleware 压缩旧消息

SummarizationMiddleware 会在消息达到触发条件后,用模型把旧消息压缩成摘要,同时保留最近几条原始消息。

Python
from langchain.agents.middleware import SummarizationMiddleware

summarize_middleware = SummarizationMiddleware(
    model=deepseek_model(),
    trigger=("tokens", 100),
    keep=("messages", 1),
)

agent = create_agent(
    model=deepseek_model(),
    checkpointer=InMemorySaver(),
    middleware=[summarize_middleware],
)
参数含义

trigger=("tokens", 100) 表示 token 数超过阈值时触发总结;keep=("messages", 1) 表示保留最近 1 条消息原文。

3 长对话示例:月球城市 Lunapolis

Notebook 构造了一段虚构对话:月球首都、天气、矿工、工会罢工。最后再问“如果你是新总统会怎么回应”。这个问题需要旧上下文,但不一定需要完整逐字历史。

Python
messages = [
    HumanMessage(content="What is the capital of the moon?"),
    AIMessage(content="The capital of the moon is Lunapolis."),
    HumanMessage(content="What is the weather in Lunapolis?"),
    AIMessage(content="Skies are clear, with a high of 120C and a low of -100C."),
    HumanMessage(content="How many cheese miners live in Lunapolis?"),
    AIMessage(content="There are 100,000 cheese miners living in Lunapolis."),
    HumanMessage(content="Do you think the cheese miners' union will strike?"),
    AIMessage(content="Yes, because they are unhappy with the new president."),
    HumanMessage(content="If you were Lunapolis' new president how would you respond to the cheese miners' union?"),
]

response = agent.invoke(
    {"messages": messages},
    {"configurable": {"thread_id": "1"}},
)
为什么这个例子适合总结

最后的问题依赖前文事实,但不依赖每一句原始表达。摘要可以保留关键事实,同时减少上下文长度。

4 用 before_agent 删除工具消息

第二个示例展示如何在模型调用前处理 state。@before_agent middleware 读取当前 messages,找出所有 ToolMessage,再返回 RemoveMessage 指令删除它们。

Python
from langchain.messages import RemoveMessage
from langchain.agents.middleware import before_agent

@before_agent
def trim_messages(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
    """Remove all the tool messages from the state"""
    messages = state["messages"]
    tool_messages = [m for m in messages if isinstance(m, ToolMessage)]

    return {"messages": [RemoveMessage(id=m.id) for m in tool_messages]}
RemoveMessage 的语义

RemoveMessage(id=...) 不是普通文本消息,而是告诉 LangGraph 从 state 中移除指定 id 的消息。

5 为什么要删 ToolMessage

Notebook 的设备故障排查示例里,工具消息包含“blorp-x7”“greeble”等噪声诊断文本。用户最后问温度时,如果模型看到所有原始工具输出,可能会被不可靠或无关的信息干扰。

Python
messages = [
    HumanMessage(content="My device won't turn on. What should I do?"),
    ToolMessage(content="blorp-x7 initiating diagnostic ping…", tool_call_id="1"),
    AIMessage(content="Is the device plugged in and turned on?"),
    HumanMessage(content="Yes, it's plugged in and turned on."),
    ToolMessage(content="temp=42C voltage=2.9v … greeble complete.", tool_call_id="2"),
    AIMessage(content="Is the device showing any lights or indicators?"),
    HumanMessage(content="What's the temperature of the device?"),
]

agent = create_agent(
    model=deepseek_model(),
    checkpointer=InMemorySaver(),
    middleware=[trim_messages],
)
适用场景

可以删除低价值工具日志、敏感中间数据、格式很乱的 API 原始响应,只把必要结论保留给模型。

6 本课 takeaway