跳到主要内容

量化记忆好不好 — 评测harness 与公开基准

30 秒导读: 你给 agent 装了个"记忆"(Zep 的时序知识图谱),怎么证明它真的比别人强? 这一章讲 Zep 仓库里两套"打分机器":一套是自研的 zep-eval-harness(把摄取→检索→答题→评分拆成 可复现的四阶段流水线),一套是跑 LoCoMo / LongMemEval 两个公开基准的适配器。核心难点只有一句: 记忆评测天生难——因为"记得对不对"要靠另一个 LLM 来判,而摄取是异步的、图谱要几十秒才建好。

本章只讲评测 / 基准这个子系统。记忆环本身怎么摄取、怎么检索,见 01-memory-model.md; 把记忆环塞进各框架见 02-integration-patterns.md。这里假设你已经知道 "Zep 把对话变成图里的节点(实体)和边(事实)"。


1. 这是什么(零基础也能懂)

1.1 一句话定义

评测 harness = 一台"考记忆的机器":它把一堆对话/文档喂进 Zep,然后拿一份"考卷"(问题 + 标准答案) 去问,最后自动判分——Zep 到底记住了没有、答得对不对

1.2 它要解决谁的什么问题

假设你是 Zep 的工程师,今天改了图谱抽取的 prompt,或者把检索的 reranker 从 rrf 换成 cross_encoder。 你怎么知道这一改到底是变好还是变坏?

  • 靠肉眼看几个 case → 不可信、不可复现、样本太小。
  • 靠"感觉" → 更糟。

你需要一个能重复跑、能给出数字、能定位是哪一环出问题的东西。这就是 harness。

1.3 两个层次的"考卷"

仓库里其实有两套评测系统,面向不同目的:

系统位置用什么数据目的
自研 harnesszep-eval-harness/自带的合成数据(一个房产 agent 场景)日常回归:改了配置,快速看指标涨没涨
公开基准benchmarks/locomo/benchmarks/longmemeval/学术界公开数据集对外证明:和别人在同一张卷子上比分数

本章两者都讲,但重点在自研 harness——因为它把"可复现工程"做得最完整(manifest + config 快照 + checkpoint), 公开基准更多是"把同一套评分逻辑套到别人的数据格式上"。

1.4 用起来什么样

自研 harness 就是四条命令,依次跑(来自 zep-eval-harness/README.md):

# ① 把用户对话+遥测摄取进各自的 user graph,并轮询等到图建好
uv run zep_ingest_users.py

# ② 把参考文档切块 + 让 LLM 生成摘要/上下文(最贵的一步,产物可复用)
uv run zep_chunk_documents.py

# ③ 把切好的块灌进一个共享 document graph
uv run zep_ingest_documents.py --chunk-set 1

# ④ 拿考卷去问,自动判分,落 results.json
uv run zep_evaluate.py --user-run 1 --doc-run 1

每一步都在 runs/ 下留一个带编号+时间戳的目录,里面有 manifest.json(这次跑了啥)和一份 config 快照(当时用的配置长啥样)。这两样是"可复现"的命根子,后面 §4 细讲。

1.5 一句话直觉

把它想成一条工厂流水线,每个工位下班时都拍一张"现场照片"存档。 照片(manifest + 快照)保证 哪怕明天改了配方,你也能翻出昨天那批货是怎么造的;传送带中途卡了(API 限流、进程被杀),下次能从 断点续上(checkpoint),不用整条重来。


2. 顶层全景(它大概怎么转)

2.1 自研 harness 的四阶段流水线

怎么读这张图:从上到下是数据流;每个方框是一个独立脚本,跑完在 runs/ 落一个目录。 关键点:用户图文档图两条支线互相独立,最后在评测阶段才汇合。

data/users.json ┐
conversations/ ├─►[① zep_ingest_users.py]──► 各 user graph ──► runs/users/{N}/manifest.json
telemetry/ ┘ (含 config 快照)

data/documents/ ──►[② zep_chunk_documents.py]──► runs/chunk_sets/{N}/chunks.jsonl
│ (切块+LLM 摘要,最贵、可复用)

[③ zep_ingest_documents.py]──► 1 个 document graph ──► runs/documents/{N}/manifest.json

