← 返回文章列表
Agent 记忆评测:基准、数据与方法 · 第 3 篇

LongMemEval:把长期记忆拆成检索与回答

LongMemEval 分别评估证据是否被找回、模型拿到证据后是否答对,让长期记忆的失败可以定位到检索或阅读阶段。本文结合真实样本拆解数据、指标和接入门槛。

Agent Memory
Benchmark
RAG
合集目录:Agent 记忆评测:基准、数据与方法第 3 / 8 篇

调研对象:LongMemEval(Benchmarking Chat Assistants on Long-Term Interactive Memory),arXiv:2410.10813,ICLR 2025。 材料:论文 + 项目主页 + 本地代码逐文件审计 + 本地真实数据逐样本走查。代码引用给出 GitHub 行号链接;本地统计与手算来自离线复现。 贯穿全文的例子:真实样本 edced276——「我在夏威夷和纽约一共旅行了几天」,答案 15 days。

摘要

LongMemEval 面向聊天助手的长期记忆评测。它包含 500 道题,每道题都带有约 50 个 session 的独立聊天历史,答案埋在其中一到两个 session 中。与只看最终答对率的基准不同,它分别检查证据是否被检索出来,以及模型拿到材料后是否回答正确,因此能把失败定位到检索或阅读阶段。

论文在同一批题上报告:GPT-4o 直接拿到证据 session 时答对 92%,需要从完整历史中自行寻找时约为 58%(论文)。基于这一拆分,论文还比较了存储粒度、检索键扩展、时间范围提示和 Chain-of-Note:round 粒度保留原文优于整段 session 或只存 fact;为检索键补充 fact 后 Recall 约增加 4%、QA 约增加 5%;时间范围提示使 temporal recall 提升 7 到 11 个百分点;先做笔记再回答时,reading accuracy 最高增加约 10 个点。

这套评测有一个明确的接入门槛:检索结果必须能指回原始 session ID。保留原文的 RAG 系统天然满足,压缩或改写记忆的系统需要补充来源标注,黑盒产品通常只能跑 QA 部分。此外,single-session-assistant 类题的证据来自助手侧,而官方索引主要收用户侧,相关检索指标覆盖不足;数据生成代码未公开,现有数据也都是英文合成历史。

LongMemEval 适合诊断存原文或 RAG 型记忆的检索与阅读质量。若要检查抽取、更新阶段的幻觉,HaluMem 更直接;若研究超长对话中的连续叙事和多模态信息,LoCoMo 的覆盖更广。

下面先把数据、评测流程和指标放到同一张图中,再进入样本与实现细节。

全文阅读地图:先分清数据、评测和指标,再讨论优化、边界与 benchmark 选型。

图 1 全文阅读地图:先分清数据、评测和指标,再讨论优化、边界与 benchmark 选型。


1. 一道题长什么样

1.1 玩法与真实样本

LongMemEval 的玩法是「记忆版大海捞针」:把答案埋进几十个 session 的聊天历史,看系统捞不捞得出来、捞出来答不答得对。

全文用同一道真实样本讲解。edced276 问「我在夏威夷和纽约一共旅行了几天」,答案 15 days——纽约 5 天、夏威夷 10 天,分别在两次不同日期的聊天里被顺口提过。系统必须把两条都找回来,再做一次加法。

