Перейти к основному содержимому
Перевод от 2026-10-15; оригинал с тех пор изменился.

Приёмка LLM-providers с реальными учётными данными

ADR 0043 поставил девять providers с зелёным набором контрактных тестов — против mock. Он сам зафиксировал в «что остаётся на потом», что приёмка с реальными учётными данными для шести smoke-тестов оставалась открытой. Этот файл — место, где это «потом» отслеживается: одна строка на provider, обновляемая каждый раз, когда появляется ключ. ADR не редактируется; он — запись решения, этот файл — запись доказательства.

Золотое правило дома действует здесь целиком: capability объявляется, только когда доказана. Пока smoke не выполнен с настоящим ключом, то, что существует об этом provider, — это чтение официальной документации: хорошее, проверенное строка за строкой в Фазе 11, и всё же это не выполнение.

Состояние на 2026-09-25 — сообщение system после user/tool (AT-161)​

Другая ось, та же мерка. Набор контрактных тестов теперь доказывает, куда каждый adapter кладёт role: "system", приходящий ПОСЛЕ user или tool (в конец messages у восьми; поднимается в верхний system у Anthropic — см. таблицу расхождений). ПРИНИМАЕТ ли provider такое тело — вот что спрашивает sistema-tardio.smoke.spec.ts, и ответить может только реальный вызов:

OLLAMA_SISTEMA_TARDIO_SMOKE=1 pnpm --filter api exec vitest run test/infrastructure/llm/sistema-tardio
# providers de nuvem: exporte a mesma *_TEST_KEY dos smokes de aceite (ANTHROPIC_TEST_KEY para o Anthropic)
providerпоздний system принят реальным provider?как
Ollama✅ да, после user и после toolлокальный демон 0.32.15, qwen2.5-coder:7b; оба хода завершились текстом и без чанка ошибки
Anthropic❌ не доказанонет ANTHROPIC_TEST_KEY — и в теле, которое он отправляет, позднего system в любом случае нет (поднят)
OpenAI❌ не доказанонет OPENAI_TEST_KEY
OpenRouter❌ не доказанонет OPENROUTER_TEST_KEY в этом окружении; в хабе ответ зависит от upstream, поэтому один прогон доказывает обслуживший его upstream
NVIDIA NIM❌ не доказанонет NVIDIA_NIM_TEST_KEY
Together AI❌ не доказанонет TOGETHER_TEST_KEY
DeepInfra❌ не доказанонет DEEPINFRA_TEST_KEY
Bitdeer❌ не доказанонет BITDEER_TEST_KEY
Vultr❌ не доказанонет VULTR_TEST_KEY

Smoke доказывает только ПРИЁМ (ход завершается текстом, без чанка ошибки). ПОДЧИНЯЕТСЯ ли модель указанию — вопрос качества, и он относится к AT-082.

Состояние на 2026-08-14 — capability эмбеддингов (ADR 0075)​

НОВАЯ ось в этом списке. До сих пор «приёмка» означала сценарий ЧАТА; ADR 0075 добавил embed в контракт, и действует та же мерка: capability равна true только с доказательством выполнения.

Ollama закрыт, и он единственный. Прогнан против настоящего локального демона (0.32.1), с моделью, загруженной на месте:

docker exec brabo-dev-ollama-1 ollama pull nomic-embed-text
OLLAMA_EMBEDDING_SMOKE=1 pnpm --filter api test ollama-provider.embeddings
чторезультат
POST /api/embed, 2 входа2 вектора, по 768 измерений
prompt_eval_count10 — estimated: false, пришло от демона
/api/tags того же демонаnomic-embed-text с capabilities: ["embedding"] и embedding_length: 768; llama3.2:1b с ["completion","tools"]
модель ЧАТА, запрашивающая эмбеддинг501 — «This server does not support embeddings»

501 — самая ценная находка, и она стала тестом на обоих уровнях: это уровень МОДЕЛИ capability, падающий в самом позднем возможном месте, и причина, по которой assertCanEmbed отказывает раньше (RN-190). Реальная стоимость: US$ 0,00 — у локальной модели нет цены, и поэтому это единственная приёмка на этой странице, которую можно повторять сколько угодно.

providerembeddingsпрогнан?причина
Ollama✅ да✅ далокальный демон, без ключа и без затрат
Anthropic❌ нет—нет собственного endpoint эмбеддингов; документация указывает на третью сторону, а это другой provider с другим ключом и другим диалектом
OpenAI❌ нет❌ пропущенOPENAI_TEST_KEY отсутствует
OpenRouter❌ нет❌ пропущенключ есть, но прогнанный smoke был для чата — в хабе эмбеддинг маршрутизируется к другому провайдеру, и доказательство одного endpoint не является доказательством другого
Together AI❌ нет❌ пропущенTOGETHER_TEST_KEY отсутствует
DeepInfra❌ нет❌ пропущенDEEPINFRA_TEST_KEY отсутствует
NVIDIA NIM❌ нет❌ пропущенNVIDIA_NIM_TEST_KEY отсутствует
Bitdeer❌ нет❌ пропущенBITDEER_TEST_KEY отсутствует — иронично, ведь их документация по эмбеддингам — единственный аутентифицированный источник, найденный Фазой 11b
Vultr❌ нет❌ пропущенVULTR_TEST_KEY отсутствует