data/test_cases/ ──►[④ zep_evaluate.py --user-run N --doc-run M]

├─ 搜图(user graph + document graph, 并行)
├─ 判"检索够不够"(主指标) ┐ 两者并行
├─ 用检索结果生成答案 ┘
└─ 判"答得对不对"(次指标, LLM judge)


runs/evaluations/{N}/results.json (含 config 快照 + 父 run 引用)

2.2 部件一句话职责

部件干什么文件
用户摄取建用户、灌对话/遥测、轮询等图建好zep-eval-harness/zep_ingest_users.py
文档切块切块 + LLM 生成摘要与逐块上下文zep-eval-harness/zep_chunk_documents.py
文档摄取读切块集,灌进独立 document graphzep-eval-harness/zep_ingest_documents.py
评测搜图→评完整性→生成→判分,落结果zep-eval-harness/zep_evaluate.py
重试指数退避 + 抖动,抗限流zep-eval-harness/retry.py
断点原子写/读/删 checkpointzep-eval-harness/checkpoint.py
公共常量轮询间隔/超时、Gemini base URLzep-eval-harness/config/constants.py

2.3 为什么拆成四步而不是一个大脚本

三条设计动机(都是"避免重复烧钱"):

  1. 摄取慢、只需一次;评测快、想多跑几遍。 分开后,一次摄取可以配多次不同参数的评测。
  2. 用户图和文档图解耦。 你可以造 2 种文档图 × 8 种用户图,评测时任意配对 (--user-run 3 --doc-run 2),而不用为每种配对重灌一遍(README.md:132-140)。
  3. 切块和灌库再拆一层。 切块要调 LLM(贵),灌库只调 Zep API(便宜)。一份切块集 (chunk set)能复用到多次不同 ontology 的灌库(README.md:142-159)。

3. 核心原理(逐个机制,由浅入深)

这一节挑五个真正体现工程含量的机制。前两个是任务点名的重点:异步摄取轮询config 快照 + checkpoint

3.1 两把尺子:主指标"检索够不够" vs 次指标"答得对不对"

它要解决的小问题

"Zep 答错了"——到底是图里根本没这条信息(检索的锅),还是信息在、但答题 LLM 没用好(生成的锅)? 一个笼统的"准确率"分不清这两者。

思路

拆成两把独立的尺子量(README.md:439-461):

  • 主指标 · 上下文完整性(Context Completeness): 只看检索出来的 context 里,有没有答这道题需要的信息, 完全不管最终答案。三档:COMPLETE / PARTIAL / INSUFFICIENT。这把尺子直接量 Zep 的检索质量
  • 次指标 · 答案准确率(Answer Accuracy): 让答题 LLM 用 context 生成答案,再让另一个 LLM 判它和标准答案 是否语义等价。二值:对 / 错。

真实实现

主指标由 evaluate_context_completeness() 实现,注意它的 prompt 里反复强调"你不是在评答案,是在评 context 本身有没有料"(zep_evaluate.py:580,prompt 见 601-654 行)。次指标由 grade_ai_response() 用一个 LLM judge + Pydantic 结构化输出打分(zep_evaluate.py:494):

# 示意,非源码:两把尺子各问一次 LLM,结构化返回
completeness = judge_llm(question, golden, context) # → COMPLETE/PARTIAL/INSUFFICIENT
answer = response_llm(context, question) # 先生成答案
is_correct = judge_llm(question, golden, answer) # → True/False

关键细节 / 坑

  • 两把尺子还能交叉分析。 结果里算了个"context 完整时答案的正确率" (accuracy_when_complete,zep_evaluate.py:1109-1113)——如果它很低,说明"检索没问题,是生成拉胯"; 如果 context 常常 INSUFFICIENT,那才是检索要改。这就是归因
  • judge 的 prompt 里埋了'时间陷阱'防御:反复叮嘱 LLM "带过去日期的历史事实仍然是有效 context,别因为 日期是过去就当它过期"(zep_evaluate.py:640-646)。这直接呼应 01 里 Zep 的 双时间(bi-temporal)模型——事实有 valid_at / invalid_at,评测端必须理解"过去发生 ≠ 无效"。

3.2 异步摄取轮询:图不是灌完就好了

它要解决的小问题

