69 lines
6.0 KiB
Markdown
69 lines
6.0 KiB
Markdown
# proxy-ws
|
|
|
|
Rust/Axum-шлюз, стоящий между фронтендом (Next.js) и PocketBase (CMS/БД). Независимый сервис — не зависит от `matrix-engine` или самого фронта, общается с ними только по HTTP.
|
|
|
|
У сервиса две обязанности:
|
|
|
|
1. **AI-модерация гостевой книги** — принимает комментарии с фронта, прогоняет никнейм и текст через ONNX-классификатор токсичности и, если всё чисто, форвардит запись в PocketBase.
|
|
2. **Backend для спидтеста** — отдаёт несжимаемый блоб на скачивание, принимает и считает байты на загрузку, отвечает на ping — без сохранения каких-либо данных.
|
|
|
|
## Эндпоинты
|
|
|
|
| Метод | Путь | Назначение |
|
|
|-------|--------------------------|------------------------------------------------------------------------------|
|
|
| POST | `/api/comments` | Приём сообщения гостевой книги, ИИ-модерация, форвардинг в PocketBase |
|
|
| GET | `/api/speedtest/ping` | Возвращает `pong` — для измерения RTT |
|
|
| GET | `/api/speedtest/download`| Отдаёт статический 25 МБ несжимаемый блоб — для измерения скорости скачивания|
|
|
| POST | `/api/speedtest/upload` | Принимает тело запроса, считает байты, ничего не сохраняет |
|
|
|
|
Лимит тела запроса — 100 МБ (`DefaultBodyLimit`), нужен именно из-за speedtest upload.
|
|
|
|
## Как работает модерация
|
|
|
|
Пайплайн для каждого проверяемого текста (`classifier.rs`):
|
|
|
|
```
|
|
текст -> токенизация (tokenizers) -> ONNX-модель (tract, чистый Rust, без Python) -> логиты -> сигмоида -> порог 0.85
|
|
```
|
|
|
|
Модель — бинарный BERT-подобный классификатор токсичности. Инференс выполняется через `tract-onnx`, поэтому сервису не нужен ни Python, ни какой-либо ML-рантайм на уровне ОС — только сам `.onnx`-файл модели и токенизатор, лежащие в `models/`.
|
|
|
|
Особенности пайплайна:
|
|
|
|
- **Никнейм проверяется всегда**, без исключений.
|
|
- **Текст сообщения проходит гвард-рейлы** перед инференсом: сообщения из whitelist безобидных слов (`vpn`, `service`, `спасибо`, `тест` и т.п.), короткие (< 4 символов) или не содержащие ни одной буквы — на модель вообще не отправляются и автоматически считаются нетоксичными. Это защищает от false positives на коротких безобидных фразах вроде "спасибо, работает" и экономит инференс.
|
|
- **Инференс — блокирующая операция**, поэтому выполняется в `tokio::task::spawn_blocking`, чтобы не задерживать поток из пула async-раннтайма.
|
|
- **Fail-open при ошибках модели**: любая ошибка на любом шаге (токенизация, прогон модели, неожиданная форма выходного тензора) означает, что текст считается нетоксичным и пропускается дальше. Это осознанный компромисс в пользу доступности сервиса — сбой модели не должен ронять или блокировать гостевую книгу целиком.
|
|
|
|
Если хотя бы один из текстов (никнейм или прошедшее гвард-рейлы сообщение) признан токсичным — сервис отвечает `406 Not Acceptable` и ничего не форвардит в PocketBase.
|
|
|
|
## Переменные окружения
|
|
|
|
| Переменная | Назначение | Значение по умолчанию |
|
|
|------------------|--------------------------------------------------------|---------------------------------|
|
|
| `PB_INTERNAL_URL`| Внутренний адрес PocketBase, куда форвардятся комментарии | `http://netrunner-cms:8090` |
|
|
|
|
## Как собрать и запустить локально
|
|
|
|
### Через Cargo
|
|
|
|
Нужны файлы модели в `models/` (`model.onnx`, `tokenizer.json` и сопутствующие конфиги — уже лежат в репозитории).
|
|
|
|
```bash
|
|
cd proxy-ws
|
|
cargo run
|
|
```
|
|
|
|
Сервис поднимется на `0.0.0.0:3001`.
|
|
|
|
### Через Docker
|
|
|
|
Dockerfile лежит внутри `proxy-ws/`, но собирать образ нужно из корня репозитория (контекст сборки — вся папка `proxy-ws`):
|
|
|
|
```bash
|
|
docker build -t proxy-ws ./proxy-ws
|
|
docker run -p 3001:3001 -e PB_INTERNAL_URL=http://netrunner-cms:8090 proxy-ws
|
|
```
|
|
|
|
Сборка двухстадийная (Alpine): сначала кэшируются зависимости отдельной компиляцией фейкового `main.rs`, затем собирается реальный код — итоговый образ содержит только бинарник и `models/`, без Rust-тулчейна.
|