ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从零搭建AI记忆系统:让大模型记住用户的完整实战

从零搭建AI记忆系统:让大模型记住用户的完整实战 前两天在帮朋友调一个基于大模型的客服助手遇到了一个特别典型的问题用户在第一轮对话里明确说了“我是会员优先处理”结果到第五轮的时候模型完全忘了这回事又按普通用户的流程走了一遍。朋友的反馈很直接“这不就是个聊天机器人吗怎么连这点事都记不住”这就是很多做 AI 应用的人都会撞上的那堵墙——大模型本身是“无状态”的它的记忆全靠你喂给它的上下文。对话一长信息就冲出窗口它就“失忆”了。围绕这个问题这些年社区里沉淀出了一个非常明确的方向ai-memory也就是给 AI 应用专门设计和搭建的记忆系统。这篇内容我打算跟你聊聊我自己从零搭建一个 ai-memory 的完整思路和实操过程。包括记忆到底该存什么、用什么存、什么时候写入、怎么把有用信息捞回来以及我在实际项目里踩过的几个坑。无论你是在做智能客服、个人助理类产品还是想在现有 Agent 上加点个性化能力这篇内容应该都能给你一条可以直接落地的参考路径。1. 核心思路拆解ai-memory 到底要解决什么问题1.1 大模型天然“记不住”问题的根子在哪要理解 ai-memory先得理解大模型为什么记不住事情。很多刚入门的朋友会觉得“模型不是挺聪明的吗怎么会连刚才说过的话都忘了”这里面的误解在于把“聪明”和“记忆”混为一谈了。大模型的底层是一个海量参数堆出来的概率模型你输入一串文字它根据训练时学到的统计规律一个 token 一个 token 地把后续文字推出来。它没有一个稳定的“存储空间”来长期保存和你之间的私人对话记录。每次你发消息它看到的只是你当前这轮发的内容加上系统层面帮你拼接进去的历史消息。一旦历史消息超过它的上下文窗口或者被你主动截断那前面聊过的东西就真的“翻篇”了。我用一个生活化的类比来解释大模型就像一个很聪明但患有“瞬时记忆障碍”的专家。你跟他聊天时他能听懂你的话也能给出漂亮的回答但只要你一转身离开再回来他就完全忘了你是谁、你们之间聊过什么。没有病历本没有档案柜他什么都留不下。所以 ai-memory 本质上是在模型外面加一层“外挂记忆”承担三件事把该记住的信息从对话里抽出来、用一个可检索的形式存下来、在合适的时机把相关记忆重新塞回模型的上下文里。模型本身的结构不需要动但这层外挂能彻底改变用户体验——从“每轮都是陌生人”变成“这是一个记得我的助手”。1.2 记忆系统的三层模型工作记忆、情景记忆、语义记忆认知科学里把人类记忆分为几种类型这个框架完全可以借鉴到 AI 记忆系统设计里。我自己在实际工程中用的是三层模型分工非常清晰第一层是工作记忆对应的是当前会话中的上下文。比如用户正在跟你讨论一个技术方案前几轮刚提到的需求和约束条件这些信息不需要持久化写入数据库只需要保持在对话上下文中即可。它的特点是容量有限、时效性强跟着会话结束就销毁。第二层是情景记忆对应的是用户跟你交互过程中发生的具体事件。比如“用户三天前问过如何部署 Kubernetes”“用户上次反馈过支付流程有问题”“用户是周五晚上通常来咨询”。这类信息需要跨会话保存它的特点是带有时间、事件维度的是用户和你历史交互的“日志”。第三层是语义记忆对应的是从交互中沉淀下来的关于用户的事实和偏好。比如“用户是某电商平台的商家”“用户偏好简洁的回复风格”“用户所在行业是跨境电商”。它不再绑定某一次具体事件而是抽象的、可复用的用户知识。三层记忆的划分不是为了学术上的严谨而是因为它们在工程上的存储方案、写入方式和检索策略完全不同。工作记忆靠 prompt 组装情景记忆靠时间排序 摘要语义记忆靠结构化抽取 向量查询。把它们混在一起做后遗症非常多下面我展开讲每一层的具体实现方案。1.3 一个完整的记忆系统该有哪些模块很多人在 GitHub 上看到一个记忆项目就去读代码结果发现东一块西一块很难复用。我建议你先在自己脑子里搭一套“记忆系统的标准模块图”再看任何项目都会豁然开朗。一个完整的 ai-memory 系统至少要包含这五个模块写入模块负责从原始对话中提取“值得记住”的信息。不是每句话都值得存这背后需要一套抽取逻辑可能是规则匹配、摘要模型或者是带提示词的结构化抽取链路。存储模块负责把抽取出来的信息落地。常见的选择有普通数据库、KV 存储、向量数据库或者组合方案。不同记忆类型进不同的存储区这是我在第 2 部分会重点展开的内容。唤起模块负责在每次对话前从存储里找出“当前情境下最相关的记忆”拼进 prompt。这是整个系统里最影响体验的一环捞少了等于没记忆捞多了会污染本轮对话。遗忘模块大多数初版记忆系统都不做这个模块但它恰恰是我认为最重要的模块之一。记忆会过期、会冲突、会占空间没有遗忘机制的记忆库很快会变成垃圾场。更新模块负责处理记忆的版本变化和冲突。比如用户刚开始说“我在创业公司做开发”几个月后说“我现在到大厂做管理了”这里面是一条记忆的覆盖还是两条记忆并行更新策略没想清楚会出现“模型一边叫你老板一边叫你同学”的尴尬情况。这五个模块不是全部都要在第一版实现但从设计一开始就要在脑子里给它们预留位置。我见过太多项目第一版只做了存储和唤起跑了两周发现库里的记忆又脏又乱回头再补写入和遗忘模块等于整个重写一遍。2. 方案选型记忆到底该放在哪里2.1 短期记忆会话摘要比无限堆上下文更靠谱短期记忆最朴素的做法是把所有对话历史直接拼到上下文里。最初几十轮没问题但聊到一定量级后你会发现输入 token 越来越长先不说费用问题模型在超长上下文里的注意力分布会严重稀释早期的关键信息容易被淹没在大量闲聊里这就是所谓的“Lost in the Middle”现象。我自己碰到的分水岭大概在上下文窗口的 60% 左右。超过这个比例后模型对早期信息的召回能力会明显下降而且每轮请求的延迟也开始线性变差。硬堆上下文的做法其实是用钱的增长来掩盖记忆系统设计的缺失。更稳健的短期记忆方案是滚动摘要每一段对话结束后用一个摘要模型把这段对话压缩成几十个字的摘要再跟最近几轮的原始消息拼在一起作为上下文。旧对话的信息价值被充分保留但体积缩到原来的几十分之一。在实际项目里我试过两种摘要策略一种是把摘要历史再摘要形成“树状压缩”内存效率最高但信息有损耗另一种是只保留最近 10 轮原始上下文 之前所有对话的摘要这个方案最省心且能覆盖九成以上的场景需求。还有个容易忽略的点短期记忆也要区分“用户说的”和“助手说的”。很多实现把所有历史消息丢进去包括助手长篇大论的解释这会导致上下文膨胀极快。我会在写入环节对助手回复做裁剪只保留结论性的、需要后续引用的信息解释过程全部不要。2.2 长期记忆向量库是标配但别盲目上重型组件跨界场景的长期记忆社区里目前的主流方案是向量化存储。把每个记忆片段做成一个向量然后通过余弦相似度或内积来召回与当前问题最相关的记忆。这个东西不神秘本质上是给每条记忆算了一个“语义指纹”检索时用指纹的相似度来匹配。但很多朋友一上来就选 MiLvus 这类重型组件其实是不必要的。我见过一个用户量不到几千的项目刚起步就搭了一套分布式向量集群运维成本比模型调用费还高。选型应该根据数据量和对延迟的要求来分级方案适合规模延迟部署成本我的评价NumPy/内存数组暴力检索万级以下毫秒级零原型验证首选SQLite 向量扩展十万级几毫秒极低个人项目最推荐pgvector百万级几毫秒低已有 PostgreSQL 时的最佳选择Qdrant 单机版百万级以上几毫秒中需要动态过滤时的好选择Milvus 集群亿级十毫秒级高团队几十人以上再考虑我现在的习惯是第一版永远先用 NumPy 暴力检索跑通逻辑等数据量规模真的涨上去了再平滑迁移到 SQLite 向量扩展。核心逻辑层用接口封装好换底层存储只是换一个类的事。长期记忆的另一个关键点是结构化字段。纯向量检索的问题在于它只看语义相似不理解你限定的条件。比如你想找“用户上个月关于物流时效的投诉”如果只做向量匹配模型可能会给你捞回十条跟物流相关的泛泛内容。但如果每条记忆入库时都带上时间戳、类型、情绪标签等结构化字段就能在向量检索前做一轮预过滤这是检索质量提升最大的一个优化点。2.3 语义记忆知识图谱值不值得上关于用户画像这类语义记忆有两种截然不同的做法。一种是把所有事实扁平化成“用户偏好简洁回答”“用户做跨境电商”这样的标签条目每条独立存储和检索。另一种是建立实体关系图把“用户-身份-商家”、“用户-偏好-简洁”、“商家-平台-亚马逊”这些关系显式地建模出来。知识图谱的优势是推理能力强比如你知道用户是商家且销售平台是亚马逊就能推出他大概率关心物流时效和汇率波动。劣势是构建和维护成本相当高非结构化文本到知识图谱三元组的抽取很容易出错一旦关系错了推导出来的东西就完全偏了。我个人的建议是如果不是做垂直领域的数据密集型产品第一版先用标签条目方案。把语义记忆拆成“主语-谓语-宾语”的简单三元组模式但存储上用普通数据库表不引入真正的图数据库。等产品跑通了你发现确实需要多跳推理了再考虑迁移到图存储。这个路径成本最低而且大部分场景根本走不到图数据库那一步。2.4 记忆写入与唤起时机比算法更重要我一直觉得记忆系统里最难的从来不是存储和检索技术而是写入时机和唤起时机的判断。算法是确定性的写个函数就能调但“什么时候该写”“什么时候该唤起”这个问题没有现成的公式可以套。写入机制上我观察下来有两种常见策略被动式写入和主动式写入。被动式是每轮对话结束后自动抽取并写入优点是不会漏缺点是会写入大量噪音。主动式是让模型判断“这一轮是否有值得长期记忆的信息”然后再抽取写入优点是精准缺点是模型偶尔会误判。我现在用的是混合策略第一层用规则做粗筛比如用户时长超过多少、包含特定动作词“我要”“我喜欢”“我是”等的消息进入候选池第二层用大模型做精筛对这候选池进行一次二分类判断“本条是否包含需要长期记忆的信息”。两层都通过了才进入抽取和入库流程。这个设计把模型的高成本判断放在低成本的粗筛之后既能控成本又能保质量。唤起机制里最影响体验的是“优先级”问题。当检索回来的记忆多达十几条不可能全塞进 prompt里面大部分还是低价值信息。我给记忆定义了三级优先级全局性事实如用户名、身份、偏好优先级最高每次对话必现近期事件最近一周内的交互记录次之保证临时性需求的连续性其余的记忆按相关度阈值决定是否唤起。这个分级逻辑写死在检索代码里比全部丢给模型让它自己挑要稳定得多。3. 实操过程从零搭一个最小可用的 ai-memory3.1 整体架构与数据流设计这部分我会带你用 Python 从零写一套最小可用的 ai-memory。这套方案不需要重型组件依赖只有三个一个 OpenAI 兼容的接口或者任何支持对话补全的模型服务、一个 SQLite 数据库、一个能算向量的 embedding 模型。整体数据流是这样的用户消息进来 → 先走记忆唤起模块从数据库捞回与该用户相关的记忆 → 拼装到系统提示词里生成回复 → 回复发送给用户的同时这条对话进入记忆写入管线 → 写入管线做粗筛和精筛决定是否抽取新记忆并入库 → 定期后台任务做记忆的合并、过期清除和冲突处理。一个完整会话开始前应用会先调一次记忆加载接口把该用户的全局性记忆和最相关的近期记忆塞进 prompt。这个过程我建议做成独立服务而不是塞进业务代码里因为记忆的存取逻辑往往需要复用后续做数据分析或运营后台时你也会需要同一套接口。3.2 记忆写入把“值得记的”提取出来写入管线的第一步可以写一个轻量级的记忆抽取函数。我通常会这样设计把新到的用户消息拼接成一个“记忆抽取提示词”让语言模型输出一个 JSON里面包含该不该记、记忆的类型和记忆的三元组内容。import json from openai import OpenAI client OpenAI() def extract_memory(user_input: str, history: str) - dict: prompt f你是记忆抽取系统请从用户的输入中判断是否有需要长期记住的信息。 仅输出 JSON格式如下 {{ should_remember: true/false, memory_type: fact | event | preference | none, memory_content: 主语|谓语|宾语例如用户|职业是|跨境电商运营, memory_summary: 一句话摘要 }} 用户历史上下文 {history[:800]} 用户最新输入 {user_input} resp client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: prompt}], temperature0, response_format{type: json_object} ) data json.loads(resp.choices[0].message.content) return data这是精筛那一步不是每次调用都值得跑。我会在粗筛阶段先用系统自带的消息长度和关键词过滤器跑一次比如消息长度太短的直接跳过消息里没有代词和变更信号的也直接跳过。这样能省掉差不多六成无意义的模型调用。抽取结果里的“主语|谓语|宾语”格式是我上面说的三元组落地方式。用户输入“我是做跨境电商的”抽出来就是“用户|从事行业|跨境电商”。这个格式不仅能直接入库做结构化查询还能在语义记忆中做简单的推理拼接比如两条记忆共享同一主语时就能推断出这个用户的完整画像。3.3 记忆存储结构化字段和向量索引一起上存储层我选择 SQLite 是有考量的。单机项目里它稳定、零运维、备份简单配合 JSON 字段和向量扩展能覆盖绝大部分场景。我建表通常会分成两张表memory_items 存记忆主体memory_meta 存向量和索引信息。CREATE TABLE memory_items ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL, memory_type TEXT NOT NULL, -- fact / event / preference content TEXT NOT NULL, -- 三元组或自然语言描述 summary TEXT, -- 简短摘要 source_conversation_id TEXT, -- 来源会话ID importance INTEGER DEFAULT 3, -- 重要度 1-5 created_at TEXT DEFAULT (datetime(now)), updated_at TEXT DEFAULT (datetime(now)), expired_at TEXT -- 过期时间null表示不过期 ); CREATE TABLE memory_vectors ( memory_id INTEGER PRIMARY KEY, user_id TEXT NOT NULL, vector BLOB NOT NULL, -- 向量二进制存储 embedding_model TEXT NOT NULL, -- 记录用的是哪个模型方便重新embedding updated_at TEXT DEFAULT (datetime(now)) );向量怎么算这一步最容易写错。很多新手会把整个句子丢给 embedding 模型结果发现语义相近但措辞不同的记忆被排到很后面。我的经验是把记忆条目里的主语、谓语、宾语拼接成一段完整的话再 embedding而不是只用宾语部分。from openai import OpenAI import numpy as np import sqlite3 import struct client OpenAI() conn sqlite3.connect(memory.db) def vector_to_blob(vector): return struct.pack(f{len(vector)}f, *vector) def store_memory(user_id, memory_type, content, summary, source_conv_id, importance3): cur conn.cursor() cur.execute( INSERT INTO memory_items (user_id, memory_type, content, summary, source_conversation_id, importance) VALUES (?,?,?,?,?,?), (user_id, memory_type, content, summary, source_conv_id, importance) ) memory_id cur.lastrowid # 生成向量用拼接后的完整句子 embed_text f记忆内容{content}。摘要{summary} emb client.embeddings.create( modeltext-embedding-3-small, inputembed_text ).data[0].embedding cur.execute( INSERT INTO memory_vectors (memory_id, user_id, vector, embedding_model) VALUES (?,?,?,?), (memory_id, user_id, vector_to_blob(emb), text-embedding-3-small) ) conn.commit() return memory_id关于 embedding 模型的选择text-embedding-3-small 是我目前性价比最高的选择。它有 1536 维单条记忆的向量只占大约 6KB 存储空间一万条记忆也才 60MBSQLite 完全扛得住。更大的模型比如 text-embedding-3-large 在语义理解上确实更细腻但 3072 维的向量带来的存储和计算压力翻倍在记忆这个场景里收益很小不值得。3.4 记忆唤起相关度检索加层级重排唤起环节的目标只有一个在用户的新对话进来时准确、克制地找回当时有用的记忆。核心逻辑分三步走——粗召回、相似度过滤、优先级重排。粗召回阶段用 SQLite 先做结构化过滤把和当前用户相关的记忆全部取出来。如果你的记忆库已经很大这步可以加上时间范围过滤比如“最近 30 天的事件型记忆”和“全部全局性记忆”。def retrieve_memories(user_id, query, top_k5): cur conn.cursor() # 1. 结构化粗召回取出该用户的全局性记忆和近30天事件 cur.execute( SELECT id, memory_type, content, summary, importance FROM memory_items WHERE user_id ? AND (memory_type fact OR (memory_type event AND created_at datetime(now, -30 days))) , (user_id,)) candidates cur.fetchall() # 2. 向量相似度过滤 emb client.embeddings.create( modeltext-embedding-3-small, inputquery ).data[0].embedding scored [] for item in candidates: mem_id, mtype, content, summary, importance item vec_blob cur.execute( SELECT vector FROM memory_vectors WHERE memory_id ?, (mem_id,) ).fetchone() if not vec_blob: continue vec np.frombuffer(vec_blob[0], dtypenp.float32) cosine_sim np.dot(emb, vec) / (np.linalg.norm(emb) * np.linalg.norm(vec)) scored.append((cosine_sim, mtype, content, summary, importance, mem_id)) # 3. 优先级重排全局性facts永远优先 scored.sort(keylambda x: (x[1] fact, x[4]), reverseTrue) return scored[:top_k]这里有个关键参数值得细聊相似度阈值。我在不同项目里测下来0.3 到 0.5 之间是最合适的区间。低于 0.3 召回的基本都是噪音高于 0.5 则会漏掉太多边缘相关的记忆。但每个领域的最优阈值都有差异建议你在自己的数据上采样 100 条对话做一次手动标注画出精确率和召回率的曲线找到曲线的拐点作为你的阈值。这个工作看着累但做完之后记忆系统的体验会明显上一个台阶。重排逻辑里“全局性事实永远优先”这个原则背后是我踩过的坑有一次我把用户在上周说的一句“我最近在学游泳”存成了事件结果一周后用户问了个工作相关的专业问题系统把“学游泳”这条事件作为 top1 记忆唤起模型在回复开头就来了句“顺便问一下您游泳学得怎么样了”直接把用户给整懵了。从那以后我就明白关联度的分要和记忆的内容性质挂钩事件型记忆再多也不能插队到事实型记忆前面。不管粗召回还是过滤唤起模块的最终产出是一段拼装好的记忆文本它会被放到系统提示词里给模型看你是一个智能助手以下是和当前用户相关的重要记忆请基于这些记忆并结合对话历史给出回答。 【长期记忆】 - 用户从事行业跨境电商重要度5 - 用户偏好回答简洁少铺垫直接给要点重要度4 - 用户最近事件3天前反馈过物流时效投诉已处理完毕重要度3 【当前对话】 ...记忆文本的格式也很讲究。我踩过的坑是直接把原始存储的三元组丢进去模型虽然能读懂“用户|从事行业|跨境电商”这种格式但它的表现明显没有“用户从事行业跨境电商”来得好。语言模型在理解通顺的自然语言时的综合表现要显著好于结构化的符号表达。所以入库时用三元组方便机器处理出库前把它还原成自然语言再塞给模型。4. 常见问题与排查技巧实录4.1 记忆污染模型把事情记串了这是我遇到过的坑里最隐蔽的一个。表面上看记忆系统能记住东西了但仔细看内容发现它会时不时冒出一些“张冠李戴”的记忆——把 A 用户的身份信息记到 B 用户头上了或者把用户随口开的玩笑当真事记了下来。问题出在写入管线的抽取环节。大模型在做“是否值得记忆”的判断时很难区分事实和观点、当前事件和长期偏好。用户说“我今天好累不想干活”模型可能会抽出“用户偏好不想干活”这就完全错失了语义。我的解决方式是在抽取提示词里加了三条强制规则一是不允许抽取出带负面情绪的随口抱怨二是事件的持续时间如果短于一天默认归类为事件型记忆而不是事实型三是同一个用户同一属性的记忆最多只保留一个新值必须标注更新时间。加了这三条之后污染率大约降低了七成。你如果要用这套方案我强烈建议把这几个规则也加到你的系统里。4.2 相似度阈值调不好召回质量忽高忽低跑记忆系统一段时间后你会发现一个奇妙现象同一个查询有时候召回特别准有时候却捞上来一堆驴唇不对马嘴的内容。仔细排查后很大概率是相似度阈值设置得太宽松导致低质量候选全部涌进来。反过来把阈值调严了又发现该记住的关键信息一条都没捞到。这个问题的根本原因是 embedding 模型的输出空间不是你想象中的“均匀分布”。真实文本的向量分布非常不均匀有些文本在空间里天然就聚集得紧有些则散得很开。同一个相似度阈值在密集区域会召回太多在稀疏区域又召回太少。我后来用的方案是动态阈值不再做一个固定的全局阈值而是针对不同类型的记忆分别设定阈值。比如“事件型记忆”因为包含较多具体时间地点数字语义距离天然拉得大阈值就放宽到 0.25而“偏好型记忆”文本重复度高阈值就收紧到 0.45。实现上只需要把相似度过滤从一次调用改成按 memory_type 分组循环调用代码量增加不到十行但效果提升非常明显。4.3 记忆膨胀库越跑越大检索越来越慢这是所有长期运行记忆系统都会遇到的问题没有例外。头两周你感觉不到但一个月后 SQLite 里的 memory_items 表可能已经塞了几万条记录。查询变慢还在其次更要命的是每条新对话进来粗召回都要扫描几万条记录然后计算向量相似度响应时间从最初的 20ms 一步步爬到了 500ms。针对这个问题我建议做两件事。第一是设置记忆的“半衰期”事件型记忆默认七天就过期偏好型记忆如果三个月没有更新也降级为低重要性。第二是定期跑一个合并任务把同一个用户相似度极高的多条事件记忆合并成一条摘要比如用户这个月问了十次物流问题合并后变成一条“本月高频咨询物流问题建议优先优化物流时效相关话术”。我在项目里写了一个每周末自动跑的清理脚本统计不同类型记忆的召回率和点击反馈把三个月内从未被召回过的记忆批次删除。刚开始跑的时候删掉了接近一半的存量记忆我以为会影响体验结果用户反馈不但没变差反而觉得“好像更懂我了”。原因很简单删掉的全都是没人会再用的噪音留下的都是真正有价值的信息。4.4 冲突更新同一个用户前后说矛盾的话还有一种很容易被忽略的情况是用户信息变更后新记忆和旧记忆打架。用户最开始用的是个人邮箱注册后来换了企业邮箱用户一开始说自己是个学生后来又说自己工作了。如果你只是机械地追加记忆库里就会留下矛盾的内容。模型在召唤记忆时看到两条冲突信息大概率会按照最后一条来理解但有时候也会抽风选到旧的那条。更麻烦的是如果不做更新旧记忆占用的检索名额也会越来越挤。我的做法是给记忆加了“版本号”和“替代关系”每条新记忆入库前先用原有的记忆数据库做一次实体对齐查询如果发现同一个实体同一个属性的已有条目就把旧条目标记为 replaced新条目带着引用旧条目的 id 入库。检索时过滤掉所有 replaced 状态的记忆这样模型永远不会看到冲突的信息。查询对齐是直接用文本匹配做的因为主谓三元组的结构足够规整不需要额外上模型。4.5 具体报错与排查速查表为了让你排查问题更顺手我把实操中常见的问题整理成一个速查表症状可能原因排查与解决办法模型完全不理会记忆回复像第一次见面记忆唤起模块未被正确调用检查请求日志里系统提示词是否包含记忆内容确认唤起接口返回是否为空回复里出现矛盾信息记忆更新逻辑未生效新旧条目共存检查 replaced 状态记忆的过滤逻辑核对更新模块版本号生成检索结果混乱经常捞回无关记忆相似度阈值过宽或阈值未按类型分组按 memory_type 分别测试阈值分布做一次人工标注调参记忆全部过期长期记忆为零过期时间设置不合理事件型记忆被过早清理调整半衰期周期提升事实型记忆的重要性值写入管线的模型调用费用过高精筛阶段过于频繁粗筛规则太弱先加粗筛条件观察候选池缩减比例再考虑精筛频率向量检索结果和关键词检索结果重复度高embedding 模型对短文本不敏感没有保留结构化字段优化 embedding 的输入拼接方式加入记忆类型的前缀标记用户否认记忆内容反馈“你记错了”抽取时把对话中的假设或猜测当成了实际事实在抽取提示词里区分“用户陈述”和“用户假设”后者不写入长期记忆这套速查表是我把两年来处理过的各种记忆系统问题做了一个汇总你踩到类似问题时可以直接对照处理。4.6 兜底手段给记忆系统一个“后悔药”最后聊一个非常实用的兜底机制。你再怎么优化写入逻辑也不可能做到百分之百准确。总有那么一天系统写入了一条让用户非常不适的记忆比如人家只是想咨询一下某个疾病的治疗方案系统却存成了“用户患有该疾病”并长期引用用户愤怒程度可想而知。所以我在记忆系统里做了一个只属于开发者的接口手动删除指定用户的所有记忆。这个能力看起来不起眼但在用户提出投诉或隐私异议时它能帮你免掉很多麻烦。其次我还做了一个“拉起开关”当任何一轮对话被用户标记为“回答得不好”时这条对话关联的所有记忆条目自动进入待审核状态隔天我再统一过一遍。记忆系统的实现从来不是一锤子买卖。它会随着用户量的增长、数据形态的变化、模型能力的升级持续地需要调优。但只要你把底层的数据模型和模块边界搭对了后面所有的优化都是在做“替换”而不是“重构”。我自己在前前后后改造了三版架构之后最大的心得就是给记忆系统做设计时永远预留一个手动干预的口子。因为它的核心评判标准只有一个——用户在对话时是否觉得这个 AI 是真的记得自己。
返回列表