你调 thread.add_messages_batch(...) 把消息发给 Zep,API 立刻返回——但这时候图还没建好。 Zep 的图谱抽取是异步的:每条 episode(一条消息/一份 JSON)要 5–20 秒才被抽成实体和边 (README.md:542-543)。如果你马上去评测,图是空的,分数全是 0。

这正是 01 讲的最终一致性在评测端的直接后果:写入是异步的,你必须等它收敛。

思路

摄取脚本默认轮询等到所有 episode 处理完才收工。机制是:每次 add 会拿到一个 task_id,轮询这些 task 的状态直到 succeeded;--no-poll 则跳过等待、立刻返回(适合"先灌一大批,过会儿再评")。

真实实现

poll_task_ids() 是核心(zep_ingest_users.py:365,文档侧同名函数在 zep_ingest_documents.py:304)。 两个巧妙点:

① 超时按 episode 数量成比例给,而不是一刀切:

# zep_ingest_users.py:389 —— episode 越多,给的等待预算越长
task_timeout = num_episodes * POLL_TIMEOUT_PER_EPISODE # 120s / episode

② 顺序轮询,每个 task 的计时"等前一个完成才开始"(注释见 zep_ingest_users.py:370-373)。 POLL_INTERVAL = 2 秒查一次,POLL_TIMEOUT_PER_EPISODE = 120 秒每 episode 的上限 (config/constants.py:5-6)。

摄取阶段是并发发起的:所有用户用 asyncio.gather 并行摄取(zep_ingest_users.py:803-813), 每个用户图的轮询也并行跑(poll_user_graph,zep_ingest_users.py:860)。轮询完还把 "平均每 episode 耗时"写回 manifest(ingestion_timing,zep_ingest_users.py:929-940)—— 这本身就是一个可观测指标:图建得快不快。

关键细节 / 坑

  • 评测脚本在搜每个用户图前,会先 user.warm(...) 预热缓存(zep_evaluate.py:856);文档图没有 warm 方法, 就用一次 query="." 的轻量搜索"骗"它预热(zep_evaluate.py:831-836)。这是为了测检索延迟时不被冷启动污染
  • 超时后不是崩,而是优雅停下并报告已完成多少(zep_ingest_users.py:409-423)。

3.3 config 快照 + checkpoint:可复现与可续跑的两根支柱

这是 harness 工程含量的最高点,拆成两件事讲。

(a) config 快照 = "把当时的配方拍照存档"

问题: 三个月后你想复现今天这次评测,但 config/ 里的 prompt、reranker、模型早被改了 N 遍。manifest 里 只记了"用了 custom ontology"这种元数据,不够——你要的是当时那份配置文件的原样

做法: 每个阶段落 run 目录时,用 shutil.copytree整个 config 子目录复制进 run 目录当快照:

# zep_ingest_users.py:571-575 —— 把 config/user_ingestion_config/ 整个拷进 run 目录
snapshot_dir = os.path.join(run_dir, "user_ingestion_config_snapshot")
shutil.copytree("config/user_ingestion_config", snapshot_dir,
ignore=shutil.ignore_patterns("__pycache__"))

四个阶段各拍各的快照(README.md:206-231):

阶段快照目录快照的是
用户摄取user_ingestion_config_snapshot/ontology / 指令 / 用户摘要指令
文档切块document_chunking_config_snapshot/chunk size、上下文化模型
文档摄取document_ingestion_config_snapshot/文档 ontology / 指令
评测evaluation_config_snapshot/搜索 limit、response/judge 模型、response prompt

评测的 results.json 还额外记了父 run 引用(parent_runs,zep_evaluate.py:1175-1187):这次评测用的是 哪个 user run、哪个 doc run。于是从一份评测结果能反查出完整的血缘:结果 → 摄取 run → 各自的 config 快照。 这就是"可复现"闭环。

这与 01 的最终一致性呼应:因为摄取是异步且有随机成分(用户 ID 加随机后缀保证幂等, zep_ingest_users.py:163-165),两次摄取不可能字节级相同;快照锁的是"输入配方",而不是妄图锁住输出。

(b) checkpoint = "传送带卡了能从断点续"

问题: 摄取要几分钟、要调几百次 API,中途一旦限流耗尽重试、或进程被 Ctrl-C,全部重来太浪费

