Skip to content

Latest commit

 

History

History
1140 lines (921 loc) · 82.6 KB

File metadata and controls

1140 lines (921 loc) · 82.6 KB

类型消解的测量台

每一组一个新库。 这条规则是整个目录存在的理由。

此前连着三轮在同一个库上调检索,而那个库带着前几轮的改类结果——容易的实体早已 精化,拒绝理由里直接写着 already correctly typed as pharmacy。后两轮的数字跟 第一轮根本不可比,却被当成依据改了两次代码。复用一个库省下的几分钟,换来的是 一整段无效的结论。

跑一组

node scripts/bench/run.mjs --corpus pharma --label seeds-only
node scripts/bench/run.mjs --corpus pharma --ontology /tmp/schemaorg.ttl --label schemaorg

前置:utopia-server 已启动、连着一个能写的库、工作区配好了对话与嵌入模型。 环境变量见 run.mjs 头部。

目录

  • corpora/*.json —— 固定语料。实体跨文档反复出现是刻意的:单篇语料里每个 实体只有一两条事实,画像基本只剩名字,量不出消解的真实水平。
  • truth/*.json —— 每个实体期望落到哪个类。key 是名字的一个足以认出它的片段 (抽取给的名字每次略有出入,全等匹配会把这种变化算成失败);值是可接受的类, 任一命中算对。空数组 = 本体里没有对得上的类,此时正确行为是不动它。
  • truth/*.wikidata.json —— 不经人手的答案卷(0016 的 C1):维基百科语料里每个 实体在 Wikidata 上的 P31(instance of),经 p31-to-classes.json 映射到 schema.org 包的类。*.wikidata.raw.json 是抓下来的原始材料(QID、P31、类落在哪些锚下), 可重抓、可 diff;答案卷由它生成,不要手改——改映射或重抓。
  • p31-to-classes.json —— 人的判断只进这一张表:Wikidata 的锚类(人、企业、软件、 城市……)→ 本体里可接受的类。空数组沿用上面的语义(正确行为是不动它); 一个锚都落不到的实体不写进答案卷,不知道就不猜。
  • run.mjs —— 一组:新建库 → 灌语料 → 可选导入本体 → 跑消解 → 打分。 --truth <file> 换一份答案卷:同一组结果对手填答案与 Wikidata 答案各打一次分。
  • fetch-wikidata-truth.mjs —— 抓 Wikidata 答案的原始材料:条目主题与正文里出现过的 链接条目(导航框里的几百个不算)的 QID 与 P31,P31 类顺着 P279 归到锚类。
  • make-truth.mjs —— 原始材料 + 映射表 → truth/<corpus>.wikidata.json。
  • fetch-ai-timeline.mjs —— 抓条目的当前版(prop=extracts)。
  • fetch-wiki-history.mjs —— 抓历史快照(action=parse&oldid)。演认知时间靠它: 同一条目的多张快照按 doc_time 灌进去,图会真的改主意。
  • subset-corpus.mjs —— 从一份语料里挑几个条目做成新语料。整条目取, 因为 supersedes 只在同一条目的相邻快照之间发生,随机抽块会把时态那根轴废掉。
  • subset.mjs —— 把 schema.org 的 TTL 切成前 N 个类,给退化曲线用。
  • recall.mjs / fetch-sec-filings.mjs / truth/nvda-public-docs.json —— 召回的台子: 文档里该有的东西进图了没有(见下面「召回的测量台」)。

召回的测量台(2026-09-09)

recall.mjs 量的是另一件事:这篇文档里该有的东西,进图了没有。 run.mjs 问「实体归到哪个类」,temporal.mjs 问「两根轴答得对不对」, 这一个问「金额、容量、期限、票数、职务丢了多少」。

判据是人读原文列出来的一张表(truth/nvda-public-docs.json,52 条), 不是 extraction_drops / ontology_misses 那两张信号表——它们只看得见 「抽出来了没落地」,看不见「根本没抽」,而后者是大头:一份 8-K 的收购价、 留任池、交割时点全都不在图里的时候,那两张表是空的。

语料是四份真实 SEC 文件(fetch-sec-filings.mjs 抓,不入库存),各压一类形态: 收购公告(一句话三个数)、多方新闻稿(七方、四位高管带职务)、财报新闻稿 (行列关系全在表格里)、投票结果(二十一张无表头的表,十位董事各四类票数)。

node scripts/bench/fetch-sec-filings.mjs                     # 一次就够
node scripts/bench/recall.mjs --kb <id>                      # 一轮:清空 → 重抽 → 打分
node scripts/bench/recall.mjs --kb <id> --score              # 只打分
node scripts/bench/recall.mjs --kb <id> --reprocess          # 改了解析器:连分块一起重来
node scripts/bench/judge_open.mjs --kb <id> --sample 200     # 开放陈述有多少不是原文说的(门槛 2%)

量「后面的分块看不到前面的分块认下了什么」对输出的影响(#588,分块并发调模型之前要知道的数): 服务端带 UTOPIA_EXTRACT_KNOWN_IN_PROMPT=false 起,照常跑 recall.mjs,与不带时的几轮比。 这个开关只动提示词,落库时按名字认回前面分块的实体照旧。

# 服务端照常起:每轮记一份
node scripts/bench/recall.mjs --kb <id> --known shown --out runs/588
# 服务端带 UTOPIA_EXTRACT_KNOWN_IN_PROMPT=false 重起:同样几轮
node scripts/bench/recall.mjs --kb <id> --known empty --out runs/588
# 按 known 分组排表
node scripts/bench/recall.mjs --table runs/588

--known 只是标签,台子查不到服务端的开关,两边对不上那一轮就作废。每轮除了总分还记每篇的 分块、事实、实体、跨块认回(一个实体出现在这篇两个以上分块的事实里)、排给裁决的对(这一轮进了 resolution_reviews 的疑似同一对,#877;提示词里没有 known 时同一个东西换个写法再列一遍,落在这里而不是静默合并。 不分召回通道:一对裁过之后 reason 被裁决改写,name_vector| 留不下来。全库总数另按待裁 / 合并 / 分开和现在的 reason 前缀记;只算这一轮排队之后排的对,--score 没有排队时刻,算的是库里现有的——每轮开头删实体时它们随之删掉)、丢弃账(extraction_drops 按原因)和排队到最后一块抽完的秒数。抽完之后还要等对齐、治理、时间消解、裁决这些任务收尾(它们还在补事实、裁对子), 等到没有该跑的任务才打分,不然每轮记的是抽完那一刻的快照;等了多久记成 settleSeconds,卡住十五分钟没收尾的轮次 --table 会单独点名。没抽完的轮次(端点整段不可用时抽取任务一个个失败、队列就空了,收尾等待立刻满足)记下来但 complete: false,--table 不把它算进任何一组,只在表下点名——它的分说的是端点那天的状态,不是这一轮的配置。 --table 给每组的均值 ± 标准差,再给事实集合(去重的主/谓/宾) 的重合度:组内两两比是运行间的抖动,跨组两两比是 known 的影响——跨组落在组内的范围里,空 known 就没把 输出挪出方差。

抽取写的是开放图谱(0044 决定 2,没有带本体的第二条路):陈述按原文短语落成 layer='open' 的 事实行,限定挂在 statement_qualifiers,时间词进 time_mentions;打分口径照旧(谓词取 proposed_predicate,限定也拼进那一行),所以下表里带本体那些轮次的分数仍然可比。judge_open.mjs 是 反面的尺子:抽样让一个裁判模型读原文判 stated / misworded / not_stated;裁判端点用 BENCH_JUDGE_BASE / BENCH_JUDGE_KEY / BENCH_JUDGE_MODEL,不给就用工作区的对话模型 (和抽取同一个模型,数字要打折看)。

--kb 要一个装了本体包、向量已补齐的库:装一次 schema.org 要嵌 2500 条向量、 二十分钟,而这个台子量的不是本体。库里除了本体没有跨轮状态——事实、实体、信号 每轮都清空,自动扩本体上一轮长出来的关系也删掉(留着就进下一轮的提示词)。

52 个条目、九成命中率,一个标准差 ≈ 2.7 条。 单轮差一两条不是证据; 同一份代码重跑一遍再说。结构性的改善要看别的:object_missing 的计数、 某一类(比如「四位高管的职务」)是不是整类进来了。

2026-09-09 那一轮(dev @ cb2bab7 + 九处修改,DeepSeek-V3.2 + bge-m3):

轮次 改了什么 分数
基线 — 33/52
r1 字面值通道放行 29/52 ← 回归
r2 +表格保住行列 +关系地板 +主语兜底 44/52
r3 +角色规则 +空列裁剪 43/52
r4 +空表头让位 44/52
r5 +名单是名单 49/52
r6 代码一字不改,重跑 49/52

r5 与 r6 漏的不是同一批(交集为空),剩下的是抽样噪声。 r1 那次回归最值钱:放开字面值之后,主语解析比关系通道严,一轮丢掉 113 条—— 这个 bug 只有在放开字面值之后才暴露得出来。

治理的测量台(0025)

govern.mjs 量的是 agent 替人裁重复项裁得对不对、还剩多少等人。真值在 truth/ai-timeline.duplicates.json:把八篇互相咬合的条目(OpenAI 那场风波、Anthropic、 DeepMind、Mistral、Inflection、SSI、ChatGPT)灌进一个治理开着的新库,消解器提出的 615 对、411 个名字对,2026-09-07 按两侧的事实与原文逐对手标:same 同一个东西; different 两个东西,含版本、部门、子公司、只是含着这个名字的一句话;unknown 一侧 没有事实、名字本身定不了的(光秃秃的「judge」「Time」),人也定不了。按两个名字键 (不分左右、不分大小写);同名的一对只有在那次导入里每一对都同一个答案时才带标签。

node scripts/bench/govern.mjs --fresh                 # 新库、灌语料、跑完打分(约一小时)
node scripts/bench/govern.mjs --kb <id>               # 已有的库:撤掉 agent 的决定、再跑、打分(约二十分钟)
node scripts/bench/govern.mjs --kb <id> --score       # 只打分
node scripts/bench/govern.mjs --kb <id> --score --stuck   # 连留给人的那些一起列出来

--kb 那条路不重新抽取:--reset 用 revert_merge 同一套还原把 agent(和模拟的人)做的 合并撤掉、分开的重开、建议删掉,直接在库里做而不经 API——经 API 会在台账上留下 merge.revert,下一轮 agent 会把它当成人的先例。所以改一次判断、二十分钟出一次数。

读数:decided_on_its_own 是它自己裁的;agreed_with_labels 是其中与标注一致的; wrong_merges 是最要紧的那个数——合错了要人去撤;left_for_people 是留给人的建议, 后面跟着按标注人会合几对、分几对;unlabeled 是这次导入抽出来、真值里没有的名字对, 不算对也不算错。语料重抽一次名字会略有出入,那一栏不为零是正常的。

同一份语料上的对照(2026-09-06,闸门还是「没先例不合」那版):治理关着老裁决器自动合 146、留 50 给人;治理开着一对不合、留 200。这个数就是记录 0025 决定 4 修订的起因。

映射的测量台(#501)

mappings.mjs 量的是探索从数据库 schema 提议的口径,对不对、漏了多少。

node scripts/bench/mappings.mjs --fresh                # 新库 → 挂源 → 探索 → 打分
node scripts/bench/mappings.mjs --fresh --no-comments  # 同上,语料不带列注释
node scripts/bench/mappings.mjs --kb <id> --score      # 只打分,不动库

打分不看名字,看数。 治理那边真值按两个名字键,因为它判的是二分类;这里概念名 是模型自己起的,「Revenue」与「已支付 GMV」按名字对不上任何一条。所以真值一条是 「一个业务口径 + 一条 gold SQL」,打分把提议真跑一遍,跟 gold 的结果比数——数一样 就是同一个口径。省掉逐条手标,判据还是客观的。

四栏分开读:covered 是真值里被算出来的有几条(漏没漏);right 是提议里跑得通 且对上某条真值的;wrong 是跑得通但一条都对不上的,附它算出的数与最接近的真值; broken 是跑不通的。要看的是 wrong。 跑不通的提议无害,它失败得很响,人一眼 看得见;跑得通而算错的才是全部风险——问数会拿它印出一个看起来完全正常的数字。这一栏 也是 #504 能不能默认开的唯一依据。

traps_hit 数的是落到陷阱列上的提议:整数外键求和、o_shippriority 这种恒为 0 的列、 把一段自由文本当维度。它们都跑得通。

语料在 schemas/<corpus>.sql,真值在 truth/<corpus>.mappings.json。

  • 口径不是我们定的。 TPC-H 的 22 条查询里写死了「收入」在这个 schema 上就是 sum(l_extendedprice * (1 - l_discount)),真值从那儿抄。自己拟一份口径来量自己的 探索,量出来的是自我一致。
  • 行是 generate_series 造的,不按 TPC 的生成规范。 探索只读 schema 不读数据, 打分时 gold 与提议跑在同一批行上,比的是两个数一不一样——那份规范值钱的是 schema 与查询,不是它的数据分布。setseed 固定,两轮之间语料不变。
  • 列注释单独一个文件,因为它是一个自变量。 真 TPC-H 一条注释都没有,而真实库里 注释是探索最主要的线索(提示词专门要求 citing column comments)。加载与不加载各跑 一轮,两个分数之差就是注释值多少分。注释只描述列,不描述口径——写「折后收入 = extendedprice × (1 - discount)」等于把 gold SQL 抄给模型。
  • 每一组一个新库在这里还多一层理由:propose 的 ON CONFLICT … WHERE status = 'proposed' 让第二轮探索继承第一轮的行,同一个库上跑两次,第二次的分不是第二次的。

问数的测量台(#520)

ask.mjs 量的是端到端:人问一句话,拿回来的数对不对。

node scripts/bench/ask.mjs --kb <id>                  # 跑全部问题
node scripts/bench/ask.mjs --kb <id> --only disc_revenue
node scripts/bench/ask.mjs --kb <id> --seed           # 先把真值写成确认口径(上界)
node scripts/bench/ask.mjs --kb <id> --confirm        # 先确认探索提的那些(产品路径)
node scripts/bench/ask.mjs --kb <id> --replay         # 不重问,拿库里上一轮的回答重判
node scripts/bench/ask.mjs --kb <id> --parallel 4     # 同时问四题:一题 2–6 个模型回合、串行半小时
node scripts/bench/ask.mjs --kb <id> --conventions    # 先把真值里的 conventions 写进库(#570)
node scripts/bench/ask.mjs --kb <id> --recall 8       # 只量检索:那条对的口径在不在前 8(#574),几秒

口径是按问题挑的,不是全塞(#574):--recall K 不问,只看检索前 K 条里有没有那条对的 口径——答案卷就是 mapped 那一步按数对上的行。要撑爆老的 30 上限,用 mappings.mjs --fresh --corpus wide --also tpch 把两个源挂进同一个库,再各 seed 一份真值 (45 条)。量过:seed 的说明是占位符时 wide recall@8 13/18、问数 15/18;说明换成真的 (真值里的 summary,用提问的语言)之后 recall 18/18、问数 18/18;tpch 两次都是 23/24 与 24/24。嵌进去的那句说明就是检索的全部,reranker 目前没有它该修的漏。

mappings.mjs --fresh --conventions 同理,只是写在探索之前——探索的提示词也读它。wide 上 量过的阶梯(chat right):什么都没有 2/18;探索生成的描述 2/18;描述 + 六条约定 11/18; 约定 + 十八条口径写成一页散文靠检索 14/18;二十七条确认口径进 prompt 17/18。探索覆盖: 没有约定 0/18,有约定 3/18——错的那几条也都带上了 is_test != 1,差在选错列。

与 mappings.mjs 量的不是一回事,一个也推不出另一个。 口径确认得再准, 答案照样可能错——模型会挑错源、join 错、按错的日期列过滤,或者压根不看语义层, 照着 schema 文档自己写 SQL。反过来,一个一条确认映射都没有的库照样答得出问题, 而且有时是对的。

判分材料只有两样,因为 query_data 记得很少(step 里只有源名与用途):助手最后 那段话,以及 tool_exchange 里模型真正跑过的 SQL。后者是有用的那个—— 把它跑过的 SQL 重跑一遍,跟 gold 比数。SQL 很短,逃得过 tool_exchange 的截断, 而它的输出逃不过。

于是两栏而不是一栏:sql_right 是它跑的那条算的就是问题问的数,answer_right 是它印出来的数就是那个数。两者会分家——查对了却把单位说错、四舍五入错、 或者转述成另一个数字,在只看 SQL 的分里是对的,而人读到的答案是错的。判等用 lib.mjs 的 roughly(比 same 松两个量级):模型说「约 8.63 亿」与 862793473.48 是同一个答案,判成错的话,量的就不是问数准不准,是模型肯不肯把 小数点后八位抄全。

问题在 truth/tpch.questions.json,按口径的 id 键,与 tpch.mappings.json 共用 gold SQL。这不只是省事:一道题答错时,脚本据此说得出它需要的那条口径有没有 确认映射——mapped / UNMAPPED。闭环就在这一句上:

  1. 跑一轮,看哪些错;
  2. 错的题里,口径没配映射的 → 覆盖率的问题,去探索或手写;
  3. 配了还错的 → 提示词、工具或口径本身的问题;
  4. 再跑。

mapped_definitions 这一栏满了,「映射全」就有了确定的意思,剩下的错都不再是 覆盖率的事。

lib.mjs 是两个测量台共用的地基——判等必须是同一份,各写一份 same() 迟早漂移,而一旦漂移,「提议对了几条」与「答案对了几条」就不是同一把尺子量出来的。

SQL 的空字段(psql 默认显示的 NULL、空字符串)与纯空白不参与数值判等,不能当成 0。 firstRow() 仍取第一行各列的数;value() 只读第一列,第一列为空就返回空,不取后面的数。 回归测试不需要模型服务;第二条额外验证真实 PostgreSQL 的输出,只执行 SELECT:

node --test scripts/bench/lib.test.mjs
BENCH_TEST_PSQL="psql -X -h 127.0.0.1 -p 1517 -U utopia -tAc" BENCH_TEST_DB=utopia PGPASSWORD=utopia \
  node --test scripts/bench/lib.pg.test.mjs

本体的两个数(0061 决定 5)

competency.mjs 把一个库接受了的能力问题按人在 chat 里问的方式问一遍(走 /kbs/{id}/chat, 不另造引擎),判答没答上,把结果写回问题(last_result),再读服务端算的两个数:

BENCH_BASE=http://127.0.0.1:1524 BENCH_PSQL="docker exec ... -d utopia_bench4 -tAc" \
BENCH_JUDGE_BASE=... BENCH_JUDGE_KEY=... BENCH_JUDGE_MODEL=... \
node scripts/bench/competency.mjs --kb <kb-id> --seed scripts/bench/truth/redocred-typed.questions.json
  • 有期望答案的问题交给裁判模型判"回答里说了期望答案没有";没有的只查它需要的形状 (needs 里的类与属性)是否都在、属性是否有类型化事实。后者弱得多——一条开放图谱就能答的问题 不需要本体,这是 0061 的开放问题。
  • 回答经没经过图谱也记下来(via:graph / text / both / none,看 chat 的 step 事件里 facts、neighbors、 timeline、path 这些图谱工具有没有拿到事实,search、document 有没有读正文)。报告里"答上"之外另给 "经过图谱"和"只靠图谱"两个数:只靠正文答上的不需要本体,这个数才是本体自己的。
  • 第二个数从 ontology_proposals 算:代理提的里人表过态的,有几条被拒或改过再采纳。
  • --seed 把写好的问题灌进库(同一句跳过),--only-report 只读数不问。
  • truth/redocred-typed.questions.json 是给 typed.mjs 那份 100 篇 Re-DocRED 库写的十一条:十条有期望答案 (其中两条要代理采纳过的 bordered_by、p40 才答得上),一条只有形状。换一个库要另写。

本体代理的测量台(0061)

agent.mjs 在同一个库状态、同一批形状上各臂跑一轮:每臂开跑前把代理没采纳的提案和看过的形状记录删掉 (所以只在测量库上用,要显式给 --reset),从服务端日志读失败批次、坏项及原因、token,再让裁判拿着 全词表判每条新提案是不是已有元素的重复或反向,判每条「已有」答案对不对、方向对不对。 POST /ontology/propose 的 glossary: full 是给全词表那一臂用的旋钮,产品路径不开。

BENCH_BASE=http://127.0.0.1:1524 BENCH_PSQL="docker exec ... -d utopia_bench4 -tAc" \
BENCH_SERVER_LOG=/path/to/server.log BENCH_JUDGE_BASE=... BENCH_JUDGE_KEY=... BENCH_JUDGE_MODEL=... \
node scripts/bench/agent.mjs --kb <kb-id> --db utopia_bench4 --reset --arms trimmed,full,trimmed,full

2026-09-27,bench4(100 篇 Re-DocRED,98 条属性),gemini-3.5-flash,每轮 120 条形状 + 69 个类别词、12 次调用:

全词表 按结构裁(#963) 按结构裁 + 意思最近 5 条
轮数 4 7 4
失败批次 0 / 48 0 / 84 0 / 48
提示 token / 次 7.1k 4.3k 5.0k
一轮 token 111k 78k 87k
新元素提案 135 292 178
其中重复或反向已有属性 1(0.7%) 18(6.2%) 6(3.4%)
「已有」答案判对 44 / 65(68%) 60 / 85(71%) 44 / 56(79%)
  • 裁词表不让调用失败,也不让模型提已存在的键;让它重提定义被裁掉的属性。按结构裁时重的集中在 positionHeld、workLocation、performer、awardReceived:形状一端没类型或宾语是字面值,结构上对不上, 定义就没送。再按意思带上最近的 5 条,重复降了一半,多花 0.7k token 一次。
  • 每轮固定出现的十来个坏项是"existing 条目没有形状",两臂都有,数目等于库里的问题数:模型把"这条属性 服务某个问题"写进了 existing。解析时丢掉,无害;提示词里已补一句。
  • 同一臂两轮之间的差别比两臂之间大:同样的输入、温度 0,两轮只有一半提案键相同。单轮对比说明不了 质量,至少四轮合起来看。
  • 提案旁边显示的"最近的已有元素"(按向量取两条)只罩住了被判重复的 16 条里的 6 条,是给人看的线索, 不是防线。

时间词的测量台(0064)

timewords.mjs 把 truth/timewords.json 里七篇写好的文档(五篇中文、两篇英文,39 句)各传进一个新库, 抽取、解析之后逐句对:这句话抽出来没有、它的时间说法记成时间词没有、起点落在期望的区间里没有、 没写时间的句子有没有被硬给一个起点、「截至」对不对,以及几遍之间每句话的结果一不一样。每一遍建一个 新库,不删任何东西。

BENCH_BASE=http://127.0.0.1:1524 BENCH_PSQL="docker exec ... -d utopia_bench4 -tAc" \
BENCH_SERVER_LOG=/path/to/server.log node scripts/bench/timewords.mjs --db utopia_bench4 --runs 3

句子按种类分:absolute(写明的日期)、relative_now(相对文本说话的那一刻:三个月后、上周、today)、 relative_event(相对一件有日期的事:上市半年后)、relative_undated_event(相对一件没日期的事,期望 不写起点)、section(时间在节标题上)、none(没写时间,期望没有起点,「截至」来自上下文)。 语料里有三篇是为了跨块写的:发出比开会晚一个月的会议纪要(起算点选错看得出来)、两份周报汇编成的 一篇(每一节有自己的提报日期)、季度写在节标题上而且离开头很远的年度回顾。

2026-09-28,gemini-3.5-flash,各三遍(三遍的数用 / 隔开):

dev(第 2 刀之前) 0064 第 2 刀 0064 第 3 刀
句子抽到了(39) 36 / 36 / 39 37 / 38 / 37 39 / 36 / 34
时间说法记成了时间词(29) 18 / 19 / 20 28 / 29 / 27 29 / 27 / 25
起点对:写明的(4) 4 / 4 / 4 3 / 4 / 4 4 / 3 / 3
起点对:相对此刻(15) 9 / 9 / 9 14 / 15 / 13 14 / 12 / 11
起点对:相对有日期的事(3) 3 / 3 / 3 3 / 3 / 3 3 / 3 / 3
起点对:节标题上的时间(6) 0 / 0 / 0 6 / 6 / 6 6 / 6 / 6
没写时间的没被给起点 10/10 ×3 9/9、9/9、10/10 10/10、9/9、9/9
「截至」对(10) 0 / 0 / 0 0 / 0 / 0 10 / 9 / 9
每块 token 6,490 / 5,744 / 7,306 6,142 / 7,204 / 8,470 6,963 / 6,621 / 7,682
三遍结果一致的句子 34/39 34/39 33/39
  • 之前丢的四种:计划类的句子时间词进了宾语(「码表二代 — 发布 → 将在三个月后」);「上周」记下了但 解释被当成坏条目丢掉(模型照字报粒度 week,阶梯上没有这一档);汇编里第二份周报的「上周」从第一份的 提报日期起算;节标题上的季度到不了它下面的句子。第 2 刀之后剩下的错几乎都是那句话整句没抽到 (?),是抽取本身的波动,不是时间。
  • 「截至」一栏第 3 刀才动:之前没写时间的陈述拿到的是处理文档的那一刻;第 3 刀之后是它所在那一节里 文本说话的那一刻(会议纪要是开会那天,不是发出那天),没对上的那一条是那句话整句没抽到。
  • gemini 的渠道当天下午下线,换 claude-haiku-4-5 把 dev 与第 2、3 刀各重跑三遍(提示词已压短): 时间说法记成时间词 12–13/29 对 23–25;起点对,相对此刻 6/15 对 11–13,节标题上 0/6 对 4–6; 「截至」0/10 对 9–10;每块 token 7.2k–7.4k 对 7.6k–8.8k。模型换了,前后的差距没变。
  • 第 3 刀没动抽取,三遍之间「句子抽到了」从 39 掉到 34 是抽取本身的波动:同一份代码、同一份文档。 这张表上凡是跟着「抽到了」一起动的数(时间词、起点)都要这样读。
  • 每块 token 多了一成上下:提示词多了两段规则、回复多了 t,少了事后读开头的那一次调用。

读数怎么算

  • prompt_tokens_est 是本体段的估算,不是整个提示词。实测 4.0 字符 ≈ 1 token (377,735↔81,855、396,716↔99,041)。真实 token 数在 LLM 客户端里,穿出来要改 一路签名;这里要量的是"本体规模",比例稳定就够用。
  • for_review 按没改算进 miss。它确实还没改——算成命中就是把人的活记在机器账上。
  • absent = 标准答案里有、但抽取压根没抽出这个实体。它不是消解的错,单独一栏。
  • 命中 = 可接受类或它的子类(按库里的类层级算)。答案卷给的是锚(organization、 place),引擎答的常常更具体(research_organization、city)——精化正是要它做的事。
  • 手填的答案卷按子串匹配名字,生成的(match: "exact")按全名精确匹配。
  • score.tiers:分档打分。auto 是自动落地那一档对答案卷的命中,review 是待人工那一档 若盲目照收会怎样——放不放开自动跑看的是前者。

标准答案会写错

第一次跑就写窄了一个:心血管健康论坛 只写了 business_event|event_series, 而系统给的 conference_event 是对的。答案错了要改答案——但要在结果出来之后 才改、且写清楚为什么,否则这份答案就变成了"系统这次答了什么"的记录,量不出任何东西。

加一个语料

两个文件:corpora/x.json 与 truth/x.json。语料换行业是有意的——同一套判断在 两个领域上都成立,才谈得上不是过拟合到某一批词上。

GLiNER 对照(2026-09-04)

有人在 discussion #298 问"能不能用 GLiNER 代替 LLM 做实体抽取"。量了一次,四套语料、 四种设置,结论和数字都在这里,下次再问直接给链接。

条件

  • 我们这边:schema-org 包(965 类),每套语料一个新库,DeepSeek-V3.2 抽取。"抽取时"是 抽取一轮的类型,"跑完类型消解"是再跑一次 POST /kbs/{id}/ontology/type-resolution。
  • GLiNER:urchade/gliner_multi-v2.1,CPU,阈值 0.3,gliner_compare.py。中文按字切开再喂 (它按空白分词,不切的话整句是一个 token)。
  • 四种标签设置:
    1. 只给答案里的类(二十来个)。这是它的上限,相当于提前知道答案的类空间。
    2. 965 类分 39 批,同一片段取各批里的最高分。这是同等难度。
    3. 965 类一次塞进去。看它面对整个本体时到底会怎样。
  • 语料:pharma、tech 是原有的中文语料;pharma-en、tech-en 是逐句对译的英文版, 答案键对应改成英文名。四份答案键的类完全一致。
  • 读数:找到 = 答案里的实体有多少被抽出(片段包含匹配);类型正确 的分母是找到且 答案里有类的实体,各行自己算——所以一行 11/11 只说明"找到的都判对了",不说明找得多。 两列要一起看。

中文

pharma 找到 pharma 类型正确 tech 找到 tech 类型正确
我们,抽取时 22/22 11/20 15/16 3/13
我们,跑完类型消解 22/22 16/20 15/16 10/13
GLiNER,只给答案里的类 18/22 16/17 12/16 10/10
GLiNER,965 类分批 20/22 9/19 13/16 5/11
GLiNER,965 类一次塞 0/22 – 0/16 –

英文

pharma 找到 pharma 类型正确 tech 找到 tech 类型正确
我们,抽取时 21/22 17/20 15/16 9/13
我们,跑完类型消解 21/22 19/20 15/16 12/13
GLiNER,只给答案里的类 22/22 17/20 13/16 11/11
GLiNER,965 类分批 22/22 13/20 16/16 4/14
GLiNER,965 类一次塞 4/22 2/3 2/16 –

耗时:GLiNER 给二十个标签时每套语料一秒左右;分 39 批约 40 秒;一次塞 965 个约 70 秒。 我们每套 40 到 100 秒,取决于模型端排队。

读法

  1. 候选类少的时候,它判得又准又快。 二十个标签下类型精度和我们抽取时打平甚至略高, 而成本低两个数量级。
  2. 面对整个本体,类型精度掉到一半以下。 它认得出"这是个组织",分不清 government_organization 和 medical_organization;amlodipine 给了 product 和 substance,drug 没赢;DeepBlue 3.0 判成 drug。它靠标签名的字面相似,没有类描述,也没有上下文推理,类一多近邻就分不开。输出里 同时混进日期、碎片和"踝部→brain_structure"这类噪声,条数翻倍。
  3. 一次塞进全部标签,它就瞎了。 编码器窗口几百个 token,标签排在正文前,965 个标签把 窗口填满,正文被截掉,模型没看到文章。它不报错,安静地给一个几乎空的结果——这比慢 更危险。所以"直接给它整个本体"这条路在技术上不存在。
  4. 中文长名字它结构性地找不到。 按字切开后最大片段宽度 12 token 就是 12 个字, 浙江大学医学院附属第一医院这类名字切不出来。英文没有这个问题,两套英文语料在分批设置 下一个不漏,召回确实好。
  5. 我们在英文上明显强于中文(pharma 抽取时 11/20 对 17/20)。抽取提示词里的类描述是 英文,对英文原文更贴。这个差距本身值得单独记一笔。
  6. 以上只是实体和类型。关系、有效期、同名区分,GLiNER 不做。

结论

不是替代,是一条候选预扫的线:把分块检索出的几十个候选类交给它打一遍,再把(片段,类型) 作为提示喂给抽取,有机会把抽取时的类型精度抬到消解后的水平、省掉一轮消解调用;它找到而 我们没找到的,直接进 drop signal 做漏抽信号。这个实验还没做。


时态问答的测量台(#306)

node scripts/bench/temporal.mjs --label rc5

上面那个台子量的是类型消解。这一个量的是这个产品真正围绕着造的那件事: 按某个日期回答问题,且在事实变过之后仍答对。

在它之前,仓库能引用的数字只有 Ontology2SQL 在 BIRD Mini-Dev 上的成绩。别人家的 记忆基准(LongMemEval、DMR)测的是对话回忆,没有一个会问「2023 年三月这个项目是谁 在管」,而语料里那个答案还变过两次。在别人的基准上拿个中游名次,说明的东西还不如 把那个缺失的基准公布出来。

两根轴各问一遍

这是这份题目里别处没有的一维:

轴 参数 问的是
world at 那时世界是什么样
record as_of 那时我们以为世界是什么样(0019)

同一个实体、同一个日期,两根轴的正确答案可以不同——aurora-lead-record-wave1-at-2024-08 就是这么一道:世界轴问 2024 年八月(那时李四已接手),记录轴停在我们还没读到交接 备忘的那一刻(那时只知道张三)。答成李四,就是拿今天的认知去答当时。

记录轴不是改时钟改出来的

语料分两波灌,第一波灌完记下一个时刻(wave-1),第二波才带来交接、薪资更正与 一次文档删除。所有 as_of: "wave-1" 的题目问的就是那一刻。这样量到的是产品真实 走过的路——一次真的摄入、一次真的抽取、一次真的删除——而不是 UPDATE 出来的场面。

判分

  • 判据是「这个实体在那一刻挂着的东西里,有没有 expect,有没有 not 里的任何一个」, 不认谓词的名字:本体在不同库里长得不一样(works_for / employed_by),而这个台子 量的是时间,不是词汇。
  • expect: null 表示那时不该有答案(项目还没开始、人还没入职、事实已被撤回)。 把开区间读成「自古如此」是双时态最容易犯的错,所以专门留了三道。
  • absent(抽取压根没抽出这个实体)单独一档,不算时态答错——与上面那个台子同理。
  • --chat 再跑一遍对话:模型带着工具自己去问。账本那一路量的是账本,对话那一路 量的是 agent 那一层,两者的差就是工具用得好不好。对话判分只做一件归一:千位分隔符 (模型写 28,000,题上写 28000)。ask 里的 {wave-N} 跑分时换成那一波灌完的时间戳。
  • 对话的回答看题目问的那个名字:expect 得出现,而且 not 里的哪一个都不在它 前面。对的回答常顺带说出前任、后任或后来改成的值——「李四管到 2025-09-01,那天 周七接手」——这不算答错(#986 的评审)。子串判分分不出的还有两样:先说对了名字、 后面又说错了别的,照样记对;expect: null 的题没有先后可比,not 里的名字一出现 就记错,否定句也一样——自然语言判分的已知弱点,下面单独有数。这条是 2026-09-28 改的,下面「对话那一路」的数是之前按「not 里的名字一出现就记错」判的。

已知的口径缺口

属性事实(薪资、职位)不是边,得从实体面板取,而那个接口今天只有 as_of,没有 at ——世界轴那一半由脚本自己按 valid_from / valid_to 过滤。图谱两个接口两根轴都在 服务端。这个不对称照实记在这里,不藏进分数里。

题目覆盖什么

37 道题(两道是已知缺口,不进主分),一个虚构公司,三波文档。每一波灌完(含这一波的删除、更正、合并)记一个时刻 wave-N,记录轴上的题目以它们为界。

场景 它量的是什么
三任接力 A→B→C 唯一性闭合,在同一条链上发生两次
薪资更正 28000 → 32000 属性事实的闭合,世界轴与记录轴各答各的
入职早于说出它的文档 世界轴靠文档写明的起点,不靠它到达的时间
删除一篇文档 撤回:记录轴倒回去看得见,今天看不见(#268 的墓碑)
人改一条事实的起点(#311) 改之后世界轴翻转;记录轴倒回改之前看得见旧区间
人合并两个拼法 "L. Si" → "Li Si"(#337) 合并之前的世界:被并掉的实体带着自己的事实重新长回来
只知道「2024 年」入职 年精度:年内成立,年外不成立
「不再担任,日期不详」 valid_to_precision = unknown——已知缺口,见 #345
「加薪由平台组批准」——没有起点的事实 世界轴把没有起点读成自古就有:入职前五个月问,答出 2024 年才出现的边——已知缺口,见 #352
三道答案是空的题 项目开始前、入职前、撤回后:把开区间读成「自古如此」是最容易犯的错

known_gap: true 的题目不进主分,单独一栏。它们钉住的是已知还没做对的行为, 修好那天自己翻绿;让主分一直背着一个我们已经知道的数,只会让人不再看它。

每一组都带着几条没裁的消解审核项(同名的 Meridian Systems / Project Aurora 分成了两三个 实体,宁分勿合,等人)。脚本不替人裁,题目按名字取第一个匹配的实体——这是这份数字的 一个已知噪音源,不藏。

数字(2026-09-05,dev @ 619061e,DeepSeek-V3 + bge-m3)

配置 主分 世界轴 记录轴 已知缺口
声明了唯一性公理 35 / 35 21 / 21 14 / 14 0 / 2
本体自己长(--no-declare) 28 / 35 15 / 21 13 / 14 1 / 2
本体自己长,灌完再声明、对账(--no-declare --reconcile) 34 / 35 21 / 21 13 / 14 1 / 2

两组的差落在需要自动闭合的那几题上:三任接力(谁在管 Aurora 的四个时刻)和 薪资更正(两个时刻)。原因只有一个:自动闭合按 functional / inverse_functional 跑,而这两位永不自动推断(bootstrap_ontology.rs 写了理由)。没有声明的库里, 接任不会闭合前任——账本变成

Zhang San | leads | Project Aurora | from 2023-02-01 | (无终点) | live
Li Si     | leads | Project Aurora | from 2024-07-05 | (无终点) | live
Zhou Qi   | leads | Project Aurora | from 2025-09-01 | (无终点) | live

三个人同时在管一个项目。问「2025 年六月谁在管」,三个都在。新用户开箱后的库 就是这个样子,见 #341。

记录轴几乎不受影响(13/14 vs 14/14):它不依赖任何声明,只看写入时就记下的两列。 声明那一组里,合并倒回与更正倒回都是满分——#337 与 #311 两刀在真实摄入 路径上是对的,不只是在连库测试里对。

第三行是 #341 那一刀之后新用户走的路:先灌、本体自己长、然后有人接受声明。 GET /ontology/uniqueness 报出「哪条谓词的哪一端挂着两个以上开放值」,跑分脚本拿语料的 公理表当那个人——只接受语料本来就声明了的那一端——PATCH 打开它、POST reconcile 把账上 已有的行按年表对一遍。世界轴回到满分;记录轴那一题(adviser-record-wave1)与声明无关: advises 只出现过一次,自动扩展没采纳它,那条事实从来没落过账。

全收过一次,分数反而更歪。 第一版脚本把候选全接受了——leads 的主语侧(一人只管 一个项目)、works_for 的宾语侧(一家公司只有一个员工)也被打开,李四的 Aurora 被 Helios 闭合,张三反而止于周七。候选是证据,不是判决;这正是产品把这一步留给人的原因, bootstrap_ontology.rs 里那句「functional 永不自动」在这里被量了一次。

先等队列清空再记时刻。 文档 done 之后本体自动扩展还在另一个任务里跑:把本体没认下 的谓词建出来、把那些事实改写过去。前两次 --reconcile 报了零条候选,是因为记时刻、 算候选时 leads 这条谓词还不存在;同一个原因也让灌完的记录轴时刻落在事实之前—— 之前 --no-declare 那一组的 28 与 25 之间的抖动,一部分就是它。现在每一波都等 jobs 里这个库的任务清空再记时刻。

第一版(25 题、没有更正/合并/三任接力)的数字是 25/25 对 15/25,第二版(36 题主分) 是 36/36 对 29/36,都留在 git 历史里。第三版只是把 lin-zhao-employer-2023-01 挪进 已知缺口(它的 not 里补了 Platform Group,见 #352),主分的分母跟着少一。

对话那一路:28 / 35

同一个库,同一批题,改成在 Chat 里问(--chat)。账本那一路量的是账本,这一路量的 是 agent 带着工具能不能问到。已知缺口的两题不进这个数:对话问的就是这本账,不可能 比账本答得更对。

这个数是在 #349(模型看得见字面值)与 #350(工具面有了 as_of)之后量的。 之前是 24 / 37,差的 13 题里 7 题是记录轴没法问、6 题是薪资没法看见——两处代码,两刀,都合了。 合完第一次重跑,数字纹丝不动,原因不在产品:模型写 "28,000 CNY",判分找 "28000"。 千位分隔符归一之后,薪资六题全绿。

还差的 7 题:

堆 题数 是什么
判分读不懂否定句 3 答案是对的:「没有记录表明李四那时与 Helios 有关」「交接备忘之前我们以为是张三,后来才改成李四」。not 里的名字出现了,子串判分记成错。要把这一路当正式数看,得换成让模型判卷——expect / not 这套词表是给账本写的
模型把工具调用当正文吐出来 2 回复里是 <|tool▁call▁begin|>function…,一次工具都没调。DeepSeek-V3 经 SiliconFlow 偶发,同一题重跑就好。计错,不遮
「…之前」的题 2 「调薪单到之前」「第三任宣布之前」——模型该去 changes 里找那一刻;一题反问用户要日期,一题把计划叙述了一遍没调工具。就算它去找,changes 只印到天,而更正与首次灌入在同一天(#351)

记录轴 9 / 14,之前 7 / 14。模型现在会带 as_of,题目给了时间戳它就原样传(轨迹里 2 facts as recorded by 2026-09-05,回答里复述了 2026-09-05T02:43:53.382Z;轨迹那一行 只印到天,也记在 #351 里)。所以 ask 里「第几波灌完」改成了 {wave-N} 占位符,跑分时填成真实时刻:「as of the second ingest」是卷子内部的说法, 模型无从知道它是哪一刻,只会反问,那样的题量不到产品。「…之前」那类题故意不给戳, 量的是模型会不会自己去找。

同一题两次跑答案会不同(前后差 ±2 题),单次数字不要当精确值。--only a,b 只重问 几题,工具轨迹(steps)留在结果里——一题是产品没答上还是模型没问对,看轨迹比看正文快。

8/37 那个数作废过一次:第一版探针读错了 SSE 字段(.delta 而不是 .text), 每题拿到的都是空串,而空串不含 not 里的名字,八道 expect: null 的题就这样被记成 通过。现在空回复一律记 error 不计分,接口失败也单独记。写在这里,因为它说明 对话那一路的判分要先验证"真的拿到了回复",否则一个解析错误会长得像一个分数。

跑出来的其他东西,都记在这里而不是藏进分数:

  • at 不给 = 不过滤世界轴,不是「现在」。as_of 不给才是「现在」。两根轴的 缺省口径不对称,aurora-lead-unfiltered 这道题把它钉住了。
  • 「不再担任,日期不详」被读成仍然成立(#345):账本专门留了 valid_to_precision = unknown 这个状态,而每一处世界轴过滤都按 valid_to IS NULL 判活。known_gap 那一栏 0/1 就是它。
  • 判分脚本自己犯过一次错:一跳邻域里含邻居之间的边,第一版把它们两端的名字都算成 "看见的",于是「李四 2023 年在管 Aurora」被误判出来。现在只看碰到主语的边。 这类错会把产品的正确行为报成失败,比漏报更坏,所以写在这里。
  • 一条 salary 值被抽取丢掉过一次(extraction_drops 里的 malformed_item): 声明了 number 之后,模型写成 "28000 CNY" 的那一次没能通过校验。宁缺勿脏是 写死的取舍,这里只记它发生过。

类型图的测量台(2026-09-23)

typed.mjs 量的是 0044 §Measurement 里「Typed graph」那一行:对齐把开放陈述算成类型化事实之后, 金标召回了多少、裁判认多少是对的。 语料是 Re-DocRED(MIT)测试集抽的 100 篇,它的 95 条属性当 已批准的本体;fetch-redocred.mjs 生成语料、答案卷和本体文件,原始数据不进仓库。

属性的定义域/值域从训练集统计(truth/redocred-ontology.json),不留空:不声明的属性对每条 签名都是候选,95 条一齐进候选就超过对齐器的上限,每条签名都溢出成 undecided。训练集学、 测试集评。值域只有时间/数值的属性建成 attribute,别的建成 relation。

node scripts/bench/fetch-redocred.mjs --n 100 --seed 1          # 一次:生成 corpora/ 与 truth/ 下的三份文件
node scripts/bench/typed.mjs --label run1 --judge 200            # 一组:新库 → 本体 → 语料 → 抽取 → 对齐 → 打分
node scripts/bench/typed.mjs --label run2 --judge 200            # 再来一组:门槛按两轮报
node scripts/bench/typed.mjs --kb <id> --score                   # 只重新打分

加 --errata 就在对齐之后排一次勘误 agent(0044 决定 7):报它看了多少、撤改加各几条、留给人几条、 花了多少 token,再打一次分;带 --judge 时裁判也判撤掉的行——判 stated 的就是撤错的。0044 §7 的 度量正是这两个数:precision gained against correct facts removed(原型:76.1% → 89.5%,撤 278 条, 约四分之一是对的)。

报的数:gold recall(同句与跨句分开,门槛只看同句;跨句的等派生规则)、judged precision (裁判抽样;金标漏标严重,精度只信裁判)、entity-pair recall(开放陈述那一层)、绑定的 bound / none / undecided。0044 的门槛:裁判精度不低于完整原型的 75.3%,同句召回不低于完整原型, 两轮各报一次,每篇文档的 token 不到原型的五分之一。

第一次真跑(2026-09-24,gemini-3.5-flash 经 apexin.net,两组各一个新库)

裁判与抽取是同一个模型,精度要打折看。--approve-rules 替审核人把对齐器提的规则全批了, 量的是蕴含机制的上限;真人会驳回一部分。

组 1 组 2
开放陈述 / 实体 1855 / 1854 1778 / —
签名 bound / none / undecided 214 / 927 / 48 409 / 1074 / 70(打分中又跑了一轮对齐,从 312 涨到 409)
类型化(按绑定) 358 411 → 629(第二轮对齐后)
gold recall 勘误前(同句 / 跨句) 6.0%(8.7% / 3.7%) 6.5%(9.6% / 4.3%)
规则:提 / 批 → 读数 / 隐含行 112 / 112 → 88 / 75 159 / 159 → 146 / 164
gold recall 批规则后、勘误前 6.6%(9.4% / 4.1%) 9.6%(13.7% / 6.2%)
裁判精度 勘误前(200 抽样) 96.9% 92.5%
勘误:看 / 撤 / 改 / 加 / 拒 429 / 53 / 24 / 463 / 49 963 / 135 / 97 / 1146 / 124(两遍累计)
撤改的行里裁判判 stated 34 / 37 72 / 110(另 34 条 misworded)
gold recall 勘误后 11.9%(17.7% / 7.0%) 17.1%(24.6% / 10.7%)
裁判精度 勘误后(全量) 96.7%(547 条) 96.4%(700 条)
entity-pair recall(开放层) 15.0% 13.7%

读数:精度远高于原型勘误前的 75.3%;召回远低于原型(同句 8.7% 与 9.6% 对比原型整体 15.3%)。短板在对齐的 覆盖:1189 条签名里 927 条判「无」,P17(国家)565 条金标只中 1 条,P27 国籍、P150 下辖为 0——这些 属性靠蕴含规则,而规则要人批;全批之后隐含 75 行,召回只涨 0.6 个点,因为读数只答出 36 / 88。勘误 这一刀的账与原型相反:撤掉的 37 条里 34 条裁判认为原文说了(撤错),加的 300 多条精度与其余持平 (全量 96.7%),召回因此从 6.0% 到 11.9%。每篇 token 这一轮量不出:apexin 的流每一帧都带累计用量, 服务端按帧记了日志;从这个 PR 起每次回复只记一行,下一轮能按阶段加总。

第二次真跑:成本与效果的几刀(2026-09-24,gemini-3.5-flash,20 篇小批量 --corpus redocred-20)

第一次真跑每篇 13.6 万 token,正文只有 300。按调用类型拆开(服务端从 #893 起每次回复记一行用量) 之后逐刀改,每刀用同一批 20 篇(735 条金标)验证,同一个裁判模型:

样本 配置 绑定 bound/none/undecided 勘误前 召回(同句)/ 精度 勘误后 召回(同句) 全量精度 每篇 token
smoke5 全 minimal,短名单,共享属性表 120/150/39 10.7%(20.8%)/ 69% 13.2%(24.4%) 74.4% ~10 万*
smoke6 对齐按默认强度思考 77/163/16 11.0%(19.2%)/ 88% 13.7%(24.0%) — ~5.3 万
smoke8b + 标签当键、批次并行 98/150/15 12.8%(22.7%)/ 83% 15.2%(26.0%) 82.1% ~5.6 万
smoke10 对齐按 low 120/163/34 11.6%(23.1%)/ 70–75% 17.1%(27.9%) 76.3% ~3.9 万

*smoke5 还带着提规则每项整张属性表的巨无霸请求(一次 5.7 万)。

刀与理由:

  • 候选短名单:结构对得上的候选超过十条时,签名的文本嵌入后取最近的十条(embed_ontology 的向量), 标签对上短语的一律保留;短名单进判定指纹。一次请求从 1.8 万降到 4 千。
  • 属性表一批只写一遍(短语对齐与提规则),每项只列键。提规则的类别词项从前每项整张表。
  • 模型答标签也认:这个本体的键是 p569,模型十有八九答 dateOfBirth;第一次真跑 1189 条签名里 586 条因此判成坏票。标签唯一对上就认它的键。
  • 推理强度按任务分:抽取、读数、勘误照原文写 JSON,用工作区设的 minimal(思考 token 归零、答案 不变);对齐是判断题,全关时 misworded 31%,默认强度 12%,low 折中在 76% 精度;提规则按 low。 (2026-10-03 起不再按任务分,每一种调用都用工作区设的强度,见「对齐不再另外想」一节。)
  • 批次并行:对齐与提规则四批同飞,20 篇的对齐从 29 分钟到 5 分钟;台子的裁判也四篇并行。
  • 只问绑上的签名提规则、基准库关掉治理 agent(它在第三轮花了约 500 次调用)、勘误只带文档 最近的 24 条属性。
  • 撤销要两票加一个结构报的理由:第三轮 100 篇整轮零错撤(撤 2 改 8,全部第二票确认);加的放开 (文档里出现的新名字可以建实体、没类型化行的文档也看一次)把召回从 9.3% 推到 18.4%,代价是加得多 之后全量精度从 96% 到 90%。

跳过的签名(第三轮 586、之后每轮 60 上下)里坏票修掉之后剩下的是结构上一条属性都对不上的:宾语是 值而本体只有 9 条属性型,或那对类没有任何属性声明。那是这份本体的覆盖,不是解析。

第三轮 100 篇(两票撤销,老成本)的完整数:勘误前 670 条、召回 9.3%(同句 13.1%)、精度 93.9%; 勘误后 973 条、15.9%(22.7%)、97.0%;全批 166 条规则隐含 149 行,再勘误累计加 790、留人 167, 1203 条、18.4%(26.5%),全量裁判 90.2%。

第三次真跑:#906 之前的 100 篇基线,两组(2026-09-25,dev @ 961c3c0,gemini-3.5-flash 经 apexin.net,bge-m3)

两组各一个新库,--judge 200 --errata,裁判与抽取同一个模型。这个二进制在 #906(短名单、属性表 一批一写、只问绑上的签名、勘误两票)之前,所以它是上面那张小批量表的 100 篇对照,不是 #906 之后的分。

组 1 组 2
开放陈述 / 实体 1927 / 1917 2214 / 2194
签名 bound / none / undecided 263 / 880 / 48 434 / 1426 / 80
类型化 勘误前 → 后 487 → 664 753 → 869
gold recall 勘误前(同句 / 跨句) 7.8%(11.3% / 4.8%) 9.7%(13.2% / 6.8%)
裁判精度 勘误前(200 抽样) 91.0% 88.5%
勘误:看 / 撤 / 改 / 加 / 拒 75 篇 723 条:67 / 31 / 437 / 63(两遍累计) 99 篇 762 条:162 / 34 / 449 / 47
撤改的行里裁判判 stated 72 / 98 164 / 196
gold recall 勘误后(同句 / 跨句) 12.8%(18.6% / 7.8%) 15.9%(22.4% / 10.4%)
裁判精度 勘误后(200 抽样) 96.0% 96.5%
entity-pair recall(开放层) 15.7% 14.5%
用时 抽取 11 分,对齐 2 时 29 分,勘误加裁判 24 分 共 284 分

按 0044 的门槛读:裁判精度两轮都在 75.3% 之上;同句召回勘误前 11.3% / 13.2%,仍低于原型整体的 15.3%,勘误后 18.6% / 22.4% 过了,但这个二进制的勘误撤的多半是原文说了的(72 / 98、164 / 196), #906 的两票撤销就是冲它来的;每篇 token 远超"原型五分之一"这一条,见下。

token 按阶段拆(服务端每次回复一行用量,按时间窗归到阶段;治理 agent 与对齐同时在跑, 归在同一格里):

阶段 组 1 调用 / token / 每篇 组 2 调用 / token / 每篇
抽取 333 / 174 万 / 1.7 万 389 / 228 万 / 2.3 万
对齐 + 提规则 + 治理 agent 2226 / 1302 万 / 13.0 万 2089 / 1497 万 / 15.0 万
勘误 + 裁判 131 / 109 万 / 1.1 万 122 / 104 万 / 1.0 万
合计每篇 15.8 万 18.3 万

那一大格里有三样东西,两样 #906 已经砍了(提规则每项整张属性表,一次 5.5 万 prompt;短语对齐 每项一份候选清单加两票),第三样是治理 agent 的并发重判:每篇文档抽完都排一个治理任务,排队 去重只挡排着的、不挡在跑的,十个任务同时读同一个队头——组 1 里 1421 个对被裁了 9481 次(一个对 38 秒内被十个任务各判一次),组 2 里 2153 个对被裁了 8714 次;粗算占整轮三分之一的 token。#916 给每个库加了一把治理运行锁。#906 之后的二进制该在同一份语料上再跑两组,那才是 cut 2 的正式度量。

两个跑台子的注意:裁判端点要显式给 BENCH_JUDGE_BASE / _KEY / _MODEL——库里的密钥是封印过的, 脚本从 llm_settings 读出来的是密文,拿它去调用回的是 401;治理 agent 从 #906 起在基准库里关掉。

第四次真跑:#906 与 #916 之后的 100 篇,两组(2026-09-25,dev @ b9e3aae,gemini-3.5-flash 经 apexin.net,bge-m3,chat_reasoning_effort = minimal)

与上一节同一份语料、同一个模型、同一个裁判,两组各一个新库,--judge 200 --errata。这是 cut 2 门槛的正式度量。

组 3 组 4
开放陈述 / 实体 1576 / 1636 1498 / 1575
签名 bound / none / undecided 426 / 1178 / 178 442 / 1332 / 142
类型化 勘误前 → 后 689 → 825 713 → (两篇勘误没跑完)
gold recall 勘误前(同句 / 跨句) 9.3%(14.2% / 5.1%) 9.8%(14.3% / 6.1%)
裁判精度 勘误前(200 抽样) 81.0% 79.0%
勘误:看 / 撤 / 改 / 加 / 留人 / 拒 100 篇 702 条:2 / 1 / 213 / 116 / 14 92 篇 682 条:1 / 0 / 214 / 123 / 9
撤改的行里裁判判 stated 0 / 3 0 / 1
gold recall 勘误后(同句 / 跨句) 12.2%(17.8% / 7.5%) 12.2%(17.1% / 8.0%)
裁判精度 勘误后(200 抽样) 83.0% 80.5%
entity-pair recall(开放层) 17.4% 16.7%
用时 47 分 48 分

token 按阶段拆(服务端日志,按时间窗;裁判走脚本直连端点,不在里面):

阶段 组 3 调用 / token / 每篇 组 4 调用 / token / 每篇
抽取 234 / 61 万 / 0.6 万 376 / 88 万 / 0.9 万
对齐 + 提规则 682 / 222 万 / 2.2 万 679 / 232 万 / 2.3 万
勘误 139 / 26 万 / 0.3 万 95 / 18 万 / 0.2 万
合计每篇 3.1 万 3.4 万

按 0044 的门槛读,两轮各报一次:

  • 裁判精度 ≥ 75.3%:过(81.0% / 79.0% 勘误前,83.0% / 80.5% 勘误后)。比上一节的基线低约十个点, 差的几乎全是 misworded(33 / 39 对 17 / 21):minimal 关掉思考 token 后抽取的措辞更糙。
  • 同句召回不低于完整原型(15.3%):勘误前 14.2% / 14.3% 差一点,勘误后 17.8% / 17.1% 过。勘误这一刀 现在只加不撤(撤 2 与 1,且没有撤错),加的 213 / 214 条把同句召回抬了三个多点。
  • 每篇 token 不到原型的五分之一(约 6 千):不过。3.1 万 / 3.4 万,是基线的五分之一,仍是门槛的五倍。 七成在对齐加提规则(每篇 2.2 万);这一段按签名摊、不按文档摊,一个新库 100 篇是它最贵的样子。 要量产品里的每篇成本,得在同一个库上再灌一批新文档看边际 token,台子还没有这个口径。

对照上一节:撤回从 67 / 162 条到 2 / 1 条,撤错从 72 / 164 到 0;用时从三到五小时到 47 分钟; 每篇 token 从 15.8 万 / 18.3 万到 3.1 万 / 3.4 万。组 4 有两篇文档的勘误回复不是合法 JSON,重试三次后 放弃,那两篇没勘误,任务记为失败(jobs.last_error)。

温库第二批:产品口径的每篇边际成本(2026-09-25,同一二进制,组 3 的库再灌 100 篇)

--into <kb> --corpus redocred-100b:fetch-redocred.mjs --seed 2 --name redocred-100b --exclude corpora/redocred-100.json 抽的第二批 100 篇(与第一批零重叠,3600 条金标全在库里那 95 条属性上),灌进组 3 跑完的库,只排新文档的 抽取,对齐照常(已判过、指纹没变的签名不重问),只对新文档打分,token 从服务端日志按阶段算 (BENCH_SERVER_LOG)。裁判只抽本批文档有证据的事实——全库抽样会抽到第一批的事实,脚本正文表里没有它们, 模型读到空正文就判 not_stated(第一次跑就是这么得出 24.6% 的)。

第二批 值
开放陈述 / 全库实体 1491 / 3217
全库签名 bound / none / undecided(第一批跑完时 426 / 1178 / 178) 674 / 1698 / 277
类型化 勘误前 → 后 486 → 684
gold recall 勘误前 → 后(同句) 5.6%(7.7%)→ 8.3%(11.7%)
裁判精度(勘误后,200 抽样) 80.0%
勘误 看了全库 200 篇 1209 条:撤 7 / 改 2 / 加 499 / 留人 221
token:抽取 / 对齐 / 勘误 / 合计 68 万 / 142 万 / 39 万 / 255 万
每篇 2.5 万(新库 3.1 万)
用时 67 分

读数:温库只省了 18%,对齐从每篇 2.2 万到 1.4 万,没有摊薄多少——第二批 100 篇带来约 800 条新签名 (绑定 +248,判无 +520),和第一批的约 1000 条差不多。Re-DocRED 是维基百科各领域的条目,短语签名跨文档 几乎不重复;每条签名约 1.4 千 token,每篇 8 到 10 条。同一领域的语料重复率会高得多,那是摊薄能兑现 的地方,这份语料量不出来。第二批召回低于第一批(同句 7.7% 对 14.2%),因为库里已经判"无"的签名不再重问, 第一批判错的"无"第二批照样错——温库就是这样工作的。勘误在温库上复查了全部 200 篇:新绑定给旧文档也添了 类型化事实,所以旧文档也在它的清单里,每篇边际里的勘误那 0.4 万有一半是复查旧文档。

第五次真跑:短语对齐排在类别词对齐收尾之后(2026-09-25 夜,#926,两组 + dev 对照一组)

组 3 的日志里短语对齐判了 1847 次而签名只有约 1000 条:第二轮类别词对齐(562 个词)和第一轮短语对齐 同时在跑,类别词绑完改了实体的类,签名指纹全变,713 条重判。#926 让类别词对齐只在最后一轮才排短语 对齐,短语对齐开跑前若发现类别词任务还排着就推迟半分钟。同一语料、同一模型、同一裁判:

组 5(#926) 组 6(#926) 组 7(dev 对照,同一时段)
签名 bound / none / undecided 419 / 362 / 165 414 / 377 / 166 420 / 1405 / 165
类型化 勘误前 → 后 739 → 694 → 707 →
gold recall 勘误前(同句) 9.4%(14.2%) 8.7%(13.4%) 9.4%(14.2%)
gold recall 勘误后(同句) 12.4%(17.5%) 12.1%(17.5%) —
裁判精度 勘误前,200 抽样 75.5% 70.6% 78.5%
裁判精度 勘误后,200 抽样 78.5% 75.0% 74.0%
裁判精度 勘误后,600 抽样(种子 7) 77.2% 81.8% 82.0%(组 4 同口径 81.7%)
每篇 token 3.1 万 2.9 万 3.2 万

读数:

  • 签名干净了:dev 上判"无"的 1405 条里约 1000 条是类别词还在变时生成的过渡签名,#926 之后没有了; 短语判定从 1847 次回到 980 次。召回不变。
  • token 几乎没省(每篇 3.1 / 2.9 万对 3.2 万):被重判的签名便宜。对齐的大头是提规则——组 5 问了 1506 次 提出 101 条(类别词 621 问 32 提,短语 411 问 69 提)——和两票本身。
  • 200 条裁判太粗:同一个库 200 条与 600 条能差十个点(组 6 的 70.6% 对 81.8%),裁判自己也是模型, 两次判同一批也不一样。门槛 75.3% 与实际水平(80% 上下)只差五个点,200 条分不出来。--judge 缺省 改成 600;这一节之前的精度都是 200 条的,读的时候按 ±5 个点看。
  • 600 条口径下 #926 与 dev 没有可分辨的差别(77.2 / 81.8 对 81.7 / 82.0)。

按语句数过滤提规则不该做:提出的 101 条规则里 80 条来自单语句签名。类别词那 621 次询问命中 5%, 是能砍的;条件第二票再砍一成多。三样都做每篇也在 2.3 万上下:对齐按签名计费,这份语料每篇 9 到 10 条 新签名、每条约 1.4 千 token,这就是它的地板。0044 的 token 门槛在 Re-DocRED 上要么换口径(每条新签名 的成本),要么换同领域语料再量。

第六次真跑:对齐少问一些(2026-09-28,dev @ b036f45,claude-haiku-4-5 经 apexin.net,bge-m3,20 篇)

20 篇按调用分(typed.mjs 的 token 表):短语对齐的两票占 38%,抽取 20%,提规则 14%,类别词对齐 9%。 短语对齐一次调用的输入约 4600 token,其中属性表将近一半——按到来次序切批时,同批签名的候选几乎不重叠。 三处改动:

  1. 按候选分批:候选相近的签名排进同一批。只改排法,每条签名看到的候选不变。
  2. 第一票说没有的不投第二票:第二票防的是「选第一个」冒充一致;第一票说没有,第二票怎么答都绑不上。
  3. 只有中心词的类别词不问规则:类别词规则的宾语从类别词自己的字里读(「british film」读出英国)。 基线一轮问了 159 个类别词,127 个是单个词,提出的 8 条规则(「state」→ 国家)没有一条读得出宾语。

两轮完整的 20 篇之间抽取自己就不一样(签名 229 对 210,只有 137 条相同),整轮的数字分不出对齐的 变化。所以在同一个库的同一批陈述上把绑定清掉重判,210 条签名,每种构建各判一遍或两遍:

基线 基线 再一遍 分批 分批 再一遍 三处都改
短语对齐调用 46 47 36 40 32
输入 token 190572 199086 128338 143900 103412
输出 token 46537 45422 35809 36644 29703
合计 237109 244508 164147 180544 133115
签名 bound / none / undecided 53 / 110 / 47 66 / 108 / 36 69 / 108 / 33 67 / 109 / 34 63 / 119 / 28
类型化事实 82 87 99 92 84

读数:

  • 分批省短语对齐的三成(24.1 万 → 17.2 万),加上条件第二票省四成五(→ 13.3 万)。
  • 模型自己的波动比改动大:同一个构建、同一批签名、温度 0,两遍判定完全相同的只有 166 / 210(基线) 和 163 / 210(分批)。基线对分批是 146 到 159。分批让判定多变了约十条,方向两边都有(绑上的数和 类型化行数没有变少);20 篇整轮的裁判精度 79.3% 对 78.8%。
  • 条件第二票的代价:它记成「无」的 98 条,在另外四遍里 81 到 86 条本来也是「无」,8 到 12 条是 「拿不定」(第二票答了一条属性),4 到 5 条绑上了(那一遍第一票答的不一样,是波动)。「拿不定」的那 十条上下不再进人的队列,votes.reason = "first_vote_none" 查得到。
  • 20 篇整轮(只有分批):合计 82.1 万 → 62.6 万,每篇 4.1 万 → 3.1 万;其中约一半来自分批,其余是 这一轮抽出的签名少、重问少。

第七次真跑:每篇只抽一遍,表格的规则只随表格带(2026-09-28,dev @ 7d73ff9,gemini-3.5-flash,20 篇)

台子从前让每篇抽两遍。 配了对话模型的库,文档解析完管线自己排抽取;typed.mjs 和 timewords.mjs 随后又对每篇调一次手动抽取,而手动抽取是强制全量(解雇在跑的任务、从头再抽)。20 篇 24 个分块,抽取调用 45 次。现在只补管线没排上的。这一节之前的每篇 token 都多算了将近一遍抽取,召回也可能受它影响(在跑的 那一遍被中途顶替):这一轮同句召回 20.1%,实体对召回 18.0%,只有 20 篇,要在 100 篇上再量。

调用 次数 每次输入 每次输出 每篇
短语对齐 38 3283 240 6695
抽取 26 2517 1152 4770
类别词对齐 24 2947 647 4314
提规则 14 4061 122 2929
时间解释 20 1770 141 1912
本体代理 5 4966 2021 1747
实体裁决 9 2504 503 1354
提问题 1 7603 1466 453

合计每篇 24175,输入占 85%。类型化 138,绑定 105 / 98 / 27,裁判精度 79.7%。

抽取的系统消息约 2380 token,一篇 Re-DocRED 文档约 270。 试了三处删减,每处单独量:31 个没有表格 的段落(这 20 篇的 24 个分块加时间台子的 7 篇),每种系统消息各抽三遍,回复用服务端的解析器读,对金标 的实体对和时间台子的期望打分:

系统消息 每次输入 金标实体对(三遍) 同句(三遍) 时间词记下 修复后只剩一条陈述的回复
原样 2488 27.0 / 27.6 / 28.6% 42.6 / 44.6 / 47.0% 28 / 28 / 27 0 / 93
不带表格的规则 2339 28.6 / 28.4 / 28.6% 47.0 / 46.2 / 46.2% 27 / 27 / 27 0 / 93
再去掉规则 6(引文的位置,规则 2 与 7 说过) 2296 29.0 / 27.4 / 26.5% 49.4 / 47.4 / 43.4% 28 / 27 / 28 3 / 93
再去掉结尾的例子 2182 27.9 / 23.3 / 27.6% 44.2 / 41.4 / 45.0% 28 / 29 / 28 0 / 93
  • 表格的规则只随有表格的段落带:没有表格的段落上分数不降,有表格的段落系统消息与从前逐字相同。落地。
  • 规则 6 重复,却在管格式:去掉之后 93 次里 3 次括号写错,解析器修到最后一个完整对象,整段只剩一条 陈述。不去。
  • 结尾的例子:三遍里一遍实体对掉到 23.3%,解析没有出错。分不清是波动还是损失,不去。
  • 同一条系统消息、温度 0,两遍抽出的陈述(主语、短语、宾语)只有六成相同。逐条比对陈述量不出提示词的 改动,要用有标准答案的指标。

不合法的回复逐条读(同一天)。 上面那 372 份回复里 7 份整体解不开,没有一份是被截断的 (finish_reason = stop):模型写坏了括号。从前的修补退到坏处之前,之后的全丢:

回复 系统消息 从前留下 实体 / 陈述 / 日期 逐条读
zh-compiled.md 原样 28 / 21 / 0 28 / 21 / 2
redocred-013 去掉规则 6 24 / 1 / 0 24 / 16 / 0,别名 6
redocred-002 去掉规则 6 14 / 1 / 0 14 / 8 / 0,别名 2
redocred-003 去掉规则 6 19 / 1 / 0 19 / 9 / 0,别名 3
另外 3 份(坏在结尾) 不变 不变

整体合法的 365 份,新旧解析逐条相同。

第八次真跑:类的定义表一批只写一遍(2026-09-29,dev @ 8109661,gemini-3.5-flash)

类别词对齐每篇 4314 token。一批二十个词,每个词下面各列一遍候选类的定义:一个词约 145 token 的输入里 约 120 是它。改成定义表一批写一遍、各项只列候选的键(与短语对齐同一个办法),判断的规则不动。

在第七次那个库上(20 篇,176 个类别词,实体不动)把类别词的绑定清掉重判,新旧各两遍。正确率是库里的实体 按名字对上金标实体之后,类别词绑到的类与金标类型是否一致(216 个对得上、类型不含糊的实体):

从前 从前 再一遍 定义表写一遍 再一遍
调用 18 18 18 18
输入 token 61382 61389 29142 29138
输出 token 19977 16361 13748 14571
绑定 bound / none / undecided 163 / 2 / 11 162 / 3 / 11 157 / 1 / 18 166 / 2 / 8
实体 对 / 错 / 没绑上 210 / 4 / 2 210 / 5 / 1 207 / 6 / 3 209 / 6 / 1
绑上的里面对的 98.1% 97.7% 97.2% 97.2%
  • 输入少一半多(6.1 万 → 2.9 万),合计 8.0 万 → 4.3 万。
  • 四遍里判得不一样的 25 个词,没有一个是两遍绑到了不同的类:全是绑上与拿不定之间来回。同一个构建两遍 相同的 166 与 160 个,新旧之间 157 到 164 个,分不出来。
  • 正确率差一个实体上下(错 4、5 对 6、6),在这个量上也分不出来;本体只有六个类,候选多、定义长的 本体省得更多,也更该在那样的库上再量一次。

第九次真跑:#906 之后的 100 篇,两组,换一个模型(2026-09-30,dev @ 798fb24,deepseek-flash 经 DeepSeek 官方接口,bge-m3 经 OpenRouter,裁判 gemini-3.5-flash 经 OpenRouter)

第四次真跑的重复:同一份语料、同一个裁判模型、两组各一个新库、--judge 200 --errata,抽取模型换成 deepseek-flash。目的有两个:0044 cut 2 的三条门槛要"两轮各报一次",第四次之后 dev 又改了七刀 (#906 之后到 #1013),在当前的 dev 上再量一次;另一个是看门槛过不过取决于模型还是管线。

三处与第四次不同,读数时要记着:

  • 抽取的推理强度是 none,不是 minimal。 deepseek-flash 默认思考,minimal 关不掉(试探请求仍有 思考 token),只有 none 能关;设置接口和库的 CHECK 只认 minimal / low / medium / high,这一轮在 测量库上去掉了那条 CHECK 直接写 none。对齐与提规则在代码里固定用 low,deepseek 在 low 下每次 调用输出七八千 token 的思考,gemini 只有几百——每篇 token 因此不能和第四次直接比,下面按调用拆开。
  • 嵌入走 OpenRouter 的 bge-m3(SiliconFlow 的余额用完了)。同一个模型,向量与库里 SiliconFlow 算的 余弦 0.9999,本体向量不用重建。
  • 裁判从台子直连,Node 自己的 fetch 不读代理变量:直连出口被 OpenRouter 按地区拒掉 gemini(403), 要 NODE_USE_ENV_PROXY=1。裁判四篇并行又撞上新账号的每分钟限速(429),两组各丢了一半样本; typed.mjs 的裁判调用从这一轮起对 429 和 5xx 退避重试,两个库用它重新打分,200 条各判满。
组 1 组 2
开放陈述 / 实体 1495 / 2100 1519 / 2113
签名 bound / none / undecided 277 / 692 / 33 283 / 677 / 54
类型化 勘误前 → 后 406 → 642 444 → 676
gold recall 勘误前(同句 / 跨句) 5.1%(7.1% / 3.4%) 6.4%(8.4% / 4.8%)
勘误:看 / 撤 / 改 / 加 / 留人 / 拒 100 篇 407 条:0 / 1 / 246 / 52 / 9 100 篇 445 条:0 / 0 / 260 / 50 / 6
gold recall 勘误后(同句 / 跨句) 8.4%(12.3% / 5.1%) 9.6%(13.2% / 6.6%)
裁判精度 勘误后(200 条判满,重新打分) 86.5%(stated 173 / misworded 22 / not_stated 5) 89.0%(178 / 18 / 4)
entity-pair recall(开放层) 10.7% 10.8%
用时 105 分 114 分

token 按阶段拆(服务端日志,按时间窗;裁判不在里面):

阶段 组 1 调用 / token / 每篇 组 2 调用 / token / 每篇
抽取 220 / 67 万 / 0.7 万 229 / 69 万 / 0.7 万
对齐 + 提规则 344 / 332 万 / 3.3 万 339 / 355 万 / 3.6 万
勘误 129 / 34 万 / 0.3 万 141 / 43 万 / 0.4 万
合计每篇 4.4 万 4.8 万

按调用(两组相近,取组 2):短语对齐 142 次、每篇 1.7 万;类别词对齐 76 次、0.9 万;提规则 38 次、 0.6 万;抽取 126 次、0.4 万;本体代理 26 次、0.4 万;实体裁决 102 次、0.3 万;勘误 110 次、0.2 万; 时间解释 85 次、0.1 万。对齐三项合计 3.2 万,其中输出 token 占四分之三——那是 low 下的思考。

按 0044 的门槛读,两轮各报一次:

  • 裁判精度 ≥ 75.3%:过(86.5% / 89.0%)。与第四次的 83.0% / 80.5% 同一水平;misworded 占错的大半, 与第四次一样。
  • 同句召回不低于完整原型(15.3%):不过,勘误后 12.3% / 13.2%。第四次 gemini 是 17.8% / 17.1%。 差在抽取那一层:实体对召回 10.7% / 10.8% 对 17.4% / 16.7%,绑上的签名 277 / 283 对 426 / 442—— deepseek-flash 在开放抽取上比 gemini-3.5-flash 少抽三分之一的实体对,后面每一步按比例缩小。 这条门槛过不过取决于抽取模型,不是管线:同一份代码在 gemini 上过、在 deepseek-flash 上不过。
  • 每篇 token 不到原型的五分之一(约 6 千):不过,4.4 万 / 4.8 万。第四次 3.1 万 / 3.4 万。两次真跑 的管线相同,多出来的一万三是 deepseek 在对齐上的思考(对齐输出 243 万 / 268 万 token,第四次的 对齐整个才 222 万 / 232 万)。四次 100 篇真跑没有一次接近这条门槛:gemini 五倍,deepseek 八倍。 它按文档摊对齐的成本,而对齐按签名收费,一个新库 100 篇是最贵的形态(第四次已指出)。要么按 签名重定,要么记下"不过"接受——这是 0044 的决定,台子只给数。

两组之间很一致:同句召回差 0.9 个点,实体对差 0.1 个点,每篇 token 差 4 千。运行间的波动小于 两个模型之间的差。勘误这一刀两组都只加不撤(撤 0 / 0,改 1 / 0),加的 246 / 260 条把同句召回 抬了五个点,判满之后精度 86.5% / 89.0%。

费用:deepseek-flash 两组加 20 篇试跑共 ¥28.7(输入 $0.15 / 百万、输出 $0.60 / 百万,思考 token 按输出计); 裁判约 600 次调用、每次 $0.0045(gemini 没关思考,每次三四百个思考 token),合计 $3 上下。

温库第二批,deepseek-flash:token 门槛再量一次(2026-10-03,同一二进制 dev @ 798fb24,第九次组 2 的库再灌 100 篇)

第九次真跑留下的口子是 token 门槛:四次 100 篇真跑没有一次接近,而新库 100 篇是对齐最贵的形态。这一次量 另一头:--into <第九次组 2 的库> --corpus redocred-100b --judge 200 --errata,同一个模型、同一份设置 (抽取 none,对齐与提规则固定 low),和 2026-09-25 gemini 的那次温库同一个做法。

第二批 值
开放陈述 / 全库实体 1488 / 4092
全库签名 bound / none / undecided(第一批跑完时 283 / 677 / 54) 478 / 1396 / 92
类型化 勘误前 → 后 367 → 603
gold recall 勘误前 → 后(同句) 3.7%(4.8%)→ 6.9%(9.6%)
entity-pair recall(开放层) 9.2%
裁判精度 勘误前 / 后 89.2%(判了 186,14 条请求失败)/ 87.5%(200 条判满)
勘误 看了全库 200 篇 822 条:撤 0 / 改 1 / 加 521 / 留人 96 / 拒 14
每篇 token 4.2 万(新库 4.8 万)

按调用,和第九次组 2 的新库并排(每篇,调用次数在括号里):

调用 新库 温库第二批
短语对齐 1.7 万(142) 1.35 万(111)
提规则 0.6 万(38) 0.60 万(34)
类别词对齐 0.9 万(76) 0.58 万(54)
实体裁决 0.3 万(102) 0.58 万(208)
抽取 0.4 万(126) 0.48 万(137)
勘误(复查 + 核对) 0.2 万(110) 0.25 万(123,全库 200 篇)
本体代理 0.4 万(26) 0.21 万(12)
时间解释 0.1 万(85) 0.14 万(84)
合计 4.8 万 4.2 万

读数:

  • 温库只省了 13%(gemini 那次 18%)。第二批 100 篇带来 952 条新签名(绑定 +195,判无 +719, 未定 +38),第一批是 1014 条:签名跟着文档线性长,不是跟着库收敛。短语对齐每条新签名约 1.4 千 token (新库 1.7 千),和 gemini 量到的一样。
  • 光是抽取加时间解释就到门槛了:0.48 万 + 0.14 万 = 0.62 万,门槛是约 0.6 万。对齐一个 token 不花, 这条门槛在这套管线上也过不了——它是照紧凑原型(一次抽取调用、类型映射走缓存)定的,而这里每篇 约 1.4 次抽取调用(100 篇 137 次),每次带着整份抽取契约。
  • 对齐三项 2.5 万,占六成,其中输出占八成(短语对齐输入 32 万、输出 103 万;类别词 8 万、49 万; 提规则 16 万、44 万)。那是 deepseek 在固定的 low 下的思考,gemini 同一步每次只有几百。
  • 实体裁决随库变大:208 次对 102 次。库里的实体从两千到四千,同名候选多了;这一项在温库上是涨的。
  • 第二批召回低于第一批(同句 9.6% 对 13.2%),原因同 gemini 那次:库里判过"无"的签名不再重问。

对着原文看:这份语料每篇约 200 个词、270 个 token。温库每篇 4.2 万是原文的约 160 倍,新库 4.8 万约 180 倍;光抽取 0.48 万就是 18 倍,门槛 0.6 万是 22 倍。文档越短这个倍数越难看——每次调用的提示词 (契约、本体、候选)是固定开销,不随正文变短。

所以「按签名重定」救不了这条门槛:签名数跟文档数成正比,换个分母,每篇的数还是那么多。能动的是两处, 都要另外量:对齐在 deepseek 上的思考(low 换成关掉,gemini 上关掉时错判从 12% 到 31%,deepseek 没量过), 和进模型之前先筛掉一部分签名。否则就是记下「不过」接受它。这是 0044 的决定,台子只给数。

这一轮的台子出过一次岔子,读数时要知道。 评测脚本 03:00 起了一次,上传完之后进程没了(runs.log 里没有它的退出行),04:06 又起了一次,原因没查出来;服务端一直在跑,文档只传了一遍,第二次接上时抽取 已经做完。脚本自己的 token 表只算它自己那一段(04:06 之后,每篇 1.6 万,没有抽取),不能用;上面的数 是服务端日志从 03:00:49 到 04:38:24 整段按 call= 归的。短语对齐因此分两段跑(04:06 之前 65 次,之后 46 次),判定数前后是接着涨的(2664 → 3236),没有看到重问。

费用:180 万输入、239 万输出,约 $1.70(余额少了 ¥10.6);裁判约 400 次调用,$2 上下。用时 98 分。

对齐不再另外想:每一种调用都跟工作区的推理强度(2026-10-03,deepseek-flash,20 篇,全关两组对 low 一组)

从 2026-09-24 起对齐、提规则、本体代理固定用 low(上面「推理强度按任务分」那一条):gemini 上全关时 misworded 从 4% 到 31%。在 deepseek-flash 上这条规矩的账是另一个样:low 每次调用想七八千 token,对齐 三项占每篇 token 的六成、其中八成是思考(温库那一节)。这一刀把固定的 low 去掉,这几种调用和抽取一样 用工作区设的强度;设置接口和库多认一个 none(迁移 0105),DeepSeek 只有它关得掉思考。

量法:--corpus redocred-20 --judge 200 --errata,新库。全关两组是这一刀的二进制、工作区设 none; low 那一组是 2026-09-30 的试跑(dev @ 798fb24,抽取 none、对齐固定 low,裁判重新打分的那一次)。 两边的抽取设置相同,差别只在对齐、提规则这几步想不想。

对齐 low(09-30) 全关 组 1 全关 组 2
签名 bound / none / undecided 75 / 120 / 10 80 / 107 / 23 52 / 131 / 24
类型化 勘误前 → 后 113 → 157 119 → 156 78 → 107
裁判精度 勘误后(判了) 91.7%(157) 83.3%(156) 89.7%(107)
misworded(勘误后) 9 条,5.7% 24 条,15.4% 8 条,7.5%
同句召回 勘误后 19.2% 14.3% 12.0%
entity-pair recall(开放层,对齐碰不到) 14.0% 11.0% 12.9%
每篇 token 4.7 万 2.2 万 1.9 万
其中短语对齐 / 类别词对齐 / 提规则 2.1 万 / 1.0 万 / 0.65 万 0.60 万 / 0.18 万 / 0.21 万 0.43 万 / 0.18 万 / 0.12 万
用时 28 分 6 分 4 分
  • 每篇 token 少了一半多,对齐三项从 3.8 万到 0.7–1.0 万。 短语对齐的输出从 32 万 token 到 4 千: 输入几乎没变(10 万对 12 万),少掉的全是思考。全关那两组里本体代理占了每篇 0.27 万,low 那一组 的时间窗里没有它;去掉它是 1.6–1.9 万。用时四到六分钟对二十八分钟。
  • 精度两组 83.3% / 89.7%,low 是 91.7%,都过 75.3% 的门槛。misworded 一组 15%、一组 7.5%,low 是 5.7%:方向和 gemini 上量到的一样(不想的时候它挑「相近」而不是「就是」的属性),幅度小得多——gemini 是 4% 到 31%。
  • 全关之后两组之间差得更大:绑上的签名 80 对 52,同句召回 14.3% 对 12.0%。两票都不想的时候,同一条 签名这一次绑上、下一次判无的更多。low 只有一组,它自己的波动没量。
  • 同句召回 low 那一组高出五到七个点,一半在抽取(实体对 14.0% 对 11.0% / 12.9%,对齐碰不到这一层), 100 篇上 low 是 12.3% / 13.2%——20 篇的召回要这样读。

这是用精度和稳定换 token 和时间,换不换是工作区的选择:设 low,这几步和从前一样想;设 none 或 minimal,全关。没有哪一步再替工作区另定一个强度。按 0044 的 token 门槛读,1.9–2.2 万仍是门槛的三倍多。

费用:全关两组共 ¥1.0,裁判约 $1。