{
  "question_id":   "edced276",
  "question_type": "multi-session",
  "question":      "How many days did I spend in total traveling in Hawaii and in New York City?",
  "answer":        "15 days",           // ← 只给 judge,系统看不到
  "question_date": "2023/05/30 (Tue) 14:53",

  // 三个 haystack_* 数组按下标一一对应:第 i 个 id ↔ 第 i 个日期 ↔ 第 i 段对话
  "haystack_session_ids": [
    "ultrachat_470709",      // 干扰
    "answer_60e8941a_2",     // 证据:纽约 5 天(answer_ 前缀表示证据)
    "5774bbb1",              // 干扰
    "answer_60e8941a_1"      // 证据:夏威夷 10 天
  ],
  "haystack_dates":    ["…", "2023/05/21 …", "…", "2023/05/24 …"],
  "haystack_sessions": [
    [{"role":"user","content":"…(无关闲聊)…"}],
    [{"role":"user","content":"… solo trip to New York City for five days …","has_answer":true}],
    [{"role":"user","content":"…(无关闲聊)…"}],
    [{"role":"user","content":"… island-hopping trip to Hawaii … the 10-day …","has_answer":true}]
  ],

  "answer_session_ids": ["answer_60e8941a_2","answer_60e8941a_1"]  // ← 只给检索指标
}

真实数据里这道题共 46 个 session:2 个证据、44 个干扰。两条证据被刻意散布在两次不同日期的聊天里——这不是偶然,而是构造出来的「记忆散落」形态。干扰从哪来、证据怎么摆、时间怎么标,见 §1.4。

1.2 谁能看到什么

一条样本里的字段,分给三个「角色」看,界限分明:

表 1 字段可见性

字段被测系统检索评测QA 评测(judge)
question / question_date✅✅(作为检索 query)✅
haystack_sessions / haystack_dates✅✅❌
answer / rubric❌❌✅
answer_session_ids❌✅(gold 证据)❌
has_answer(turn 级)❌✅(精确到哪句)❌

⚠️ has_answer 标签在拼入 reader prompt 前会被显式删掉(run_generation.py:182)——否则等于把「答案在这句」直接告诉模型,评测就作弊了。

1.3 每道题自带一段独立历史

一个常见误解是「有一个大历史库,针对它出了 500 道题」。实际不是:每道题自带一段完全独立的历史,题与题之间不共享。用本地数据验证——

题 e47becba("What degree did I graduate with?")  → 自带 53 个 session
题 118b2229("How long is my daily commute?")      → 自带 45 个 session
两题 haystack 的 session id 交集 = 0

全库:500 题共出现 19195 个不同 session id;
      940 个证据 id,每个平均只在 1.009 道题里出现——几乎每题证据独有。

正确的心智模型:不是「一个图书馆配 500 道题」,而是「500 套试卷,每套附一本几十页的独立材料,答案藏在自己那本里」。这直接决定了检索的跑法:一题一建索引,只在本题的历史里搜(run_retrieval.py:232)。

1.4 数据是怎么造出来的

这些题和历史不是采集来的真实聊天记录,而是一条两段式流水线的产物——上游造题(未开源),下游拼装(已开源):

数据构造流水线:上游造题(黑盒)、下游拼装(可复现)。上下游的分界线,就是「能不能独立复现」的分界线。

图 2 数据构造流水线:上游造题(黑盒)、下游拼装(可复现)。上下游的分界线,就是「能不能独立复现」的分界线。

下游拼装的几个关键动作,都能在脚本里找到对应行:

  • 只用过审的证据:选题时要求证据 session 带 human_valid_label = true(:172)——这是上游存在人工校验环节的直接证据。

  • 干扰按固定比例混入:其他用户的模拟对话 50%、ShareGPT 25%、UltraChat 25%(:99),与本题完全无关,专门制造噪声。

  • 证据按题型插入:脚本里每种题型一个分支(:209-285)。multi-session 把多条证据随机散进历史各处;knowledge-update 则保证「旧值 session 在前、新值 session 在后」,否则「更新」就不成立。

  • 时间戳同样按题型定制(:288-385):edced276 的两条证据日期不同,就是这一步赋的。

  • 长度可控:enforce_json_length 参数把整段历史压到目标 token 数,这就是 S / M 两个规格的由来。

拼装的产物是三份成品文件,对应三种用法:

表 2 三份数据文件

文件每题历史用途
longmemeval_oracle.json只含证据 sessionoracle 喂法直接用它——不用自己筛证据
longmemeval_s_cleaned.json~50 个 session(~115k tokens)标准评测(本文统计都基于它)
longmemeval_m_cleaned.json~500 个 session(~1.5M tokens)压力测试超长历史

