调研对象: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 的覆盖更广。
下面先把数据、评测流程和指标放到同一张图中,再进入样本与实现细节。

图 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 | 只含证据 session | oracle 喂法直接用它——不用自己筛证据 |
longmemeval_s_cleaned.json | ~50 个 session(~115k tokens) | 标准评测(本文统计都基于它) |
longmemeval_m_cleaned.json | ~500 个 session(~1.5M tokens) | 压力测试超长历史 |
⚠️ 同一道题在 oracle 和 S 两份文件里的时间戳不一致(各自独立采样赋的),跨文件引用日期不能混写。
理解这条流水线,才能理解这个 benchmark「可归因」的根本前提:构造者从一开始就知道每条证据的真值和位置,所以评测能精确指出哪条证据没找回来。代价也在这里——数据全是合成的,上游造题环节无法独立复现(§6.2)。
2. 评测怎么跑:两条线,三种喂法
一道题进来后,评测全景如下:

图 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-context | 46 个 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 感受一下:
| 系统的 hypothesis | judge | 为什么 |
|---|---|---|
| "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 公式)
| k | recall_any | recall_all | ndcg | 解读 |
|---|---|---|---|---|
| @1 | 0 | 0 | 0.00 | 第 1 名是干扰 |
| @3 | 1 | 0 | 0.50 | 命中纽约,漏了夏威夷——只知 5 天 |
| @5 | 1 | 1 | 0.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 用哪套判分规则。除此之外它什么都不影响。

图 4 五大能力落到七种题型。「信息抽取」拆成三种;拒答是由 question_id 的 _abs 标记识别,不是单独的 question_type。
4.1 作用一:决定证据数量
表 5 题型分布与证据数量(本地真实统计,共 500 题)
| question_type | 题数 | 拒答 | 证据数分布 | haystack 均值 |
|---|---|---|---|---|
temporal-reasoning | 133 | 6 | {1:20, 2:88, 3:15, 4:2, 5:5, 6:3} | 47.5 |
multi-session | 133 | 12 | {2:84, 3:26, 4:17, 5:6} | 47.2 |
knowledge-update | 78 | 6 | {2:78}(恒为 2:旧+新) | 47.5 |
single-session-user | 70 | 6 | {1:70} | 48.9 |
single-session-assistant | 56 | 0 | {1:56} | 48.7 |
single-session-preference | 30 | 0 | {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-preference | answer 当 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 补时间范围 → 模型先写笔记再作答。
把四个控制点放回同一条数据流,就能看清每一步究竟改了什么、又保留了什么:

图 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,于是这些题被一路过滤出检索评测:

图 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 题库,它最核心的「定位瓶颈」能力你用不上。

图 7 LongMemEval 适用性门槛:能否形成「检索结果 → 原始 ID」闭环,决定了系统能跑完整评测还是只能测 QA。
这也暴露一个容易忽略的事实:不同架构的系统即便都报了 QA 分数,含义并不相同——Recall 能摊开「证据到底找没找回来」这一层,单看 QA 会把它盖住。这是下一节对比的引子。
7. 怎么选:和 LoCoMo、HaluMem 比
三个 benchmark 最根本的区别不是数据规模,而是各自假设「记忆长什么样」——这个假设决定了打分方式,进而决定了各自能测什么。
表 8 三种记忆评测范式对比
| 维度 | LoCoMo | LongMemEval | HaluMem |
|---|---|---|---|
| 假设记忆是 | 原始对话片段 | 原始对话片段 | 抽取改写后的新句子 |
| 打分方式 | 证据 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 label | answer / answer_session_ids / has_answer,只给评测用,系统看不到 |
| LLM-as-judge | 用大模型(默认 gpt-4o)判 hypothesis 对不对,输出 yes/no |
| oracle / full-context / RAG | 三种喂法:只喂证据 / 喂全部历史 / 先检索再喂 top-k |
| recall_any / recall_all | top-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_all | eval_utils.py:4-29 |
| 删除 has_answer(防作弊) | run_generation.py:182-189 |
| Chain-of-Note(con-separate) | :194-222 |
| 输出 hypothesis | :375 |
| 题型专属 judge prompt | evaluate_qa.py:24-43 |
| label = 'yes' in response | :113 |
| macro / micro / abstention 汇总 | print_qa_metrics.py:26-33 |
F. 参考资料
-
Di Wu et al. LongMemEval, ICLR 2025
-
LoCoMo:arXiv:2402.17753
-
HaluMem:arXiv:2511.03506
-
Mem0 benchmark 报告:mem0.ai/blog/ai-memory-benchmarks-in-2026
本报告基于论文、开源代码审计与本地真实数据走查,未运行完整评测 pipeline。所有本地统计来自离线脚本读取真实数据;Recall/NDCG 数字用忠实复现 eval_utils.py 公式的离线脚本算出。§6 的发现含一手核对与独立判断。