# Support Bot SaaS — RAG-ассистент техподдержки

Система поиска решений по истории обращений в техподдержку. Основана на RAG (Retrieval-Augmented Generation) — находит похожие решённые тикеты в векторной БД и формирует ответ на их основе через LLM.

Развёрнута на Kubernetes (k0s), используются OpenRouter для эмбеддингов и LLM.

---

## Архитектура

```
Пользователь (браузер)
      │
      ▼
┌─────────────┐     ┌─────────────┐
│   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
├── tickets*.json              # тестовые данные
└── README.md
```
