agent-memory LoCoMo 评测数据集实践:数据放置、mini 冒烟样本截取与 SHA-256 校验
agent-memory LoCoMo 评测数据集实践:数据放置、mini 冒烟样本截取与 SHA-256 校验
本篇聚焦 agent-memory(jiuwen_memory 内核)本地端到端评测框架中的 LoCoMo 数据集约定:官方 locomo10.json 的标准放置位置、locomo_mini.json 冒烟样本的机械截取规则、SHA-256 校验口径,以及如何通过 evaluation/config.yml 与 runner 参数在 mini 与标准全量数据之间切换。读完本篇,你可以独立完成 LoCoMo 冒烟评测的跑批、复现验证与结果溯源,并理解 mini 样本"只减规模、不换链路"的设计边界。
LoCoMo 数据集在评测框架中的位置
agent-memory 仓库的 evaluation/ 目录提供本地一键端到端评测能力,支持 LongMemEval 与 LoCoMo 两套相互独立的 runner(分别位于 evaluation/longmemeval/ 与 evaluation/locomo/),通过官方 assemble_runtime() 装配记忆内核,覆盖"写入 → 抽取 → 存储 → 检索 → 回答生成 → 判分"全链路。
数据侧的目录约定由 输入数据目录说明 明确:
evaluation/datasets/只存放评测输入,不存放运行结果、日志、审计包或缓存;locomo/子目录存放 LoCoMo 原始数据和可提交的微型样本,longmemeval/子目录同理;- 完整数据文件(
locomo10.json、longmemeval_oracle.json)默认被.gitignore排除;两个 mini 样本作为本地冒烟输入保留并提交。
这一边界在 evaluation/README.md 的"PR 提交边界"中进一步落实:可提交的是两个数据目录的 README 与 locomo_mini.json、longmemeval_mini.json;不得提交完整标准数据与 evaluation/environment/.env 中的真实密钥。
数据文件放置约定
按 LoCoMo 数据说明 的约定,官方数据集与冒烟样本的放置位置固定为:
evaluation/datasets/locomo/locomo10.json # 官方完整数据(不提交,.gitignore 排除)
evaluation/datasets/locomo/locomo_mini.json # 本地冒烟样本(提交入库)
locomo10.json 是 LoCoMo benchmark 的完整 10 段双人对话样本(数组下标 0~9,共 10 个 conversation)。完整文件获取后放入上述路径即可,evaluation/config.yml 中 locomo.data 默认指向 mini 文件,切换全量只需改数据路径与范围(见后文"切换到标准数据集")。
真实连接串与模型配置经 environment/.env.example 复制为 evaluation/environment/.env 后注入;配置文件本身不落密。LoCoMo 相关的关键变量包括:
API_BASE= # LLM OpenAI 兼容端点
MODEL_NAME=GLM-5.2
API_KEY=
EMBED_MODEL_NAME=BAAI/bge-m3
REDIS_URL=redis://127.0.0.1:6379/0
MILVUS_URI=http://127.0.0.1:19530
MILVUS_COLLECTION=agent_memory_vectors
ES_HOSTS=http://127.0.0.1:9200
ES_FULLTEXT_INDEX=agent_memory_fulltext
这些变量在 locomo/config.yml 中以 ${VAR} / ${VAR:-默认值} 形式被 shared/config_loader.py 的 expand_env() 递归展开,实现"配置文件不落密、连接串经环境变量注入"。
locomo_mini.json 冒烟样本:机械截取规则
locomo_mini.json 的定位是本地冒烟输入,它只用于验证端到端链路是否打通,不代表正式分数。文档对其生成方式的描述非常严格——由当前标准 locomo10.json 机械截取得到,而非人工改写或 Mock:
- 来源:标准
locoma10.json(SHA-256:79FA87E90F04081343B8C8DEBECB80A9A6842B76A7AA537DC9FDF651EA698FF4); - 截取内容:原始数组下标
1、sample_id=conv-30、session_1的D1:1和D1:2、原始qa[0]; - 对话、日期、问题、答案、证据、category 均未改写。
当前仓库中 locomo_mini.json 的完整内容如下,可直接对照上述规则验证截取范围:
[
{
"qa": [
{
"question": "When Jon has lost his job as a banker?",
"answer": "19 January, 2023",
"evidence": ["D1:2"],
"category": 2
}
],
"conversation": {
"speaker_a": "Jon",
"speaker_b": "Gina",
"session_1_date_time": "4:04 pm on 20 January, 2023",
"session_1": [
{
"speaker": "Gina",
"dia_id": "D1:1",
"text": "Hey Jon! Good to see you. What's up? Anything new?"
},
{
"speaker": "Jon",
"dia_id": "D1:2",
"text": "Hey Gina! Good to see you too. Lost my job as a banker yesterday, so I'm gonna take a shot at starting my own business."
}
]
},
"sample_id": "conv-30"
}
]
从内容可以直观看到截取规则的自洽性:qa[0] 是一道 category=2(temporal,时间推理)题,证据指向 D1:2——Jon 在 1 月 20 日的会话中说"昨天辞掉了银行的工作",因此标准答案为 19 January, 2023。这道题恰好要求记忆系统结合 session_1_date_time 做相对时间("yesterday")到绝对日期的换算,是冒烟样本中很有代表性的最小单元。
为什么不保留 event_summary、observation、session_summary
标准 LoCoMo 样本中带有 event_summary、observation、session_summary 等派生标注字段,但 mini 中刻意不放,原因有二(见 LoCoMo 数据说明):
- 一致性:这些字段会引用被裁掉的 turn 或 session(例如 session 摘要会概括整个 session,而 mini 只保留了 2 个 turn),保留它们会引入自相矛盾的上下文;
- 无消费方:SSH LoCoMo v5 runner 也不读取这些派生标注字段。从源码结构可以印证这一点——locomo/run.py 的写入路径只读取
conversation下的speaker_a/speaker_b、session_*对话 turn(含可选blip_caption)以及qa列表;locomo/prompts.py 的判分走get_judge_prompt()(不带 evidence 参数),数据集中的evidence字段同样不直接送入 judge。
mini 与正式链路的等价性
mini 样本"只减规模、不换链路":它仍运行远端 v5 的完整写入、抽取、Redis/Milvus/Elasticsearch 存储、Top-200 检索、多 cutoff 生成与 Judge 链路,不是 Mock。因此 mini 结果可以用来验证环境与配置是否跑通(例如后端是否健康、抽取 prompt 是否零产出、RRF 融合是否生效),但其分数与 LoCoMo 官方口径的正式分数不可比。
SHA-256 校验口径:以 Git blob 原始字节为准
LoCoMo 数据说明 给出了两个文件的 SHA-256 值,并特别说明了校验口径:
| 文件 | SHA-256 |
|---|---|
locomo10.json(标准数据) |
79FA87E90F04081343B8C8DEBECB80A9A6842B76A7AA537DC9FDF651EA698FF4 |
locomo_mini.json(冒烟样本) |
C983279B695C284BBE60DB8257DE951C0298D18690F41550100CBEA02E3EB306 |
关键约束是:以上 SHA-256 均以仓库 raw/Git blob 的原始字节为准。这样做的动机是避免 Windows 检出时的 CRLF 换行符转换影响校验值——如果本地 git config core.autocrlf 在检出时把 LF 改写为 CRLF,直接对磁盘文件做 sha256sum 会得到与记录值不同的结果。校验时应对比仓库 raw 字节(例如 git cat-file blob 或仓库的 raw 文件),而不是直接哈希工作区文件。
这套口径在跑批产物中也得到了延续:locomo/run.py 的 _file_digest() 会对本轮实际加载的数据文件计算 SHA-256 前 16 位,并写入 run_meta.json 的 data_sha256_16 字段,供事后核对"这一轮的 qa_idx 是否仍指向同一批题"。
运行 mini 样本:一键脚本与配置项
一键运行与 benchmark 切换
在仓库根目录执行一键脚本即可(Windows 用 run.ps1,Linux/SSH 用 run.sh):
sh evaluation/run.sh
脚本会自动创建/复用 .venv、按 evaluation/environment/requirements.txt 安装依赖、复用或拉起健康检查通过的 Redis(6379)/Elasticsearch(9200)/Milvus(19530),然后按 evaluation/config.yml 执行评测。切换 benchmark 只需改配置第一项:
benchmark: locomo # 或 longmemeval
也可以临时覆盖而不改文件(注意:该方式只运行 Python,不会自动拉起后端):
.\.venv\Scripts\python.exe -m evaluation --config evaluation\config.yml --benchmark locomo
run_id 每次启动唯一生成(时间戳 + uuid4 前 8 位)。LoCoMo 用它生成唯一 SCOPE_TAG/scope_org,因此即使复用本机已有的 Redis/Elasticsearch/Milvus,重复运行也不会检索到上一轮留下的记忆。
locomo 配置段与 runner 参数
evaluation/config.yml 的 locomo 段是当前默认指向 mini 数据的运行入口:
locomo:
data: datasets/locomo/locomo_mini.json # 相对 evaluation/ 解析
memory_config: locomo/config.yml # 内核装配配置
output_root: outputs/locomo
conversation_ids: [0] # mini 中只有下标 0
max_sessions: 0 # 0 = 不限制
max_turns: 5 # 每 session 前 N 轮(mini 实际只有 2 轮)
max_qa: 2 # 每 conv 最多 N 道 QA(mini 实际只有 1 道)
concurrency: 1
recall_top_k: 200
cutoffs: [10, 20, 50, 200]
run_tag: local-smoke
scope_tag: "" # 正常运行保持为空
skip_write: false
cleanup_after_run: false
evaluation/locomo/entry.py 的 CLI 参数与配置项一一对应,常用项如下:
| 参数 | 默认值 | 含义 |
|---|---|---|
--data |
datasets/locomo/locomo10.json |
数据文件路径 |
--conversation-ids |
0 |
逗号分隔的会话数组下标 |
--max-sessions / --max-turns / --max-qa |
0 / 5 / 2 |
截取范围,0 表示不限制 |
--concurrency |
1 |
conversation 级并发(conv 内部 cutoff/QA 串行) |
--recall-top-k |
200 |
每题召回条数 |
--cutoffs |
10,20,50,200 |
多 cutoff 切片,生成 top-k 准确率曲线 |
--run-tag |
local-smoke |
产物目录标签 |
--scope-tag |
空 | 仅在复用已写入数据时填写 run_meta.json 中的 SCOPE_TAG |
--skip-write |
关 | 跳过写入、复用后端已有记忆(必须与 --scope-tag 同时使用) |
--cleanup-after-run |
关 | 成功完成后清理本轮可检索记忆与派生索引 |
入口对三个易误用参数组合做了硬性校验(见 entry.py):--skip-write 必须同时指定 --scope-tag;--scope-tag 仅能与 --skip-write 一起用于显式复用旧 Scope;复用旧 Scope 时禁止开启 --cleanup-after-run,避免误删对比基线。
mini 默认规模即"1 个 conversation、2 个 turn、1 道 QA"(因为数据中只有这些内容),max_turns: 5、max_qa: 2 等截取上限对 mini 实际不生效。
切换到标准全量数据集
切换到全量只需换数据路径和范围,不换测评实现:
locomo:
data: datasets/locomo/locomo10.json
conversation_ids: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
max_sessions: 0
max_turns: 0
max_qa: 0
框架建议(见 evaluation/README.md):本阶段是功能验证,先保持 mini 与 concurrency: 1,确认端到端通过后再放大范围。
数据如何进入记忆内核:写入、检索与判分
写入路径
locomo/run.py 中 ConversationProcessor.write_conversation() 把每个 turn 独立写入,关键口径:
- role 映射:
speaker_a → user、speaker_b → assistant,并在正文中做名称替换,与 prompt 中"User = main person"对齐; - 时间锚点:
occurred_at取 session 起始时间逐 turn 递增 1 秒;metadata 携带observation_date(session 日期,%Y-%m-%d)、speaker、session、seed_key(形如conv_N/D1:2)与write_id; - 抽取触发:metadata 中
infer: "true"触发Engine.write走 Evolver(EXTRACT) → LLM Extractor 抽取派生记忆(否则只走 classifier 打标 + 建索引的默认路径); - 默认关掉的兜底开关:
RAW_FALLBACK=0(抽取零产出不补写原文,以测纯抽取质量)、RAW_ALWAYS=0、DATE_PREFIX=0(正文不加绝对日期前缀,时间锚点统一取 metadata 的observation_date行)。
Scope 隔离方面,run.py 将 SCOPE_TAG 校验后拼为 SCOPE_ORG = "locomo-infer-{SCOPE_TAG}"(默认 SCOPE_TAG 即 {RUN_TAG}-{RUN_ID}),每个 conversation 使用 Scope(org=SCOPE_ORG, user="conv_N"),写入、检索、清理都限定在各自 Scope 内。
检索路径与多 cutoff 评测
每题只召回一次(top_k = RECALL_TOP_K = 200,DisclosureLevel.L2、with_trajectory=True),再按 10 → 20 → 50 → 200 多个 cutoff 对同一批召回结果切片,各自生成答案并判分,得到 top-k 准确率曲线。多 cutoff 复用同一召回,题内串行、conv 间按 concurrency 并行。
召回后端由 locomo/config.yml 定义:rerank_enabled: false(无 rerank 基线)、graph_enabled: false,keyword 路走本地 Elasticsearch(BM25)、vector 路走 Milvus(COSINE,dim=1024,对齐 bge-m3),融合用 weighted RRF(k=20、vector 2.0 / keyword 1.0);检索管线的 min_score、min_score_ratio、min_score_ratio_uncalibrated 均显式置 0,让融合结果不经阈值再砍,对齐基线口径。
回答生成与 J-score 判分
回答与判分 prompt 在 locomo/prompts.py 中,采用业界标准 J-score 方法论(categories 1–4 上的二分类 LLM judge):
- 类别映射(prompts.py):1 = multi-hop(282 题)、2 = temporal(321 题)、3 = open-domain(96 题)、4 = single-hop(841 题);5 = adversarial(446 题)不参与计分,runner 中
category > 4 or category < 1的题直接跳过; - 回答生成:
get_answer_generation_prompt()取召回结果前ANSWERER_MEMORY_LIMIT = 200条,按created_at升序(时间叙述式)呈现,不展示排名与分数以避免锚定偏差,并注入reference_date(由第一个可解析的session_*_date_time推断)作为时间推理基准; - 判分:统一 judge prompt 输出
{"label": "CORRECT" | "WRONG"},规则包含部分正确即算对(partial credit)、同义改写等价、14 天日期容差、时长 50% 容差等;category 3(open-domain)的标准答案在判分前经preprocess_answer()截取分号前的主答案部分。
运行产物与数据清理
每轮产物写入 evaluation/outputs/locomo/<run_id>_<run_tag>/(默认 local-smoke 标签),LoCoMo 侧的关键文件:
evaluation/outputs/locomo/<run_id>_local-smoke/test_result_v5.json # 各 conv 分 cutoff 准确率
evaluation/outputs/locomo/<run_id>_local-smoke/test_metrics_v5.json # 阶段耗时/token/分位数
evaluation/outputs/locomo/<run_id>_local-smoke/run_meta.json # 参数快照/Scope/数据 digest
evaluation/outputs/locomo/<run_id>_local-smoke/run.log # 脚本日志
evaluation/outputs/locomo/<run_id>_local-smoke/engine.log # 引擎日志
另有按 conv 分文件的 test_mem_extract_v5<conv>.jsonl(每 turn 的派生记忆)、test_recall_v5<conv>.jsonl(每题召回明细与轨迹)、test_errors_v5<conv>.jsonl(错题详情:召回记忆 + 回答 + judge 输出)。按轮次隔离产物目录是必要的:这几类文件以追加模式写入,共用目录会跨轮混数据。run_meta.json 除记录生效参数与 SCOPE_TAG 外,还通过环境变量白名单 + 密钥标记双保险排除 API Key 类值,配置快照落的是原始 YAML(只含 ${VAR} 变量名,不含展开后的密钥)。
后端数据默认保留以便 bad case 与 evidence 溯源。确认无需复盘后,可在 evaluation/config.yml 中显式开启 locomo.cleanup_after_run: true:程序会先完成写入、检索、Judge 与指标产物采集,再经正式 MemoryAPI 的 DeleteMode.PURGE 删除本轮 Scope 下的 Redis 记忆真源及 Milvus/Elasticsearch 派生索引(见 run.py 的 _purge_run_data(),删除后会分页复核 Scope 内无残留)。/messages/ 原文缓存与审计记录按内核契约保留;清理失败返回非零退出码,不会静默报告成功。
注意事项与自检清单
- mini 分数不是正式分数:
locomo_mini.json只有 1 conv / 2 turn / 1 QA,仅验证链路;报告正式指标必须切换datasets/locomo/locomo10.json全量范围。 - 校验 SHA 用 Git blob 原始字节:跨平台(尤其 Windows CRLF)场景下,对磁盘文件直接哈希可能得到不同值。
- 复用旧 Scope 的三件套约束:
scope_tag取自原运行run_meta.json的effective.SCOPE_TAG,必须同时设skip_write: true,且禁止开启cleanup_after_run。 - 完整数据与密钥不入库:
locomo10.json、evaluation/environment/.env及其真实值均被.gitignore排除,提交 PR 时只带数据目录 README 与 mini 文件。 DISABLE_THINKING默认保持0:仅自建 vLLM GLM 部署需要时设为1;托管 OpenAI 兼容端点(如华为云 MaaS DeepSeek)传该参数会触发慢路径,不要开。
综上,agent-memory 的 LoCoMo 数据侧以"标准数据本地放置 + 机械截取的 mini 冒烟样本 + Git blob 字节级 SHA-256 口径"三件套保证了评测输入的可复现性,而 runner 通过 Scope 隔离、多 cutoff 判分与全链路产物落盘,把这一最小样本扩展成了可溯源的完整评测闭环。