150 lines
9.2 KiB
Python
150 lines
9.2 KiB
Python
#!/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()
|