⚠️ 同一道题在 oracle 和 S 两份文件里的时间戳不一致(各自独立采样赋的),跨文件引用日期不能混写。

理解这条流水线,才能理解这个 benchmark「可归因」的根本前提:构造者从一开始就知道每条证据的真值和位置,所以评测能精确指出哪条证据没找回来。代价也在这里——数据全是合成的,上游造题环节无法独立复现(§6.2)。


2. 评测怎么跑:两条线,三种喂法

一道题进来后,评测全景如下:

评测全景。任何喂法都产出 hypothesis、走同一套 QA 判分;只有 RAG 喂法有检索步骤,因此只有它能算 Recall/NDCG。

图 3 评测全景。任何喂法都产出 hypothesis、走同一套 QA 判分;只有 RAG 喂法有检索步骤,因此只有它能算 Recall/NDCG。

2.1 检索线:用问题搜历史

检索的 query 就是当前的 question——代码里就一行 query = entry['question'](run_retrieval.py:275)。

⚠️ 不是用 answer 去搜,也不是用证据 id 去搜。答案和证据位置只参与评测打分,不参与检索本身——否则就是作弊。

建索引前先决定切片粒度,代码支持两种(--granularity):session 粒度——一整个 session 拼成一条可检索文本;turn 粒度——每个 user turn 单独一条,corpus_id 在 session id 后追加序号(如 answer_60e8941a_2_3)。粒度的选择直接影响检索质量,是 §5 第一条发现的主题。

切好后,每个片段带两部分:用于相似度计算的文本,和挂在旁边当元数据的 corpus_id(就是原始 session/turn id)。id 不进入检索文本,排序只看文本。检索完成后,id 干两件事:

top-k 的 corpus_id
  ├── 和 gold 证据 id 比对 → Recall / NDCG        (检索线的计分)
  └── 回表取原始对话文本 → 交给回答线               (两线的交接点)

原文一旦取出,corpus_id 的使命就结束了。

2.2 回答线:读原文,语义判分

Reader 收到的 prompt 是「日期 + 对话内容 + 问题」,按日期排序,不含任何证据标记。它输出一行 {"question_id": "edced276", "hypothesis": "15 days"}。

然后 judge(默认 gpt-4o,temperature=0, max_tokens=10)只看三样东西:question、answer(或 rubric)、hypothesis,输出 yes/no。它不看历史、不看检索结果、不看证据位置——它评的是「最终答案对不对」,与证据怎么来的无关。question_id 只是对齐系统输出和标准答案的行号,不参与判断。

2.3 三种喂法

同一道题可以用三种方式喂给被测系统,区别只在「reader 拿到什么材料」;hypothesis 产出后走的是同一套判分。

表 3 三种喂法对照(以 edced276 为例)

喂法系统看到主要测什么有检索指标
full-context46 个 session 全部长上下文定位 + 推理❌
oracle只有 2 个证据 session纯阅读/推理(假设检索完美)❌
RAG检索出的 top-k检索 + 阅读全链路✅

三种喂法合起来才有诊断力:oracle 高而 RAG 低,瓶颈在检索;oracle 本身就低,瓶颈在阅读;两者的差距约等于检索这一环损失了多少。这就是论文同时报三种模式的原因。

2.4 为什么两条线必须分开

因为「检索对」和「回答对」是独立事件,组合起来四种情况,各说明不同的问题:

检索回答意味着
✅ 找齐✅ 答对两环都好
✅ 找齐❌ 答错阅读/推理是瓶颈
❌ 漏了❌ 答错检索是瓶颈
❌ 漏了✅ 答对靠常识蒙对,少见

只看 QA Accuracy,会把第二、三行混在一起——知道错了,却不知道该修检索还是修阅读。


3. 指标与手算

