一句话总结:短期记忆(Short-term memory)是 AI Agent 在单次会话(Thread)内保持多轮交互上下文的核心机制,通过状态检查点(Checkpointer)实现状态持久化,并结合截断、删除和摘要等策略解决大模型上下文窗口受限及注意力分散的问题。
注意:
- 在 LangChain 的 create_agent 底层机制中,通过 system_prompt 传入的系统提示词,是不会被持久化存储到 AgentState 的 messages 数组中的!
- 根据 OpenAI 的强契约协议:ToolMessage 的前一条消息,必须是包含 tool_calls 的 AIMessage。 孤立的 ToolMessage 会直接导致网关拒绝服务(HTTP 400)。
核心概念与常用 API 解析
在 LangChain 和 LangGraph 的架构中,短期记忆并非单纯的数组变量,而是深度集成在图状态(Graph State)中的上下文管理系统。
- AgentState:Agent 的基础状态数据结构。默认提供 messages 键来存储对话历史。支持通过继承该类添加自定义字段(如 user_id、preferences),以在整个会话生命周期中流转结构化数据。
- checkpointer:状态持久化接口。Agent 每次被调用或完成一个步骤(如工具调用)后,都会通过 Checkpointer 将当前状态写入存储介质。测试环境常用 InMemorySaver,生产环境则依赖 PostgresSaver 等数据库级实现。它依靠外部传入的 thread_id 来严格隔离和恢复不同会话的上下文。
- InMemorySaver 与 PostgresSaver:checkpointer 的具体实现类。InMemorySaver 将状态保存在内存中,适用于开发调试;PostgresSaver 将状态持久化到 PostgreSQL 数据库中,适用于生产环境。
- ToolRuntime:工具运行时注入对象。当在工具函数的参数中声明它时,底层会自动注入。工具可通过
runtime.state读取当前的短期记忆,或通过返回 Command 对象直接更新状态。- 读状态:通过
runtime.state获取当前对话的上下文(如用户的 VIP 级别)。 - 写状态:通过返回
Command(update={...})来直接修改 Agent 的记忆,供下游其他工具或大模型使用。
- 读状态:通过
周边与扩展 API 梳理
为了干预上下文和处理大模型的复杂记忆,文档还介绍了以下高级拦截与修改机制:
- @before_model:前置中间件装饰器。在状态传递给大模型之前触发,常用于执行历史消息的动态截断或系统提示词的动态修改。
- @after_model:后置中间件装饰器。在大模型返回生成结果之后触发,常用于输出验证,如扫描生成的最新消息并抹除敏感信息。
- @dynamic_prompt:动态提示词中间件。允许在模型调用前,通过读取
request.runtime.context或当前的状态数据,动态拼接并返回新的系统提示词(System Prompt)。 - RemoveMessage 与 REMOVE_ALL_MESSAGES:状态清除指令。由于 AgentState 中对 messages 字段采用了追加和合并的 Reducer 机制,直接赋予新列表是无效的,必须在更新指令中返回 RemoveMessage 对象来触发底层的物理删除逻辑。
- SummarizationMiddleware:官方内置的消息摘要中间件。自动监控对话的 Token 使用量,并在达到阈值时触发摘要压缩机制。
- Command:状态更新指令类。允许工具在执行完毕后,直接向 Agent 返回
Command(update={...})来突变(Mutate) AgentState 中的记忆字段。
工程化代码落地示例
以下代码整合了自定义状态扩展、前置截断中间件(Trim)、以及工具动态读写短期记忆(Command)的完整工作流。
|
|
输出结果:
|
|
常见踩坑与高频面试点
在复杂 Agent 系统的开发与面试环节,短期记忆的维护直接关系到系统的稳定性与使用成本。
踩坑 1:截断消息(Trim)破坏了大模型协议结构
- 现象与痛点:在编写 trim_messages 时单纯按条数(如 messages[-5:])截断。如果在截断边界处,刚好保留了 ToolMessage 却丢失了对应的 AIMessage(包含 tool_calls 的那条),大模型 API 会直接抛出 HTTP 400 格式验证错误,导致对话链断裂。
- 核心对策:截断策略必须具备“Schema 感知”。如果最后 N 条消息的起始处出现了孤立的工具回调结果,必须将其一并移除,或向上回溯保留成对的 Tool Call。同时,必须保留对话流中第一条 SystemMessage。
高频考点 1:消息列表的 Reducer 更新机制
- 面试官提问:如果在工具中直接通过 state[“messages”] = new_messages 来修改历史记录,为什么不生效?
- 满分回答:LangGraph 的 AgentState 对 messages 字段默认配置了 add_messages Reducer(聚合器)。这意味着所有直接写入 messages 键的字典操作,在底层都会被转译为 Append(追加) 或 Merge(ID相同的合并)。如果要物理删除上下文消息,必须显式传递 RemoveMessage(id=…) 对象,框架层才会逆向执行移除逻辑。
高频考点 2:超长上下文的 4 大应对策略
- 面试官提问:“当用户的多轮对话历史越来越长,超出了大模型的 Token 限制,或者导致 API 极度昂贵时,在工程上有哪几种解决方案?”
- 满分回答:针对记忆过载问题,通常有 4 种标准对策:
- Trim messages (截断消息):采用滑动窗口机制,通过 @before_model 中间件动态移除最前面的 N 条消息。实现最简单、开销为零,但会导致 Agent 丧失对早期对话的记忆。
- Delete messages (删除消息):利用 RemoveMessage 从图状态中永久性地物理删除指定消息。通常配合 @after_model 使用,用于主动清洗包含密码、密钥等敏感词的消息或执行强制清空。
- Summarize messages (摘要消息):引入 SummarizationMiddleware。当对话 Token 数达到设定阈值时,自动触发一个小模型将早期的多条消息压缩成一段高密度的摘要文本,并用摘要替换原消息列表。这种方案在保留全局长效语义和压缩 Token 之间取得了最佳平衡。
- Custom strategies (自定义策略):在复杂的业务链路中,结合自定义的 Middleware编写极细粒度的裁剪、替换规则(例如针对多模态 Agent 定向剔除历史记录中的 Base64 图片残骸)。
高频考点 3:滑动窗口截断(Trim) vs 摘要压缩(Summarize)的取舍
- 面试官提问:面对超长对话导致 Context 溢出的问题,Trim 和 Summarize 该如何选型?
- 满分回答:
- Trim 方案:延迟极低,成本零开销,适合任务导向型、具有局部上下文依赖的 Agent(如点餐、代码审查),因为此类任务无需追溯太早的聊天记录。
- Summarize 方案:依赖 SummarizationMiddleware 进行后台的小模型推理压缩。它保护了全局长效逻辑不丢失,适合情感陪伴、长流程推理型 Agent。缺点是带来了额外的 Token 消耗与异步推理延迟。在生产环境中,通常会将两套方案结合:短周期采用 Trim 防爆仓,长周期异步执行归档 Summarize 注入到系统 Prompt 中。