这篇重点学什么
本篇重点:记录记忆中的事实、出处和权限;以完整记忆组为单位选取内容,并检查是否超出上下文预算。
开始读正文 ↓学过相近内容?查看哪些可以跳过
两条路线有些基础相同。已经学会的部分可以跳过,另一条路线只用来补你当前需要的内容。
检索与记忆
共同基础检索、证据和上下文选择相通;使用检索器与训练检索器有不同验收目标。
另一条路线补充什么?
分别为查询和文档生成向量(双编码器),用负样本和对比损失学习区分相关资料,再检查内容选择策略及对新数据的效果。
读完这篇,你能做什么
能压缩长对话并核对关键事实出处,验证账号隔离和实际存储的消息类型。
学习理由、准备要求与知识点
为什么学它
每次模型调用能读的内容有限。把完整历史、当前任务信息和长期偏好分开保存,能减少压缩丢条件或混入其他用户资料的问题。
先读这些内容
知识点清单
- 完整历史与工作记忆
- 长期偏好
- 消息条数与 Token 预算
- 摘要有损性
- 来源消息标识
- 结构化关键状态
- 会话身份隔离
- 存储工具消息的限制
运行前先确认完整算法使用 Java 17 标准库;JDK 源码基线为 OpenJDK 17 GA。示例是内存中的机制模型,外部服务接入需要另外配置和验证。
01先弄懂它是什么
对话越来越长时,既要接得上前文,也要避免把所有旧内容反复发送。完整历史用于回看,工作记忆是当前调用实际使用的内容,长期记忆则保存有来源、经过确认的偏好或事实。全部历史都塞进去,会浪费用量,也可能让旧要求干扰当前任务;只剩一份摘要,又可能丢掉关键条件。
摘要能缩短文字,但不能保证每个细节都留下。比如“不要删除”里的否定、某个版本号、尚未完成的操作,都可能在概括时丢失。因此任务状态、权限范围、确认记录和操作 ID 应单独保存成字段,不能全靠一段摘要。压缩后的内容也应能找到原消息或原文,发现疑点时才有办法核对。
消息条数上限与Token预算不同。一条巨大工具结果可能超过许多短对话总量,完整工具请求与结果还应成组保留。选择记忆时先留必须状态,再分配可选信息预算;本篇用整数背包说明完整证据组的选择,而不把价值分数当作客观语义真值。
文中这几个词,先认一认
- 工作记忆
- 当前这次任务或模型调用能直接读取的一组有限信息。
- 有损摘要
- 缩短文字时可能舍去部分细节的概括,需要保留来源,并把关键条件另存为明确字段。
- 不可拆组
- 需要一起保留才有完整含义的信息,例如一条工具请求和它对应的结果。
02看看它怎么工作
把可选记忆组抽象为成本和价值。成本是外部计数器给出的Token预算,价值是应用为练习设定的非负整数。预算五、组成本三和二、价值四和三,可以同时保留得七;若另一组成本五价值六,最优仍是前两组。每组只能选择一次,保持工具交换完整。
可以画一张表逐组决定选不选。dp[i][b] 表示只考虑前 i 组、预算最多 b 时,能得到的最大总价值。对第 i 组,先看“不选它”的结果;若预算够,再看“给它留出成本后,前 i-1 组还能贡献多少,加上它本身的价值”,取较大者。两种情况都读取上一行,保证当前这一组最多选一次。最后从表的末尾倒着检查哪些选择形成了结果,就能找回实际要放入上下文的组 ID。
算法仅对给定价值函数最优,不保证回答质量最优。真实记忆价值依赖当前问题、来源可信度、时效和组合关系,可能不可相加。任何选择后还需检查必须状态、来源和最终Token;被选摘要也不能获得高于原始资料的可信等级。
正在加载流程图…
查看流程图源码(Mermaid)
flowchart LR
H["完整历史与来源"] --> K["结构化必须状态"]
H --> G["可选完整记忆组"]
K --> B["剩余 Token 预算"]
G --> D["0/1选择或摘要"]
B --> D
D --> V["关键事实与最终计量核对"]按顺序看,每一步做了什么
- 分开保存完整历史、当前工作记忆和已确认长期事实。
- 将权限、操作和未完成任务作为结构化必须状态。
- 把可选资料按完整消息组计量并估值。
- 在剩余预算内选择或摘要,保留来源与不确定性。
- 对最终上下文复算并用关键事实保持测试验收。
03对照实现代码
OpenJDK 17 GA,jdk-17+35 · BitSet.set / get / clone
下方为原创 Java 教学片段,调用已核验的 JDK API 说明局部机制;不是该类原样源码,也不实现神经网络。完整程序见算法模块。
BitSet selected = new BitSet(items.size());
selected.set(index); // 一个比特表示一个完整记忆组,不代表自然语言内容。
BitSet snapshot = (BitSet) selected.clone(); // 保存独立选择状态,避免后续修改污染快照。
if (snapshot.get(index)) includeWholeGroup(items.get(index));
// 必须状态与来源另行保存,位图不压缩原文语义。BitSet用位表示布尔选择状态,set和get操作对应索引。它适合记录“是否选择”,但不保存文本内容、版本和来源,不能被称为语义记忆压缩器。
clone得到独立位集合,避免回溯或后续选择修改已保存方案。完整算法用DP表恢复ID列表,展示选择机制;位图是等价的方案表示选择。
Spring AI的消息窗口与存储实现有各自保留政策,不应仅凭框架接口名推断全部工具消息会被持久保存。需要按固定版本核对仓库过滤行为,并为审计另建可靠记录。
JDK源码桥梁覆盖选择状态管理,不实现摘要模型。摘要是否保留事实必须用原消息对照测试,不能通过一个位集合或消息条数证明。
代码先调用谁,再调用谁
- 结构化必须状态先占预算
- 可选完整组生成成本与价值
- DP计算最佳组合
BitSet或ID列表保存选择结果- 恢复来源并构造最终上下文
04动手写一个例子
完整记忆组的0/1预算选择
输入唯一ID、正整数成本、非负价值和预算,输出价值最大的完整组集合;同价值时优先沿用已有方案。预算上限和表大小显式限制。Java17 Main.java运行自测。
- 输入 1
组a成本3价值4,b成本2价值3,预算5- 输出 1
价值7,a/b
- 输入 2
预算0- 输出 2
价值0,空
dp每行只使用上一行,所以每个组最多取一次;不满足预算的组只能跳过。价值相等时不替换,给方案恢复一个固定规则。
从最后一格回溯,如果本行值不同于上一行,则该组被选中,减去成本继续;相同时按约定跳过。恢复后反转ID列表,保持原输入顺序。
预算数值进入表维度,因此O(nB)是伪多项式复杂度。预算用Token数量时可能很大,不能忽略内存;本程序限制表规模,正式系统可使用近似估值或稀疏状态。
为什么这样写是对的dp[i][b]是前i个完整组在预算b内最大可加价值,每组至多一次;回溯只从确实改善价值的转移选组,所选总成本不超过预算。
n为组数,B为整数预算,完整表用于恢复方案;这是对预算数值线性的伪多项式算法,不代表输入位数意义下多项式。
保存为 Main.java,运行 javac --release 17 -encoding UTF-8 Main.java,再运行 java Main。程序包含固定输入自测,断言失败时会抛出错误。
import java.util.*;
public class Main {
record Item(String id,int cost,long value){} record Choice(long value,List<String> ids){}
static Choice choose(List<Item> items,int budget){
if(budget<0||budget>10000||(long)(items.size()+1)*(budget+1)>2000000)throw new IllegalArgumentException("table budget");
Set<String> ids=new HashSet<>();for(Item x:items)if(x.id()==null||!ids.add(x.id())||x.cost()<=0||x.value()<0)throw new IllegalArgumentException("item");
long[][] dp=new long[items.size()+1][budget+1];
for(int i=1;i<=items.size();i++){Item x=items.get(i-1);
for(int b=0;b<=budget;b++){
dp[i][b]=dp[i-1][b]; // 不选当前组,先沿用旧方案。
if(x.cost()<=b){long take=Math.addExact(dp[i-1][b-x.cost()],x.value());if(take>dp[i][b])dp[i][b]=take;} // 只读上一行,禁止重复选择。
}
}
List<String> selected=new ArrayList<>();int b=budget;
for(int i=items.size();i>0;i--)if(dp[i][b]!=dp[i-1][b]){Item x=items.get(i-1);selected.add(x.id());b-=x.cost();}
Collections.reverse(selected);return new Choice(dp[items.size()][budget],selected); // 恢复完整组ID。
}
static void eq(Choice e,Choice a){if(!e.equals(a))throw new AssertionError(a);}
public static void main(String[] args){
List<Item> x=List.of(new Item("a",3,4),new Item("b",2,3),new Item("c",5,6));
eq(new Choice(7,List.of("a","b")),choose(x,5));
eq(new Choice(0,List.of()),choose(x,0));eq(new Choice(0,List.of()),choose(List.of(),5));
eq(new Choice(4,List.of("a")),choose(List.of(new Item("a",2,4),new Item("b",2,4)),2));
eq(new Choice(0,List.of()),choose(List.of(new Item("large",9,100)),2));
try{choose(List.of(new Item("bad",0,1)),2);throw new AssertionError();}catch(IllegalArgumentException expected){}
System.out.println("PASS 6 tests");
}
}这些特殊情况也要试一试
- 成本必须为正,预算为零合法。
- 价值相同按固定不替换规则,避免方案随遍历细节漂移。
- 巨大预算触发表规模限制,不能失控申请内存。
进阶选读 · 后续再读:相关性、多样性与互斥记忆
场景两段记忆高度重复,简单相加价值高估收益;新旧偏好互相冲突。
做法把来源时效、互斥组与冗余惩罚纳入约束,先解决事实冲突再选上下文;必要时用贪心近似或整数规划,并人工核对关键状态。
复杂度带一般交互和约束的组合优化可能NP-hard,精确搜索最坏指数级;背包模型不能覆盖所有语义关系。
好处与代价复杂评分更贴近实际却难校准,必须项结构化与简单可审计选择常是更可靠基线。
05项目里怎么用
压缩测试使用关键事实清单:用户明确否定的事项、当前版本、操作ID、未完成步骤与权限范围。把摘要与原消息逐项对照,发现遗漏就调整结构化字段或摘要策略。只看摘要读起来通顺无法证明关键状态保留。
完整历史与工作记忆可以采用不同保留期限和访问范围,避免把所有原始内容永久复制到长期存储。摘要和向量索引同样可能含敏感信息,用户撤回或资料过期时需要同步处理派生数据,不能只删除可见聊天文本。
会话身份应使用可信用户和会话组合,跨会话偏好须有明确确认与来源。临时要求只对当前任务生效时,不应被自动提升为长期习惯。模型提炼的推测先标记未确认,不能与用户明确声明的事实混在一起。
容易踩的坑
- 按消息条数估算Token而不看工具结果大小。
- 摘要覆盖权限、确认与操作ID等关键结构状态。
- 拆开工具请求和结果以便凑预算。
- 把背包价值最优称为答案质量最优。
几种做法,怎么选
表格有四列,窄屏可左右滑动查看代价和适用条件。
| 方案 | 优势 | 代价与限制 | 适用条件 |
|---|---|---|---|
| 完整历史 | 追溯信息充分 | 存储与上下文成本高 | 审计与故障复现 |
| 结构化关键状态 | 准确保留控制事实 | 需要字段设计与迁移 | 权限、操作和任务进度 |
| 来源摘要与选择 | 降低当前输入成本 | 有损并依赖估值 | 长期对话工作上下文 |
06检验是否读懂了
先自己想、自己写,再展开答案核对。场景题是假设的练习情境;遇到还没学过的内容,可以做完基础后再回来。
先用自己的话解释
01工作记忆与长期记忆区别是什么?
展开参考答案与推导
工作记忆服务当前调用预算,长期记忆保存经确认且有来源的持续事实;不能把当前上下文所有内容自动长期化。
02为什么背包必须读取上一行?
展开参考答案与推导
上一行只包含前i-1组,保证当前组至多使用一次;错误地正向读写同一行会变成可重复选择。
03摘要流畅能证明压缩正确吗?
展开参考答案与推导
不能,需核对否定、数字、版本和未完成操作等关键事实,并保留原始来源。
换个情况,还会处理吗
01摘要把“不要发送”改成“发送”,怎样防止?
展开参考答案与推导
把禁止动作和确认状态结构化保存,摘要不能覆盖控制字段;加入否定事实保持测试并阻止未确认执行。
02一条工具消息超窗口,按条数截取为何无效?
展开参考答案与推导
消息长度差异很大,需实际Token计量与完整组策略;对超长结果做受控分页或摘要。
03框架保存了聊天但审计缺工具结果,怎么办?
展开参考答案与推导
核对具体仓库版本的过滤政策,建立独立运行事件审计;ChatMemory接口名称不保证完整轨迹。
写代码试一试
01同一高价值组能选两次吗?
- 输入 / 条件
成本2价值4,预算4- 输出 / 预期
只选一次价值4
提示 这是0/1而非完全背包。
展开参考答案与推导
转移只读上一行,当前组没有机会再次贡献;测试可以发现错误的一维正向循环。
02同价值方案如何稳定?
- 输入 / 条件
a和b均成本2价值4,预算2- 输出 / 预期
选a
提示 相等时不替换。
展开参考答案与推导
先处理a得到四,处理b候选也是四,不更新,回溯按约定保留先前方案。
03O(nB)为什么称伪多项式?
- 输入 / 条件
B用二进制输入- 输出 / 预期
复杂度依B数值而非log B
提示 区分数值与编码长度。
展开参考答案与推导
预算翻倍会使表规模翻倍,但输入只多约一位;因此不能称为一般背包的输入长度多项式算法。
读完之后,独立完成
课后作业:自己做一遍
本篇共 12 道作业,先完成 9 道基础与综合任务,再按前置知识挑战进阶题。其中包含 2 道思维拓展,试着换个条件,看看原来的办法是否还管用。不附参考答案。
能压缩长对话并核对关键事实出处,验证账号隔离和实际存储的消息类型。
本篇重点加练
摘要可能丢失条件,关键结构化状态需要独立维护并在压缩后核验。
- 摘要有损性
- 结构化关键状态
重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。
先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。
基础必做
- 作业 01原理问答
将“用户偏好中文、今天想练集合、工具报错日志、去年聊天记录”分配到你设计的记忆层,并说明保存依据与生命周期。
- 提交内容
- 一份独立书面回答或标注图,包含自造例子、概念边界及判断依据。
- 怎样算完成
- 逐项回应题目要求,区分事实、假设与个人判断。
- 能用自己的例子解释适用条件,记录尚未弄懂的点。
练到的知识点完整历史与工作记忆 · 长期偏好
- 作业 02代码推演
给六条长短不同的消息设置总预算,分别按条数截断和摘要压缩;比较被保留的信息,逐项列出不可默默丢弃的关键事实。
- 提交内容
- 手工推演记录,写明输入约定、中间状态、最终判断;可运行时附程序对照记录。
- 怎样算完成
- 推演包含中间过程,数据形状、单位、顺序或状态前后一致。
- 主动检查至少一个边界或异常条件,能解释手算与运行差异。
练到的知识点消息条数与 Token 预算 · 摘要有损性
- 作业 03原理问答
为“用户只在周末允许自动调整计划”设计文字摘要与结构化状态的并行表达,指出需要显式保存的限定条件及冲突检查项。
- 提交内容
- 概念或合同说明、关系图与自造案例,明确事实、假设和适用条件。
- 怎样算完成
- 关键概念有明确边界,能用案例检验声明。
- 未获证据支持的假设单独标注。
练到的知识点摘要有损性 · 结构化关键状态
综合应用
- 作业 04面试场景
摘要写“用户已确认删除”,原消息却是“先不要删除”。设计可回到来源的结构和校验过程,并说明状态冲突如何呈现给操作者。
- 提交内容
- 场景分析与决策记录,包含信息缺口、约束、备选方案和验证计划。
- 怎样算完成
- 方案回应给定约束,列出至少一种替代方案及代价。
- 每项关键结论说明所需证据,不能核实的部分明确标注。
练到的知识点来源消息标识 · 结构化关键状态
- 作业 05编程实践
用两个用户和两个会话实现记忆读取测试,加入未配对工具消息、过期工具结果与错误会话ID;保存输入、读取记录和异常分类。
- 提交内容
- 可复现的最小程序或实验材料、自写测试、环境说明与运行记录;题目允许纸面实验时提交完整案例表。
- 怎样算完成
- 提交物按说明可以重现;测试覆盖正常、边界与失败场景。
- 运行证据与断言能核验声明的行为,注明模拟与真实执行范围。
练到的知识点会话身份隔离 · 存储工具消息的限制
- 作业 06排错实验
连续压缩同一段对话三次,再与原始消息比较,定义可量化的遗漏与错误归因标准,提交至少两个自行发现的失真样本。
- 提交内容
- 最小故障输入、复现步骤、观测记录、定位依据及回归用例。
- 怎样算完成
- 其他人可按记录复现故障,并区分现象与已验证原因。
- 定位过程有观测依据,回归用例可以再次暴露原问题。
练到的知识点完整历史与工作记忆 · 摘要有损性 · 来源消息标识
- 作业 07编程实践
实现一个纯本地记忆验收器,输入原消息、候选摘要和关键状态。自行定义三条业务不变量,用包含否定、条件和修订的对话检查压缩产物。
- 提交内容
- 可复现的小程序或题目允许的离线实验、自写测试、输入和运行记录。
- 怎样算完成
- 输入、环境和运行过程可重现,测试覆盖声明的重点条件。
- 验证目标由合同确定,模拟结果不冒充真实服务证据。
练到的知识点摘要有损性 · 结构化关键状态
- 作业 08排错实验
把同一对话按不同分段顺序生成手工摘要,再合并为上下文。比较关键状态是否一致,并记录何种信息丢失使后续无法恢复。
- 提交内容
- 最小故障序列、观测记录、原因核对与可再次触发原问题的测试。
- 怎样算完成
- 故障可以稳定重现,现象与已验证原因分开。
- 观测记录足以判断声明的合同是否被违反。
练到的知识点摘要有损性 · 结构化关键状态
- 作业 09面试场景
审计要求保留可追溯历史,但模型上下文仅有很小预算。提出查询历史、摘要缓存和关键状态的职责安排,并给出失效与重建用例。
- 提交内容
- 场景评审记录,包含约束、备选方案、未决信息和验证计划。
- 怎样算完成
- 方案回应题目约束,取舍能对应到可核验的事实。
- 给出失败或条件变化时的检查方式。
练到的知识点摘要有损性 · 结构化关键状态
进阶挑战 · 掌握前置后选做
- 作业 10设计评审
在已掌握持久化基础后,设计跨设备更新偏好的协议,给出并发冲突、用户撤回和会话隔离的验证计划。
- 提交内容
- 设计评审文档或对照实验报告,包含前置条件、方案取舍、验收证据与尚未验证的范围。
- 怎样算完成
- 比较使用一致条件与事先声明的标准,记录失败与未知结果。
- 说明方案的适用前提、未覆盖风险及后续验证方法。
练到的知识点长期偏好 · 结构化关键状态 · 会话身份隔离
思维拓展
先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。
- 作业 11思维拓展 · 反例构造
审阅“摘要覆盖全部关键词就足以保留用户意图”。构造关键词相同而条件或否定关系不同的两段对话,设计可区分它们的检查。
- 提交内容
- 具体反例、原主张的适用前提、复现或逻辑证明及结论边界。
- 怎样算完成
- 反例满足原主张所声称的前提,确实针对其结论。
- 说明一个反例能推翻什么、不能进一步证明什么。
练到的知识点摘要有损性 · 结构化关键状态
- 作业 12思维拓展 · 条件变化
如果后续任务从“给学习建议”变为“自动修改计划”,重新审查原记忆压缩政策;列出需要提高保证强度的状态及验证成本。
- 提交内容
- 变更前后条件表、重新评审的方案、检验用例与新增代价。
- 怎样算完成
- 只改变明确列出的条件,比较口径保持一致。
- 修改方案说明收益、代价与仍未验证的范围。
练到的知识点摘要有损性 · 结构化关键状态
完成后,再检验一次
给每题标记“独立完成 / 查阅后完成 / 尚未完成”。查过资料或没做出的题,记下卡在哪里。隔一天换一组输入再试,能解释结果和出错原因,再把这个知识点标为掌握。
继续阅读
先看能帮你完成当前练习的部分。例子跑通后,再读更深入的解释。
- 必读参考文档英文
Spring AI:Chat Memory (新标签页)↗
阅读
ChatMemory与完整历史的区别、会话标识和仓库存储限制,验证隔离及重载消息类型。 - 实践参考文档英文
LangGraph:Persistence (新标签页)↗
阅读 checkpointer 与 store 的职责、会话作用域和内存存储限制,验证重启后是否真的可恢复。
- 深入参考文档英文
PostgreSQL:数据约束 (新标签页)↗
阅读主键、唯一约束、外键与检查约束,用数据库约束验证重复事件和幂等记录。
- 深入参考文档英文
OpenJDK 17 BitSet 源码 (新标签页)↗
按指定版本定位
BitSet.set/ get / clone;对照源码分析理解实际调用边界。