Пять ошибок при подключении LLM-шлюза, которые я совершил лично
- Ошибка 1. Проверять доступ через curl и делать выводы
- Ошибка 2. Обходить проблему вместо того, чтобы её понять
- Ошибка 3. Читать документацию вместо того, чтобы спросить сервер
- Ошибка 4. Фильтровать симптом, а не класс проблемы
- Ошибка 5. Считать, что токены стоят одинаково
- Итог
Шлюз к языковым моделям выглядит просто: меняешь один адрес в конфиге и получаешь доступ к десятку моделей по одному ключу. Настройка занимает две минуты, если всё идёт правильно, и три вечера, если нет. У меня пошло не так, поэтому вот список грабель в том порядке, в котором я на них наступил.
Речь про AgentRouter, но первые три ошибки универсальные, они повторятся с любым посредником.
Ошибка 1. Проверять доступ через curl и делать выводы
Самое естественное действие после получения ключа: дёрнуть API руками.
curl https://agentrouter.org/v1/chat/completions \
-H "Authorization: Bearer ВАШ_КЛЮЧ" \
-H "Content-Type: application/json" \
-d '{"model":"claude-opus-5","messages":[{"role":"user","content":"hi"}]}'
Я получил отказ:
{"error":{"message":"unauthorized client detected"},"type":"unauthorized_client_error"}
Живу в России, поэтому диагноз поставил за две секунды: заблокирован по стране. Записал «ключ бесполезен, нужен VPN» и закрыл тему на несколько дней.
Диагноз был неверный. Тот же ключ, та же квартира, без VPN, но запрос из настоящего клиента прошёл и вернул нормальный ответ. Шлюз смотрит не только на адрес, но и на то, чем вы пришли, и голый curl ему подозрителен.
Что делать: проверяйте тем клиентом, которым будете работать. Отказ curl не означает ничего, кроме того, что curl не понравился. У меня на этой ошибке ушёл вечер и почти улетел рабочий ключ.
Ошибка 2. Обходить проблему вместо того, чтобы её понять
Дальше выяснилось, что один протокол работает, другой падает с ошибкой валидации. Я обрадовался и пошёл рабочим путём: если Anthropic-эндпоинт принимает запросы, буду ходить туда, там та же модель.
Оказалось, что «принимает запросы» и «возвращает ответ» это разные вещи. На моём аккаунте этот путь отдавал успешный статус, ноль токенов и пустой текст. Никакой ошибки, просто ничего. Это худший режим отказа, потому что похоже на «модель решила промолчать», а не на «вы стучитесь не туда».
По факту шлюз честно поддерживает только OpenAI-совместимый /v1/chat/completions, а второй эндпоинт декоративный.
Что делать: если обход дал подозрительно тихий результат, проверьте, что в ответе есть токены, а не только отсутствие ошибки. Пустой успешный ответ это симптом, а не удача.
Ошибка 3. Читать документацию вместо того, чтобы спросить сервер
В доках был один список моделей, в реальности другой. Модель, которой в таблице не значилось, работала прекрасно.
У шлюза есть открытый эндпоинт с прайсом, регистрация не нужна:
curl -s https://agentrouter.org/api/pricing | jq '.data[] | {model_name, model_ratio, completion_ratio}'
Он вернул мне claude-opus-4-8, claude-opus-5, gpt-5.6-sol, и это оказалось достовернее документации. Заодно там видны коэффициенты списания, о которых пункт 5.
Что делать: спрашивайте сервер. Документация у быстро меняющихся сервисов отстаёт всегда.
Ошибка 4. Фильтровать симптом, а не класс проблемы
Теперь про ту самую ошибку валидации. Клиент падал с AI_TypeValidationError, потому что шлюз досылает в поток лишний кадр:
data: {"billing":{...},"object":"billing.summary"}
По спецификации каждый кадр это объект с choices либо с error, а это справка о списании, и строгий клиент от неё умирает. Я написал фильтр, который выкидывает кадры с object == "billing.summary", всё заработало, я закрыл вопрос.
Через неделю на другой модели прилетело:
data: null
Фильтр, заточенный под биллинг, пропустил это дальше, и клиент упал заново, уже с другой ошибкой про expected object, received null. Я лечил конкретный симптом вместо класса проблемы.
Правильное правило: выкидывать любой data:, который не является JSON-объектом. Пропускать [DONE], пропускать не-JSON, пропускать объекты (включая {"error":...}, клиент сам их покажет), выбрасывать null, массивы, числа и биллинг.
Что делать: когда пишете фильтр для чужого нестандартного протокола, описывайте, что вы согласны принимать, а не что хотите выбросить. Список допустимого конечен, список мусора нет.
И отдельная мелочь, которая стоила мне часа: первый дамп потока был бинарным мусором, потому что клиент просил gzip. Пока не заставите апстрим отдавать Accept-Encoding: identity, вы фильтруете сжатые байты и не понимаете, почему ничего не находится.
Ошибка 5. Считать, что токены стоят одинаково
Кредиты у меня уходили быстрее, чем я ожидал, и я решил, что дело в длинных промптах. Начал их подрезать, эффекта почти не заметил.
Смотрю в прайс и вижу completion_ratio: 5 у всех моделей, которыми пользуюсь. Ответ модели стоит впятеро дороже запроса. То есть я экономил не на той стороне.
Что реально работает после этого открытия:
- просить короткий ответ выгоднее, чем присылать короткий запрос;
- автономный агент дорогой не из-за задачи, а из-за того, что он сам с собой разговаривает, и каждая его внутренняя реплика это исходящие токены по пятикратной ставке;
- служебные мелочи вроде автогенерации заголовков чатов тоже тратят деньги и обычно отключаются в настройках клиента.
Итог
Ни одна из этих пяти ошибок не была ошибкой конфигурации, все пять были ошибками рассуждения: поверил не тому инструменту проверки, обошёл вместо того чтобы понять, доверился доке, лечил симптом, оптимизировал не ту сторону.
Схема при этом рабочая, я на ней остался: один ключ на все инструменты, кредиты на старте без привязки карты, смена модели правкой строки в конфиге. Если хотите попробовать, регистрация через GitHub здесь: agentrouter.org/register. Ссылка реферальная, бонус получаем оба; хотите без этого, уберите ?aff= из адреса, статья от этого не изменится.
Для продакшна с гарантиями берите вендора напрямую: посредник это лишняя точка отказа, которой вы не управляете. Для перебора моделей и экспериментов схема окупается.
Write a comment