textmachine/eval/tenant_panel/editors.py

192 lines
9.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
"""Ф3 — ПАНЕЛЬ РЕДАКТОРОВ поверх ОДНОЙ якорной базы.
База — ЗАМОРОЖЕННЫЙ боевой черновик `deepseek-v4-flash` из Ф2 (тот самый, что прошёл гейт эха).
Все редакторы правят побайтно один текст, поэтому контраст между ними чист от разницы черновиков —
ровно того конфаунда, который эксп-21 назвал носителем 70% эффекта.
Конфигурация редактора одинакова у всех: full-regen боевым `editor.md` + ЗАКОН-БЛОК (D39.104).
Единственное различие клеток — модель. Это проверяется `--isolation` до покупок.
`gemini-3.1-pro-preview` — ОБЯЗАТЕЛЬНЫЙ кандидат промта: он идёт в Ф3 независимо от скрина
(гипотеза эксп-04 «gemini-pro — аутлаер прозы»), скрин-числа при нём печатаются.
Вторая база (лучший черновик Ф2) — ОПЦИЯ промта: берётся только если Ф2 дал отрыв выше пола И
смета влезает; решение и его основание печатаются `--plan`.
Запуск: --plan состав панели, смета, изоляция, $0
--isolation чем клетки различаются — сверка сообщений, $0
--run покупка редактур
--score батарея, канон, цена, $0
"""
from __future__ import annotations
import json
import os
import statistics as st
import sys
import time
from pathlib import Path
REPO = Path("/home/ubuntu/projects/textmachine")
ZONE = Path(__file__).resolve().parent
sys.path.insert(0, str(ZONE))
sys.path.insert(0, str(REPO / "eval" / "role_topology"))
from dotenv import load_dotenv # noqa: E402
from openai import OpenAI # noqa: E402
import bank as BANK # noqa: E402
import battery as B # noqa: E402
import material as M # noqa: E402
import money as MONEY # noqa: E402
import panel as P2 # noqa: E402
import prompts as PR # noqa: E402
from roster import CANDIDATES, call_kwargs # noqa: E402
load_dotenv(REPO / "eval" / ".env")
OUT = MONEY.OUT
BATTLE = "deepseek-v4-pro" # R0 — боевой редактор, база сравнения
MANDATORY = "gemini-3.1-pro-preview" # обязательный кандидат промта
MAX_EDITORS = 5
def client(model: str) -> OpenAI:
base, env, *_ = CANDIDATES[model]
return OpenAI(api_key=os.environ[env], base_url=base, timeout=900)
def panel() -> list[str]:
"""R0 = боевой редактор · затем обязательный кандидат · затем прошедшие скрин по цене.
Порядок фиксирован, потому что имена армов (R0, R1, …) впечатываются в ключ судейского
прохода: сдвиг порядка после выпуска заданий сделал бы семейство неповторимым.
"""
res = P2.screen_result()
out = [BATTLE, MANDATORY]
ok = [m for m, r in res.items()
if r["verdict"] == "прошёл" and m not in out and r.get("price_editor") is not None]
ok.sort(key=lambda m: res[m]["price_editor"])
return (out + ok)[:MAX_EDITORS]
def base_text(uid: str) -> str:
"""Якорная база: замороженный боевой черновик Ф2."""
return P2.draft_text(P2.ANCHOR, uid)
def tag(model: str, uid: str) -> str:
return f"tp3-ed-{model}-{uid}"
def text_of(arm: str, uid: str) -> str:
if arm == "F":
return base_text(uid)
model = panel()[int(arm[1:])]
f = OUT / f"{tag(model, uid)}.json"
return json.loads(f.read_text(encoding="utf-8")).get("content", "") if f.exists() else ""
def msgs_for(model: str, u: dict) -> list[dict]:
return PR.editor_msgs(u["source"], base_text(u["uid"]),
PR.law_block(PR.terms_in(u["source"])))
def cmd_isolation() -> None:
"""Клетки обязаны различаться ТОЛЬКО моделью. Проверяется сборкой сообщений, а не верой."""
u = M.units_zh()[0]
ms = {m: msgs_for(m, u) for m in panel()}
ref = ms[BATTLE]
bad = 0
for m, v in ms.items():
same = [a["content"] == b["content"] for a, b in zip(ref, v, strict=True)]
ok = all(same) and len(ref) == len(v)
bad += not ok
print(f"[{'OK ' if ok else 'ПРОВАЛ'}] {m:26s} сообщения совпадают с базой "
f"({len(v)} блоков)")
print(f" закон-блок в сообщениях: {'да' if any('КАНОНИЧЕСКИЕ ПЕРЕВОДЫ' in x['content'] for x in ref) else 'НЕТ'}")
print(f" база — замороженный черновик {P2.ANCHOR}, {len(base_text(u['uid']))} знаков")
print(f"\n{'ИЗОЛЯЦИЯ ЧИСТА' if not bad else f'ПРОВАЛОВ: {bad}'}")
def cmd_plan() -> None:
pan, res, us = panel(), P2.screen_result(), M.units_zh()
print(f"ПАНЕЛЬ Ф3: {len(pan)} редакторов × {len(us)} единиц поверх ОДНОЙ якорной базы")
est = 0.0
for i, m in enumerate(pan):
r = res.get(m, {})
price = r.get("price_editor")
free = " (куплено в Ф2, переиспользуется)" if m == BATTLE else ""
if m != BATTLE:
est += (price or 0.02) * len(us)
print(f" R{i} {m:26s} скрин={r.get('verdict', 'не скринился'):12s} "
f"$/ед={price if price is not None else float('nan'):.5f}{free}")
print(f"\nсмета ${est:.4f} при потолке ${MONEY.CEILINGS['Ф3']:.2f}")
if est > MONEY.CEILINGS["Ф3"]:
print("СМЕТА НЕ ВЛЕЗАЕТ — сократить панель или пинг владельцу ДО покупок")
def cmd_run() -> None:
led = MONEY.Guarded("Ф3", default_expect=0.03)
for m in panel():
if m == BATTLE:
print(f" {m}: клетка куплена в Ф2 (tp2-edit) — не перекупается")
continue
for u in M.units_zh():
if not base_text(u["uid"]):
print(f"{u['uid'][:6]}: якорной базы нет — клетка пропущена")
continue
rec = MONEY.purchase(led, tag(m, u["uid"]), m, client(m),
call_kwargs(m, msgs_for(m, u)),
role="editor3", uid=u["uid"], editor=m)
if rec.get("skipped"):
print("⛔ потолок Ф3 — стоп")
return
print(f" {m:26s} {u['uid'][:6]} finish={str(rec.get('finish')):9s} "
f"{len(rec.get('content', '')):5d} зн ${rec.get('cost_usd', 0):.5f}")
time.sleep(0.15)
print(f"потрачено Ф3 ${led.spent():.6f}")
def cmd_score() -> None:
pan, us, cards = panel(), M.units_zh(), B.load_cards()
print(f"{'арм':30s}{'ед.':>5s}{'ханьцзы':>9s}{'типогр':>8s}{'канон':>14s}"
f"{'покрытие':>10s}{'$/ед':>10s}")
for i, m in enumerate(pan + ["БАЗА"]):
rows, k, s, costs = [], 0, 0, []
for u in us:
t = base_text(u["uid"]) if m == "БАЗА" else text_of(f"R{i}", u["uid"])
if not t.strip():
continue
rows.append(B.run_unit(B.Unit(source=u["source"], draft=t, final=t, cards=cards)))
kk, ss = BANK.coverage(u["source"], t)
k += kk
s += ss
if m == "БАЗА":
costs.append(P2.draft_cost(P2.ANCHOR, u["uid"]))
elif m == BATTLE:
f = OUT / f"{P2.tag_edit(P2.ANCHOR, u['uid'])}.json"
if f.exists():
costs.append(json.loads(f.read_text(encoding="utf-8")).get("cost_usd", 0.0))
else:
f = OUT / f"{tag(m, u['uid'])}.json"
if f.exists():
costs.append(json.loads(f.read_text(encoding="utf-8")).get("cost_usd", 0.0))
if not rows:
continue
label = "БАЗА (черновик)" if m == "БАЗА" else f"R{i} {m}"
print(f"{label:30s}{len(rows):5d}"
f"{sum(r['cjk_leak']['han_chars'] for r in rows):9d}"
f"{sum(r['typography']['violations'] for r in rows):8d}"
f"{f'{k}/{s}':>14s}{(k / s if s else 0):>10.3f}"
f"{(st.median(costs) if costs else 0):>10.5f}")
led = MONEY.Guarded("Ф3")
print(f"\nпотрачено Ф3 ${led.spent():.6f} из ${led.ceiling:.2f}")
if __name__ == "__main__":
a = sys.argv[1:] or ["--plan"]
fn = {"--plan": cmd_plan, "--isolation": cmd_isolation, "--run": cmd_run,
"--score": cmd_score}.get(a[0])
fn() if fn else print(__doc__)