То, что доказано без каких-либо учётных данных, — это ДИАЛЕКТ: набор контрактных тестов запускается второй раз поверх OpenAI-совместимой базы с включённой capability и проверяет упорядочивание по index, чтение usage.prompt_tokens, отказ для неполного пакета и ошибку, нормализованную по code. Перевести provider в true, когда ключ появится, — значит изменить одну строку литерала и прогнать smoke, а не писать парсинг.

Состояние на 2026-08-07​

Первый платный прогон в этом списке. OpenRouter закрыт; остальные пять остаются пропущенными из-за отсутствия ключа — их учётных данных нет нигде в окружении, а в продукте зарегистрирован только ключ OpenRouter (тот же, что использовался в прогоне hello-limpo).

providerпрогнан?результат
OpenRouter✅ давесь сценарий зелёный, один платный вызов
Together AI❌ пропущенTOGETHER_TEST_KEY отсутствует
DeepInfra❌ пропущенDEEPINFRA_TEST_KEY отсутствует
NVIDIA NIM❌ пропущенNVIDIA_NIM_TEST_KEY отсутствует
Bitdeer❌ пропущенBITDEER_TEST_KEY отсутствует
Vultr❌ пропущенVULTR_TEST_KEY отсутствует

Реальная стоимость, прочитанная из token_usage тестовой базы:

providerмодельinoutcost_microsзамороженная ценаestimated
openrouteropenai/gpt-4o-mini2035150.000 /млнfalse

Пять микро-USD — US$ 0,000005, на два порядка ниже оценки «< US$ 0,001» из раздела выше. Здесь важно estimated: false: потребление пришло от provider, а не от локального оценщика, а записанная цена — та, что была заморожена в момент использования (RN-044), — а не цена таблицы models сегодня.

Что smoke нашёл по пути​

Он ни разу не запускался и поэтому молча сгнил относительно ADR 0049, который перенёс курирование из models.is_active в workspace_models:

  • он утверждал alvo.isActive === false о глобальном каталоге — поле, которого больше не существует и которое приходило undefined. Правильное утверждение RN-043 сегодня — workspaceModels.isActive(...), потому что отсутствие строки и есть выключено;
  • он собирал SetModelsActiveUseCase и SetModelBindingUseCase со старыми сигнатурами, без workspaceId/curatedBy и без репозитория курирования.

Ничто из этого не обнаруживалось CI: без ключа describe.skipIf пропускает весь файл, а typecheck api выполняется по tsconfig.build.json, который исключает test/. Приёмка, которая никогда не запускается, — не приёмка: это тот же механизм, что оставил таблицу Фазы 10 как «не измерено», и урок действует так же и здесь.

Исправлено вместе с прогоном: smoke теперь утверждает RN-043 путём ADR 0049. Когда остальные пять ключей появятся, оставшиеся пять smoke, вероятно, окажутся сгнившими так же и по той же причине.

Состояние на 2026-08-03​

Проверка окружения на эту дату: ни одна из шести переменных не экспортирована, и нет ключа provider под другим именем в .env. Без переменной describe.skipIf(!apiKey) пропускает весь набор с предупреждением — это правильное поведение, а не сбой. Ни одного платного вызова не сделано; стоимость этого прогона — US$ 0,00.

providersmokeпеременнаямодель по умолчаниюпрогнан?причинареальная стоимость (token_usage)
OpenRouteropenrouter-provider.smoke.spec.tsOPENROUTER_TEST_KEYopenai/gpt-4o-mini❌ пропущенключ отсутствует в окружении—
Together AItogether-provider.smoke.spec.tsTOGETHER_TEST_KEYmeta-llama/Llama-3.3-70B-Instruct-Turbo❌ пропущенключ отсутствует в окружении—
DeepInfradeepinfra-provider.smoke.spec.tsDEEPINFRA_TEST_KEYdeepseek-ai/DeepSeek-V3❌ пропущенключ отсутствует в окружении—
NVIDIA NIMnvidia-nim-provider.smoke.spec.tsNVIDIA_NIM_TEST_KEYmeta/llama-3.2-3b-instruct❌ пропущенключ отсутствует в окружении—
Bitdeerbitdeer-provider.smoke.spec.tsBITDEER_TEST_KEYmoonshotai/Kimi-K2.5❌ пропущенключ отсутствует в окружении—
Vultrvultr-provider.smoke.spec.tsVULTR_TEST_KEYkimi-k2-instruct❌ пропущенключ отсутствует в окружении—

