ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Memory Graph与Memora对决:0.831背后的技术链路与复现指南

2026/8/30 13:22:03 拓冰建站 浏览量
Memory Graph与Memora对决:0.831背后的技术链路与复现指南 一个自研 memory graph 系统的 benchmark 结果最近被摆到了台面上作者用自己实现的记忆图谱方案和 Memora 做同场评估得到 0.831 vs 0.801。单看数字领先只有 0.03感觉不大但这类系统的对比真正的看点从来不在那一点分差而在背后的整条链路——实体怎么抽、关系怎么存、记忆怎么召回、上下文怎么回填。分数只是链路质量的最终投影。这篇文章不会替原作者公布他根本没有放出的源码也不会把两个数字当成“谁强谁弱”的结论。更值得做的是把 memory graph 这类项目拆开讲清楚它为什么会比普通向量记忆更耐测以及如果你想在本地复现一套同类系统并跑出可信的 benchmark应该准备什么、按什么流程验证、最容易在哪个环节翻车。看完之后你至少能判断0.831 和 0.801 这种对比到底值不值得信。先说明一个前提本文所有代码和配置都是通用实现思路不是某个具体开源仓库的原始内容。你可以把它当成一份“memory graph 复现与评测路线图”来用。1. 先看 benchmark0.831 vs 0.801 意味着什么从标题可以提取三个关键事实项目类型是 memory graph对比对象是 Memora评估结果是 0.831 对 0.801。如果按常见的记忆系统评估指标来理解这个分数大概率是准确率、召回率或 F1 中的某一项在 0 到 1 的区间内0.80 以上已经属于“可用但仍有明显错误”的水平0.83 则是略好一档。先把领先幅度算清楚(0.831 - 0.801) / 0.801 ≈ 0.0375即相对提升约 3.7%3.7% 的相对差距在信息检索和对话记忆任务里算不上“代差”但已经足够说明两个系统在同等条件下的输出质量存在可测差异。真正决定这个差异是否可信的是下面这个清单检查项说明数据集是否一致两个系统必须用同一批对话记录、同一批查询问题否则没有可比性Prompt 是否一致实体抽取和问答生成阶段Prompt 差异会直接影响分数评估脚本是否共用自动评估的打分逻辑、指标口径、后处理方式必须完全一致是否多次运行取均值LLM 推理有随机性单次结果不能代表真实水平是否排除数据泄漏训练数据或示例数据不能出现在评估数据中是否控制 token 上限一个系统能看 8k 上下文另一个只能看 2k结果自然不同0.831 和 0.801 并不可怕可怕的是没有人能解释这个分数是怎么来的。所以本文后续会给出一个能够自证的 benchmark 流程让每个模块的贡献都可以被复查。2. 什么是 memory graph比向量记忆强在哪很多对话系统现在的记忆方案是“对话文本切块 - 向量化 - 存向量库 - 检索相似片段”。这种方案实现简单但有两个明显问题第一多个片段里出现同一个实体时无法自动合并第二只支持相似度召回不支持关系推理。memory graph 的思路完全不同。它会把对话内容解析成一组结构化的三元组实体、关系、实体。例如用户 提到 项目A 项目A 使用 技术B 用户 偏好 Python 项目A 开始于 2024-03这些三元组被写入图结构后可以回答三类问题直接召回用户偏好什么语言- Python关系查询项目A 用了哪些技术- 通过“项目A - 使用 - 技术”这条边展开多跳推理用户提到过哪些与技术B 相关的项目- 从技术B反查项目A普通向量检索面对“多跳问题”时通常需要同时召回多个片段再靠 LLM 拼凑而图记忆天生就把关系存在边上查询路径更短。维度向量记忆Memory Graph存储结构向量 原始文本片段实体节点 关系边 属性检索方式相似度 Top-K相似度召回 图遍历 重排实体去重依赖文本相似效果一般可显式合并同一实体关系推理弱需要 LLM 二次加工强图结构直接支持时间线维护难片段之间没有因果关系可以在边上存储时间戳和事件实现成本低中高需要抽取和存储逻辑从 0.831 这个分数来看作者大概率不是简单把文本塞进图数据库而是在抽取精度、去重策略、检索路径上做了优化。这类系统最适合的场景是智能助手长期记忆、个人知识库、多轮对话中的用户画像维护、企业内部文档关系梳理。3. 从标题反推项目架构模块与数据流标题里只有“memory graph”和“benchmark”两个关键词但足以反推一个完整系统应该包含的模块。memory graph 项目的标准数据流如下输入对话/文档 - 信息抽取LLM 抽取实体、关系、属性 - 图写入去重、合并、建立索引 - 检索触发收到用户提问 - 候选召回图遍历 向量相似度 - 重排筛选相关性、时间权重、置信度 - 生成回答把记忆片段注入 Prompt - 输出答案并评估每个环节都可以单独测试也可以联合评测。模块划分如下模块核心职责常用实现方式信息抽取从非结构化文本中识别实体和关系LLM JSON Schema或微调 NER 模型图存储保存实体和关系支持查询Neo4j、NetworkX、EdgeDB实体合并处理同义实体避免节点膨胀规则 向量相似度 LLM 判断记忆检索根据查询找到相关记忆子图BFS/DFS 遍历 向量 Top-K上下文组装把子图转成自然语言片段模板序列化评估模块计算召回、准确率、F1脚本 人工标注样本集从工程角度看抽取模块是整个系统的天花板。如果实体抽得不准后面图存得再漂亮检索结果也是垃圾。所以 benchmark 里 0.831 vs 0.801 的差距最可能首先出在抽取和实体合并策略上。4. 本地复现同类记忆系统的环境准备如果你想自己搭一套 memory graph 并做对比测试不需要一开始就上 Graph Database先用内存图结构跑通流程再考虑 Neo4j 这类重组件。下面是一份通用环境清单。操作系统Windows / macOS / Linux 均可。语言版本Python 3.10 或更高。核心依赖pip install networkx langchain openai ollama pydantic pandasnetworkx内存图结构适合原型验证langchain封装 LLM 调用链不强依赖openai 或 ollama负责实体抽取和答案生成pydantic校验 LLM 输出的结构化 JSONpandas统计评估结果如果选择 Neo4j 作为正式存储需要额外安装pip install neo4j同时在本地启动 Neo4j 服务或使用 Dockerdocker run -d --name neo4j \ -p 7474:7474 -p 7687:7687 \ -e NEO4J_AUTHneo4j/testpassword \ neo4j:5-community硬件要求纯 API 方案无 GPU 要求API 模型负责抽取和生成内存 16G 足够。本地小模型抽取建议 8G 显存以上可以运行 7B 级别的量化模型做实体抽取。本地大模型推理取决于模型规模14B 以上建议 24G 显存或者使用 CPU 推理但接受较低速度。说明具体显存占用会因模型量化方式、上下文长度和并发数而变化实际以本机 nvidia-smi 或任务管理器为准不建议照搬任何固定数字。端口方面LangChain 默认服务可能占用 8000Neo4j 默认使用 7474/7687如果你本地已经跑着其他服务要提前检查冲突。5. 最小 Memory Graph 原型搭建为了理解 0.831 这种分数是怎么产生的我们先动手搭一个最小原型。下面代码使用 NetworkX 存储图用 LLM 完成实体抽取然后实现基础写入、检索和序列化。这不是某开源项目的原始实现而是一个可运行的简化版本。import json import networkx as nx from pydantic import BaseModel, Field from typing import List, Optional class Relation(BaseModel): head: str Field(description头实体) relation: str Field(description关系名) tail: str Field(description尾实体) timestamp: Optional[str] Field(defaultNone, description事件时间可为空) class ExtractionResult(BaseModel): relations: List[Relation] entities: List[str] Field(description出现的所有实体列表) class MemoryGraph: def __init__(self): self.graph nx.MultiDiGraph() self._entity_aliases {} def add_relations(self, result: ExtractionResult): for ent in result.entities: self._upsert_entity(ent) for rel in result.relations: self._upsert_entity(rel.head) self._upsert_entity(rel.tail) self.graph.add_edge( self._canonical(rel.head), self._canonical(rel.tail), relationrel.relation, timestamprel.timestamp, ) def _upsert_entity(self, name: str): name name.strip() if not name: return if name not in self.graph: self.graph.add_node(name) def _canonical(self, name: str) - str: name name.strip().lower() return self._entity_aliases.get(name, name) def recall(self, query_entities: List[str], max_depth: int 2): 从查询实体出发做有限深度遍历返回相关子图节点和边 nodes set() edges [] for q in query_entities: q q.strip().lower() if q not in self.graph: continue visited set() frontier {q} for _ in range(max_depth): next_frontier set() for node in frontier: if node in visited: continue visited.add(node) nodes.add(node) for _, target, data in self.graph.out_edges(node, dataTrue): edges.append((node, target, data[relation])) next_frontier.add(target) for source, _, data in self.graph.in_edges(node, dataTrue): edges.append((source, node, data[relation])) next_frontier.add(source) frontier next_frontier return {nodes: list(nodes), edges: edges} def to_context_text(self, subgraph) - str: 把子图序列化成 LLM 可读的自然语言片段 lines [] for h, t, rel in subgraph[edges]: lines.append(f{h} --[{rel}]-- {t}) return \n.join(lines) if lines else 无相关记忆这个原型足够跑通“写入记忆 - 查询召回 - 序列化上下文”的流程。真实项目会把实体抽取替换成更精细的 LLM 调用并用 Neo4j 替代 NetworkX但整体思路一致。接入 LLM 做抽取时可以让模型输出符合ExtractionResult结构的 JSONimport openai client openai.OpenAI( base_urlhttp://localhost:11434/v1, # 这里是 Ollama 的通用地址示例 api_keyollama ) EXTRACTION_PROMPT 请从下面的对话记录中抽取实体和关系。 实体包括人名、项目名、技术名、地点、时间等名词。 关系需要是简洁的谓词例如“使用”“偏好”“开始于”。只输出 JSON。.format() def extract_with_llm(text: str): resp client.chat.completions.create( modelqwen2.5:7b, messages[ {role: system, content: EXTRACTION_PROMPT}, {role: user, content: text} ], response_format{type: json_object}, temperature0.2, ) data json.loads(resp.choices[0].message.content) return ExtractionResult(**data)注意这里的base_url和model只是示例实际项目中需要替换成你本地可用的模型服务地址和模型名。更稳妥的做法是先不依赖本地模型直接用兼容 OpenAI 格式的 API把基座模型换成一个抽取能力强的大模型。6. 搭一个能自证的 benchmark 流程有了原型接下来就是回答核心问题如何得到像 0.831 vs 0.801 这样的分数并且让这个分数经得起质疑。benchmark 的第一步是固定数据集。建议准备三类数据多轮对话记录模拟用户与助手的长期交流包含偏好、项目进展、人物关系。文档片段描述技术选型、产品历史、团队结构。测试查询集覆盖直接事实问答、时间线查询、多跳关系推理三类问题。每个测试查询必须带有标准答案例如{ query: 用户最常用的编程语言是什么, expected: Python, type: direct_fact }第二步是定义评估指标。推荐三个维度指标计算方式适合场景准确率回答正确的百分比直接事实问答召回率正确答案条目中被系统找到的比例关系推理、多候选问题完整性回答是否覆盖了标准答案的所有要点复杂问题第三步是控制变量。两个系统对比时除了记忆实现不同其余条件全部固定同一个 LLM 生成答案、同一个 Prompt 模板、同一个评估脚本。如果自研 memory graph 用 gpt-4o而 Memora 用大 context 模式差别就不代表架构优劣。下面是评估脚本的核心逻辑import json import statistics def evaluate_answer(predicted: str, expected: str) - float: 简化评估检查预期答案是否出现在预测结果中 if not expected: return 0.0 return 1.0 if expected.lower() in predicted.lower() else 0.0 results [] test_set json.load(open(test_queries.json, r, encodingutf-8)) for item in test_set: # 每个系统都要跑一遍 predicted_graph run_memory_graph(item[query]) # 自定义函数 score_graph evaluate_answer(predicted_graph, item[expected]) predicted_memora run_memora(item[query]) # 被对比系统的调用接口 score_memora evaluate_answer(predicted_memora, item[expected]) results.append({ query: item[query], graph_score: score_graph, memora_score: score_memora, }) graph_avg statistics.mean([r[graph_score] for r in results]) memora_avg statistics.mean([r[memora_score] for r in results]) print(fmemory graph: {graph_avg:.3f}) print(fmemora: {memora_avg:.3f})这里run_memory_graph和run_memora都需要你自己实现分别封装两套记忆查询逻辑。如果需要更高可信度每一条 query 重复 3 次取平均值同时把日志完整保存下来。7. 解读一轮测试结果分数是怎么算出来的假设你跑了 100 条测试问题memory graph 答对 83 条Memora 答对 80 条那么准确率就是 0.830 和 0.800和标题里的 0.831 / 0.801 处于同一量级。注意0.831 不是 83.1% 的“标准答案”它只是某个指标在某个数据集上的均值可能是 F1可能是加权准确率也可能是把多项指标合成后的结果。在解读分数时有四个原则0.03 的差距只在相同口径下有意义跨数据集对比没有说服力。要看失败样本分布。如果 memory graph 在 83 个正确回答里全部集中在简单问题上而 Memora 在复杂多跳问题上更好那么平均分高并不能说明系统更强。要看错误类型。常见错误包括实体抽错、关系方向反转、时间戳丢失、多跳路径断裂。要看稳定性。跑 5 次如果分数在 0.80 到 0.86 之间大幅波动说明系统对随机性敏感单次 benchmark 不可信。建议在 benchmark 报告中至少包含错误样本分析表问题类型错误数典型错误直接事实7实体名被合并错误关系推理9关系方向反转多跳查询12遍历深度不足时间线5时间戳遗漏或错误分数只是起点错误样本才是改进系统的地图。8. 资源占用与性能观察memory graph 类项目在资源占用上和普通 RAG 有较大差异。普通 RAG 的主要开销是向量索引和文本存储memory graph 的主要开销则集中在三个方面实体抽取阶段。每段对话都要调用 LLM 抽取实体和关系token 消耗比单纯向量化高很多。如果使用的是本地模型抽取速度会直接成为瓶颈。观察指标包括抽取一条对话的耗时、prompt token 用量、输出 token 用量。降低开销的方法是批量抽取把多段对话一次性发给 LLM用 JSON 数组返回减少请求次数。图存储阶段。NetworkX 原型的内存开销会随节点和边的数量线性增长。几万条边以内问题不大超过百万条边建议切换到 Neo4j 或图数据库服务。观察指标节点数、边数、重复实体的合并率。如果实体别名过多说明抽取阶段的实体合并策略不完整。检索阶段。图遍历深度、候选节点数量和向量召回 Top-K 决定了检索延迟。遍历深度从 2 提高到 3效果可能变好但耗时可能翻倍。观察指标单次查询的 p50/p95 延迟、召回子图的大小。一个稳妥的调优顺序是先固定遍历深度为 2评估召回率。再提升实体合并质量减少冗余节点。然后增加向量候选召回作为补充通道。最后才考虑增加深度或并发。不要一上来就堆资源。0.831 这个分数如果真的是靠大量实体合并规则和精确抽取刷出来的那么复现它最重要的工作是在数据质量和抽取 Prompt 上而不是在显存上。9. 常见问题与排查方法在复现或构建 memory graph 的过程中下面几个问题最容易出现。问题现象可能原因排查方式解决方案抽取出的实体关系全是垃圾Prompt 没有给示例或模型太小打印抽取原始 JSON抽查 10 条在 Prompt 中加 few-shot 示例或换更大模型同一实体在图中出现多个重复节点缺少实体合并逻辑导出全量实体检查命名分布增加别名表用 embedding 相似度做合并查询时召回不到相关记忆查询实体和图里实体无法对齐打印查询实体列表和召回子图增加查询扩展用向量召回补充图遍历多跳推理总是断链关系方向抽取错误检查典型错误样本中的边方向在抽取 Prompt 中强化方向约束回答质量不稳定上下文序列化顺序无规则固定随机种子观察多次输出对上下文片段按相关性和时间排序本地模型推理很慢并发低或使用了 CPU 推理查看 nvidia-smi确认 GPU 利用率缩小模型量化等级或改用 API评估分数忽高忽低测试集太小或 LLM 随机性过大增加测试集到 100 条以上重复跑 3 次温度调整为 0多次运行取均值服务启动后端口冲突上一次进程未退出或端口被占用查看端口监听状态更换端口如--port 7861实体合并是 memory graph 里最容易被低估的环节。许多人一开始只做规则归一化比如全小写、去空格。但对“Python”和“python”这种大小写差异以及“OpenAI”和“Open AI”这类拼写变体规则只能覆盖一部分。更稳妥的做法是维护一个别名映射表再结合向量相似度判断是否属于同一实体。实体合并做不好图里的节点会迅速膨胀召回准确率降低最终分数自然上不去。另一个常见坑是时间戳丢失。对话记录里常常含有多个事件时间“上个月开始”“昨天改成了”“2025-03 立项”。如果不把时间解析成标准格式并存储到关系边上时间线查询基本无法完成。在信息抽取阶段给 LLM 的 Prompt 里要明确要求如果文本中出现时间表达必须给出标准时间戳。没有时间属性的记忆图只能回答静态关系无法回答“项目从什么时候开始”这类问题。10. 最佳实践与隐私合规memory graph 存储的是用户偏好、对话内容、人物关系、项目内部信息这类数据天然属于敏感数据。无论是一个本地测试项目还是一个准备上线的记忆系统都绑着隐私责任。使用边界方面建议遵守以下原则数据最小化只抽取系统运行所需的信息不要记录与功能无关的敏感字段。明确授权涉及真实用户数据时必须获得授权明确告知用户数据将被存储和被使用的方式。可删除性用户要求删除记忆时要能定位到对应实体、关系并彻底删除。图结构的相关性删除比向量库删除更复杂需要写递归删除逻辑。审计与透明记录记忆写入的来源和修改时间便于追溯和纠错。版权合规如果输入数据来自书籍、论文、企业内部文档或他人内容需要确认是否允许用于模型抽取和商用。肖像与身份信息如果记忆系统中包含人脸、声音、身份证号、银行卡号等身份信息必须做脱敏处理并遵循相关规定。工程层面建议从一开始就为每条记忆写入源头 ID。例如用一个source_id字段记录这条实体关系来自哪段对话、哪个文档。这样当某条记录需要修正或删除时可以准确地找到所有相关节点。同时在写图之前增加一层抽检逻辑随机抽 5% 的抽取结果由人工或规则检查实体、关系、时间戳是否正确。这个步骤会显著提高记忆图的质量。对于 benchmark 本身建议把测试集、评估脚本、两次评估的日志全部保存。如果未来有人引用 0.831 vs 0.801 这个结果你至少要能说明数据集是什么、评估指标怎么算、每个系统的调用参数是什么、跑了几次、方差多大。缺少这些信息的分数只能当成宣传数据不能当成工程结论。11. 总结与建议memory graph 能够在上一个 benchmark 对比中拿到 0.831说明这条技术路线在对话记忆、关系推理、长期记忆这类场景里确实有竞争力。但 0.831 和 0.801 的分差只有在完整复现了同样评估条件之后才有意义。真正值得学习的是它的架构思路抽取不用求全但求准存储不用复杂但要支持关系查询检索不用贪深但要能覆盖多跳路径。如果你打算试水这个方向第一步不是去复现原作者的全部代码而是先跑通一个最小系统用 NetworkX 存图用一个本地 LLM 或 API 做抽取准备 30 到 50 条测试问题跑出第一版本分数。然后集中精力优化实体合并和 Prompt观察指标变化是否合理。这个过程能让你快速判断 memory graph 是否适合你的实际场景也能帮你理解那些 benchmark 数字背后到底有多少工程细节。如果后续作者放出了完整的源码和测试集建议按本文的验证流程重新跑一遍先确认数据集口径再检查评估脚本最后复现分数。能复现的 benchmark 才算数不能复现的 0.831只能当个参考值。