Skip to content

跑起来之后:我自己的三项评测都在量自己 - #19

Closed
YunyueLi wants to merge 8 commits into
mainfrom
claude/audit-gate-and-eval
Closed

跑起来之后:我自己的三项评测都在量自己#19
YunyueLi wants to merge 8 commits into
mainfrom
claude/audit-gate-and-eval

Conversation

@YunyueLi

@YunyueLi YunyueLi commented Aug 5, 2026

Copy link
Copy Markdown
Owner

接上 #18。第一次真的接上模型(DeepSeek)跑,三项需要模型的评测全都有 bug,而且犯的是这个 PR 系列一开始批评的同一类错——评测在给自己打分,或者在量一个把证据切掉了的窗口。

蕴含率:35.8% 是假的,真实值 85.0%

判分时我发的是 chunk_text[:1500],而爱丽丝的段落有四千八百多字符,引文经常落在截断之外。判分模型如实回答「这句话不在你给我的段落里」。同一条断言在同一次运行里判了两次——引文落在窗口内就通过,落在窗口外就不通过。

改成以引文为中心开窗:120 条抽样里 0 条落在窗口外。真实蕴含率 102/120 = 85.0%

而且它验证了分层抽样的意义:两条 multi_step 全部不成立(0/2),one_step 各层在 0.5 到 0.75,explicit 在 0.8 以上。这正是让 Pass-7 对 multi_step 全查不抽样的理由。

污染探针:图谱 12/12 是构造出来的

ground_truthgraph_answer 的改写,判分模型被问「这个答案跟它自己一致吗」。改成两边都对着原文段落判:闭卷 0/12,图谱 5/12

扰动测试:判分把明显正确的答案算成了错

「Pellwyn 是一个跟着白兔跳进兔子洞的好奇女孩」被判定为「退回了出版版本」。原因是我把一句指令塞进了「给答案打分」那个判分器的标准答案栏。给蕴含判断和退回判断各写了专用提示词,不再挪用为别的问题设计的那一个。

重判后:凭记忆跟上改动 2/6,拿到改后文本跟上 4/6。

原始回答比这两个数说明得多。问「原文是不是说了 X」,凭记忆那一路答「是」并引用了原文——引的是出版版本的句子,根本不在给它的材料里。而失败的两次里,模型引用了改动后的句子,紧接着说了相反的话:引了「the Queen tried the little golden key」然后回答「Alice 有钥匙」,引了「她没有偷看那本书」然后回答「是的,她看了」。

所以把原文放进上下文不等于答案会忠于原文。证据落地是个没解决的真问题,这对一个强制片段级绑定的系统是好消息——它有真实的靶子。

阈值现在是测出来的

LOREGRAPH_COVE_SUPPORTED_FLOOR 从 0.85 改成 0.80。0.85 是我猜的,实测正好落在这个数上,会让每次运行都中止。0.80 是爱丽丝实测值减 5 个点,注释里写明来源、日期、判分模型,以及一本书不构成分布。

记账

model_arm.SPEND 现在跨所有调用累计,花费从评测自身报出来而不是去服务商后台翻。整轮包括三次白跑的运行,共 0.65 元

没做的

  • 只测了爱丽丝,西游记一本没测。
  • 扰动测试的两路都不是 LoreGraph 流水线本身:在改后的文本上重跑抽取需要数据库,本机没有 Docker。测到的是流水线瞄准的上限,不是流水线。
  • Pass-8 那个修复仍然只在代码里。爱丽丝笔记里「她最终的命运和如何回家没有交代」这句假话还挂着,要重跑流水线才会消失。

🤖 Generated with Claude Code

Pass-8 caps each entity's evidence at 30 mentions / 25 edges / 15 facts.
The cap was `items[:N]` over rows ordered by id — i.e. book order — so it
took the earliest chapters and nothing else. On Alice that meant her
outgoing edges and implicit facts came from chapter 1 alone, out of 12.

The cap bites hardest on whoever has the most evidence, which is the
protagonist. Pass-8 is then asked to list what "the source does not tell
us", and duly reported the unread chapters: Alice's note claims her fate
and how she returns home are never given. The full text is in the
database, all 42 chunks through THE END.

A confident false claim about the work, in the voice of evidence, on the
character the reader most wants to read about — the worst failure mode
available to a project whose whole argument is that its claims are
checkable.

Round-robin over chapters instead of taking a prefix. Same budget, same
cost; Alice now sees all 12 chapters in every evidence stream. Tell the
model when its evidence is a sample and which chapters are missing, and
require [GAPS] to be phrased about the evidence shown rather than about
the work.
Pass-7 is described as the hallucination gate and enforces one number:
`literal_match_rate < 0.95` aborts the run. That number cannot fail.
Pass-2/5/6 already drop any claim whose evidence span is not a literal
substring, using this same `is_literal_match`, so on a healthy pipeline
it is 1.0 by construction — as measured: 19,868 of 19,868 spans across
alice and xyj match exactly. The gate proves the quote is real. It says
nothing about whether the quote supports the claim.

`supported_rate` is the number that does — and Pass-7 already computed
it, recorded it, and then ignored it. It never left `pass_runs.stats`.