做法: 每完成一个单位就写一次 checkpoint,记下"已完成哪些";--resume 时跳过已完成的部分。

用户摄取里,每个用户摄取完就更新 checkpoint(ingest_user_with_checkpoint,zep_ingest_users.py:778):

# zep_ingest_users.py:787-797 —— 每完成一个用户,原子写一次进度
async with checkpoint_lock:
completed_users.append(result)
save_checkpoint(cp_path, {
"run_number": run_number,
"config": {...}, # 连配置一起存,resume 时恢复
"completed_users": completed_users,
})

checkpoint 的写是原子的——先写 .tmpos.replace,保证半路挂掉不会留下损坏文件 (checkpoint.py:5-11)。全部成功后 checkpoint 会被删掉(delete_checkpoint,checkpoint.py:20)。

关键细节 / 坑

  • 切块和文档摄取有各自更细的续跑粒度。 切块以"(文件名, 块序号)"为单位记已完成,resume 时读回 chunks.jsonl 跳过已切的块,还复用上次的文档摘要保持一致(read_completed_chunks, zep_chunk_documents.py:224)。文档摄取以"已灌块数 + 文件偏移"续跑(下一节)。
  • retry 与 checkpoint 是两层防线: retry 抗瞬时错误(限流),checkpoint 抗致命中断(进程死)。

3.4 切块集的 follow 模式:切一半就能边切边灌

它要解决的小问题

切块(调 LLM)慢,灌库(调 Zep)也要时间。串行等切块全切完再灌,浪费。

思路

切块脚本把每切好一块就追加一行写进 chunks.jsonl(append_chunk_line,zep_chunk_documents.py:218), meta.jsonstatus 先是 "in_progress",全切完才改 "complete"(zep_chunk_documents.py:311 / 414)。 灌库脚本则tail -f 一样盯着这个 JSONL:有新行就灌,没新行就等,直到 meta 变 complete 且行数追平。

真实实现

follow_and_ingest()(zep_ingest_documents.py:109)用文件偏移量避免每轮重读整个文件:

# zep_ingest_documents.py:149-151 —— 从上次读到的字节位置续读
with open(jsonl_path, "r") as f:
f.seek(file_offset)
for line in f: ...

退出条件是"切块集完成 我已灌完所有块"(zep_ingest_documents.py:235);还没好就 await asyncio.sleep(FOLLOW_POLL_INTERVAL) 每 3 秒探一次(zep_ingest_documents.py:64),最长等 1 小时。

于是你可以开两个终端并发跑:一个切块、一个灌库,灌库自动追着切块屁股跑(README.md:161)。 --chunk-size N 的"inline 模式"则是图省事:先切完再灌,一条命令搞定,但不产生可复用的切块集 (README.md:163)。

3.5 抗限流:指数退避 + 抖动

它要解决的小问题

所有阶段都在高并发打 LLM / Zep API,必然撞限流。撞了不能直接死,得退一步再试。

真实实现

一个通用装饰器式函数 retry_with_backoff()(retry.py:5)包住每个 API 调用:最多 8 次重试、初始 2 秒、 指数增长、上限 300 秒(5 分钟),并加抖动(jitter)防止一堆请求同时重试造成"惊群":

# retry.py:35-37 —— 指数退避 + 抖动
delay = min(initial_delay * (2 ** attempt), max_delay) # 2,4,8,...,≤300
jitter = random.uniform(0, delay * 0.1) # 加最多 10% 随机
delay += jitter

评测阶段还有个 asyncio 信号量控总并发(默认 15,zep_evaluate.py:820),切块默认 5、评测默认 15 (README.md:166-173)。限流基本靠退避自然节流,不用手调(README.md:173)。


4. 深入实现:评测阶段的一次完整流水

process_single_query()(zep_evaluate.py:690)是单道题的端到端流程,值得逐步看它哪些并行、哪些必须串行:

搜图(user+doc, 全并行) ─► 拼 context

┌───────────────┴───────────────┐ ← 这两步并行(gather)
评完整性(主指标) 生成答案
└───────────────┬───────────────┘

