Skip to content

fix: 消除对抗式复核的反驳过度 (A 保留默认 + B 视角分化 + C 全票否决)#15

Merged
NeverENG merged 4 commits into
mainfrom
fix/verify-anti-over-refute
May 31, 2026
Merged

fix: 消除对抗式复核的反驳过度 (A 保留默认 + B 视角分化 + C 全票否决)#15
NeverENG merged 4 commits into
mainfrom
fix/verify-anti-over-refute

Conversation

@NeverENG

Copy link
Copy Markdown
Owner

背景

PR #14 的评测暴露了一个灾难:在弱反驳者模型(DeepSeek)上,对抗式复核把所有真 finding 都误杀了(召回 100%→0%,F1→0,token +173%)。根因是反驳者提示词"存疑即否" + N 个同质反驳者齐声否决。

⚠️ 栈在 #13(generalFindings)/ #14(评测脚手架)之上,建议依次合入。

修复(三件事)

  • A 保留默认:反驳者只有能指出具体证据才否决,"拿不准"一律保留——反转旧的"不确定⇒否决"偏向。
  • B 视角分化:3 个反驳者各侧重一个角度(diff 依据 / 是否误读 / 能否发生)但都做整体判定。关键:是整体判定而非"只在某轴否决"——后者会和 C 抵消成空操作(§六.3)。
  • C 全票才删:只有所有反驳者一致否决才丢弃,一个辩护者就能救下一条 finding。

重跑验证(DeepSeek,诚实)

旧复核器 新复核器
对真 finding 全部误杀(召回 100%→0%) 不误杀(召回 33.3%→33.3%,#43 存活)
F1(baseline→优化) 35.3% → 0% 22.2% → 40.0%
token +173% +5%

已证实:over-refutation 消除——#43 的真并发 finding 在复核后存活,旧版会杀掉它。

诚实披露的混淆(写进报告,不粉饰):

  • 精确率提升部分是假象——#35 的"改善"实为 DeepSeek 解析失败(降级 0 finding),非复核所为。
  • A/B 把"复核效果"与 DeepSeek 生成随机性混在一起(两配置各自重新生成)。下一步:改成"生成一次→对同一批复核/不复核"以净化归因。
  • DeepSeek 解析失败 3/12(#58 ×2 是召回锚点,导致布隆案从未评分)。再次印证"真实评测须用 Opus"。

详见 docs/评测报告.md 的"结论与诚实解读 / 修复前后对比 / 局限"。

104 测试全绿(新增 A 的 keep-default 检查、B 的视角分化检查;C 的全票语义替换旧多数否决测试)。

🤖 Generated with Claude Code

NeverENG and others added 4 commits May 31, 2026 10:55
…eralFindings)

Architecture findings are BanGD's differentiator, but emitting only those
cedes ordinary bugs (off-by-one, swallowed errors, nil deref, races) to
Copilot. Add a second result class, generalFindings: concrete diff-evidenced
correctness/logic defects, lightweight (no four-段式), filed inline in the PR
comment (not as tracked issues).

- schema: GeneralFindingSchema + generalFindings (.default([]) for graceful
  degradation), kept in sync with the tool JSON schema by a test
- prompt/system-prompt: 4th output part + quality red-lines (diff-evidenced
  only, no style nits, no dup of architecture findings, <=6, [] when none)
- verify: generalize VerifyOutcome<T>/verifyItems<T>; verifyGeneralFindings
  runs the SAME adversarial majority-refute pass (refuter prompt reframed to
  "is this a real correctness defect")
- review: verify both finding kinds in parallel; ReviewOutcome.droppedGeneralFindings
- format: render general findings inline; omit the section when empty
- action(.yml): general_finding_count output; dropped count covers both kinds
- DESIGN.md §七: why both classes coexist, structural/delivery differences
- tests: rendering, verification, schema-sync, default-omit coverage

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR #13 shipped the generalFindings niche described in the system prompt but
with no worked example, while every architecture dimension has one. A wrong-or-
absent few-shot is the single biggest quality lever per CLAUDE.md, so add the
missing exemplar.

- prompts/examples/general-findings.md: a worked example (binary-search off-by-
  one that infinite-loops) showing the 7-field generalFinding shape AND, just as
  important, the red-line — what NOT to report (style/naming nits, "add a test",
  unfounded speculation, dup of an architecture finding), plus the boundary vs
  architecture findings.
- prompt.ts: assembleSystemPrompt takes an always-on generalExample, appended in
  its own block regardless of selected dimensions (generalFindings is requested
  on every review). Architecture examples relabeled "架构级 Few-shot 范例".
- prompts.ts: PromptTexts.generalExample, loaded unconditionally (not dimension-
  gated); lives in the prompt-cached system block so marginal token cost ~= nil.
- DESIGN.md §七: note the niche now ships with its own few-shot (parity with the
  per-dimension architecture examples).
- tests: new prompt.test.ts (assembleSystemPrompt always-on behavior + user
  prompt parts); prompts.test.ts loads + red-line check; review.test.ts asserts
  the exemplar is present regardless of dimension selection.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…DESIGN §六.2)