- Enforce `supported_rate` with a configurable floor
  (LOREGRAPH_COVE_SUPPORTED_FLOOR, 0 disables). Provisional at 0.85:
  no calibrated distribution exists yet, which is what `eval entailment`
  is for. Keep the literal check as an invariant tripwire and say in the
  error that it points at a span-handling bug, not at quality.
- Stratify the sample by (relation|dimension) x inference_depth instead
  of drawing uniformly. A uniform draw of Alice's edges is 63% INTERACTS
  and 80% explicit; PREDICTS is 1.3% and a 150-row uniform sample can
  miss it entirely. Verify every multi_step claim rather than sampling
  it — deepest inference, likeliest to be wrong, rare enough to afford.
- Raise the default sample from 50 to 150 per claim family. 50 was 0.5%
  of xyj's 18,868 claims.
- Report per-stratum rates and the weakest strata, so an operator learns
  which kind of claim is failing rather than a pooled average that hides
  it.
- Ship `metadata.quality` in the book export. The graph's quality number
  belongs with the graph; until now the only number a reader could see
  was a count of edges.
Pass-5 asks for "a specific verb-phrase … stay close to the verb in the
evidence_span" and gets one: alice and xyj between them carry **2627
distinct predicates over 9843 edges, 1554 of them singletons**. A third
of every edge in the graph is labelled with a relation name that appears
nowhere else in it. You cannot ask "who betrayed whom" when betrayal is
spread across BETRAYS, DECEIVES and TRICKS at one occurrence each. The
5-way `relation` enum above it cannot compensate — 65% of edges are
INTERACTS.

Taking the specific verb away would be the wrong fix; it is the most
informative thing on the edge and what a reader wants to see. Add a
second, closed axis instead:

    relation        INTERACTS            5 values — too coarse
    predicate_class CONFLICT             25 values — queryable
    predicate       THREATENS_TO_BURN    open — readable

`classify` resolves the open verb by longest leading stem, so the
model's habit of gluing the object on survives: CAUSES_SNEEZING ->
CAUSATION, THREATENS_TO_BURN -> CONFLICT, PREDICTS_RESURRECTION ->
FORESIGHT. Modal and tense prefixes and both regular and irregular
inflections are peeled (TAUGHT -> TEACH was a real miss).

The vocabulary is derived from the observed distribution, not invented:
every family has a real head in the data, and it was grown by measuring
the misses and adding what they showed. Anything still unplaceable goes
to `<RELATION>_OTHER` rather than a plausible wrong bucket — the share
landing there is the coverage metric, and it now reads 90.1% on alice
and 94.0% on xyj into 25 real classes.

Both shipped exports are backfilled, and `metadata.predicate_coverage`
carries the score so it is visible rather than asserted.
…k good

Every figure in the catalogue was invented. All 85 rows in data.js carried
hard-coded entity/edge/glucose counts, a matchRate, a lastRun timestamp, a
provider and a cost — $116.12 of claimed spend across 84 timestamps, none
of which came from a run. Only alice and xyj have real exports, and even
xyj's row disagreed with its own: 502/1158/2876 claimed against 600/8833/
10035 actual. Twelve rows were marked `verified` while displaying a
matchRate of 0.93 or 0.94, below the 0.95 gate that would have aborted
their runs.

`build_frontend_data.py` was the other half of it, stamping
`status: "verified"` and `matchRate: 0.96` onto any book the moment it was
exported — before Pass-7 had said anything at all.

For a project whose entire argument is that its claims are checkable, the
shop window was the one place nothing was.

- Derive run facts from the export's own Pass-7 record. A book that is
  extracted but not audited reads `extracted` with no rate, which is what
  alice and xyj actually are.
- Null the run fields on the other 83. `queued` is the honest state, and
  the card already had a "not extracted yet" treatment for null stats —
  166 cells now use it.
- Library header gains an "extracted" count so 1,077 entities reads as the
  total for 2 books rather than for 85, and drops the cost stat when no
  cost was recorded.
- Sidebar budget shows the real default ceiling ($20, the
  LOREGRAPH_COST_CEILING_USD default) with spend absent, not $14.27/$50.
- Label the pipeline view's hand-written run as an illustration. It is the
  one screen still showing sample data; now it says so instead of sitting
  next to real figures looking equally sourced.

Bibliographic fields are untouched — title, author, year, tokens, chapters
are properties of the work and were never the problem.

Also fixes a pre-existing overflow this made visible: the three card stats
need ~222px of labels and the grid dropped to 160px columns under 720px,
so labels ran into the neighbouring card. Verified 0 overflowing cells and
0 horizontal page scroll at 320/375/430/592/900/1280.
The project could report 8,833 edges and a 100% literal-match rate and
still not answer either question that matters: is the extraction correct,
and — on a corpus where every work is canonical — does it beat simply
asking a model that has read a thousand essays on each one.