3.1 QA Accuracy(用 edced276 走一遍判分)

每道题 judge 判 yes=1、no=0,然后求平均。judge 收到的 prompt 就是把模板(evaluate_qa.py:24-43)填上这道题的三个真实字段:

I will give you a question, a correct answer, and a response from a model.
Please answer yes if the response contains the correct answer. ...

Question: How many days did I spend in total traveling in Hawaii and in New York City?
Correct Answer: 15 days
Model Response: <系统的 hypothesis 填在这里>

Is the model response correct? Answer yes or no only.

judge 怎么判,用几个假想的 hypothesis 感受一下:

系统的 hypothesisjudge为什么
"You spent 15 days in total."yes语义等同 15 days,不要求逐字匹配
"5 in NYC and 10 in Hawaii, so 15 days."yes含正确答案,过程正确
"You spent 5 days traveling."no只找到纽约那条证据——检索漏了夏威夷的典型下场
"I don't have enough information."no答不出也算错(本题不是拒答题)

第三行值得注意:检索线的失误最终会以 QA=no 的形式显形——§3.2 手算里 recall_all@3=0(漏了夏威夷)对应的正是这种「只答 5 天」的错误回答。两条线就是这样对上的。

汇总时报两种口径:Overall(micro,所有题拉平均,题多的类型权重大)和 Task-averaged(macro,先按题型各算均值再平均,每类等权)。区分两种口径是因为题型分布不均——temporal 有 133 题,preference 只有 30 题。

3.2 Recall@k / NDCG@k(用 edced276 手算)

对每道题,看 top-k 是否覆盖了它的证据:recall_any@k 命中任意一条即 1;recall_all@k 必须全部命中才 1;ndcg@k 额外看证据排得靠不靠前。

edced276 有两条证据。假设检索 top-5 是:

排名 1:ultrachat_470709      (干扰)
排名 2:answer_60e8941a_2     ← 命中证据①(纽约 5 天)
排名 3:5774bbb1              (干扰)
排名 4:answer_60e8941a_1     ← 命中证据②(夏威夷 10 天)
排名 5:sharegpt_xxx          (干扰)

表 4 edced276 的 Recall/NDCG 手算(离线复现 eval_utils.py 公式)

krecall_anyrecall_allndcg解读
@1000.00第 1 名是干扰
@3100.50命中纽约,漏了夏威夷——只知 5 天
@5110.75两条证据都进来了

这张表说明两件事。

其一,多证据题要看 recall_all。 @3 时 recall_any=1 看着「找到了」,但漏了夏威夷,模型只能答 5 天,必错。单证据题则 recall_any 恒等于 recall_all,不用纠结。

其二,这份实现的 NDCG 前两名不打折。 dcg 写作 relevances[0] + Σ relevances[i]/log2(i+1)(i 从 1 起),第 2 名除以 log2(2)=1,等于没打折——证据排第 1 和第 2 分数相同,第 3 名起才下降。手算或解读时别用「越靠前分越高」的直觉套前两名。

3.3 两个指标的分母不一样

QA 对全部 500 题都算(含拒答题);Recall 会跳过两类题——拒答题(本来就没有证据可召回)和证据全在 assistant 侧的题(§6.1)。所以 Recall 的分母小于 500,两个指标不能直接横向对比。


4. 题型:一个字段,两个作用

每道题带一个 question_type 字段。它是「五种记忆能力」的载体,但在代码里只干两件事:决定一道题有几条证据,决定 judge 用哪套判分规则。除此之外它什么都不影响。

五大能力落到七种题型。「信息抽取」拆成三种;拒答是由 questionid 的 abs 标记识别,不是单独的 questiontype。

图 4 五大能力落到七种题型。「信息抽取」拆成三种;拒答是由 question_id 的 _abs 标记识别,不是单独的 question_type。

4.1 作用一:决定证据数量

表 5 题型分布与证据数量(本地真实统计,共 500 题)

