From d7a202edc6b00a3067dd77f6998cac62fa35eedd Mon Sep 17 00:00:00 2001 From: heaven Date: Sat, 29 Aug 2026 21:32:01 +0300 Subject: [PATCH] Freeze the step-4 pre-registration and prove from bought cells that Gemini drops the FIRST system message, so the engine loses the stage prompt rather than the glossary --- docs/experiments/00-provider-quirks.md | 4 +- docs/experiments/23-editor-tier.md | 245 +++++++++++++++++++++++++ eval/dovodka/rol.py | 17 +- eval/dovodka/sysmsg.py | 111 +++++++++++ 4 files changed, 369 insertions(+), 8 deletions(-) create mode 100644 eval/dovodka/sysmsg.py diff --git a/docs/experiments/00-provider-quirks.md b/docs/experiments/00-provider-quirks.md index 550a096b..28986ac3 100644 --- a/docs/experiments/00-provider-quirks.md +++ b/docs/experiments/00-provider-quirks.md @@ -154,8 +154,8 @@ ## Контент-фильтры / safety -- ⛔⛔ **Gemini, ДВА СИСТЕМНЫХ СООБЩЕНИЯ = ПРОМТ ТЕРЯЕТСЯ МОЛЧА (полигон 22.08, живая проба `eval/dovodka/proba_gemini.py`).** **МЕХАНИЗМ НАЗВАН** (вендор-дока `ai.google.dev/api/generate-content`): в нативном API `systemInstruction` — это ОДИН объект `Content`, поэтому слой ФИЗИЧЕСКИ не может пронести больше одного системного, и уход на нативный API проблемы НЕ решает — там пришлось бы склеивать так же. При двух и более лишние выбрасываются без ошибки. ⚠ КАКОЕ именно выживает — НЕ УСТАНОВЛЕНО: форум Google (discuss.ai.google.dev/t/86097, 30.05.2025) сообщает «выживает последнее», а наш замер даёт 535 токенов ≈ размер одного user-сообщения, то есть не выжило НИ ОДНО. Расхождение открыто. Документированного вендором ограничения в разделе «Current limitations» НЕТ. Замер на одном и том же системном тексте в 7970 знаков: ОДНИМ сообщением → `prompt_tokens` 2994 и маркерная инструкция ИСПОЛНЕНА; ДВУМЯ → **535 токенов (это размер одного user-сообщения) и инструкция НЕ исполнена**. Ответ приходит с `finish=stop` и выглядит правильным переводом — отказа нет, есть тихая потеря инструкции, невидимая всем гейтам годности. ⚠ Так были куплены 16 клеток арма `RE` (промт-роль на gemini): они переведены ВООБЩЕ БЕЗ ИНСТРУКЦИИ, и вердикт о переносимости из них был бы ложным. Лечение: склеивать системные в одно ДЛЯ ЭТОГО ВЕНДОРА (`rol.ONE_SYSTEM`/`fit_msgs`) — общая склейка сменила бы форму запроса у всех моделей и рассогласовала бы уже купленные клетки. ⚠ Проверено, что у `deepseek-v4-pro`, `gpt-5.6-luna`, `glm-5` и `grok-4.3` два системных доезжают целиком. -- ⛔⛔ **ТОТ ЖЕ ДЕФЕКТ ЖИВЁТ В ДВИЖКЕ, А НЕ ТОЛЬКО В ПОЛИГОНЕ (аудит 22.08).** `backend/internal/pipeline/render.go:256-258` штатно строит ВТОРОЕ системное сообщение — инъекцию банка памяти (глоссарий/STM), `backend/internal/llm/httpllm.go:517-522` копирует сообщения дословно, склейки под Gemini нет нигде, а `gemini-3.1-pro-preview` стоит в ратифицированной цепочке эскалации во ВСЕХ арм-конфигах (`pipeline-c1.yaml:129`, `pipeline-arm-glm.yaml:61`, `pipeline-arm-deepseek-pro.yaml:62`, `pipeline-arm-mistral.yaml:61`). ⇒ **любой эскалационный хоп на Gemini при непустой инъекции ТИХО теряет глоссарий.** Оговорка D22.3 «экспозиция Ф1 нулевая» держится только на том, что до Gemini в дефолте не доходят. ⚠ Класс в проекте уже был решён рядом: `backend/internal/llm/provider_anthropic.go:169-179` сворачивает ведущий system-префикс в одно поле и падает громко на system посреди диалога — просто не обобщён на Gemini. **Зона бэкенда, пинг оркестратору отправлен.** +- ⛔⛔ **Gemini, ДВА СИСТЕМНЫХ СООБЩЕНИЯ = ПРОМТ ТЕРЯЕТСЯ МОЛЧА (полигон 22.08, живая проба `eval/dovodka/proba_gemini.py`).** **МЕХАНИЗМ НАЗВАН** (вендор-дока `ai.google.dev/api/generate-content`): в нативном API `systemInstruction` — это ОДИН объект `Content`, поэтому слой ФИЗИЧЕСКИ не может пронести больше одного системного, и уход на нативный API проблемы НЕ решает — там пришлось бы склеивать так же. При двух и более лишние выбрасываются без ошибки. ⛔ **УСТАНОВЛЕНО 29.08: ВЫЖИВАЕТ ПОСЛЕДНЕЕ СИСТЕМНОЕ, ВЫБРАСЫВАЕТСЯ ПЕРВОЕ** (полигон, Д35.3 отчёта 23; $0, детерминированно, на 16 уже купленных клетках). Доказательство арифметическое: у арма `RE` первое системное (ролевой промт) **побайтно одинаково на всех 16 главах**, второе (глоссарий) — **разное** (255–404 знака, термины из текста главы); разность `RE.prompt_tokens − RQ.prompt_tokens` равна **2436 РОВНО на каждой из 16 глав, sd = 0.00** ⇒ выброшено сообщение ПОСТОЯННОГО размера, а таково только первое; будь выброшены оба, разность росла бы вместе с глоссарием. Побочно: `prompt_tokens` карантинных клеток коррелируют с длиной глоссария +0.52 и с длиной исходника +0.04 — глоссарий внутри них физически есть. ~~⚠ КАКОЕ именно выживает — НЕ УСТАНОВЛЕНО~~ — форум Google (discuss.ai.google.dev/t/86097, 30.05.2025) был ПРАВ, наша прошлая интерпретация «не выжило НИ ОДНО» неверна: она сравнила 535 токенов с «размером одного user-сообщения» на глаз, а при шкале 0.324 ток/знак русского глоссарий в 300 знаков весит ~100 токенов и в такой оценке теряется. Документированного вендором ограничения в разделе «Current limitations» НЕТ. Замер на одном и том же системном тексте в 7970 знаков: ОДНИМ сообщением → `prompt_tokens` 2994 и маркерная инструкция ИСПОЛНЕНА; ДВУМЯ → **535 токенов (это размер одного user-сообщения) и инструкция НЕ исполнена**. Ответ приходит с `finish=stop` и выглядит правильным переводом — отказа нет, есть тихая потеря инструкции, невидимая всем гейтам годности. ⚠ Так были куплены 16 клеток арма `RE` (промт-роль на gemini): они переведены ВООБЩЕ БЕЗ ИНСТРУКЦИИ, и вердикт о переносимости из них был бы ложным. Лечение: склеивать системные в одно ДЛЯ ЭТОГО ВЕНДОРА (`rol.ONE_SYSTEM`/`fit_msgs`) — общая склейка сменила бы форму запроса у всех моделей и рассогласовала бы уже купленные клетки. ⚠ Проверено, что у `deepseek-v4-pro`, `gpt-5.6-luna`, `glm-5` и `grok-4.3` два системных доезжают целиком. +- ⛔⛔ **ТОТ ЖЕ ДЕФЕКТ ЖИВЁТ В ДВИЖКЕ, А НЕ ТОЛЬКО В ПОЛИГОНЕ (аудит 22.08).** `backend/internal/pipeline/render.go:256-258` штатно строит ВТОРОЕ системное сообщение — инъекцию банка памяти (глоссарий/STM), `backend/internal/llm/httpllm.go:517-522` копирует сообщения дословно, склейки под Gemini нет нигде, а `gemini-3.1-pro-preview` стоит в ратифицированной цепочке эскалации во ВСЕХ арм-конфигах (`pipeline-c1.yaml:129`, `pipeline-arm-glm.yaml:61`, `pipeline-arm-deepseek-pro.yaml:62`, `pipeline-arm-mistral.yaml:61`). ⇒ ~~**любой эскалационный хоп на Gemini при непустой инъекции ТИХО теряет глоссарий.**~~ ⛔⛔ **ФОРМУЛИРОВКА ИСПРАВЛЕНА 29.08 И СТАЛА ХУЖЕ, А НЕ МЯГЧЕ.** По установленному выше правилу выживает ПОСЛЕДНЕЕ системное, а `render.go:247-258` кладёт первым **сам промт стадии**, вторым — инъекцию банка. ⇒ **на эскалационном хопе в Gemini при непустой инъекции движок теряет НЕ глоссарий, а ВЕСЬ ПРОМТ СТАДИИ, и оставляет глоссарий.** Модель получает список канонных форм и текст — без единого слова о том, что с ними делать. Ровно так выглядят 16 клеток `RE`: перевод есть, `finish=stop`, русский, правильной длины, инструкции за ним нет. **Тихая потеря промта не отличима от нормального ответа ничем, кроме счётчика входных токенов.** Лечение прежнее (склейка системных под этого вендора), приоритет — выше. Оговорка D22.3 «экспозиция Ф1 нулевая» держится только на том, что до Gemini в дефолте не доходят. ⚠ Класс в проекте уже был решён рядом: `backend/internal/llm/provider_anthropic.go:169-179` сворачивает ведущий system-префикс в одно поле и падает громко на system посреди диалога — просто не обобщён на Gemini. **Зона бэкенда, пинг оркестратору отправлен.** - **Gemini**: `safety_settings` через OpenAI-совместимый слой **не передаются** (HTTP 400 «Unknown name safety_settings»). Дефолт фильтров у 2.5/3 — OFF (gap-2). Для явного контроля фильтров (роль судьи 18+) нужен **нативный** Gemini API, не OpenAI-слой. ⚠ **D22.6: на ГРАФИЧНОЙ эротике Gemini 3.x fail-closed `PROHIBITED_CONTENT` (8× wire-verified, exp11) — фильтр НЕконфигурируем, нативный API не спасает; судья эротики — только Grok. На violence/SFW Gemini-судья жив.** `finish_reason` приходит СОСТАВНОЙ строкой `'content_filter: PROHIBITED_CONTENT'` (точные матчеры мертвы — D22.8а). Биллинг Gemini: thinking ТОЛЬКО в `total_tokens` → `reasoning: additive_total`, иначе недоучёт 146×/вызов (D22.3). Нативное имя поля — `usageMetadata.thoughtsTokenCount`; слой AI-Studio `completion_tokens_details.reasoning_tokens` не отдаёт ВОВСЕ, тогда как Vertex AI то же поле отдаёт (форум 15.01.2026) — при переезде на Vertex учёт менять. ⚠⚠ **`reasoning_tokens=0` у Gemini — НОРМА, а не измерение: вывод «размышление выключено» из этого поля НЕДЕЙСТВИТЕЛЕН.** Полигон 22.08 на этом и поскользнулся, объявив «вендор-дефолт flash-lite = размышление ВЫКЛ (замерено)»; считать надо `total − prompt − completion`. ⚠ **В роли редактора Gemini течёт сервис-преамбулой в выход («Вот отредактированный…», 6/6 чанков — exp12) → за output-санитайзером D30.3.** ⚠ **Дополнение эксп-21 (08.08):** fail-closed ответ OpenAI-слоя приходит вовсе БЕЗ объекта `message` (не с пустым `content`) — парсер обязан переживать его отсутствие; на вебновелльном violence-материале срабатывания массовые (10 клеток судейского прогона с `content_filter: PROHIBITED_CONTENT`) ⇒ оговорка «на violence/SFW Gemini-судья жив» целиком НЕ держится — Gemini непригоден как ЕДИНСТВЕННЫЙ второй контур на этом материале (эксп-21 §1/§8). - **Qwen Model Studio**: внешний фильтр `data_inspection_failed` на вход/выход, неотключаем (риск молчаливых вырезаний) — не тестировано (нет ключа). - Детект отказов/вырезаний — см. `eval/refusal_bench.py` (паттерны soft-отказа + coverage-гейт по len_ratio/sent_cov, пороги в эксп. 02). diff --git a/docs/experiments/23-editor-tier.md b/docs/experiments/23-editor-tier.md index c4b157d2..256f5123 100644 --- a/docs/experiments/23-editor-tier.md +++ b/docs/experiments/23-editor-tier.md @@ -5147,3 +5147,248 @@ fable-5: медиана ρ +0.52 · среднее +0.55 · глав согла действительно прощает брак за прозу, то на масштабе он будет систематически ЗАВЫШАТЬ гладкий текст с дефектами — то есть ровно тот исход, против которого продукт и строится (класс А). Ошибка в эту сторону дороже экономии на судье. + +--- + +## Д35 — ПЕРЕЕЗД МАШИНЫ: ЧТО ОН СЛОМАЛ В ПРИБОРАХ ФАЗЫ, И ДВЕ ЭРРАТЫ, НАЙДЕННЫЕ ПРИ ВОССТАНОВЛЕНИИ + +Сессия 29.08 открылась не с замера, а с того, что **ни один риг фазы не запускался**: на новой +машине нет ни `~/books`, ни `eval/.venv`. Восстановление дало две находки, обе — не про эту фазу, +а про то, чем фаза меряет. + +### Д35.1 ОКРУЖЕНИЕ ВОССТАНОВЛЕНО, И ОТКЛОНЕНИЯ ОТ СТЕНДА НАЗВАНЫ + +* **Книги.** С 24.08 они лежат в `<репозиторий>/books` и версионируются ОТДЕЛЬНЫМ git-репозиторием + (`CLAUDE.md`), а все риги полигона ходят по `Path.home()/"books"` — **65 вхождений в 31 файле**. + Поднято симлинком `~/books → <репозиторий>/books`. ⚠ Это восстановление контракта путей, а не + починка: следующая чистая машина упрётся в ту же стену, пока корень книг не станет одной + разрешающей функцией. Долг назван здесь, чтобы не всплыл как «риг сломан». +* **Интерпретатор.** `eval/.venv` пере-собран, `eval/requirements.txt` поставлен целиком. + ⚠ **CPython 3.14.7 против пина `.python-version` 3.14.4** — патч-расхождение, названо, не + спрятано. Риги `dovodka/` — чистый stdlib (сверено импортами), поэтому расхождение на их + арифметику не влияет; для рига, зовущего вендора, это была бы другая оценка. +* Селфтесты после восстановления: `rol --selftest`, `money_d --selftest`, `runs --selftest`, + `sud --axis=d4 --selftest`, `naklon --selftest`, `gain --selftest`, `adjud --controls` — **зелены**. + +### Д35.2 ⛔ ПЕРЕЕЗД ОБНУЛИЛ mtime, И ОДИН ГЕЙТ ФАЗЫ БОЛЬШЕ НЕ ПЕРЕ-СНИМАЕТСЯ + +`eval/tenant_panel/verify22.py:174-185` выводит верхнюю границу двойной оплаты Ф2 как «сумма +клеток, купленных ДО коммита атомарного замка `fd3e522`», и отделяет «до» от «после» +**временем файла на диске**. После переезда **все 335 клеток Ф2 имеют ОДИН И ТОТ ЖЕ mtime** +(проверено: `уникальных mtime: 1`), причём поздний. ⇒ граница считается как `$0.000000`, +не совпадает с напечатанным в отчёте эксп-22 числом, и **R71 реестра требований падает**. + +**Диагноз, а не зелень.** Числа в отчёте эксп-22 верны — они сняты, когда mtime ещё существовали. +Утрачена **улика**, а не результат. Клетки времени покупки в себе не хранят (`dv-*.json`: +`arm · uid · model · finish · *_tokens · cost_usd · latency_s` — поля `ts` нет), значит граница +из сырья больше НЕ ВЫВОДИМА ничем. +⇒ **R71 объявляется НЕ ПЕРЕ-СНИМАЕМЫМ на этой машине.** Гейт не правится под зелень (D39.121): +подгонка спрятала бы ровно тот факт, что провенанс покупок держался на файловой системе. +⚠ **Класс шире одного гейта:** `runs.py:166` и `role_topology/absjudge.py:323` атрибутируют +судью ответу тем же способом — по mtime файла ответа. Для БУДУЩИХ чтений это работает (файлы +пишутся сейчас), для исторических — атрибуция утрачена там же и тогда же. +⇒ **Норма на будущее: время покупки и время ответа писать ПОЛЕМ В АРТЕФАКТ.** Файловая система — +не журнал. + +### Д35.3 ⛔⛔ КАКОЕ СИСТЕМНОЕ СООБЩЕНИЕ ВЫЖИВАЕТ У GEMINI — УСТАНОВЛЕНО. ВЫЖИВАЕТ ПОСЛЕДНЕЕ + +`00-provider-quirks.md:157` оставлял вопрос открытым дословно: «⚠ КАКОЕ именно выживает — НЕ +УСТАНОВЛЕНО: форум Google сообщает „выживает последнее“, а наш замер даёт 535 токенов ≈ размер +одного user-сообщения, то есть не выжило НИ ОДНО. Расхождение открыто». + +**Закрыто арифметикой уже купленных клеток, за $0 и детерминированно.** У арма `RE` на провод +уходят ТРИ сообщения: `system[0]` — ролевой промт, **7 525 знаков, побайтно один и тот же на всех +16 главах**; `system[1]` — глоссарий, **255–404 знака, РАЗНЫЙ на каждой главе** (термины берутся +из текста этой главы); `user` — исходник. Карантинные клетки — тот же вызов, у которого вендор +что-то выбросил. + +``` +RE.prompt_tokens − RQ.prompt_tokens = 2436 на КАЖДОЙ из 16 глав, sd = 0.00 +``` + +Разность **строго постоянна**. Выброшенное сообщение обязано быть постоянного размера — а +постоянен из двух системных только `system[0]`. Будь выброшены оба, разность росла бы вместе с +глоссарием (размах 149 знаков ≈ 50 токенов) и постоянной быть не могла. +⇒ **вендор выбросил ПЕРВОЕ системное и оставил ПОСЛЕДНЕЕ.** Форум Google был прав, наша прошлая +интерпретация — нет. (Подтверждение с другой стороны: `prompt_tokens` карантинных клеток коррелируют +с длиной ГЛОССАРИЯ +0.52 и с длиной исходника +0.04 — то есть внутри них глоссарий физически есть.) + +**Почему прошлый вывод разошёлся с фактом.** Проба 22.08 сравнила 535 токенов с «размером одного +user-сообщения» на глаз. 2 436 токенов на 7 525 знаков русского — это 0.324 ток/знак; при такой +шкале глоссарий в 300 знаков весит ~100 токенов и в оценке «на глаз» просто теряется. + +**⛔ И это меняет вывод про ДВИЖОК в худшую сторону.** `backend/internal/pipeline/render.go:247-258` +кладёт первым `system` — **сам промт стадии**, вторым `system` — инъекцию банка (глоссарий/STM). +Записанное следствие звучало как «эскалационный хоп на Gemini тихо теряет глоссарий» +(`00-provider-quirks.md:158`). По установленному правилу всё наоборот: + +> **на эскалационном хопе в Gemini при непустой инъекции банка движок теряет НЕ глоссарий, а ВЕСЬ +> ПРОМТ СТАДИИ, и оставляет глоссарий.** + +То есть модель получает список канонных форм и текст — без единого слова о том, что с ними делать. +Ровно это и произошло с 16 клетками `RE`, и ровно так они выглядят: перевод есть, `finish=stop`, +русский, правильной длины — и никакой инструкции за ним. **Тихая потеря промта не отличима от +нормального ответа ничем, кроме счётчика входных токенов.** +⇒ Пинг оркестратору (зона бэкенда): лечение прежнее — склейка системных под этого вендора, — но +**приоритет и формулировка дефекта в бэклоге неверны и подлежат правке**. + +--- + +## Д36 — ⭐ ШАГ 4 ПЛАНА 22.08: ДУЭЛЬ «РОЛЕВОЙ ПРОМТ ПРОТИВ ЕГО ОТСУТСТВИЯ». ПРЕ-РЕГ, ЗАПИСАН ДО СБОРКИ ПАКЕТОВ + +**Зачем этот шаг вообще есть.** Фаза произвела много «неразличимо»: вся середина архитектурной +панели, все ступени размышления, семь вендоров из восьми. У каждого такого нуля есть паспорт с +порогом — но нет **шкалы**: неизвестно, какой перевес прибор в принципе способен развести на +промт-инженерном материале. Дуэль даёт эту шкалу природным экспериментом, который уже оплачен: +16 клеток одной модели на одних главах, где единственный варьируемый фактор — **есть ролевой +промт или его нет**. + +⇒ Если даже полное исчезновение ролевого промта не даёт устойчивого знака — **у прибора нет +разрешения для промт-инженерных выводов вовсе**, и это печатается рядом с каждым «не хуже» дуги +Д23–Д25 (экзамен на смелость `RN`/`RC`/`RB`, абляция). Если даёт — все прошлые нули получают +масштаб: «меньше, чем ВЕСЬ промт, во столько-то раз». + +### Д36.0 ЧТО ТАКОЕ АРМ `RQ` — ТОЧНАЯ ФОРМУЛИРОВКА, А НЕ «БЕЗ ПРОМТА» + +По Д35.3 установлено: у карантинных клеток выпал **ролевой промт (7 525 знаков)**, а **глоссарий +остался**. ⇒ Арм называется точно: **«тот же `gemini-3.1-flash-lite`, тот же исходник, тот же +глоссарий — но БЕЗ ролевого промта»**. Формулировка «вообще без инструкции», стоявшая в плане +22.08 и в шапке `rol.FOREIGN["RQ"]`, **неточна и здесь исправляется**: 130 из 2 566 токенов +инструкции до модели доехали, и это ровно те токены, что несут канон. + +⚠ **Следствие для чтения результата, объявленное ДО чисел:** дуэль меряет вес **ролевого промта** +(2 436 токенов из 2 566, то есть 95%), а не «всего промта». Разницу в 5% не назвать нулём заранее +нельзя — именно эти 5% несут класс «банк». + +### Д36.1 СОСТАВ ПАНЕЛИ И ЗАМОРОЖЕННЫЙ СПИСОК ГЛАВ + +**Тег `p4`, новый; существующие теги не пере-эмитируются.** Пять армов: + +| арм | что это | роль в панели | +|---|---|---| +| `RQ` | gemini flash-lite, исходник + глоссарий, **ролевого промта нет** | испытуемый | +| `RE` | тот же gemini, тот же вызов, **промт доехал** | компаратор | +| `Z0` | боевая связка (продакшен) | половина шумового пола | +| `ZF` | её вторая генерация | вторая половина пола | +| `ZD` | голый черновик `deepseek-v4-flash` | **ГРУБЫЙ ДЕКОЙ** | + +**⚠ ОТКЛОНЕНИЕ ОТ ПЛАНА 22.08, ОБЪЯВЛЯЮ ЕГО ЗДЕСЬ, А НЕ ПОСЛЕ.** План (§5, Шаг 4) назвал панель из +четырёх армов — `RE · RQ · Z0 · ZF`. Добавлен пятый, `ZD`. Основание — **норма рига R57**: декой +обязан быть в каждой судейской пачке, а паспорт панели-0 прямо записал свой изъян строкой +«грубого декоя НЕТ — разрешение держится только на близнецах». `ZD` на этот материал годится как +никакой другой: он **различимо последний в ОБОИХ кругах и у ОБОИХ судейских семейств** +(Д33.8: +4.12 при пороге 2.39, p=0.006; +4.92 при пороге 2.10, p=0.0005). Панель, где известно +плохой арм не оказался последним, аннулируется целиком. +**Цена отклонения, названная заранее:** шкала мест 1–5, а не 1–4, поэтому числа этой панели +**не сопоставимы напрямую** с панелью-0 и с 11-армовой `arch2`. Сопоставимы отношения внутри +панели — ради них декой и добавлен. + +**16 английских глав (пересечение всех пяти армов, сверено ДО чтения — 16/16 у каждого):** + +``` +f2274720c9 · 53f1a12dff · b065a92dc0 · 26c3bff1d4 · 49ca859c94 · 100b088f71 · 197f98eb61 +d3237e1dea · eefdade2c3 · 27cb3a7e69 · 3bd6481182 · 8e50448cea · 90a20cb443 · 001e6ac4eb +c3517980c7 · 5a8f48d24a +``` + +Это те же 16 глав, что у панели-0, — поэтому **эталон дефект-классов, снятый по исходнику вслепую +(`eval/dovodka/etalon-panel0.json`, sha256 `02a7352e…`, фриз коммитом `bedc39a` 22.08), применим к +этой панели без единой правки**, и счётный прибор достаётся даром. + +### Д36.2 ГОДНОСТЬ МАТЕРИАЛА — СНЯТА ДО ПРЕ-РЕГА И ПЕЧАТАЕТСЯ ЦЕЛИКОМ + +Проверки годности (не эндпойнты) сняты раньше этого текста, и это объявляется прямо: + +``` +RQ 16/16 finish=stop · prompt_tokens 468–548 (медиана 515) · доля кириллицы 0.995–1.000 + медиана длины выхода 1 708 знаков · всего уплачено $0.01513 +RE 16/16 finish=stop · prompt_tokens 2 904–2 984 (медиана 2 951) · кириллица ≥0.997 + медиана длины выхода 1 698 знаков +``` + +⇒ Материал годен: обе половины дуэли — законченные русские переводы сопоставимой длины. +**Гейт правильности файла — числом, а не именем** (`rol.FOREIGN["RQ"]["prompt_tokens"] = (300,900)`): +подложенный файл нормального арма роняет сборку, и это проверено селфтестом на больном входе. + +### Д36.3 ⛔ ЧТО В ЭТОЙ ПАНЕЛИ НЕ УРАВНЕНО — НАЗЫВАЕТСЯ ДО ЗАМЕРА + +1. **`RQ` и `RE` — родня**: одна модель, один вызов, одни главы. Читатель может опознать родство + текстуально, как опознавал `Z0`/`ZF`. ⇒ пол панели по паре `Z0`/`ZF` есть **НИЖНЯЯ граница** + порога, и то же смещение действует на пару `RQ`/`RE` — в СТОРОНУ занижения разрыва. +2. **Три вендора в одной панели**: `RQ`/`RE` — Google, `Z0`/`ZF` — DeepSeek (связка), `ZD` — + `deepseek-v4-flash`. Межармовые сравнения через границу вендора несут вендор-эффект. +3. **Потолки вывода не уравнены** (у `RQ`/`RE` `max_tokens` 32 000, у армов связки — свои). +4. **Размышление**: у `gemini-3.1-flash-lite` уровень **НЕ УСТАНОВЛЕН** (вендор не публикует + дефолт для этого слага, `00-provider-quirks.md:55`), и `reasoning_tokens=0` у Gemini — + норма поля, а не измерение. ⇒ конфигурация испытуемого и компаратора идентична друг другу, но + в абсолютных терминах неизвестна. Для дуэли это не важно (фактор один), для переноса — важно. +5. **Объём инструкции играет ЗА `RQ`**: у него на входе в 5.7 раза меньше токенов. Если он + проиграет — вывод сильнее объявленного; если выиграет — слабее. + +### Д36.4 ДВА ПРИБОРА И ПЕРВИЧНЫЙ ЭНДПОЙНТ + +**Прибор 1 — счётный, детерминированный.** `eval/dovodka/schet.py` (sha256 `1e292d1d…`, фриз +`bedc39a`) на четырёх классах: **род · язык · приём · банк**. Детекторы, эталон и правила +классификации **заморожены с 22.08 и в этой сессии не тронуты ни на строку** — это то же условие, +на котором стоял вердикт панели-0. + +**Прибор 2 — ранговый.** Слепое чтение, **`claude-fable-5`, модель задаётся ЯВНО при запуске и +сверяется по транскриптам ПОСЛЕ прогона** (решение Д34.2; заявление о намерении моделью не +является — урок Д33.6). Обёртка `eval/dovodka/prompts/reader-wrapper.md`, sha256 `15cfac60…`, +та же, что во вторых чтениях и в пере-суде панели-0. По одному читателю на главу, 16 новых сессий. + +**⭐ ПЕРВИЧНЫЙ ЭНДПОЙНТ — СИСТЕМАТИЧЕСКИЙ ЗНАК КОНТРАСТА `RE ↔ RQ` ПО ГЛАВАМ,** парный знаковый +тест, p<0.05 двусторонний. Так велел план: «Решает систематический знак, а не величина разрыва». +Потолок значимости при n=16 — 2/2¹⁶, дизайн мощности не лишён; при 12 главах в одну сторону +p≈0.077, при 13 — p≈0.021, то есть **для вывода нужно не менее 13 глав из 16 в одну сторону** +(при отсутствии связок). Это печатается ДО чисел. + +**Вторичные:** (i) величина разрыва в местах против собственного порога панели (конъюнкция: +|Δ|>порог И p<0.05); (ii) те же четыре дефект-класса счётным прибором, парный знаковый; +(iii) контроль пола `Z0`/`ZF`; (iv) контроль декоя — `ZD` обязан оказаться последним. + +**Порог** считается собственным полом панели (пара `Z0`/`ZF`), как в Д32 и Д34; априорный MDE +рангового прибора при n=16 и k=5 держится около двух мест — поэтому **величина разрыва объявляется +ОПИСАТЕЛЬНОЙ, а несущим остаётся ЗНАК**. + +### Д36.5 ⛔ ПРАВИЛО РЕШЕНИЯ, ОБЪЯВЛЕННОЕ ДО ЧИСЕЛ. ВСЕ ТРИ ИСХОДА + +* **`RE` систематически лучше `RQ` (знак есть, p<0.05).** У прибора ЕСТЬ разрешение на + промт-инженерном материале, и вес ролевого промта известен в местах. Тогда каждое «неразличимо» + дуги получает шкалу: «эффект меньше, чем весь ролевой промт, во столько-то раз». +* **Знака нет (p≥0.05).** ⛔ **Худший исход, и печатать его придётся первым.** Он означает, что + прибор не разводит даже полное исчезновение промта, ⇒ **ни один промт-инженерный вывод фазы + (экзамен на смелость `RN`/`RC`/`RB`, абляция Д21, «роль лучше склейки») не имеет под собой + разрешения**, и это сносится в каждую строку, где такой вывод стоит. +* **`RQ` систематически ЛУЧШЕ `RE`.** Промт ВРЕДИТ на этом жильце. Печатается первым и отдельно; + для продукта это означает, что ролевой промт, писанный под сильную модель, на слабой работает + против неё — и тогда «промт — часть арма» надо читать как «промт — часть ПАРЫ модель×промт». + +**Что этот шаг НЕ закрывает, объявляю заранее:** он не переносится на другого жильца (один +`gemini-3.1-flash-lite`), не переносится на китайскую пару, и не говорит ничего об оси ВЕРНОСТИ +владельца (Д30). Он даёт шкалу ОДНОГО прибора на ОДНОМ жильце. + +### Д36.6 ⭐ ПРЕДСКАЗАНИЕ, КОТОРОЕ МОЖНО ОПРОВЕРГНУТЬ, И ОНО ЗАПИСАНО ДО СЧЁТА + +Из Д35.3 следует не только формулировка арма, но и **проверяемое предсказание счётного прибора**: + +> глоссарий у `RQ` ЕСТЬ, ролевого промта НЕТ ⇒ по классу **«банк»** армы `RQ` и `RE` должны +> оказаться БЛИЗКИ, а расходиться обязаны классы, которые несёт ролевой промт: **«язык»** +> (мандат «выведи ТОЛЬКО перевод», правила передачи французского слоя, запрет латиницы на именах) +> и **«приём»** (в промте о типографике не сказано ничего — предсказываем ноль различия), +> плюс проза, которую счётный прибор не видит вовсе. + +⛔ **Если по классу «банк» окажется большая разница — интерпретация Д35.3 неверна, и её надо +пере-открывать, а не подгонять.** Предсказание записано именно для этого. + +### Д36.7 ПРОЦЕДУРА И СВЕРКИ ДО ЧТЕНИЯ + +1. `rol.py --emit-read en --panel=RQ,RE,Z0,ZF,ZD --tag=p4 --each --n=16` — один пакет на главу. +2. `rol.py --verify-read en --tag=p4` — побайтная сверка каждого текста с клеткой своего арма, + исходник главы, раскладки, имена армов в пакете, побайтно совпадающие армы, гейт длины. + ⚠ EXIT=0 сертификатом НЕ является (норма плана §6): вывод читается ГЛАЗАМИ. +3. Ключ эмитируется ДО ответов, читателю не даётся; в пакете нет ни имён армов, ни строки + «БЕЗ-ПРОМТА», ни вендоров, ни слова «однопроходка». +4. `runs.py --claim p4 en ` ДО запуска каждого читателя; модель — параметром запуска. +5. Аудит транскриптов ПОСЛЕ: поле `model` у всех 16 агентов обязано быть `claude-fable-5`. +6. `READERS-p4.json` пишется **ДО** прогона (долг Д33.6 закрывается по норме, а не задним числом). diff --git a/eval/dovodka/rol.py b/eval/dovodka/rol.py index 582f6734..cfaadecc 100644 --- a/eval/dovodka/rol.py +++ b/eval/dovodka/rol.py @@ -492,11 +492,15 @@ def _e6_wire(uid: str) -> tuple[list[dict], str, dict]: def _rq_wire(uid: str) -> tuple[list[dict], str, dict]: """WIRE-АНАЛОГ `RQ` — ЗАПРОС В ТОЙ ФОРМЕ, В КОТОРОЙ ОН УХОДИЛ ДО ПОЧИНКИ СКЛЕЙКИ. - ⛔ Клетки `RQ` — это `RE`, у которого вендор МОЛЧА ВЫБРОСИЛ второе системное сообщение: - OpenAI-совместимый слой Gemini принимает ровно одно. Поэтому здесь НЕ зовётся `fit_msgs` + ⛔ Клетки `RQ` — это `RE`, у которого вендор МОЛЧА ВЫБРОСИЛ ПЕРВОЕ системное сообщение — + ролевой промт, — и оставил ПОСЛЕДНЕЕ, то есть глоссарий: OpenAI-совместимый слой Gemini + принимает ровно одно. ⚠ Прежняя редакция этого комментария говорила «второе», и это было + неверно: разность `RE.prompt_tokens - RQ.prompt_tokens` равна 2436 РОВНО на всех 16 главах, + а постоянного размера из двух системных только первое (эррата Д35.3). Поэтому НЕ зовётся `fit_msgs` (склейка, заведённая после находки) и НЕ зовётся `rol.call_kwargs`: печатается ровно то, что отправлял код 21.08. ⚠ Печатается ОТПРАВЛЕННОЕ, а не ПОЛУЧЕННОЕ моделью — второе нам недоступно по построению, и разница видна только по счётчику: 515 токенов входа против 2951 у `RE`. + ⚠ Из этих 515 глоссарий составляет ~100 — арм есть «без РОЛЕВОГО промта», а не «без промта». """ src = next(u["source"] for u in chosen("en") if u["uid"] == uid) msgs = rol_msgs("en", src, "RE") @@ -507,9 +511,9 @@ FOREIGN = { "E6": {"pair": "en", "prefix": "dv-g-e6-", "model": "glm-5", "role": "editor3", "phase": "ФД-G", "rig": "eval/dovodka/en_axis.py --e6", "wire": _e6_wire, "что": "ВТОРОЙ редактор glm-5 поверх боевого черновика D0 (носитель гипотезы H-4)"}, - # ⚠ ПСЕВДО-АРМ ПОД КАРАНТИННЫМ СУФФИКСОМ. 16 клеток `RE`, которые вендор перевёл БЕЗ ИНСТРУКЦИИ: - # OpenAI-совместимый слой Gemini принимает РОВНО ОДНО системное сообщение и молча выбрасывает - # лишние. Деньги за них списаны, тексты годны, `finish=stop` 16/16 — это оплаченный и + # ⚠ ПСЕВДО-АРМ ПОД КАРАНТИННЫМ СУФФИКСОМ. 16 клеток `RE`, у которых до модели НЕ ДОЕХАЛ РОЛЕВОЙ + # ПРОМТ: OpenAI-совместимый слой Gemini принимает РОВНО ОДНО системное сообщение, выбрасывает + # ПЕРВОЕ и оставляет ПОСЛЕДНЕЕ — то есть глоссарий доехал, а инструкция нет (Д35.3). Деньги за них списаны, тексты годны, `finish=stop` 16/16 — это оплаченный и # незапланированный природный эксперимент «сколько весит ВЕСЬ промт». # ⛔ ИМЯ НЕЙТРАЛЬНОЕ И СУФФИКСА В ПАКЕТЕ БЫТЬ НЕ ДОЛЖНО: читатель, увидевший «БЕЗ-ПРОМТА», # мерил бы подсказку. `arm_field` = "RE", потому что в самой клетке написано «RE» — она и есть @@ -521,7 +525,8 @@ FOREIGN = { "model": "gemini-3.1-flash-lite", "role": "onepass", "arm_field": "RE", "phase": "ФД-N", "rig": "eval/dovodka/rol.py --buy en RE (клетки 21.08, карантин)", "prompt_tokens": (300, 900), "wire": _rq_wire, - "что": "тот же gemini БЕЗ ИНСТРУКЦИИ — вендор выбросил системное сообщение"}, + "что": "тот же gemini БЕЗ РОЛЕВОГО ПРОМТА — вендор выбросил первое системное, " + "глоссарий доехал"}, } diff --git a/eval/dovodka/sysmsg.py b/eval/dovodka/sysmsg.py new file mode 100644 index 00000000..c088dfe1 --- /dev/null +++ b/eval/dovodka/sysmsg.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +"""КАКОЕ СИСТЕМНОЕ СООБЩЕНИЕ ВЫЖИВАЕТ У GEMINI — ДЕТЕРМИНИРОВАННАЯ УЛИКА. $0. + +⚠ ЗАЧЕМ ОТДЕЛЬНЫЙ ФАЙЛ. Вопрос стоял открытым в `docs/experiments/00-provider-quirks.md` +дословно: «КАКОЕ именно выживает — НЕ УСТАНОВЛЕНО … Расхождение открыто». Закрыт он не новой +живой пробой (она стоит денег и протухает вместе со слагом), а АРИФМЕТИКОЙ уже купленных клеток. +Такой вывод обязан иметь исполняемого носителя: иначе через месяц он останется утверждением в +отчёте, которое никто не может пере-снять. + +ЛОГИКА ДОКАЗАТЕЛЬСТВА, и она не требует ни одного вызова модели: + + у арма `RE` на провод уходят ТРИ сообщения — + system[0] ролевой промт ПОБАЙТНО ОДИН И ТОТ ЖЕ на всех главах + system[1] глоссарий РАЗНЫЙ на каждой главе (термины берутся из текста главы) + user исходник разный + карантинные клетки `RQ` — тот же вызов, у которого вендор что-то выбросил. + + ⇒ если разность `RE.prompt_tokens − RQ.prompt_tokens` ПОСТОЯННА по главам, выброшено + сообщение постоянного размера, а таково только system[0] — ролевой промт. + Будь выброшены ОБА системных, разность росла бы вместе с глоссарием и постоянной быть + не могла; будь выброшено только system[1], постоянной была бы не разность, а сам `RQ`. + +⛔ ГЕЙТ ПАДАЕТ, ЕСЛИ ВЫВОД НЕ ДЕРЖИТСЯ. Он проверяет ТРИ вещи, и каждая может уронить: + 1. system[0] действительно побайтно постоянен, а system[1] действительно варьирует + (иначе рассуждение неприменимо — и это надо увидеть, а не предположить); + 2. разность постоянна с точностью до нуля; + 3. остаток `RQ` разложим на глоссарий + исходник: он обязан коррелировать с длиной ГЛОССАРИЯ + сильнее, чем с длиной исходника. + +Запуск: eval/.venv/bin/python eval/dovodka/sysmsg.py +""" + +from __future__ import annotations + +import json +import statistics as st +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" + + +def _corr(a: list[float], b: list[float]) -> float: + ma, mb = st.mean(a), st.mean(b) + num = sum((x - ma) * (y - mb) for x, y in zip(a, b, strict=True)) + den = (sum((x - ma) ** 2 for x in a) * sum((y - mb) ** 2 for y in b)) ** 0.5 + return num / den if den else 0.0 + + +def main() -> int: + rows = [] + for u in rol.chosen("en"): + uid = u["uid"] + pair = OUT / f"dv-n-re-{uid}.json", OUT / f"dv-n-re-{uid}.БЕЗ-ПРОМТА.json" + if not all(p.exists() for p in pair): + print(f"⛔ нет пары клеток для {uid[:10]} — улика неполна") + return 1 + re_, rq_ = (json.loads(p.read_text(encoding="utf-8")) for p in pair) + msgs, _model, _kw = rol.FOREIGN["RQ"]["wire"](uid) + if [m["role"] for m in msgs] != ["system", "system", "user"]: + print(f"⛔ форма вызова не «system·system·user»: {[m['role'] for m in msgs]}") + return 1 + rows.append({ + "uid": uid[:10], + "re": re_["prompt_tokens"], "rq": rq_["prompt_tokens"], + "sys0": len(msgs[0]["content"]), "sys1": len(msgs[1]["content"]), + "user": len(msgs[2]["content"]), + }) + + print(f"{'глава':12}{'RE_pt':>7}{'RQ_pt':>7}{'RE-RQ':>7}{'sys0':>7}{'sys1':>6}{'user':>7}") + for r in rows: + print(f"{r['uid']:12}{r['re']:>7}{r['rq']:>7}{r['re'] - r['rq']:>7}" + f"{r['sys0']:>7}{r['sys1']:>6}{r['user']:>7}") + + bad = 0 + sys0 = {r["sys0"] for r in rows} + sys1 = {r["sys1"] for r in rows} + diff = {r["re"] - r["rq"] for r in rows} + + def ck(ok: bool, what: str) -> None: + nonlocal bad + print(f"[{'OK ' if ok else 'ПРОВАЛ'}] {what}") + bad += 0 if ok else 1 + + ck(len(sys0) == 1, f"ролевой промт постоянен по главам: вариантов длины {len(sys0)} ({sys0})") + ck(len(sys1) > 1, f"глоссарий по главам РАЗНЫЙ: вариантов длины {len(sys1)}") + ck(len(diff) == 1, f"разность RE−RQ постоянна: вариантов {len(diff)} ({diff})") + + d = next(iter(diff)) if len(diff) == 1 else -1 + tok_per_char = d / next(iter(sys0)) if len(sys0) == 1 and d > 0 else 0.0 + c_gl = _corr([float(r["rq"]) for r in rows], [float(r["sys1"]) for r in rows]) + c_src = _corr([float(r["rq"]) for r in rows], [float(r["user"]) for r in rows]) + ck(c_gl > c_src, f"остаток RQ идёт за ГЛОССАРИЕМ, а не за исходником: " + f"corr {c_gl:+.2f} против {c_src:+.2f}") + + print(f"\nвыброшено {d} токенов на {next(iter(sys0))} знаков ролевого промта " + f"= {tok_per_char:.3f} ток/знак русского текста") + print("⇒ ВЫЖИВАЕТ ПОСЛЕДНЕЕ СИСТЕМНОЕ, ВЫБРАСЫВАЕТСЯ ПЕРВОЕ." + if not bad else "\n⛔ ВЫВОД НЕ ДЕРЖИТСЯ — пере-открывать, а не подгонять.") + print("⚠ Это утверждение о ПОВЕДЕНИИ СЛОЯ на дату покупки клеток (21.08.2026). " + "Слаг ≠ модель, поведение слоя тоже имеет срок годности.") + return 1 if bad else 0 + + +if __name__ == "__main__": + sys.exit(main())