搭一个会记事的AI助手
你跟普通聊天机器人说完一句话,它下一句就忘了上一句在聊什么。要做个真正能陪你长期协作的助手,比如记住你的项目背景、你的用药习惯、你上周交代的事,得自己动手给它加"记忆"。这篇我把短期记忆和长期记忆两种做法都写出来,代码能直接抄。
短期记忆:把历史一起发回去
大模型本身是无状态的,它不记得你上一句说了啥。所谓"记住",其实是每轮对话时,你把之前聊过的内容连同新问题一起重新发给它。做法就是维护一个消息列表:
messages = []
def chat(user_text):
messages.append({"role": "user", "content": user_text})
reply = call_model(messages)
messages.append({"role": "assistant", "content": reply})
return reply
每轮都把整个 messages 传给模型,它就能看到前面聊了什么,自然就接得上话。这就是大多数对话产品的短期记忆原理,一点也不神秘。
图注:每轮把累积的对话历史连同新问题一起发给模型,模型据此保持上下文连贯。
不截断会出事
这个列表会一直变长,而模型一次能读的字数有上限。我一开始偷懒不处理,结果聊了半天之后,调用直接报错说超长了,而且账单也跟着涨。正确做法是保留最近若干轮,更早的截断掉:
MAX_ROUNDS = 10
if len(messages) > MAX_ROUNDS * 2:
messages = messages[-MAX_ROUNDS * 2:]
这样模型永远只看最近十轮,既不超长也省钱。代价是它真会忘掉十天前聊的,所以短期记忆只适合"这次对话内"的连贯,跨天的事得靠长期记忆。
长期记忆:跨会话存下来
长期记忆的思路是把重要信息落库,下次对话时再取出来。最简单的版本,把关键信息存进一个文件或者数据库,对话开始时读出来拼进系统提示:
memory = load_from_db(user_id)
system_prompt = "已知用户背景:" + memory
messages = [{"role": "system", "content": system_prompt}]
我做过一个记日程的小助手,用户说"我每周三开会",我就把它写进库。下次他问"我这周有安排吗",助手能从库里把这条捞出来回答。这种"跨会话记得住"的体验,才是助手和玩具的区别。
图注:把关键事实写入长期存储,新会话开始时检索相关记忆拼入提示,实现跨轮记忆。
我踩过的坑
第一个坑上面说了,不截断历史迟早爆 token。第二个坑更隐蔽:长期记忆我一开始把所有聊天记录都原样塞回提示,心想记得多总没错。结果模型被一堆无关旧事干扰,答当前问题时反而混乱,还白白烧 token。后来我改成只存"提炼过的关键事实",比如用户偏好、约定、待办,不再存闲聊流水,效果立马上来。
第三个坑是关于"该忘就忘"。有些信息有时效,比如"今天要去体检",过了那天就不该再提。我加了一个带过期时间的字段,过期的记忆自动不召回。这点看起来小,体验差别却很大,助手不会在月底还提醒你已经做过的体检。
记忆多了怎么找
当长期记忆积累到几百条,每次全塞进去不现实,得按需检索。这时候上期讲的 embedding 就派上用场了:把每条记忆向量化存起来,用户提问时,先算问题和哪条记忆最相关,只把相关的几条拼进提示。我自己的助手到这个阶段才真正好用,它不会在几千条记忆里瞎翻,而是精准调取那两三条。
最后说句实在的,记忆这件事的核心不是"记多少",是"记什么、忘什么"。一股脑全记,既贵又乱;有选择地记关键事实、按语义取用、该过期的过期,助手才像个靠谱的搭档。你今天把短期窗口和长期存储这两层搭起来,再慢慢接上向量检索,一个会记事的助手骨架就立住了。
把完整示例拼起来
前面拆着讲,这里把短期和长期拼一个能跑的骨架。初始化时,从库里读这个用户的长期记忆,拼成系统提示,messages 只放系统提示开头。每次用户说话,append 到 messages,调用模型,拿回回复再 append,同时把这次对话里提炼出的关键事实写回长期库。短期窗口超长就截断。这样一个助手,既能在一次对话里接得住话,也能跨会话记得你交代过的事,两层记忆互补。
我实现的第一个版本就这几十行,跑起来那一刻挺有成就感的,它真的记住了我上周说周三农展别约会议,这周问它这周安排它就提醒我。那一刻我才切实感到,所谓有记忆的助手,技术含量没想象中高,门槛在于你愿不愿意把记什么、忘什么想清楚。想清楚之后,代码反而简单。
上线前自查清单
准备拿给真人用之前,我习惯过一遍清单。第一,历史会不会无限变长导致超 token 和烧钱,截断逻辑在不在。第二,长期记忆是不是只存关键事实而不是闲聊流水,避免干扰。第三,带时效的信息有没有过期机制,别月底还提醒过期的体检。第四,敏感操作有没有人工复核,绝不自动执行高危动作。这四关过了,基本不会出大丑,用户也不会被奇怪的回答吓到。
还有一个容易被忽略的,是让用户能看见和删除自己的记忆。助手记了什么,用户应该能查、能改、能清空,这既是信任问题也是合规问题。我后来加了个查看我的记忆入口,用户反而更敢用了,因为知道东西在自己手里,不会被偷偷乱记。这个小动作带来的信任感,远超技术本身,值得你early做进去。
记忆和隐私的边界
记忆越做越细,就越碰隐私。一个助手记得你每天去了哪、买了什么、和谁聊了什么,强大是强大,但也危险。我的做法是分层:能改善体验的非敏感偏好(比如你习惯简短回答)大胆记;涉及身份、健康、财务的,要么不记,要么记脱敏后的结论而非原始内容。比如记用户对价格敏感,而不是记他刚查了某医院某项检查多少钱,这一字之差就是合规和翻车的分野。
踩坑后我定的架构
折腾几轮之后,我给记忆定了三层:会话内的短期窗口、用户级的长期事实库、还有跨用户的内容向量库(上期的 embedding 那套)。短期负责接话,长期负责记得住你,内容库负责找得准。三层各管各的,互不污染。这个结构清晰之后,出 bug 也好定位,哪层召回错了就调哪层,不会再像最初那样一团乱麻,改一处坏三处。
别让记忆变成负担
最后提醒,记忆不是越多越好。我早期贪多,什么鸡毛蒜皮都存,结果库越来越臃肿,检索变慢,还经常召回无关旧事干扰当下。后来我加了遗忘机制:久不触发的事实自动降权,明显过期的直接清。助手这才从记忆力超群但啰嗦的老汉,变成清爽靠谱的搭档。会记,也要会忘,才是好记忆,这一点对人成立,对模型也成立。
回到开头那句话,会记事的助手核心不在记多少,在记什么、忘什么。今天你把这长短两层记忆加好,再配上向量检索和遗忘机制,一个靠谱的搭档就立住了。剩下的,是在真实使用里慢慢调出属于你自己的手感,工具最终长成什么样,取决于你喂给它怎样的习惯。
夜雨聆风