358 lines
13 KiB
Markdown
358 lines
13 KiB
Markdown
# Support Bot SaaS — RAG-ассистент техподдержки
|
||
|
||
Система поиска решений по истории обращений в техподдержку. Основана на RAG (Retrieval-Augmented Generation) — находит похожие решённые тикеты в векторной БД и формирует ответ на их основе через LLM.
|
||
|
||
Развёрнута на Kubernetes (k0s), используются OpenRouter для эмбеддингов и LLM.
|
||
|
||
---
|
||
|
||
## Быстрый старт (локальный Docker)
|
||
|
||
```bash
|
||
# 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**
|
||
|
||
### Первый вход
|
||
|
||
1. Открой **http://localhost:8080** в браузере
|
||
2. Зарегистрируйся (Email + пароль, тариф free)
|
||
3. После регистрации автоматически войдёшь в систему
|
||
4. Создай коллекцию (например «OFRS»)
|
||
5. Загрузи JSON-файл с тикетами через кнопку «Выбрать JSON/CSV файл»
|
||
|
||
Формат JSON:
|
||
```json
|
||
[
|
||
{
|
||
"ticket_id": "12345",
|
||
"category": "Сбой работы",
|
||
"description": "Описание проблемы",
|
||
"client": "Имя клиента",
|
||
"messages": [
|
||
{"role": "client", "text": "У меня проблема"},
|
||
{"role": "support", "text": "Решение проблемы"}
|
||
]
|
||
}
|
||
]
|
||
```
|
||
|
||
6. После загрузки тикетов введи вопрос в поле поиска и нажми «Найти»
|
||
|
||
Система найдёт похожие тикеты, а LLM сформирует ответ на их основе.
|
||
|
||
### Остановка
|
||
|
||
```bash
|
||
docker compose down
|
||
# С данными:
|
||
docker compose down -v
|
||
```
|
||
|
||
---
|
||
|
||
## Архитектура
|
||
|
||
```
|
||
Пользователь (браузер)
|
||
│
|
||
▼
|
||
┌─────────────┐ ┌─────────────┐
|
||
│ 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`
|
||
|
||
### Получить информацию об аккаунте
|
||
|
||
```bash
|
||
curl -H "Authorization: Bearer <API_KEY>" http://sovet.itoservice.ru/api/v1/me
|
||
```
|
||
|
||
Ответ:
|
||
```json
|
||
{
|
||
"id": "uuid",
|
||
"email": "user@example.com",
|
||
"api_key": "d469cee785e444cf9abd24a3e709108c",
|
||
"plan": "free",
|
||
"created_at": "2026-06-20T12:00:00Z"
|
||
}
|
||
```
|
||
|
||
### Сбросить API-ключ
|
||
|
||
```bash
|
||
curl -X PATCH -H "Authorization: Bearer <API_KEY>" http://sovet.itoservice.ru/api/v1/me/api-key
|
||
```
|
||
|
||
Ответ:
|
||
```json
|
||
{
|
||
"api_key": "новый-ключ"
|
||
}
|
||
```
|
||
|
||
### Список коллекций
|
||
|
||
```bash
|
||
curl -H "Authorization: Bearer <API_KEY>" http://sovet.itoservice.ru/api/v1/collections
|
||
```
|
||
|
||
Ответ:
|
||
```json
|
||
[
|
||
{
|
||
"id": "49a3f88e-...",
|
||
"user_id": "uuid",
|
||
"name": "OFRS",
|
||
"ticket_count": 2901,
|
||
"created_at": "2026-06-20T..."
|
||
}
|
||
]
|
||
```
|
||
|
||
### Загрузить тикеты в коллекцию
|
||
|
||
```bash
|
||
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": "Решение проблемы"}
|
||
]
|
||
}
|
||
]
|
||
}'
|
||
```
|
||
|
||
Ответ:
|
||
```json
|
||
{
|
||
"processed": 1,
|
||
"skipped": 0,
|
||
"collection_ticket_count": 2902
|
||
}
|
||
```
|
||
|
||
### Поиск с AI-ответом
|
||
|
||
```bash
|
||
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)
|
||
|
||
Ответ:
|
||
```json
|
||
{
|
||
"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. Поиск и генерация
|
||
|
||
Пользователь вводит вопрос. Система:
|
||
1. Векторизует вопрос той же моделью эмбеддингов
|
||
2. Ищет 20 похожих точек в Qdrant (фильтр по `tenant_id` + `collection_id`)
|
||
3. Сортирует по скорингу (косинусная близость), берёт топ-5
|
||
4. Формирует контекст из `full_text` найденных тикетов
|
||
5. Отправляет в LLM с системным промптом
|
||
6. Возвращает ответ + исходные тикеты
|
||
|
||
### Очистка персональных данных
|
||
|
||
Перед сохранением в Qdrant из текста удаляются:
|
||
- Номера телефонов: `+7xxxxxxxxxx`, `8xxxxxxxxxx` → `[ТЕЛЕФОН]`
|
||
- Email-адреса → `[EMAIL]`
|
||
- Русские имена в обращениях → `[ИМЯ]`
|
||
|
||
---
|
||
|
||
## Разработка
|
||
|
||
### Локальный запуск бэкенда
|
||
|
||
```bash
|
||
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
|
||
```
|
||
|
||
### Локальный запуск фронтенда
|
||
|
||
```bash
|
||
cd frontend
|
||
npm install
|
||
npm run dev
|
||
```
|
||
|
||
### Сборка Docker-образов
|
||
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
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 для бэкенда
|
||
```
|