Головна / Статті / Коли класифікатор тихо підміняє модель: навіщо потрібні заголовки з фактичною моделлю

Коли класифікатор тихо підміняє модель: навіщо потрібні заголовки з фактичною моделлю

Тихий відкат на дешевшу модель не лишає ні помилки, ні рядка в лозі. Як знайти дані про фактичну модель у транскриптах Claude Code і чому провайдери мають віддавати їх у відповідях.

1861 слів

Коли 7 серпня Anthropic написала про послаблення біологічного класифікатора безпеки для своєї флагманської моделі, більшість коментаторів віднесли це до старої суперечки про надмірні відмови, теми, яка здавалася вичерпаною. Але найцікавіше слово в анонсі — не відмова, а fallback, відкат.

Підміна без сигналу

Згідно з анонсом, класифікатор, що спрацював, не зупиняє запит. Він перенаправляє його на Opus 5 — сильну модель, якій бракує біологічних можливостей Fable 5. Нічого не блокується й не фільтрується, жодного попередження не з'являється. Користувач просить топову модель, класифікатор вирішує, що промпт схожий на біологію, і відповідає інша модель.

Моделі відрізняються ціною. За публічним прайсом Fable 5 коштує $10 за мільйон вхідних токенів і $50 за мільйон вихідних, а Opus 5 — $5 і $25, удвічі дешевше в обидва боки. Як тарифікується перенаправлений запит, у дописі не сказано, і це не перевірялося, тож про білінг тут тверджень немає. Встановлено інше: підміна з задокументованою ціллю й опублікованою різницею в ціні.

Характеристики важливіші за ціну. У документації порівняння виглядає так:

  • Позиціювання в документації: Fable 5 розрахована на агентів, які працюють довго, а Opus 5 — на складне агентне програмування та бізнес-навантаження.
  • Ціна за мільйон токенів, вхід і вихід: $10 і $50 проти $5 і $25.
  • Контекстне вікно: 1M в обох.
  • Максимальний вивід: 128K в обох.
  • Надійна межа знань: січень 2026 у Fable 5, травень 2026 в Opus 5.

Розмір контексту збігається, стеля виводу збігається, а знання моделі-заміни на чотири місяці свіжіші. Її власна документація навіть рекомендує її насамперед для вимогливого агентного програмування. Анонс прямо називає ціну підміни — біологічні можливості, — і це втрата, яку не покаже жоден дифф коду.

Тож підміна не вичерпує бюджет контексту, не впирається в ліміт токенів і на задачі з програмування може взагалі не виглядати гірше. Це не пом'якшувальна обставина. Саме тому підміна й невидима.

Уважно читаємо цифри

Головна цифра — зниження приблизно на 85% відкатів, пов'язаних із біологією, по всіх продуктах Anthropic: одне число, лише про біологію, агреговане по всьому.

Цифри за окремими продуктами вимірюють інше й наводяться у виносці про всі відкати, біологічні й ні. Загальна кількість відкатів, за прогнозом, впаде приблизно на дві третини в Claude.ai (67%), трохи більше ніж наполовину в Cowork (55%), на 17% у Claude Code і лише на 7% на Claude Platform. Це не частки від 85, і якщо змішати два набори цифр, вийде охайна, придатна для цитування й хибна фраза.

До того ж це скорочення, а не частоти. Зниження на 7% на Platform нічого не каже про абсолютну частоту там — можливо, відкати завжди були рідкісними. Зате розкид показує, де переналаштування спрацювало: споживчий чат зсунувся приблизно вдесятеро сильніше, ніж платформа для розробників, а програмування опинилося посередині з 17%.

Класифікатор, послаблення якого зсуває вікно чату вдесятеро сильніше, ніж API, явно не калібрували на трафіку API. Кожен, хто ганяє флот субагентів, сидів нижче за течією від обмежувача, налаштованого під людину, яка друкує питання в текстове поле.