判答案对错(次指标) ← 必须等答案生成完才能判
  • 搜图并行到极致: user graph 的 nodes/edges、document graph 的 nodes/edges 全塞进一个 asyncio.gather 一起发(perform_graph_search,zep_evaluate.py:180;并行 gather 在 269 行)。默认 reranker 用 cross_encoder(精度最高、最慢,README.md:397-401)。
  • 完整性评估与答案生成并行(zep_evaluate.py:730-739):因为完整性只看 context、不看答案,可以和生成同时跑。
  • 判分必须串行在生成之后(zep_evaluate.py:746):judge 要拿到答案才能判。

搜索的规模由 config/evaluation_config/constants.py 里的 limit 常量控:USER_FACTS_LIMIT=20USER_ENTITIES_LIMIT=10USER_EPISODES_LIMIT=0(默认关 episode)等。结果聚合成 总分 / 分类别 / 分用户 / 相关性分析四种视角(calculate_aggregate_statistics,zep_evaluate.py:948)。

context 怎么拼给 LLM 也有讲究:construct_context_block()(zep_evaluate.py:360)把 facts 标上日期区间、 明确写"以 present 结尾的事实当前有效,过去结束日期的事实不再有效"(zep_evaluate.py:395-401)—— 又是把 01 的时序语义显式喂给答题 LLM。


5. 两套公开基准

自研 harness 用的是仓库自带的合成数据(2 用户、4 对话、4 文档、8 题,README.md:584-593), 胜在流程完整;但要对外比分,得跑公开基准。仓库带了两个。

5.1 LoCoMo — 长对话、跨会话记忆

评什么

LoCoMo(来自 SNAP Research)是长期对话记忆基准:10 个合成用户, 每人约 35 段会话,多轮 + 需要时序推理跨会话回忆;题目分类别(navigation/media/communication)和 难度(easy/medium/hard)(benchmarks/locomo/README.md:206-214)。

怎么用 Zep 跑

这套 harness 做得比 longmemeval 工程化得多——Pydantic 配置 + YAML + 时间戳 run + config 快照 + 测试套件。 统一 CLI 一个入口三个模式(benchmark.py:354-359):--ingest / --eval / --cleanup

  • 摄取(ingestion.py):每个用户对话建一个 graph_id 的独立图,先设 ontology 再灌数据 (set_ontology,ingestion.py:90),然后把每条消息用 graph.add(type="message", ...)会话真实时间戳 灌进去(ingestion.py:138)。ontology 是一套 8 类实体(Person/Preference/Location/Event/Object/Topic/ Organization/Document,ontology.py:86),每个实体的 docstring 其实是给抽取器的分类优先级指令—— 比如 Preference 那条写"优先级高于除 User/Assistant 外的一切,用低阈值触发"(ontology.py:11-18)。
  • 评测(evaluation.py):evaluate_locomo()(evaluation.py:75)对每题搜 nodes+edges、拼 context、 生成、判分,跳过 category 5(没提供标准答案,evaluation.py:88)。它同样有主指标完整性评估 (evaluate_context_completeness,evaluation.py:251),和自研 harness 同源。搜索包了 tenacity 重试抗 503 (_graph_search_with_retry,evaluation.py:63)。
  • 多跑取分布(重点):--num-runs N 跑 N 遍,save_experiment_summary()(persistence.py:301)做 两种聚合——一是把所有 run 的原始值合并成"统一分布"看真实离群(all_runs_combined,persistence.py:397), 二是各 run 的汇总值再算均值/标准差看跨 run 稳定性。仓库里存的 experiment_20251207_215609 就是 10 次 run、 准确率 mean≈0.803、std≈0.004 的真实产物——标准差这么小本身就是'评测可复现'的证据

配置全在 benchmark_config.yaml,经 Pydantic 校验(config.py:47,from_yaml 在 58 行):reranker 只能是 cross_encoder/rrf/mmr,limit 卡 1–100(config.py:13-25)。

5.2 LongMemEval — 多会话大海捞针

评什么

LongMemEval 把一个问题埋进几百段无关会话("haystack")里,考记忆系统能不能从海量历史中捞出那根针, 还按问题类型分别判分:temporal-reasoning、knowledge-update、single-session-preference 等 (zep_longmem_eval.py:47)。

怎么用 Zep 跑