Five evaluations, `loregraph eval [name] --book [id]`:

  perturbation   Alter the source (rename a character to a name never in
                 print, move an object, negate a clause) and ask a
                 question that turns on the change. A system reading the
                 text follows the edit; one reciting its memory reverts.
                 The edit IS the ground truth, so no annotation is needed.
                 This is the only experiment that can show the pipeline
                 earning its cost on a famous book — or that it cannot.
  contamination  The same questions closed-book. Scored per book, never
                 averaged: the useful output is a list of which works
                 justify the pipeline, not a mean that hides them.
  entailment     The check Pass-7 was not making. Stratified, multi_step
                 exhaustive, and it emits a suggested floor so
                 LOREGRAPH_COVE_SUPPORTED_FLOOR can be set on a measured
                 distribution rather than a guess.
  gaps           Every [GAPS] bullet against the source.
  graph          Twenty analyst questions, answered by traversal alone.

`graph` and `gaps` need no model or credentials and produce numbers now.
The other three fall back to a dry preview that prints exactly what would
be sent, so the battery can be inspected before anything is spent.

Two of these evals caught their own first version being useless, which is
recorded in the code because it is the point:

- `graph` scored 20/20 on both books while returning "her brother's Latin
  Grammar -> the Mouse" for "which characters come into conflict".
  "Returns something" is not a threshold. It now also scores whether the
  endpoints are the kind of thing the question asked for: 78% on alice,
  91% on xyj, and the misses are real extraction defects — an Object as
  the aggressor in a fight, 倒身下拜 ("prostrating oneself") as a
  character present from chapter 1 to 100.
- `perturbation`'s possession swap renamed the holder globally: 464
  occurrences of "Alice" became "the Queen", leaving a text about nobody.
  It now rewrites the one passage, and reports any probe where the
  original wording survives, since a leaky probe can be answered from the
  leftovers instead of by reading.

`gaps` independently found the Pass-8 defect: Alice's note claims whether
her adventures were a dream "is not resolved in the provided text", and
"dream" and "reality" both occur in chapter 12 — which the note never
read. On xyj, 267 of 599 profiled entities were profiled without citing
every chapter they appear in.

Every result carries a `skipped` list and the renderer always prints it.
An eval that silently scores the subset it could reach is worse than none,
because the number looks like coverage.
…hange

The spec described Pass-7's literal-match rate as the hard gate, which is
how it came to be trusted as a quality figure. It is an invariant Pass-2/5/6
already guarantee. Name both numbers, say which one measures anything, and
document the eval harness as the way to check an extraction change rather
than counting rows.
The literal-gate test asserted the message contained "gate fails". The
message now explains that the literal check is an invariant tripwire and
not a quality figure — which is the point of the change, since taking
that number for a quality score is how it came to be trusted as one — so
the phrase went and CI went red on the wording, not the behaviour.

Assert on what the message has to convey instead, and cover what was
never covered: the entailment gate had no test at all. Added, in both
tiers.

- Integration: the entailment gate aborts on literal-perfect spans the
  judge rejects (exactly the run the old single-gate Pass-7 waved
  through); `supported_floor=0` records without enforcing; per-stratum
  counts reach the stats.
- Unit: the same three paths plus the floor boundary and settings
  defaults, with no database. A gate this load-bearing should not be
  reachable only through a Postgres testcontainer.
…graph

Ran the harness against DeepSeek for the first time. All three model-backed
evals had a scoring bug of the same shape as the Pass-7 tautology this PR
set out to fix — the eval graded its own output, or graded a window that
excluded the evidence.

1. entailment reported 35.8%. It sends the judge `chunk_text[:1500]`, but
   Alice's chunks run to ~4,800 characters, so the evidence span usually
   fell past the cut and the judge correctly answered "that quote is not in
   the passage you gave me". The same claim was judged both ways in one
   run depending on where its span landed. Centre the window on the span
   instead: 0/120 samples now fall outside it, and the real rate is 85.0%.

2. contamination reported the graph at 12/12. `ground_truth` was a
   restatement of `graph_answer`, so the judge was asked whether an answer
   matched itself. Grade both arms against the source passage: closed-book
   0/12, graph 5/12.

3. perturbation scored two plainly correct answers as reversions —
   "Pellwyn is a curious young girl who follows a White Rabbit down a
   rabbit-hole" was marked as having reverted to the published text. Cause:
   an instruction was being fed into the answer-scoring judge's ground-truth
   slot. Gave reversion and entailment their own judges and prompts rather
   than borrowing one built for a different question.

Also: `LOREGRAPH_COVE_SUPPORTED_FLOOR` drops 0.85 -> 0.80. The 0.85 was a
guess, and the measurement landed exactly on it — it would have aborted
every run. 0.80 is alice's measured rate less a 5-point margin, and the
comment says so, along with the fact that one book is not a distribution.

Spend for the whole session, including the three wasted runs: 0.65 CNY.
`model_arm.SPEND` now accumulates across every arm and judge so the figure
comes out of the harness rather than the provider dashboard.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 5, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
loregraph 721168d Commit Preview URL

Branch Preview URL
Aug 05 2026, 02:30 AM

@YunyueLi

YunyueLi commented Aug 5, 2026

Copy link
Copy Markdown
Owner Author

分支基线错了(#18 是压缩合并),重开在 #20

@YunyueLi YunyueLi closed this Aug 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant