给Agent做长期记忆:存用户偏好向量化召回

📅 2026/6/28 5:18:34
给Agent做长期记忆:存用户偏好向量化召回
先把结论甩前面:想让智能体记住用户,别一股脑把历史对话塞进 prompt,那个又贵又记不准。靠谱做法是——把用户偏好抽成短句、向量化存进一个向量库,下一次对话开头先按当前问题召回 top-k 条,拼进 system。我自己跑通后,体感最明显的不是更聪明,是它终于不每次都问我你想要什么风格了。问题:它压根不记得我事情起因很土。我给团队搭了个写周报的小助手,第一周我跟它说了三遍别写虚的、只要数据和结论、别用赋能这种词。它当场听话。第二天新开一轮对话,又给我整出一篇持续赋能业务增长。我当时有点上头。智能体没有跨会话记忆这事我知道,但每次重新教一遍真的烦。塞历史记录?我数了下,光那几轮调教就两千多 token,而且它会被淹没在一堆寒暄里抓不住重点。所以需求很清楚:用户说过的偏好,得攒下来,下次按需取出来。注意是按需——用户问周报的事,你别把他三个月前对 PPT 配色的吐槽也翻出来。这就引出了向量召回。记忆存储设计:偏好抽取 向量化 按相关性召回整个链路我拆成三段,刻意做薄:环节干的事我用的抽取从对话里挑出稳定偏好,忽略一次性指令让大模型自己判定,输出短句存储偏好句 → embedding → 落库,带 user_idsqlite 一张表召回新问题 embedding,余弦相似度取 top-knumpy 暴力算,够用几个我踩过坑后定下的原则:存句子不存整段对话。每条记忆是一句独立的人话,比如用户讨厌赋能/-{}-赋能这类官腔词。自包含,召回出来直接能用。加用户隔离。user_id必须进 where 条件,不然 A 的偏好串到 B 头上,社死。top-k 取 3 到 5。取多了又把噪声请回来了,等于没做。别上来就 Pinecone。几百上千条偏好,sqlite 存向量、numpy 算相似度,毫秒级,没必要为这点量引一个云服务。小代码:60 行跑通整条链路存储和召回的核心就这么点东西:import sqlite3, json, numpy as np from openai import OpenAI client OpenAI() # embedding 接口 def embed(text): r client.embeddings.create(modeltext-embedding-3-small, inputtext) return np.array(r.data[0].embedding, dtypefloat32) def remember(user_id, pref): v embed(pref).tobytes() db.execute(INSERT INTO mem(uid, text, vec) VALUES (?,?,?), (user_id, pref, v)) db.commit() def recall(user_id, query, k4): rows db.execute(SELECT text, vec FROM mem WHERE uid?, (user_id,)).fetchall() if not rows: return [] q embed(query) scored [] for text, vec in rows: v np.frombuffer(vec, dtypefloat32) sim float(q v / (np.linalg.norm(q) * np.linalg.norm(v))) scored.append((sim, text)) scored.sort(reverseTrue) return [t for s, t in scored[:k] if s 0.3] # 0.3 是相关性闸门对话开头这样拼:prefs recall(uid, user_input) sys 你是周报助手。已知该用户偏好:\n \n.join(- p for p in prefs)抽取那一步我偷懒了,直接让模型判:每轮结束甩一句从刚才对话里挑出值得长期记住的用户偏好,没有就回 NONE,拿到非 NONE 就remember()。省了写规则。那个s 0.3的阈值是我调出来的,一开始没设,结果用户问周报时把喜欢深色主题也召回来了,看着就尴尬。取舍:它只是省事,不是变聪明实话实说几个不爽的地方。抽取会误判。有回我随口说这次先口语化点,被它当成长期偏好存了,之后写正式邮件也给我整得吊儿郎当。后来我加了道人工确认——新记忆先标 pending,我瞄一眼再转正。麻烦,但比污染记忆强。embedding 调用要钱也要时间。每轮多两次 embedding 请求,本地体感多个一两百毫秒,量大了费用也得算。我的折中是抽取异步做、召回才同步,用户无感。它治标不治本。这套记忆让助手省事,可没让它写得更好。第一版召回拼进去,产出还是干巴巴,得我再调 prompt 措辞。记忆解决的是别每次重教,不解决教得好不好。还有个隐性成本:搭这套之前我对 embedding 维度、余弦相似度这些半懂不懂,真上手调阈值才搞明白。学习曲线不陡,但不是零。折腾完最大的感受是,有记忆的智能体才像个熟人,没记忆的永远是客服话术第一句。值不值得?对高频、强个性化的场景,我觉得值。一次性的问答,别折腾,纯属过度设计。对了,上面这套我没自己部署模型——embedding 和对话模型都走的现成 MaaS,讯飞星辰那种直接调 API 的,省了搭算力的事;整条链路我甚至是先在一个零代码配智能体的平台上拖出原型验证逻辑,跑通了才落到这段代码。你们给智能体存偏好,是硬塞历史还是也走向量召回?阈值都设多少?评论区聊聊,我那个 0.3 也未必最优。