claude-mem 的 LoCoMo 长对话记忆评测:基于 Worker API 构建端到端记忆摄入管道
本文讲解如何在 claude-mem 代码库中搭建 LoCoMo 长期对话记忆基准评测的第一阶段(Phase 01: Foundation & Ingestion Prototype):下载基准数据集、将多轮对话会话转换为 claude-mem 的 observations(记忆观察项)写入 Worker 服务,并通过检索接口验证整条记忆管道的端到端可用性。读完后你将掌握"外部对话数据集 → 确定性会话 ID → Worker HTTP API → 可检索 observations"的完整接入方案,理解双评分方法论(token 级 F1 + LLM-as-a-Judge)的设计意图,并具备复现该摄入管线的全部实操细节。
评测背景与方法论:为什么用 LoCoMo、为什么是双评分
LoCoMo 是面向长程多会话对话的记忆评测数据集,其原始论文(ACL 2024)使用 token 级 F1 作为核心指标。claude-mem 的这份 LoCoMo 评测 playbook(LOCOMO-EVAL-01.md)采用**双评分(dual scoring)**方法:
- token 级 F1:用于与原 LoCoMo 论文结果对齐比较;
- LLM-as-a-Judge(J-score):用于与现代记忆系统横向比较。文档中给出的参照基线(引自 Mem0 论文,arXiv 2504.19413)为:Mem0 66.88%、Zep 65.99%、OpenAI Memory 52.90%、全上下文上限(full-context ceiling)72.90%。
一个关键的方法论细节:adversarial(对抗性)QA 类别在 J-score 比较中被排除——因为该类别没有可用的 ground truth,这与 Mem0 的方法论保持一致。这一取舍直接影响了后续数据集加载器的接口设计(见下文 getQuestionsForConversation 的默认行为)。
Phase 01 的验收目标是:真实 LoCoMo 对话数据被存储为可检索的 claude-mem observations,证明"摄入 → 压缩 → 存储 → 检索"的完整记忆管道端到端跑通。
评测项目结构:目录布局与 TypeScript 配置
按 playbook 的定义,评测代码组织在仓库根目录下的 evals/locomo/ 中,目录树如下:
evals/locomo/
├── src/
│ ├── ingestion/ # 摄入适配器 + Worker API 客户端
│ ├── qa/ # QA 生成逻辑
│ └── scoring/ # F1 与 Judge 评分
├── data/ # 数据集(含克隆的 locomo 仓库,已加入 .gitignore)
├── results/
│ └── checkpoints/ # 断点/中间结果
├── scripts/ # 可执行脚本(验证、摄入、核对)
└── tests/ # 单元测试
配套配置要求:
evals/locomo/tsconfig.json使用严格 TypeScript 设置:module: esnext、target: esnext、moduleResolution: bundler、strict: true。由于 Bun 原生运行 TypeScript,这份配置主要服务于 IDE 支持而非运行时;- 将
evals/locomo/data/locomo-repo/加入项目根目录.gitignore,避免克隆下来的数据集进入版本控制。
说明:在当前仓库快照中未检索到
evals/locomo/目录,上述布局以该 playbook 的描述为准(playbook 中所有任务均标记为已完成,实现可能位于独立分支或未合入当前主线)。下文引用的 Worker 路由源码路径则均已确认存在于当前仓库。
数据集准备与类型建模:先读数据,再写类型
数据获取流程强调"先检查、后建模":
- 将 snap-research 的 LoCoMo 官方仓库克隆到
evals/locomo/data/locomo-repo/(文档刻意提醒要先检查仓库结构确认数据集路径,预期在data/locomo10.json); - 检查
locomo10.json的第一条记录,确认实际字段名后再编写类型定义——这是避免类型与数据脱节的关键纪律; - 在
evals/locomo/src/types.ts中基于真实数据创建接口。
playbook 定义的完整类型清单(信息密度高,值得逐条理解):
| 类型 | 字段 | 说明 |
|---|---|---|
LoCoMoConversation |
sample_id, speaker_a, speaker_b, conversation(session 数组), qa(问题数组) | 单条对话样本 |
LoCoMoSession |
session_id, date, turns 数组,另有索引签名 [key: string]: any |
索引签名用于承接 session_1_observation、session_1_summary、events_session_1 等动态键 |
LoCoMoTurn |
speaker ("A" | "B"), dia_id (number), text (string),可选 img_url 与 blip_caption | 单个话轮,图像轮带 BLIP 描述 |
LoCoMoQA |
question, answer, category (string), evidence_dialog_ids (number[]) | 问答对与证据轮 ID |
IngestionProgress |
sample_id, total_sessions, sessions_ingested, observations_queued, status | 摄入进度 |
QAResult |
question, predicted_answer, ground_truth_answer, category, f1_score, judge_scores(可选 JudgeAggregation), search_results_used, search_latency_ms, answer_latency_ms, answer_input_tokens, answer_output_tokens | 单题结果,含延迟与 token 计量 |
JudgeResult |
score (0–100), explanation | 单次 Judge 打分 |
JudgeAggregation |
mean_score, std_dev, run_count, individual_scores | 多次打分的聚合 |
LatencyStats |
search_p50_ms, search_p95_ms, answer_p50_ms, answer_p95_ms, total_p50_ms, total_p95_ms | 检索/回答延迟分位 |
EvalReport |
results, per_category_f1_scores(每类含 mean_f1/count/min_f1/max_f1), overall_f1, per_category_judge_scores(mean_j/std_dev/count), overall_judge_score, latency_stats, token_stats(total_input_tokens/total_output_tokens/mean_tokens_per_question), metadata(model, judge_model, timestamp, total_questions, scoring_methods: string[]) | 最终报告结构 |
从类型设计可以推断出评测的完整数据流:逐题记录检索命中数与两段延迟(search/answer)、双套评分(F1 与多轮 Judge 聚合)、以及按类别拆分与全局汇总的报告结构——这正是 Phase 03+ 评分阶段要消费的数据模型。
数据集加载器:动态键解析与 adversarial 默认排除
evals/locomo/src/dataset-loader.ts 是摄入与 QA 阶段共同的只读入口,playbook 定义了五个函数:
loadDataset():读取并解析克隆仓库中的locoma10.json,返回类型化的 conversation 数组;getConversation(sampleId):按 sample_id 返回单条对话;getSessionsForConversation(conversation):提取 sessions 并为每个 session 解析动态键——例如 session_id=1 的会话需查表取session_1_observation、session_1_summary、events_session_1,返回带 observation/summary/events 字段的增强会话对象。这是对 LoCoMo 数据"扁平键 + 数字后缀"结构的针对性封装;getQuestionsForConversation(conversation, options?):返回类型化 QA 数组,接受可选的excludeCategories参数,默认排除 "adversarial"(服务于 J-score 比较);getAllQuestionsForConversation(conversation):返回包含 adversarial 在内的全量类别(服务于 F1-only 分析)。
两套问答入口的分离,把方法论层面的"adversarial 不参与 J-score"约束固化成了 API 默认行为,调用方不需要记得手工过滤。
此外还有数据集自检能力:
getDatasetStats():返回conversation_count、total_sessions、total_qa_questions、qa_by_category(类别 → 计数)、qa_excluding_adversarial;evals/locomo/scripts/validate-dataset.ts:加载数据集、调用getDatasetStats()、打印格式化的统计表,单独列出 adversarial 计数并注明其不参与 J-score 比较。
运行方式与验收标准:
bun evals/locomo/scripts/validate-dataset.ts
验收:输出应显示 10 条对话,且 session 与 QA 数量合理。
Worker API 客户端:以路由源码为契约的 HTTP 封装
这是 Phase 01 中与 claude-mem 主干代码耦合最深的一环。playbook 明确要求:先读 Worker 的 HTTP 路由源码,弄清确切的 API 契约(端点路径、请求/响应格式、认证要求),再动手写客户端。当前仓库中可以确认这两个路由文件的存在:
- SessionRoutes.ts:会话初始化、观察项入队、会话完成等端点。从源码结构看,路由层依赖
SessionManager、DatabaseManager、ClaudeProvider/GeminiProvider/OpenRouterProvider与 provider 分发逻辑(selectProviderForGenerator),请求体经 zod schema 与validateBody中间件校验——这解释了为什么评测客户端必须严格按契约构造请求; - SearchRoutes.ts:检索端点。源码中
semanticContextSchema定义的查询参数为q(可选)、project(可选)、limit(字符串或数字,可选),与评测客户端search(query, project, limit)的三参签名一一对应。
客户端 evals/locomo/src/ingestion/worker-client.ts 的规格:
- 认证:从
~/.claude-mem/.env读取 auth token(文档提示变量名形如AUTH_TOKEN或CLAUDE_MEM_AUTH_TOKEN,要求实际读文件确认精确变量名后再写代码); - Base URL:
http://localhost:37777。该端口在仓库主干中也有印证——cmem-memory-credentials.ts 中定义了HOST_OBSERVER_DEFAULT_PORT = '37777'; - 类型化方法集:
| 方法 | 端点 | 说明 |
|---|---|---|
initSession(contentSessionId, project, userPrompt) |
POST 会话初始化端点 | 建立 claude-mem 会话 |
queueObservation(contentSessionId, toolName, toolInput, toolResponse, promptNumber) |
POST observations 端点 | 入队一条工具执行观察项 |
completeSession(contentSessionId) |
POST 会话完成端点 | 收尾会话 |
getSessionStatus(contentSessionId) |
GET 会话状态 | 供轮询 |
waitForProcessing(contentSessionId, timeoutMs) |
轮询 | 每 2 秒轮询一次 getSessionStatus,直到队列为空或超时 |
search(query, project, limit) |
GET 检索端点 | 必须带计时埋点:记录从请求发起到响应返回的 search_latency_ms |
搜索方法的计时埋点不是装饰——QAResult.search_latency_ms 与 LatencyStats 中的 p50/p95 指标正是从这些逐次采样聚合而来的。
- 错误处理要求描述性消息:连接被拒 →
"Worker not running at localhost:37777";401 →"Invalid auth token";5xx → 附带响应体。
摄入适配器:把对话伪装成一次 Read 工具调用
evals/locomo/src/ingestion/adapter.ts 是 LoCoMo 数据与 claude-mem 摄入协议之间的翻译层,其核心设计是将每段对话会话伪装成一次真实的工具执行,让记忆管道按"正常使用"的方式运转:
1. 确定性 ID 生成
generateContentSessionId(sampleId, sessionId):返回确定性 ID,形如locomo-{sampleId}-s{sessionId}。确定性意味着重复摄入同一会话可幂等对账;generateProjectName(sampleId):返回locomo-eval-{sampleId}——每条对话一个独立 project,这是 QA 阶段能把检索范围隔离到单条对话内的前提(search的 project 参数与摄入时的 project 名必须一致)。
2. 会话到工具执行的转换 formatSessionAsToolExecution(conversation, session, enrichedSession):
toolName:"Read"——模拟一次文件读取;toolInput:JSON.stringify({file_path: "conversation-transcript/session-" + sessionId + ".txt"});toolResponse:格式化后的对话转录文本,模板如下:
[Session {N} — {date}]
[Conversation between {speaker_a} and {speaker_b}]
{speaker_a}: {turn 1 text}
{speaker_b}: {turn 2 text}
...
userPrompt:"Conversation between {speaker_a} and {speaker_b} on {date}"。
3. 一个刻意的设计决策:tool_response 保持原始对话文本,不做任何预处理——由 claude-mem 的观察项压缩 agent(文档记为 Sonnet 4.6)自然完成观察项的提取与压缩,以此模拟真实使用场景。换句话说,评测的不只是"存储 + 检索",而是把"AI 压缩"这个核心环节也纳入了被测范围。这一设计与主干代码中 SessionRoutes 的生成器机制相呼应:入队的观察项会经由 provider 分发(selectProviderForGenerator)交给对应的 LLM provider 处理。
单条对话摄入原型:ingest-one.ts 的七步流程
evals/locomo/scripts/ingest-one.ts 是端到端验证的最小闭环脚本,流程规格:
- 启动前健康检查:请求
http://localhost:37777/api/health(或任意已知端点);连接被拒则提示用户执行以下命令启动 Worker 并以退出码 1 结束:
bun plugin/scripts/worker-service.cjs start
该入口脚本存在于仓库主干(worker-service.cjs)。
- 从数据集加载第一条对话;
- 对对话中的每个 session 执行七步摄入循环:
- 用适配器生成会话 ID 与 project 名;
- 经 worker client 初始化 claude-mem 会话;
- 用适配器把对话轮次格式化为一次工具执行;
- 经 worker client 入队观察项;
- 等待处理完成(每 3 秒轮询一次,单 session 超时 180 秒);
- 完成会话;
- 记录日志:
"Session {N}/{total} ingested — processing took {seconds}s";
- 打印最终汇总:对话 sample_id、摄入的 session 总数、总耗时。
运行与验收:
bun evals/locomo/scripts/ingest-one.ts
无错误完成即通过。playbook 特别提醒:该脚本会发起真实的 Anthropic API 调用用于观察项压缩,可能需要数分钟——这是理解"评测成本主要来自 LLM 压缩环节"的第一个直观证据。
检索验证:证明数据真的存进去且查得到
evals/locomo/scripts/verify-ingestion.ts 完成 Phase 01 的最后一块拼图:
- 以第一条对话的 project 名(即
locomo-eval-{sample_id})在 claude-mem 中检索其名下所有 observations; - 打印观察项总数,并逐项打印 title 与 narrative 的前 100 字符;
- 从该对话中挑选 3 道 QA 题——一道 single-hop、一道 multi-hop(若可用)、一道 temporal(若可用),跳过 adversarial;
- 以问题文本为 query、以该对话的 project 为范围检索,按以下格式输出:
Q: {question text}
Category: {category}
Ground Truth: {answer}
Top Search Results:
1. {observation title} — {first 80 chars of narrative}
2. {observation title} — {first 80 chars of narrative}
3. {observation title} — {first 80 chars of narrative}
bun evals/locomo/scripts/verify-ingestion.ts
这一步验证的是整条链路:LoCoMo 对话 → claude-mem observations → 可检索上下文。如果检索结果能与 ground truth 对应上,说明"压缩时保留的信息量 + 检索的召回质量"共同满足了下游 QA 阶段的基本前提。
小结:Phase 01 交付了什么、下一步是什么
Phase 01 的交付物可以概括为四层:
- 可复现的数据基线:类型化的数据集加载器 +
validate-dataset.ts统计自检(10 条对话、按类别计数的 QA 分布); - 契约驱动的客户端:先读 SessionRoutes.ts 与 SearchRoutes.ts 源码再写客户端,认证、端点、计时、错误消息全部有明确规格;
- 保真的摄入适配:确定性 ID + per-conversation project 隔离 + "原始对话交给压缩 agent"的模拟真实设计;
- 端到端证据:
ingest-one.ts证明管道能跑通,verify-ingestion.ts证明数据可检索。
Phase 02 将在此基础上做全量摄入(10 条对话、支持断点续跑、逐会话容错),playbook 系列文件 LOCOMO-EVAL-02.md 记录了批量摄入与完整性核对的进一步细节,Phase 01 建立的 Worker 客户端与适配器模块会被直接复用。
复现这套评测时的实用提醒:
- 37777 端口被占用时,先确认是 cmem-memory-credentials.ts 中定义的 host observer 默认端口冲突,再决定是否改端口;
search的 project 参数必须与摄入时generateProjectName生成的名字完全一致,否则检索范围为空;- 摄入速度受 LLM 压缩调用限制,单 session 180 秒的轮询超时是为真实 API 延迟留的余量,调低它只会造成假性失败。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00