LLM-модели и API
Verboo, Venice AI и Drael против NotHoly для задач ИБ
Verboo, Venice AI и Drael рядом с NotHoly, ноябрь 2026: оплата, формат API, условия лицензии, базы моделей и где работают инструменты, с источниками.
Как устроен запрос к чат-API: модель, сообщения, токены, стриминг, API-ключи, лимиты запросов и ошибки HTTP, с разобранными примерами.
LLM API — это веб-эндпоинт, который принимает текст и возвращает ответ модели. Ваша программа отправляет HTTP-запрос с именем модели, списком сообщений и лимитом токенов. Сервис присылает сгенерированный текст, причину остановки и число токенов, которые модель прочитала и написала. Чат-приложение делает ту же работу за полем ввода. API даёт вашему коду эти части напрямую, и код сам решает, что отправить и что делать с ответом.
В чат-приложении модель кажется собеседником. На деле каждое ваше сообщение уходит отдельным полным HTTP-запросом. Приложение собирает предыдущие сообщения, добавляет новое, отправляет всё на сервер и показывает пришедший текст. Модель не помнит ваше прошлое сообщение, если приложение не отправит историю заново.
API обходится без интерфейса. Ваш код собирает список сообщений, задаёт лимит токенов и обрабатывает ответ. Сервису всё равно, кто его вызывает: браузер, скрипт или задача по расписанию. Он проверяет формат запроса и ключ.
С самого начала понадобятся три термина:
В окне чата вопросы задаёт один человек. Коду нужны повторяемые входные данные, вывод, который можно разобрать, и ошибки, которые можно перехватить. Как только скрипт начинает вызывать модель, ключ, счёт и сбои становятся вашей ответственностью. Большинство проблем при первой интеграции возникает из-за некорректного тела запроса, отсутствующего или неверного ключа, лимита, который никто не проверил, или ответа, который обрабатывали так, будто он всегда успешен.
Почти любой LLM API, который вам встретится, — это веб-API поверх HTTP. У запроса есть метод, URL, заголовки и обычно тело в JSON. Вызов модели почти всегда идёт через POST. RFC 9110, действующий стандарт семантики HTTP, определяет значение этих частей и то, как сервер должен отвечать. Коды статуса и заголовки, которые встретятся дальше, взяты из него.
Тело запроса — это объект JSON. В минимальном виде он называет модель и передаёт диалог на текущий момент. Это формат Messages, один из двух стилей запроса, которые сравниваются ниже. Значения здесь условные, и ничего не выполняется.
{
"model": "<MODEL_ID_FROM_CURRENT_CATALOG>",
"max_tokens": 300,
"system": "You explain security concepts in plain English. Keep answers under 120 words.",
"messages": [
{
"role": "user",
"content": "What is the difference between hashing and encryption?"
}
],
"stream": false
}Разбор:
model содержит ID модели, которая генерирует ответ. Копируйте его из актуального списка провайдера, старые туториалы для этого не годятся.max_tokens ограничивает число токенов, которые может написать модель. Размер промпта он не ограничивает.system содержит постоянные инструкции. В этом формате поле находится вне списка сообщений.messages содержит диалог. Здесь в нём один ход пользователя.stream равно false, поэтому сервер ждёт полного ответа и только потом отвечает.Роль каждого сообщения говорит о том, кто его автор:
user: текст от конечного пользователя или вашей программы.assistant: ответ, который модель написала раньше. Его отправляют обратно, чтобы продолжить диалог.system или developer: постоянные инструкции. Название зависит от формата.tool: результат вызова инструмента, об этом в одной из следующих статей.Чтобы продолжить диалог, добавьте последний ответ модели как сообщение assistant, а затем новое сообщение user. Если забыть предыдущие ходы, модель ответит на новый вопрос без контекста, и ничто об этом не сообщит. Это один из самых частых багов.
max_tokens (или его аналог) ограничивает объём текста, который напишет модель. Если ответ упрётся в лимит, текст оборвётся на середине фразы, и ответ об этом сообщит. Проверяйте причину остановки (о ней в шаге 6) и не считайте каждый ответ полным. На стоимость промпта лимит не влияет, потому что входные токены всё равно идут в расход.
С "stream": true сервер отправляет ответ по частям, по мере генерации, через server-sent events. Пользователь видит, как слова появляются одно за другим. Ваш код читает события и склеивает части, а данные о расходе и причина остановки приходят в заключительных событиях. Используйте стриминг, когда на ответ смотрит человек. Для пакетной задачи, которая сохраняет ответ, обычный запрос проще написать и протестировать.
Типичный ответ содержит:
choices.Прежде чем доверять тексту, прочитайте причину остановки. Ответ, остановленный лимитом токенов, неполон. Ответ, который заканчивается вызовом инструмента, ещё не окончательный: ваш код должен запустить инструмент и отправить результат обратно.
Объект usage определяет ваш счёт. Логируйте счётчики, ID модели, время, причину остановки и код статуса. По умолчанию не логируйте промпты и ответы, потому что в них могут оказаться данные клиентов, приватный код или секреты. Чтобы найти скачки расходов и сбои, достаточно метаданных.
В публичной документации широко используются два стиля запросов, и многие сервисы копируют один из них, чтобы готовый клиентский код продолжал работать с другим базовым URL. Если знать оба, можно читать примеры из любого источника.
Первый — формат Messages, его описание по первой ссылке раздела «Источники». Он принимает system как поле верхнего уровня, требует max_tokens, возвращает блоки контента с stop_reason и сообщает input_tokens и output_tokens.
Второй — формат Chat Completions, его описание по второй ссылке того же раздела. Инструкции system или developer он помещает внутрь messages, принимает max_completion_tokens и возвращает массив choices, где в каждом элементе есть finish_reason и message. Его объект usage сообщает prompt_tokens, completion_tokens и total_tokens.
| Параметр | Формат Messages | Формат Chat Completions |
|---|---|---|
| Постоянные инструкции | Поле system верхнего уровня |
Сообщение system или developer в messages |
| Обязательные поля | model, messages, max_tokens |
model, messages |
| Лимит вывода | max_tokens (обязательно) |
max_completion_tokens (необязательно) |
| Где лежит ответ | content, список блоков |
choices[0].message |
| Причина остановки | stop_reason |
choices[0].finish_reason |
| Счётчики токенов | usage.input_tokens, usage.output_tokens |
usage.prompt_tokens, usage.completion_tokens, usage.total_tokens |
| Стриминг | stream: true, server-sent events |
stream: true, чанки server-sent events |
Форматы различаются формой, смысл у них один. Оба отправляют модель, диалог и лимит, оба возвращают текст, причину и счётчики. Если пример написан в другом формате, сопоставьте поля по их роли в этой таблице и не копируйте JSON как есть.
Тот же вопрос в формате Chat Completions:
{
"model": "<MODEL_ID_FROM_CURRENT_CATALOG>",
"max_completion_tokens": 300,
"messages": [
{
"role": "developer",
"content": "You explain security concepts in plain English. Keep answers under 120 words."
},
{
"role": "user",
"content": "What is the difference between hashing and encryption?"
}
],
"stream": false
}Ответ в этом формате выглядит так. Значения условные, это не реальный ответ:
{
"id": "<RESPONSE_ID>",
"object": "chat.completion",
"model": "<MODEL_ID_FROM_CURRENT_CATALOG>",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "<GENERATED_TEXT>" },
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 0,
"completion_tokens": 0,
"total_tokens": 0
}
}Ваш код читает choices[0].message.content, choices[0].finish_reason, а затем объект usage. Формат Messages хранит те же данные в content, stop_reason и usage, под другими именами.
API-ключ определяет аккаунт, который платит за вызов, а также доступные модели и лимиты. Форматы передают его по-разному. Chat Completions использует заголовок Authorization со схемой Bearer. Messages использует отдельный заголовок для ключа и требует ещё и заголовок с версией API, чтобы провайдер мог сохранять прежнее поведение.
Имена заголовков зависят от провайдера и версии, поэтому копируйте их из актуальной документации. Выглядит это так:
# Placeholder shape only. Do not run this with a real key in a shared shell history.
curl https://<API_HOST>/<MESSAGES_PATH> \
-H "x-api-key: <YOUR_API_KEY>" \
-H "anthropic-version: <VERSION_FROM_CURRENT_DOCS>" \
-H "content-type: application/json" \
-d '{"model":"<MODEL_ID>","max_tokens":300,"messages":[{"role":"user","content":"Hello"}]}'# Placeholder shape only. Same caution as above.
curl https://<API_HOST>/<CHAT_COMPLETIONS_PATH> \
-H "Authorization: Bearer <YOUR_API_KEY>" \
-H "Content-Type: application/json" \
-d '{"model":"<MODEL_ID>","messages":[{"role":"user","content":"Hello"}]}'Разбор:
Authorization: Bearer — стандартная схема HTTP для токенов. RFC 9110 определяет заголовок Authorization в разделе об аутентификации.Отсюда начинается большинство расходов и утечек, которых можно было избежать. Правила ниже следуют OWASP Secrets Management Cheat Sheet.
Не храните ключи в исходном коде. Ключ, записанный в файл Python, живёт в каждой копии этого файла: на вашем ноутбуке, на машинах коллег, в системе CI и в каждом коммите, который его затронул. OWASP называет рискованной практикой секреты, зашитые в исходный код и файлы конфигурации.
Никогда не коммитьте ключ. Git хранит историю. Если удалить ключ следующим коммитом, в старом он останется. OWASP отмечает, что для удаления секрета из истории может понадобиться её переписывание, а у него есть побочные эффекты. Считайте ключ, попавший в общий репозиторий, раскрытым. Отзовите его, создайте новый и проверьте аккаунт на следы чужого использования.
Не оставляйте ключи в логах, на скриншотах и в коде для браузера. Строку лога, сообщение об ошибке или скрипт на веб-странице могут прочитать другие. Вызовы, которым нужен ключ, должны выполняться на сервере под вашим контролем.
Не зашивайте ключи в образы контейнеров. OWASP предостерегает от секретов в инструкциях Docker ENV и ARG, потому что значение оказывается в метаданных образа и истории сборки.
Используйте хранилище секретов. OWASP пишет, что переменные окружения, как правило, доступны всем процессам и могут попасть в логи или дампы, поэтому рекомендует их только там, где лучших вариантов нет. На ноутбуке подойдёт менеджер паролей или хранилище учётных данных операционной системы. На серверах используйте управляемый сервис секретов или смонтированные файлы с ограниченными правами.
Ограничивайте и ротируйте. Выдавайте каждому ключу только нужный ему доступ и заменяйте ключи по расписанию. OWASP пишет, что секретам должны быть назначены минимальные привилегии, и рекомендует автоматическую ротацию там, где это возможно.
Безопасная локальная схема хранит значение в окружении и читает его по имени:
# Read the key from the environment. Fail early if it is missing.
import os
api_key = os.environ.get("LLM_API_KEY")
if not api_key:
raise SystemExit("LLM_API_KEY is not set. Set it in your shell or secret store.")Добавьте файлы локальных настроек в .gitignore ещё до их создания. Закоммитьте шаблон вроде .env.example с именами переменных и пустыми значениями, а настоящий файл никогда не коммитьте.
Провайдеры ограничивают число запросов и токенов, которые аккаунт может использовать за промежуток времени. Их часто называют лимитами запросов, а у некоторых аккаунтов есть ещё и лимиты расходов. Значения зависят от аккаунта, модели и времени, поэтому смотрите консоль провайдера и актуальную документацию. Конкретных цифр в этой статье нет.
Когда вы упираетесь в лимит, сервер обычно возвращает 429 Too Many Requests. Этот код определён в RFC 6585, который расширяет базовый набор HTTP. В ответе может быть Retry-After: RFC 9110 определяет его в разделе о полях ответа, и он говорит, сколько нужно ждать.
Коду следует читать статус раньше тела, делать паузу перед повтором (использовать Retry-After, если он есть, а иначе нарастающую задержку со случайным разбросом) и останавливаться после нескольких попыток, чтобы не зациклиться навсегда. Считайте токены перед отправкой очень больших промптов: слишком большой запрос не пройдёт при любом балансе.
Первая цифра кода статуса указывает класс: проблема на стороне клиента, сбой сервера или что-то ещё. Классы и отдельные коды определены в RFC 9110. Вот ошибки, с которыми чаще всего сталкиваются новые интеграции.
| Статус | Название | Что это обычно значит | Что проверить |
|---|---|---|---|
| 400 | Bad Request | Тело запроса некорректно или в поле недопустимое значение | Синтаксис JSON, имена полей, обязательные поля, диапазоны значений |
| 401 | Unauthorized | Ключ отсутствует, искажён или отозван | Имя заголовка, префикс Bearer, значение ключа в окружении |
| 403 | Forbidden | Ключ действителен, но этому аккаунту ресурс недоступен | Доступ к модели, права аккаунта, состояние оплаты |
| 404 | Not Found | Неверный путь, или такого ID модели нет для вашего аккаунта | Базовый URL, путь, точный ID модели из актуального каталога |
| 429 | Too Many Requests | Достигнут лимит запросов или квота | Retry-After, страница расхода, объём запросов |
| 500 | Internal Server Error | Сбой на стороне сервера | Повторите с нарастающей задержкой, затем проверьте страницу статуса провайдера |
| 503 | Service Unavailable | Сервис перегружен или на обслуживании | Повторите с нарастающей задержкой и максимальным числом попыток |
Ошибка 4xx означает, что запрос нужно изменить, и повторная отправка того же запроса не поможет. Запрос с ошибкой 5xx или 429 может пройти позже, поэтому в этих случаях стоит повторять с нарастающей задержкой. Логируйте код статуса, ID модели и ID запроса. Никогда не логируйте ключ или промпт.
Доверие первому ответу. Ответ может оборваться из-за лимита токенов или остановиться по другой причине. Проверяйте причину остановки каждый раз.
Забытые предыдущие ходы. API без состояния ничего не знает о предыдущем запросе, если его не отправить заново. Собирайте список сообщений из своего хранилища.
ID модели, зашитый в код. ID моделей выводят из обращения и переименовывают. Храните ID в конфигурации, тогда замена сводится к правке одной строки.
Смешение двух форматов. Тело Chat Completions, отправленное в эндпоинт Messages, и наоборот, приводит к ошибкам 400. Прежде чем копировать пример, проверьте, какой формат описан в документации эндпоинта.
Прочитайте справочник одного провайдера от начала до конца. Разделы о запросах, ответах и ошибках короткие и закрывают большинство вопросов первой недели. Затем почитайте о стриминге и контекстных окнах: от них зависит, какой объём диалога можно отправить, прежде чем запрос завершится ошибкой.
Шлюз NotHoly документирует свои поля и коды ошибок. Сравните их с двумя формами выше, а в вопросах его поведения опирайтесь на его документацию.
Если вы хотите, чтобы модель вызывала ваш код, следующий шаг — tool use: ход assistant, причина остановки которого просит вашу программу что-то запустить и отправить результат обратно. Этот цикл разобран в следующей статье.
Retry-After.429 Too Many Requests.LLM-модели и API
Verboo, Venice AI и Drael рядом с NotHoly, ноябрь 2026: оплата, формат API, условия лицензии, базы моделей и где работают инструменты, с источниками.
LLM-модели и API
Безлимитные ИИ-тарифы обычно ограничивают скорость, а не токены. Verboo, Venice.ai, Drael и предоплата за токены сравнены по условиям и счетам.
LLM-модели и API
Перенос рабочего процесса, совместимого с OpenAI, с Verboo, Venice AI или Drael на NotHoly: base URL, ключ, ID моделей, оплата, лицензия и инструменты.