Файлы находятся в apps/api/test/infrastructure/llm/. Модель по умолчанию переопределяется через <PROVIDER>_TEST_MODEL — полезно, когда у учётной записи нет доступа к модели из колонки.

Сколько стоит закрыть каждую строку​

Дёшево намеренно. Каждый smoke делает ровно один ход чата, с prompt Responda só a palavra "ok", sem mais nada. — порядка 20 входных токенов и горстка выходных. Всё остальное в сценарии бесплатно: проверка учётных данных — это GET /v1/models (или GET /key) только по статусу, а синхронизация каталога (у трёх, у которых listModels: true) — чтение.

платные вызовыбесплатные вызовыпорядок величины
на smoke1 ход чата (~20 входных токенов)тест соединения + sync< US$ 0,001
все шесть вместе6 ходов чата—< US$ 0,01

Даже на самой дорогой модели таблицы это не доходит до цента доллара за прогон. Стоит не токен, а наличие ключа.

Что доказывает каждый smoke — и это не одно и то же для всех​

Разделение следует capability, ровно как спроектировал ADR 0041:

С listModels: true — OpenRouter, Together, DeepInfra. Sync обращается к настоящему API и наполняет каталог. Именно здесь реальная цена встречает парсер. Самый важный случай — Together: ADR 0043 фиксирует, что единица цены (USD за миллион токенов vs. за токен) была выведена сравнением с рыночной ценой, потому что официальная документация её не объявляет, — и у smoke есть диапазон здравого смысла ровно для этого (inputPricePerMillionMicros между 10.000 и 1.000.000.000; вне его диагноз — «неверная единица», а не «дорогая модель»). Если однажды это пойдёт не так, это находка для исправления в парсере и здесь, никогда не для замалчивания. Smoke также подтверждает RN-043: обнаруженная модель входит выключенной, а активация — ручное курирование.

С listModels: false — NVIDIA NIM, Bitdeer, Vultr. Ни один из трёх не публикует цену за токен в доступной документации (NIM вообще не берёт плату за токен: коммерческая единица — GPU/час). Поэтому sync должен пропускаться с pulado: 'sem_capability' — и smoke утверждает ровно это, что делает объявленную capability проверяемой, а не принятой на веру. Модель входит через seed / ручное курирование с manual_pricing = true, и smoke реально проверяет id модели против настоящего API — сегодня это оценка, прочитанная из документации, — и путь metering от начала до конца с замороженной ценой.

Во всех шести шаг 1b проверяет настоящий ключ против настоящего API через TestStoredCredentialUseCase — ok в пяти и nao_suportado в DeepInfra, единственном без объявленного тестового endpoint. До ADR 0050 это доказательство происходило внутри регистрации; оно не исчезло, а сменило место и стало явным.

Во всех шести финальный шаг одинаков, и именно он важен для продукта: полная сессия чата, без событий error и metering_failed, со строкой в token_usage, несущей цену, замороженную в момент использования (RN-044), — а не сегодняшнюю цену из таблицы models.

Как закрыть незакрытый пункт​

export OPENROUTER_TEST_KEY=... # a variável do provider em questão
# opcional, se a conta não tiver o modelo default:
# export OPENROUTER_TEST_MODEL=...

pnpm --filter api test -- openrouter-provider.smoke.spec.ts

Требования:

  • запущенная тестовая база — TEST_DATABASE_URL, по умолчанию postgres://brabo:brabo@localhost:5432/brabo_test (та же, что у любого интеграционного теста api; smoke вызывает truncateAll перед началом);
  • кредит на счёте provider. Начиная с ADR 0050 регистрация ничего не проверяет — ключ проверяет шаг 1b smoke (TestStoredCredentialUseCase, только статус), а действительный ключ без баланса падает только на ходе чата.

После прогона обновите строку таблицы выше с датой, вердиктом и реальной стоимостью. Стоимость берётся из базы, а не из оценки:

SELECT provider, model_name, input_tokens, output_tokens, cost_micros
FROM token_usage ORDER BY created_at DESC LIMIT 1;

Если smoke провалится, строка становится ❌ reprovado с причиной, а находка становится исправлением в provider — никогда capability, объявленной на доверии.

Почему это живой документ​

Таблица — не отчёт на дату: это текущее состояние приёмки. Каждый появившийся ключ закрывает строку, а остальные остаются открытыми и видимыми. Режим отказа, ради предотвращения которого существует этот файл, — это режим Фазы 10, описанный в урожае первого dogfooding: таблица наблюдений, которую никто не заполнил и которая навсегда стала não medido. Здесь число берётся из token_usage запросом, а то, что не запускалось, записано как не запускавшееся.