support-bot-saas/README.md
ed0ss 11d018d4f6
Some checks are pending
CI / lint-backend (push) Waiting to run
CI / lint-frontend (push) Waiting to run
CI / build-backend (push) Blocked by required conditions
CI / build-frontend (push) Blocked by required conditions
feat: docker-compose для локального запуска, .env.example, инструкция в README
2026-06-25 12:24:57 +03:00

358 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 для бэкенда
```