Измерение эвристики языка
AT-160 (EP-028, HS-053). Измеряемая эвристика — та, что предложила спецификация AT-080 («A — собственная эвристика, без зависимостей»). Начиная с AT-163 (RN-624) продукт её выполняет: api определяет с её помощью язык автора и СПРАШИВАЕТ, прежде чем что-либо изменится. Пороги продукта предварительные — см. Предварительные пороги продукта.
Что существует
Инструмент живёт в scripts/idioma/ и запускается Node репозитория (type
stripping, без шага сборки). Никаких новых зависимостей: эвристика — это
список маркеров и регулярные выражения, а извлечение общается с Postgres
через psql внутри контейнера базы.
Сама эвристика живёт в api —
apps/api/src/domain/iam/heuristica-de-idioma.ts, — а scripts/idioma/heuristica.ts
лишь реэкспортирует её, поэтому инструмент измеряет ровно тот код, который
выполняет продукт. Это направление вынужденное: api не достаёт ни до
scripts/, ни до какого-либо runtime-пакета без шага сборки, а
packages/shared содержит только типы. В файле нет импортов, поэтому Node
запускает его из scripts/ через type stripping (команды idioma:* глушат
сопутствующее предупреждение о "typeless package"), а у scripts/idioma/ есть
собственный tsconfig.json — единственное отличие в отключении
verbatimModuleSyntax, который отвергает любой export модуля CommonJS,
как в api. Сначала пробовали файл .mts: он прошёл обе проверки типов и
сборку, но сломал все скрипты ts-node в api. Правило о том, что
является доказательством (evidenciasDoAutor), переехало вместе с
эвристикой, и извлечение реального корпуса использует то же правило.
| файл | роль |
|---|---|
heuristica.ts | реэкспортирует эвристику api: очистка, контрастная оценка, кандидат и предварительные параметры, выборка из 10 сообщений, гистерезис и правило доказательства |
corpus-sintetico.jsonl | версионируемый синтетический корпус — каждая строка написана вручную для инструмента, origem: "sintetico" |
sequencias-sinteticas.json | синтетические диалоги для пути выборки и гистерезиса |
medicao.ts / medir.ts | инструмент и его CLI |
extrair.ts | извлекает реальный корпус из локального event log в файл вне git |
rotular.ts | разметка реального корпуса с подсказками, в терминале |
Два корпуса и где живёт каждый
Решение сопровождающего (AT-168, ответ 5) — реальный + синтетический. У них противоположные правила хранения:
- Синтетический — версионируется, ревьюится как код. Покрывает девять обязательных случаев AT-080 (опечатки, настоящий испанский, английский, смешанный текст, "ok", код, логи, цитаты на другом языке) плюс португальский, насыщенный английским жаргоном, встроенные идентификаторы и языки вне трёх (французский, итальянский, немецкий, галисийский, каталанский, нидерландский) — список языков открыт (AT-168, ответ 2).
- Реальный — сообщения самого пользователя в локальном event log.
Никогда не попадает в git, в fixture, в лог CI или в тело PR.
extrair.tsиcorpus.tsотвергают любое место назначения внутри checkout, а файл записывается с правами 600 в$XDG_CACHE_HOME/brabo/corpus-idioma/(иначе~/.cache/…). Отчёт никогда не печатает текст элемента — только id, метки и счётчики, — поэтому его вывод можно вставить в PR.
То, что извлечение считает доказательством, следует очистке AT-080: только
события, актор которых — пользователь (ответ агента никогда не является
доказательством, поэтому испанский ответ модели не может подпитывать
определение), и в структурированной форме — только ответы:
склеенный chat.message, который api записывает сразу после, и чьи метки
— текст агента, пропускается.
Помощник разметки не подставляет заранее догадку эвристики: её
принятие нажатием ENTER привязало бы метку именно к тому, что метка
призвана измерять. ENTER принимает язык, который владелец объявляет через
--padrao.
Метки: код BCP-47 (pt-BR, es, fr…), und (решать нечего: "ok", только
код, только лог) или mul (смешанный, без доминирующего языка). Оценка:
pt/es/en должны получить свой язык; und, mul и любой другой язык
должны получить indeterminado — единственный правильный ответ, который
может дать им эвристика трёх языков. indeterminado на решаемом элементе —
это упущенное покрытие (известное предпочтение сохраняется); неверный
язык — это ошибка, а falso es считается отдельно, потому что это тот
дефект, ради устранения которого существует эпик.
Как запускать
# synthetic corpus (the default)
pnpm --filter @brabo/scripts idioma:medir
# the owner's real corpus, on the owner's machine
pnpm --filter @brabo/scripts idioma:extrair --container brabo-dev-postgres-1
pnpm --filter @brabo/scripts idioma:rotular --padrao pt-BR
pnpm --filter @brabo/scripts idioma:medir --real
--container запускает psql внутри этого контейнера с единственным
SELECT только на чтение в session_events (--usuario/--banco по
умолчанию brabo/brabo из compose); --database-url <url> использует
вместо этого psql хоста. Повторный запуск извлечения сохраняет
существующие метки и добавляет новые сообщения без метки.
Числа на синтетическом корпусе (2026-09-28)
123 размеченных элемента, 7 последовательностей, параметры-кандидаты AT-080
(минимум 20 слов, порог 0,8, отрыв 0,3, 10 сообщений / 2 000 символов,
гистерезис 2). Два списка маркеров: at080 — список из спецификации,
дословно; ampliada — больший список, написанный этим инструментом до
запуска на корпусе (частые служебные слова, отсутствующие в двух других
языках). Корпус тот же, поэтому ampliada этими числами не подтверждается —
сказать может реальный корпус.
По сообщениям, параметры-кандидаты:
| метка | n | at080 верно | at080 не определено | ampliada верно | ampliada не определено |
|---|---|---|---|---|---|
| pt | 56 | 26 (46,4%) | 30 | 30 (53,6%) | 26 |
| es | 20 | 6 (30,0%) | 14 | 7 (35,0%) | 13 |
| en | 14 | 6 (42,9%) | 8 | 6 (42,9%) | 8 |
| und | 15 | 15 (100%) | 0 | 15 (100%) | 0 |
| mul | 5 | 5 (100%) | 0 | 5 (100%) | 0 |
| другие языки | 13 | 10 | 0 | 9 | 0 |
Вердикты с неверным языком: ни одного на элементе pt/es/en ни в
одном из списков. Все они на языках вне трёх — at080: французский → pt
(ê в arrêté), галисийский → es (единственный falso es),
нидерландский → en; ampliada: французский → es, немецкий → pt и → en,
нидерландский → en.
Очистка удалила больше половины слов у нуля решаемых элементов.
Семь синтетических последовательностей заканчиваются там, где ожидается, с не более чем допустимым числом переключений, в обоих списках — включая португальский с длинной цитатой на испанском, целое сообщение на испанском посреди португальского и португальский, плотно насыщенный английским жаргоном.
CPU на этой машине: at080 11,7 мкс на сообщение и 49 мкс на оценку выборки
из 10 сообщений; ampliada 16,8 мкс и 60 мкс. Зависит от машины; важен
порядок величины.
Что говорят числа
- Со списком
at080порог и отрыв никогда не срабатывают. 66 из 67 элементов, которые хоть что-то набирают, набирают для одного языка (уверенность = 1,0), поэтому перебор порога от 0,5 до 1,0 ничего не меняет. Минимум слов — единственная действующая ручка: по сообщениям решаемые элементы приходят к своему языку в 42,2% случаев при 20 словах, 58,9% при 10 и 66,7% без минимума — при 20 он выбрасывает каждое короткое сообщение с опечатками и большую часть технических. - Снятие минимума позволяет решать смешанному тексту. Без минимума
at080превращает три из пяти смешанных сообщений вenилиpt, аampliadaтакже решает два элементаundкакen— код без ограждения (const total = itens.reduce(…)) и вывод pnpm (ELIFECYCLE Test failed. See above for more details.) проходят через очистку, которая знает только огороженный код и форматы логов, перечисленные в AT-080. - Отрыв 0,3 избыточен при пороге 0,8 в любом списке: при трёх языках победитель с ≥ 0,8 оказывается минимум на 0,6 выше второго.
- Значимая единица — выборка, а не сообщение: в последовательностях окно
из 10 сообщений приходит к языку с сообщениями, которые по отдельности
были бы
indeterminado. Единственное испанское сообщение в португальском окне делает выборкуindeterminado, пока оно находится в окне (последовательностьq03): ни переключения, но и ни подтверждения. - Ошибки — в открытом списке языков. При трёх языках пользователь, пишущий по-галисийски или по-французски, может услышать, что пишет по-испански или по-португальски. По правилу AT-168 определение лишь спрашивает, а пользователь подтверждает, поэтому цена — неверный вопрос, но это всё равно единственный режим отказа эвристики здесь.
Это синтетические числа: корпус написан той же рукой, что и эвристика. Пороги AT-163 должны были бы прийти из реального корпуса.
Предварительные пороги продукта
AT-163 должна была выйти с каким-то числом до появления реального
корпуса, поэтому api читает PARAMETROS_PROVISORIOS — предварительные, не
откалиброванные и так и объявленные в RN-624:
| параметр | предварительный | кандидат AT-080 | почему |
|---|---|---|---|
| список маркеров | at080 | — | ampliada выигрывает 4 пункта в pt и дважды ошибается на немецком; ни один из двух не подтверждён реальными данными |
| минимум слов (по выборке) | 10 | 20 | покрытие решаемых элементов 42,2% → 58,9% при тех же трёх неверных вердиктах (французский, галисийский, нидерландский — языки вне трёх); ниже 10 начинает решать смешанный текст |
| порог / отрыв | 0,8 / 0,3 | 0,8 / 0,3 | на синтетическом корпусе никогда не срабатывают, поэтому выбрать лучшее число пока не из чего |
| выборка | 10 сообщений / 2 000 символов | то же | — |
| гистерезис | 2 | 2 | — |
Гистерезис пересчитывается, но никогда не хранится: api читает последние
11 доказательств и проверяет, согласны ли две последние оценки выборки. CPU
на этой машине, синтетический корпус: 11,6 мкс на очистку и
классификацию сообщения, 110 мкс на определение (11 сообщений, две
оценки). Чтение обслуживается частичным индексом в session_events:
0,056 мс против 13,5 мс последовательного сканирования, измерено на 200 000
событий.
Для перекалибровки: запустите реальный корпус (idioma:medir --real), а
затем измените PARAMETROS_PROVISORIOS в api — это единственное место,
откуда продукт их читает, и инструмент импортирует тот же файл (его отчёт
продолжает перебирать кандидатов AT-080).
Платная проверка языковой ориентации (AT-167, 2026-09-29)
Протокол — из AT-082, с решениями AT-169: бюджет US$ 5, 5 прогонов на случай и ветвь, порог приёмки «в обработанной ветви ноль испанских ответов там, где ожидается pt-BR, и правильный язык ≥ 95% в C01–C04 и C06–C15», тестируемая модель
deepseek/deepseek-v4.1-flash, модель сравненияanthropic/claude-haiku-4.5, потолок дополнительной стоимости 50 входных токенов на вызов. Код вd48f99f8e(devот 2026-09-29).
Где запускается и что является (и что не является) продуктом
Скрипт вне продукта, scripts/idioma/validar.ts (чистая часть — матрица,
оценка, отчёт — в validacao.ts, протестированная validacao.spec.ts). Он
вызывает OpenRouter напрямую и ничего не пишет в базу. То, что продукт
отправляет модели, читается из файлов продукта при каждом запуске,
никогда не копируется — чтение выбрасывает исключение, называя то, чего не
нашло, если файл изменит форму:
- ориентация (RN-622/623):
@textos, общая фраза, оговорка об артефакте,@forma_curtaи@ferramentas_de_artefatoизapps/engine/lib/engine/harness/idioma_da_resposta.ex, отправляемая как ПОСЛЕДНЕЕ сообщение,role: "system", в каждом вызове обработанного хода (включая итерации инструментов) и никогда не сохраняемая в истории; - промпт резюме и его фраза о языке (RN-621) из
apps/engine/lib/engine/harness/context_manager.ex; - персона Criativo (
CRIATIVO_INSTRUCTIONS), два его инструмента с настоящими описаниями и результаты инструментов, которые возвращает продукт.
Заявленные отличия от продукта: system prompt — только персона (без слоёв
ContextBuilder); сжатие принудительное перед пятым ходом C04 (с
сохранением двух последних сообщений, keep_recent продукта), с
тестируемой моделью в роли резюмирующей; возобновление (C15) — история,
восстановленная как текст, в форме Engine.Agents.Reidratacao; не более 4
вызовов модели за ход (Criativo допускает 12); и max_tokens ограничен 4000
(продукт его не передаёт). Upstream-провайдер не был зафиксирован —
записан по каждому вызову как смешивающий фактор. Baseline = код до
RN-621/622: без ориентации и со старым промптом резюме (AT-169, ответ
4: проверка измеряет совокупность, отдельного baseline для старого
резюме нет).
Разрешённый язык автора каждого сообщения — часть случая: pt-BR для Ana
(C01–C05, C07–C11, C13–C15), en для Bob (C06 как определённый и
подтверждённый en, C12 как явное предпочтение en при письме
по-португальски); язык проекта pt-BR. C05 ожидает pt-BR: по ответу 4
AT-168 определение лишь спрашивает, поэтому разрешённый язык автора
остаётся pt-BR до подтверждения.
Первый запуск нулевого шага с max_tokens 1000 был отброшен:
рассуждения DeepSeek израсходовали весь потолок в 7 вызовах, и ответ
вернулся пустым (два резюме превратились в запасной текст продукта). Его
записи лежат отдельно в папке вывода; пустой ответ теперь считается
falha, а не indeterminado.
Нулевой шаг: не воспроизведено
Только baseline, deepseek/deepseek-v4.1-flash, C01–C04, 5 прогонов: 0
испанских ответов из 35 оценённых (35 pt, 0 неопределённых). Сообщение от
13.09 (испанский в ответ на португальский ввод) не воспроизведено,
поэтому никакое улучшение именно этого дефекта нельзя приписать
обработке. Запуск продолжился, потому что порог AT-169 абсолютен в
обработанной ветви, а остальные случаи (C05, C06, C12, C16, C17)
действительно показывают, что baseline отвечает на неверном языке.
Результаты по случаю × ветви × модели
Первый судья — локальный классификатор (список ampliada,
параметры-кандидаты) по прозе. "ok" = ожидаемый язык; "errado (es)" = другой
язык (из них испанский); "ind." = не определено. n — число оценённых
ответов (5 прогонов × оценённые ходы).
| случай | DeepSeek baseline | DeepSeek обработка | Haiku baseline | Haiku обработка |
|---|---|---|---|---|
| C01 (n=5) | 5 ok | 5 ok | 5 ok | 5 ok |
| C02 (n=5) | 5 ok | 5 ok | 5 ok | 5 ok |
| C03 (n=15) | 15 ok | 15 ok | 15 ok | 15 ok |
| C04 (n=10) | 10 ok | 10 ok | 10 ok | 10 ok |
| C05 (n=5, вне списка 95%) | 1 ok, 4 errados (4 es) | 5 ok | 0 ok, 5 errados (5 es) | 2 ok, 3 errados (3 es) |
| C06 (n=5) | 2 ok, 2 errados, 1 ind. | 3 ok, 2 ind. | 4 ok, 1 errado | 5 ok |
| C07–C10 (n=5 каждый) | все ok | все ok | все ok | все ok |
| C11 (n=10) | 6 ok, 4 errados | 5 ok, 5 errados | 5 ok, 1 errado, 4 ind. | 6 ok, 4 errados |
| C12 (n=10) | 0 ok, 10 errados | 4 ok, 6 ind. | 0 ok, 10 errados | 0 ok, 10 errados |
| C13 (n=5) | 5 ok | 5 ok | 5 ok | 5 ok |
| C14, C15 (n=5 каждый) | все ok | все ok | все ok | все ok |
| C16 (n=20, вне списка) | 10 ok, 10 errados | 19 ok, 1 ind. | 10 ok, 10 errados | 18 ok, 2 errados |
| C17 (n=20, вне списка) | 12 ok, 8 errados | 20 ok | 16 ok, 4 errados | 20 ok |
C13 вызвал emit_artifact в 20 из 20 диалогов. Ни один вызов не упал.
Против порога
| модель | испанский там, где ожидается pt-BR (обработка) | верно в C01–C04, C06–C15 (строго, классификатор) | вердикт по классификатору |
|---|---|---|---|
deepseek/deepseek-v4.1-flash | 0 | 82/95 (86,3%), 8 неопределённых | не проходит |
anthropic/claude-haiku-4.5 | 3 (все в C05) | 81/95 (85,3%), 0 неопределённых | не проходит |
13 отказов DeepSeek образуют две группы. Прочтение ниже — прочтение агента, сделанное ДО ревью; ревью (следующий раздел) его исправило — 8 неопределённых не все являются прозой на английском:
- 8 неопределённых (C06 ×2, C12 ×6) выглядят как проза на английском —
список
ampliadaплохо оценивает английский ("Great starting point — …"); - 5 отказов C11 отвечают на просьбу о переводе переводом на английский внутри португальской рамки, что ориентация допускает («если только пользователь явно не попросит … в этом сообщении» покрывает перевод, но не рамку). «Перевод на en» в протоколе не говорит, какой из двух вариантов правильный.
Если ревью примет обе группы, DeepSeek доходит до 95/95 и проходит;
если отвергнет C11 — 90/95 (94,7%) и не проходит на один ответ. Haiku не
проходит в любом случае: при явном предпочтении en и сообщениях
по-португальски (C12) он ответил по-португальски в 10 из 10 обработанных
ответов и ответил на 3 из 5 испанских сообщений по-испански (C05). Гипотеза,
не измеренная: для моделей Anthropic OpenRouter поднимает сообщение
system из конца в верхний system prompt, где оно теряет новизну, которую
имеет в конце списка. (Измерено позже и не подтверждено: см. диагноз
ниже.)
Ответы для ревью (все неопределённые или расходящиеся, все переводы C11 и
детерминированная выборка 1 из 10 согласующихся) — 146 из 560, перечислены в
revisao.md в папке вывода.
Дополнительная стоимость ориентации
Парные prompt_tokens первого вызова каждого диалога (вход идентичен в
обеих ветвях, кроме ориентации), взятые из usage OpenRouter, никогда не
оценённые:
| модель | ориентация | пар | Δ входных токенов |
|---|---|---|---|
| DeepSeek | pt-BR (108 символов) | 75 | 32 (каждая пара) |
| DeepSeek | en + оговорка об артефакте (128 символов) | 15 | 27 |
| Haiku | pt-BR | 75 | 30 |
| Haiku | en + оговорка об артефакте | 15 | 27 |
Всё ниже потолка в 50 токенов. По входной цене каталога это около
US$ 0,0000096 за вызов у DeepSeek и US$ 0,00003 у Haiku — верхняя граница,
поскольку DeepSeek обслужил большую часть входа из кеша (кешированные
токены возвращаются; стоимость вызова с кешем находится внутри
usage.cost, не разделённая по сообщениям). Ориентация идёт в каждом
вызове хода, поэтому ход с итерациями инструментов оплачивает её по разу
на итерацию.
Расходы
Стоимость проверки — сумма usage.cost каждого ответа, сохранённая по
вызовам в chamadas.jsonl: US$ 2,3071 всего — US$ 2,2629 на учтённых
запусках (DeepSeek 0,1526 baseline + 0,2133 обработка; Haiku 0,9526 +
0,9443; 865 вызовов), US$ 0,0281 на отброшенном нулевом шаге и US$ 0,0162 на
smoke одного случая. Счётчик GET /api/v1/key вырос с 1,8301 до 4,1578
(Δ 2,3277) за тот же период, но ключ общий с другими платными lanes,
работавшими одновременно, и счётчик запаздывает, поэтому эта дельта —
контекст, а не расход этой проверки. Id генераций не записывались, поэтому
стоимость каждого вызова не сверялась с GET /api/v1/generation.
Собственный metering продукта (token_usage) не участвовал: скрипт идёт в
обход api.
Как воспроизвести
# the key file holds OPENROUTER_TEST_KEY=…; the key is never printed
pnpm --filter @brabo/scripts idioma:validar --arquivo-de-chave ~/.config/brabo/openrouter-test.env \
--modelos deepseek/deepseek-v4.1-flash --bracos baseline --casos C01,C02,C03,C04 # step zero
pnpm --filter @brabo/scripts idioma:validar --arquivo-de-chave ~/.config/brabo/openrouter-test.env \
--pular-existentes --teto-usd 2.5 # everything else
pnpm --filter @brabo/scripts idioma:validar --relatorio # report only
Вывод идёт в $XDG_CACHE_HOME/brabo/validacao-idioma/<date>/ (иначе
~/.cache/…), права 600, и скрипт отвергает папку внутри checkout.
--teto-usd останавливает перед вызовом, который превысил бы его, по
текущей сумме usage.cost.
Не измерено: сама экономия от кеша, полный system prompt продукта, реальное сжатие (запускаемое порогом), перезапуск настоящего engine, подсчёт токенизатора для оговорки об артефакте на португальском и любой фиксированный upstream.
146 ответов были проверены вручную; результат и единственное решение, которое по-прежнему за владельцем, — в следующем разделе.
Ревью человеком и диагноз Haiku (продолжение AT-167)
Два офлайн- или дешёвых продолжения платной проверки, оба по одному и тому
же запуску (respostas.jsonl, 560 оценённых ответов): ревью человеком 146
ответов, отмеченных классификатором (Часть A, без расходов), и проверка
гипотезы «OpenRouter поднимает сообщение system из конца» для Haiku
(Часть B, US$ 0,1485). Ни то, ни другое не меняет продукт.
Часть A — ревью 146
Каждый из 146 ответов в revisao.md прочитан, один за другим, агентом,
написавшим этот раздел (а не вторым человеком; файл меток сохранён для
тех, кто захочет ему возразить). Для каждого: реальный язык текста,
который читает пользователь, верен ли он для случая, и причина в одну
строку. Файлы живут вне git, рядом с ответами —
~/.cache/brabo/validacao-idioma/2026-09-29/revisao-feita.md (146) и
amostra-lida.md (выборка ниже). pnpm --filter @brabo/scripts idioma:revisar читает их и печатает все таблицы этого раздела; метки —
данные, арифметика — код (scripts/idioma/revisao.ts, протестирован).
Меток четыре, потому что чтение обнаружило факт, которого классификатор не видит:
sim/nao: ответ на ожидаемом / не на ожидаемом языке;formulario: проза на ожидаемом языке, но вопросы структурированной формы (ask_structured_questions, суть первого ответа) вышли по-португальски для автора, чей язык —en;c11: перевод на английский внутри португальской рамки — решение владельца, см. ниже.
| из 146 | верные | неверные | проза верна, форма на португальском | C11 (решение владельца) |
|---|---|---|---|---|
| все | 42 | 81 | 8 | 15 |
| ветвь baseline (89) | 18 | 64 | 1 | 6 |
| обработанная ветвь (57) | 24 | 17 | 7 | 9 |
14 ответов, которые классификатор назвал неопределёнными, после прочтения оказались: 3 ответа на английском (простые переводы C11 от Haiku), 2 неверных (DeepSeek C12, португальские и английские фразы вперемешку в прозе), 8 — проза на английском с формой на португальском, и 1 C11. Значит, прежняя фраза агента — «8 неопределённых выглядят как проза на английском» — была наполовину верна: проза была английской, а форма не была в 6 из 8.
Ошибка классификатора, измеренная. В 118 окончательных вердиктах
(pt/es/en) среди 146 прочитанных ответов чтение противоречило 0.
Классификатор ни разу не назвал верным неверный ответ и неверным — верный;
его единственные сбои — 3 «неопределённых», оказавшихся простым
английским. Для 414 ответов, которые не были отмечены (все согласны с
ожиданием), была прочитана выборка: 60 отобранных через
random.Random(20260929).sample(range(414), 60) по ответам в порядке
respostas.jsonl, плюс 14 неотмеченных ответов C06/C12 с ожидаемым en,
которые не попали в отбор (они решают порог, поэтому прочитаны целиком), —
74 прочитано, 0 расхождений (верхняя граница Уилсона при 95%: 4,9%).
Вместе с 39 согласующимися ответами, которые уже отметил хеш 1 из 10 и
которые тоже были прочитаны, случайная часть даёт 0 из 99 (верхняя граница
3,7%): из 414 неверными могли бы быть не более ~15, а выборка указывает на
ноль. Две оплошности, замеченные в выборке, — вставки одного слова
("What falta for us…", "reminders automáticos"), не меняющие языка
ответа.
Чего классификатор не может увидеть — это форму: clf=en на ответе на
английском не говорил, что его вопросы были на португальском (он отмечал
такие ответы как неопределённые, только когда смесь была сильной). Поэтому
выборка прочитала ответы C06/C12 целиком, и поэтому существуют два
прочтения ниже.
Порог, пересчитанный по проверенным меткам
Обработанная ветвь, C01–C04 и C06–C15 (n = 95), испанский считается по реальному языку каждого обработанного ответа там, где ожидается pt-BR (включая C05). Интервал Уилсона при 95%. Два независимых прочтения: C11 (за владельцем, открыто) и форма (проза верна, форма на португальском — агент разрешил её как факт о том, что читает пользователь, и показывает другую сторону, чтобы ей можно было возразить).
| модель | C11 засчитывается | форма засчитывается | попаданий | доля | 95% интервал | испанский (ожидается pt) | вердикт |
|---|---|---|---|---|---|---|---|
| DeepSeek | да | да | 93/95 | 97,9% | 92,6–99,4% | 0 | проходит |
| DeepSeek | да | нет | 87/95 | 91,6% | 84,3–95,7% | 0 | не проходит |
| DeepSeek | нет | да | 88/95 | 92,6% | 85,6–96,4% | 0 | не проходит |
| DeepSeek | нет | нет | 82/95 | 86,3% | 78,0–91,8% | 0 | не проходит |
| Haiku | да | (любое) | 85/95 | 89,5% | 81,7–94,2% | 3 (C05) | не проходит |
| Haiku | нет | (любое) | 81/95 | 85,3% | 76,8–91,0% | 3 (C05) | не проходит |
Порог прохождения — 91 попадание из 95 (94,7% не проходит). Читая таблицу:
- Haiku не проходит ни при каком прочтении: 3 испанских ответа в C05 (одного условия нуля испанского уже достаточно) и, при принятии всего, 85/95.
- DeepSeek проходит ровно в одной ячейке: C11 принят и проза на английском с формой на португальском принята. Отвергните любое из двух — и он не проходит: на 4 ответа, если отвергнута форма (87 из нужных 91), на 3, если C11 (88). Отвергните оба — и получится число классификатора, 82.
- Поэтому решение по C11 важно только при принятой форме. Если форма
засчитана как сбой, DeepSeek не проходит при любом C11, и находка, на
которую нужно реагировать, — не C11, а форма: в обработанной ветви 8 из
15 ответов C06+C12 у DeepSeek несут португальский текст внутри
английского ответа (6 только в форме, 2 вперемешку), а 7 — чистый
английский, включая формы. Модель способна написать форму
по-английски — делает это примерно в половине случаев.
Откуда это взялось (AT-282, RN-667):
само описание инструмента говорило модели, что
label— это «текст вопроса, em pt-BR», — вторая языковая инструкция, фиксированная, вне фасада, конкурирующая с ориентацией в конце в каждом вызове. Теперь оно гласит «no idioma da sua resposta» и не называет язык; сама ориентация не изменилась. Измерение «после» не запускалось (не было ключа OpenRouter на машине, где делалось изменение) и ожидает владельца — C06 и C12, обработанная ветвь, 5 прогонов, с потолком бюджета из RN.
Что такое C11 в данных. 5 обработанных отказов DeepSeek: у 3 перевод на
английский процитирован в португальской рамке ("Registrei a regra… A
tradução que você pediu: > …"), а у 2 нет перевода в записанном тексте
— запуск хранит только последнее сообщение хода, а эти ходы сначала
вызвали emit_artifact, так что перевод был в предыдущем сообщении того же
хода (10 вызовов на 5 диалогов, по два на диалог), то есть перевод,
предположительно, был в предыдущем сообщении того же хода и не сохранился.
Оба считаются только рамкой, то есть попаданиями в «C11 засчитывается». 4
обработанных отказа Haiku — перевод на английский с примечанием на
португальском или португальскими альтернативами вокруг.
TODO(humano): Засчитывается ли в C11 как верный перевод на английский внутри португальской рамки? Это решает ось C11 таблицы выше: принят → DeepSeek 93/95 и проходит (если проза на английском с формой на португальском тоже принята; 87/95, если нет); отвергнут → DeepSeek 88/95 (или 82/95) и не проходит. Haiku не проходит в любом случае. Рекомендация, одной фразой: принять — исключение ориентации покрывает только запрошенный перевод, а рамка остаётся на языке автора, поэтому отказ наказал бы именно то поведение, ради которого ориентация существует. (Второй вопрос, форма на португальском, относится к рамкам продукта и сформулирован как находка выше; владельцу он не передавался, потому что агент разрешил его как факт — то, что читает пользователь.)
Часть B — дело в позиции ориентации? (AT-279)
Гипотеза: для моделей Anthropic OpenRouter поднимает сообщение system из
конца в верхний system prompt, теряя его новизну. Проверка: C12
(предпочтение автора en, сообщения на pt-BR), anthropic/claude-haiku-4.5,
первый ход, 5 прогонов на позицию, текст ориентации прочитан из
продукта (он был Respond in English (en), unless the user explicitly asks for another language in this message. Write project artifacts in pt-BR.), меняется только
позиция:
ultima-system— как сегодня (idioma_da_resposta.ex:231,role: "system"в конце);system-fundido— дописана к первомуsystem(персона + ориентация);sufixo-user— суффикс последнего сообщенияuser, без добавленногоsystem;ultima-system-e-sufixo-user—systemв конце плюс та же фраза как суффикс.
| позиция | попаданий классификатора | прочтение |
|---|---|---|
1 ultima-system (как сегодня) | 0/5 | 5 на португальском, включая формы |
2 system-fundido | 0/5 | 5 на португальском |
3 sufixo-user | 0/5 | 5 на португальском |
4 ultima-system-e-sufixo-user | 0/5 | 5 на португальском |
| контроль: 1, без оговорки об артефакте | 0/5 | 5 на португальском |
| контроль: 3, без оговорки об артефакте | 0/5 | 5 на португальском |
Четыре позиции прочитаны целиком, два контроля — частично, и все 30
ответов также проверены на служебные слова английского (ни одного ни в
одном): все 30 — на португальском, проза и форма. Дефект позиции 1
воспроизведён (0/5, как 0/10 платного запуска), и ни одна позиция его
не исправляет — даже та, где ориентация находится внутри самого
последнего сообщения пользователя и system в конце нет. Гипотеза не
подтверждается. Подсчёты токенов промпта показывают, что текст дошёл до
модели в каждой позиции (2 426, 2 427, 2 427 и 2 454 входных токена: +1 для
двух простых размещений и +28 для двойного), так что сообщение не
отбрасывается. Контроль исключает и вторую подозрительную фразу: оговорка
об артефакте ("Write project artifacts in pt-BR.", RN-623) тоже не причина
(0/10 без неё).
Чего это не исключает и что не измерялось: второй ход (запускался только
первый, из-за бюджета — в платном запуске второй ход тоже был неверен 5/5);
более сильная формулировка (фраза гласит "unless the user explicitly asks",
и португальский ввод можно так прочитать); язык самой персоны
(CRIATIVO_INSTRUCTIONS — на португальском); и upstream (все 30 вызовов
обслужил Amazon Bedrock, поэтому эффект upstream отделить нельзя). DeepSeek
следует той же ориентации в том же случае — его обработанные ответы C12
были прозой на английском в 8 из 10, — что указывает на модель, а не на
размещение.
Предлагаемое изменение: никакого по позиции. Единственное место, где
позиция была бы изменена, если бы это помогло, —
Engine.Harness.IdiomaDaResposta.anexar/4
(apps/engine/lib/engine/harness/idioma_da_resposta.ex:231, messages ++ [%{"role" => "system", …}]), вызываемое обоими путями llm_turn в
apps/engine/lib/engine/sessions/engine_api_client.ex:609 и :661. Оно
остаётся как есть. Следующая работа, если владелец хочет, чтобы Haiku
подчинялся en при португальском вводе, — проверить формулировку и язык
персоны (список выше) этим же скриптом.
Расходы (сумма usage.cost каждого ответа, никогда не счётчик /key):
US$ 0,0980 на четыре позиции (20 вызовов) и US$ 0,0505 на два контроля (10
вызовов) — US$ 0,1485 из потолка US$ 0,20, 30 вызовов, ни один не упал.
Чтобы воспроизвести Часть B (файл ключа хранит OPENROUTER_TEST_KEY=…; он
никогда не печатается; вывод идёт за пределы checkout):
pnpm --filter @brabo/scripts idioma:diagnosticar \
--arquivo-de-chave ~/.config/brabo/openrouter-test.env \
--turnos 1 --rodadas 5 --teto-usd 0.13 --concorrencia 4 # the four positions
pnpm --filter @brabo/scripts idioma:diagnosticar \
--arquivo-de-chave ~/.config/brabo/openrouter-test.env \
--turnos 1 --rodadas 5 --posicoes ultima-system,sufixo-user --sem-clausula \
--saida ~/.cache/brabo/validacao-idioma/<date>-diagnostico-haiku-sem-clausula \
--teto-usd 0.06 --concorrencia 4 # the controls
pnpm --filter @brabo/scripts idioma:revisar # Part A tables
--turnos N и --sem-clausula существуют только для этой диагностики;
продукт никогда не помещает ориентацию никуда, кроме конца, и posicionar
в validacao.ts им не используется.