zep_longmem_eval.py 是一个 LongMemEvalRunner 类:

  • 摄取(ingest_data,zep_longmem_eval.py:184):每条多会话样本建一个用户,逐会话 memory.add_session + 逐消息 memory.add,消息带会话原始日期(zep_longmem_eval.py:230-252)—— 时间戳对时序推理题是命根子。
  • 评测(evaluate_conversation,zep_longmem_eval.py:330):搜 edges + nodes(各 limit 20)、拼 context、 生成、判分。判分 prompt 按问题类型切换(GRADING_PROMPTS 字典查表,zep_longmem_eval.py:293), 比如 temporal-reasoning 那条特意宽容"天数差一天(off-by-one)也算对"(zep_longmem_eval.py:49)。
  • 对照基线(重点):evaluate_conversation_baseline(zep_longmem_eval.py:376)不走 Zep 检索,直接把 所有会话全文塞进 context 让 LLM 硬答。跑 --baseline 就得到"不用记忆系统、全靠长上下文"的分数—— 这正是证明'Zep 有用'的关键对照组:如果 Zep 检索能用少得多的 token 逼近甚至超过全量塞入,就赢了。 评测按 batch 并发跑(run_evaluation,zep_longmem_eval.py:420)。

另有一个独立的 Zep Test Harness/zep_eval.py(benchmarks/longmemeval/Zep Test Harness/zep_eval.py:18): 它把"生成答案"和"判分"解耦——先由别处产出 zep_responses.json,这个脚本只负责离线判分。 好处是判分可以脱离摄取/检索单独重跑(换个 judge 模型重判一遍不用重灌图)。

5.3 三套系统横向对比

维度自研 harnessLoCoMoLongMemEval
数据自带合成(房产场景)公开(SNAP,10 用户)公开(数百会话/样本)
工程化最完整(4 阶段+快照+断点)高(Pydantic/YAML/多 run)中(单类,notebook 起家)
主指标完整性(COMPLETE/PARTIAL/INSUFF)完整性 + 准确率准确率(按题型判)
判分LLM judge + PydanticLLM judge + PydanticLLM judge,prompt 按题型切换
基线对照(全文塞入)
多 run 稳定性单 run(每 run 独立目录)(experiment_summary)

一句话:自研 harness 教你"怎么把评测做可复现",公开基准教你"怎么和世界比"。 三者共享同一套 "搜图→拼 context→生成→LLM 判分"骨架,以及那个反复出现的时序防御 prompt——因为它们量的是同一个 东西:01 里那个时序知识图谱。


6. 巧妙之处(可借鉴的技术)

  • 把"记忆好不好"拆成两把独立的尺子,再做交叉归因。 主指标只量检索、次指标量端到端,交叉出 accuracy_when_complete 就能回答"是检索烂还是生成烂"(zep_evaluate.py:1105-1113)。
  • config 快照用最笨也最可靠的办法:shutil.copytree 整目录拍照。 不发明格式、不做 diff,直接留原样, 三个月后照样能读(zep_ingest_users.py:571)。
  • 超时按工作量成比例给,而非一刀切。 num_episodes * POLL_TIMEOUT_PER_EPISODE——大图多等、小图少等 (zep_ingest_users.py:389)。
  • JSONL 追加 + 文件偏移续读,让切块和灌库能流水并发。 生产者边写边标 status,消费者 seek 到偏移续读 (zep_ingest_documents.py:150)。
  • 原子写 checkpoint(.tmp + os.replace)+ 成功即删。 崩溃安全,且不留垃圾(checkpoint.py:5-24)。
  • LongMemEval 的全文基线是"证伪自己"的诚实设计。 主动给出"不用你也行"的对照分,而不是只报好看的数字 (zep_longmem_eval.py:376)。
  • 退避加抖动防惊群。 一堆并发请求撞限流后不会同时重试(retry.py:35-37)。

