textmachine/eval/exp14b_arms.py

110 lines
5.6 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
"""exp14b — армы батареи смысл-трапов + P1a↔F-disc. ПРОМПТ константен (дискурс), варьируем
editor-модель: C=glm-5 / F=gpt-5.4 / X=grok-4.3(reasoning-ON). Общий flash-черновик.
Реюз exp14-выходов где есть (не пере-оплата). Per-call кап ОБЯЗАТЕЛЕН. ЧЕСТНЫЙ учёт ошибок
(finish=length/empty/http/retry — раздельно; НЕ «0 ошибок»). $ персист.
Использование: exp14b_arms.py [--dry]
"""
from __future__ import annotations
import argparse, json, shutil, sys, time
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
import exp14_common as C
import exp14_prompts as P
RERUN = Path("/home/ubuntu/books/gu-zhenren/rerun")
RECS = {(r["chapter"], r["chunk_idx"]): r for r in json.load(open(RERUN / "records.json"))}
INJ = json.load(open(Path("/home/ubuntu/books/gu-zhenren/exp14") / "injection_blocks.json"))
EX14 = Path("/home/ubuntu/books/gu-zhenren/exp14/arms")
OUT = Path("/home/ubuntu/books/gu-zhenren/exp14b"); OUT.mkdir(parents=True, exist_ok=True)
ARMS = OUT / "arms"; ARMS.mkdir(exist_ok=True)
# базовые (пре-рег §1) + добавленные семьи-редакторы (расширение панели floor-теста, прозрачно)
MODEL = {"C": "glm-5", "F": "gpt-5.4", "X": "grok-4.3",
"D": "deepseek-v4-pro", "K": "kimi-k2.6", "M": "mistral-large-2512"}
EX14_ARM = {"C": "P1a", "F": "F-disc", "X": "X-disc"} # соответствие для реюза (только базовые)
CAPS = {"glm-5": 0.08, "gpt-5.4": 0.40, "grok-4.3": 0.40,
"deepseek-v4-pro": 0.06, "kimi-k2.6": 0.20, "mistral-large-2512": 0.10}
# battery chunks + stage2 (для head-to-head C vs F)
BATTERY = ["7.0", "8.1", "11.1", "17.0", "19.0", "10.1", "6.0", "16.0", "2.1", "9.1"]
STAGE2 = ["17.0", "17.1", "18.0", "18.1", "18.2", "19.0", "19.1"]
# что каждому арму нужно покрыть
NEED = {
"C": sorted(set(BATTERY) | set(STAGE2)),
"F": sorted(set(BATTERY) | set(STAGE2)), # F на stage2 уже есть в exp14 → реюз
"X": sorted(set(BATTERY)),
"D": sorted(set(BATTERY)), # deepseek-v4-pro — только батарея (floor-тест)
"K": sorted(set(BATTERY)), # kimi-k2.6
"M": sorted(set(BATTERY)), # mistral-large-2512
}
def esys():
return P.editor_system("discourse")
def main():
ap = argparse.ArgumentParser(); ap.add_argument("--dry", action="store_true"); a = ap.parse_args()
sp = C.Spender(OUT / "costs.jsonl", CAPS)
errs = {"length": 0, "empty": 0, "http": 0, "content_filter": 0, "other": 0, "retried_ok": 0}
sysd = esys()
for arm, chunks in NEED.items():
d = ARMS / arm; d.mkdir(exist_ok=True)
model = MODEL[arm]
for cid in chunks:
outp = d / f"{cid}.txt"
if outp.exists():
continue
# РЕЮЗ exp14 (только базовые C/F/X — D/K/M новых семей в exp14 не было)
if arm in EX14_ARM:
ex = EX14 / EX14_ARM[arm] / f"{cid}.txt"
if ex.exists():
shutil.copy(ex, outp)
print(f" [reuse exp14] {arm} {cid}{EX14_ARM[arm]}")
continue
ch, ck = cid.split("."); ch = int(ch); ck = int(ck)
r = RECS.get((ch, ck))
if not r:
print(f" [MISS rec] {cid}"); continue
einj = INJ.get(f"{ch}/{ck}", {}).get("editor_constraint", "")
user = P.editor_user(r["source"], r["draft"])
msgs = C.messages_with_injection(sysd, einj, user)
fam = "deepseek" if model.startswith("deepseek") else "glm"
in_est = C.count_tokens(sysd + einj + user, fam)
s = C.spec(model, temp=0.4)
ok, pred = sp.guard(model, in_est, s["max_tokens"])
print(f" {arm} {cid}: in≈{in_est} predict=${pred:.4f} cap=${CAPS[model]} {'OK' if ok else 'OVER'}")
if a.dry or not ok:
continue
# до 2 ретраев на transient (honest error accounting)
text, err, usage, attempts = "", None, {}, 0
for attempt in range(3):
attempts += 1
text, err, usage = C.call(s, msgs, timeout=360)
sp.record({"arm": arm, "model": model, "keyenv": s["keyenv"], "id": cid,
"attempt": attempts, "err": err, "usage": usage})
if not err:
if attempt > 0: errs["retried_ok"] += 1
break
# классификация ошибки
if "length" in (err or ""): errs["length"] += 1
elif "empty" in (err or ""): errs["empty"] += 1
elif "http" in (err or ""): errs["http"] += 1
elif "content_filter" in (err or ""): errs["content_filter"] += 1
else: errs["other"] += 1
if "http" in (err or "") or "transport" in (err or ""):
time.sleep(3); continue # transient → retry
break # length/empty/filter → не ретраим слепо
if err:
print(f" ERR {err[:60]} (attempts={attempts})"); continue
outp.write_text(text, encoding="utf-8")
print(f" ok {len(text)}ch")
print(f"\n=== ЧЕСТНЫЙ учёт ошибок: {errs} ===")
print(f"=== spend by key: {dict((k,round(v,4)) for k,v in sp.by_key.items())} ===")
if __name__ == "__main__":
main()