Задля справедливості потрібне одне застереження, і для довіри воно важливе: нічого не витекло. Anthropic сама опублікувала деталі разом із виправленням і додала цифру. Претензія не в приховуванні. Вона в будові механізму.

Чому пониження гірше за відмову

Відмова — добрий вид збою. Вона має тип, код статусу, видна на дашборді, викликає алерт, іде шляхом ретраю й очевидно є чужою провиною.

Пониження не має жодної з цих зачіпок. Не приходить ні помилка, ні код статусу, ні запис у лозі. Повертається валідна, правильно оформлена відповідь, просто трохи гірша: патч, у якому не врахований один крайній випадок, тест, списаний як нестабільний, підсумок без того найважливішого застереження.

Слабкий результат субагента зазвичай не змушує нікого звинувачувати вендора. Звинувачують промпт, бо це частина під прямим контролем і найчастіше саме вона винна. Його переформульовують, результати покращуються, і теорія ніби підтверджується. Але нове формулювання — це й інший вхід для класифікатора. Звичайні ліки від поганої відповіді зачіпають те, що могло спричинити підміну, тож виправлення виглядає робочим, а хибний діагноз закріплюється. Якщо одна правка лагодить і розмитий промпт, і промпт, що зачепив фільтр, ніхто не скаже, яка проблема була насправді.

Місяці посередньої роботи субагентів могли списувати на промпти, хоча частина була підміною, і дізнатися, яка саме, неможливо, бо ніхто не записував, яка модель відповіла.

Дані були завжди

Нічого тут не бракувало. В об'єкті відповіді Messages API є поле model поруч із id, content, stop_reason, stop_details і usage. Кожна будь-коли повернена відповідь називала модель, яка її створила. Майже ніхто це поле не читає, бо воно виглядає як луна запиту.

SDK досі подає його як луну запиту: doc-коментар до model у типі відповіді описує його як модель, яка виконає промпт. Майбутній час — у поля всередині вже завершеної відповіді. Роками це формулювання було правильним, а тепер ні. Яка модель обслужила виклик, визначається під час виконання, і єдиний авторитетний запис про це — відповідь, яку більша частина коду викидає.

У користувачів Claude Code це вже лежить на диску. Кожен хід асистента дописується в JSONL-транскрипт у ~/.claude/projects/ з повним об'єктом повідомлення API. Очевидний однорядковик рахує рядки з моделями:

grep -ho '"model":"[^"]*"' *.jsonl | sort | uniq -c | sort -rn

Підрахунок по дванадцяти останніх транскриптах в одній теці проєкту дав такі підсумки: claude-opus-5 відповіла на 4,691 хід, claude-opus-4-8 — на 1,231, шість ходів позначені як синтетичні, а остання група зі 133 рядків містила лише sonnet, haiku або opus.

Ці 133 — не фактичні моделі. Вони лежать у message.content.input.model, де зберігаються параметри запуску субагента: модель, запитана для субагента й записана всередині виклику інструмента. Фактична модель живе в message.model. Назва ключа та сама, шлях інший, а зміст протилежний. Вибірка за шляхом прибирає цей хвіст:

jq -r 'select(.type=="assistant") | .message.model' *.jsonl | sort | uniq -c | sort -rn

Перша спроба помилилася, і сам характер помилки — це і є урок. Один файл зберігає і запитану, і фактичну модель під однаковим ключем, і розрізняє їх лише шлях. Простий grep дає число, але не ту відмінність, яка важлива.

Обидва повні id обирали навмисно, у сесіях, що пережили зміну версії, тож ці підсумки не показують жодної підміни класифікатором — у цих логах її не знайшлося. Є інструмент вимірювання, а не знахідка. У записах також є isSidechain, що позначає трафік субагентів, і requestId, тож порівняти запитану й фактичну модель для кожного субагента — робота на пів дня по файлах, які вже на диску. Таку просту обв'язку не мають писати самі користувачі.