question_type题数拒答证据数分布haystack 均值
temporal-reasoning1336{1:20, 2:88, 3:15, 4:2, 5:5, 6:3}47.5
multi-session13312{2:84, 3:26, 4:17, 5:6}47.2
knowledge-update786{2:78}(恒为 2:旧+新)47.5
single-session-user706{1:70}48.9
single-session-assistant560{1:56}48.7
single-session-preference300{1:30}47.6

读表两条:单会话三类恒为 1 条证据,recall_any 与 recall_all 无区别;knowledge-update 恒为 2 条(旧+新)、multi-session 和 temporal 常有 2–5 条——这些题必须看 recall_all,漏一条就答不全。每题平均近 50 个 session、证据只有 1–2 个,就是「大海捞针」的量化写照。

4.2 作用二:决定判分规则

judge 按题型换判分标准,差异只是 prompt 里多加的一两句话(evaluate_qa.py:24-43):

表 6 题型专属判分规则

题型相比通用规则,额外加了什么
single-session-user / -assistant / multi-session无(通用:回答含正确答案即 yes)
temporal-reasoning容忍天数差一(预测 19 天、答案 18 天也算对)
knowledge-update带旧信息也算对,只要更新后的答案正确
single-session-preferenceanswer 当 rubric 判,不要求全覆盖,用对偏好即可
abstention(_abs)换一套:判模型有没有正确识别为不可答

4.3 它不影响什么

汇总方式。把每题的 0/1 变成最终 accuracy 永远是求平均,与题型无关;论文里的 per-type accuracy 只是「筛出这类题再平均」,没有权重,也不是特殊算法。顺带一提:检索侧连按题型分组都没实现——print_retrieval_metrics.py 定义了 task2type 映射却没用,只输出全局平均。


5. 论文的发现:存 → 找 → 读

评测机制讲完了。LongMemEval 不只是一把尺子——论文用它跑了大量对照实验,回答一个更实用的问题:要做一个记性好的系统,每一步该怎么选?论文把记忆系统的工作拆成三步:存(怎么切历史、以什么形式存)、找(问题来了怎么检索)、读(找回来怎么组织给模型),逐个做了消融。

起点:更长的上下文救不了记忆。 §0 那个 34 点的落差就是这一节的起点——光把上下文做长没用,多个长上下文与记忆增强模型相对「证据递到手上」的理想设置普遍下滑 30%~60%。必须有一层机制帮模型存好、找准、读对。

存:切细,但别切碎。 一整个 session 常混多个话题,拿它当检索单元,信号被无关内容稀释;改成 round(一问一答)粒度,语义聚焦,检索更准。但也别走到另一头——把内容压成一句干巴巴的 fact 存储,检索命中提高了些,原文上下文却丢了,模型读时信息不足,QA 反而掉。结论:切到 round,保留原文。

找(其一):给检索的 key 挂上 fact。 原文往往又长又绕,用户的问题和原文字面对不上。论文的做法:存储内容(value)保留原文,但额外抽一句 user fact 挂在检索键(key)上——fact 帮命中,原文供阅读,两头不误。实测 Recall@k 提升约 4%、QA 提升约 5%。这就是 key expansion。

找(其二):时间类问题,query 要带时间。 时间推理不能只靠文本相似度。我们走查过一道真实 temporal 题:两条证据 session 的时间戳是同一天,真正决定先后的是正文里写的「April 10th 开会」和「May 1st 游行」——日期在话里,不在时间戳里。论文先从问题推断时间范围,再据此裁剪或重排检索空间,temporal recall 提升 7~11 个百分点。

读:先做笔记,再答题。 检索找对只是把材料摆上桌。让模型先从每段检索内容里写一句「与问题相关的笔记」(Chain-of-Note),再基于笔记作答,并用结构化 JSON 呈现历史,reading accuracy 最高提升约 10 个点——即便检索完美,阅读这一步仍有可观空间。

连起来就是一条完整的优化路径:

