179 lines
9 KiB
Python
179 lines
9 KiB
Python
#!/usr/bin/env python3
|
||
"""ФАЗА A — СКРИН СИЛЬНОГО ТИРА. Платный (потолок $1.10), идёт через кассу пака.
|
||
|
||
Пороги НЕ свои: берутся у эксп-22 побайтно (`screen.PRICE_CAP/CYR_MIN/ECHO_MAX`, `gates`,
|
||
`verdict`). Порог, взятый из прошлого пака без правки, — единственная защита от подгонки состава
|
||
под желаемый результат, а состав здесь и есть предмет спора: пак существует потому, что эксп-22
|
||
срезал сильный тир.
|
||
|
||
⚠ ОСОБЫЙ РЕЖИМ `kimi-k3`, объявлен ДО покупок. По отношению «цена клетки / цена выхода» внутри
|
||
Kimi он ожидается ~$0.25/ед — вчетверо выше порога роли, — и `k2.6` уже отдал пустой выход на трёх
|
||
клетках из четырёх при 100% доли размышления. Полный скрин был бы покупкой предсказуемого провала
|
||
примерно за доллар. Берётся ОДНА клетка редакторской роли с жёстким потолком выхода: этого хватает
|
||
и на ценовой вердикт, и на второй замер отказного режима. Отдай он против ожидания годный дешёвый
|
||
выход — досняли бы полным скрином отдельным решением.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
REPO = Path("/home/ubuntu/projects/textmachine")
|
||
ZONE = Path(__file__).resolve().parent
|
||
sys.path.insert(0, str(REPO / "eval" / "tenant_panel"))
|
||
sys.path.insert(0, str(REPO / "eval" / "role_topology"))
|
||
|
||
def _load(name: str, path: Path):
|
||
"""⚠ Модули соседнего пака грузятся ПО ПУТИ, а не `import <имя>`. При прямом запуске своя
|
||
папка стоит в sys.path первой, и `import screen` находит ЭТОТ файл, а не скрин эксп-22 —
|
||
молчаливый само-импорт. Ловилось трижды подряд (ростер, касса, скрин), поэтому правило одно:
|
||
всё чужое — только явной загрузкой."""
|
||
import importlib.util # noqa: PLC0415
|
||
spec = importlib.util.spec_from_file_location(name, path)
|
||
mod = importlib.util.module_from_spec(spec)
|
||
sys.modules[name] = mod
|
||
spec.loader.exec_module(mod)
|
||
return mod
|
||
|
||
|
||
TP = REPO / "eval" / "tenant_panel"
|
||
S22 = _load("screen22", TP / "screen.py")
|
||
MONEY = _load("money_et", ZONE / "money.py")
|
||
ROSTER = MONEY.ROSTER_ET
|
||
|
||
PHASE = "ФA"
|
||
NEW = list(ROSTER.NEW) # gpt-5.6-terra · kimi-k3 · grok-4.5 · glm-5.2
|
||
SPECIAL = {"kimi-k3": dict(units=1, roles=("editor",), max_out=4000)}
|
||
ROLES = ("translator", "editor")
|
||
|
||
|
||
def client(model: str):
|
||
"""Соединение по РАСШИРЕННОЙ таблице. Клиент эксп-22 знает только свои 11 слагов и падает
|
||
KeyError на новом кандидате — тот же класс, что уронил его собственный скрин на середине."""
|
||
import os # noqa: PLC0415
|
||
|
||
from dotenv import load_dotenv # noqa: PLC0415
|
||
from openai import OpenAI # noqa: PLC0415
|
||
load_dotenv(REPO / "eval" / ".env")
|
||
base, env, *_ = ROSTER.CANDIDATES[model]
|
||
key = os.environ.get(env)
|
||
if not key:
|
||
raise SystemExit(f"нет ключа {env} для {model}")
|
||
return OpenAI(base_url=base, api_key=key, timeout=600.0, max_retries=1)
|
||
|
||
|
||
def tag(model: str, role: str, uid: str) -> str:
|
||
return f"et-scr-{model}-{role}-{uid[:6]}"
|
||
|
||
|
||
def plan() -> list[tuple[str, str, dict]]:
|
||
"""Что будет куплено. Печатается ДО покупок и лежит во фризе."""
|
||
us = S22.units() # те же две единицы, что скринил эксп-22
|
||
out = []
|
||
for m in ROSTER.by_new_price():
|
||
sp = SPECIAL.get(m, {})
|
||
for role in sp.get("roles", ROLES):
|
||
for u in us[:sp.get("units", len(us))]:
|
||
out.append((m, role, u))
|
||
return out
|
||
|
||
|
||
def cmd_plan() -> None:
|
||
tot = 0.0
|
||
print(f"{'модель':16s}{'роль':12s}{'ед.':>4s}{'ожидание $':>12s}")
|
||
for m in ROSTER.by_new_price():
|
||
sp = SPECIAL.get(m, {})
|
||
n = len(sp.get("roles", ROLES)) * sp.get("units", len(S22.units()))
|
||
exp = ROSTER.EXPECT_PER_UNIT[m] * n
|
||
tot += exp
|
||
print(f"{m:16s}{'/'.join(sp.get('roles', ROLES)):12s}{n:4d}{exp:12.3f}"
|
||
+ (" ← особый режим" if m in SPECIAL else ""))
|
||
print(f"{'ИТОГО':16s}{'':12s}{'':4s}{tot:12.3f} потолок {MONEY.CEILINGS[PHASE]:.2f}")
|
||
if tot > MONEY.CEILINGS[PHASE]:
|
||
print("⛔ смета выше потолка фазы — СТОП, не резать молча")
|
||
raise SystemExit(1)
|
||
|
||
|
||
def cmd_run() -> None:
|
||
led = MONEY.Guarded(PHASE)
|
||
for m, role, u in plan():
|
||
t = tag(m, role, u["uid"])
|
||
if (MONEY.OUT / f"{t}.json").exists():
|
||
continue
|
||
msgs = S22.msgs_for(role, u)
|
||
kw = ROSTER.call_kwargs(m, msgs, max_out=SPECIAL.get(m, {}).get("max_out", 16000))
|
||
rec = MONEY.purchase(led, t, m, client(m), kw, role=role, uid=u["uid"])
|
||
if rec is None:
|
||
print(f" {t}: не куплено")
|
||
continue
|
||
print(f" {t}: ${rec.get('cost_usd', 0):.5f} finish={rec.get('finish')} "
|
||
f"знаков={len(rec.get('content') or '')}")
|
||
MONEY.report()
|
||
|
||
|
||
def result() -> dict:
|
||
"""Вердикт по каждому кандидату — порогами эксп-22, без единой правки."""
|
||
res = {}
|
||
for m in NEW:
|
||
rows = []
|
||
for f in MONEY.OUT.glob(f"et-scr-{m}-*.json"):
|
||
rec = json.loads(f.read_text(encoding="utf-8"))
|
||
u = next((x for x in S22.units() if x["uid"] == rec.get("uid")), None)
|
||
rows.append(S22.gates(rec, u["source"] if u else ""))
|
||
v, why = S22.verdict(rows)
|
||
pt = [r["cost"] for r in rows if r["role"] == "translator"]
|
||
pe = [r["cost"] for r in rows if r["role"] == "editor"]
|
||
import statistics as st # noqa: PLC0415
|
||
res[m] = dict(verdict=v, why=why, n=len(rows),
|
||
price_translator=(st.median(pt) if pt else None),
|
||
price_editor=(st.median(pe) if pe else None),
|
||
reasoning_share=(max((r["reasoning_share"] for r in rows), default=0.0)),
|
||
han=sum(r["han_chars"] for r in rows))
|
||
return res
|
||
|
||
|
||
def cmd_score() -> None:
|
||
print(f"{'модель':16s}{'вердикт':>10s}{'ед.':>4s}{'$/ед transl':>13s}{'$/ед edit':>11s}"
|
||
f"{'размышл':>9s} причины")
|
||
for m, r in result().items():
|
||
print(f"{m:16s}{r['verdict']:>10s}{r['n']:4d}"
|
||
f"{(r['price_translator'] or 0):13.5f}{(r['price_editor'] or 0):11.5f}"
|
||
f"{r['reasoning_share']:9.1%} {'; '.join(r['why'])}")
|
||
ok = [m for m, r in result().items() if r["verdict"] == "прошёл"]
|
||
print(f"\nпрошли фазу A: {ok or '—'}")
|
||
|
||
|
||
def selfcheck() -> int:
|
||
"""До покупок: пороги те же, план в потолке, теги уникальны."""
|
||
bad = 0
|
||
if (S22.PRICE_CAP, S22.CYR_MIN, S22.ECHO_MAX) != ({"translator": 0.02, "editor": 0.06},
|
||
0.5, 0.15):
|
||
print("[ПРОВАЛ] пороги эксп-22 изменились — состав пака стал бы подгонкой")
|
||
bad += 1
|
||
tags = [tag(m, r, u["uid"]) for m, r, u in plan()]
|
||
if len(tags) != len(set(tags)):
|
||
print("[ПРОВАЛ] теги покупок не уникальны — клетки перетрут друг друга")
|
||
bad += 1
|
||
if not all(t.startswith("et-scr-") for t in tags):
|
||
print("[ПРОВАЛ] тег вне глоба фазы — расход будет невидим кассе")
|
||
bad += 1
|
||
for m in NEW:
|
||
if m not in ROSTER.THINK_ET:
|
||
print(f"[ПРОВАЛ] боевая конфигурация размышления не объявлена: {m}")
|
||
bad += 1
|
||
if "kimi-k3" not in SPECIAL:
|
||
print("[ПРОВАЛ] особый режим kimi-k3 потерян")
|
||
bad += 1
|
||
print(f"скрин: клеток в плане {len(tags)} · провалов {bad}")
|
||
return bad
|
||
|
||
|
||
if __name__ == "__main__":
|
||
a = sys.argv[1:] or ["--plan"]
|
||
fn = {"--plan": cmd_plan, "--run": cmd_run, "--score": cmd_score,
|
||
"--selfcheck": lambda: sys.exit(1 if selfcheck() else 0)}.get(a[0])
|
||
if fn:
|
||
fn()
|
||
else:
|
||
print(__doc__)
|