Обв'язка вже вважає зміну моделі подією

Claude Code вже місяцями, реліз за релізом, вважає відкат повноцінною концепцією. Версія 2.1.166 додала налаштування fallbackModel зі списком до трьох моделей, які пробуються по черзі, коли основна перевантажена чи недоступна. У 2.1.76 ці сповіщення стали видимими завжди, а не ховалися в докладному режимі, і отримали зрозумілі назви. Реліз 2.1.183 виводить попередження в stderr у режимі print, якщо запитана модель застаріла або її тихо оновили. У 2.1.233 невідомий id моделі дає рядок про нерозпізнану модель у stderr. Остання збірка додає події хуків PreModelSwitch і PostModelSwitch, щоб код міг заборонити, підтвердити чи позначити перемикання.

П'ять рішень ведуть до одного висновку: про зміну моделі оператор має дізнатися.

Усе це стосується перемикань, які робить сама обв'язка. Підміна, зроблена вище за течією класифікатором, приходить без сповіщення, без події хука, без виводу в stderr і без типізованої помилки. Команда, яка вирішила, що пониження через доступність заслуговує постійного банера, випускає пониження через безпеку взагалі без нічого. Злий намір — малоймовірне пояснення. Краще пасує шов: обв'язка повідомляє про свої дії, модель — про свої, а підміна відбувається в проміжку між ними.

Той самий патерн деінде

19 серпня OpenAI показала прев'ю Private Safety Processing — шару моніторингу, який дивиться на входи й виходи кількох розмов разом, щоб помічати зловживання, які проявляються лише між сесіями. Компанія каже, що не зберігає дані клієнта й видає лише вузько обмежений сигнал про тип активності. Це відповідь на 30-денне зберігання, яке Anthropic оголосила в липні для відповідних моделей, — політику, що стривожила компанії, які працюють із чутливими даними.

Називати це неправдою було б нечесно. Це просто неможливо перевірити. Зіставляти поведінку між сесіями й водночас нічого не зберігати — твердження в явній суперечності, і щоб її розв'язати, треба знати, що містить сигнал і скільки триває вікно зіставлення. Ні те, ні інше не опубліковано.

Форма та сама, що й з відкатом: властивість сервісу, яка суттєво впливає на клієнтів, спостережувана лише вендором і розкрита з тією деталізацією, яку обере вендор. Поведінка безпеки стала полем конкуренції, і конкурують там за властивостями, для яких у клієнтів немає вимірювального приладу.

Що це виправило б

Ніхто тут не просить відкочувати зміни. Біологічний класифікатор може лишитися, як і відкати, які допис перелічує для вірусології, токсикології та молекулярного дизайну. Прохання маленьке: воно коштує одного рядка.

Підміні місце в конверті відповіді. Підійде будь-який варіант: заголовок, додаткове поле в usage або атрибут телеметрії, що розділяє запитану модель і модель, яка відповіла. Нічого незвичного в цьому немає. Запити від субагентів уже йдуть із заголовками agent-id і parent-agent-id, тож додавати до запиту відомості про модель в один бік — усталена практика. Водночас експорт OpenTelemetry позначає атрибутом model лише одну метрику (рядки коду), і ніде немає сигналу, що модель, яка відповіла, відрізнялася від запитаної.

Причину розкривати не обов'язково. Достатньо категорії або навіть просто факту, що це сталося, — переданого в момент, коли це відбувається, усередині відповіді.

Правило, яке повторюється знову й знову, переживе і цього вендора, і цей місяць: усе, що провайдер відмовляється класти у відповідь, стає багом клієнта. Воно потрапляє в його бюджет помилок, його код-рев'ю, його постмортем і його оцінку власної компетентності. Ті 85% виміряли по той бік межі, де вендор. Заголовок переніс би наступну цифру на бік клієнта.