Memory · 2026.08.30

一个 Memory 系统该忘掉什么:DeerMem 容量淘汰的问题、修复与可复现评测

库满了,删哪条

DeerFlow 的记忆后端 DeerMem 给每个 agent 的 fact 库设了上限,默认 100 条。新 fact 进来,超了上限就得挤掉一条。假设现在有两个候选:一条 confidence 0.9,180 天没人再提过;另一条 confidence 0.7,用户七天前刚确认过"对,就是这样"。再换一组:一条 correction 类别的 fact,记录的是用户纠正过 agent 的某个错误做法,confidence 0.7,对面是十条 confidence 0.9 的 preference

2026 年 8 月之前,DeerMem 的答案是删 0.7 那条,也删那条 correction。

这个问题我在 issue #4641 里提出,AnnaSuSu 在 PR #4789 里用一个需要显式打开(opt-in)的 hybrid-v1 策略修掉了它,PR #4810 把他的评测协议做成了仓库里可以独立重跑的评测。两个 PR 都在 2.1.0 里。

问题:只看 confidence 的淘汰(issue #4641)

#4641 提出时 main8234370a,容量控制全部集中在 _trim_facts_to_max 一个函数里:所有 fact 按 confidence 降序排,保留前 max_facts 条,updater 在追加新 fact 之后调用它。排序不看 updatedAt,没有"最近一次确认"的时间戳,没有类别配额,也没有任何多样性信号。fact 本身有 createdAtupdatedAt 两个字段,淘汰路径不用它们。

单看这一个函数,问题只是排序键太单一。放回整个系统里看,问题是三套治理互不认识:

  • DeerMem 在注入侧对 correction 有专门保护:它是默认的 guaranteed 类别,拥有独立的 500 token 预算,prompt 格式化时和普通 fact 分开挑选,目的是让"别用 pip,用 uv"这种纠正在预算紧张时也能进 prompt。
  • 按 fact 年龄触发的 staleness review 也默认保护 correction。(解决"这条 fact 还是真的吗"的问题)
  • 但容量裁剪两个设置都不读。(解决"库满了,牺牲哪几条"的问题)

correction 一旦被高 confidence 的 preference 挤出库,注入侧那 500 token 保障的是一个已经不存在的东西。#4641 里的这张图是当时的容量路径,和它绕开的两项保护:

flowchart TD Update["应用一次记忆更新"] --> Append["追加通过校验的新 fact"] Append --> Over{"fact 数 > max_facts?"} Over -- "否" --> Persist["持久化当前 facts"] Over -- "是" --> Trim["_trim_facts_to_max"] Trim --> Rank["按 confidence 降序排序"] Rank --> Keep["保留前 max_facts 条"] Rank --> Drop["剔除其余条目"] Keep --> Persist Drop --> DeleteSet["缺失的 fact ID 转为删除"] DeleteSet --> Unlink["物理 unlink Markdown 文件"] Injection["注入侧:correction 有 500 token 保障预算"] Staleness["staleness review:默认保护 correction"] Injection -. "不参与裁剪" .-> Trim Staleness -. "不参与裁剪" .-> Trim

第三层是删除方式。fact 的 schema 只接受 status: "active",删除是物理的:持久化时把快照里缺失的 fact ID 转成 delete,storage 直接 unlink 对应的 Markdown 文件。没有 archived 状态,没有审计记录,没有恢复路径。淘汰错了,看不到删了什么,也看不到它为什么输了排序。

issue 里 AnnaSuSu 先后提了两个要求:有没有具体测试,以及效果要有具体的测试集。前一个我在 upstream/main@e16ef296 上用三个本地离线测试回答,结果和上面的推演一致:0.9@180 天赢了 0.7@7 天;一条 0.7 的 correction 对十条 0.9 的 preference,correction 出局;重新加载后这条 correction 不在持久化数据里,Markdown 文件已删,没有 archived 状态。后一个要求划定了后面两个 PR 的边界:修复的参数要来自受控对比,评测要能被别人重跑。

修复:hybrid-v1(PR #4789,AnnaSuSu)

#4789 由 AnnaSuSu 设计并实现,PR 里对 #4641 的发现和方向做了致谢。我本地那版方向重合的实现没有再提,转去做评测。

评分:三个有界信号

hybrid-v1 的容量评分是三个 [0, 1] 区间信号的加权和:

1
2
3
score = 0.65 * confidence
+ 0.25 * confirmation_freshness
+ 0.10 * access_heat
  • confidence 是现成字段,解析失败时退回 0.5,一条脏数据不会让整个淘汰路径抛异常。
  • confirmation_freshness 用半衰期衰减:有 lastConfirmedAt 就按它算,半衰期 90 天,七天前确认过的 fact 拿到约 0.95;从来没确认过的 fact 退回 createdAt,再乘 0.5,创建这件事本身是比用户确认弱得多的证据;两个时间戳都解析不了就记 0。
  • access_heat 来自一个单独的 usage sidecar,先按 30 天半衰期衰减,再做 log1p(heat) / log(9) 的对数饱和,截到 1。对数饱和的意思是热度 8 就到顶,被搜到一百次和八次没有区别,这条信号只占 0.10 的权重,不会因为某个 fact 反复命中就把别的都挤掉。

三个权重在 config 里可调,加起来必须是 1,不然启动就报错。

回到开头那两条 fact,两条都没有访问热度,access 项为 0。记 confirmation_freshness 为 FF,总分为 SS。0.9@180 天、从未确认的那条:

F=0.5×2180/90=0.125,S=0.65×0.9+0.25×0.125+0.10×00.62F = 0.5 \times 2^{-180/90} = 0.125,\qquad S = 0.65 \times 0.9 + 0.25 \times 0.125 + 0.10 \times 0 \approx 0.62

0.7@7 天、七天前确认过的那条:

F=27/900.95,S=0.65×0.7+0.25×0.95+0.10×00.69F = 2^{-7/90} \approx 0.95,\qquad S = 0.65 \times 0.7 + 0.25 \times 0.95 + 0.10 \times 0 \approx 0.69

七天前的确认赢了。

确认信号从哪来:不加 LLM 调用

难点在 confirmation 信号。"用户确认过这条 fact"不是现成字段,要有人判断。Anna 的做法是不加新的 LLM 调用,复用 memory-update 那次已有的调用:响应 schema 里新增可选的 factsToReinforce 数组,让模型在做记忆更新时顺便报告这轮对话明确确认了哪条已有 fact。模型说了不算,lastConfirmedAt 只有在三个条件同时满足时才会更新:

  1. 确定性的消息处理在最近六条过滤后的人类消息里匹配到了 reinforcement 模式,“对,就是这样”“完全正确”“that’s exactly right” 这类正则,pattern 文件可以扩展;
  2. 模型给出的条目 scope 是 user;reason 非空。
  3. 重复提取、自动注入、搜索命中都不算确认,prompt 里专门写了这条。

pattern 文件的注释里记了一条取舍:“你说的对”“没错”"对的"这类单独的肯定故意不在默认列表里,“对"在"对不起”“对方”"对啊"里都会误命中,需要时自行扩展。有效确认同时重置那条 fact 的 staleness review 时钟,不然一条 createdAt 很老的 fact 刚被确认,转头就被年龄清理掉。

flowchart TD Msg["最近六条过滤后的人类消息"] --> Det["确定性检测:是否命中 reinforcement 正则"] LLM["memory-update 响应里的 factsToReinforce
id / scope / reason"] --> Cond["模型条目:scope == user,reason 非空"] Det --> Gate{"两边同时成立?"} Cond --> Gate Gate -- "是" --> Upd["按 id 找到已有 fact
lastConfirmedAt = now
confirmationCount + 1"] Gate -- "否" --> Skip["不更新"] Upd --> Clock["staleness review 时钟重置"] Excl["重复提取 / 自动注入 / 搜索命中"] -. "都不算确认" .-> Skip

correction 保留槽和其他

对开头第二组 fact,评分改了也不够,十条 0.9 的 preference 在任何加权下都能压过一条 0.7 的 correction。所以 hybrid 模式先给 correction 留 min(10, ceil(max_facts × 10%)) 个槽,按分数最高的 correction 先占,占不满的槽立即释放给普通竞争,超出保留数的 correction 也正常竞争。默认 100 条的库,保留 10 个槽。整条选择路径合起来是这样:

flowchart LR subgraph signals["三个有界信号"] C["confidence
字段值,解析失败取 0.5"] F["confirmation_freshness
lastConfirmedAt 按 90 天半衰期
未确认则 createdAt 衰减再乘 0.5"] A["access_heat
usage sidecar,30 天半衰期
log1p(h) / log(9) 截到 1"] end C --> S["score = 0.65 C + 0.25 F + 0.10 A"] F --> S A --> S S --> R["correction 保留槽
min(10, ceil(max_facts × 10%))
按分数先占,占不满即释放"] R --> Fill["剩余名额按 score 降序填满"] Fill --> Kept["kept"] Fill --> Evicted["evicted"] Evicted --> Audit["审计 sidecar
只记 ID、类别、分数分量、原因"]

另外几处设计一起说:

  • 自动提取、手工或工具创建、导入三条写入路径原来各走各的容量逻辑,现在统一过 select_facts_for_capacity()
  • 淘汰仍是物理删除,软删除涉及存储迁移,但每次容量淘汰会追加一条只含元数据的审计记录:fact ID、类别、策略分数、各分量、原因,不复制内容,默认保留 200 条。
  • usage 和审计 sidecar 与 canonical Markdown 解耦,搜索命中不会改写 fact 文件和它的 updatedAt;sidecar 只在 hybrid-v1 或 shadow mode 打开时才读写,默认部署不多一次文件锁。
  • 默认策略仍是 confidencehybrid-v1 要显式打开;还有一个 shadow mode,用 hybrid 算一遍但不改实际保留结果,只记录两种策略的分歧。

opt-in 的原因 Anna 在 PR 里写得很直接:权重是在受控的、强制到容量上限的对比里选出来的,“They remain opt-in because the evaluation used synthetic metadata and is not sufficient to justify a default switch.”

评测:把一段文字协议做成可以重跑的东西(PR #4810)

#4810 目标是任何人拿到仓库和数据集,能跑出一样的池子、一样的 prompt、一样的判分,再用同一套东西跑一次两边同预算的 QA 对比。"一样"有好几层,每层要单独设计。整条链路如下,后面几小节各对应其中一段:

flowchart LR DS["longmemeval_oracle.json
本地文件,pin revision 与 SHA-256"] --> V["validate
校验 hash,重算 40 题筛选规则
构建 bank,准备 45 个 case"] M["仓库内:config、两个 manifest、prompt
只有题目 ID,没有题目"] --> V V --> Pool["确定性池子重建
sha256 offset 取 9 条干扰项
gold_ / d_ 命名,按 ID 排序"] Pool --> Sel["生产 select_facts_for_capacity()
confidence 与 hybrid-v1
容量 5 / 7 / 9"] Sel --> RP["run-policy
270 行元数据
support retention"] Sel --> RQ["run-qa,容量 7
90 次调用,两边同参数
每行存 request_fingerprint"] RQ --> Rows["responses/ 行文件
预测与非敏感元数据"] Rows --> G["grade-qa
核对五个 hash,重算任务与 fingerprint
盲判分,按 row ID join 回策略"] G --> Out["qa.rows.jsonl
qa.summary.json
qa.stats.json"]

评什么:四个 scenario 各控制一件事

40 道题来自 Hugging Face 数据集 xiaowu0162/longmemeval-cleanedlongmemeval_oracle.json,只取 knowledge-updatetemporal-reasoning 两类,排除 8 个 pilot ID、_abs 结尾的 ID、答案为空或超过 100 字符、答案含 “not enough” 或 “only mentioned”、证据为空或超过 2000 字符的行,每类按 question_id 排序取前 20,切成四组各五题,两类对应的组合并成一个 10 题的 scenario。每道题的证据从 haystack_sessions 里抽 has_answer 的轮次拼成一条支持事实,另配 9 条干扰项。干扰项来自一个 40 条的 bank,取 single-session-usersingle-session-preference 两类、渲染后证据长度在 40 到 700 字符之间的记录。

四个 scenario 的区别全在支持事实和干扰项的元数据上:

Scenario 支持事实 干扰项 想验证什么
confirmation_help confidence 0.70,7 天前确认 前 N 条 0.94 起每条减 0.02,其余 0.68 起递减,都无信号 确认信号能否救回低 confidence 的事实
access_help confidence 0.70,访问热度 8 同上 访问热度能否救回
confidence_control confidence 0.95,无信号 前五条 0.94 起递减;其余 0.70,交替带 7 天前确认或热度 8 0.25 + 0.10 的权重会不会反过来掀翻高 confidence 的事实
noisy_signal_control confidence 0.90,无信号 前 N 条 0.70 且交替带信号,其余 0.70 无信号 干扰项上的噪声信号会不会把稳的事实挤出去

N 由每题的 loss_rank 决定,序列固定为 (6, 6, 6, 8, 8, 8, 10, 10, 10, 10),N = loss_rank − 1。loss_rank 8 的意思是按 confidence 排支持事实排第八,容量 7 时刚好出局。所以纯 confidence 策略在两个 help scenario 各只能保住 3 条,也就是 loss_rank 6 的那三题;两个 control scenario 全保住;5 个 correction case 只保住 1 条。加起来 27/45。这个数字不经过任何模型,是协议对齐的第一道关卡。QA accuracy 是第二层指标:保留下来的 fact 按固定 prompt 交给模型,要求只用给出的 stored memory 回答,不够就输出 INSUFFICIENT,YES/NO 题只答 YES 或 NO,其他题给最短的直接答案。

5 个 synthetic correction case 由 Anna 编写,比如 “USER CORRECTION: I am allergic to peanuts. The earlier note saying I enjoy peanut snacks was wrong.”,问题是 YES/NO,支持事实类别 correction、confidence 0.65,干扰项从 0.90 起每条减 0.03。它测的是保留槽,不是评分。

数据边界:pin 住,不下载,不提交

LongMemEval 的题目、答案和对话不能进仓库,评测又必须能确认大家跑的是同一份数据。做法是把数据集 pin 到仓库 revision 98d7416c 和文件 SHA-256 821a2034…,CLI 从不下载,调用者自己把文件放到本地,任何命令在读之前先校验 hash,不匹配就拒绝。仓库里只提交 40 个 question ID 和 scenario 分组,以及 Anna 原创的 5 个 synthetic case 全文。所有输出行只含 ID、kept 和 evicted 的 fact ID、分数分量、保留标记、模型预测和判分,不含事实内容、题目和参考答案。

池子重建没有随机数。每题的 9 条干扰项从 bank 里连续取,起点是 sha256("deermem-medium-v1:{case_id}") 前四个字节对 bank 长度取模,取到尾部就绕回开头;支持事实和干扰项的元数据按上面的规则赋值,确认时间和访问时间都相对于固定的评测时钟 2026-08-13T00:00:00Z,半衰期衰减用这个时钟算,隔一年重跑结果也不会漂。

第二条边界是不抄评分实现。评测直接 import 生产代码里的 select_facts_for_capacity(),两种策略都通过它跑;config 里 required_policy_version: hybrid-v1 会和生产常量 EVICTION_POLICY_HYBRID_V1 对比,生产策略版本变了,validate-contracts 直接失败。

grader:把文字规则变成可冻结的判分器

QA accuracy 表里的每一个"对"都要有人判。模型输出是自由文本,参考答案是一句话,不能直接比字符串。Anna 用的是确定性规则,不是人工,也不是再叫一个模型当裁判,零成本,每次跑结果一样。要复现他的数字,我的判分器必须在每一行上和他做出同样的决定。

他披露的规则是一段文字,实现成 deterministic-overlap-v1 后是这样:先归一化,两边全小写、非字母数字字符换成空格、英文数字词 one 到 ten 和 fifteen 换成数字;然后按顺序过六条规则:

  1. 输出为空或等于 INSUFFICIENT,判错;
  2. 归一化后完全相同,判对;
  3. 一边是另一边的连续片段,判对,比如参考答案 Paris、模型答 in Paris;
  4. 参考答案是 “ranging from X to Y” 的区间、模型答的整数都落在区间里,判对;
  5. 两边都有整数但对不上,判错;
  6. 以上都没命中,去掉 the、of 这类功能词后,剩下的词双向都有 60% 以上重合才判对。

有两处光靠文字定不下实现。第 3 条必须按词匹配:参考答案 5、模型答 25,按字符 “5” 是 “25” 的子串会误判成对,按词就不会。第 6 条要先剔掉功能词,Anna 没有给词表,我提交了一份固定的英文功能词表,特意把 yes、no、not 留在外面,YES/NO 题的整个答案就是这一个词,剔掉就没法判了。

冻结的依据是 Anna 公开的 90 行历史记录:每行有模型当时的回答、参考答案和他判的对错,两种策略各 45 行。把这 90 对回答和参考答案喂给新判分器,90 个对错逐条一致,才把版本号写进 config,validate-contracts 校验这个版本号,之后改任何规则都必须换版本号。新的 live run 跑完后没有回头改过规则。

判分函数 grade_answer(prediction, reference) 只收两个字符串,不知道这条回答来自哪个策略,判完再按行 ID 拼回策略和 scenario。判分逻辑没有机会偏向任何一边。

协议等价的三层:聚合、逐行、逐字节

"复现 Anna 的评测"可以停在三个深度:

  1. 聚合指标一致,比如容量 7 下的 27/45 对 45/45;
  2. 每题的池子和保留集逐行一致;
  3. 发给模型的请求逐字节一致。

三层之间不能互相推出。这个协议里,聚合保留率只由每条干扰项的序号决定,第 i 条给什么 confidence、带不带信号,都和它是哪条记录无关;换掉 bank 里的记录,序号不变,保留率一个数都不动。27/45 对 45/45 只能证明元数据规则对了,证明不了池子对了。Anna 逐行比对第一版实现时的结论就是这句:

The matching aggregate alone does not establish row-level reproduction.

逐字节一致最终取决于三个披露里没有写的协议细节。

证据渲染格式,每个 session 的证据前缀是 SESSION {id} AT {date}。它决定渲染后的长度,而 bank 的准入条件是 40 到 700 字符。记录 35a27287 用这个前缀是 697 字符,换成 [session_id={id} date={date}] 是 704,跨过上限被 60d45044 顶替。bank 变了一条,每题的 offset 不变但指向的记录变了,33 个池子、264 条元数据分配、65/90 个容量 7 保留集、全部 90 条 prompt 随之变化,保留率仍是 27/45 对 45/45。

flowchart TD Fmt["session 头格式
SESSION id AT date 写成了另一种前缀"] --> Len["记录 35a27287 的渲染长度
697 字符变成 704 字符"] Len --> Bound["跨过 bank 的 700 字符上限
被 60d45044 顶替"] Bound --> Bank["bank 成员变了,offset 不变"] Bank --> Pools["33/45 个池子变化
264 条元数据分配变化"] Pools --> Kept["65/90 个容量 7 保留集变化"] Pools --> Prompts["90/90 条 prompt 变化"] Pools -. "元数据只看干扰项序号" .-> Agg["支持事实保留 27/45 对 45/45
不变"]

fact ID 命名与排序,支持事实是 gold_{case},干扰项是 d_{case}_{index}_{source},进入选择器前按 ID 排序。生产选择器按分数降序、同分按输入顺序,所以 ID 的字典序就是 tie-break。用数据集原始 ID 时,题 41698283 在容量 7 下保留的是 001be529,历史保留的是 58bf7951,2/90 个保留集不同。

prompt 序列化STORED MEMORY 里 fact 块之间是空行,块的顺序就是排序后的 ID 顺序。这一项不改变任何保留集,只改变请求字节。

三次 live run 对应这三项逐步对齐的协议,每次协议改动后旧运行整体作废,不部分复用:

run 协议状态 官方 40 题 confidence / hybrid-v1 exact McNemar p
1 自定义前缀、原始 ID、单换行 23/40 / 35/40 0.0042
2 历史前缀 23/40 / 33/40 0.0129
3 历史前缀、历史 ID、空行 23/40 / 35/40 0.0018

三次运行的确定性保留率都是 27/45 对 45/45。前两层可以自验,第三层只有持有历史产物的人能验,所以"复现"的结论由 Anna 给出。他在最终版 e481a2e7 上的比对结果:45/45 个池子的 fact ID、内容和元数据一致,90/90 个容量 7 保留集一致,90/90 条请求消息逐字节一致,冻结的 grader 复现 90 个历史判分。

运行身份与完整性校验

设计目标是一份发布的 qa.rows.jsonl 能自己证明每一行是在被判分的那个协议下产生的,不靠作者保证。协议身份由五个文件的 SHA-256 定义:config、官方 manifest、synthetic manifest、answer prompt、数据集。run-qa 在输出目录写 qa_run.json,记下这五个 hash 和 git 状态。每一行回答文件除了预测和用量,还存 row_idcase_idsourcescenariopolicycapacitykept_fact_ids,以及 request_fingerprint,也就是发给模型的完整消息的 SHA-256。

校验分布在两个阶段:

阶段 校验内容 不通过时
run-qa 续跑 五个 hash 与当前协议一致;每条已存在的行用当前协议重算任务,row_idcase_idsourcescenariopolicycapacity、保留集、fingerprint 全部一致 hash 不一致:拒绝在该目录续跑,指出变的是哪个文件;行不一致:重新调用,不复用
grade-qa 发布 只读核对五个 hash;重建全部 90 个任务;逐行核对 fingerprint 和身份字段;参考答案取自重算的 PolicyResult,不取自行内的 case_id 任一行不匹配或缺失:整个判分拒绝执行

回归测试逐个篡改每个身份字段,每一种都必须被拒绝;另一组测试从发布的行重算 McNemar 和 bootstrap,与 qa.stats.json 逐字节一致。

这些检查有三条来自 review。续跑最初只绑 config 一个 hash,已存在的行不核对 fingerprint 就复用,改一条消息后第二次运行报 1 reused, 0 called,于是有了五个 hash 的 marker 和逐行重算。发布路径当时没有同样的校验,一条在旧序列化下产生的行可以直接进入发布结果,这正是前一节三次重跑遇到的漂移类型,此前只靠 Anna 的带外比对才发现。判分最初从行里存的 case_id 取参考答案,把一条合法行的 case_id 改成另一个合法 case,fingerprint、保留集、容量、策略都不动,所有校验通过而判分从对变错,续跑的匹配函数同样没有比对 case_idsourcescenario。三处都在合入前补上,已发布的行没有改动,判分结果不变。

统计:两边同预算

Anna 的历史运行里,confidence 基线用的是 max_tokens=1024,hybrid 候选是 2048 的新调用,基线行被复用。他在披露里主动说了这一点,也建议后续两边重跑。#4810 里两种策略都是新调用,deepseek-v4-flash,temperature 0,max_tokens=2048,三次重试,三个 worker,走 DeepSeek 官方 API,每行记录 API 返回的 response_model。90 次调用一共消耗 86,342 input 和 12,340 output token,大约 0.03 美元。

配对统计用 exact McNemar 加 seed 固定的 paired bootstrap,seed 4789,10000 次,α=0.05,官方 40 例、synthetic 5 例、45 例 overall 三个 suite 分开报,qa.summary.json 里永远不把官方题和 synthetic 折成一个数。离线测试从发布的行重新计算统计量,和 qa.stats.json 逐字节一致。

怎么跑

backend/ 下执行,四条命令对应四层。validate-contracts 不需要数据集,校验提交的 config、manifest 和 prompt 的 hash;validate 校验数据集 hash、重新计算 40 题的筛选规则、构建 bank、准备 45 个 case;run-policy 在容量 5、7、9 下跑确定性选择,输出 270 行元数据,不调任何模型;run-qagrade-qa 才需要 API key。前三条完全离线,不需要 API key 就能复现 27/45 对 45/45 那张表。

1
2
3
4
5
export LONGMEMEVAL_ORACLE_PATH=/absolute/path/to/longmemeval_oracle.json
PYTHONPATH=. uv run python -m scripts.benchmark.deermem_eviction validate \
--dataset "$LONGMEMEVAL_ORACLE_PATH"
PYTHONPATH=. uv run python -m scripts.benchmark.deermem_eviction run-policy \
--dataset "$LONGMEMEVAL_ORACLE_PATH" --output-dir /tmp/deermem-eviction-policy-run

合并前最后一处改动是文档。维护者试跑时发现 README 只写了数据集名和 hash,没写它是公开、无需登录的 Hugging Face 数据集,也没给下载命令,有些网络里 huggingface.co 本身就访问不了。69eecfa5 补上了直接下载的 curl 命令、期望的 hash 和 hf-mirror.com 的替代主机。

工具方面按 PR 里的披露:评测脚手架和协议校验用了 Codex,rebase、grader、runner、统计和 live run 用了 Claude Code;协议、代码和结果我逐一审过并负责。

结果怎么读

发布在 results/pr4789-reproduction-v1/ 的最终运行,容量 7,两种策略同参数:

Suite confidence hybrid-v1 Exact McNemar p 准确率差(95% CI)
40 官方题 23/40 35/40 0.0018 +0.300 [+0.150, +0.450]
5 synthetic corrections 1/5 5/5 0.1250 +0.800 [+0.400, +1.000]
45 overall 24/45 40/45 0.0001 +0.356 [+0.200, +0.511]

按 scenario 拆:confirmation_help 3/10 对 10/10,access_help 3/10 对 9/10,confidence_control 8/10 对 7/10,noisy_signal_control 9/10 对 9/10,synthetic 1/5 对 5/5。两个 help scenario 的差距就是保留率的差距,支持事实不在池子里,模型按 prompt 的要求输出 INSUFFICIENT

confidence_control 是 hybrid 唯一低于基线的格子,要单独看。40 道官方题里 hybrid 只输了一道,1cea1afa,它的支持事实在两种策略下都被保留了,confidence 策略下模型答对,hybrid 下模型输出了 INSUFFICIENT。问题不在淘汰。两种策略保留的干扰项集合不同,同一支持事实旁边放着不同的干扰项,模型的措辞和弃答倾向会变。grader 是冻结的,这个格子按原样报告。

两边都比历史数字高不少,历史是 14/45 对 23/45。confidence 基线从 14 涨到 24,和输出预算从 1024 翻到 2048 是一致的,披露里记录过一次 hidden reasoning 耗尽预算后重试到 2048 的情况。hybrid 的历史行本来就是 2048,从 23 涨到 40 不能用预算解释;这次走的是 DeepSeek 官方 API,历史记录里是聚合命名空间 deepseek/deepseek-v4-flash,底层模型相同但服务路径不同,我没有证据把差异归到某一个原因上。两种策略之间的差距在三次运行里都在,官方 40 题上 hybrid 分别多答对 12、10、12 道。

这些数字不构成把 hybrid-v1 改成默认的论据。评测用的元数据是构造的,confidence_control 的那一行说明信号权重和干扰项集合会影响模型的弃答行为,真实部署里 confirmation 信号还依赖前面说的 batch-level 门。#4789 自己也把 confidence 留作默认,切默认是另一个需要社区讨论的问题。

回到那两条 fact

开头那条 0.7、七天前确认过的 fact,在 hybrid-v1 下总分约 0.69,赢过 0.62 的 0.9@180 天。那条 0.7 的 correction,在 100 条的库里有 10 个保留槽,十条 0.9 的 preference 挤不掉它。这两件事在 issue 里是文字推演,现在是生产代码里的行为,也是评测里 confirmation_help 的 10 行和 correction_reserve 的 5 行。

评测这边只有一条结论:聚合指标对协议漂移不敏感,逐字节一致才算复现。仓库里现在的五个 hash、每行的 request fingerprint 和 grade-qa 的发布前自证,都是为这一条服务的。

参考

输入关键词开始搜索

Image 01