480 auto

Корпоративный AI‑чат. Часть II: бэкенд на VPS

В первой части цикла мы рассмотрели верхнеуровневую логику работы AI‑чата и рассказали о его возможностях и ресурсах, которые нужны для его запуска и явном профите, который он приносит. В этой части — мы погрузимся в технические детали реализации бэкенда AI‑чата на VPS.

Будет много кода, YAML‑синтаксиса и логики работы Docker‑контейнеров. Напомним, что код проекта можно скачать из этого репозитория, а VPS, на котором можно попробовать проект, можно заказать тут — в разделе VPS/VDS. GPU‑инстансы заказываются у любого GPU‑провайдера. Мы использовали Runpod GPU‑инстансы.

Сердце AI‑чата — GPU‑инстансы, на которых происходят все расчеты, а мозг AI‑чата — VPS, на котором запущены все сервисы. Такой подход дает важное преимущество — все данные остаются в корпоративном контуре. На GPU‑инстансах данные удаляются каждые 12 часов из‑за перезапуска контейнеров, поэтому об утечке NDA‑информации можно не беспокоиться.

Сервисы, запущенные на VPS:

  • Open WebUI — open‑source Web UI, принимающий запросы от пользователей через поддомен chat.domain.online
  • LiteLLM — proxy‑сервер к среде запуска vLLM, который передает запросы в GPU‑инстансы на стороне GPU‑провайдера
  • LiteLLM — proxy‑сервер к среде запуска vLLM, который передает запросы в GPU‑инстансы на стороне GPU‑провайдера
  • Redis — in‑memory СУБД, в которой мы храним кеш LiteLLM для ускорения ответов и экономии токенов
  • PostgreSQL — СУБД для хранения SQL‑данных, в которой мы храним учетные записи пользователей Open WebUI
  • Grafana + Prometheus — связка двух сервисов для сбора метрик и построения отчетов

Графически стек, запущенный на VPS, можно представить так:

Разберем детально каждый сервис, опишем его свойства и некоторые тонкости настройки.

Open WebUI

Open WebUI — это open‑source Web UI, который работает по API со средами запуска LLM (Ollama, vLLM). Open WebUI управляет пользователями, чатами и выбором LLM‑модели. Контейнер поднят на порту 8080. Код сервиса находится тут.

Из коробки Open WebUI настроен для максимально простого и бесконтрольного доступа к средам запуска LLM. В нашем стеке выставлены следующие параметры безопасности и контроля доступа:
  • OPENAI_API_KEY — ключ доступа к OpenAI‑совместимому API. Пример: OPENAI_API_KEY=sk-1234. Для нескольких ключей используется OPENAI_API_KEYS (значения ключей перечисляются через ;). Пример: OPENAI_API_KEYS=sk-1234;sk-456.
  • ENABLE_SIGNUP — разрешает регистрацию пользователей. Значения: true/false. Выставлено: true. Стратегия работы с опцией следующая: при первом старте сервиса ENABLE_SIGNUP выставляется в true и через Open WebUI регистрируется администратор, затем опция выставляется в false и контейнер перезапускается. Дальше пользователи добавляются администратором в БД, а возможность регистрации пользователей в Open WebUI закрыта.
  • WEBUI_SECRET_KEY — секрет для JWT и шифрования. Опция нужна для аутентификации сессий и сохранения истории чатов. Задается вручную. Пример: WEBUI_SECRET_KEY=jwt17b56.
  • DEFAULT_USER_ROLE — роль по умолчанию для новых пользователей (pending, user, admin). Первому регистрирующемуся автоматически назначается роль admin, а остальным user, если регистрация открыта. Пример: DEFAULT_USER_ROLE=user.
  • WEBUI_AUTH — включает аутентификацию по почте и паролю. По умолчанию true. Значение выставлено в true. Пример: WEBUI_AUTH=true.

Для большей гибкости управления учетными записями пользователей они вынесены в отдельную БД PostgreSQL, а в Open WebUI заданы следующие опции работы с внешней БД:

  • DATABASE_URL — полный URL подключения к базе данных, где хранятся учетные записи пользователей и история их чатов. Используется как SQLite, так и PostgreSQL. Пример для PostgreSQL: DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres-users:5432/${POSTGRES_DB}.

