155 lines
8.9 KiB
Python
155 lines
8.9 KiB
Python
#!/usr/bin/env python3
|
||
"""ЭНДПОЙНТ ДИЗАЙНА Д (пре-рег Д47): РЕЖЕТ ЛИ `reasoning_effort:"low"` РАЗМЫШЛЕНИЕ У `pro`. $0.
|
||
|
||
⚠ ЗАЧЕМ ОТДЕЛЬНЫЙ ФАЙЛ, а не счёт в уме отчёта. Правило решения Д47.3 написано ДО чисел, и оно
|
||
обязано применяться механически, иначе «правило до чисел» превращается в «правило под числа».
|
||
Файл написан ДО прихода клеток и коммитится вместе с ними: кто угодно пере-снимает вывод одной
|
||
командой и видит ровно то, что видел автор.
|
||
|
||
ЧТО ЗДЕСЬ ЕСТЬ И ЧЕГО НЕТ:
|
||
* есть парный знаковый тест по восьми главам (`reasoning_tokens` у `RNL` против `RN2`);
|
||
* есть по-клеточный гейт годности В ТОЙ ФОРМЕ, В КАКОЙ ОН ЗАМОРОЖЕН, включая проверку
|
||
провенанса `Δprompt_tokens ∈ [60,100]` — она ловит клетку, до которой параметр НЕ доехал;
|
||
* НЕТ ни одной ручки, которой можно подвинуть порог: α, полосы и список глав — константы файла,
|
||
и селфтест стережёт, что они те же, что в отчёте.
|
||
|
||
⛔ ОТНОШЕНИЯ (×N) ПЕЧАТАЮТСЯ КАК ОПИСАНИЕ, А НЕ КАК ВЫВОД. Дизайн Д стоит на восьми ДЕШЁВЫХ главах
|
||
(10.2% цены панели) — переносить их величины на дорогую моду запрещено там же, где выбраны главы.
|
||
|
||
Запуск: eval/.venv/bin/python eval/dovodka/stupen.py [--selftest]
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import math
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
ZONE = Path(__file__).resolve().parent
|
||
sys.path.insert(0, str(ZONE))
|
||
|
||
import rol # noqa: E402
|
||
|
||
OUT = Path.home() / "books" / "dovodka"
|
||
|
||
# ── КОНСТАНТЫ ПРЕ-РЕГА Д47. Меняются только вместе с отчётом и только ДО покупок ───────────────
|
||
CHAPTERS = ("b065a92dc0", "5a8f48d24a", "100b088f71", "49ca859c94",
|
||
"8e50448cea", "c3517980c7", "3bd6481182", "26c3bff1d4")
|
||
ARM_HIGH, ARM_LOW = "RN2", "RNL" # контроль (ручка не шлётся) и испытуемый (`low`)
|
||
ALPHA = 0.05 # двусторонний, Д47.3
|
||
DPT_BAND = (60, 100) # провенанс параметра: ожидается 79 (Д46.1)
|
||
CYR_MIN = 0.99 # доля кириллицы среди букв
|
||
LAT_MAX = 0 # латиница-эхо
|
||
|
||
|
||
def _letters(t: str) -> tuple[int, int, int]:
|
||
"""(букв всего, кириллических, латинских)."""
|
||
al = [c for c in t if c.isalpha()]
|
||
cyr = sum(1 for c in al if "а" <= c.lower() <= "я" or c.lower() == "ё")
|
||
lat = sum(1 for c in al if "a" <= c.lower() <= "z")
|
||
return len(al), cyr, lat
|
||
|
||
|
||
def _cell(arm: str, uid: str) -> dict | None:
|
||
f = OUT / f"{rol.tag('en', arm, uid)}.json"
|
||
return json.loads(f.read_text(encoding="utf-8")) if f.exists() else None
|
||
|
||
|
||
def sign_p(k: int, n: int) -> float:
|
||
"""Точный двусторонний знаковый тест при p=0.5. k — сколько пар легли в ожидаемую сторону."""
|
||
if n == 0:
|
||
return 1.0
|
||
tail = sum(math.comb(n, i) for i in range(k, n + 1)) / 2 ** n
|
||
return min(1.0, 2 * tail)
|
||
|
||
|
||
def main() -> int:
|
||
rows, missing = [], []
|
||
for uid in CHAPTERS:
|
||
hi, lo = _cell(ARM_HIGH, uid), _cell(ARM_LOW, uid)
|
||
if not hi or not lo:
|
||
missing.append(uid)
|
||
continue
|
||
n_al, cyr, lat = _letters(lo.get("content") or "")
|
||
share = cyr / n_al if n_al else 0.0
|
||
dpt = hi["prompt_tokens"] - lo["prompt_tokens"]
|
||
bad = []
|
||
if hi.get("finish") != "stop" or lo.get("finish") != "stop":
|
||
bad.append("finish≠stop")
|
||
if share < CYR_MIN:
|
||
bad.append(f"кириллица {share:.3f}")
|
||
if lat > LAT_MAX:
|
||
bad.append(f"латиница {lat}")
|
||
if not DPT_BAND[0] <= dpt <= DPT_BAND[1]:
|
||
bad.append(f"Δpt {dpt} вне [{DPT_BAND[0]},{DPT_BAND[1]}] — параметр не доехал")
|
||
rows.append(dict(uid=uid, hi=hi["reasoning_tokens"], lo=lo["reasoning_tokens"],
|
||
dpt=dpt, cyr=share, lat=lat, bad=bad,
|
||
cost=hi["cost_usd"] + lo["cost_usd"]))
|
||
|
||
if missing:
|
||
print(f"⛔ клеток нет у глав: {' '.join(missing)} — эндпойнт неполон")
|
||
if not rows:
|
||
return 1
|
||
|
||
print(f"{'глава':12}{'RN2 дум':>9}{'RNL дум':>9}{'отнош':>8}{'Δpt':>6}{'кирилл':>8}"
|
||
f"{'лат':>5} гейт")
|
||
for r in rows:
|
||
ratio = r["hi"] / r["lo"] if r["lo"] else float("inf")
|
||
print(f"{r['uid']:12}{r['hi']:>9}{r['lo']:>9}{ratio:>8.2f}{r['dpt']:>6}"
|
||
f"{r['cyr']:>8.3f}{r['lat']:>5} "
|
||
f"{'OK' if not r['bad'] else '⛔ ' + ' · '.join(r['bad'])}")
|
||
|
||
good = [r for r in rows if not r["bad"]]
|
||
n = len(good)
|
||
k = sum(1 for r in good if r["lo"] < r["hi"])
|
||
p = sign_p(k, n)
|
||
ratios = sorted(r["hi"] / r["lo"] for r in good if r["lo"])
|
||
med = ratios[len(ratios) // 2] if ratios else float("nan")
|
||
|
||
print(f"\nгодных пар {n} из {len(CHAPTERS)} · размышление у `low` МЕНЬШЕ в {k} из {n}")
|
||
print(f"знаковый тест, двусторонний: p = {p:.4f} при пороге α = {ALPHA}")
|
||
print(f"цена прогона: ${sum(r['cost'] for r in rows):.6f}")
|
||
|
||
# ── ПРАВИЛО Д47.3. Ветки написаны до чисел; ни одна из них ничего не «дозакупает» ──────────
|
||
print()
|
||
if n < len(CHAPTERS):
|
||
print("⚠ ПАР МЕНЬШЕ ВОСЬМИ: правило Д47.3 писалось на n=8. Вывод печатается по факту "
|
||
"годных пар и объявляется НЕПОЛНЫМ — добор до восьми ПОСЛЕ взгляда на числа запрещён.")
|
||
if k == n and p < ALPHA:
|
||
print(f"⭐ ЕДИНОГЛАСНО И ЗНАЧИМО: ручка РЕЖЕТ размышление. Медиана отношения ×{med:.2f} — "
|
||
"ОПИСАНИЕ этих восьми дешёвых глав, не перенос на дорогую моду и не на пару zh.")
|
||
elif p >= ALPHA:
|
||
print(f"⛔ НЕ ЗНАЧИМО (p = {p:.4f} ≥ {ALPHA}): «не установлено». Это РЕЗУЛЬТАТ, а не повод "
|
||
"докупать девятую главу — добор до значимости после чисел запрещён (Д47.3).")
|
||
else:
|
||
print(f"⛔ ЗНАЧИМО В ОБРАТНУЮ СТОРОНУ (k={k} из {n}): ручка размышление НЕ режет.")
|
||
return 0
|
||
|
||
|
||
def selftest() -> int:
|
||
bad = 0
|
||
|
||
def ck(ok: bool, what: str) -> None:
|
||
nonlocal bad
|
||
print(f"[{'OK ' if ok else 'ПРОВАЛ'}] {what}")
|
||
bad += 0 if ok else 1
|
||
|
||
ck(len(CHAPTERS) == 8 and len(set(CHAPTERS)) == 8, "восемь глав пре-рега, без повторов")
|
||
rep = (ZONE.parents[1] / "docs" / "experiments" / "23-editor-tier.md").read_text(encoding="utf-8")
|
||
ck(all(u in rep for u in CHAPTERS), "все восемь глав названы в отчёте (пре-рег Д47.2)")
|
||
ck(ARM_HIGH in rol.ARMS and ARM_LOW in rol.ARMS, "оба арма есть в реестре покупок")
|
||
ck(rol.THINK_LEVEL.get(ARM_HIGH) is None and rol.THINK_LEVEL.get(ARM_LOW) == "low",
|
||
"контроль без ручки, испытуемый на `low`")
|
||
ck(rol.ARMS[ARM_HIGH][0] == rol.ARMS[ARM_LOW][0] == "deepseek-v4-pro", "модель у армов одна")
|
||
ck(rol.tag("en", ARM_HIGH, "x") != rol.tag("en", ARM_LOW, "x"), "теги армов не схлопываются")
|
||
# правило решения обязано быть НЕ достижимо ничем, кроме единогласия — это и есть цена n=8
|
||
ck(sign_p(8, 8) < ALPHA <= sign_p(7, 8), "при n=8 значимость даёт ТОЛЬКО 8 из 8")
|
||
ck(abs(sign_p(8, 8) - 0.0078125) < 1e-9, "минимальный двусторонний p посчитан верно")
|
||
ck(sign_p(4, 8) == 1.0, "полный ноль эффекта даёт p = 1")
|
||
print(f"\n{'ПРИБОР ГОДЕН' if not bad else f'ПРОВАЛОВ: {bad}'}")
|
||
return bad
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(selftest() if "--selftest" in sys.argv else main())
|