7. 边界与局限(诚实)

  • 判分靠 LLM,天生有噪声。 主/次指标都用 LLM judge,判分本身不是确定性的——所以 LoCoMo 才要 --num-runs 多跑取均值/标准差(persistence.py:301)。"评测难做"的根源就在这:考官也是个会犯错的模型。
  • 默认关掉 episode 检索。 USER_EPISODES_LIMIT=0DOC_EPISODES_LIMIT=0 (config/evaluation_config/constants.py),默认只搜 facts/entities;要评原始 episode 检索得手动开。
  • 自研 harness 是单 run 心态。 每次评测落一个独立目录,但没有内建的跨 run 聚合/方差统计 (那是 LoCoMo 才有的);要看稳定性得自己比对多个 results.json
  • 异步 + 随机后缀 ⇒ 无法字节级复现。 快照锁的是输入配方,不是输出;同一配置两次跑,图的内容会因 异步抽取与 LLM 随机性而有差异(见 §3.3a 与 01 的最终一致性)。
  • --no-poll 是把双刃剑。 跳过等待能省时,但评测前得自己确认图建好了,否则测的是半成品图。
  • 公开基准要联网下数据、要额外 API key。 LoCoMo 从 GitHub raw 拉、LongMemEval 从 Google Drive 拉 (ingestion.py:41 / zep_longmem_eval.py:142),且 LoCoMo/LongMemEval 分别用 OpenAI、自研 harness 用 Gemini, 跨基准的分数不能直接比(模型不同)。

8. 代码地图(导航索引)

主题文件路径符号名
用户摄取主流程zep-eval-harness/zep_ingest_users.pyingest_user / main
异步摄取轮询zep-eval-harness/zep_ingest_users.pypoll_task_ids
checkpoint 包装(每用户)zep-eval-harness/zep_ingest_users.pyingest_user_with_checkpoint
config 快照(copytree)zep-eval-harness/zep_ingest_users.pywrite_run_manifest
文档切块 + LLM 上下文化zep-eval-harness/zep_chunk_documents.pyrun_chunking / contextualize_chunk
切块续跑(读已完成)zep-eval-harness/zep_chunk_documents.pyread_completed_chunks
文档灌库 follow 模式zep-eval-harness/zep_ingest_documents.pyfollow_and_ingest
并行搜图zep-eval-harness/zep_evaluate.pyperform_graph_search
主指标·完整性zep-eval-harness/zep_evaluate.pyevaluate_context_completeness
次指标·准确率zep-eval-harness/zep_evaluate.pygrade_ai_response
单题端到端流水zep-eval-harness/zep_evaluate.pyprocess_single_query
并发评测 + 预热zep-eval-harness/zep_evaluate.pyevaluate_all_questions
结果聚合(总/类别/用户/相关性)zep-eval-harness/zep_evaluate.pycalculate_aggregate_statistics
保存结果 + 父 run 引用zep-eval-harness/zep_evaluate.pysave_results
退避重试zep-eval-harness/retry.pyretry_with_backoff
原子 checkpointzep-eval-harness/checkpoint.pysave_checkpoint / delete_checkpoint
轮询常量zep-eval-harness/config/constants.pyPOLL_INTERVAL / POLL_TIMEOUT_PER_EPISODE
评测搜索/模型常量zep-eval-harness/config/evaluation_config/constants.pyUSER_FACTS_LIMIT / LLM_JUDGE_MODEL
LoCoMo CLI 三模式benchmarks/locomo/benchmark.pymain / evaluate_data
LoCoMo 摄取(建图+ontology)benchmarks/locomo/ingestion.py_ingest_locomo_graph
LoCoMo 评测benchmarks/locomo/evaluation.pyevaluate_locomo
LoCoMo 多 run 聚合benchmarks/locomo/persistence.pysave_experiment_summary
LoCoMo ontology(分类优先级)benchmarks/locomo/ontology.pyZEP_NODE_ONTOLOGY_V2
LoCoMo Pydantic 配置benchmarks/locomo/config.pyBenchmarkConfig
LongMemEval 摄取benchmarks/longmemeval/zep_longmem_eval.pyLongMemEvalRunner.ingest_data
LongMemEval Zep 评测benchmarks/longmemeval/zep_longmem_eval.pyevaluate_conversation
LongMemEval 全文基线benchmarks/longmemeval/zep_longmem_eval.pyevaluate_conversation_baseline
LongMemEval 按题型判分benchmarks/longmemeval/zep_longmem_eval.pyGRADING_PROMPTS
LongMemEval 离线判分benchmarks/longmemeval/Zep Test Harness/zep_eval.pyzep_grader