235 lines
12 KiB
Python
235 lines
12 KiB
Python
#!/usr/bin/env python3
|
||
"""Проба C (промт POLYGON_EDITOR_WIRE_PROBE §C): фронтир-пасс по голду — «сколько докупает ум».
|
||
|
||
Тот же терминолог-протокол, что research/24 §B3 (`consilium_probe.py`: ростер 63 строки, 5 батчей,
|
||
engine-faithful RenderBatch, боевой terminologist.md, добавка CONF_NUM), против чемпиона glm-5 25/45.
|
||
Кандидаты (пре-рег §0C, слаги live /models 04.08): deepseek-v4-pro · gpt-5.6-terra · gpt-5.6-luna.
|
||
Цены — прайс-страница вендора, короткий контекст-тир (батчи ≈4.5k ток.).
|
||
|
||
⚠ Wire-квирк GPT-5.6 НЕ гадаем: `reasoning.mode`/`reasoning.effort` задокументированы для Responses
|
||
API; плоский `reasoning_effort` в Chat Completions вендором не заявлен, а прецедент exp08 (xAI) —
|
||
вложенная форма МОЛЧА ГЛОТАЕТСЯ. Поэтому `--wirecheck` меряет ручку живьём (none против high по
|
||
reasoning_tokens) ДО основного прогона, и транспорт выбирается по замеру.
|
||
"""
|
||
from __future__ import annotations
|
||
import argparse
|
||
import json
|
||
import os
|
||
import sys
|
||
import time
|
||
from pathlib import Path
|
||
|
||
from dotenv import load_dotenv
|
||
from openai import OpenAI
|
||
|
||
REPO = Path("/home/ubuntu/projects/textmachine")
|
||
HERE = Path(__file__).resolve().parent
|
||
RAW = Path.home() / "books" / "gu-zhenren" / "bank-arbitration"
|
||
load_dotenv(REPO / "eval" / ".env")
|
||
sys.path.insert(0, str(HERE))
|
||
|
||
from consilium_probe import (build_roster, batches_of, block_of, render_terminologist, # noqa: E402
|
||
CONF_NUM)
|
||
from arbitrate import GOLD, parse_pass, eq # noqa: E402
|
||
|
||
CEILING_USD = 0.50 # потолок пробы C (промт §C)
|
||
HARD_STOP_USD = 0.45 # пре-рег: живой стоп ниже потолка
|
||
# Пер-модельные потолки вывода. Гард ПРОЕКЦИОННЫЙ: вызов не делается, если ХУДШИЙ его исход
|
||
# (упор в cap) пробил бы CEILING_USD — иначе стоп «по факту» ловил бы уже потраченное.
|
||
CAPS = {"deepseek-v4-pro": 16000, "gpt-5.6-luna": 16000, "gpt-5.6-terra": 8000}
|
||
ORDER = ["deepseek-v4-pro", "gpt-5.6-luna", "gpt-5.6-terra"] # дешёвые первыми
|
||
|
||
# (base_url, env, in, cached_in, out, openai_family)
|
||
CAND = {
|
||
"deepseek-v4-pro": ("https://api.deepseek.com/v1", "DEEPSEEK_API_KEY", 0.435, 0.003625, 0.87, False),
|
||
"gpt-5.6-terra": ("https://api.openai.com/v1", "OPENAI_API_KEY", 2.00, 0.20, 12.00, True),
|
||
"gpt-5.6-luna": ("https://api.openai.com/v1", "OPENAI_API_KEY", 0.20, 0.02, 1.20, True),
|
||
}
|
||
TAG = {"deepseek-v4-pro": "fdp", "gpt-5.6-terra": "fte", "gpt-5.6-luna": "flu"}
|
||
WIRE = HERE / "gold" / "frontier_wire.json" # результат --wirecheck: как слать reasoning
|
||
|
||
|
||
def client(model: str) -> OpenAI:
|
||
base, env, *_ = CAND[model]
|
||
return OpenAI(api_key=os.environ[env], base_url=base, timeout=900)
|
||
|
||
|
||
def price(model: str, pt: int, cached: int, ct: int) -> float:
|
||
_, _, pin, pcache, pout, _ = CAND[model]
|
||
return (pt - cached) / 1e6 * pin + cached / 1e6 * pcache + ct / 1e6 * pout
|
||
|
||
|
||
def usage_of(u):
|
||
pt = getattr(u, "prompt_tokens", 0) or 0
|
||
ct = getattr(u, "completion_tokens", 0) or 0
|
||
cached = getattr(getattr(u, "prompt_tokens_details", None), "cached_tokens", 0) or 0
|
||
rt = getattr(getattr(u, "completion_tokens_details", None), "reasoning_tokens", 0) or 0
|
||
return pt, cached, ct, rt
|
||
|
||
|
||
def call(model: str, system: str, user: str, tag: str, effort: str | None, cap: int) -> dict:
|
||
_, _, _, _, _, is_openai = CAND[model]
|
||
cl = client(model)
|
||
msgs = [{"role": "system", "content": system}, {"role": "user", "content": user}]
|
||
kw: dict = dict(model=model, messages=msgs)
|
||
if is_openai:
|
||
kw["max_completion_tokens"] = cap # quirks 00: не max_tokens
|
||
if effort: # temperature не шлём (reasoning-модель)
|
||
kw["reasoning_effort"] = effort
|
||
else:
|
||
kw["max_tokens"] = cap
|
||
kw["temperature"] = 0
|
||
# v4-pro: reasoning-параметр НЕ шлём (low маппится в high; ручки бюджета нет)
|
||
t0 = time.time()
|
||
r = cl.chat.completions.create(**kw)
|
||
ch = r.choices[0]
|
||
pt, cached, ct, rt = usage_of(r.usage)
|
||
cost = price(model, pt, cached, ct)
|
||
rec = dict(tag=tag, model=model, model_returned=r.model, effort=effort or "default",
|
||
finish=ch.finish_reason, ts=time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
||
cap=cap, prompt_tokens=pt, cached_tokens=cached, completion_tokens=ct,
|
||
reasoning_tokens=rt, cost_usd=round(cost, 6), latency_s=round(time.time() - t0, 1),
|
||
content=ch.message.content or "", system=system, user=user)
|
||
(RAW / f"{tag}.json").write_text(json.dumps(rec, ensure_ascii=False, indent=1), encoding="utf-8")
|
||
print(f"[{tag}] {model} eff={effort} finish={ch.finish_reason} in={pt}(c{cached}) out={ct}"
|
||
f"(r{rt}) ${cost:.6f} {rec['latency_s']}s content={len(rec['content'])}c")
|
||
return rec
|
||
|
||
|
||
def cmd_slugcheck():
|
||
for model in CAND:
|
||
base, env, *_ = CAND[model]
|
||
ids = [m.id for m in OpenAI(api_key=os.environ[env], base_url=base, timeout=120).models.list().data]
|
||
print(f"/models {model}: {'OK' if model in ids else 'ОТСУТСТВУЕТ'} (всего {len(ids)})")
|
||
if model not in ids:
|
||
raise SystemExit(f"слаг {model} не листится — СТОП+релей")
|
||
|
||
|
||
def cmd_wirecheck():
|
||
"""Живой замер ручки reasoning на OpenAI-кандидатах: none против high по reasoning_tokens.
|
||
Ручка ЖИВАЯ ⇔ reasoning_tokens различаются. Плюс проверка, что Chat Completions вообще принимает."""
|
||
probe = "Ответь ровно одним словом: сколько будет 17*23?"
|
||
out = {}
|
||
total = 0.0
|
||
for model in CAND:
|
||
if not CAND[model][5]:
|
||
out[model] = {"transport": "chat", "effort": None, "note": "v4-pro: параметр не шлём (дефолт high)"}
|
||
continue
|
||
res = {}
|
||
for eff in ("none", "high"):
|
||
try:
|
||
r = call(model, "Ты — калькулятор.", probe, f"wire-{TAG[model]}-{eff}", eff, 2000)
|
||
res[eff] = r["reasoning_tokens"]
|
||
total += r["cost_usd"]
|
||
except Exception as e:
|
||
res[eff] = f"ERR {type(e).__name__}: {str(e)[:200]}"
|
||
live = isinstance(res.get("none"), int) and isinstance(res.get("high"), int) and res["none"] != res["high"]
|
||
out[model] = {"transport": "chat" if live else "НЕ ПОДТВЕРЖДЕНО",
|
||
"reasoning_tokens": res, "knob_live": live,
|
||
"effort": "low" if live else None}
|
||
print(f" ⇒ {model}: ручка {'ЖИВАЯ' if live else 'НЕ подтверждена'} {res}")
|
||
WIRE.write_text(json.dumps(out, ensure_ascii=False, indent=1), encoding="utf-8")
|
||
print(f"wirecheck стоил ${total:.6f}; вердикты → {WIRE}")
|
||
|
||
|
||
def cmd_plan():
|
||
roster = build_roster()
|
||
batches = batches_of(roster)
|
||
print(f"ростер={len(roster)} строк, батчей={len(batches)}")
|
||
# вход берём фактический из сырья §B3 (тот же промпт и те же батчи)
|
||
est_in = sum(json.load(open(RAW / f"glm1-b{i}.json", encoding="utf-8"))["prompt_tokens"]
|
||
for i in range(len(batches)))
|
||
for model in CAND:
|
||
for out_est in (5000, 15000, 40000):
|
||
print(f" {model:18s} вход {est_in} ток. + выход {out_est:6d} → ${price(model, est_in, 0, out_est):.4f}")
|
||
print(f"ПОТОЛОК ПРОБЫ $0.50; живой стоп харнесса ${HARD_STOP_USD}")
|
||
|
||
|
||
def cmd_run(models: list[str]):
|
||
wire = json.load(open(WIRE, encoding="utf-8")) if WIRE.exists() else {}
|
||
roster = build_roster()
|
||
batches = batches_of(roster)
|
||
total = sum(json.load(open(f, encoding="utf-8"))["cost_usd"]
|
||
for m in CAND for f in RAW.glob(f"{TAG[m]}-b*.json"))
|
||
if total:
|
||
print(f"уже потрачено пробой C по сырью: ${total:.6f}")
|
||
for model in [m for m in ORDER if m in models]:
|
||
eff = wire.get(model, {}).get("effort")
|
||
cap = CAPS[model]
|
||
for i, b in enumerate(batches):
|
||
tag = f"{TAG[model]}-b{i}"
|
||
if (RAW / f"{tag}.json").exists():
|
||
print(f"[{tag}] уже персистирован — пропуск")
|
||
continue
|
||
system, user = render_terminologist("\n\n".join(block_of(c) for c in b))
|
||
worst = price(model, len(user) // 2 + 2000, 0, cap) # худший исход ЭТОГО вызова
|
||
if total + worst > CEILING_USD or total >= HARD_STOP_USD:
|
||
print(f"СТОП (проекционный гард): накоплено ${total:.4f}, худший исход вызова "
|
||
f"${worst:.4f} → пробил бы потолок ${CEILING_USD}. Остаток арма не гоню.")
|
||
return
|
||
total += call(model, system, user + CONF_NUM, tag, eff, cap)["cost_usd"]
|
||
time.sleep(1.0)
|
||
print(f"TOTAL пробы C: ${total:.6f}")
|
||
|
||
|
||
def cmd_score():
|
||
gb = {r["bank_src"]: r for r in GOLD if r["in_bank"]}
|
||
passes = {"glm1 (чемпион)": "glm1", "ds1 (flash low)": "ds1", "mis1": "mis1"}
|
||
passes.update({m: TAG[m] for m in CAND})
|
||
acc, cost, rows = {}, {}, {}
|
||
for name, tag in passes.items():
|
||
p = parse_pass(tag)
|
||
if not p["map"]:
|
||
continue
|
||
acc[name] = {s for s, g in gb.items() if eq(p["map"].get(s, ("", None))[0], g["gold_dst"])}
|
||
cost[name] = p["cost"]
|
||
rows[name] = len(p["map"])
|
||
print("=== Точность против голда (45) и деньги пасса ===")
|
||
for n in acc:
|
||
print(f" {n:22s} {len(acc[n])}/45 строк={rows[n]} ${cost[n]:.6f} "
|
||
f"пер-терм ${cost[n]/63:.6f}")
|
||
print("\n=== Парное против чемпиона glm-5 (критерий пре-рега: ≥ +5) ===")
|
||
base = acc.get("glm1 (чемпион)")
|
||
for n in acc:
|
||
if n == "glm1 (чемпион)":
|
||
continue
|
||
x, y = len(acc[n] - base), len(base - acc[n])
|
||
verdict = "ОПРАВДАН" if x - y >= 5 else "не оправдан"
|
||
print(f" {n:22s} +{x} / -{y} (net {x-y:+d}) → {verdict}")
|
||
print("\n=== Цена/книга (методика research/24 §B5: 2000 различных строк банка) ===")
|
||
for n in acc:
|
||
print(f" {n:22s} ${cost[n]/63*2000:.2f}/книга")
|
||
print("\n=== Единицы: что кандидат берёт, а glm-5 теряет (и наоборот) ===")
|
||
for n in acc:
|
||
if n == "glm1 (чемпион)":
|
||
continue
|
||
for s in sorted(acc[n] - base):
|
||
print(f" +{n} {s}: gold={gb[s]['gold_dst']!r}")
|
||
for s in sorted(base - acc[n]):
|
||
print(f" -{n} {s}: gold={gb[s]['gold_dst']!r}")
|
||
|
||
|
||
def main():
|
||
ap = argparse.ArgumentParser()
|
||
ap.add_argument("--slugcheck", action="store_true")
|
||
ap.add_argument("--wirecheck", action="store_true")
|
||
ap.add_argument("--plan", action="store_true")
|
||
ap.add_argument("--run", nargs="*", default=None)
|
||
ap.add_argument("--score", action="store_true")
|
||
a = ap.parse_args()
|
||
if a.slugcheck:
|
||
cmd_slugcheck()
|
||
elif a.wirecheck:
|
||
cmd_wirecheck()
|
||
elif a.plan:
|
||
cmd_plan()
|
||
elif a.run is not None:
|
||
cmd_run(a.run or list(CAND))
|
||
elif a.score:
|
||
cmd_score()
|
||
else:
|
||
ap.print_help()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|