Запросы пользователей Open WebUI транслирует по OpenAI совместимому API для обработки в liteLLM — прокси‑сервер к vLLM, который запущен в поде на стороне GPU‑провайдера. В Open WebUI выставлены следующие настройки API:

  • OPENAI_API_BASE_URL — базовый URL OpenAI совместимого API. Для liteLLM‑прокси указывается адрес вида http://:/v1. Пример: OPENAI_API_BASE_URL=http://litellm-1:4000/v1. Поддерживается список из множества URLs через OPENAI_API_BASE_URLS (URLы разделяются ;). Пример: OPENAI_API_BASE_URLS=http://litellm-1:4000/v1;http://litellm-2:4000/v1.Это удобно для балансировки, если есть высоконагруженный Open WebUI и нужно несколько OpenAI совместимых API.
  • OPENAI_API_KEY — ключ доступа к OpenAI совместимому API. Пример: OPENAI_API_KEY=sk-1234. Для нескольких ключей используется OPENAI_API_KEYS (значения ключей перечисляются через ;). Пример: OPENAI_API_KEYS=sk-1234;sk-456

liteLLM

liteLLM — это proxy‑сервер к средам запуска LLM (vLLM, Ollama, LM Studio, Xinference, Llamafile и другие). В нашем стеке liteLLM проксирует запросы в vLLM, который запущен на подах.

Контейнер с LiteLLM можно сконфигурировать через переменные окружения и с помощью конфигурационного файла в YAML‑формате. В нашем случае сервис сконфигурирован и через конфигурационный файл, и через переменные окружения. Мы используем два GPU‑инстанса, к которым подключаемся по одному API‑ключу.

