Как встроить LLM API в свой продукт
Практика4 минРедакция Innovialabs
Коротко
Вызывайте LLM только с сервера, стримьте ответ пользователю, обрабатывайте лимиты через повторы с backoff, держите запасного провайдера и логируйте каждый запрос с затратами токенов.
Короткий ответ
Интеграция LLM API — это не один fetch к провайдеру, а небольшой слой на вашем сервере. Он хранит ключи, ограничивает нагрузку, повторяет неудачные запросы, переключается на запасную модель и пишет логи. Пользовательский интерфейс общается только с вашим бэкендом, а не с провайдером напрямую.
Минимальная надёжная схема:
- Клиент отправляет запрос на ваш API.
- Бэкенд проверяет пользователя и его лимиты, собирает промпт.
- LLM-шлюз (ваш модуль) вызывает провайдера с таймаутом, повторами и фолбэком.
- Ответ стримится обратно клиенту, а метаданные пишутся в лог.
Ключи API: только на сервере
- Никогда не кладите ключ во фронтенд или мобильное приложение — его вытащат из бандла за минуты.
- Храните ключи в переменных окружения или менеджере секретов, а не в репозитории.
- Заведите отдельные ключи для dev, staging и production, чтобы утечка тестового ключа не трогала боевой.
- Установите лимиты расходов в кабинете провайдера и настройте оповещения.
- Регулярно ротируйте ключи и сразу отзывайте скомпрометированные.
Стриминг ответов
Модели генерируют текст постепенно, и полный ответ может занять заметное время. Стриминг показывает текст по мере генерации, и интерфейс ощущается быстрым.
- Большинство провайдеров отдают поток через Server-Sent Events (SSE).
- Ваш бэкенд читает поток от провайдера и проксирует его клиенту, не дожидаясь конца.
- Учитывайте обрыв соединения: если пользователь закрыл вкладку, прерывайте запрос к провайдеру, чтобы не платить за ненужные токены.
- Для фоновых задач (суммаризация, классификация) стриминг не нужен — проще получить ответ целиком.
Лимиты и повторные запросы
У провайдеров есть ограничения на число запросов и токенов в минуту. При превышении приходит ответ 429, при перегрузке — ошибки 5xx.
Правила повторов:
- Повторяйте только временные ошибки: 429, 5xx, таймауты сети. Ошибки 400 и 401 повторять бессмысленно.
- Используйте экспоненциальный backoff с джиттером: пауза растёт с каждой попыткой и немного случайна.
- Уважайте заголовок
Retry-After, если провайдер его прислал. - Ограничьте число попыток и общий таймаут.
async function withRetry<T>(fn: () => Promise<T>, attempts = 4): Promise<T> {
for (let i = 0; ; i++) {
try {
return await fn();
} catch (err: any) {
const retryable = err.status === 429 || err.status >= 500 || err.code === "ETIMEDOUT";
if (!retryable || i >= attempts - 1) throw err;
const delay = Math.min(1000 * 2 ** i, 15000) * (0.5 + Math.random() / 2);
await new Promise((r) => setTimeout(r, delay));
}
}
}
Кроме того, ставьте собственные лимиты на пользователя и на тариф — иначе один активный клиент съест общий бюджет.
Фолбэк между провайдерами
Сбой одного провайдера не должен ронять ваш продукт.
- Опишите единый интерфейс вызова модели внутри кода, а адаптеры под каждого провайдера держите отдельно.
- Задайте цепочку: основная модель → запасная модель того же провайдера → модель другого провайдера.
- Помните, что промпты ведут себя по-разному на разных моделях. Тестируйте запасной вариант на тех же сценариях.
- Для критичных функций держите простой резервный ответ без LLM: шаблон, поиск по базе, сообщение «попробуйте позже».
Логирование и наблюдаемость
Без логов вы не поймёте, почему ответ плохой или откуда счёт.
Что записывать по каждому вызову:
| Поле | Зачем |
|---|---|
| ID запроса и пользователя | Разбор жалоб |
| Модель и версия промпта | Сравнение качества |
| Входные и выходные токены | Контроль затрат |
| Задержка до первого токена и общая | Скорость |
| Статус, число повторов, фолбэк | Надёжность |
Тексты промптов и ответов могут содержать персональные данные. Маскируйте их или храните ограниченно, с доступом только у нужных людей.
Частые ошибки
- Вызов провайдера прямо из браузера с ключом в коде.
- Повторы без задержки, которые усиливают перегрузку.
- Отсутствие таймаута: запрос висит, пользователь ждёт.
- Промпт зашит в код без версий — невозможно понять, что изменилось.
- Нет лимитов на пользователя и расходы.