textmachine/eval/role_topology/route_arm.py

150 lines
9.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
"""Ф2б, армы E и G — обратная связка и маршрутизация. Обе топологии считаются на ОДНОМ материале.
E ОБРАТНАЯ СВЯЗКА: сильный черновик (однопроходка `deepseek-v4-pro`, арм D фазы 2а) → дешёвый
фиксер по флагу. Проверяет прямо противоположную нынешней ставку: не «дешёвый черновик +
дорогой редактор», а «дорогой черновик + дешёвая добивка».
G МАРШРУТИЗАЦИЯ: дешёвый черновик → детерминированные гейты скорят КАЖДУЮ единицу → стиль-пасс
уходит ТОЛЬКО флагнутым. Проверяет, можно ли не платить за переписывание там, где переписывать
нечего.
Почему обе считаются здесь, а не по отдельности. Ф2а показала, что связка «черновик + редактор»
доминируется однопроходкой по цене (0.80×) и не выигрывает у неё по качеству. Значит вопрос
сместился: не «нужен ли второй проход», а «можно ли платить за него ИЗБИРАТЕЛЬНО». E и G — два
разных способа платить избирательно, и сравнивать их надо с уже намеренными A, D и F.
⚠ Форма арма G исправлена против буквы промта по результату калибровки §2.5, и это объявлено
там же. Промт предписывал «принимать правку, только если внешний гейт видит улучшение»; замер
на 91 реальной правке показал, что гейт улучшения НЕ ВИДИТ (47%, p=0.675) при том, что порчу
видит решительно. Правило в исходной форме отсекало бы половину полезной работы по причине,
которую гейт измерить не в состоянии. Рабочая форма обратная: правка принимается по умолчанию,
отвергается только при УХУДШЕНИИ сверх порога.
Запуск: eval/.venv/bin/python eval/role_topology/route_arm.py --plan # что флагается, $0
eval/.venv/bin/python eval/role_topology/route_arm.py
"""
from __future__ import annotations
import json
import os
import statistics
import sys
import time
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"))
sys.path.insert(0, str(REPO / "eval" / "editor_harness"))
sys.path.insert(0, str(REPO / "eval" / "role_topology"))
from dotenv import load_dotenv # noqa: E402
from openai import OpenAI # noqa: E402
import bakeoff as BO # noqa: E402
import battery as BAT # noqa: E402
import detect_word as DW # noqa: E402
import repair_arm as RA # noqa: E402
from prices import CANDIDATES # noqa: E402
load_dotenv(REPO / "eval" / ".env")
OUT = Path.home() / "books" / "role-topology"
CEILING_USD_МЁРТВ = None # ⚠ объявлен и НИКЕМ не читается;
# оставлен видимым маркером: охраны здесь нет, покупки идут мимо кассы (ревью 07.08)
DRAFT_COST = 0.00195 # медиана flash(low) на переводческой роли, замер §2.7
EDITOR = "deepseek-v4-pro"
def gate_flags(source: str, text: str, bank: frozenset) -> list[str]:
"""ДЕТЕРМИНИРОВАННЫЙ гейт единицы: что в ней объективно не так. Ровно те классы, которые
батарея Ф0.5 умеет находить БЕЗ судьи и без модели — иначе маршрутизация стоила бы денег."""
why = []
r = BAT.run_unit(BAT.Unit(source=source, draft="", final=text))
if r["cjk_leak"]["han_chars"]:
why.append(f"утечка иероглифов ({r['cjk_leak']['han_chars']})")
if r["typography"]["violations"]:
why.append(f"типографика ({r['typography']['violations']})")
if r["numbers"]["lost_n"] or r["numbers"]["invented_n"]:
why.append(f"величины ({r['numbers']['lost_n']}/+{r['numbers']['invented_n']})")
if r["palladius"]["nonconformant_text"]:
why.append(f"не-палладиевские имена ({r['palladius']['nonconformant_text']})")
bad = [w for w in BAT.words(text) if DW.verdict(w, bank)[0]]
if bad:
why.append(f"выдуманные слова ({len(bad)})")
return why
def main() -> None:
plan_only = "--plan" in sys.argv
bank = DW.load_bank()
us = BO.units()
print("ГЕЙТ-СКОРИНГ КАЖДОЙ ЕДИНИЦЫ (детерминированный, $0)\n")
print(f"{'ед.':4s}{'F черновик':>42s}{'D однопроходка':>42s}")
print("-" * 90)
flagged_F, flagged_D = [], []
for ui, u in enumerate(us):
f_why = gate_flags(u["source"], u["draft"], bank)
d_txt = BO.text_of("D", u, ui)
d_why = gate_flags(u["source"], d_txt, bank) if d_txt.strip() else ["нет выхода"]
if f_why:
flagged_F.append(ui)
if d_why:
flagged_D.append(ui)
print(f"u{ui:<3d}{('; '.join(f_why) or 'чисто'):>42s}{('; '.join(d_why) or 'чисто'):>42s}")
print(f"\nфлагнуто гейтом: черновик F {len(flagged_F)}/{len(us)} · "
f"однопроходка D {len(flagged_D)}/{len(us)}")
# ЭКОНОМИКА МАРШРУТИЗАЦИИ считается детерминированно, без единого вызова: цена единицы известна
# из замеров Ф2а, а доля флагнутых — из гейта выше.
ed = statistics.median(json.loads((OUT / f"bo-A-u{i}.json").read_text(encoding="utf-8"))
["cost_usd"] for i in range(len(us))
if (OUT / f"bo-A-u{i}.json").exists())
d_cost = statistics.median(json.loads((OUT / f"bo-D-u{i}.json").read_text(encoding="utf-8"))
["cost_usd"] for i in range(len(us))
if (OUT / f"bo-D-u{i}.json").exists())
share = len(flagged_F) / len(us)
print(f"\n{'топология':46s}{'$/единицу':>11s}{'против A':>10s}")
print("-" * 68)
rows = [
("A — черновик + редактор ВСЕГДА (боевая)", DRAFT_COST + ed),
("D — однопроходка сильной моделью", d_cost),
(f"G — черновик + редактор ТОЛЬКО флагнутым ({share:.0%})", DRAFT_COST + ed * share),
("E — однопроходка + дешёвая починка по флагу", d_cost + 0.000275 * 2),
("F — черновик как есть", DRAFT_COST),
]
base = rows[0][1]
for name, v in rows:
print(f"{name:46s}{v:11.5f}{v / base:9.2f}×")
if plan_only:
return
# АРМ E: сильный черновик прогоняется через те же реальные детекторы и чинится дешёвым фиксером.
cl = OpenAI(api_key=os.environ[CANDIDATES[RA.FIXER][1]], base_url=CANDIDATES[RA.FIXER][0],
timeout=300)
spent = 0.0
fixed = found = 0
for ui, u in enumerate(us):
d_txt = BO.text_of("D", u, ui)
if not d_txt.strip():
continue
hits = RA.detect(d_txt, bank)
found += len(hits)
for hi, h in enumerate(hits):
rec = RA.fix(cl, u["source"], h["sentence"], h["cls"], f"e-u{ui}-{hi}")
spent += rec["cost_usd"]
fixed += bool(rec["content"].strip())
time.sleep(0.15)
print(f"\nАРМ E: реальные детекторы нашли в сильном черновике {found} дефектов, "
f"починено {fixed}, потрачено ${spent:.6f}")
print("⇒ сравнивать с армом C: там на ДЕШЁВОМ черновике те же детекторы находили 8 из 8 "
"посаженных;\n здесь счёт идёт по ЕСТЕСТВЕННЫМ дефектам, и их число само есть "
"мера качества базы.")
(OUT / "route-arm.json").write_text(json.dumps(
dict(flagged_F=flagged_F, flagged_D=flagged_D, e_found=found, e_fixed=fixed,
e_cost=spent), ensure_ascii=False), encoding="utf-8")
if __name__ == "__main__":
main()