Как вы проектируете 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 секунды (TTFT — Time 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 Status | code | описание |
|---|---|---|
| 400 | invalid_prompt | пустой prompt или превышен лимит длины |
| 401 | unauthorized | отсутствует или невалидный API key |
| 429 | rate_limit_exceeded | превышен лимит |
| 500 | internal_error | внутренняя ошибка сервера (модель упала) |
| 503 | model_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 limitingtransformersилиllama-cpp-python(можно локальную модельку)sse-starletteдля SSE
Шаги:
- Настройка FastAPI приложения.
- Регистрация middleware для проверки API-ключа (простой dict в памяти).
- Эндпоинт
POST /v1/generate:- принимает
GenerateRequest(pydantic модель). - вызывает модель (синхронно).
- возвращает
GenerateResponse.
- принимает
- Эндпоинт
POST /v1/generate-stream:- тот же сериализатор, но
stream: True. - использует
StreamingResponseсtext/event-stream. - генерация чанков через цикл по токенам.
- тот же сериализатор, но
- Rate limiting через
slowapi:@limiter.limit("10/minute"). - Swagger-документация (встроенная в FastAPI) с
securitySchemes. - Тестирование через 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 |
| 94 | A/B тестирование моделей |
| 95 | Multi-model сервинг (роутинг запросов) |
Эти вопросы дополняют друг друга: дизайн API → безопасность → мониторинг → кэширование → эксперименты с моделями. Вместе они формируют полную картину промышленного LLM-сервиса.
Навигация
- Предыдущий: 89
- Следующий: 91
- Индекс: 00. Индекс разборов