lili 882ca3f357 feat(cheap-gen): W-AXIS 波2 判定语义重置——独立模型玩法判定阻断+拆残笼+判定档切 M3
裁定三落地:accepted = 机械预筛 ∧ 独立模型玩法判定,九门 pass 降为「未见明显死」预筛。

- 波2a 玩法判定器(cheap_verify.py):读 brief+run-summary+全程截图(含新增局中连拍
  midplay-1..N,play.cdp.cjs 只读并发拍、不进任何门判据),布尔裁 broken/hollow/off_brief,
  fail-closed;判定真相层落 evidence/judge.json + verdict.json 写回 accepted/judge 段;
  金标夹具 fixtures/judge-golden/ + judge_golden.py 校准跑批
- 波2b 拆残笼:check 词法 tokenizer 替换 stripCode 正则(字符串内 // 假阴根修,292 在册
  源零差异);prompt v1.6.3 外科清洗(两层奖励→纯设计语/删盲驱动器围设计/删熔断恐吓),
  registry 热取==内置逐字节一致(cheap_roles 内置回退同步);edit_file 空白归一+近似片段提示
- 判定器补修:空输出有界重试一次(glm 思考吃光 max_tokens 时 content 空)+重试放大公式
  反缩 bug 修(旧 min(×2,6000) 大 base 反缩)+finishReason 落盘;imagesSeen 模型自报+
  盲检微扰重掷、两掷均盲=仪器故障 degraded(闲鱼中转静默丢图实锤,不再错杀 hollow)
- 判定档切 MiniMax-M3(创始人 2026-07-10 拍板,裁定三①边界随 v2 plan 修订):
  generation.yaml judge.model+max_tokens=202752(512K 网关实探 400 拒,取实探上限);
  3 例盲案 M3 重判全 accept、¥0.026/局
- 三路消费对齐:CLI 预筛语义修(cheap_studio 喂 verdict.pass 而非 finished,违裁定三的
  放行已纠)/Service 路 apply_gameplay_judge 显式 prefilter 参数/result_out 权威改读
  accepted(无键回落预筛,旧产物兼容)+trace.gameplayJudge additive 透传;
  tier2 gate_judge/genconfig 同步判定配置与消费

测试:test_gameplay_judge 33 项新增,全仓 pytest 480 绿;真判定闭环 accept ¥0.061。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 04:27:18 -07:00

