传统软件系统的记忆是一张张数据表,字段明确、类型固定,程序按规则读写。而大语言模型的出现让记忆的形态发生了变化:模型本身理解自然语言,那么记忆为什么不能就是一段段文字?用语言描述记忆,本质上是把"模型能读懂的存储"作为系统的一等公民,让记忆的写入、检索和更新都通过文本完成。这种方式的核心吸引力在于透明——每一条记忆都是人能直接看懂的话,出问题时可以逐条审计,而不是面对一堆谁也解释不了的向量坐标。下面从原理、方案设计和工程实现三个角度展开讨论。

为什么用语言描述记忆:原理与优势
先回到一个基础问题:记忆要解决什么。大语言模型的上下文窗口有限,用户与系统的交互历史不可能无限塞进提示词里,因此需要一个外部存储,在合适的时机把相关信息取回来注入上下文。传统做法是向量数据库加语义检索,把记忆切成片段、编码成向量、按相似度召回。这条路成熟可用,但有一个隐含代价:记忆一旦变成向量,就脱离了人类可读的范畴,你无法直接看出模型"记住了什么",也无法手动修改某条记忆而不破坏整体结构。
语言化记忆走的是另一条路。记忆以自然语言句子或段落的形式存放,例如"用户偏好深色界面,曾多次抱怨浅色主题刺眼"。这条记忆本身就可以直接拼进提示词,不需要检索模型再把它"翻译"回来。语言作为记忆载体有几个天然优势:第一,可解释,每条记忆的语义边界清晰;第二,可编辑,用户或管理员可以直接增删改;第三,与模型的能力对齐,因为模型的全部知识都建立在语言之上,语言描述的记忆天然处于模型的"母语"环境中。
当然也有代价。语言的检索效率低于向量:直接关键词匹配容易漏召回,完全靠模型通读全部记忆又太贵。因此实际系统中,语言化记忆通常配合轻量的索引机制使用,比如给每条记忆打上主题标签,或者在记忆前面附加时间戳和实体名,先粗筛再精读。这属于典型的用工程手段弥补表示形式效率短板的思路。
三种语言化记忆方案:文本、摘要与情景日志
方案一是原子事实条目。每条记忆是一个独立的短句,描述一个事实或偏好,例如"用户的名字是李明""用户居住在上海"。这种粒度的记忆最容易被检索和更新,冲突也好处理——发现新事实后直接覆盖旧条目。缺点是碎片化,一段对话可能产生十几条原子记忆,数量增长快,需要在写入时做去重和合并。
方案二是滚动式人物摘要。系统维护一份不断更新的长文档,概括用户的画像、偏好和历史,每次对话后把新信息融入这份摘要。摘要的好处是信息密度高,注入提示词时一次搞定,模型看到的是一个连贯的整体认知。但摘要会持续丢失细节,早期信息在反复改写中可能被稀释掉,而且摘要的更新本身需要模型调用,有额外的成本和出错风险。工程上常见的折中是摘要加原子条目并存:摘要做日常注入,原子条目留做细节追溯。
方案三是情景日志式记忆。不做提炼,直接按时间顺序记录交互片段,类似日记。人类记忆的情景性在这种方案里保留得最好,模型回答"我们上次聊了什么"这类问题时最准确。但日志的增长是线性的,必须配合作废策略,比如只保留最近N天,或者定期把旧日志压缩成摘要归档。这三种方案不是互斥的,实际系统往往分层组合:底层是情景日志,中间是原子事实,顶层是滚动摘要,各层之间通过定时任务完成信息的流动与压缩。
工程实现:一个简易的语言化记忆管理器
下面用Python实现一个简化版的记忆管理器,演示写入去重、标签索引和提示词注入三个核心环节。真实系统会把记忆落到数据库,这里用内存列表示意即可。
import re
from datetime import datetime
class LanguageMemory:
def __init__(self):
# 每条记忆是一个字典:包含文本、标签和时间戳
self.entries = []
def _normalize(self, text):
# 简单归一化:去空白、转小写,用于粗略去重
return re.sub(r"\s+", "", text).lower()
def add(self, text, tags=None):
norm = self._normalize(text)
# 去重:语义相同或高度相似的记忆不重复写入
for e in self.entries:
if norm == self._normalize(e["text"]):
e["updated"] = datetime.now()
return False
self.entries.append({
"text": text,
"tags": tags or [],
"created": datetime.now(),
"updated": datetime.now()
})
return True
def recall(self, query_tags, limit=5):
# 按标签粗筛,再按更新时间排序取前N条
matched = [e for e in self.entries
if set(query_tags) & set(e["tags"])]
matched.sort(key=lambda e: e["updated"], reverse=True)
return matched[:limit]
def inject(self, query_tags, limit=5):
# 生成可直接拼入提示词的记忆块
items = self.recall(query_tags, limit)
if not items:
return "(暂无相关记忆)"
lines = [f"- {e['text']}" for e in items]
return "已知关于用户的信息:\n" + "\n".join(lines)
# 使用示例
mem = LanguageMemory()
mem.add("用户偏好深色界面", tags=["偏好", "界面"])
mem.add("用户的名字是李明", tags=["身份"])
print(mem.inject(["偏好"]))
这段代码里有两个设计点值得说明。其一是去重发生在写入侧而不是读取侧,因为语言描述的记忆重复写入非常常见——用户可能在十次对话里八次提到同一个偏好,如果不做合并,记忆库会迅速膨胀。其二是inject方法的输出是纯文本,可以直接拼接在系统提示词的末尾,模型读到后自然会把这部分当作背景知识来使用,不需要任何特殊的提示工程技巧。
在去重之外,更进阶的合并逻辑通常交给模型本身完成:把新信息和疑似冲突的旧记忆一起送入模型,让它输出一个合并后的版本,替换原有条目。同时建议给每条记忆加一个访问计数和最后命中时间,定期清理长期未被命中的条目,这就是语言化系统的"遗忘"机制——遗忘不是删除数据,而是让不重要的描述在竞争中自然沉底。
语言化记忆的边界与适用场景
这套方案并非万能。当记忆条目达到数万条以上,纯语言方案的成本会明显上升,此时必须引入向量检索做第一道召回,语言化只作为存储和展示层保留。此外,语言描述天然带有模糊性,"用户不喜欢等待"这种记忆在不同场景下的解释可能不同,关键决策场景中应尽量写入具体、可量化的事实,而不是笼统的印象。
综合来看,语言化记忆最适合的三个场景是:个人助理类应用(记忆量中等、可解释性要求高)、需要用户查看和编辑自己记忆的产品(透明性是卖点)、以及记忆条目需要在多人之间共享审核的协作系统。在这些场景里,"用语言描述记忆"不只是技术选型,更是一种产品态度——让机器的记忆像人的记忆一样,可以被讲述、被质疑、也被信任。