在大模型应用里,会话历史并不是越小越好,但也不是越大越有价值。一条普通对话里可能包含大量诸如‘好的’、‘明白了’、‘稍等’之类的填充内容,真正影响后续回答质量的是用户偏好、任务约束和已确认的事实。如果把这些原始消息全部长期保存,存储空间会随会话数和轮次线性膨胀。记忆压缩解决的正是这个问题:它不追求把所有内容原样保留,而是把低密度文本转换成高密度表示,在语义不严重丢失的前提下减少磁盘、缓存和上下文窗口的占用。

从实现角度看,压缩对象通常是消息列表,压缩产物可以是摘要文本、结构化事实记录或向量片段。三者对存储空间的削减幅度不同,对检索和推理的影响也不同。后面的内容会分别讨论这些路径,并给出可量化的对比。
记忆压缩为什么能减少存储空间
原始对话的存储开销来自多个方面。以JSON格式保存的消息为例,每条消息除了正文内容,还包含角色字段、时间戳、token计数元数据等。十轮对话可能只有几百个有效汉字,但序列化后的字节数可能是有效信息的数倍。压缩后的摘要文本或事实记录去掉了元数据和寒暄,自然占用更小。
更重要的是,摘要和事实提取可以把多轮消息合并成一条或几条记录。比如用户分三次确认了预算、截止时间和目标平台,压缩后可以合并为‘预算五万元,截止时间下周五,目标平台为Web端’。原始三条消息的存储和后续检索成本都高于这一条结构化记录。压缩率通常可以达到百分之六十到百分之九十,具体取决于原始对话的冗余程度。
但压缩不是无损操作。摘要模型可能忽略语气、隐含条件或低频但重要的事实。因此记忆压缩需要和近期消息保留策略配合:最近几轮完整保留,较早的消息才进入压缩池。这样既能降低存储,又不会让当前对话失真。
import json
raw_messages = [
{"role": "user", "content": "好的,那我们继续。"},
{"role": "assistant", "content": "可以,您希望怎么调整?"},
{"role": "user", "content": "预算改成五万,截止时间下周五,平台还是Web端。"},
{"role": "assistant", "content": "收到,我记录一下。"},
]
raw_size = len(json.dumps(raw_messages, ensure_ascii=False).encode("utf-8"))
compressed = {
"summary": "用户确认预算五万元,截止下周五,目标平台Web端。",
"open_questions": []
}
compressed_size = len(json.dumps(compressed, ensure_ascii=False).encode("utf-8"))
print(f"原始大小: {raw_size} 字节")
print(f"压缩后: {compressed_size} 字节")
print(f"压缩率: {(1 - compressed_size / raw_size) * 100:.2f}%")
三种主流的记忆压缩实现方式
摘要式压缩是最直接的方案。当消息数量超过阈值时,将较早的消息批量发送给语言模型,要求它生成一份紧凑摘要。摘要可以只保留当前任务相关信息,也可以包括用户长期偏好。摘要生成后,原始消息被删除或归档到冷存储,上下文中只保留摘要和最近消息。优点是实现简单,缺点是摘要质量依赖模型能力,且每次压缩都需要一次额外的模型调用。
为了控制成本,摘要可以分层次进行。第一层提取最近一小段对话的局部摘要,第二层再把多个局部摘要合并成全局摘要。这样即使后续对话很长,也不需要一次性处理全部历史。局部摘要可以保存在内存或轻量数据库中,形成摘要链。
与摘要不同,关键事实提取输出的是结构化键值对或事实列表。比如用户说‘我不吃香菜’、‘项目交付日期是下周三’、‘登录接口超时时间设为八秒’,这些信息会被抽取为偏好、时间约束、系统参数等字段。结构化记录占用的存储更小,后续查询也更明确。实现时可以用大模型直接抽取,也可以结合规则和命名实体识别。抽取结果通常写入SQLite或Redis,读取时按需加载。
这种方式的压缩率通常高于摘要,因为彻底去掉了叙述性文字。但如果抽取逻辑不完善,某些隐含信息可能无法被归入预设字段。解决办法是保留一份低置信度事实的原始片段,或者在抽取失败时退回到摘要方式。
向量记忆库把历史消息切成块,通过嵌入模型转换为向量,存储到向量数据库中。需要时根据当前问题检索Top-K相关块,只把检索结果放入上下文。它并不直接压缩单条消息,而是通过筛选相关记忆来减少每次请求携带的数据量,同时也降低了常驻存储的压力,因为不相关的内容不会被频繁读取。向量库本身会占用磁盘,但通过量化、降维和增量更新可以进一步压缩索引体积。
向量记忆适合知识分散、查询目标不固定的场景。它的缺点是检索质量受嵌入模型和分块策略影响,且需要维护额外的索引服务。如果用户问题与历史消息的语义相似度不高,可能漏掉关键记忆。
def compress_history(messages, max_tokens=2000):
recent = []
old = []
token_count = 0
for msg in reversed(messages):
estimate = len(msg["content"]) // 2
if token_count + estimate <= max_tokens:
recent.insert(0, msg)
token_count += estimate
else:
old.append(msg)
old.reverse()
if not old:
return recent, None
summary_prompt = "请把下面的对话压缩为简洁摘要,只保留关键事实和约束:\n" + "\n".join(
f'{m["role"]}: {m["content"]}' for m in old
)
summary = call_llm(summary_prompt)
return [{"role": "system", "content": f"历史摘要:{summary}"}] + recent, summary
存储空间对比与多级压缩策略
为了判断记忆压缩是否值得引入,可以把存储成本拆成三部分:原始消息日志、压缩产物、检索索引。原始消息日志通常最大,但可以定期归档到对象存储;压缩产物体积小,常驻内存或数据库;向量索引取决于分块数量和维度,属于固定开销。下表给出了一个十轮客服对话场景的大致体积估算。
| 存储形式 | 记录数 | 约占用 | 说明 |
|---|---|---|---|
| 原始JSON消息 | 20条 | 约14KB | 含角色、时间戳和正文 |
| 摘要文本 | 1条 | 约0.6KB | 仅保留关键事实 |
| 结构化事实 | 6条 | 约0.3KB | 键值对形式 |
| 向量索引 | 20块 | 约80KB | 含原始块与向量 |
直接对全部历史做一次压缩虽然简单,但后期对话可能推翻早期结论。更好的做法是建立多级摘要链:一级摘要保存最近五十轮内的局部压缩,二级摘要保存更早的全局压缩。当新的局部摘要生成后,可以异步触发全局摘要的更新。这样既能保证压缩率,又不会因为一次压缩失败而丢失全部历史。多级链还能支持回看,比如用户询问‘我们之前定的截止时间是什么’,系统可以先查事实库,查不到再翻二级摘要。
触发压缩的阈值要根据业务调整。客服机器人可能每二十轮压缩一次,教育辅导应用可能每十轮就压缩,因为知识更新更快。阈值的单位可以是消息数量、token数或时间间隔。建议同时设置多个条件,例如消息数超过四十条且距上次压缩超过十分钟,避免频繁调用语言模型。
def size_in_bytes(text: str) -> int:
return len(text.encode("utf-8"))
raw_text = open("conversation_history.json", encoding="utf-8").read()
compressed_text = "用户确认预算五万元,截止时间下周五,目标平台Web端。"
raw_bytes = size_in_bytes(raw_text)
compressed_bytes = size_in_bytes(compressed_text)
ratio = (1 - compressed_bytes / raw_bytes) * 100
print(f"原始占用 {raw_bytes} 字节")
print(f"压缩后 {compressed_bytes} 字节")
print(f"压缩率 {ratio:.2f}%")
生产环境中的压缩调优与避坑
一个常见的错误是把system消息也交给摘要模型处理。系统指令通常包含角色设定、输出格式和安全约束,这些内容一旦被摘要化,可能会导致模型行为偏移。压缩范围应该只包括较早的user和assistant消息,system消息始终保留完整内容。最近几轮消息也不应进入压缩池,因为当前上下文需要完整语义。一般建议最近三到五轮保留原文,更早的内容才参与压缩。
另一个容易忽略的点是压缩后的存储更新。摘要和事实库写入后,旧消息不能立即物理删除,否则后续审计或人工排查会缺少依据。可以先将旧消息移动到归档表或冷存储,保留一段时间后再清理。这样既控制了热存储占用,又保留了追溯能力。
摘要式压缩每次都会消耗模型token,如果压缩频率过高,节省的存储成本可能被推理成本抵消。可以设置压缩最小收益阈值:当预计压缩后节省的存储大于某个字节数时才执行压缩。还可以用较小规模的模型做提取和摘要,例如使用本地部署的七到十亿参数模型处理离线压缩任务,在线请求只读取压缩结果。这样既能控制成本,又能保持响应速度。
压缩质量需要定期评估。可以从生产日志中随机抽取若干会话,人工检查压缩后是否遗漏关键事实,或者用一个测试集对比压缩前后模型回答的准确率。如果准确率明显下降,说明摘要模型丢失了重要信息,需要调整摘要提示词或降低压缩比例。
记忆压缩不是简单地删掉旧消息,而是一套包含摘要、事实提取、向量检索和多级更新机制的信息管理策略。它的目标是在存储空间、上下文长度和回答质量之间找到平衡点。通过选择合适的压缩触发条件、保留system指令和最近消息、分层维护摘要链,可以把长会话场景下的存储占用降下来,同时避免明显的语义损失。