Перейти к содержимому
New

DeepSeek V4.1 Flash free for 7 days: 10M tokens a day after a top-up of $10 or more. Until Nov 30.

How it works

Что такое LLM API: запросы, сообщения, токены и ошибки

Как устроен запрос к чат-API: модель, сообщения, токены, стриминг, API-ключи, лимиты запросов и ошибки HTTP, с разобранными примерами.

Начальный11 мин чтенияАвтор: NotHoly

LLM API — это веб-эндпоинт, который принимает текст и возвращает ответ модели. Ваша программа отправляет HTTP-запрос с именем модели, списком сообщений и лимитом токенов. Сервис присылает сгенерированный текст, причину остановки и число токенов, которые модель прочитала и написала. Чат-приложение делает ту же работу за полем ввода. API даёт вашему коду эти части напрямую, и код сам решает, что отправить и что делать с ответом.

Что такое LLM API

В чат-приложении модель кажется собеседником. На деле каждое ваше сообщение уходит отдельным полным HTTP-запросом. Приложение собирает предыдущие сообщения, добавляет новое, отправляет всё на сервер и показывает пришедший текст. Модель не помнит ваше прошлое сообщение, если приложение не отправит историю заново.

API обходится без интерфейса. Ваш код собирает список сообщений, задаёт лимит токенов и обрабатывает ответ. Сервису всё равно, кто его вызывает: браузер, скрипт или задача по расписанию. Он проверяет формат запроса и ключ.

С самого начала понадобятся три термина:

  • Модель: именованная модель, которая генерирует ответ. Каждый сервис публикует свои ID моделей, и этот список со временем меняется.
  • Сообщения: упорядоченный список ходов, у каждого из которых есть роль, например user или assistant.
  • Токены: фрагменты текста, которые модель читает и пишет. Токен часто короче слова. В токенах измеряются лимиты, расход и цены.

Зачем это разработчику

В окне чата вопросы задаёт один человек. Коду нужны повторяемые входные данные, вывод, который можно разобрать, и ошибки, которые можно перехватить. Как только скрипт начинает вызывать модель, ключ, счёт и сбои становятся вашей ответственностью. Большинство проблем при первой интеграции возникает из-за некорректного тела запроса, отсутствующего или неверного ключа, лимита, который никто не проверил, или ответа, который обрабатывали так, будто он всегда успешен.

Как работает запрос, шаг за шагом

1. Ваш код отправляет HTTP-запрос

Почти любой LLM API, который вам встретится, — это веб-API поверх HTTP. У запроса есть метод, URL, заголовки и обычно тело в JSON. Вызов модели почти всегда идёт через POST. RFC 9110, действующий стандарт семантики HTTP, определяет значение этих частей и то, как сервер должен отвечать. Коды статуса и заголовки, которые встретятся дальше, взяты из него.

2. В теле указаны модель и сообщения

Тело запроса — это объект JSON. В минимальном виде он называет модель и передаёт диалог на текущий момент. Это формат Messages, один из двух стилей запроса, которые сравниваются ниже. Значения здесь условные, и ничего не выполняется.

