6.0 KiB
proxy-ws
Rust/Axum-шлюз, стоящий между фронтендом (Next.js) и PocketBase (CMS/БД). Независимый сервис — не зависит от matrix-engine или самого фронта, общается с ними только по HTTP.
У сервиса две обязанности:
- AI-модерация гостевой книги — принимает комментарии с фронта, прогоняет никнейм и текст через ONNX-классификатор токсичности и, если всё чисто, форвардит запись в PocketBase.
- 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 и сопутствующие конфиги — уже лежат в репозитории).
cd proxy-ws
cargo run
Сервис поднимется на 0.0.0.0:3001.
Через Docker
Dockerfile лежит внутри proxy-ws/, но собирать образ нужно из корня репозитория (контекст сборки — вся папка proxy-ws):
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-тулчейна.