118 lines
6.4 KiB
Python
118 lines
6.4 KiB
Python
#!/usr/bin/env python3
|
||
"""Селф-ревью рига эксп-20 ДО платных вызовов (требование владельца 05.08).
|
||
|
||
Проверяется исполнением, а не глазами:
|
||
С1 ключи всех заявленных провайдеров присутствуют в окружении (значения НЕ печатаются);
|
||
С2 все четыре контракта рендерятся без незаполненных плейсхолдеров, и блок закона одинаков;
|
||
С3 `classify_ops` воспроизводит счёт отказов настоящего аппликатора на УЖЕ ОПЛАЧЕННОМ сырье
|
||
эксп-19 — иначе раунд починки вернёт модели не те операции;
|
||
С4 контракты действительно разные на проводе (иначе меряем один и тот же промпт дважды);
|
||
С5 гардрейл: попытка погасить мышление у DeepSeek обязана падать, а не тихо проходить.
|
||
"""
|
||
from __future__ import annotations
|
||
import json
|
||
import os
|
||
import re
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
REPO = Path("/home/ubuntu/projects/textmachine")
|
||
HERE = Path(__file__).resolve().parent
|
||
# ⚠ ПОРЯДОК ВАЖЕН (селф-ревью 05.08): в `eval/editor_contract/` лежит СВОЙ `probe.py`, и при
|
||
# обратном порядке вставок он затеняет наш — `import probe` подтягивал чужой модуль.
|
||
# HERE вставляется ПОСЛЕДНИМ, поэтому стоит первым в sys.path и выигрывает.
|
||
sys.path.insert(0, str(REPO / "eval" / "editor_contract"))
|
||
sys.path.insert(0, str(REPO / "eval" / "bank_arbitration"))
|
||
sys.path.insert(0, str(HERE))
|
||
|
||
import probe as Q # noqa: E402
|
||
import editor_wire_probe as P # noqa: E402
|
||
from apply import apply_ops # noqa: E402
|
||
from inject_probe import pick_windows # noqa: E402
|
||
|
||
OLD = 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 and not ok else ""))
|
||
|
||
|
||
def main() -> None:
|
||
# С1 — ключи (значения наружу не идут)
|
||
for m, spec in Q.MODELS.items():
|
||
check(f"С1 ключ {spec[1]} для {m}", bool(os.environ.get(spec[1])), "не найден в окружении")
|
||
|
||
wins = pick_windows()
|
||
_, buf, terms = wins[0]
|
||
draft = P.draft_of(0)
|
||
blk = P.editor_block(terms, P.HEADER_LAW_CLAUSE, None)
|
||
|
||
# С2 — рендер
|
||
sysmsgs = {}
|
||
for c in Q.CONTRACTS:
|
||
msgs = Q.messages(c, buf, draft, blk)
|
||
holes = set()
|
||
for mm in msgs:
|
||
holes |= set(re.findall(r"\{\{(\w+)\}\}", mm["content"]))
|
||
check(f"С2 {c}: плейсхолдеров нет", not holes, ", ".join(sorted(holes)))
|
||
check(f"С2 {c}: блок закона на месте", any("КАНОНИЧЕСКИЕ" in mm["content"] for mm in msgs))
|
||
sysmsgs[c] = msgs[0]["content"]
|
||
|
||
# С4 — контракты различны
|
||
uniq = len(set(sysmsgs.values()))
|
||
check("С4 все четыре контракта различны на проводе", uniq == len(Q.CONTRACTS),
|
||
f"уникальных системных промптов {uniq} из {len(Q.CONTRACTS)}")
|
||
for c, t in sysmsgs.items():
|
||
print(f" {c:13s} системный промпт {len(t):5d} зн.")
|
||
|
||
# С6 — секция FEWSHOT обязана дропаться (дефект 05.08, пойман в ходе прогона: арм `full`
|
||
# уехал с примерами перевёрстки, то есть база сравнения не была боевым контрактом).
|
||
fullsys = sysmsgs["full"]
|
||
check("С6 full: блок FEWSHOT дропнут, как в проде",
|
||
"---FEWSHOT---" not in fullsys and "Он вышел за дверь" not in fullsys)
|
||
check("С6 full: блок ДИСКУРС-ПЕРЕВЁРСТКИ на месте", "ДИСКУРС-ПЕРЕВЁРСТКА" in fullsys)
|
||
check("С6 full: длина совпала с проводом эксп-19 (3071 зн.)", len(fullsys) == 3071,
|
||
f"сейчас {len(fullsys)}")
|
||
|
||
# С3 — классификатор против настоящего аппликатора на оплаченном сырье
|
||
tot = mism = seen = 0
|
||
for k in range(6):
|
||
for r in (1, 2, 3):
|
||
f = OLD / f"ec-D1-w{k}-r{r}.json"
|
||
if not f.exists():
|
||
continue
|
||
reply = json.loads(f.read_text(encoding="utf-8"))["content"]
|
||
d = P.draft_of(k)
|
||
res = apply_ops(d, reply)
|
||
mine = Q.classify_ops(d, reply)
|
||
seen += 1
|
||
# аппликатор считает отказы notfound+ambiguous; мой классификатор — те же классы
|
||
ref = res.rejected_notfound + res.rejected_ambiguous
|
||
got = sum(1 for _, _, st in mine if st in ("reject_notfound", "reject_ambiguous"))
|
||
tot += ref
|
||
if ref != got:
|
||
mism += 1
|
||
print(f" ⚠ {f.stem}: аппликатор {ref}, классификатор {got}")
|
||
if len(mine) != res.ops_total and not res.malformed:
|
||
mism += 1
|
||
print(f" ⚠ {f.stem}: операций аппликатор {res.ops_total}, классификатор {len(mine)}")
|
||
check(f"С3 классификатор совпал с аппликатором на {seen} вызовах эксп-19 (отказов в сырье {tot})",
|
||
mism == 0, f"расхождений {mism}")
|
||
|
||
# С5 — гардрейл
|
||
try:
|
||
Q.call("deepseek-v4-pro", [{"role": "user", "content": "x"}], "selfcheck-guard", True)
|
||
check("С5 гашение мышления у DeepSeek запрещено", False, "вызов ПРОШЁЛ — гардрейл дырявый")
|
||
except SystemExit:
|
||
check("С5 гашение мышления у DeepSeek запрещено", True, "падает, как и должно")
|
||
|
||
print(f"\n{'РИГ ГОТОВ' if not FAIL else f'ПРОВАЛОВ: {FAIL}'}")
|
||
sys.exit(1 if FAIL else 0)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|