В конфигурации такой подход выглядит так:

  • VLLM_API_BASE_1 — URL первого GPU‑инстанса: ${VLLM_API_BASE_1}(https://9o1qrfpuwamaiv-8000.proxy.runpod.net/v1)
  • VLLM_API_BASE_2 — URL второго GPU‑инстанса: ${VLLM_API_BASE_2}(https://33pi69ozbz1sw5-8000.proxy.runpod.net/v1)
  • VLLM_API_KEY — API‑ключ для подключения к vLLM: ${VLVLLM_API_KEY(some_key))

Переменные окружения пробрасываются внутрь контейнера liteLLM и используются в конфигурационном файле следующим образом:

model_list:
  - model_name: os.environ/MODEL_NAME
   litellm_params:
    model: os.environ/MODEL_PATH
    api_base: os.environ/VLLM_API_BASE_1
    api_key: os.environ/VLLM_API_KEY
    num_retries: 3
    timeout: 300
    weight: 1
   model_info:
    id: vllm-pod1
  - model_name: os.environ/MODEL_NAME
   litellm_params:
    model: os.environ/MODEL_PATH
    api_base: os.environ/VLLM_API_BASE_2
    api_key: os.environ/VLLM_API_KEY
    num_retries: 3
    timeout: 300
    weight: 2
   model_info:
    id: vllm-pod2

Опции num_retries и timeout не проброшены переменными из‑за того, что они числовые, а переменные окружения приходят внутрь контейнера в формате строк. Главное, на что нужно обратить внимание — это то, что model_name одинаковый в секции model_list. Именно благодаря этому работает балансировка между GPU‑инстансами.

Также важный раздел в конфигурационном файле — это router_settings, он задает поведение liteLLM при работе с упавшими GPU‑инстансами:

router_settings:
  routing_strategy: simple-shuffle
  num_retries: 3
  timeout: 300
  allowed_fails: 3 # Больше попыток перед отключением
  cooldown_time: 60 # Реже проверяем недоступные поды
  enable_pre_call_checks: true
  retry_after: 10 # Больше времени между retry

При приведенной конфигурации liteLLM будет повторять каждый запрос к GPU‑инстансу до 3 раз при ошибке (num_retries: 3), причем повторы происходят мгновенно, без задержки. После того как 3 запроса подряд провалятся (каждый уже с учетом retry), GPU‑инстанс будет помечен как недоступный (allowed_fails: 3) и исключен из ротации на 60 секунд (cooldown_time: 60), в течение которых все запросы будут направляться только на другие доступные инстансы. По истечении 60 секунд liteLLM снова начнет пробовать обращаться к упавшему GPU‑инстансу.

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

  1. Балансировка при возникновении сетевых проблем с GPU‑инстансами
  2. Выключение/подключение GPU‑инстанса в зависимости от нагрузки

Еще liteLLM умеет кешировать ответы vLLM. Для большего удобства управления ресурсами мы вынесли кеш liteLLM в отдельный Redis‑сервис — redis-cache, про который поговорим отдельно, а пока приведем блок настроек liteLLM для кеширования:

litellm_settings:
  cache: True
  cache_params:
    type: redis
    host: os.environ/REDIS_HOST
    port: os.environ/REDIS_PORT
    ttl: os.environ/REDIS_CACHE_TTL
    namespace: os.environ/REDIS_CACHE_NAMESPACE

Тут важно отметить, что ttl задает время хранения кеша в секундах. Мы выставили значение 2592000, что соответствует 30 дням, после этого кеш будет сброшен. TTL подбирается индивидуально под ресурсы VPS, на котором он запущен.

Redis

Redis — in‑memory СУБД для хранения данных в памяти. Данные из Redis извлекаются и записываются очень быстро, поэтому Redis часто используют как кеш‑хранилище. В нашем стеке он хранит кеш ответов vLLM: liteLLM при получении запроса сначала обращается к Redis за контекстом ответа, а затем — к vLLM.

Благодаря использованию кеша сильно сокращается время ответа и нагрузка на LLM, так как тратится меньше токенов. Время хранения кеша задается в конфигурационном файле LiteLLM, а объемы выделенных ресурсов для хранения кеша — в compose‑файле Redis с помощью переменной окружения ${REDIS_MAX_MEMORY}:

redis-cache:
  ...
  command: redis-server --maxmemory ${REDIS_MAX_MEMORY}
--maxmemory-policy ${REDIS_EVICTION_POLICY}
  ...

Подход разделения времени хранения данных (ttl) и выделения нужного объема памяти под хранение ${REDIS_MAX_MEMORY} позволяет более гибко управлять сервисом и выносить его, например, на отдельную ВМ.

Caddy

Caddy — HTTP/HTTPS‑сервер и обратный прокси‑сервер с TLS‑шифрованием. У Caddy несколько сильных сторон:

  • Встроенный клиент Let’s Encrypt, который сам выдает TLS‑сертификаты и обновляет их
  • Синтаксис конфигурационного файла Caddy сильно проще и удобнее, чем у NGINX

В нашем стеке Caddy используется как reverse‑proxy‑сервер с автоматическим SSL/TLS и обрабатывает запросы на поддомене chat.some_domain.com, на котором проксирует запросы к Open WebUI (корпоративный чат) на порт 8080 с настройками для долгих LLM‑запросов (таймауты до 5 минут).

Конечно, вместо Caddy можно использовать NGINX, но нам больше нравится Caddy из-за его простоты и функциональности из коробки.

PostgreSQL

PostgreSQL — СУБД для хранения SQL‑данных. В нашем стеке в PostgreSQL мы храним учетные записи пользователей Open WebUI и их чаты. Это удобно для администрирования: администратор добавляет пользователя в БД и выдает доступы лично — так все под контролем.

Сервис настраивается очень просто:

services:
  postgres-users:
   image: postgres:16-alpine
   container_name: postgres-users
   restart: unless-stopped
   environment:
    - POSTGRES_DB=${POSTGRES_DB}
    - POSTGRES_USER=${POSTGRES_USER}
    - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
   volumes:
    - postgres_users_data:/var/lib/postgresql/data
   networks:
    - internal
   healthcheck:
    test: ["CMD-SHELL", "pg_isready -U openwebui"]
    interval: 10s
    timeout: 5s
    retries: 5

Open WebUI для работы с внешними базами данных использует Alembic и SQLAlchemy. Поэтому ничего настраивать, кроме POSTGRES_DB, POSTGRES_USER и POSTGRES_PASSWORD, не нужно.

Monitoring

Grafana + Prometheus — классическая система сбора метрик и графическое представление метрик в виде графиков. В нашем стеке мы собираем следующие метрики:

  1. vLLM (GPU‑инстансы):
    1. Time To First Token (TTFT) — время до первого токена (средние значения, p50, p90, p99)
    2. End‑to‑End (E2E) Request Latency — полная задержка запроса от начала до конца (средние значения, перцентили)
    3. Количество запущенных и ожидающих запросов (num_requests_running, num_requests_waiting)
    4. Утилизация GPU cache (gpu_cache_usage_perc)
    5. Количество обработанных промпт‑токенов и сгенерированных токенов
    6. Успешные/неуспешные запросы
  2. liteLLM (прокси‑сервер):
    1. Общее количество запросов (litellm_requests_total)
    2. Длительность обработки запросов (litellm_request_duration_seconds)
    3. Задержка API‑вызовов к LLM (litellm_llm_api_latency_seconds)
    4. Cache hit/miss rate — эффективность кеширования
    5. Метрики по deployment (pod1/pod2)
  3. Redis (кеш LLM‑ответов):
    1. Использование памяти (used/max bytes, процент загрузки)
    2. Количество ключей в базе (всего, с TTL, без TTL)
    3. Cache hits/misses rate — попадания/промахи кеша
    4. Скорость выполнения команд по типам (GET, SET, DEL и т. д.)
    5. Среднее время выполнения команд
    6. Network I/O (входящий/исходящий трафик)
    7. Количество подключенных клиентов
    8. Evicted/expired keys — вытесненные и истекшие ключи
  4. Системные метрики (через cAdvisor):
    1. Утилизация CPU и памяти контейнеров
    2. Network I/O контейнеров
    3. Disk I/O
  5. Caddy (reverse‑proxy):
    1. HTTP‑метрики запросов и ответов
    2. Статусы сертификатов TLS
  6. Open WebUI:
    1. Health check статус приложения

Мы измеряем все необходимые показатели для четкого понимания загруженности сервисов и ресурсов. Если один из сервисов начнет “захлебываться” из‑за большой нагрузки — это будет сразу видно. “Расшить” узкое место инфраструктуры можно будет масштабированием.

Потенциал к масштабируемости

Сейчас весь стек крутится на одном VPS малой мощности: 8 CPU‑ядер, 16 GB RAM, 200 GB SSD. Такой конфигурации с лихвой хватает для обслуживания 20‑30 одновременных запросов к LLM. Для обслуживания 40‑50 одновременных запросов потребуется увеличить количество ядер на +2‑4 и оперативной памяти на 8+ GB.

Есть две стратегии масштабирования сервисов:

  1. Увеличивать количество процессов в контейнерах Open WebUI и LiteLLM
  2. Создавать кластеры из однопроцессных инстансов Open WebUI и LiteLLM

Выбор стратегии индивидуален и зависит от ресурсов. Конечно же, наилучшим вариантом будет K8s‑кластер для всего стека, но на старте сгодится и одна VPS средней мощности.

Кратко о бэкенде AI‑чата на VPS

На VPS запущены все сервисы AI‑чата за исключением vLLM: он запущен в поде на стороне GPU‑провайдера. Такая архитектура бэкенда позволяет держать все под контролем — запросы пользователей, персональные данные и вся статистика хранятся на VPS, а поды только обрабатывают запросы с периодической очисткой данных.

Бэкенд AI‑чата на VPS — это связка из трех основных сервисов:

  • Caddy (HTTP/HTTPS‑сервер, Reverse proxy) — обрабатывает все входящие HTTP/HTTPS‑запросы на домен и поддомены
  • Open WebUI (open‑source Web UI) — принимает запросы от пользователей и транслирует их в liteLLM
  • liteLLM (proxy‑сервер к средам запуска LLM) — передает запросы в GPU‑инстансы на стороне GPU‑провайдера

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

  • Redis — in‑memory СУБД, в которой мы храним кеш LiteLLM для ускорения ответов и экономии токенов
  • PostgreSQL — СУБД для хранения SQL‑данных, в которой мы храним учетные записи пользователей Open WebUI
  • Grafana + Prometheus — связка двух сервисов для сбора метрик и построения отчетов

Сервисы на VPS под нагрузкой потребляют незначительное количество ресурсов. Для одновременной работы 100+ пользователей будет достаточно VPS с 4 CPU‑ядрами, 8 GB RAM и 150 GB SSD. Схема разделенных ресурсов между VPS и GPU‑инстансами дает три важных преимущества:

  1. Независимое масштабирование сервисов на VPS и GPU‑инстансов
  2. Частичная или полная замена GPU‑провайдера без остановки сервисов и потери данных
  3. Сохранность NDA‑информации и тонкий контроль ресурсов и сервисов в корпоративном контуре

Подчеркнем еще раз мысль о том, что мы используем удаленные GPU‑инстансы лишь для запуска LLM, а весь стек с данными у нас запущен внутри корпоративного контура на VPS.

Корпоративный AI‑чат. Часть II: бэкенд на VPS Блог 2026-05-18 ru
182 70
1cloud LLP +7-708-9122620 050000, Алматы, ул. Макатаева, 117
Корпоративный AI‑чат. Часть II: бэкенд на VPS

Наши услуги