ADR 0051 — Доказанные грани capability; "для чего годится" — это курирование
Контекст
Первая реальная синхронизация OpenRouter принесла 338 моделей на экран курирования. Группировка по производителю (Фаза 12, хаб с подгруппами) сделала список навигируемым, но не ответила на вопрос, который действительно задаётся перед ним: какая из них подходит для того, что мне нужно сейчас?
Два открытия, измеренных против живого API, очерчивают проблему.
Первое: каталог уже публиковал то, чего экран не показывал. Парсер
читал id, name, context_length, pricing и supported_parameters — и
отбрасывал architecture. Хуже того, синхронизация никогда не обращалась к удалённому источнику за
capability модальности:
// sync-model-catalog.use-case.ts:202, antes
supportsVision: local?.supportsVision ?? false,
Значение бралось из уже записанного, а записанное рождалось
false. Итог: supports_vision = false у всех 338, включая модели,
у которых вся страница provider называется "vision". Колонка рождалась ложной и
умирала ложной — не было пути, по которому она могла бы стать истинной.
По каталогу от 2026-08-04 OpenRouter объявляет: 181 модель принимает
изображение на входе, 11 производят изображение, 213 принимают reasoning, 25
принимают аудио, 0 производят видео.
Второе: половины того, что хочется фильтровать, нет ни в одном каталоге. Запрос включал "melhores IAs por tipo — documentar, imagem, vídeo, thinking, code". Изображение и thinking provider объявляет. "Лучшее для кода" и "хорошее для документации" не публикует ни один provider — нет поля, нет соглашения, нет ничего, кроме имени модели. А выводить capability из имени — ровно то, что ADR 0041 запрещает: capability объявляется только тогда, когда suite её доказывает.
Видео — крайний случай: ноль моделей у девяти providers. Грань видео была бы фильтром, который никогда ничему не соответствует.
Решение
Обе оси существуют, раздельно, и каждая живёт там, где живёт её истина.
1. Грань capability — что provider ДОКАЗЫВАЕТ
Три новых поля в models, питаемые удалённым каталогом:
| поле | источник в OpenRouter |
|---|---|
supports_vision | architecture.input_modalities содержит image |
generates_image | architecture.output_modalities содержит image |
supports_reasoning | supported_parameters содержит reasoning |
Вход и выход изображения — разные оси, а не одна: модель, которая читает диаграмму, и модель, которая рисует, решают разные задачи, и их слияние отправило бы пользователя к неверной модели.
Синхронизация теперь читает из удалённого источника с локальным fallback, в той же форме, что и
supportsToolCalling:
supportsVision: remoto.supportsVision ?? local?.supportsVision ?? false,
undefined в удалённом источнике сохраняет локальное значение — отсутствие объявления не есть
объявление отсутствия. Парсер опускает поле, когда provider молчит,
вместо выдачи false; объявление false там стёрло бы ручное курирование
при первом же изменении формата каталога у provider.
Аудио и видео остаются вне: аудио — потому что ни одна часть продукта его сегодня не потребляет, видео — потому что его не существует. Войдут, когда будет что фильтровать.
2. Курирование по использованию — что ОБНАРУЖИЛА КОМАНДА
Закрытый словарь из пяти использований — codigo, documentacao, analise,
imagem, conversa — отмечаемый по workspace, в колонке workspace_models.uses
(text[]).
Живёт в workspace_models, а не в models, по той же причине, что и
ADR 0049: это мнение того, кто
эксплуатирует. Одна и та же модель — "та, что для кода" в одном workspace и дешёвая для разговора в
другом, а право решать имеет тот, кто платит по счёту. Глобальной оси нет,
потому что не существует глобального ответа.
Словарь закрытый, а не свободный текст: code, coding, Code и código
на одном экране за неделю дают фильтр, который ничему не соответствует, — хуже, чем
отсутствие фильтра. Новое использование входит в тип и получает миграцию, с доказательством
исчерпывающести во время компиляции с обеих сторон (тот же механизм, что у
llm-provider-names.ts).
text[], а не enum Postgres, как delegations.area Фазы 8: новое использование
не должно требовать миграции типа.
3. Две оси не смешиваются ни в UI, ни в базе
- Метка грани и метка использования имеют разные тона в строке каталога, а чипы фильтров разделены разделителем.
- Отметка использования не включает модель в селекторе. Колонка
is_activeимеет DEFAULTtrue, поэтому строка, рождённая отметкой использования, вставляется с явнымisActive: false— без этого мнение о модели разрешило бы ей тратить, вопреки RN-043. - Смена использования не выключает включённое:
is_activeостаётся внеSETвON CONFLICT. - Экран никогда не пишет "не читает изображение". Метка утверждает только истинное, потому что
falseздесь означает "provider не объявил".
Последствия
- Экран отвечает "какая модель подходит для этого" двумя путями: что provider доказывает и что команда обнаружила в использовании.
- Фильтр, обнуляющий список, становится отличимым от пустого каталога — раньше экран сказал бы "зарегистрируйте учётные данные" тому, у кого они уже есть.
- Существующий каталог получает истинные грани только при следующей синхронизации:
миграция создаёт колонки с
false, а синхронизация заполняет их из удалённого источника. Никакой backfill невозможен без обращения к provider, а выдумывать значение было бы снова исходным дефектом. - Восемь providers, не публикующих модальность, остаются с
false— честно и деградируемо, как только любой из них начнёт объявлять. - "Лучшее для кода" никогда не будет capability в этом продукте. Если provider когда-нибудь опубликует такое поле, это будет ещё одно мнение — его собственное — и оно не заменит мнения команды.
Три проекта были отброшены, и причина каждого — последствие, которое осталось бы:
- Выводить использование из имени модели (
*-coder-*→ код): догадка, переодетая в данные. Ошибается на универсальной модели, хорошей в коде, ошибается на той, у которой "coder" в имени, а команда сочла её плохой, и никто из знающих не может исправить. - Глобальное использование в
models: повторило бы ровно тот дефект, который ADR 0049 исправил, — один workspace решает за соседа. - Свободный текст в использовании: фрагментирует словарь за неделю и превращает фильтр в текстовый поиск.
Что остаётся на потом
Грань аудио и видео, когда будет что фильтровать: у аудио уже 25 моделей в OpenRouter, но ни одна часть продукта сегодня аудио не потребляет; у видео ноль у девяти providers.
Сортировать каталог по отмеченному использованию — сегодня использование только фильтрует. С зафиксированным словарём сортировка — одна строка; пока никто ничего не отметил, она сортировала бы по пустому списку.
Грани у остальных восьми providers. Только OpenRouter публикует модальность сегодня; каждый из прочих нужно исследовать по официальной документации, как Фаза 11 сделала с quirks, — наследовать парсер одного в другом запрещено.