| .github/workflows | ||
| .opencode/plans | ||
| backend | ||
| frontend | ||
| helm/support-bot-saas | ||
| k8s | ||
| .gitignore | ||
| app.py | ||
| docker-compose.monitoring.yml | ||
| docker-compose.yml | ||
| prepare_data.py | ||
| prometheus.yml | ||
| README.md | ||
| requirements.txt | ||
| test_upload.py | ||
| tickets-202511.json | ||
| tickets-202512.json | ||
| tickets-202601.json | ||
| tickets-202602.json | ||
| tickets-202603.json | ||
| tickets.json | ||
Support Bot SaaS — RAG-ассистент техподдержки
Система поиска решений по истории обращений в техподдержку. Основана на RAG (Retrieval-Augmented Generation) — находит похожие решённые тикеты в векторной БД и формирует ответ на их основе через LLM.
Развёрнута на Kubernetes (k0s), используются OpenRouter для эмбеддингов и LLM.
Быстрый старт (локальный Docker)
# 1. Клонировать репозиторий
git clone https://git.itoservice.ru/dosnav/support-bot-saas.git
cd support-bot-saas
# 2. Создать backend/.env из шаблона
cp backend/.env.example backend/.env
# отредактировать backend/.env — указать OPENROUTER_API_KEY и JWT_SECRET
# 3. Запустить
docker compose up --build -d
После запуска веб-интерфейс доступен по адресу: http://localhost:8080
Первый вход
- Открой http://localhost:8080 в браузере
- Зарегистрируйся (Email + пароль, тариф free)
- После регистрации автоматически войдёшь в систему
- Создай коллекцию (например «OFRS»)
- Загрузи JSON-файл с тикетами через кнопку «Выбрать JSON/CSV файл»
Формат JSON:
[
{
"ticket_id": "12345",
"category": "Сбой работы",
"description": "Описание проблемы",
"client": "Имя клиента",
"messages": [
{"role": "client", "text": "У меня проблема"},
{"role": "support", "text": "Решение проблемы"}
]
}
]
- После загрузки тикетов введи вопрос в поле поиска и нажми «Найти»
Система найдёт похожие тикеты, а LLM сформирует ответ на их основе.
Остановка
docker compose down
# С данными:
docker compose down -v
Конфигурация (.env)
Все настройки задаются через файл backend/.env (создать из backend/.env.example):
cp backend/.env.example backend/.env
| Переменная | По умолчанию | Описание |
|---|---|---|
OPENROUTER_API_KEY |
— | API-ключ OpenRouter (обязательно) |
JWT_SECRET |
change-me-in-production |
Секрет для подписи JWT (обязательно сменить) |
OPENROUTER_LLM_MODEL |
openrouter/owl-alpha |
Модель LLM |
OPENROUTER_EMBEDDING_MODEL |
nvidia/llama-nemotron-embed-vl-1b-v2:free |
Модель эмбеддингов |
DATABASE_URL |
postgresql+asyncpg://postgres:postgres@localhost:5432/support_bot |
Подключение к PostgreSQL |
REDIS_URL |
redis://localhost:6379/0 |
Подключение к Redis |
QDRANT_URL |
http://localhost:6333 |
Подключение к Qdrant |
JWT_ALGORITHM |
HS256 |
Алгоритм подписи JWT |
JWT_EXPIRE_MINUTES |
1440 |
Время жизни токена (минут) |
Значения по умолчанию заданы в backend/app/config.py. Если переменная в .env не указана — используется дефолт.
Архитектура
Пользователь (браузер)
│
▼
┌─────────────┐ ┌─────────────┐
│ Traefik │────▶│ Nginx │ (frontend, React SPA)
│ (Ingress) │ └─────────────┘
│ │ ┌─────────────┐
│ │────▶│ FastAPI │ (backend)
└─────────────┘ └──────┬──────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│PostgreSQL│ │ Qdrant │ │ Redis │
│(пользова-│ │(векторная│ │(кэш/сес- │
│ тели, │ │ БД) │ │ сии) │
│коллекции)│ └──────────┘ └──────────┘
└──────────┘
│
▼
┌──────────────┐
│ OpenRouter │
│(эмбеддинги + │
│ LLM) │
└──────────────┘
Компоненты
| Компонент | Технология |
|---|---|
| Фронтенд | React + Vite + TypeScript, Nginx |
| Бэкенд | FastAPI (Python 3.12) + SQLAlchemy async |
| База данных | PostgreSQL (пользователи, коллекции) |
| Векторная БД | Qdrant (точки с эмбеддингами) |
| Кэш | Redis |
| Эмбеддинги | nvidia/llama-nemotron-embed-vl-1b-v2:free через OpenRouter |
| LLM | openrouter/owl-alpha через OpenRouter |
| Ingress | Traefik v3.7 |
| Оркестрация | Kubernetes (k0s v1.36.1) |
| CI | GitLab CI (Forgejo) |
REST API v1
Аутентификация: Authorization: Bearer <API-ключ>
API-ключ можно посмотреть и сбросить на странице Настройки в веб-интерфейсе.
Базовый URL: http://sovet.itoservice.ru/api/v1
Получить информацию об аккаунте
curl -H "Authorization: Bearer <API_KEY>" http://sovet.itoservice.ru/api/v1/me
Ответ:
{
"id": "uuid",
"email": "user@example.com",
"api_key": "d469cee785e444cf9abd24a3e709108c",
"plan": "free",
"created_at": "2026-06-20T12:00:00Z"
}
Сбросить API-ключ
curl -X PATCH -H "Authorization: Bearer <API_KEY>" http://sovet.itoservice.ru/api/v1/me/api-key
Ответ:
{
"api_key": "новый-ключ"
}
Список коллекций
curl -H "Authorization: Bearer <API_KEY>" http://sovet.itoservice.ru/api/v1/collections
Ответ:
[
{
"id": "49a3f88e-...",
"user_id": "uuid",
"name": "OFRS",
"ticket_count": 2901,
"created_at": "2026-06-20T..."
}
]
Загрузить тикеты в коллекцию
curl -X POST "http://sovet.itoservice.ru/api/v1/tickets?collection_id=<ID_КОЛЛЕКЦИИ>" \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"tickets": [
{
"ticket_id": "12345",
"category": "Сбой работы",
"description": "Описание проблемы",
"client": "Имя клиента",
"messages": [
{"role": "client", "text": "У меня проблема"},
{"role": "support", "text": "Решение проблемы"}
]
}
]
}'
Ответ:
{
"processed": 1,
"skipped": 0,
"collection_ticket_count": 2902
}
Поиск с AI-ответом
curl -X POST http://sovet.itoservice.ru/api/v1/search \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"collection_id": "<ID_КОЛЛЕКЦИИ>",
"query": "не могу начать смену в приложении",
"generate_answer": true
}'
Параметры:
collection_id— ID коллекции (обязательно)query— текст проблемы (обязательно)generate_answer— true/false, генерировать ответ LLM (опционально, по умолчанию true)
Ответ:
{
"answer": "Добрый день! Для решения проблемы...",
"sources": [
{
"ticket_id": "38446",
"score": 0.514,
"category": "Вопрос по работе мобильного приложения",
"full_text": "Категория: ...\nПроблема: ...\nРешение: ..."
}
]
}
Как это работает
1. Загрузка тикетов
Пользователь загружает JSON-файл с тикетами через веб-интерфейс или API. Бэкенд:
- Проверяет дубликаты (по
tenant_id:ticket_idчерез md5) - Очищает персональные данные: телефоны →
[ТЕЛЕФОН], email →[EMAIL], имена →[ИМЯ] - Строит чанк: выделяет сообщения поддержки (роли
support/assistant/operator/agent) как решение - Фильтрует бесполезные тикеты:
- последнее сообщение поддержки — просьба уточнить / прислать скрин
- слишком короткие (< 100 символов)
- нет описания и нет сообщений клиента
- Векторизует
search_text(описание + сообщения клиента) через OpenRouter - Сохраняет точку в Qdrant с полями:
tenant_id,collection_id,ticket_id,category,client,search_text,full_text
2. Поиск и генерация
Пользователь вводит вопрос. Система:
- Векторизует вопрос той же моделью эмбеддингов
- Ищет 20 похожих точек в Qdrant (фильтр по
tenant_id+collection_id) - Сортирует по скорингу (косинусная близость), берёт топ-5
- Формирует контекст из
full_textнайденных тикетов - Отправляет в LLM с системным промптом
- Возвращает ответ + исходные тикеты
Очистка персональных данных
Перед сохранением в Qdrant из текста удаляются:
- Номера телефонов:
+7xxxxxxxxxx,8xxxxxxxxxx→[ТЕЛЕФОН] - Email-адреса →
[EMAIL] - Русские имена в обращениях →
[ИМЯ]
Разработка
Локальный запуск бэкенда
cd backend
python -m venv venv
venv\Scripts\activate # Windows
pip install -r requirements.txt
# Настройки в .env:
# openrouter_api_key=...
# database_url=postgresql+asyncpg://postgres:postgres@localhost:5432/support_bot
# qdrant_url=http://localhost:6333
uvicorn app.main:app --reload
Локальный запуск фронтенда
cd frontend
npm install
npm run dev
Сборка Docker-образов
docker build -t git.itoservice.ru/dosnav/support-bot-saas/backend:latest ./backend
docker build -t git.itoservice.ru/dosnav/support-bot-saas/frontend:latest ./frontend
docker push git.itoservice.ru/dosnav/support-bot-saas/backend:latest
docker push git.itoservice.ru/dosnav/support-bot-saas/frontend:latest
Деплой в K8s
kubectl apply -f k8s/00-namespace.yaml
kubectl apply -f k8s/01-postgres.yaml
kubectl apply -f k8s/02-redis.yaml
kubectl apply -f k8s/03-qdrant.yaml
kubectl apply -f k8s/04-backend.yaml
kubectl apply -f k8s/05-frontend.yaml
kubectl apply -f k8s/traefik/
kubectl apply -f k8s/06-ingress.yaml
Структура проекта
support-bot-saas/
├── backend/
│ └── app/
│ ├── auth/ # JWT + API-ключ аутентификация
│ ├── models/ # SQLAlchemy модели + Pydantic схемы
│ ├── routers/ # FastAPI роутеры
│ │ ├── auth.py # регистрация, логин, коллекции
│ │ ├── tickets.py # загрузка тикетов
│ │ ├── search.py # поиск (для фронтенда)
│ │ └── api_v1.py # внешний REST API v1
│ ├── services/ # бизнес-логика
│ │ ├── chunking.py # нарезка чанков, очистка ПД
│ │ ├── embedding.py # эмбеддинги через OpenRouter
│ │ ├── vector_store.py# Qdrant: upsert, scroll, search
│ │ ├── reranking.py # сортировка результатов
│ │ └── llm.py # генерация ответа через OpenRouter
│ ├── config.py # настройки из .env
│ ├── database.py # подключение к БД
│ └── main.py # точка входа FastAPI
├── frontend/
│ └── src/
│ ├── api/client.ts # API-клиент
│ ├── pages/ # страницы React
│ │ ├── Login.tsx / Register.tsx
│ │ ├── Dashboard.tsx
│ │ ├── CollectionView.tsx
│ │ └── Settings.tsx
│ └── nginx.conf # конфиг Nginx
├── k8s/ # манифесты Kubernetes
│ ├── 00-namespace.yaml
│ ├── 01-postgres.yaml ... 06-ingress.yaml
│ └── traefik/ # Traefik CRDs + Deployment
├── docker-compose.yml # локальный запуск (Docker)
├── tickets*.json # тестовые данные
├── README.md
└── backend/.env.example # шаблон .env для бэкенда