From 30755661049de0d08a9e63b36679b939b064ba5f Mon Sep 17 00:00:00 2001 From: "Claude (backend session)" Date: Thu, 20 Aug 2026 18:52:19 +0300 Subject: [PATCH] Freeze two ablation variants with the verification tail cut from both and opt-in trace capture --- eval/dovodka/rol.py | 96 +++++++++++++++++++++++++++++++++++++-- eval/role_topology/buy.py | 11 ++++- 2 files changed, 102 insertions(+), 5 deletions(-) diff --git a/eval/dovodka/rol.py b/eval/dovodka/rol.py index 3b0c2a6f..6f5e7779 100644 --- a/eval/dovodka/rol.py +++ b/eval/dovodka/rol.py @@ -119,8 +119,84 @@ PAIRS["zh"]["restore"] = None # первую клетку второй раз. ARMS = {"RN": ("deepseek-v4-pro", 1), "RN2": ("deepseek-v4-pro", 2), "RG": ("glm-5", 1), "RK": ("grok-4.3", 1), - "RA": ("deepseek-v4-pro", 1)} # тот же промт, ВОССТАНОВЛЕННЫЕ абзацы исходника + "RA": ("deepseek-v4-pro", 1), # тот же промт, ВОССТАНОВЛЕННЫЕ абзацы исходника + "RC": ("deepseek-v4-pro", 1), # промт МИНУС рамка оценки, см. CUT + "RB": ("deepseek-v4-pro", 1)} # рамка оценки ПЕРЕВЁРНУТА: брак = чужой язык RESTORE_ARMS = ("RA",) + +# ⚠ РЕЗАНЫЕ ВАРИАНТЫ ПРОМТА. Замер `RN` дал ТРИ независимых показания одного сдвига: размышление +# ×28 против прежней склейки при входе ×1.17, слепое чтение — «самый точный по деталям, но проза +# самая серая: гладкопись подстрочника», перевёрстка 1.60 против 1.77–1.79 у обеих старых схем. +# Модель стала аккуратнее и трусливее по всем осям сразу. +# Гипотеза: пять напечатанных осей, «по которым проверят результат», заставляют оптимизировать +# ЧЕК-ЛИСТ, а не текст — держаться ближе к букве есть самый дешёвый способ не нарушить пять +# критериев разом. Ровно этот отказ предсказывали и приёмка Fable-5, и документация Anthropic +# (перенесённые инструкции самопроверки дают избыточную проверку). +# ⚠ РЕЖЕТСЯ РОВНО ОДИН БЛОК, остальное побайтно то же — иначе замер не скажет, что подействовало. +# Селфтест это ПРОВЕРЯЕТ: разница промтов обязана быть равна вырезанному куску и ничему больше. +# ⚠ ПЕРВАЯ РЕДАКЦИЯ РЕЗА БЫЛА НЕГОДНОЙ, и поймала её проверка исполнением ДО покупки: вырезание +# блока целиком уносило 1871 знак — вместе с ним исчезали ВСЕ запреты и рамка РЕГИСТРА, лучшая +# часть промта по мнению приёмки. Это был бы замер «промт без правил», а не «промт без чек-листа». +# ⇒ Режется РОВНО РАМКА ОЦЕНКИ, содержание остаётся побайтно: «по чему будет проверен результат, +# это же и есть список ошибок» → «чего нельзя». Одна переменная: знает ли модель, что её ОЦЕНЯТ +# по перечню. Убирается и мета-строка про «вольность ошибкой не считается» — она сама ссылается +# на оценку и без рамки повисает. +# ⚠ RB — ВТОРАЯ ФОРМУЛИРОВКА, И ОНА ОТ ВЛАДЕЛЬЦА. Разбор Fable-5 показал, ПОЧЕМУ экзамен +# переиграл свободы: все пять осей сформулированы как оси ИЗБЕГАНИЯ, и серая проза проходит все +# пять — «ни кальки, ни канцелярита» гладкий подстрочник проходит, факт/полноту/канон проходит +# лучше всех. Весь риск объявленного экзамена лежал на смелости и ни грамма на осторожности, +# значит держаться буквы было доминантной стратегией. +# Лечение — не убрать экзамен, а ПЕРЕВЕРНУТЬ его риск. Формулировка владельца 20.08 дословно: +# «не прям серость, я бы сказал присутствие одного языка в другом. То есть полное отсутствие +# художественности и родного звучания текста. Машинный перевод грубо говоря. Всё же перевод это +# отчасти какое-то но творчество, без перевирания смысла нужно написать текст, чтоб он звучал +# естественно». Это уточняет его же решение 16.08 («штрафовать только за перевирание смысла»): +# вторым браком объявляется не вольность, а ПРОСВЕЧИВАЮЩИЙ ИСХОДНЫЙ ЯЗЫК. +_BRAK = ( + "БРАКУЮТСЯ ДВА ИСХОДА, и второй так же строго, как первый.\n" + "Первый — перевранный смысл: факт, смысловой ход, канон.\n" + "Второй — текст, сквозь который просвечивает исходный язык: чужой синтаксис, переведённо\n" + "звучащая фраза, отсутствие живого русского. Машинный перевод бракуется наравне с враньём.\n" + "Перевод — отчасти творчество: не переврав смысла, напиши так, чтобы звучало по-русски\n" + "естественно. Вольность не проверяет никто.\n\n" + "Ниже — то, что относится к первому браку и к границам второго:") +# ⚠⚠ ХВОСТ РЕЖЕТСЯ В ОБОИХ АРМАХ, И ЭТО ПОЧИНКА ДИЗАЙНА, А НЕ ФОРМУЛИРОВКИ. Приёмка Fable-5 +# поймала: после того как императив самопроверки оправдан замером (его не было на момент покупки), +# ведущим подозреваемым становится ХВОСТОВАЯ СТРОКА «не нарушает ни одной из осей» — единственное +# оставшееся место, где смелость ОБУСЛОВЛЕНА полной верификацией пяти осей. В первой редакции +# обоих резов она стояла НЕТРОНУТОЙ, то есть при исходе «оба не сработали» вывод «рамка ни при +# чём» был бы неинтерпретируем: ведущий подозреваемый не вырезан нигде. +_TAIL_OLD = ("Вольность в пределах этих пяти ошибкой НЕ СЧИТАЕТСЯ. Текст, который читается лучше " + "и не нарушает\nни одной из осей, лучше текста, который следует за буквой.") +# ⚠ Формулировка второго брака — СЛОВА ВЛАДЕЛЬЦА 20.08: «присутствие одного языка в другом … +# полное отсутствие художественности и родного звучания … машинный перевод грубо говоря … перевод +# это отчасти какое-то но творчество, без перевирания смысла нужно написать текст, чтоб он звучал +# естественно». Правки по приёмке: убран дубль «так же строго»/«наравне с враньём»; «отсутствие +# живого русского» → «мёртвый русский» (проверка СВОЙСТВА, а не отсутствия — последняя глобальна +# по построению); строка-мост исправлена (ЯЗЫК и РЕГИСТР — СУЩЕСТВО второго брака, а не «границы»). +_BRAK = ( + "БРАКУЮТСЯ ДВА ИСХОДА, и второй так же строго, как первый.\n" + "Первый — перевранный смысл: факт, смысловой ход, канон.\n" + "Второй — текст, сквозь который просвечивает исходный язык: чужой синтаксис, переведённо\n" + "звучащая фраза, мёртвый русский. Машинный перевод — брак, даже когда в нём всё верно.\n" + "Перевод — отчасти творчество: не переврав смысла, напиши так, чтобы звучало по-русски\n" + "естественно. Вольность не проверяет никто.\n\n" + "Первому браку служат СМЫСЛ, ПОЛНОТА и КАНОН; второму — ЯЗЫК и РЕГИСТР:") +CUT = { + # RB: риск ПЕРЕВЁРНУТ. Хвост заменён не условием, а ПЕРЕОПРЕДЕЛЕНИЕМ: смелость перестаёт быть + # разрешённым исключением и становится проверяемым требованием. + "RB": [("ПО ЧЕМУ БУДЕТ ПРОВЕРЕН РЕЗУЛЬТАТ. Это же и есть полный список того, что считается " + "ошибкой —\nдругих запретов нет:", _BRAK), + (_TAIL_OLD, + "Живая фраза вместо буквы — не вольность, а исполнение второго требования.")], + # RC: экзамена нет вовсе. Хвост тоже без условия верификации и без слова «оси», которое в RC + # повисало бы без референта — выше ни одна вещь осью не названа. + "RC": [("ПО ЧЕМУ БУДЕТ ПРОВЕРЕН РЕЗУЛЬТАТ. Это же и есть полный список того, что считается " + "ошибкой —\nдругих запретов нет:", "ЧЕГО НЕЛЬЗЯ:"), + (_TAIL_OLD, + "Всё, что за пределами этих пяти, — твоя воля; живая фраза вместо буквы — норма, " + "а не риск.")], +} FREE = ("ZD", "Z0", "ZP") PRIMARY = ("RN", "ZP") # единственный несущий контраст, см. шапку @@ -155,7 +231,7 @@ def pair_blocks(p: str) -> tuple[str, str]: return _block(tr_core, UNITS_HEAD), _block(ed_core, REFLOW_HEAD) -def rol_msgs(p: str, src: str) -> list[dict]: +def rol_msgs(p: str, src: str, arm: str = "RN") -> list[dict]: """Однопроходка НОВЫМ промтом: роль, рамки, регистр, критерии проверки, пар-блоки, глоссарий.""" cfg = PAIRS[p] cfg["wire"]() @@ -165,6 +241,11 @@ def rol_msgs(p: str, src: str) -> list[dict]: raise SystemExit(f"⛔ у пары {p} нет блока «{UNITS_HEAD}» либо «{REFLOW_HEAD}» — " "промт собрать нечем, арм не строится") core, _few, user = PR.load_template(CORE) + for was, now in CUT.get(arm, []): + if was not in core: + raise SystemExit(f"⛔ у арма {arm} не найден заменяемый кусок «{was[:40]}…» — " + "промт изменился под замером, СТОП") + core = core.replace(was, now) core = (core.replace("{{ПАР_ЕДИНИЦЫ}}", units_blk) .replace("{{ПАР_ПЕРЕВЁРСТКА}}", reflow_blk)) brief = getattr(PR, PR.PAIR_BRIEF[key]) @@ -359,12 +440,19 @@ def call_kwargs(model: str, msgs: list[dict]) -> dict: # ── ПОКУПКА ────────────────────────────────────────────────────────────────────────────────── +# ⚠ АБЛЯЦИИ НЕ НУЖНА ВСЯ ПАНЕЛЬ. Эффект, который ищется, огромен (×28 размышления), и виден он +# на трёх главах; тратить на него шестнадцать значит выбрать между двумя вариантами вместо того, +# чтобы купить оба. Ограничитель берёт ПЕРВЫЕ N глав отбора — тот же детерминированный порядок, +# значит абляция сравнима с уже купленным `RN` глава в главу. +N_LIMIT = next((int(a.split("=", 1)[1]) for a in sys.argv if a.startswith("--n=")), 0) + + def buy(p: str, which: list[str]) -> None: ZK.price_gate() cfg = PAIRS[p] cfg["wire"]() ZK.preflight([ARMS[a][0] for a in which], cfg["phase"], cfg["letter"]) - us = chosen(p) + us = chosen(p)[:N_LIMIT] if N_LIMIT else chosen(p) led = MONEY.Guarded(cfg["phase"], default_expect=0.03) print(f"\nпара {p} · глав {len(us)} · армы {' '.join(which)} · касса {cfg['phase']}") for arm in which: @@ -380,7 +468,7 @@ def buy(p: str, which: list[str]) -> None: # часами, и пересёкший 16:00 UTC цикл писал бы вызовы по вчерашнему прайсу. ZK.price_gate(quiet=True) rec = MONEY.purchase(led, tg, model, ED22.client(model), - call_kwargs(model, rol_msgs(p, src_of(p, arm, u))), + call_kwargs(model, rol_msgs(p, src_of(p, arm, u), arm)), role="onepass", uid=u["uid"], arm=arm, gen=gen, pair=p, podacha="абзацы" if arm in RESTORE_ARMS else "плоско") if rec.get("skipped"): diff --git a/eval/role_topology/buy.py b/eval/role_topology/buy.py index 7c60354f..1478cd69 100644 --- a/eval/role_topology/buy.py +++ b/eval/role_topology/buy.py @@ -19,6 +19,7 @@ from __future__ import annotations import json import time +import os from pathlib import Path from prices import cost @@ -78,7 +79,15 @@ def usage_of(u, ch) -> dict: return dict(prompt_tokens=u.prompt_tokens or 0, cached_tokens=cached, completion_tokens=u.completion_tokens or 0, reasoning_tokens=reasoning, total_tokens=getattr(u, "total_tokens", 0) or 0, - reasoning_chars=len(getattr(msg, "reasoning_content", None) or "")) + reasoning_chars=len(getattr(msg, "reasoning_content", None) or ""), + # ⚠ САМ ТРЕЙС РАЗМЫШЛЕНИЯ — ТОЛЬКО ПО ЯВНОМУ ФЛАГУ, и это не осторожность, а вес: + # у dspro он бывает 30+ тыс. знаков на клетку, то есть удваивает файл сырья, а + # нужен он редко. Провайдер отдаёт его в ответе БЕСПЛАТНО, но `usage_of` до 20.08 + # сохранял одну ДЛИНУ — и когда замер промта дал ×28 размышления, прочитать, + # на что оно потрачено, оказалось нечем: пришлось бы пере-покупать клетки. + # Флаг `TM_KEEP_TRACE=1` включает сохранение для абляционных прогонов. + **({"reasoning_text": getattr(msg, "reasoning_content", None) or ""} + if os.environ.get("TM_KEEP_TRACE") == "1" else {})) def priced(model: str, us: dict) -> float: