# 类型消解的测量台 **每一组一个新库。** 这条规则是整个目录存在的理由。 此前连着三轮在同一个库上调检索,而那个库带着前几轮的改类结果——容易的实体早已 精化,拒绝理由里直接写着 `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 ` 换一份答案卷:同一组结果对手填答案与 Wikidata 答案各打一次分。 - `fetch-wikidata-truth.mjs` —— 抓 Wikidata 答案的原始材料:条目主题与**正文里出现过的** 链接条目(导航框里的几百个不算)的 QID 与 P31,P31 类顺着 P279 归到锚类。 - `make-truth.mjs` —— 原始材料 + 映射表 → `truth/.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 # 一轮:清空 → 重抽 → 打分 node scripts/bench/recall.mjs --kb --score # 只打分 node scripts/bench/recall.mjs --kb --reprocess # 改了解析器:连分块一起重来 node scripts/bench/judge_open.mjs --kb --sample 200 # 开放陈述有多少不是原文说的(门槛 2%) ``` 量「后面的分块看不到前面的分块认下了什么」对输出的影响(#588,分块并发调模型之前要知道的数): 服务端带 `UTOPIA_EXTRACT_KNOWN_IN_PROMPT=false` 起,照常跑 `recall.mjs`,与不带时的几轮比。 这个开关只动提示词,落库时按名字认回前面分块的实体照旧。 ``` # 服务端照常起:每轮记一份 node scripts/bench/recall.mjs --kb --known shown --out runs/588 # 服务端带 UTOPIA_EXTRACT_KNOWN_IN_PROMPT=false 重起:同样几轮 node scripts/bench/recall.mjs --kb --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 # 已有的库:撤掉 agent 的决定、再跑、打分(约二十分钟) node scripts/bench/govern.mjs --kb --score # 只打分 node scripts/bench/govern.mjs --kb --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 --score # 只打分,不动库 ``` **打分不看名字,看数。** 治理那边真值按两个名字键,因为它判的是二分类;这里概念名 是模型自己起的,「Revenue」与「已支付 GMV」按名字对不上任何一条。所以真值一条是 「一个业务口径 + 一条 gold SQL」,打分把提议真跑一遍,跟 gold 的结果比数——数一样 就是同一个口径。省掉逐条手标,判据还是客观的。 四栏分开读:`covered` 是真值里被算出来的有几条(漏没漏);`right` 是提议里跑得通 且对上某条真值的;`wrong` 是**跑得通但一条都对不上**的,附它算出的数与最接近的真值; `broken` 是跑不通的。**要看的是 `wrong`。** 跑不通的提议无害,它失败得很响,人一眼 看得见;跑得通而算错的才是全部风险——问数会拿它印出一个看起来完全正常的数字。这一栏 也是 #504 能不能默认开的唯一依据。 `traps_hit` 数的是落到陷阱列上的提议:整数外键求和、`o_shippriority` 这种恒为 0 的列、 把一段自由文本当维度。它们都跑得通。 语料在 `schemas/.sql`,真值在 `truth/.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 # 跑全部问题 node scripts/bench/ask.mjs --kb --only disc_revenue node scripts/bench/ask.mjs --kb --seed # 先把真值写成确认口径(上界) node scripts/bench/ask.mjs --kb --confirm # 先确认探索提的那些(产品路径) node scripts/bench/ask.mjs --kb --replay # 不重问,拿库里上一轮的回答重判 node scripts/bench/ask.mjs --kb --parallel 4 # 同时问四题:一题 2–6 个模型回合、串行半小时 node scripts/bench/ask.mjs --kb --conventions # 先把真值里的 conventions 写进库(#570) node scripts/bench/ask.mjs --kb --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: ```bash 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 --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 --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 --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 --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。