textmachine/eval/premise_review/verify_run.py

113 lines
6.2 KiB
Python
Raw Permalink 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.

#!/usr/bin/env python3
"""Проверка, что ПРОГОН прошёл правильно (владелец 05.08: «после прогона понять, что он прошёл верно»).
Проверяется не смысл результатов, а их санитария — то, что молча портит выводы:
П1 множество тегов ровно то, что запланировано: ни пропусков, ни лишних (пере-прогон чужого);
П2 ни одного обрыва по потолку вывода (`finish` ≠ stop) — обрыв даёт «модель не нашла правок»;
П3 модель и режим на проводе те, что заявлены армом (L — low, остальные — дефолт);
П4 у КАЖДОГО диффового вызова есть файл применения, и применение не молчит об отказах;
П5 деньги сходятся двумя путями: сумма записей против напечатанного скриптом итога;
П6 дифф-выходы не выродились: пустой/односимвольный ответ ≠ «NO_CHANGE по делу».
"""
from __future__ import annotations
import json
import sys
from collections import Counter
from pathlib import Path
RAW = Path.home() / "books" / "gu-zhenren" / "bank-arbitration"
FAIL = 0
def check(name: str, ok: bool, note: str = "") -> None:
global FAIL
if not ok:
FAIL += 1
print(f"[{'OK ' if ok else 'ПРОВАЛ'}] {name}" + (f"{note}" if note else ""))
def expected() -> set[str]:
t = set()
for k in range(6):
for r in (1, 2, 3):
t |= {f"fp-LD-w{k}-r{r}", f"fp-LF-w{k}-r{r}", f"fp-N-w{k}-r{r}"}
for r in (1, 2):
t |= {f"fp-ED-w{k}-r{r}", f"fp-EF-w{k}-r{r}"}
for u in range(2):
for r in (1, 2, 3):
t |= {f"fp-GD-u{u}-r{r}", f"fp-GF-u{u}-r{r}"}
return t
def main() -> None:
recs = {}
for f in RAW.glob("fp-*.json"):
if f.name.endswith(".applied.json") or f.name.startswith("fp-draft"):
continue
recs[f.stem] = json.loads(f.read_text(encoding="utf-8"))
exp = expected()
got = set(recs)
check("П1 все запланированные вызовы есть", not (exp - got),
f"нет: {sorted(exp - got)[:8]}" if exp - got else f"{len(got)} шт.")
check("П1 лишних вызовов нет", not (got - exp), f"лишние: {sorted(got - exp)[:8]}")
fin = Counter(r["finish"] for r in recs.values())
check("П2 нет обрывов по потолку вывода", set(fin) == {"stop"}, str(dict(fin)))
models = Counter(r.get("model_returned", "?") for r in recs.values())
check("П3 модель одна на всех вызовах", len(models) == 1, str(dict(models)))
# П3а — режим НА ПРОВОДЕ, прямо из артефакта. ⚠ Инструмент добавлен по адверсариальному
# ревью 05.08: строка П3а стояла в отчёте как выполненная проверка, а кода под ней не было.
want = {t: ('{"reasoning_effort": "low"}' if t.startswith(("fp-LD", "fp-LF")) else "{}")
for t in recs}
bad = {t: json.dumps(recs[t].get("extra_body"), ensure_ascii=False)
for t in recs
if json.dumps(recs[t].get("extra_body"), ensure_ascii=False) != want[t]}
check("П3а режим на проводе совпадает с заявленным армом", not bad,
f"расходится у {len(bad)}: {list(bad)[:5]}" if bad
else f"low на {sum(1 for t in want.values() if 'low' in t)} · {{}} на "
f"{sum(1 for t in want.values() if t == '{}')}")
# ⚠ П3а проверяет, что параметр УШЁЛ, но НЕ что он ПОДЕЙСТВОВАЛ. Отличимость `low` от дефолта
# на этой модели пере-счётом 05.08 НЕ подтверждена (отчёт 19 §6.2) — держать это в виду.
print(" ⚠ уход параметра ≠ его действие: отличимость low от дефолта не установлена")
diffs = [t for t in recs if t.startswith(("fp-LD", "fp-GD", "fp-ED", "fp-N-"))]
noapp = [t for t in diffs if not (RAW / f"{t}.applied.json").exists()]
check("П4 у каждого диффа есть файл применения", not noapp, f"нет у: {noapp[:6]}")
rej = Counter()
for t in diffs:
f = RAW / f"{t}.applied.json"
if f.exists():
a = json.loads(f.read_text(encoding="utf-8"))
for k in ("rejected_notfound", "rejected_ambiguous", "rejected_overlap",
"rejected_empty", "malformed"):
rej[k] += a[k]
print(f" отказы аппликатора по всем диффам: {dict(rej)}")
tot = sum(r["cost_usd"] for r in recs.values())
drafts = sum(json.loads(f.read_text(encoding="utf-8"))["cost_usd"]
for f in RAW.glob("fp-draft-*.json"))
print(f" деньги: вызовы ${tot:.6f} + черновики ${drafts:.6f} = ${tot + drafts:.6f}")
check("П5 итог сошёлся с напечатанным скриптом ($0.349566)",
abs(tot + drafts - 0.349566) < 1e-5, f"расхождение {tot + drafts - 0.349566:+.6f}")
tiny = [(t, len(recs[t]["content"])) for t in diffs if len(recs[t]["content"]) < 20]
nc = []
for t, _ in tiny:
a = json.loads((RAW / f"{t}.applied.json").read_text(encoding="utf-8"))
nc.append((t, a["no_change"]))
check("П6 короткие дифф-ответы объяснены как NO_CHANGE",
all(v for _, v in nc), f"короткие БЕЗ no_change: {[t for t, v in nc if not v]}")
if nc:
print(f" коротких дифф-ответов {len(nc)}, из них NO_CHANGE {sum(v for _, v in nc)}")
print(f"\n{'ПРОГОН ЧИСТ' if not FAIL else f'ПРОВАЛОВ: {FAIL}'}")
sys.exit(1 if FAIL else 0)
if __name__ == "__main__":
main()