Как вы проектируете API для внешних систем, использующих вашу LLM?

Краткий тезис

Проектирование API для LLM — это не просто «сделать REST-эндпоинт», а создание эффективного, безопасного и масштабируемого интерфейса, который учитывает специфику генеративных моделей. Ключевые решения включают выбор протокола (REST/gRPC), обязательную поддержку стриминга (SSE/WebSocket) для улучшения пользовательского опыта, версионирование, аутентификацию с rate limiting, и описание через OpenAPI. API должно быть устойчивым к сбоям, обеспечивать мониторинг и кэширование, а также предусматривать гибкую настройку параметров генерации (temperature, max_tokens и др.).


1. Выбор протокола: REST vs gRPC

Первый шаг — определиться с транспортом. Основные варианты: REST (HTTP/1.1) и gRPC (HTTP/2). Выбор зависит от приоритетов: простота разработки или производительность и нативный стриминг.

ХарактеристикаREST (JSON over HTTP)gRPC (Protobuf over HTTP/2)
Формат данныхJSON (текстовый)Protobuf (бинарный)
Сложность разработкиНизкая (знаком всем)Средняя (генерация клиентов, Proto-файлы)
СтримингSSE (Server-Sent Events), WebSocket (доп. протокол)Нативная поддержка Server-Side Streaming, Bi-di Streaming
ПроизводительностьМедленнее (JSON парсинг)Быстрее (бинарный, сжатие заголовков)
Экосистема инструментовОгромная (Postman, curl, Swagger)Меньше, но растёт (grpcurl, BloomRPC)
КешированиеПростое (HTTP-кеши, CDN)Сложное (кеш на уровне приложения)

Рекомендация для большинства внешних систем, особенно если клиенты — веб-приложения или мобильные приложения, выбирайте REST + SSE. Это даёт меньший порог входа и совместимость с инструментами разработчика. Если же предъявляются высокие требования к задержкам и пропускной способности (например, внутренние микросервисы), рассмотрите gRPC. В любом случае критически важный стриминг должен быть реализован через SSE (поверх REST) или встроенный стриминг gRPC.


2. Базовые эндпоинты: синхронная генерация и стриминг

API должно предоставлять как минимум два эндпоинта:

2.1 POST /v1/generate (синхронный)

Клиент отправляет запрос и ждёт полный ответ. Используется, когда потребитель — неинтерактивный сервис (автоматическая обработка, batch-задачи).

Особенности

  • Таймаут — установить разумное ограничение (например, 60 секунд). Если генерация дольше — возвращать Gateway Timeout|504 Gateway Timeout.
  • Блокировка ресурсов — синхронный вызов может исчерпать пул потоков, поэтому использовать асинхронные фреймворки (FastAPI, aiohttp).
  • Размер ответа — может быть большим (например, генерация нескольких страниц). Ограничить с помощью max_tokens и/ max_output_length.

2.2 POST /v1/generate-stream (стриминг через SSE)

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

Реализация

  • Используем SSE (Server-Sent Events) — стандарт HTML5, простой в реализации на бэкенде (FastAPI: StreamingResponse с event-stream). Клиент использует EventSource в браузере.
  • Альтернатива — WebSocket (более сложный, двусторонний), но для простого стриминга текста SSE достаточно.
  • Каждый чанк — JSON объект с полем token, finished, error.
data: {"token": "Привет", "finished": false, "id": "chunk_1"}

data: {"token": " мир", "finished": false, "id": "chunk_2"}

data: {"token": "", "finished": true, "id": "chunk_final"}

Акцент «Стриминг обязателен для хорошего UX» — пользователь не должен ждать весь ответ, чтобы увидеть первую букву.


3. Аутентификация и rate limiting

API для LLM — дорогой ресурс (вычислительные затраты). Необходимо защитить от несанкционированного доступа и злоупотреблений.

3.1 Аутентификация

  • API Keys — простой и распространённый способ. Ключ передаётся в заголовке Authorization: Bearer <key>. Ключи хранятся в надёжном vault (например, HashiCorp Vault, AWS Secrets Manager).
  • OAuth 2.0 / JWT — для корпоративных сценариев, где нужно делегировать доступ с ограниченными правами.
  • Никогда не использовать API Key напрямую в URL (риск утечки через логи).

3.2 Rate Limiting

Ограничение количества запросов от клиента за единицу времени. Реализуется на уровне API Gateway (Kong, NGINX, Envoy) или внутри приложения (например, через slowapi для FastAPI).