119 lines
5.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""judge_golden.py — 玩法地板判定的金标校准包读取器 + 复验脚本(W-AXIS 波2 · 裁定三校准纪律)。
这份解决什么:判定器自己会假阳假阴,裁定三③钉死靠【每品类金标正反例复验 + 创始人抽玩定标】治。本模块是那套
校准纪律的工程落点——把金标语料(fixtures/judge-golden/manifest.json)读进来,对【已标注】的金标跑一次判定、
报与标注不符的漂移(rubric 规范三同构:漂移超线先复采样再回退)。
现状(2026-07-09):五份审计在册的 score-only pass 样本登记为【待标注种子】,expectReject 全为 null——
标注归创始人/主会话亲玩定标,本模块【不预设它们是反例】(双评审明令)。故 check 现在会报「0 已标注金标、
5 待标注」;等亲玩把 expectReject 填上,check 才真正复验漂移。
用法:
cheap-worker/.venv/bin/python cheap-worker/judge_golden.py list # 列金标清单(标注状态)
cheap-worker/.venv/bin/python cheap-worker/judge_golden.py check # 对已标注金标真跑判定、报漂移(真花判定¥)
load_golden() 是纯函数(读 manifest、解析、把 evidenceDir 解析成绝对路径),可单测、零网络。
"""
import json
import sys
from pathlib import Path
_REPO_ROOT = Path(__file__).resolve().parents[1]
_MANIFEST = Path(__file__).resolve().parent / "fixtures" / "judge-golden" / "manifest.json"
def load_golden(manifest_path=None) -> dict:
"""读金标 manifest → {schemaVersion, samples:[{gid, brief, evidenceDir(绝对), expectReject, rejectClasses, ...}]}。
纯函数:把每条 evidenceDir(相对仓根)解析成绝对路径 + 附 evidenceExists 标记(证据在不在盘上)。
manifest 缺失/坏 → 返回 {"schemaVersion": None, "samples": []}(绝不抛,列不出就是空清单)。
"""
p = Path(manifest_path) if manifest_path else _MANIFEST
try:
data = json.loads(p.read_text(encoding="utf-8"))
except Exception as e: # noqa: BLE001 读不到/坏 → 空清单
print(f"[judge-golden] manifest 读取失败(按空清单):{type(e).__name__}: {e}", file=sys.stderr)
return {"schemaVersion": None, "samples": []}
out = {"schemaVersion": data.get("schemaVersion"), "_note": data.get("_note"), "samples": []}
for s in (data.get("samples") or []):
if not isinstance(s, dict) or not s.get("gid"):
continue
ev_rel = s.get("evidenceDir") or ""
ev_abs = (_REPO_ROOT / ev_rel) if ev_rel else None
out["samples"].append({
"gid": s.get("gid"),
"brief": s.get("brief") or "",
"evidenceDir": str(ev_abs) if ev_abs else None,
"evidenceExists": bool(ev_abs and ev_abs.is_dir()),
"expectReject": s.get("expectReject"), # True/False/None(None=未标注)
"rejectClasses": s.get("rejectClasses") or [], # 标注为拒时应命中的类
"labeledBy": s.get("labeledBy"),
"labelNote": s.get("labelNote"),
})
return out
def labeled_samples(golden: dict) -> list:
"""只取【已标注】(expectReject 非 None)的金标——金标复验只对已定标者跑,未标注种子不参与漂移判定。"""
return [s for s in (golden.get("samples") or []) if s.get("expectReject") is not None]
def _cmd_list() -> int:
g = load_golden()
samples = g.get("samples") or []
labeled = labeled_samples(g)
print(f"金标清单(schemaVersion={g.get('schemaVersion')}):{len(samples)} 份,已标注 {len(labeled)} 份,"
f"待标注 {len(samples) - len(labeled)}\n")
for s in samples:
state = "待标注" if s["expectReject"] is None else (
f"应拒[{','.join(s['rejectClasses']) or '?'}]" if s["expectReject"] else "应过")
ev = "证据在盘" if s["evidenceExists"] else "⚠证据缺失"
print(f"· {s['gid']:<16} [{state}] {ev} brief={s['brief'][:36]}")
if not labeled:
print("\n(尚无已标注金标——五份为待标注种子,标注归创始人/主会话亲玩定标;check 暂无可复验项。)")
return 0
def _cmd_check() -> int:
"""对已标注金标真跑判定、报漂移(判定器判定 ≠ 金标标注即漂移)。真花判定¥。"""
import asyncio
sys.path.insert(0, str(Path(__file__).resolve().parent))
import cheap_verify # noqa: PLC0415
g = load_golden()
labeled = labeled_samples(g)
if not labeled:
print("无已标注金标可复验(五份待标注种子的 expectReject 均为 null;先由创始人亲玩定标再 check)。")
return 0
async def _run():
drift, total_cost = [], 0.0
for s in labeled:
r = await cheap_verify.judge_gameplay_floor(
s["gid"], brief=s["brief"], evidence_dir=s["evidenceDir"])
got_reject = not bool(r.get("accepted")) # 判定拒 = 未 accept
expect_reject = bool(s["expectReject"])
cost = r.get("costRmb")
if isinstance(cost, (int, float)):
total_cost += cost
ok = (got_reject == expect_reject)
tag = "一致" if ok else "★漂移"
print(f"· {s['gid']:<16} 金标={'' if expect_reject else ''} 判定={'' if got_reject else ''}"
f" rejectClasses={r.get('rejectClasses')} degraded={r.get('degraded')} ¥{cost} [{tag}]")
if not ok:
drift.append(s["gid"])
print(f"\n复验完成:{len(labeled)} 份已标注金标,漂移 {len(drift)}{(''+','.join(drift)+'') if drift else ''},"
f"判定总成本 ≈¥{round(total_cost, 4)}")
return 1 if drift else 0
return asyncio.run(_run())
if __name__ == "__main__":
cmd = sys.argv[1] if len(sys.argv) > 1 else "list"
if cmd == "check":
sys.exit(_cmd_check())
sys.exit(_cmd_list())