日期:2026-08-09
客户是一家做企业级服务的公司,自己的客服助手上线大半年,接的是私有化部署的大模型。功能上没什么毛病,问答准确率不低,但用户满意度一直卡在一个不上不下的位置。
我们去做诊断的时候,翻了两千多段真实对话记录,发现问题集中在一处:助手完全没有记忆。同一个客户,上周花了十分钟讲清楚自己的部署环境是信创服务器加国产数据库,这周再来问一个相关问题,助手又从"请问您使用的是什么环境"开始问。有个客户在对话里直接打了一句"我上次说过了",后面就没再理了。
客户的运营人员统计过,会话中出现重复提问的比例是百分之三十四点六,其中有百分之十一的会话因为反复确认信息导致用户中途放弃。他们也试过简单的办法,把最近若干轮对话拼进上下文,但这只解决了单次会话内的连续性,跨会话、跨天的信息还是丢。而且上下文一长,token 成本和首字延迟都往上顶。
真正推动这件事立项的,是他们销售侧的一个反馈。大客户觉得这个助手"不认人",跟通用聊天机器人没区别,体现不出专属服务的感觉。
我们做的这套记忆机制跑在客户已有的 AI 私有化底座上,向量检索用的是底座里的向量数据库,模型服务化层没动。整体分成四个部分。
短期会话状态维护的是当前这一轮对话的上下文,包括用户刚提到的实体、正在处理的任务、待确认的信息。这部分生命周期跟会话一致,存在缓存里,会话结束或者超时后归档。
长期偏好抽取与存储是核心。会话结束后,有一个异步任务从对话里抽取值得长期记住的信息,比如用户的技术环境、行业属性、关注的产品模块、沟通习惯、明确表达过的偏好。抽出来的每条记忆带上类型、置信度、来源会话、时间戳,存进结构化表和向量库两份。
记忆检索与冲突消解在对话开始和进行中触发。新会话开始时,按用户 ID 拉取高频记忆;对话过程中,根据当前问题做向量检索,召回相关记忆注入上下文。冲突消解处理的是同一个用户在不同时间给出矛盾信息的情况。
遗忘与过期策略负责让记忆不无限膨胀。不同类型的记忆有不同的有效期,长时间未被命中的记忆会降权直至归档,用户也可以主动查看和删除自己的记忆条目。
记忆抽取的准确率是第一道坎。第一版我们让模型自由抽取,提示词里写"提取对话中值得长期记住的用户信息",结果抽出来一堆垃圾。有的把用户的一句吐槽当成偏好记下来,有的把临时性的信息当成长期属性,最离谱的一条是把"用户说今天有点忙"存成了长期记忆。
改进的做法是把抽取从开放式改成受限式。我们定义了十一类记忆槽位,每类有明确的语义边界和取值约束,模型只能往这些槽位里填,填不进去的信息一律丢弃。同时要求模型输出每条记忆的原文依据,也就是对话里的哪一句话支撑了这个结论。没有原文依据的抽取结果直接判无效。这两个约束加上之后,抽取的准确率从百分之六十一提到百分之八十八,噪声条目减少了七成多。
记忆冲突消解比想象中麻烦。用户三个月前说自己用的是 A 数据库,上个月说换成 B 了,这种情况相对好处理,按时间新的覆盖旧的。但还有一类是同时成立的,比如用户说"我们测试环境用 A,生产环境用 B",这不是冲突,是两条并列的记忆。还有一类是同一时期的真冲突,用户在两个会话里说了不一致的话,可能是他自己记错了,也可能是不同的人用同一个账号。
我们的处理逻辑是三段式。先判断是否可以通过限定条件共存,能共存的就加上限定词分别存储。不能共存的看时间差,超过设定阈值的按新覆盖旧,但保留旧记录作为历史。时间差在阈值内的真冲突,两条都降低置信度,并且在下次对话中找机会向用户确认,确认后再定。这个确认动作的措辞我们改了好几版,不能问得太生硬,最后用的是在相关回答里带一句"我这边记录的是您用 A,如果有变化您跟我说一声"。
隐私与授权边界这块,客户的法务介入得很深。哪些信息可以记、记多久、用户能不能查、能不能删、员工能不能看,每一条都要明确。最终定的是:技术环境类和业务偏好类可以记,身份证号手机号这类个人敏感信息一律不进记忆库;用户在个人中心能看到自己的全部记忆条目并逐条删除;客服人员在坐席端能看到记忆摘要但看不到原始对话引用;所有记忆的读写都留审计日志。这些约束让功能变复杂了不少,但没有商量余地。
检索延迟是最后一个要压的指标。记忆注入发生在生成之前,这段耗时是纯增量的。最初的实现串行做了三件事:拉高频记忆、向量检索、冲突过滤,加起来 P95 到了三百二十毫秒,用户能感觉到卡顿。优化的思路是把能提前做的都提前:高频记忆在会话建立时就预加载到缓存;向量检索和模型的首轮 token 生成并行,检索结果通过增量方式注入;冲突过滤的规则预编译,不在请求路径上做复杂判断。改完之后 P95 降到六十八毫秒,用户基本感知不到。
有个地方我们判断失误过。最初认为记忆越多越好,把召回数量设成了十五条,结果发现注入太多记忆反而干扰模型,回答变得啰嗦,还会强行关联不相关的历史信息。调到五条之后效果最好,再往下降召回不足。这说明记忆这个东西不是多多益善,得节制。
案例片段(已脱敏):记忆槽位定义与一次抽取结果。
json { "slots": { "tech_env": {"ttl_days": 180, "conflict": "TIME_WIN", "multi": true}, "industry": {"ttl_days": 730, "conflict": "TIME_WIN", "multi": false}, "focus_module": {"ttl_days": 90, "conflict": "MERGE", "multi": true}, "comm_style": {"ttl_days": 365, "conflict": "TIME_WIN", "multi": false}, "known_issue": {"ttl_days": 60, "conflict": "MERGE", "multi": true} }, "extracted": [ {"slot":"tech_env","value":"信创服务器+国产数据库","conf":0.93, "evidence":"我们这边是信创环境,数据库用的是国产的那套", "scope":"生产环境","ts":"2026-XX-XXT10:22:41"}, {"slot":"focus_module","value":"批量导入性能","conf":0.81, "evidence":"主要是十万条以上导入的时候特别慢","ts":"2026-XX-XXT10:31:07"} ], "dropped": [ {"reason":"no_evidence","raw":"用户可能是技术负责人"}, {"reason":"transient","raw":"用户今天时间比较紧"} ] }案例片段(已脱敏):一次记忆冲突的消解过程。
``` user=U0**831 slot=tech_env existing: {value:"MySQL 8.0", ts:2026-XX-XX, conf:0.90, hits:14} incoming: {value:"达梦数据库", ts:2026-XX-XX, conf:0.88} gap_days = 96 (threshold=30) -> 判定为版本演进,非真冲突 action: incoming 生效,existing 转历史(archived),不删除 next_turn_probe: 关闭(时间差足够大,无需向用户确认)
user=U0**742 slot=industry existing: {value:"医疗器械", ts:2026-XX-XX, conf:0.85} incoming: {value:"医药流通", ts:2026-XX-XX, conf:0.83} gap_days = 3 (threshold=30) -> 真冲突 action: 两条 conf 各降至 0.60,标记 pending_confirm probe_text: "我记录的您这边是医疗器械方向,如果不对您随时纠正我" result: 用户回复"我们两块都做",两条转为并列有效 ```
功能全量上线四个月,几个核心指标的变化如下。
会话中的重复提问率从百分之三十四点六降到百分之七点二。剩下的这部分主要是用户自己重复问同一个问题,不是助手要求重复提供信息。
记忆命中率百分之七十三点五。这个口径是"注入的记忆中至少有一条被最终回答实际用到"的会话占比。判断方式是用另一个模型做事后标注,抽样人工复核过,标注准确率九成以上。
平均对话轮次从五点八轮降到四点一轮。轮次减少不是因为用户问得少了,是因为不用再来回确认背景信息,达成目的的路径变短了。
用户满意度评分从三点九提到四点四(五分制)。大客户那边的反馈变化更明显,客户成功团队反馈"不认人"这个抱怨基本听不到了。
成本方面有个反直觉的结果。原本担心加记忆会推高 token 消耗,实测下来单次会话平均消耗反而降了百分之十一,注入五条精准记忆比拼接十几轮历史对话便宜得多。
记忆库现在存了约四十七万条有效记忆,覆盖两万三千多个用户。
以上数据为脱敏后的复盘口径。
记忆这件事,抽什么比怎么抽重要。我们在开放式抽取上浪费了两周,后来把槽位定死才走上正轨。开放式看起来更智能,实际上是把判断责任推给了模型,而模型在"什么值得记住"这个问题上的判断力,远不如一个业务专家花半天时间列出来的清单。
要求模型给出原文依据这个约束,收益超出预期。它不仅过滤掉了幻觉式的抽取,还让整个记忆库变得可审计。出问题时能顺着依据回溯到具体哪句话,排查效率高很多。这个做法我现在会推荐给任何需要从文本里抽结构化信息的场景。
冲突消解不要追求全自动。我们最初想设计一套完备的规则把所有冲突都自动解决,做到一半发现有些冲突本质上需要用户参与才能确定。加了那个"下次对话中顺带确认"的机制之后,问题解决了,而且用户对这种确认方式接受度很高,有人还专门夸了一句这个助手挺细心。
记忆数量要节制。召回十五条不如召回五条,这个结论我们是被数据打脸打出来的。上下文里塞太多东西会稀释模型的注意力,也会诱导它做不必要的关联。这跟检索增强里的老问题是一回事,只是换了个场景重演一遍。
隐私设计要在功能设计之前。我们是先做了功能再补隐私控制,结果返工了两处存储结构。
目前这套记忆机制还有一块没解决好,就是组织级记忆。同一家客户公司的不同员工来提问,有些信息应该共享(比如公司的技术环境),有些不应该(比如个人的沟通偏好)。共享的粒度和授权模型还在跟客户讨论,短期内不会动。