json
{
  "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, поэтому сервер ждёт полного ответа и только потом отвечает.

3. У сообщений есть роли

Роль каждого сообщения говорит о том, кто его автор:

  • user: текст от конечного пользователя или вашей программы.
  • assistant: ответ, который модель написала раньше. Его отправляют обратно, чтобы продолжить диалог.
  • system или developer: постоянные инструкции. Название зависит от формата.
  • tool: результат вызова инструмента, об этом в одной из следующих статей.

Чтобы продолжить диалог, добавьте последний ответ модели как сообщение assistant, а затем новое сообщение user. Если забыть предыдущие ходы, модель ответит на новый вопрос без контекста, и ничто об этом не сообщит. Это один из самых частых багов.

4. Лимит токенов ограничивает ответ

max_tokens (или его аналог) ограничивает объём текста, который напишет модель. Если ответ упрётся в лимит, текст оборвётся на середине фразы, и ответ об этом сообщит. Проверяйте причину остановки (о ней в шаге 6) и не считайте каждый ответ полным. На стоимость промпта лимит не влияет, потому что входные токены всё равно идут в расход.

5. Стриминг меняет только способ доставки

С "stream": true сервер отправляет ответ по частям, по мере генерации, через server-sent events. Пользователь видит, как слова появляются одно за другим. Ваш код читает события и склеивает части, а данные о расходе и причина остановки приходят в заключительных событиях. Используйте стриминг, когда на ответ смотрит человек. Для пакетной задачи, которая сохраняет ответ, обычный запрос проще написать и протестировать.

6. Ответ содержит текст, причину и счётчики

Типичный ответ содержит:

  • ID запроса или сообщения, он пригодится, если придётся сообщать о проблеме.
  • Сгенерированное содержимое. В формате Messages это список блоков контента. В формате Chat Completions оно лежит в массиве choices.
  • Причину остановки, то есть объяснение, почему генерация завершилась: например, естественный конец хода, лимит токенов или вызов инструмента.
  • Счётчики входных и выходных токенов.

Прежде чем доверять тексту, прочитайте причину остановки. Ответ, остановленный лимитом токенов, неполон. Ответ, который заканчивается вызовом инструмента, ещё не окончательный: ваш код должен запустить инструмент и отправить результат обратно.

7. Логируйте расход и не пишите содержимое

Объект 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:

json
{
  "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
}

Ответ в этом формате выглядит так. Значения условные, это не реальный ответ:

json
{
  "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-ключам

API-ключ определяет аккаунт, который платит за вызов, а также доступные модели и лимиты. Форматы передают его по-разному. Chat Completions использует заголовок Authorization со схемой Bearer. Messages использует отдельный заголовок для ключа и требует ещё и заголовок с версией API, чтобы провайдер мог сохранять прежнее поведение.

Имена заголовков зависят от провайдера и версии, поэтому копируйте их из актуальной документации. Выглядит это так:

bash
# 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"}]}'
bash
# 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 в разделе об аутентификации.
  • Набранные команды могут попасть в историю оболочки. Используйте переменную окружения, как показано ниже, и не вставляйте ключ напрямую.

Как не допустить ключи в код и Git

Отсюда начинается большинство расходов и утечек, которых можно было избежать. Правила ниже следуют OWASP Secrets Management Cheat Sheet.

Не храните ключи в исходном коде. Ключ, записанный в файл Python, живёт в каждой копии этого файла: на вашем ноутбуке, на машинах коллег, в системе CI и в каждом коммите, который его затронул. OWASP называет рискованной практикой секреты, зашитые в исходный код и файлы конфигурации.

Никогда не коммитьте ключ. Git хранит историю. Если удалить ключ следующим коммитом, в старом он останется. OWASP отмечает, что для удаления секрета из истории может понадобиться её переписывание, а у него есть побочные эффекты. Считайте ключ, попавший в общий репозиторий, раскрытым. Отзовите его, создайте новый и проверьте аккаунт на следы чужого использования.

Не оставляйте ключи в логах, на скриншотах и в коде для браузера. Строку лога, сообщение об ошибке или скрипт на веб-странице могут прочитать другие. Вызовы, которым нужен ключ, должны выполняться на сервере под вашим контролем.

Не зашивайте ключи в образы контейнеров. OWASP предостерегает от секретов в инструкциях Docker ENV и ARG, потому что значение оказывается в метаданных образа и истории сборки.

Используйте хранилище секретов. OWASP пишет, что переменные окружения, как правило, доступны всем процессам и могут попасть в логи или дампы, поэтому рекомендует их только там, где лучших вариантов нет. На ноутбуке подойдёт менеджер паролей или хранилище учётных данных операционной системы. На серверах используйте управляемый сервис секретов или смонтированные файлы с ограниченными правами.

Ограничивайте и ротируйте. Выдавайте каждому ключу только нужный ему доступ и заменяйте ключи по расписанию. OWASP пишет, что секретам должны быть назначены минимальные привилегии, и рекомендует автоматическую ротацию там, где это возможно.

Безопасная локальная схема хранит значение в окружении и читает его по имени:

python
# 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, если он есть, а иначе нарастающую задержку со случайным разбросом) и останавливаться после нескольких попыток, чтобы не зациклиться навсегда. Считайте токены перед отправкой очень больших промптов: слишком большой запрос не пройдёт при любом балансе.

Частые ошибки HTTP

Первая цифра кода статуса указывает класс: проблема на стороне клиента, сбой сервера или что-то ещё. Классы и отдельные коды определены в 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, причина остановки которого просит вашу программу что-то запустить и отправить результат обратно. Этот цикл разобран в следующей статье.

Источники

Источники

  1. Messages API reference
  2. Chat Completions API reference
  3. RFC 9110: HTTP Semantics
  4. RFC 6585: Additional HTTP Status Codes
  5. OWASP Secrets Management Cheat Sheet

Ещё по теме: LLM-модели и API

LLM-модели и API

Verboo, Venice AI и Drael против NotHoly для задач ИБ

Verboo, Venice AI и Drael рядом с NotHoly, ноябрь 2026: оплата, формат API, условия лицензии, базы моделей и где работают инструменты, с источниками.

Средний8 мин чтения