大模型记忆系统架构解析:从向量检索到图数据库的工程实践
1. 项目概述:当大模型需要“记住”用户
在货拉拉这样日订单量巨大的同城货运平台,每天有海量的用户与司机在交互。想象一下,你是一位经常需要搬家或运送大件物品的用户,每次打开App,都希望系统能记住你的偏好:比如你习惯用哪种车型、你常联系的搬家师傅是谁、你上次反馈说某个司机服务特别好。对于司机而言,他们也希望平台能记住自己的服务习惯、常跑的路线、以及和哪些老客户合作愉快。这种“记忆”能力,是提升用户体验、构建平台粘性的关键。
然而,传统基于规则或简单标签的用户画像系统,在面对大模型驱动的智能客服、智能调度、个性化推荐等场景时,显得力不从心。大模型(LLM)虽然拥有强大的理解和生成能力,但其上下文窗口有限,且本质上是“无状态”的——它无法记住跨对话、跨会话的长期信息。这就是“大模型记忆系统”要解决的核心问题:如何让大模型拥有持续、准确、可用的长期记忆,使其能像一位熟悉的老朋友一样与用户互动。
货拉拉大模型记忆系统的工程实践,正是为了解决这一挑战。它不是一个简单的缓存或数据库,而是一套从信息提取、结构化存储、到高效召回的完整技术体系。简单来说,它的工作流是:从海量的多轮对话、用户行为、订单日志等非结构化数据中,智能地“提取”出有价值的记忆点(如用户偏好、关键事实、情感倾向),将其转化为结构化的“记忆单元”并存储;当用户再次发起对话或产生交互时,系统能根据当前上下文,从庞大的记忆库中精准地“召回”最相关的记忆,注入到大模型的提示词中,从而让大模型做出更个性化、更连贯的响应。
这套系统是AI工程化落地的典型代表,它连接了大模型的前沿能力与实际的业务需求,其背后的“提取”与“召回”环节,充满了工程上的权衡与巧思。接下来,我将深入拆解从提取到召回的全链路,分享我们在实践中趟过的坑和积累的经验。
2. 记忆系统的核心架构与设计思路
构建一个大模型记忆系统,首先要回答几个关键问题:记忆什么?怎么存?何时用?怎么用?这直接决定了系统的架构设计。
2.1 记忆的范畴与分类
并非所有信息都值得被记忆。盲目存储所有对话历史,会导致存储爆炸和召回噪音。我们根据业务价值,将记忆分为几个核心类别:
- 事实性记忆:用户明确陈述的、相对稳定的信息。例如:“我住在北京朝阳区三元桥”、“我公司经常需要运送打印机耗材”、“我对猫毛过敏”。这类记忆准确性要求最高。
- 偏好性记忆:用户通过行为或对话隐含表现出的倾向。例如:多次选择“厢式货车”、与特定司机师傅互评五星、在夜间下单频率更高。这类记忆需要从行为中挖掘。
- 情感性/关系性记忆:用户对平台、司机或某次服务的情感反馈。例如:“上次的李师傅非常细心,包装得很结实”、“雨天加价让我不太舒服”。这类记忆对于维护用户关系和提升服务质量至关重要。
- 会话性记忆:单次对话中涉及的上下文,主要用于保证当前对话的连贯性,通常具有较短的生命周期。
我们的系统主要聚焦于长期记忆,即事实性、偏好性和情感性记忆。这些记忆构成了用户的“数字画像”,是提供个性化服务的基础。
2.2 整体架构设计
整个记忆系统可以抽象为一个经典的“写-存-读”管道,但其每个环节都针对大模型的特点进行了特别设计。
[数据源] --> [记忆提取器] --> [记忆存储器] --> [记忆召回器] --> [大模型] (对话、行为日志) (LLM + 规则) (向量库 + 图数据库) (检索 + 重排) (提示词增强)写入路径(记忆提取):
- 数据源:实时对话流、用户行为事件(点击、下单、评价)、订单详情数据。
- 记忆提取器:这是系统的“感官”。它持续监听数据源,运用大模型(作为理解引擎)结合预定义规则,判断当前信息是否包含值得长期记忆的内容,并将其结构化提取出来。
存储层(记忆存储):
- 记忆库:采用混合存储架构。
- 向量数据库:用于存储记忆的嵌入式向量,核心服务于基于语义相似度的快速召回。我们选用的是Milvus,主要看中其在高维向量上的性能和在云原生环境下的成熟度。
- 图数据库:用于存储记忆之间的关系。例如,“用户A” - [偏好] -> “厢式货车”,“用户A” - [表扬过] -> “司机B”。这有助于实现基于关系的复杂召回(如“找到这个用户好评过的司机”)。Neo4j是当前的选择。
- 传统数据库/缓存:用于存储记忆的元数据(创建时间、类型、置信度、来源)和原始文本片段,提供精确的关键词查询和事务管理。
读取路径(记忆召回):
- 记忆召回器:这是系统的“思考”环节。当需要服务用户(如智能客服对话)时,召回器根据当前用户ID和对话上下文,生成一个或多个“查询”。
- 检索与重排:查询被同时发送到向量库(做语义搜索)和图数据库(做关系查询)。初步检索出的记忆候选集会经过一个“重排序”模型,综合考虑相关性、新鲜度、置信度等因素,选出Top-K条最相关的记忆。
- 提示词组装:最终选出的记忆被格式化成自然语言描述,作为“上下文”或“系统指令”的一部分,注入到大模型的提示词中,从而影响其生成结果。
设计思路的核心:这个架构的核心思想是解耦。提取、存储、召回各司其职,通过明确的接口连接。提取层专注于“理解”,存储层专注于“组织”,召回层专注于“关联”。这样的设计使得每个环节都可以独立优化、迭代和扩展。
3. 记忆提取:从混沌到结构化的关键一跃
记忆提取是整个系统的源头,决定了记忆库的质量。垃圾进,垃圾出。我们的目标是高精度、高覆盖地捕捉有价值信息。
3.1 基于大模型的智能提取
最初我们尝试过纯规则的方式(正则表达式、关键词匹配),但很快发现其局限性:无法理解语义、无法处理多样化的表达、召回率低。例如,用户说“我住望京”,和“公司在望京soho附近”,规则很难统一识别出“位置”这个记忆点。
因此,我们转向了以大模型为判断和抽取核心的方案。具体流程如下:
触发与过滤:并非所有对话流都需经过大模型,那样成本太高。我们首先设置一个轻量级规则过滤器,过滤掉明显无意义的会话(如“你好”、“在吗”),或业务确定性高的场景(如用户明确选择车型“小面”,可直接作为偏好记忆入库)。只有通过过滤的文本片段,才会进入大模型处理管道。
记忆点识别与分类:我们将提取任务设计成一个对大模型的指令微调任务。提示词(Prompt)大致如下:
你是一个专业的用户信息分析助手。请分析下面的用户对话,判断是否包含以下类别的、值得长期记忆的用户信息: - 个人事实(如常住地址、公司地址、联系方式、身体状况禁忌) - 服务偏好(如对车型、搬运服务、时间的偏好) - 情感反馈(如对司机、费用、平台功能的表扬或投诉) - 关系绑定(如指定某位司机服务) 如果包含,请严格按照JSON格式输出,包含字段:`memory_type`(记忆类型), `memory_content`(记忆内容文本), `confidence`(你的置信度,0-1), `expiration`(建议过期时间,如“永久”、“30天”)。 如果不包含任何值得长期记忆的信息,请输出:`{"memory_type": "none"}`。 对话文本:[用户输入的实际文本]我们使用货拉拉场景的对话数据,对如Llama 3、Qwen等基础模型进行了少量参数的微调(LoRA),使其更擅长识别货运领域的特定记忆点。
结构化与归一化:大模型输出的
memory_content可能是自然语言,如“用户说他住在朝阳区三元桥”。为了便于后续存储和召回,我们需要将其归一化为结构化数据。这一步通常结合一个小的文本解析模型或规则。- 示例:对于地址“朝阳区三元桥”,我们可能调用内部的地理编码服务,将其归一化为标准的
{city: “北京”, district: “朝阳区”, poi: “三元桥”}格式,并关联到用户的“常用发货地”属性。 - 对于偏好“喜欢用厢式货车”,则映射到用户画像的
preferred_vehicle_type: “box_truck”字段。
- 示例:对于地址“朝阳区三元桥”,我们可能调用内部的地理编码服务,将其归一化为标准的
3.2 提取环节的工程实践与挑战
挑战一:成本与延迟的平衡大模型API调用(即使是自研模型)有成本和延迟。我们的解决方案是:
- 异步批处理:非实时性要求的记忆提取(如从历史订单评论中挖掘情感记忆),采用离线批处理任务,夜间运行。
- 模型蒸馏:将微调后的大模型(教师模型)的知识,蒸馏到一个更小、更快的模型(学生模型)上,用于线上实时流量的初步筛选,只有高不确定性的case才fallback到大模型。
- 缓存策略:对频繁出现的、模式固定的记忆点(如各种地址表述),建立缓存,直接匹配,避免重复调用模型。
挑战二:提取的准确性与置信度模型可能会“幻觉”出不存在的记忆,或误判。
- 置信度阈值:为模型输出的
confidence设置阈值(如0.7),低于阈值的不入库,或进入人工审核队列。 - 多源验证:对于关键事实(如地址),尝试与用户档案中的已有信息交叉验证。如果冲突,则置信度降低,或触发澄清流程(例如让智能客服主动询问确认)。
- 持续监控与迭代:我们构建了一个记忆提取的评估看板,定期抽样检查准确率、召回率。将错误案例加入训练集,持续迭代微调模型。
实操心得:Prompt工程比想象中重要在微调模型之前,我们在Prompt上花了大量时间。一个清晰的、包含负样本示例的Prompt,能极大提升零样本或少样本下的提取效果。例如,在Prompt中明确写出“用户说‘今天天气真好’不属于任何记忆类别”,能有效减少模型对闲聊内容的误判。
4. 记忆存储:为高效召回设计的数据组织
存储不是目的,召回才是。因此,存储结构的设计必须服务于后续的检索需求。我们采用了“向量+图+关系型”的混合存储模式。
4.1 向量数据库:存储语义的“影子”
记忆的文本内容经过嵌入模型(Embedding Model)转化为高维向量(例如768维或1024维)。这个向量就是记忆的“语义影子”。
- 嵌入模型选型:我们测试了多种开源模型(如BGE、text2vec)和商用API。最终选择了在中文领域、特别是生活服务类文本上表现稳定的BGE模型,并针对货运场景的词汇(如“厢货”、“搬运费”、“里程”)进行了微调。关键是要保证同一语义的记忆(如“我要搬家”和“我有家具需要运输”)的向量距离尽可能近。
- 向量库的schema设计:在Milvus中,一个记忆条目不仅包含向量字段,还包含标量字段用于过滤。
{ “id”: “memory_123”, “vector”: [0.12, -0.05, …, 0.78], // 嵌入向量 “user_id”: “u_1001”, “memory_type”: “preference”, “content_text”: “用户多次选择厢式货车”, “source”: “order_history”, “timestamp”: 1698765432, “confidence”: 0.95 }user_id和memory_type是后续检索时最重要的过滤条件。我们通常按user_id进行集合分区,能大幅提升查询效率。
4.2 图数据库:刻画记忆的关系网络
这是让记忆“活”起来的关键。许多有价值的洞察隐藏在关系里。
- 图模型设计:
- 节点:用户、司机、车型、地址、物品类别等实体。
- 边:关系类型,如
HAS_PREFERENCE(用户->车型)、LIVES_IN(用户->地址)、PRAISED(用户->司机)、USED_FOR(车型->物品类别)。
- 应用场景:
- 协同过滤式召回:当用户A的记忆不足时,可以通过图查询“与用户A有相似偏好(例如都喜欢用某车型)的其他用户还喜欢什么?”,作为补充记忆。
- 复杂关系查询:“给我召回用户上次表扬过的、并且常跑中关村路线的司机”。这种多跳查询,用向量检索很难表达,用图数据库则非常自然。
- 记忆溯源与解释:当一条记忆被召回时,我们可以通过图数据库快速找到它的来源(哪次对话、哪个订单),增强系统的可解释性。
4.3 传统数据库:可靠的元数据管家
PostgreSQL用于存储所有记忆的完整元数据和原始文本。它是向量库和图库的“锚点”,提供:
- 精确查询:通过
user_id和memory_type直接拉取某个用户的所有偏好记忆。 - 事务支持:记忆的写入、更新、删除需要保证一致性。
- 备份与归档:存储完整的、不可变的记忆日志。
三者如何协同工作?当一条新记忆产生时:
- 写入PostgreSQL,生成唯一ID。
- 文本内容通过嵌入模型生成向量,存入Milvus,并关联PostgreSQL的ID。
- 记忆内容被解析出的实体(用户、司机、地址等)和关系,更新到Neo4j中。
注意事项:数据一致性是个大坑混合存储带来了数据一致性的挑战。我们采用了“异步最终一致性”策略。以PostgreSQL为“主记录”,任何记忆的增删改都以PostgreSQL的事务为准。然后通过消息队列(如Kafka),将变更事件发布出去,由消费者异步更新向量库和图库。这意味着在极短时间窗口内,三个库的数据可能有细微延迟,但对于记忆召回场景,这是可接受的权衡。关键是要有完善的数据监控和补偿修复机制。
5. 记忆召回:在正确的时间送上正确的记忆
召回是记忆系统的价值出口。目标是在低延迟(通常要求<100ms)的前提下,从海量记忆中精准找出最相关的几条。
5.1 召回链路详解
一次完整的召回请求通常由某个业务服务(如智能客服对话引擎)发起,参数包括user_id和当前的query(用户最新的一句话或对话上下文)。
查询构造:
- 基础查询:直接使用当前用户的
query作为向量检索的输入文本。例如,用户说“还是叫上次那种大车吧”,query就是这句话。 - 查询增强:为了提升召回率,我们会对
query进行增强。例如,使用大模型对query进行改写或扩展(同义词扩展:“大车” -> “厢式货车 大型货车”),或者利用图数据库,先查出该用户的常用地址、偏好车型,将这些信息拼接到query中,形成更丰富的检索文本。
- 基础查询:直接使用当前用户的
多路召回:
- 向量检索路:将增强后的
query转化为向量,在Milvus中搜索与该用户相关的(通过user_id过滤)、且向量距离最近的Top-N条记忆。这是召回的主体,负责捕捉语义相似性。 - 图检索路:在Neo4j中,以当前用户为起点,执行图遍历查询。例如:“查找该用户所有的
PRAISED关系,并返回被表扬的司机节点及其属性”。这条路负责捕捉明确的、结构化的关系。 - 关键词检索路(备用):在某些对确定性要求极高的场景(如用户明确说“我住在XX小区”),我们会直接用该关键词在PostgreSQL中进行精确或模糊匹配,作为兜底。
- 向量检索路:将增强后的
重排序: 多路召回会得到一个合并的、较大的候选记忆列表(比如50条)。直接返回前几条可能不是最优的,因为向量检索只考虑了语义相似度,忽略了其他重要因素。 我们训练了一个轻量级的重排序模型(例如基于Cross-Encoder的BERT小型模型),它的任务是对
(query, memory)进行打分。这个分数综合了:- 语义相关性:模型本身的核心能力。
- 记忆新鲜度:越近的记忆通常权重越高。
- 记忆置信度:提取时模型给出的置信度。
- 记忆类型优先级:事实性记忆可能比情感性记忆在特定场景下更重要。 经过重排序模型重新打分后,选出最终的Top-K(通常K=3~5)条记忆。
5.2 召回策略的精细化设计
不同的业务场景,需要不同的记忆召回策略。我们设计了一个可配置的“召回策略引擎”。
- 客服场景:优先召回“情感反馈”和“事实性记忆”。当用户表达不满时,如果能立刻回忆起“用户上周投诉过运费问题”,客服AI就能更体贴地回应。
- 推荐场景:优先召回“偏好性记忆”和通过图数据库发现的“协同过滤记忆”。用于推荐车型、增值服务等。
- 调度场景:优先召回与地址、时间相关的“事实性记忆”,以及用户与司机的“关系绑定”记忆,辅助调度系统做更人性化的派单。
在策略引擎中,我们可以调整各召回路的权重、过滤的记忆类型、重排序模型的特征权重等。这一切都通过配置中心动态管理,无需重启服务。
5.3 性能优化与缓存之道
召回链路的性能至关重要,尤其在高并发场景下。
- 向量检索优化:Milvus索引的选择(HNSW vs. IVF)和参数调优是基础。我们根据数据规模和召回延迟要求,选择了HNSW,因为它适合高召回率、低延迟的场景。同时,严格按
user_id分区,将搜索范围缩小到单个用户的数据子集,这是最大的性能提升点。 - 多级缓存:
- L1缓存(本地缓存):使用Guava Cache或Caffeine,缓存单个用户最近被召回的高频记忆(如常用地址)。过期时间较短(如5分钟)。
- L2缓存(分布式缓存):使用Redis,缓存经过重排序后的、用户维度的最终记忆集合。Key设计为
user:memory:{user_id}:{scene},过期时间根据场景设定(客服场景短,推荐场景可稍长)。 - 缓存更新:当系统写入一条新的高置信度记忆时,会主动失效该用户相关的缓存,确保下次召回能获取到最新记忆。
- 异步预加载:对于即将进入可能使用记忆场景的用户(例如,用户打开客服页面),可以提前异步触发一次轻量级的记忆召回,将结果预热到缓存中,从而在用户真正发起对话时实现“零等待”。
实操心得:重排序模型不必复杂,但数据要准我们最初尝试用非常复杂的模型做重排序,效果提升并不明显,反而延迟增加。后来发现,关键在于训练重排序模型所用的
(query, memory, relevance_score)数据对要高质量。我们通过大量业务人员标注,并结合线上点击、转化数据作为反馈信号,构建了高质量的训练集。一个在小而精的数据集上训练的简单Cross-Encoder模型,其效果远好于在嘈杂数据上训练的大模型。
6. 工程实践中的挑战与解决方案
在构建和迭代这套系统的过程中,我们遇到了无数坑,以下是几个最具代表性的挑战及我们的应对之策。
6.1 记忆冲突与消解
用户可能在不同时间说出矛盾的信息。例如,3月说“我住望京”,6月说“我搬去通州了”。系统里就会存在两条矛盾的“常住地址”记忆。
我们的解决方案:
- 置信度与时间加权:每条记忆都有置信度和时间戳。在召回时,对于同一类型的记忆,我们会进行聚合。例如,地址记忆,优先选择置信度高且更新的。也可以设计一个衰减函数,让旧记忆的权重随时间下降。
- 显式记忆优先:用户明确说“我搬家了,新地址是XXX”产生的记忆,其权重远高于系统从对话中推测出的地址记忆。
- 主动澄清机制:当系统检测到高置信度的新记忆与旧记忆直接冲突,且旧记忆近期被使用过,可以触发一个主动澄清。例如,让智能客服在对话中自然地问一句:“对了,看到您之前提过住望京,现在主要发货地址还是那里吗?” 用户的回答会产生一条更高置信度的新记忆,并标记旧记忆为“已覆盖”。
6.2 记忆的“保鲜”与遗忘
不是所有记忆都值得永久保存。用户的偏好会变,过时的记忆会产生干扰。
我们的解决方案:
- 显式过期时间:在提取阶段,模型或规则可以预测记忆的“保质期”。例如,“对某个司机的表扬”可能设置半年过期,“手机号”可能设置永久。
- 隐式衰减与淘汰:我们为每条记忆设计了一个“能量值”。每次该记忆被成功召回并得到用户正面交互(如用户确认、订单成交),能量值增加;长时间未被使用,能量值缓慢衰减。系统有一个后台任务,定期清理能量值低于阈值的记忆,或将其归档到冷存储。
- 场景化记忆生命周期:不同场景记忆生命周期不同。客服会话的上下文记忆,对话结束即失效;用户偏好记忆,则长期保留但会衰减。
6.3 系统可观测性与调试
记忆系统是个“黑盒”,如何知道它工作得好不好?为什么这次召回了这条记忆?
我们的解决方案:
- 全链路日志与Trace:为每一次记忆的提取、存储、召回请求分配唯一的Trace ID。记录下关键决策点的信息:提取时的原始文本、模型输出、置信度;召回时的查询语句、各召回路的结果、重排序分数等。这便于问题追踪和复盘。
- 记忆效果评估看板:我们定义了多个业务指标:
- 记忆利用率:被召回的记忆中,有多少比例最终被用于大模型生成并影响了用户交互?
- 记忆准确率:抽样检查被系统标记的记忆,是否真实准确?
- 业务指标提升:使用记忆系统后,客服解决率、用户满意度、推荐点击率等核心业务指标是否有提升?我们通过A/B测试来严格衡量。
- 记忆沙盒与调试工具:我们开发了一个内部工具,允许产品经理和工程师输入一个用户ID和模拟对话,实时查看系统会提取出什么记忆,以及召回链条的完整过程。这对于理解系统行为和调试策略至关重要。
6.4 安全与隐私考量
记忆系统涉及大量用户数据,安全和隐私是红线。
- 数据脱敏:在记忆提取后、存储前,对敏感信息(手机号、身份证号、详细地址门牌号)进行严格的脱敏处理。存储的是脱敏后的信息,仅在必要的、有严格权限控制的下游服务中,才通过令牌化的方式换取真实信息。
- 用户控制:提供用户隐私中心,让用户可以查看、管理甚至删除平台关于自己的“记忆”。这是合规要求,也是建立信任的关键。
- 访问审计:所有对记忆库的读写操作,都有完整的审计日志,确保可追溯。
7. 未来演进方向
目前这套系统已经稳定服务了货拉拉多个核心场景,但技术演进永无止境。我们正在探索以下几个方向:
- 记忆的主动应用与触发:当前的记忆召回是被动的,基于用户当前query。未来我们希望系统能更“主动”,例如,检测到用户频繁在雨天叫车,可以主动推送“雨天用车注意事项”或相关优惠;根据用户过去的投诉记忆,在类似场景出现风险时提前预警给客服。
- 多模态记忆:当前的记忆主要是文本。未来,用户上传的货物图片(识别出是易碎品)、沟通时的语音语调(识别出焦急情绪),都可以作为多模态记忆存储和召回,让记忆更立体。
- 记忆的推理与合成:系统不应只是记忆的“复读机”。我们希望它能对记忆进行简单的推理。例如,从“用户表扬了李师傅的搬运技术”和“用户抱怨王师傅搬运有磕碰”这两条记忆中,可以合成出一条更高阶的“用户非常看重搬运过程中的物品保护”的偏好记忆。这需要更复杂的图神经网络或大模型推理能力。
- 更轻量、更廉价的架构:向量数据库和图数据库的运维成本不低。我们正在评估新一代的、支持混合检索的一体化数据库,以及通过量化、剪枝等技术压缩嵌入模型和重排序模型,在保证效果的同时进一步降低成本。
构建大模型记忆系统,就像为AI打造一个不断成长的“数字大脑”。从精准的提取到智能的召回,每一步都充满了工程上的挑战与乐趣。这套系统没有银弹,必须紧密结合自身业务场景,从简单版本开始,通过持续的数据飞轮和算法迭代,逐步演化成熟。希望我们在货拉拉的这些实践,能为你带来一些启发。