Freeze the role-built one-pass prompt, its buying code and the sanctioned ceilings before the first cell
This commit is contained in:
parent
a335acc0af
commit
7f15323a8d
3 changed files with 469 additions and 2 deletions
|
|
@ -81,6 +81,8 @@ TAGS = {
|
|||
"ФД-J": ("dv-j-*.json",), # Д17: заход под починенную рубрику, английская пара
|
||||
"ФД-K": ("dv-k-*.json",), # Д22: подача входа критику, китайская пара
|
||||
"ФД-L": ("dv-l-*.json",), # Д22: подача входа критику, английская пара
|
||||
"ФД-M": ("dv-m-*.json",), # Д23: однопроходка как РОЛЬ, китайская пара
|
||||
"ФД-N": ("dv-n-*.json",), # Д23: однопроходка как РОЛЬ, английская пара
|
||||
}
|
||||
# ⚠ Потолки фаз = смета `plan.py` плюс МИНИМАЛЬНЫЙ запас, так чтобы их СУММА не превышала
|
||||
# пакетный потолок. Первая редакция округляла каждый вверх до цента и дала сумму 4.54 > 4.50 —
|
||||
|
|
@ -231,8 +233,9 @@ TAGS = {
|
|||
# времени суток. В офф-пик спишется около $1.09 вместо $1.51. Касса букирует ПИК осознанно.
|
||||
CEILINGS = {"ФД-A": 1.910, "ФД-B": 0.900, "ФД-C": 0.370, "ФД-D": 0.050,
|
||||
"ФД-E": 2.290, "ФД-F": 0.500, "ФД-G": 0.060, "ФД-H": 0.090,
|
||||
"ФД-I": 3.950, "ФД-J": 1.950, "ФД-K": 0.400, "ФД-L": 1.300}
|
||||
PACK_CEILING = 13.80
|
||||
"ФД-I": 3.950, "ФД-J": 1.950, "ФД-K": 0.400, "ФД-L": 1.300,
|
||||
"ФД-M": 2.100, "ФД-N": 2.400}
|
||||
PACK_CEILING = 18.30
|
||||
# ⚠ ТРЕТИЙ ПОДЪЁМ ФД-I — ЭТО МОЯ ОШИБКА ОЦЕНКИ, А НЕ РАСШИРЕНИЕ ЗАКАЗАННОГО, И ЗАПИСЫВАЮ ЕЁ ТАК.
|
||||
# Владелец санкционировал словом «Поднимай» ПЯТЬ клеток шумового пола. Три купились, две не
|
||||
# влезли: я считал китайскую клетку пола по $0.059, а фактическая цена оказалась $0.098 —
|
||||
|
|
|
|||
76
eval/dovodka/prompts/onepass-core.md
Normal file
76
eval/dovodka/prompts/onepass-core.md
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
<!-- ЯДРО ПРОМТА ОДНОПРОХОДКИ. Общее для всех пар; пар-специфика подставляется двумя блоками
|
||||
из файлов САМОЙ ПАРЫ (см. `rol.py`): «Единицы и приёмы» из её translator.md и
|
||||
«ДИСКУРС-ПЕРЕВЁРСТКА» из её editor.md. Второго источника правды не заводится: пар-блоки
|
||||
остаются там, где они уже живут и где их сторожит гейт пар-промтов. Пара, которой в
|
||||
репозитории ещё НЕТ, заводится своими двумя блоками и работает без правки кода.
|
||||
|
||||
ЧЕМ ЭТОТ ПРОМТ ОТЛИЧАЕТСЯ ОТ ПРЕЖНЕЙ ОДНОПРОХОДКИ И ПОЧЕМУ — каждый пункт грунтован замером,
|
||||
а не вкусом:
|
||||
· прежний промт был СКЛЕЙКОЙ (промт переводчика + мандатная часть промта редактора,
|
||||
четыре строки выброшены, четыре подставлены) и нёс мета-фразу про наш конвейер
|
||||
«отдельного редактора после тебя НЕ БУДЕТ». Склейка сделана ради честности контраста
|
||||
(уравнять ОБЪЁМ инструкции), но продуктовым промтом она не была ни разу;
|
||||
· ВОЛЬНОСТЬ ЯВНО РАЗРЕШЕНА. Решение владельца 16.08: «штрафовать за вольность нельзя, живой
|
||||
язык — приоритет; штрафовать можно только за перевирания смысла». Ни один боевой промт
|
||||
такого разрешения не давал — оба только ЗАПРЕЩАЛИ. Модель осторожничала законно;
|
||||
· РЕГИСТР назван отдельной рамкой. Слепое чтение владельца 17.08 забраковало боевой перевод
|
||||
именно по регистру: ярость передана словом «шествовала», мат смягчён там, где на мате
|
||||
держится сцена, вежливое «Пойдёмте» в реплике, которую выплёвывают. Ни в одном промте
|
||||
слова «регистр» нет вовсе;
|
||||
· КРИТЕРИИ ПРОВЕРКИ напечатаны в самом промте — прямое требование владельца 20.08: «зажми
|
||||
рамками и по каким критериям мы будем проверять результат». Это же снимает противоречие,
|
||||
на котором модель раньше зажималась: она видит, что вольность в рамках не штрафуется;
|
||||
· мета-фраз про устройство конвейера нет ни одной. -->
|
||||
Ты — литературный переводчик. Твой текст идёт в книгу, которую будут читать по-русски ради
|
||||
удовольствия. Читатель не должен чувствовать, что перед ним перевод.
|
||||
|
||||
Книга: «{{title}}». Жанр: {{genre}}. Аудитория: {{audience}}.
|
||||
Язык исходника — «{{source_lang}}», язык перевода — «{{target_lang}}».
|
||||
Хонорифики: {{honorifics}}. Транскрипция имён и реалий: {{transcription}}.
|
||||
Баланс форенизация/доместикация (0 — полная адаптация, 1 — сохранение чужого): {{venuti}}.
|
||||
Сноски: {{footnotes}}.
|
||||
|
||||
ТЫ ВПРАВЕ — и это не оговорка, а часть задания:
|
||||
переставлять предложения · сливать и дробить абзацы · менять синтаксис до неузнаваемости ·
|
||||
заменять оборот на более живой русский · добавлять связки, которых нет в исходнике ·
|
||||
разворачивать идиому и сворачивать описание · выбирать слово, которого в словаре напротив
|
||||
исходного не стоит. Буквальность здесь не достоинство. Если выбор стоит между точной калькой и
|
||||
живой русской фразой — бери живую фразу.
|
||||
|
||||
ЧЕГО НЕЛЬЗЯ — четыре запрета, и они жёсткие:
|
||||
1. Перевирать факт: участника действия, отрицание, число, ранг, меру, родство, кто кому что
|
||||
сказал. Полярность и адресат неприкосновенны.
|
||||
2. Терять смысловые ходы. Дробить и сливать можно; выбрасывать нельзя. Ничего не добавляй от себя
|
||||
как факт — связка и уточнение допустимы, новое событие нет.
|
||||
3. Менять канон. Имена, термины и реалии — строго в тех формах, что даны в глоссарии ниже,
|
||||
склоняя по контексту. Своих вариантов не придумывай.
|
||||
4. Оставлять в выводе исходный язык, служебные преамбулы, комментарии, пояснения, заметки о
|
||||
решениях и markdown-заголовки.
|
||||
|
||||
РЕГИСТР — держи тот же градус, что в исходнике:
|
||||
грубость передавай грубостью, ярость — яростью, спокойствие — спокойствием. Брань не смягчай и не
|
||||
заменяй эвфемизмом: если персонаж ругается, он ругается. Не поднимай слог — там, где в исходнике
|
||||
простое резкое слово, книжное слово будет ошибкой. Вежливость есть свойство персонажа, а не
|
||||
переводчика: не добавляй вежливых форм и смягчающих оборотов, которых в реплике нет. Речевой
|
||||
портрет героя (просторечие, архаика, чужой акцент) передавай лексикой и синтаксисом.
|
||||
|
||||
ПО КАКИМ КРИТЕРИЯМ БУДЕТ ПРОВЕРЕН РЕЗУЛЬТАТ — проверь себя по ним сам, молча, перед выводом:
|
||||
* СМЫСЛ — ни один факт не перевран (см. запрет 1);
|
||||
* ПОЛНОТА — каждый смысловой ход исходника присутствует в переводе;
|
||||
* КАНОН — каждый термин глоссария стоит в канонной форме;
|
||||
* ЯЗЫК — в тексте нет ни одной фразы, которую носитель русского не написал бы сам: ни кальки,
|
||||
ни канцелярита, ни переводного суржика;
|
||||
* РЕГИСТР — градус тот же, что в исходнике.
|
||||
Вольность в пределах этих критериев ошибкой НЕ СЧИТАЕТСЯ. Текст, который читается лучше и не
|
||||
нарушает ни одного из пяти, лучше текста, который следует за буквой.
|
||||
|
||||
{{ПАР_ЕДИНИЦЫ}}
|
||||
|
||||
{{ПАР_ПЕРЕВЁРСТКА}}
|
||||
|
||||
Выведи ТОЛЬКО перевод.
|
||||
|
||||
---USER---
|
||||
Переведи следующий фрагмент:
|
||||
|
||||
{{text}}
|
||||
388
eval/dovodka/rol.py
Normal file
388
eval/dovodka/rol.py
Normal file
|
|
@ -0,0 +1,388 @@
|
|||
#!/usr/bin/env python3
|
||||
"""ОДНОПРОХОДКА КАК РОЛЬ — новый промт против прежней склейки, на нескольких моделях. Платный.
|
||||
|
||||
⚠ ЗАЧЕМ. Однопроходка — единственный живой продуктовый кандидат из всего, что дуга 19→23
|
||||
измерила: по судейской оси от боевой связки не отличается, по банку терминов ЛУЧШЕ неё на обеих
|
||||
парах (потерянных терминов на главу 1.38 против 2.88 на zh, 0.31 против 0.56 на en), стоит ОДИН
|
||||
вызов вместо двух, и при слепом чтении владелец поставил её ПЕРВОЙ на английском.
|
||||
|
||||
⚠⚠ И всё это она делает С ГАНДИКАПОМ. Её промт (`contour.a2_msgs`) — не продуктовый, а
|
||||
ИЗМЕРИТЕЛЬНЫЙ: боевой промт переводчика плюс мандатная часть боевого промта редактора,
|
||||
механически склеенные (четыре строки выброшены, четыре подставлены), с прямой мета-фразой про наш
|
||||
конвейер — «отдельного редактора после тебя НЕ БУДЕТ». Склейка сделана по уважительной причине:
|
||||
в эксп-21 у однопроходки системный промт был вдвое короче, и замеренная разница могла быть
|
||||
разницей ОБЪЁМА ИНСТРУКЦИИ, а не топологии. Уравнивание было правильным ДЛЯ ЗАМЕРА — но
|
||||
следствие в том, что однопроходка ни разу не испытывалась как нормально написанный промт-роль.
|
||||
|
||||
ЧТО ПОКУПАЕТСЯ (имена армов ОДНИ на обе пары, пара живёт в данных):
|
||||
|
||||
ZP ПРЕЖНЯЯ СКЛЕЙКА, deepseek-v4-pro БЕСПЛАТНО, куплена заходом Д17 — это база
|
||||
Z0 боевая связка (черновик → перепис) БЕСПЛАТНО — эталон продакшена
|
||||
ZF вторая генерация связки БЕСПЛАТНО — пол СТАРОЙ панели
|
||||
ZD голый черновик БЕСПЛАТНО — якорь низа
|
||||
RN НОВАЯ РОЛЬ, deepseek-v4-pro, генерация 1 покупается — несущий арм
|
||||
RN2 НОВАЯ РОЛЬ, deepseek-v4-pro, генерация 2 покупается — СВОЙ пол новой панели
|
||||
RG НОВАЯ РОЛЬ, glm-5 покупается — переносимость, другой вендор
|
||||
RK НОВАЯ РОЛЬ, grok-4.3 покупается — переносимость, третья семья
|
||||
|
||||
⚠ ПЕРВИЧНЫЙ КОНТРАСТ ОДИН: `RN/ZP` — новый промт против прежней склейки НА ТОЙ ЖЕ МОДЕЛИ и ТЕХ ЖЕ
|
||||
главах. Всё остальное описательное. Одного контраста требует мощность: при n=16 и поправке Холма
|
||||
по четырём контрастам MDE вырос бы выше цели, и ось не дала бы вывода ни при каком исходе — ровно
|
||||
та болезнь, что погубила английскую ось эксп-22.
|
||||
|
||||
⚠ ТРЕТЬЯ МОДЕЛЬ — `grok-4.3`, и выбор не случаен. Главная претензия владельца при слепом чтении
|
||||
была РЕГИСТР: «смягчённый мат там, где на мате держится сцена». xAI — единственный вендор, у
|
||||
которого слова `violen*` в правилах нет вовсе, и модель меньше прочих склонна смягчать.
|
||||
⚠ `gemini` в панель НЕ берётся: на этом материале он массово падает в контент-фильтр (эксп-21:
|
||||
10 клеток судейского прогона с `PROHIBITED_CONTENT`), и его пустые клетки мерили бы фильтр, а не
|
||||
промт. `grok-4.3` идёт с ВКЛЮЧЁННЫМ размышлением (вендор-дефолт `low`): запрет D30.1 прямой —
|
||||
grok с reasoning-off в редакторской роли есть no-op-редактор.
|
||||
|
||||
Запуск: rol.py --show [zh|en] | --dry | --selftest | --buy [zh|en] [армы…] | --canon
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import statistics as st
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path("/home/ubuntu/projects/textmachine")
|
||||
ZONE = Path(__file__).resolve().parent
|
||||
for d in ("dovodka", "editor_tier", "tenant_panel", "role_topology", "bank_arbitration"):
|
||||
sys.path.insert(0, str(REPO / "eval" / d))
|
||||
|
||||
|
||||
def _load(name: str, path: Path):
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
sys.modules[name] = mod
|
||||
spec.loader.exec_module(mod)
|
||||
return mod
|
||||
|
||||
|
||||
# ⚠ Контур берётся У ДРАЙВЕРА английской оси, а не грузится вторым экземпляром: `en_axis` держит
|
||||
# СВОЙ `contour.py`, и `wire()` настраивает хуки именно в нём. Свой второй экземпляр остался бы с
|
||||
# китайскими хуками, и английская ось молча стала бы пустой. Класс уже стоил фазе оплаченных клеток.
|
||||
EN = _load("en_axis_r", ZONE / "en_axis.py")
|
||||
C = EN.C
|
||||
MONEY, PR, ROSTER, ED22, PARA = C.MONEY, C.PR, C.ROSTER, C.ED22, C.PARA
|
||||
OUT = MONEY.OUT
|
||||
ZK = _load("zakhod_r", ZONE / "zakhod.py") # гейт перелома цены, пред-полёт, отбор глав
|
||||
|
||||
CORE = ZONE / "prompts" / "onepass-core.md"
|
||||
MAX_OUT = 32000 # как в заходе Д17: обрыв на потолке стоит денег и не даёт данных
|
||||
|
||||
# ── ПАРА КАК ДАННЫЕ. Ни одна ветка ниже не спрашивает «а это китайский?» ──────────────────────
|
||||
PAIRS = {
|
||||
"zh": {"letter": "m", "phase": "ФД-M", "onepass": "A2",
|
||||
"wire": lambda: C.configure(PARA.ZH), "units": lambda: C.units_ok(),
|
||||
"para": lambda: PARA.ZH},
|
||||
"en": {"letter": "n", "phase": "ФД-N", "onepass": "E2",
|
||||
"wire": EN.wire, "units": EN.units, "para": lambda: PARA.EN},
|
||||
}
|
||||
# арм → (модель, номер генерации). Генерация нужна, чтобы два прогона ОДНОЙ модели не схлопнулись
|
||||
# в один тег: кэш `purchase` смотрит на существование файла и вернул бы первую клетку второй раз.
|
||||
ARMS = {"RN": ("deepseek-v4-pro", 1), "RN2": ("deepseek-v4-pro", 2),
|
||||
"RG": ("glm-5", 1), "RK": ("grok-4.3", 1)}
|
||||
FREE = ("ZD", "Z0", "ZP")
|
||||
PRIMARY = ("RN", "ZP") # единственный несущий контраст, см. шапку
|
||||
|
||||
# ── СБОРКА ПРОМТА ────────────────────────────────────────────────────────────────────────────
|
||||
# ⚠ ПАР-БЛОКИ НЕ ДУБЛИРУЮТСЯ, А ПОДТЯГИВАЮТСЯ ИЗ ФАЙЛОВ САМОЙ ПАРЫ. Второй экземпляр «Единиц и
|
||||
# приёмов» разъехался бы с боевым молча, и замер мерил бы промт, которого в бою нет. Гейт
|
||||
# пар-промтов сторожит те файлы, а не мою копию, — значит копии быть не должно.
|
||||
UNITS_HEAD = "Единицы и приёмы"
|
||||
REFLOW_HEAD = "ДИСКУРС-ПЕРЕВЁРСТКА"
|
||||
|
||||
|
||||
def _block(core: str, head: str) -> str:
|
||||
"""Секция от заголовка до пустой строки перед следующим заголовком верхнего уровня."""
|
||||
i = core.find(head)
|
||||
if i < 0:
|
||||
return ""
|
||||
tail = core[i:]
|
||||
# секция кончается там, где начинается строка «Выведи ТОЛЬКО» или следующий ЗАГОЛОВОК КАПСОМ
|
||||
stop = len(tail)
|
||||
for mark in ("\nВыведи ТОЛЬКО", "\nХонорифики:"):
|
||||
j = tail.find(mark)
|
||||
if 0 <= j < stop:
|
||||
stop = j
|
||||
return tail[:stop].strip()
|
||||
|
||||
|
||||
def pair_blocks(p: str) -> tuple[str, str]:
|
||||
"""(«Единицы и приёмы» пары, «ДИСКУРС-ПЕРЕВЁРСТКА» пары) — из её же боевых файлов."""
|
||||
d = PR.PAIR_DIR[PAIRS[p]["para"]().key]
|
||||
tr_core, _f, _u = PR.load_template(d / "translator.md")
|
||||
ed_core, _f2, _u2 = PR.load_template(d / "editor.md")
|
||||
return _block(tr_core, UNITS_HEAD), _block(ed_core, REFLOW_HEAD)
|
||||
|
||||
|
||||
def rol_msgs(p: str, src: str) -> list[dict]:
|
||||
"""Однопроходка НОВЫМ промтом: роль, рамки, регистр, критерии проверки, пар-блоки, глоссарий."""
|
||||
cfg = PAIRS[p]
|
||||
cfg["wire"]()
|
||||
key = cfg["para"]().key
|
||||
units_blk, reflow_blk = pair_blocks(p)
|
||||
if not units_blk or not reflow_blk:
|
||||
raise SystemExit(f"⛔ у пары {p} нет блока «{UNITS_HEAD}» либо «{REFLOW_HEAD}» — "
|
||||
"промт собрать нечем, арм не строится")
|
||||
core, _few, user = PR.load_template(CORE)
|
||||
core = (core.replace("{{ПАР_ЕДИНИЦЫ}}", units_blk)
|
||||
.replace("{{ПАР_ПЕРЕВЁРСТКА}}", reflow_blk))
|
||||
brief = getattr(PR, PR.PAIR_BRIEF[key])
|
||||
sysmsg = PR.render(core, brief, src, "")
|
||||
# ⚠ БАНК-ЗАКОН НА ВСЕХ АРМАХ БЕЗ ИЗЪЯТИЙ (норма D39.104). Прежнее отступление — 72 клетки без
|
||||
# блока — владелец пометил как дефект. Термы берутся ТОЛЬКО релевантные этому фрагменту:
|
||||
# прямое требование брифа V6 «не давать слишком много контекста».
|
||||
bf = None if key == "zh-ru" else cfg["para"]().bank_file
|
||||
law = PR.law_block(PR.terms_in(src, bf), bf)
|
||||
out = [{"role": "system", "content": sysmsg}]
|
||||
if law:
|
||||
# ⚠ БАНК-ЗАКОН НАПИСАН ДЛЯ ДВУХСТАДИЙНОГО ПУТИ и говорит «в ЧЕРНОВИКЕ термин слева должен
|
||||
# быть передан формой справа». У однопроходки черновика нет вовсе: модель получила бы
|
||||
# указание про текст, которого не существует. Поймано селфтестом ДО покупки. Правится
|
||||
# ровно одно слово и ровно здесь — трогать общий `law_block` нельзя, он едет в бою.
|
||||
out.append({"role": "system", "content": law.replace("в черновике", "в переводе")})
|
||||
out.append({"role": "user", "content": PR.render(user, brief, src, "")})
|
||||
return out
|
||||
|
||||
|
||||
def tag(p: str, arm: str, uid: str) -> str:
|
||||
return f"dv-{PAIRS[p]['letter']}-{arm.lower()}-{uid}"
|
||||
|
||||
|
||||
def cell(tg: str) -> str:
|
||||
f = OUT / f"{tg}.json"
|
||||
if not f.exists():
|
||||
return ""
|
||||
d = json.loads(f.read_text(encoding="utf-8"))
|
||||
return (d.get("content") or "") if d.get("finish") == "stop" else ""
|
||||
|
||||
|
||||
def free_text(p: str, arm: str, uid: str) -> str:
|
||||
"""Бесплатные армы берутся у рига захода Д17 — те же главы, те же тексты, $0."""
|
||||
return ZK.free_text(p, arm, uid)
|
||||
|
||||
|
||||
def chosen(p: str) -> list[dict]:
|
||||
"""ТЕ ЖЕ главы, что у захода Д17 — иначе бесплатные армы не переиспользуются и база уедет."""
|
||||
return ZK.chosen(p)
|
||||
|
||||
|
||||
def call_kwargs(model: str, msgs: list[dict]) -> dict:
|
||||
"""Боевая конфигурация модели. Отступлений нет: у `grok-4.3` вендор-дефолт = размышление ON,
|
||||
а запрет D30.1 прямой — reasoning-off в редакторской роли даёт no-op-редактора."""
|
||||
return ROSTER.call_kwargs(model, msgs, max_out=MAX_OUT)
|
||||
|
||||
|
||||
# ── ПОКУПКА ──────────────────────────────────────────────────────────────────────────────────
|
||||
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)
|
||||
led = MONEY.Guarded(cfg["phase"], default_expect=0.03)
|
||||
print(f"\nпара {p} · глав {len(us)} · армы {' '.join(which)} · касса {cfg['phase']}")
|
||||
for arm in which:
|
||||
model, gen = ARMS[arm]
|
||||
for u in us:
|
||||
tg = tag(p, arm, u["uid"])
|
||||
if cell(tg).strip():
|
||||
continue
|
||||
# ⚠ Гейт перелома цены — перед КАЖДОЙ клеткой, а не раз на старте: прогон длится
|
||||
# часами, и пересёкший 16:00 UTC цикл писал бы вызовы по вчерашнему прайсу.
|
||||
ZK.price_gate(quiet=True)
|
||||
rec = MONEY.purchase(led, tg, model, ED22.client(model),
|
||||
call_kwargs(model, rol_msgs(p, u["source"])),
|
||||
role="onepass", uid=u["uid"], arm=arm, gen=gen, pair=p)
|
||||
if rec.get("skipped"):
|
||||
raise SystemExit(f"⛔ ГАРД ОТКАЗАЛ на клетке {tg} — прогон ОСТАНОВЛЕН.\n"
|
||||
" Резать панель или поднимать потолок решением сессии ЗАПРЕЩЕНО.\n"
|
||||
" Это СТОП и вопрос владельцу: назвать недостающую сумму.")
|
||||
if rec.get("finish") != "stop":
|
||||
print(f" ⚠ {tg}: finish={rec.get('finish')} "
|
||||
f"{str(rec.get('error'))[:120]} — клетка объявляется несобранной")
|
||||
elif C.draft_defect(cell(tg)):
|
||||
# штатный гейт годности проекта: эхо исходника вместо перевода ловится ЗДЕСЬ,
|
||||
# иначе негодная клетка уедет в судейскую пачку за полную цену
|
||||
print(f" ⚠ {tg}: {C.draft_defect(cell(tg))} — клетка НЕГОДНА, объявить в отчёте")
|
||||
MONEY.report()
|
||||
|
||||
|
||||
# ── $0-ГЕЙТ КАНОНА: там, где однопроходка уже выигрывала ──────────────────────────────────────
|
||||
def canon() -> None:
|
||||
print("КАНОН-ГЕЙТ ($0, детерминированно): потерянных терминов банка на главу, МЕНЬШЕ лучше\n")
|
||||
for p, cfg in PAIRS.items():
|
||||
cfg["wire"]()
|
||||
us = chosen(p)
|
||||
print(f"=== пара {p} · глав {len(us)} ===")
|
||||
for arm in FREE + tuple(ARMS):
|
||||
vals = []
|
||||
for u in us:
|
||||
t = free_text(p, arm, u["uid"]) if arm in FREE else cell(tag(p, arm, u["uid"]))
|
||||
if t.strip():
|
||||
vals.append(len(C.canon_gaps(u["source"], t)))
|
||||
if vals:
|
||||
print(f" {arm:4s} n={len(vals):2d} потерь/глава {st.mean(vals):5.2f}")
|
||||
|
||||
|
||||
# ── СМЕТА И ПРОВЕРКИ ─────────────────────────────────────────────────────────────────────────
|
||||
def price_of(p: str, model: str) -> float:
|
||||
"""Ожидаемая цена клетки — из ЗАМЕРЕННЫХ токенов уже купленных ОДНОПРОХОДОК ЭТОЙ пары,
|
||||
пере-оценённых ТЕКУЩИМ пином. Зашитых цен в файле нет: после пере-пина они врали вчетверо."""
|
||||
toks = []
|
||||
for f in OUT.glob(f"{C.tag(PAIRS[p]['onepass'], '')}*.json"):
|
||||
if any(x in f.name for x in ("ERROR", "LENGTH", "ЭХО", "STALE", "TWIN")):
|
||||
continue
|
||||
r = json.loads(f.read_text(encoding="utf-8"))
|
||||
if r.get("finish") == "stop" and r.get("prompt_tokens"):
|
||||
toks.append((r["prompt_tokens"], r.get("cached_tokens") or 0,
|
||||
r.get("completion_tokens") or 0, r.get("reasoning_tokens") or 0,
|
||||
r.get("total_tokens") or 0))
|
||||
if not toks:
|
||||
return 0.03
|
||||
med = [st.median([x[i] for x in toks]) for i in range(5)]
|
||||
# ⚠ Новый промт ДЛИННЕЕ склейки на входе — берём фактическую длину системного промта, а не
|
||||
# длину прежнего: смета, посчитанная по старому входу, занижала бы резерв гарда.
|
||||
grow = _sys_len(p) / max(_sys_len_old(p), 1)
|
||||
return ROSTER.cost(model, int(med[0] * grow), int(med[1]), int(med[2]), int(med[3]),
|
||||
int(med[4]))
|
||||
|
||||
|
||||
def _sys_len(p: str) -> int:
|
||||
u = chosen(p)[0]
|
||||
return sum(len(m["content"]) for m in rol_msgs(p, u["source"]))
|
||||
|
||||
|
||||
def _sys_len_old(p: str) -> int:
|
||||
PAIRS[p]["wire"]()
|
||||
u = chosen(p)[0]
|
||||
return sum(len(m["content"]) for m in C.a2_msgs(u["source"]))
|
||||
|
||||
|
||||
def cmd_show(p: str) -> None:
|
||||
"""Промт целиком, глазами. Обязательная вычитка ПЕРЕД покупкой."""
|
||||
u = chosen(p)[0]
|
||||
for m in rol_msgs(p, u["source"]):
|
||||
body = m["content"]
|
||||
if m["role"] == "user":
|
||||
body = body.split("Переведи следующий фрагмент:")[0] + \
|
||||
"Переведи следующий фрагмент:\n\n<текст главы, %d знаков>" % len(u["source"])
|
||||
print(f"\n────────── {m['role'].upper()} ──────────\n{body}")
|
||||
|
||||
|
||||
def cmd_dry() -> None:
|
||||
print("ОДНОПРОХОДКА КАК РОЛЬ · главы те же, что у захода Д17\n")
|
||||
ZK.price_gate()
|
||||
tot = 0.0
|
||||
for p, cfg in PAIRS.items():
|
||||
cfg["wire"]()
|
||||
us = chosen(p)
|
||||
print(f"\n=== пара {p} · глав {len(us)} · касса {cfg['phase']} ===")
|
||||
print(f" системный промт: новый {_sys_len(p)} знаков против склейки {_sys_len_old(p)} "
|
||||
f"({_sys_len(p) / max(_sys_len_old(p), 1):.2f}×)")
|
||||
sub = 0.0
|
||||
for arm, (model, _g) in ARMS.items():
|
||||
need = sum(1 for u in us if not cell(tag(p, arm, u["uid"])).strip())
|
||||
pr = price_of(p, model)
|
||||
sub += need * pr
|
||||
print(f" {arm:4s} {model:18s} надо {need:2d} × ${pr:.5f} = ${need * pr:.4f}")
|
||||
sp = MONEY.Guarded(cfg["phase"]).spent()
|
||||
ceil = MONEY.CEILINGS.get(cfg["phase"], 0.0)
|
||||
mark = "ВЛЕЗАЕТ" if sub <= ceil - sp else f"НЕ ВЛЕЗАЕТ (не хватает ${sub - (ceil - sp):.4f})"
|
||||
print(f" ИТОГО ${sub:.4f} · потолок ${ceil:.2f} · потрачено ${sp:.4f} → {mark}")
|
||||
tot += sub
|
||||
print(f"\nВЕСЬ ЗАМЕР: ${tot:.4f} по текущему (пиковому) пину. В офф-пик DeepSeek вдвое дешевле.")
|
||||
|
||||
|
||||
def cmd_selftest() -> int:
|
||||
bad = 0
|
||||
|
||||
def ck(n: str, ok: bool, d: str = "") -> None:
|
||||
nonlocal bad
|
||||
bad += not ok
|
||||
print(f"[{'OK ' if ok else 'ПРОВАЛ'}] {n}" + (f" {d}" if d else ""))
|
||||
|
||||
for p, cfg in PAIRS.items():
|
||||
cfg["wire"]()
|
||||
us = chosen(p)
|
||||
ck(f"{p}: глав ровно 16", len(us) == 16, f"их {len(us)}")
|
||||
ck(f"{p}: все три бесплатных арма есть на каждой главе",
|
||||
all(free_text(p, a, u["uid"]).strip() for a in FREE for u in us))
|
||||
u = us[0]
|
||||
m = rol_msgs(p, u["source"])
|
||||
sysall = "\n".join(x["content"] for x in m if x["role"] == "system")
|
||||
# ⚠ ПАР-БЛОКИ ДОЕХАЛИ. Пустой блок сделал бы промт беднее боевого, и замер сравнивал бы
|
||||
# не «роль против склейки», а «роль минус пар-специфика против склейки».
|
||||
ub, rb = pair_blocks(p)
|
||||
ck(f"{p}: блок «{UNITS_HEAD}» пары подтянут", len(ub) > 200, f"{len(ub)} знаков")
|
||||
ck(f"{p}: блок «{REFLOW_HEAD}» пары подтянут", len(rb) > 400, f"{len(rb)} знаков")
|
||||
ck(f"{p}: оба блока стоят В ПРОМТЕ", ub[:60] in sysall and rb[:60] in sysall)
|
||||
ck(f"{p}: плейсхолдеры раскрыты все до одного", "{{" not in sysall)
|
||||
# ⚠ ГЛАВНОЕ ОТЛИЧИЕ ОТ СКЛЕЙКИ — мета-фразы про конвейер быть не должно
|
||||
ck(f"{p}: мета-фразы про отсутствующего редактора НЕТ",
|
||||
"НЕ БУДЕТ" not in sysall and "после тебя" not in sysall)
|
||||
ck(f"{p}: вольность РАЗРЕШЕНА явно", "ТЫ ВПРАВЕ" in sysall)
|
||||
ck(f"{p}: регистр назван отдельной рамкой", "РЕГИСТР" in sysall)
|
||||
ck(f"{p}: критерии проверки напечатаны в промте",
|
||||
"ПО КАКИМ КРИТЕРИЯМ" in sysall and "ошибкой НЕ СЧИТАЕТСЯ" in sysall)
|
||||
ck(f"{p}: черновик в промт НЕ попадает (однопроходка)",
|
||||
"ЧЕРНОВ" not in sysall.upper() and "черновик" not in sysall.lower())
|
||||
ck(f"{p}: банк-закон на месте либо термов в главе нет",
|
||||
bool(PR.law_block(PR.terms_in(u["source"], None if cfg["para"]().key == "zh-ru"
|
||||
else cfg["para"]().bank_file),
|
||||
None if cfg["para"]().key == "zh-ru"
|
||||
else cfg["para"]().bank_file)) == (len(m) == 3),
|
||||
f"сообщений {len(m)}")
|
||||
ck(f"{p}: пользовательское сообщение несёт ТЕКСТ ГЛАВЫ",
|
||||
u["source"][:80] in m[-1]["content"])
|
||||
ck(f"{p}: ровно одно пользовательское сообщение",
|
||||
sum(1 for x in m if x["role"] == "user") == 1)
|
||||
# промт обязан ОТЛИЧАТЬСЯ от склейки, иначе замер пуст
|
||||
ck(f"{p}: новый промт не равен склейке",
|
||||
sysall.strip() != "\n".join(x["content"] for x in C.a2_msgs(u["source"])
|
||||
if x["role"] == "system").strip())
|
||||
# теги и касса
|
||||
tgs = {tag(p, a, u["uid"]) for a in ARMS}
|
||||
ck(f"{p}: теги армов различны", len(tgs) == len(ARMS))
|
||||
ck(f"{p}: теги покрыты глобом кассы {cfg['phase']}",
|
||||
all(t.startswith(f"dv-{cfg['letter']}-") for t in tgs))
|
||||
ck(f"{p}: фаза заведена в кассе", cfg["phase"] in MONEY.CEILINGS)
|
||||
ck(f"{p}: две генерации несущей модели — РАЗНЫЕ теги",
|
||||
tag(p, "RN", u["uid"]) != tag(p, "RN2", u["uid"]))
|
||||
ck("пары не делят буквы тегов", len({v["letter"] for v in PAIRS.values()}) == len(PAIRS))
|
||||
ck("несущий контраст объявлен один", len(PRIMARY) == 2 and PRIMARY == ("RN", "ZP"))
|
||||
for a, (mo, _g) in ARMS.items():
|
||||
try:
|
||||
ck(f"цена считается для {a} ({mo})",
|
||||
MONEY.M22.BUY.cost(mo, 3000, 0, 3600, 800, 7400) > 0)
|
||||
except Exception as e: # noqa: BLE001
|
||||
ck(f"цена считается для {a} ({mo})", False, f"{type(e).__name__}: {e}")
|
||||
ck("grok идёт с размышлением (D30.1: reasoning-off = no-op-редактор)",
|
||||
"reasoning_effort" not in json.dumps(call_kwargs("grok-4.3",
|
||||
[{"role": "user", "content": "x"}])))
|
||||
print(f"\n{'ЗАМЕР ГОДЕН К ПОКУПКЕ' if not bad else f'ПРОВАЛОВ: {bad}'}")
|
||||
return bad
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
a = sys.argv[1:] or ["--dry"]
|
||||
if a[0] == "--selftest":
|
||||
sys.exit(1 if cmd_selftest() else 0)
|
||||
elif a[0] == "--dry":
|
||||
cmd_dry()
|
||||
elif a[0] == "--show":
|
||||
cmd_show(next((x for x in a[1:] if x in PAIRS), "zh"))
|
||||
elif a[0] == "--canon":
|
||||
canon()
|
||||
elif a[0] == "--buy":
|
||||
pr = [x for x in a[1:] if x in PAIRS] or list(PAIRS)
|
||||
ar = [x for x in a[1:] if x in ARMS] or list(ARMS)
|
||||
for x in pr:
|
||||
buy(x, ar)
|
||||
else:
|
||||
raise SystemExit(f"⛔ неизвестный режим {a[0]!r}; знаю --show --dry --selftest --buy --canon")
|
||||
Loading…
Add table
Reference in a new issue