Стратегии

  • Token Bucket — фиксированная скорость (например, 10 запросов в секунду).
  • Sliding Window — скользящее окно времени.
  • User-based vs IP-based — лучше привязывать к ключу, а не к IP (или оба).

Возвращаемые заголовки

X-RateLimit-Limit: 10
X-RateLimit-Remaining: 3
X-RateLimit-Reset: 1620000000

При превышении — HTTP 429 Too Many Requests с заголовком Retry-After.


4. Схема запроса и ответа

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

4.1 Тело запроса (POST /generate)

{
  "prompt": "Расскажи о квантовых вычислениях",
  "model": "llama-3-8b",
  "temperature": 0.7,
  "max_tokens": 1024,
  "top_p": 0.9,
  "stop": ["\n\n"],
  "stream": false
}

Поля

  • prompt (обязательный) — строка с запросом.
  • model (опционально, по умолчанию выбрана модель) — идентификатор модели. Позволяет A/B тестировать или выбирать multi-model.
  • temperature (0..2) — случайность генерации. 0 — детерминированно.
  • max_tokens — максимальное количество токенов в ответе.
  • top_p, frequency_penalty, presence_penalty — другие параметры генерации.
  • stop — список стоп-строк.
  • stream — если true, возвращать потоки.

4.2 Ответ (синхронный)

{
  "id": "gen_12345",
  "object": "text_completion",
  "choices": [
    {
      "index": 0,
      "text": "Квантовые вычисления... [полный ответ]",
      "finish_reason": "stop",
      "logprobs": null
    }
  ],
  "usage": {
    "prompt_tokens": 15,
    "completion_tokens": 200,
    "total_tokens": 215
  },
  "model": "llama-3-8b"
}

Поле usage критично для мониторинга затрат и отладки. finish_reason указывает, почему генерация остановилась (stop, length, timeout, error).


5. Стриминг (SSE/WebSocket)

Зачем Задержка от начала запроса до первого токена в синхронном режиме может составлять 1-3 секунды (TTFTTime to First Token). Стриминг сокращает воспринимаемую задержку до сотен миллисекунд. Кроме того, пользователь может прервать генерацию на середине.

Архитектура SSE

  • Клиент подключается к /v1/generate-stream с теми же параметрами, но stream: true.
  • Сервер отправляет чанки через text/event-stream.
  • Клиент собирает чанки и обновляет UI.

Вариант с WebSocket

  • Позволяет также отправлять сигнал «стоп» обратно на сервер.
  • Более сложный в реализации (управление сессиями, keep-alive).

Выбор Для внешних систем (сайты, мобильные приложения) чаще выбирают SSE из-за простоты. WebSocket — для интерактивных чат-ботов с возможностью прерывания.


6. Версионирование

API должно развиваться без ломания совместимости. Основные подходы:

  • URI-based /v1/generate, /v2/generate — самый распространённый, явный и простой для клиента.
  • Header-based Клиент передаёт Accept-Version: 2025-03-01. Менее заметно.
  • Query parameter ?version=2 — часто используется вместе с основным URI.

Рекомендация URI-based. Каждая версия живёт отдельно. Старые версии поддерживаются хотя бы несколько месяцев с предупреждением о deprecated.

Пример объявления в OpenAPI

servers:
  - url: https://api.example.com/v1

7. OpenAPI спецификация

OpenAPI (Swagger) — стандарт для описания REST API.

Что включает

  • список эндпоинтов, их параметры, тела запросов/ответов.
  • модели схем (например, GenerateRequest, GenerateResponse).
  • информацию об аутентификации (securitySchemes).
  • примеры запросов и ответов.

Инструменты

  • FastAPI автоматически генерирует OpenAPI (через /docs — Swagger UI, /redoc).
  • stoplight studio, Spectacle — для ручного описания.

Польза

  • Клиенты могут генерировать SDK (OpenAPI Generator, swagger-codegen).
  • Документация всегда актуальна.
  • Возможность автоматического тестирования.

8. Обработка ошибок

Унифицированный формат ошибок упрощает жизнь потребителям.

Рекомендуемая структура

{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Too many requests. Retry after 30 seconds.",
    "type": "rate_limit",
    "param": null,
    "details": {}
  }
}

Распространённые коды

HTTP Statuscodeописание
400invalid_promptпустой prompt или превышен лимит длины
401unauthorizedотсутствует или невалидный API key
429rate_limit_exceededпревышен лимит
500internal_errorвнутренняя ошибка сервера (модель упала)
503model_unavailableмодель перегружена или в процессе загрузки