切到 round 粒度存原文 → 检索键挂 fact 帮命中 → 时间题给 query 补时间范围 → 模型先写笔记再作答。

把四个控制点放回同一条数据流,就能看清每一步究竟改了什么、又保留了什么:

论文四个控制点在同一条链路中的位置:round 与 fact 改善「存和找」,时间扩展改善 query,Chain-of-Note 改善「读」;原文与来源 ID 始终保留。

图 5 论文四个控制点在同一条链路中的位置:round 与 fact 改善「存和找」,时间扩展改善 query,Chain-of-Note 改善「读」;原文与来源 ID 始终保留。

这四步论文里叫四个「控制点」,代码里都有开关:粒度与 fact 由 --granularity 和 src/index_expansion/ 控制,时间感知由 temp_query_search_pruning.py 控制,笔记与格式由 run_generation.py 的 --cot、--con、--history_format 控制。

业界的落点。 Mem0 公布的 LongMemEval 分数:单会话(用户)98.6、单会话(助手)98.2、知识更新 93.6、多会话 88.0(来源)。两个信号:难度随能力递增,跨多段综合的 multi-session 明显低于单会话回忆;这些分数出自专用记忆系统,远高于裸 GPT-4o 的 58%——加一层好的记忆机制,确实能把差距补回大半。


6. 适用边界与接入要求

以上是设计。这一节是逐文件审计的结果——只读论文和 README 发现不了、但直接影响结果解读的地方:一个让指标失真的盲区(6.1)、两个引用数据时的提醒(6.2)、以及一道决定这个 benchmark 对你有几成可用的适用门槛(6.3)。

6.1 盲区:assistant 侧证据的检索基本测不了

single-session-assistant 类题的答案大多在助手说过的话里——真实统计,56 题里有 51 题的 has_answer 落在 assistant turn。但索引逻辑(run_retrieval.py:202-229)只处理 user turn,于是这些题被一路过滤出检索评测:

assistant 证据只从检索线消失,回答线仍然可见。因此这类题的 Recall 不是低,而是没有被统计。

图 6 assistant 证据只从检索线消失,回答线仍然可见。因此这类题的 Recall 不是低,而是没有被统计。

如果你想评「助手以前说过 / 推荐过什么」,必须让 assistant 侧内容进入索引和证据标注,否则 Recall 看着正常,实为假象。

6.2 两个提醒

复现是半开源的。 数据流水线的上游(造题)没开源、只有下游(拼装)能跑(§1.4)——可以复现评测和拼装,但不能从零复现论文的数据构造。

NDCG 前两名不打折。 这份实现里证据排第 1 和第 2 分数相同、第 3 名起才下降(§3.2)——比较不同系统的 NDCG 时,前两名的排序差异是看不出来的。

6.3 硬门槛:你的记忆系统能不能「指回 ID」

这一条比前面的坑都重要,因为它决定了 LongMemEval 对你到底有几成可用。

回顾 §2.1:检索线的打分全靠 ID 闭环——系统检索返回的 corpus_id 要和 gold 证据 id 比对,Recall/NDCG 才算得出来。这背后是一个隐含的硬性要求:被测系统检索出的每条结果,必须能指回「原始 session/turn 的 ID」。 按这个门槛,市面上的记忆系统分三类:

表 7 三类记忆系统的适用程度

记忆系统类型例子能测什么原因
存原文 chunk 型朴素 RAG、向量库方案✅ 两条线全套chunk 天然带来源 id,开箱即用
合成改写型(存 fact/summary)Mem0、MemOS、Zep 这类⚠️ 需改造后才有检索线合成时原始 id 就丢了;须在写记忆时把来源 session id 存进元数据(provenance)、检索时一并返回
黑盒型(只暴露对话接口)ChatGPT 内置记忆、Coze❌ 只剩 QA 半套拿不到检索中间结果,检索线无从谈起