Builds the measurement scaffolding that turns "feels accurate" into
precision/recall, and runs the first real A/B. The corpus is 6 REAL BanDB PRs
(eval/cases.json, frozen from GitHub), labels anchored on author intent or a
maintainer-confirmed fix (e.g. #58's crash-consistency + Stat→Seek race, which
BanGD flagged and the maintainer fixed in #63). PR bodies are neutralized so the
model detects from code, not from a body that states the answer.

- src/eval/score.ts: pure precision/recall/F1 scorer; fuzzy match on basename +
  accepted type/category, one-to-one greedy. Fully unit-tested (13 tests).
- src/eval/corpus.ts + eval/cases.json + eval/build-corpus.mjs: the frozen
  real-PR corpus and its (re-runnable) builder.
- src/eval/run.ts: single-variable A/B (adversarial verify off vs on, all else
  held constant, fresh client per config to isolate tokens); writes
  docs/评测报告.md. Key from ANTHROPIC_API_KEY/DEEPSEEK_API_KEY or the gitignored
  eval/.apikey; npm run eval.
- docs/评测报告.md: the first run (DeepSeek). Honest, counterintuitive result —
  on the cheap model the adversarial refuters OVER-refute: recall 100%→0% (true
  findings killed), F1 35.3%→0%, +173% tokens. Empirically backs DESIGN §二
  (model choice is the divide) and motivates §六.3 (perspective-diverse refuters
  instead of homogeneous ones). Real accuracy must be re-run on Opus.

Stacks on the generalFindings branch (eval scores result.generalFindings too).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…findings)

The first eval run (docs/评测报告.md) exposed a disaster: on a weak refuter
model the verification pass deleted EVERY true finding (recall 100%→0%, F1→0,
+173% tokens). Root cause: the refuter prompt said "default to refuted when
evidence is thin", and N identical refuters echoed the same over-refute. Fix:

- (A) keep-by-default: a refuter rejects a finding only when it can cite concrete
  evidence it is wrong; "unsure" keeps it. Inverts the old uncertain⇒refute bias.
- (B) perspective-diverse lenses: each refuter scrutinizes one angle hardest
  (diff-grounding / misread / can-it-happen) but returns a HOLISTIC verdict, so N
  refuters decorrelate. (Holistic, not per-axis — per-axis refuting would let a
  one-axis FP survive unanimity; DESIGN §六.3.)
- (C) unanimous-to-drop: a finding is dropped only if every refuter rejects it —
  one defender keeps it. Makes killing a real finding hard.

Re-run on DeepSeek confirms the fix: recall held 33.3%→33.3% (the true finding
#43 survived verification, vs being killed before); F1 22.2%→40%; +5% tokens.
The report is honest about confounds: part of the apparent precision gain is a
DeepSeek parse-error artifact (#35) and the A/B confounds verify-effect with
generation non-determinism — the clean claim is "over-refute eliminated". Also
surfaced: DeepSeek returned unparseable output 3/12 times (#58 ×2, #35 ×1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@NeverENG NeverENG merged commit 603422a into main May 31, 2026
1 check passed
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