Всегда возвращайте понятное текстовое описание и, если возможно, время до повторной попытки.


9. Мониторинг и логирование

Без мониторинга невозможно понять, как работает API и где проблемы.

Метрики (Prometheus + Grafana):

  • llm_requests_total (counter) по статусу, модели, эндпоинту.
  • llm_request_duration_seconds (histogram) — распределение задержек.
  • llm_tokens_generated_total — затраты токенов.
  • llm_streaming_ttft_seconds — время до первого токена.

Трейсинг (OpenTelemetry, Jaeger):

  • Каждый вызов LLM трейсится: время подготовки, вызов model_inference, постобработка.
  • Помогает находить узкие места (например, медленный retrieval или сеть).

Логирование:

  • Логировать входящие запросы (без чувствительных данных!) с ID запроса.
  • Использовать структурированные логи (JSON). Пример:
{"timestamp": "...", "request_id": "abc123", "method": "POST", "path": "/v1/generate", "user_id": "u_42", "model": "llama-3-8b", "status": 200, "duration_ms": 2500}

10. Кэширование запросов

LLM-генерация недетерминирована (особенно при temperature > 0), поэтому кэширование тривиальное (точное совпадение prompt) ограничено. Однако можно кэшировать результаты для идентичных prompt с одинаковыми параметрами и temperature = 0.

Реализация

  • Использовать Redis с TTL.
  • Ключ: hash(prompt + model + temperature + max_tokens).
  • Поступать только для синхронных запросов (стриминг обычно не кэшируется).

Плюсы снижение нагрузки на GPU, уменьшение стоимости. Минусы риск выдачи устаревших данных, если модель обновляется (тег модели должен включать версию).


11. Безопасность: предотвращение инъекций и проверка входных данных

LLM могут быть подвержены prompt injection — атаке, когда злонамеренный запрос заставляет модель выполнить нежелательные действия.

Меры

  • Валидация длины prompt — ограничить количество символов (например, 8192 токена).
  • Фильтрация нежелательных паттернов (SQL-подобные команды, meta-instructions) на уровне входа.
  • Использование системных промптов в самом эндпоинте (инструкции для модели, которые клиент не может переопределить).
  • Output sanitization — проверка ответа на приватные данные (например, емайлы, пароли) перед отправкой клиенту.

Дополнительно: настройка CORS для веб-клиентов, ограничение HTTP-методов (только POST для генерации).


Пет-проект для закрепления

Задача Разработать минимальный LLM API на FastAPI с одним синхронным и одним стриминговым эндпоинтом, аутентификацией по API-ключу и rate limiting.

Инструменты

  • FastAPI + Uvicorn
  • slowapi для rate limiting
  • transformers или llama-cpp-python (можно локальную модельку)
  • sse-starlette для SSE

Шаги:

  1. Настройка FastAPI приложения.
  2. Регистрация middleware для проверки API-ключа (простой dict в памяти).
  3. Эндпоинт POST /v1/generate:
    • принимает GenerateRequest (pydantic модель).
    • вызывает модель (синхронно).
    • возвращает GenerateResponse.
  4. Эндпоинт POST /v1/generate-stream:
    • тот же сериализатор, но stream: True.
    • использует StreamingResponse с text/event-stream.
    • генерация чанков через цикл по токенам.
  5. Rate limiting через slowapi: @limiter.limit("10/minute").
  6. Swagger-документация (встроенная в FastAPI) с securitySchemes.
  7. Тестирование через curl и Postman.

Ожидаемый результат

  • Полностью работающий API, который можно вызвать отдельно (например, curl -X POST http://localhost:8000/v1/generate -H "Authorization: Bearer mytestkey" -H "Content-Type: application/json" -d '{"prompt":"Hello","model":"tiny-llama","stream":false}').
  • Понимание, как устроен стриминг (можно увидеть пошаговую передачу токенов).
  • Навык подключения rate limiting и мониторинга (добавить Prometheus метрики опционально).

Связь с другими вопросами

ВопросТема
89Развёртывание LLM (деплой, инференс)
91Безопасность LLM и предотвращение prompt injection
92Мониторинг LLM систем
93Кэширование запросов LLM
94A/B тестирование моделей
95Multi-model сервинг (роутинг запросов)

Эти вопросы дополняют друг друга: дизайн API → безопасность → мониторинг → кэширование → эксперименты с моделями. Вместе они формируют полную картину промышленного LLM-сервиса.


Навигация