第三行不是推测——论文自己实测 ChatGPT / Coze 时,就只报了 QA 准确率,没有任何 Recall 数字,因为根本拿不到。

所以选型时先问一个问题:「我的系统检索出来的东西,带不带原始出处?」 带,两条线的全套诊断价值都是你的;不带但能改,补 provenance 后可用;完全黑盒,那 LongMemEval 对你只是一套(不错的)QA 题库,它最核心的「定位瓶颈」能力你用不上。

LongMemEval 适用性门槛:能否形成「检索结果 → 原始 ID」闭环,决定了系统能跑完整评测还是只能测 QA。

图 7 LongMemEval 适用性门槛:能否形成「检索结果 → 原始 ID」闭环,决定了系统能跑完整评测还是只能测 QA。

这也暴露一个容易忽略的事实:不同架构的系统即便都报了 QA 分数,含义并不相同——Recall 能摊开「证据到底找没找回来」这一层,单看 QA 会把它盖住。这是下一节对比的引子。


7. 怎么选:和 LoCoMo、HaluMem 比

三个 benchmark 最根本的区别不是数据规模,而是各自假设「记忆长什么样」——这个假设决定了打分方式,进而决定了各自能测什么。

表 8 三种记忆评测范式对比

维度LoCoMoLongMemEvalHaluMem
假设记忆是原始对话片段原始对话片段抽取改写后的新句子
打分方式证据 ID + 词面 F1证据 ID(Recall@k)+ LLM judge纯 LLM 语义比对,不靠 ID
被测系统需暴露什么检索结果须带来源 id检索结果须带来源 id(§6.3 门槛)记忆库内容(抽取/更新可见)
评测粒度端到端端到端,分检索/回答两条线操作级(抽取/更新/问答各自标注)
检索诊断有(仅 RAG 模式)有(强,独立一条线)无
测知识更新❌✅✅
测抽取失真/幻觉❌❌✅
最大上下文~16K tokens(发布版实测)~115K(S)/ ~1.5M(M)~1M tokens

第一行推导出其余一切:LongMemEval 和 LoCoMo 假设记忆存原文,片段天然带 id,可以用廉价精确的 Recall@k;HaluMem 假设记忆是被嚼碎改写的新句子,没有原文页码可对,只能靠 LLM 逐条语义比对——也因此拿不到 Recall@k,这正是 §6.3 那个 provenance 问题的另一面。

选型一句话:

评「存原文 / RAG 型」记忆、想分开定位检索和阅读的瓶颈 → LongMemEval; 评「抽取改写型」记忆、关心幻觉在存/改/取哪一步产生 → HaluMem; 研究长对话里连续人物、事件、多模态一致性 → LoCoMo。


8. 结论

LongMemEval 的价值不在 500 道题的规模,而在它把「记性好不好」拆成了可度量、可归因、可比较的工程问题。两条独立评测线是它最核心的方法论贡献——「答错了但不知道改哪里」变成「答错了但知道是检索没找对,还是找对了没读懂」。

但这份价值有一个前提条件:两条线的全套诊断,只对「检索结果能指回原始 ID」的系统成立(§6.3)。存原文的 RAG 系统开箱即用;合成改写型记忆要先补来源标注;黑盒系统只能用它的 QA 题库,享受不到「定位瓶颈」这个最核心的能力。选型前先过这道门槛,再谈别的。

同时记住它的固有约束:500 题规模不大;历史全部合成,可能与真实长期交互的话题分布有偏差;上游构造链路不开源,无法完全独立复现;只测英文;assistant 侧证据的检索存在盲区。这些不是否定,但拿它的数字做对外比较或产品决策前,应当心里有数。


9. 附录:术语 / 复现命令 / 代码索引 / 参考

A. 术语速查

