尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

记忆系统基准测试:自建记忆图0.831 vs Memora 0.801,差异全解析

记忆系统基准测试:自建记忆图0.831 vs Memora 0.801,差异全解析 最近我做了一轮记忆系统基准测试把自己的记忆图实现和 Memora 放在同一组数据集上对比自建方案得分 0.831Memora 得分 0.801。差距不到 0.03这个结果不能说自研方案全面胜出更不能说明 Memora 不行。它真正说明的是两套方案在记忆抽取、存储结构和召回链路上的侧重点不一样。这篇内容就是把这轮测试从数据集设计、评测方法到实际复现步骤完整拆开适合正在做长期记忆、用户画像、多轮对话记忆或 RAG 增强的开发者看。最值得关注的不是那个 0.83 对 0.80 的结果而是“为什么会出现这种差异”以及“换一个数据集之后结果会不会翻过来”。1. 先搞清楚这次评测在测什么能力1.1 记忆图不是把文档塞进向量库很多人提到 AI 记忆第一反应就是 embedding 加向量检索。向量库确实能解决“哪段文本和当前问题语义最接近”但它解决不了“A 和 B 是什么关系”“C 项目用了哪些组件”“过去五次对话里用户的偏好是否发生过变化”这类需要跨片段关联的问题。记忆图的思路是先把文本里的实体、关系、事件、属性抽取出来形成类似“张三 - 就职于 - 某公司”“项目X - 依赖 - 框架Y”的三元组结构再用图去维护这些关系。相比纯向量检索记忆图有几项天然优势关系型问题可以直接沿着边回答不需要把所有相关文本都塞进上下文。同一实体的多条事实可以合并形成长期画像。多跳查询比向量检索更可控比如先找到“项目X的负责人”再找“这个负责人还负责过哪些项目”。但这不意味着图可以替代向量。实际工程里更常见的是图加向量的混合方案向量负责候选召回图负责关系扩展和结构化筛选。1.2 Memora 这类方案解决什么问题Memora 在帖子标题里是作为对照出现的。我没有办法确认它当前版本的所有功能只能把它理解为一个侧重长期记忆管理的方案通常这类系统会把历史内容整理成人设、偏好、事件时间线或者按会话摘要来组织记忆。这类方案的优势在“开箱即用”接入成本低不需要自己设计抽取 Schema也不需要维护一套实体对齐逻辑。你只要把内容丢进去它自己会生成可检索的记忆单元。适合快速验证一个想法也适合那些不想在图结构上投入太多精力的团队。但“开箱即用”的另一面是“内部逻辑不可控”。你很难知道它到底抽取了哪些关系也很难在丢分时定位是抽取问题、召回问题还是生成问题。这也是我坚持自建记忆图做对比的原因我想知道自己可控的部分到底能比通用方案高多少。2. 评测方法和数据设计比分数更重要2.1 评测集是怎么构造的这一轮测试用的不是公开 benchmark因为我想要的是“贴近长期记忆场景”的数据而不是通用问答数据。我构造的数据集包含三块内容20 份虚拟用户档案覆盖工作经历、技术偏好、社交关系。一批项目会议记录里面有明确的负责人、时间节点、依赖关系和决策理由。多轮技术选型讨论涉及候选方案、最终结论和替换原因。这 20 份档案不是单独存在的它们之间互相有引用。比如用户 A 是用户 B 的项目负责人用户 B 在会议里提到过某个老系统而这个老系统又出现在用户 C 的履历里。之所以这样设计是为了制造跨文档、跨会话的关系型问题不然记忆图根本没有发挥空间。最终我从这些材料里整理出 80 道记忆问题分为四类问题类型示例数量事实型哪个项目在 3 月进入测试阶段20偏好型用户 A 更倾向使用哪类数据库20关系型用户 B 和用户 C 在哪家公司共事过20多跳型用户 A 负责的项目依赖了谁主导开发的工具20事实型和偏好型相对容易靠向量检索摘要通常就能答对。关系型和多跳型才是区分两套系统的关键。2.2 打分标准怎么定我采用的做法是对每个问题由同一个答案生成模型分别读取两个系统召回的记忆块生成回答然后由一个裁判模型按 0 到 1 分打分。打分规则不是只看“语义是否差不多”而是拆成三个维度是否包含正确答案中的核心实体。实体之间的关系是否表达正确。是否引入了与问题无关的错误信息。每个维度可以得 0 分或 0.5 分三个维度相加后换算到 0 到 1。比如“哪个项目在 3 月进入测试阶段”正确答案是“订单系统 3 月进入测试阶段”。如果系统回答了“订单系统在 3 月进入开发阶段”实体对了但关系错误只能拿约 0.33 分。最终得分是 80 道题的平均值。自建记忆图是 0.831Memora 是 0.801。只看总分会觉得差距很小但分项差异其实更明显关系型问题上自建方案领先约 0.05事实型问题上 Memora 反而略微领先。2.3 不能只凭一个总分下结论0.831 和 0.801 的差距大概在 0.03。如果样本量不是 80 道题而是 20 道题这个差距很容易被一两个随机结果翻转。所以这次对比更像一次工程测试而不是严格的学术 benchmark。它不能证明谁的架构更好只能说明在这个数据集分布下自建记忆图的整体召回略好。关系型问题确实是图的优势区。两者的绝对差距不大说明通用记忆方案并没有被“吊打”。看这类评测时先看数据集分布再看分项分数最后才看总分。总分只适合判断“有没有明显短板”。3. 0.831 与 0.801 的差异到底从哪来3.1 抽取粒度和结构化程度不同自建记忆图在抽取阶段就强制输出三元组比如{subject: 订单系统, relation: 进入阶段, object: 测试} {subject: 用户B, relation: 曾经主导, object: 日志组件}这种结构化输出的好处是关系型问题可以走图查询而不是靠语义相似度猜。Memora 这类方案通常会把记忆整理为自然语言摘要比如“用户 B 之前主导过日志组件该组件在订单系统里被使用”。当问题明确要求“谁主导了日志组件”时摘要型记忆也能答对但当问题变成“用户 A 负责的项目依赖了谁主导开发的工具”时摘要里可能没有现成路径只能靠语言模型自己把多个摘要串起来容易丢中间环节。分项数据也印证了这一点多跳型问题上自建记忆图得分 0.79Memora 是 0.74。差距不算大但稳定出现在不同随机种子下。3.2 召回链路不同我的召回链路是先向量、后图扩展。具体来说用向量检索捞出和问题相关的 5 条三元组。把三元组里的实体作为种子节点做一跳邻居扩展。扩展后的节点再次映射回文本记忆块。最后把原始文本和三元组结构一起送给生成模型。Memora 的召回方式更接近“直接检索记忆块”没有额外的关系扩展。这也是为什么它在事实型问题上表现不差因为事实型问题通常只需要命中一段话但遇到需要拼接两段记忆的问题时它会稍微吃亏。图扩展不是越多越好。我在测试里试过二跳扩展结果多跳型分数没涨事实型分数反而掉了因为二跳邻居带进来很多与问题无关的历史信息生成模型被噪声干扰了。3.3 有些差距来自数据偏差和裁判偏差我必须承认这组测试数据本身更偏向图结构。20 份档案和会议记录里人物关系和项目依赖关系都写得很明确实体名称也相对规范。如果换成大量口语化聊天记录实体抽取会难很多图方案的优势会被削弱。裁判模型也可能存在偏好。如果裁判模型本身就擅长处理结构化输出它可能会给三元组结果更高的分。为了降低这种偏差我在两个系统里都只让生成模型输出自然语言答案裁判看不到候选是来自三元组还是摘要。但模型内部的偏好仍然没法完全消除。所以更稳妥的说法是这 0.03 的差距一部分来自关系召回的真实优势另一部分来自数据分布和评测方法带来的偏差。4. 自己复现一版最小记忆图4.1 环境准备这个方案不需要很高的硬件条件。我用的环境是Python 3.10 或更高版本。一个可以用 API 调用的 LLM负责抽取和答案生成。图存储用 NetworkX数据量不大时内存足够。向量索引用轻量方案即可比如 Chroma 或基于 TF-IDF 的检索器。如果只是做实验不需要上 Neo4j。NetworkX 配合 JSON 持久化足够跑完 80 道评测题。等到图节点到几十万级别时再换正式图数据库也不迟。4.2 抽取记忆三元组第一步是把原始文档切成长度合适的文本块。我按段落切而不是按固定 token 切这样可以尽量保证一个完整事实不被切断。然后对每个文本块调用 LLM抽取三元组。示例 Prompt 大致是这样请从下面的文本中抽取与长期记忆相关的事实只保留客观事实不要推测。 输出 JSON 数组每项包含 subject、relation、object。 如果同一主体有多个宾语分别输出多条三元组。 文本{text}对应的 Python 骨架import json def extract_memory(llm, text): prompt ( 请从下面的文本中抽取与长期记忆相关的事实只保留客观事实不要推测。\n 输出 JSON 数组每项包含 subject、relation、object。\n 如果同一主体有多个宾语分别输出多条三元组。\n 文本\n text ) resp llm.generate(prompt, temperature0) triples json.loads(resp) return [(t[subject], t[relation], t[object]) for t in triples]这里有两个容易注意不到的地方。第一温度必须设成 0 或接近 0。记忆抽取是确定性问题不希望模型每次对同一段文本输出不同实体。第二Prompt 里要限制“不要推测”。没有这个约束模型会脑补出材料里不存在的关系比如把“项目 A 可能使用项目 B”当成确定事实。这类幻觉会直接影响后续召回质量。4.3 构建图索引和向量索引拿到三元组后先做一轮实体名归一化否则“NVIDIA”“nvidia”“英伟达”会被当成三个节点。import networkx as nx graph nx.MultiDiGraph() def normalize_entity(name): name name.strip().lower() # 这里可以加规则去掉公司后缀、全角转半角、同义词映射等 return name for subj, rel, obj in triples: s normalize_entity(subj) o normalize_entity(obj) graph.add_node(s, typeentity) graph.add_node(o, typeentity) graph.add_edge(s, o, relationrel)同时把每个三元组成一种可检索的文本memory_units [] for subj, rel, obj in triples: memory_units.append(f{subj} {rel} {obj})这些文本写入向量索引。我用的是 Chroma因为本地运行省事。生产环境可以换成更重的向量库但实验阶段不必过度设计。4.4 用图扩展召回相关记忆召回阶段的核心逻辑是先用向量找种子再用图做扩展。def recall(question, vec_index, graph, top_k5, max_hops1): seed vec_index.query(question, ktop_k) candidates set(seed) if max_hops 1: for node in seed: neighbors graph.neighbors(node) candidates.update(neighbors) # 也可以把入边节点加入进来这里简化处理 result [] for item in candidates: if item in graph.nodes: for edge in graph.out_edges(item, dataTrue): result.append(edge) return result这一步是记忆图相对纯向量检索的核心差异。向量只负责“找到相似”图负责“从相似实体继续往外走”。需要注意 hop 数量的选择。我实测下来max_hops1 最稳。扩展成二跳会有提升但伴随着噪声变多生成答案时经常把不是重点的历史信息也带进去。如果你的问题集里多跳问题很少建议直接从一跳开始。最后把命中的三元组和对应原始文本块一起拼进 Prompt交给生成模型基于以下记忆内容回答问题。请直接给出答案不要解释推理过程。 记忆内容 {retrieved_memories} 问题{question}4.5 评测打分评测打分阶段我用另一个 LLM 实例做裁判。裁判输入包括问题、标准答案、系统生成答案。裁判不会看到存储结构避免格式偏好。def judge(question, ground_truth, answer): prompt f对比标准答案和模型回答从三个维度打分。 1. 是否包含核心实体如果是打0.5否则0。 2. 实体关系是否正确如果是打0.5否则0。 3. 是否包含错误信息如果没有打0.5包含则0。 输出JSON包含维度分和总分。\n问题{question}\n标准答案{ground_truth}\n模型回答{answer} result llm.generate(prompt, temperature0) return json.loads(result)这里有一个公平性问题如果裁判和生成模型是同一个模型裁判可能对同模型的表达方式更友好。我在这次测试里没有完全隔离只用了不同 Prompt 和不同会话。严谨的评测应该使用不同厂商的模型或至少使用不同系列的模型。5. 哪些参数真的会影响最终分数5.1 关键参数清单以下参数是我在调这轮评测时实际关注过的参数推荐初始值影响抽取温度0温度高会引入实体幻觉文本切片方式按段落切按 token 切容易切断实体关系向量召回 top_k5太小会漏掉关系种子太大会引入噪声图扩展 hop10 就是纯向量检索2 开始噪声明显生成答案温度0评测阶段必须可复现抽取模型和生成模型同梯队抽取能力差会直接拖低后续所有环节评测样本量至少 30 题少于 30 题的差距基本没有统计意义5.2 怎样判断结果稳定如果只看一次评测0.831 对 0.801 可能只是偶然。我建议至少跑三到五次每次更换随机种子或者打乱问题顺序。然后看两点总分均值差是否保持同向。关系型和多跳型的分项是否稳定领先。如果总分有时赢、有时输说明两套系统在目标数据集上能力接近没必要折腾自建方案。如果关系型稳定领先但事实型落后就要评估你的实际场景更需要哪类记忆能力。还要记得做一次“零样本基线”不接任何记忆系统直接把问题丢给生成模型。这能告诉你多少问题是靠模型常识答对的。如果零样本基线已经到 0.7说明你的记忆系统只贡献了 0.1 的提升这时得换个更难的数据集。6. 常见踩坑和排查顺序6.1 分数偏低先查哪个环节我踩过最深的坑是自建记忆图得分反而低于 Memora。最后排查发现问题不在图模块而在抽取环节。Prompt 里没加“不要推测”模型抽出了大量“可能、也许、倾向于”这类事实导致问答时答案虽然流畅但实体和关系跟标准答案对不上。如果分数异常按下面的顺序排查先看抽取结果。直接打印 10 条三元组看是否覆盖了原始文本里的关键实体。再看召回结果。对某一道关系题把命中的三元组单独输出看是否包含标准答案里的实体。然后看生成结果。如果召回里有正确答案但生成答错可能是 Prompt 拼接方式有问题或上下文太长导致模型忽略关键信息。最后判断裁判是否误判。抽取 10 道低分题人工复核确认打分是否合理。大多数记忆系统问题都出在抽取和召回而不是生成。6.2 公平对比需要控制哪些变量做 A/B 对比时最容易犯的错误是只换了记忆库其他环节全都放任不管。这会导致分数差异根本说不清来源。建议至少固定以下变量生成答案的模型必须相同。裁判模型必须相同。所有模型温度设成 0。两套系统面对的是同一版原始文本。问题顺序最好打乱避免“先问了 A再问 B”时模型上下文互相影响。另外要防止数据泄漏。评测问题不能提前进入记忆库否则就变成了“开卷考试”。我在构造评测集时会把 80 道题的问题文本单独隔离确保记忆图只看到原始档案和会议记录不看到问题和答案。7. 真正落地时比分数更重要的几件事7.1 增量更新比一次性索引更关键评测环境里记忆是静态的文档一次性写入然后开始提问。生产环境不一样用户每说一句话每来一份文档都要更新记忆。这时图方案的优势会变成负担同一个实体的新事实可能和旧事实冲突比如用户之前说“部署在 AWS”后来改成“迁移到自建机房”。如果不做冲突处理图里会同时存在两条矛盾边召回时模型既看到旧信息又看到新信息答案就会摇摆。我建议给每条边加上时间戳和来源 ID查询时优先取最新时间戳。这个处理在实验里不会影响 0.831 和 0.801 的差距但直接影响上线后的体验。7.2 隐私和删除能力决定了能不能上线记忆系统存的是用户历史这意味着必须有删除能力。向量库里删除一条记录相对简单图结构里删除一个实体要同时删除它关联的所有边。更麻烦的是一条记忆可能来自多条来源比如“用户 B 主导日志组件”既出现在会议纪里也出现在用户 B 的简历里。删除时如果只删一个来源另一个来源又会让这条记忆复活。所以在建图时就要保留来源列表而不是只存三元组。每条边维护一个 source_ids 列表删除请求到来时把所有关联来源都清掉再判断是否删除节点。这个能力如果后期补会很痛苦。7.3 不是所有场景都适合记忆图这次测试里自建记忆图略微领先不代表它适合所有项目。如果你的场景只是“让 AI 记住用户喜欢喝什么咖啡”一个 JSON 文件就够了不需要图。如果对话历史短每次问答只需要最近几轮也不需要用记忆图直接把上下文拼进 Prompt 反而更简单。记忆图最适用的场景是大量历史信息、跨文档关系、关系型提问频繁、需要定期更新和冲突合并。不符合这些条件时Memora 这类通用方案更省事。最后留一句我这轮测试的总结0.831 对 0.801 的结果说明自建方案在关系记忆上有一定优势但这优势建立在可控的抽取、规范的实体归一化和合理的图扩展之上。真要在项目里落地最该盯住的不是谁比谁高 0.03而是历史更新、冲突合并和删除链路有没有做好。
返回列表