textmachine/eval/premise_review/dryrun_arms.py

119 lines
6.4 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.

#!/usr/bin/env python3
"""Сухая проверка ПОСЫЛКИ четырёх армов: что реально уедет на провод. Ни одного платного вызова.
Инварианты, которые обязаны держаться (иначе арм измерит не то, что заявлено):
И1 диффовый и полный контракты одного арма получают ОДИН И ТОТ ЖЕ блок закона и ОДИН И ТОТ ЖЕ
черновик — различаться должен ТОЛЬКО системный контракт, иначе сравнение не контролировано;
И2 ни в одном сообщении не осталось незаполненных `{{плейсхолдеров}}`;
И3 у арма E инструкции и черновик АНГЛИЙСКИЕ (латиница > кириллицы) — иначе повтор дефекта 05.08;
И4 у арма L reasoning_effort='low' стоит ровно на обоих контрактах, и больше нигде;
E5 у арма N системный контракт НЕ содержит стиль-мандата, а у арма D (проба 19) — содержит.
"""
from __future__ import annotations
import re
import sys
from pathlib import Path
REPO = Path("/home/ubuntu/projects/textmachine")
sys.path.insert(0, str(REPO / "eval" / "editor_contract"))
sys.path.insert(0, str(REPO / "eval" / "bank_arbitration"))
import final_probes as F # noqa: E402
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 cyr_lat(s: str) -> tuple[int, int]:
return (sum(1 for c in s if "а" <= c.lower() <= "я"),
sum(1 for c in s if "a" <= c.lower() <= "z"))
def plan_of(arm: str):
if arm == "L":
return F.plan_L()
if arm == "N":
return F.plan_N()
if arm == "G":
return F.plan_G({f"G{u}": "ЧЕРНОВИК-G" + str(u) for u in range(2)})
return F.plan_E({f"E{k}": "DRAFT-E" + str(k) for k in range(6)})
def main() -> None:
for arm in ("L", "N", "G", "E"):
plan = plan_of(arm)
print(f"\n=== арм {arm}: {len(plan)} вызовов ===")
tags = [p[0] for p in plan]
check("теги уникальны", len(set(tags)) == len(tags))
# И2 — плейсхолдеры
holes = set()
for tag, msgs, kind, draft, extra in plan:
for m in msgs:
holes |= set(re.findall(r"\{\{(\w+)\}\}", m["content"]))
check("И2 нет незаполненных плейсхолдеров", not holes, ", ".join(sorted(holes)))
# И1 — парность diff/full на одном материале
bykey = {}
for tag, msgs, kind, draft, extra in plan:
key = re.sub(r"^fp-.?.?", "", tag)
bykey.setdefault(key, {})[kind] = (msgs, draft)
pairs = [v for v in bykey.values() if len(v) == 2]
if pairs:
same_block = all(
[m["content"] for m in v["diff"][0] if m["role"] == "system"][1:]
== [m["content"] for m in v["full"][0] if m["role"] == "system"][1:]
for v in pairs)
same_draft = all(v["diff"][1] == v["full"][1] for v in pairs)
differ_sys = all(v["diff"][0][0]["content"] != v["full"][0][0]["content"] for v in pairs)
check(f"И1 блок закона одинаков в diff/full ({len(pairs)} пар)", same_block)
check("И1 черновик одинаков в diff/full", same_draft)
check("И1 системный контракт РАЗЛИЧАЕТСЯ", differ_sys)
else:
check("И1 арм одноконтрактный (пар нет)", arm == "N", "N — только дифф, это по дизайну")
# И4 — режим разума
efforts = {str(p[4]) for p in plan}
if arm == "L":
check("И4 reasoning_effort=low на всех вызовах",
efforts == {"{'reasoning_effort': 'low'}"}, str(efforts))
else:
check("И4 режим разума не задаётся (дефолт)", efforts == {"None"}, str(efforts))
# И3 — язык арма E
if arm == "E":
sysmsg = plan[0][1][0]["content"]
c, l = cyr_lat(sysmsg)
check("И3 инструкции английские", l > c, f"лат={l} кир={c}")
blk = plan[0][1][1]["content"]
c2, l2 = cyr_lat(blk)
check("И3 блок закона английский", l2 > c2, f"лат={l2} кир={c2}")
# E5 — стиль-мандат. ⚠ Подстрочный регекс `стил|благозвуч` здесь НЕ работает: fidelity-промпт
# называет стиль в ЗАПРЕТЕ («Стиль… НЕ ТРОГАЙ ВООБЩЕ»), и наивная проверка считала запрет
# мандатом. Отличаем по полярности: у N обязан быть явный запрет и сужение мандата.
sysmsg = plan[0][1][0]["content"]
if arm == "N":
forbids = bool(re.search(r"(стил|благозвуч|лексик)[^.]{0,80}не трогай", sysmsg, re.I))
narrow = bool(re.search(r"мандат\s+узкий|только\s+ВЕРНОСТЬ", sysmsg, re.I))
full_sys = [p[1][0]["content"] for p in F.plan_L() if p[2] == "full"][0]
positive_elsewhere = bool(re.search(r"Правь стиль", full_sys))
check("E5 у N стиль назван ЗАПРЕТОМ", forbids)
check("E5 у N мандат сужен до верности", narrow)
check("E5 у полного контракта стиль-мандат ЕСТЬ (контраст)", positive_elsewhere)
print(f" системный контракт, первая строка: {sysmsg.splitlines()[0][:100]!r}")
print(f" сообщений в вызове: {len(plan[0][1])} (роли: "
f"{[m['role'] for m in plan[0][1]]})")
print(f"\n{'ПОСЫЛКА ЧИСТА' if not FAIL else f'ПРОВАЛОВ: {FAIL}'}")
sys.exit(1 if FAIL else 0)
if __name__ == "__main__":
main()