Agent 开发 / 学习记录
上下文与记忆:语义整理的艺术
从一句“继续,下一部分”出发,理解本轮上下文如何被组织,以及记忆如何保留含义与边界
在《Agent开发指南》这段学习聊天里,我多次用一句话推进讨论:“继续,下一部分”
这句话需要依靠前面的约定才能被理解。当前是在补 Python 语法,还是已经可以进入下一节?示例应该使用哪套 API?哪些问题只是讲过,哪些已经通过练习验证?如果这些信息没有被保留下来,回答就可能跳过尚未理解的内容,或者重新采用已经修改过的方案
我因此开始关注一个具体问题:怎样把一段不断增长的交互,整理成下一轮真正需要的背景。本文沿着材料筛选、学习会话和群聊三个场景,讨论上下文与记忆如何配合
零、背景:上下文是有限的稀缺资源
1. 能放进去,还需要能被有效使用
上下文是模型在本轮调用中能够使用的信息,可能包括应用规则、当前问题、历史消息、检索材料和工具结果。完整历史可以保存在应用中,但本轮上下文仍然需要经过选择
Anthropic 在上下文工程文章中讨论了 context rot,通常译为“上下文腐化”:上下文变长时,模型在一些任务中的信息召回和推理表现可能下降。程度与模型、任务及材料组织有关,不能据此推导出一个统一的失效阈值,也不能认为每增加一句话都会让回答变差。Anthropic:上下文工程
对学习聊天来说,问题往往很具体:早期的 Java 示例、后来确定的 Python 路线、已经修复的报错和当前的追问,都可能留在同一份历史里。它们占用篇幅,却不具有相同的有效性
因此,我把上下文视为有限的稀缺资源。窗口长度决定能够容纳多少材料,成本和延迟影响处理方式,而材料是否相关、是否过期,则影响这一轮能否得到合适的回答
2. 从收集材料到形成输入
Hello-Agents 第九章用 Gather、Select、Structure、Compress 组织上下文构建过程。它提供了一套便于理解的工程顺序,具体筛选规则、预算和压缩方法仍需按任务设计。Hello-Agents:上下文工程
| 环节 | 处理什么 | 学习聊天中的例子 |
|---|---|---|
| Gather:收集 | 找到可能有用的材料 | 当前问题、会话摘要、近期问答、相关资料 |
| Select:筛选 | 判断相关性与有效性 | 保留最新的 Python 要求,排除已经失效的旧方案 |
| Structure:组织 | 让不同职责的信息各就其位 | 应用规则与参考材料分开,保留来源和未确认状态 |
| Compress:压缩 | 在预算内保留必要含义 | 较早问答形成摘要,近期细节继续保留原文 |
这里需要注意两个条件。筛选之后,一些材料可以继续留在外部,等后续任务需要时再取回;压缩也可能遗漏信息,重要结论应能回到原始记录核对。普通对话和检索材料进入输入后,仍然只是参考数据,不能因此改变应用权限或任务边界
下面的动画展示材料如何经过这几个环节。观察哪些内容被选中、规则和数据如何分区,以及超出预算时哪些内容被整理,能比只看一份最终输入更容易理解这个过程
向下滚动推进执行,向上滚动回看,停下即可观察当前步骤
上下文工程需要持续判断:本轮需要什么,哪些信息仍然有效,以及哪些内容可以留在外部、按需取回
一、以学习聊天为例:turns、summary 与 session
1. 三个结构分别保留什么
《Agent开发指南》中的教学示例,把偏好、摘要和近期问答分开保存。这里的 turns、summary 和 session 是示例采用的组织名称,不是所有框架都必须使用的标准字段
| 结构 | 示例中的内容 | 解决的问题 |
|---|---|---|
turns |
每轮的 user 和 assistant 文本 |
保留刚发生的提问、解释与追问细节 |
summary |
goal、facts、unresolved |
保留较早交互中的目标、必要事实和未解决问题 |
session |
style、summary、turns |
组织当前会话的状态,便于读取、更新和接续 |
本文把一次用户输入及其对应的回复过程称为一轮。教学示例只记录用户文本和最终回答;工具型 Agent 的一轮内部,还可能发生多次模型调用、工具执行与结果返回
以学习聊天为例,摘要可以记录“通过 Python 学习 Agent 开发”这一目标,并保留“需要解释语法和框架概念、提供参考资料”的要求。对于工具调用、LangGraph 和 RAG 等已经讨论过的主题,还应记录哪些地方仍需补讲
“已讨论”和“已掌握”必须分开。模型讲过一段代码,只能说明出现过相关内容;能否独立解释和修改,还需要练习或运行结果支持。把前者压成后者,会让后续学习安排建立在错误的进度上
2. 摘要怎样参与下一轮
指南采用了一个容易观察的教学策略:历史超过六轮时,整理较早的部分,保留最近三轮原文。假设当前已有七轮,就把前四轮纳入摘要更新,后三轮继续保留细节
更新需要先成功,再替换活动历史。摘要失败时保留原记录,避免一次失败导致上下文丢失。教学代码为简化而压缩活动状态;真实应用如果需要审计或回查,还应单独归档原始记录
接到“继续,下一部分”之后,程序再组合应用规则、讲解偏好、较早摘要、最近问答和当前问题。这样,短指令就能接上此前的目标和进度;如果近期记录显示基础语法还没讲清楚,就应当先处理这个未解决问题
下面的动画按这段教学逻辑重组一次输入。它展示摘要、近期原文和当前问题如何进入上下文,属于流程示意,不是聊天产品内部实现或一次真实 API 请求的回放
向下滚动推进执行,向上滚动回看,停下即可观察当前步骤
六轮与三轮是教学参数,实际应用还要结合 token 数、工具结果长度和任务边界决定。工具调用记录也不能按普通文本随意裁剪:仍然必要的调用与结果需要保留正确关系,尚未完成的交互应在合适边界处理
API 提供的续接方式也需要单独理解。previous_response_id 可以串联响应,Conversations 可以组织服务端会话条目,它们与应用自行维护用户画像、学习进度承担不同职责。OpenAI Docs:会话状态
应用生成的可读摘要,与 API 的 compaction 也有不同使用方式。后者可能包含不透明的压缩条目,独立压缩返回的窗口应按官方约定用于后续请求,不能只挑出一段可读文本替代它。OpenAI Docs:Compaction
turns 留下近期细节,summary 保留能够接续的含义,session 组织当前状态;摘要还需要保留纠正、来源和未解决问题
二、把记忆术语放回各自的维度
1. 相似的问题,可以有不同的分类方法
短期记忆、长期记忆、外部记忆、用户画像、会话状态和检索,常常出现在同一篇技术介绍里。它们大多围绕信息怎样写入、更新和取回展开,但关注的维度不同
LangChain 主要按召回范围区分短期与长期记忆;Hello-Agents 第八章则讨论工作、情景、语义和感知记忆。前者关注信息在哪些会话中使用,后者关注记忆的功能和内容,不能直接逐项对应。LangChain:Memory overview、Hello-Agents:记忆与检索
| 概念 | 主要关注 | 在本文场景中的例子 |
|---|---|---|
| 短期记忆 | 当前 thread 如何连续推进 | 近期问答、任务状态、会话摘要 |
| 长期记忆 | 哪些信息需要跨会话使用 | 稳定的讲解偏好、持续有效的项目约定 |
| 外部记忆 | 信息在窗口外怎样保存和取回 | 文件、数据库及其检索机制 |
| 用户画像 | 关于某个人的信息怎样组织 | 背景、偏好、来源与明确约定 |
这里的“外部记忆”是本文对实现位置的描述,不是 LangChain 分类中与短期、长期并列的第三类。用户画像则是一种内容组织方式:其中的某些条目可以长期有效,也可能只适用于某个场景
例如,“希望结合已有后端经验解释 Python”可以是一条画像信息。允许跨会话使用时,它发挥长期记忆的作用;保存在数据库里时,它依靠外部存储;只有本轮需要并且允许使用时,它才进入当前上下文
2. 落库、恢复和检索解决不同问题
短期记忆也可以持久化到数据库。判断它是否“短期”,关键在于服务哪个会话范围,不能只看进程退出后数据是否仍在
LangGraph 的 checkpointer 保存 thread 的图状态检查点,store 用于保存跨 thread 使用的应用数据。两者可以配合,但 thread_id 只是状态标识,不能代替用户身份或访问权限。LangGraph:Persistence
语义记忆与语义检索也需要区分:前者描述保存的事实与知识,后者描述寻找相关内容的方法。向量检索可以帮助找到记录,但“找得像”并不能自动说明记录属于谁、是否仍然有效或是否允许披露
对于持续推进的项目,还要留下“做了什么、如何验证、下一步是什么”。Anthropic 的长任务 Harness 文章采用进度文件、功能清单和 Git 记录帮助新会话接续工作,说明项目进度需要可检查的产物支持。Anthropic:长任务 Harness
因此,在选择记忆组件前,我更愿意先写清需求:这条信息属于谁,服务哪段任务,需要保留多久,更新时覆盖什么,以及下一次按什么条件取回
三、走进群聊:一个 Agent 如何整理多个人的上下文
1. 同一段摘要里,要保留人物与任务的归属
假设一个允许 Bot 整理项目进展的技术群里,有下面两条消息。人物与情境均为虚构,用来讨论设计:
A:这次请用 Python 给我解释,鉴权模块还有问题
B:我习惯 Java,订单模块的测试已经通过
如果把它们整理为“大家使用 Python,项目测试通过”,就同时丢掉了人物、任务范围和确认条件。A 的本次要求被扩展成所有人的稳定偏好,订单模块的局部结果也被扩展成整个项目的状态
更合适的组织方式,是让偏好归属到人,让进展归属到具体任务,并保留消息来源、时间和可见范围。回答 A 的鉴权问题时,再选择与 A 和当前话题有关的记录;回答群内项目进展时,则按各模块分别保留结论
2. 先限定可用范围,再查找相关内容
下面是我面向群聊场景提出的应用设计规则。身份应来自应用能够核验的登录或消息事件,回复目的地也需要明确;用户在文本里自称某个人,不能据此获得另一个人的历史或权限
确认身份与目的地后,先限定允许使用的资料范围,再在范围内检索、检查有效性并组装上下文。其他群和私聊里的内容,即使语义相近,也不能只因为“相关”就进入当前群的回答
namespace 可以帮助组织用户与群的数据,但访问控制仍需由应用实现。摘要、画像和检索索引也要继承来源的可见范围,不能在生成派生内容时丢掉边界。LangGraph:Stores
下面的动画展示这条设计路径。注意被排除的资料应当在召回范围之外;本轮成功对话的写回发生在回复送达之后,并附带所属对象、范围和来源。这是虚构数据的流程演示,不代表已经实现并验证了完整的群聊记忆系统
向下滚动推进执行,向上滚动回看,停下即可观察当前步骤
3. 分类存储,还要处理修正与失效
群消息可能是稳定偏好、一次性要求、临时进展、引用他人的话,也可能是尚未确认的猜测。分类过粗容易把不同性质的信息混在一起;分类过细又可能留下大量重复和冲突记录
一条候选记忆至少需要回答这些问题:
| 需要记录的条件 | 用途 |
|---|---|
| 对象与话题 | 区分是谁的偏好、哪个模块的进度 |
| 来源与确认状态 | 区分本人明确表达、工具结果、转述和推测 |
| 时间与版本 | 判断结论是否仍适用于当前任务 |
| 可见范围 | 决定哪些人、哪些回复目的地允许使用 |
| 更新与失效关系 | 说明旧记录被什么替换,何时需要重新确认 |
“这次请用 Python”不一定值得写成长期偏好;“测试通过”也应关联模块和版本。其他成员对 A 的评价,应当保留为带来源的陈述,不能未经确认就更新 A 的画像
后续修正同样重要。原始记录撤销或过期后,需要明确相关摘要、画像和索引怎样更新。本轮问答只有在确认回复成功后,才能按成功对话写回;用户输入和失败日志则可以依照各自的记录策略保存
可以用三组输入检查设计:让 A 修改偏好,再分别询问 A 和 B;在两个群中提出相同问题,检查资料范围;撤销一条“测试通过”的记录,再查询当前进度。检查对象应包括实际召回的材料和最终回答,不能只看程序是否运行结束
语义整理需要保留一条信息的含义与边界:它属于谁、依据什么、在什么条件下成立,以及什么时候适合再次使用
参考资料
- Anthropic:Effective context engineering for AI agents,理解上下文预算、压缩与按需取用
- Hello-Agents 第八章:记忆与检索,比较记忆功能与检索方式
- Hello-Agents 第九章:上下文工程,查看材料收集、筛选、组织与压缩的实现思路
- LangChain:Memory overview,理解短期、长期与用户画像的关系
- LangGraph:Persistence与 Stores,区分状态检查点、跨会话数据与存储组织
- OpenAI Docs:Conversation state与 Compaction,核对会话续接与 API 压缩的使用约定
- Anthropic:Effective harnesses for long-running agents,了解跨会话项目进展怎样留下可检查的记录