术语解释
haystack一道题自带的整段历史,几十个 session,答案埋在其中,其余是干扰
session一次多轮对话,含若干 turn
evidence含答案的 session/turn;session 级由 answer_session_ids 标出,turn 级由 has_answer 标出
distractor无关干扰 session,来自其他用户模拟对话 / ShareGPT / UltraChat
hypothesis被测系统对某题给出的最终回答(一句自然语言)
corpus_id建索引时给每个可检索片段的定位标签,本质是原始 session/turn ID
gold labelanswer / answer_session_ids / has_answer,只给评测用,系统看不到
LLM-as-judge用大模型(默认 gpt-4o)判 hypothesis 对不对,输出 yes/no
oracle / full-context / RAG三种喂法:只喂证据 / 喂全部历史 / 先检索再喂 top-k
recall_any / recall_alltop-k 命中任意一条证据 / 命中全部证据
provenance一条合成记忆能回溯到来源 session 的能力,是合成型系统能算 Recall 的前提

B. 数据下载

mkdir -p data/ && cd data/
wget https://huggingface.co/datasets/xiaowu0162/longmemeval-cleaned/resolve/main/longmemeval_oracle.json
wget https://huggingface.co/datasets/xiaowu0162/longmemeval-cleaned/resolve/main/longmemeval_s_cleaned.json
wget https://huggingface.co/datasets/xiaowu0162/longmemeval-cleaned/resolve/main/longmemeval_m_cleaned.json

C. 最小可跑:只评 QA Accuracy

export OPENAI_API_KEY=...
cd src/evaluation
python3 evaluate_qa.py gpt-4o <hypothesis.jsonl> ../../data/longmemeval_oracle.json
python3 print_qa_metrics.py <上一步.eval-results-gpt-4o> ../../data/longmemeval_oracle.json

hypothesis.jsonl 每行 {"question_id": "...", "hypothesis": "..."}。500 题 = 500 次 judge 调用。

D. 跑检索 + generation

# 检索(dense 检索器需要 GPU)
cd src/retrieval
bash run_retrieval.sh ../../data/longmemeval_s_cleaned.json flat-bm25 session

# full-context generation
cd src/generation
bash run_generation.sh ../../data/longmemeval_s_cleaned.json gpt-4o full-history-session 1000 json false con

# RAG generation(把检索 log 喂给 generation)
bash run_generation.sh <retrieval_log> gpt-4o flat-bm25-session 10 json false con

E. 代码索引(关键位置,可点击)

功能位置
最终样本字段构造sample_haystack_and_timestamp.py:391-401
multi-session 证据插入:227-236
knowledge-update 旧/新证据(_1/_2):257-260
一题一建索引的循环run_retrieval.py:232-247
session/turn 索引,只处理 user turn:202-229
correct_docs = 含 "answer" 的 id:272
query = question:275
跳过 abstention / 无 user 证据的题:396-402
dcg / ndcg / recall_any / recall_alleval_utils.py:4-29
删除 has_answer(防作弊)run_generation.py:182-189
Chain-of-Note(con-separate):194-222
输出 hypothesis:375
题型专属 judge promptevaluate_qa.py:24-43
label = 'yes' in response:113
macro / micro / abstention 汇总print_qa_metrics.py:26-33

F. 参考资料

  1. Di Wu et al. LongMemEval, ICLR 2025

  2. 项目主页:xiaowu0162.github.io/long-mem-eval

  3. 数据集:HuggingFace xiaowu0162/longmemeval-cleaned

  4. 代码:github.com/xiaowu0162/LongMemEval

  5. LoCoMo:arXiv:2402.17753

  6. HaluMem:arXiv:2511.03506

  7. Mem0 benchmark 报告:mem0.ai/blog/ai-memory-benchmarks-in-2026


本报告基于论文、开源代码审计与本地真实数据走查,未运行完整评测 pipeline。所有本地统计来自离线脚本读取真实数据;Recall/NDCG 数字用忠实复现 eval_utils.py 公式的离线脚本算出。§6 的发现含一手核对与独立判断。

分享这篇文章

复制链接,或分享到你常用的地方。

评论

发表评论

0 / 1000