Документация сильно разошлась с кодом за последние недели работы — привёл в
соответствие ARCHITECTURE.md, README, DIAGRAM.md, INGESTION.md:
- LLM (L4) и эмбеддинги (L3) теперь переключаемые бэкенды (LLM_BACKEND,
EMBED_BACKEND), а не жёстко Ollama/qwen2.5:7b — старая модель эмбеддингов
(768d mpnet) заменена на bge-m3 (1024d); L4 может идти через OpenRouter
(DeepSeek) с прокси singbox для обхода геоблокировки.
- .env на проде больше не редактируется руками — на каждом деплое
генерируется из Infisical; старая инструкция "cp .env.example .env; nano
.env" вводила в заблуждение (правки терялись бы на следующем деплое).
- README "Три docker-compose файла"/deploy разделы противоречили реальности:
фронтенд+TLS реально раздаёт хостовой nginx на CT 102, не докеризованный
nginx-сервис из compose (тот не запускается вообще).
добавлен PMC-парсер, self-match исключение объяснено, число тестов (113)
синхронизировано между файлами (было 82/122 в разных местах).
- INGESTION.md: снимок "42K доков, 0 русских" от 12.08 заменён актуальным
(165K доков, ru теперь большинство) — иначе документ откровенно врёт.
- Diagram: узлы под текущую топологию (embedding-gpu, OpenRouter, Infisical).
Заодно, раз перепроверял код на соответствие докам:
- Удалён мёртвый и битый эндпоинт GET /reports/{task_id} — фильтровал по
внутреннему Task.id вместо public_id (никогда не мог сработать через
обычный клиентский поток), фронтенд его всё равно не вызывал —
функциональность полностью дублирует рабочий GET /tasks/{public_id}.
- Убран мёртвый конфиг FAISS_NLIST/FAISS_NPROBE (worker-gpu/config.py) —
индекс всегда IndexFlatIP, IVF нигде не строится, эти поля ничего не делали.
17 KiB
Архитектура — Академический помощник
Каноничное описание системы. Обновляется вместе с кодом; при расхождении верить коду, а не этому файлу. Визуальная схема — DIAGRAM.md. Смежные документы: DR-HA.md (отказоустойчивость), INGESTION.md (наполнение корпуса), ../README.md (быстрый старт и команды).
1. Назначение
Веб-сервис для студентов: ввёл тему → система находит открытые академические источники, делает изложения, форматирует список литературы по ГОСТ и проверяет текст на плагиат. Всё асинхронно: пользователь отправляет задачу, закрывает браузер и получает результат на email (или в реальном времени через WebSocket, если вкладка открыта).
2. Общая схема
┌────────────────────────────┐
│ Frontend (React SPA) │ CT 102 → nginx
│ Home/Search/Check/Cabinet/ │
│ Bibliography/Settings/Admin │
└──────────────┬──────────────┘
│ HTTPS (/api, /ws)
┌──────────────▼──────────────┐
│ API Gateway (FastAPI) │ 1.32:8000
│ JWT · rate-limit · WebSocket │
│ /metrics (Prometheus) │
└───┬───────────────────────┬──┘
│ publish (RabbitMQ) │ read/write
│ │
┌────────────────────┼───────────────────┐ │
▼ ▼ ▼ │
queue.index queue.gpu queue.gost
┌───────────┐ ┌────────────┐ ┌───────────┐
│ worker- │ │ worker-gpu │ │ worker- │
│ indexer │ │ FAISS/Qdr. │ │ gost │
│ L1 Winnow │ │ L3 семант. │ │ ГОСТ 7.1/ │
│ L2 MinHash│ │ L4 LLM │ │ 7.0.5 │
│ PDF/DOCX │ │ эмбеддинги │ │ │
└─────┬─────┘ └──────┬─────┘ └─────┬─────┘
│ │ │
└─────────┬─────────┴───────┬──────────┘
▼ ▼
queue.notify общие данные
┌────────────┐ ┌──────────────────────────────┐
│ worker- │ │ PostgreSQL 1.38 · Redis 1.35 │
│ notifier │ │ RabbitMQ .82 · MinIO 1.21 │
│ SMTP email │ │ Elasticsearch (local 1.32) │
└────────────┘ │ Ollama .109 (bge-m3, эмбед.) │
│ OpenRouter/DeepSeek — LLM (опц.)│
│ [opt] Qdrant · Prometheus/Graf.│
└──────────────────────────────┘
3. Сервисы
| Сервис | Технологии | Ответственность |
|---|---|---|
| api | FastAPI, SQLAlchemy async (asyncpg), Redis, Celery-producer | HTTP/WS API, auth (JWT), rate-limits, диспетч задач в очереди, админ-панель |
| worker-indexer | Celery, PyMuPDF, python-docx, xxhash, datasketch | Извлечение текста (PDF/DOCX/TXT), фрагментация, L1 Winnowing, L2 MinHash LSH, парсинг источников, обогащение full-text |
| worker-gpu | Celery, FAISS/Qdrant, httpx→Ollama/OpenRouter | Эмбеддинги (EMBED_BACKEND: ollama/sentence_transformers/cloud), L3 семантический поиск, L4 LLM-анализ парафраза (LLM_BACKEND: ollama/openrouter), семантический поиск источников |
| worker-gost | Celery | Список литературы по ГОСТ 7.1-2003 / Р 7.0.5-2008 |
| worker-notifier | Celery, smtplib | Email: письма-результаты и верификация (jze9mail.ru) |
| frontend | React 18, Vite, TS, Tailwind, Zustand, React Query | SPA: 11 публичных страниц + админ-панель. Собирается в статику, отдаётся nginx |
4. Модель данных (PostgreSQL)
- users —
email,hashed_password(bcrypt),name,is_verified,is_admin,plan(free/student/premium),verification_token. - tasks —
id(UUID),public_id(внешний),user_id,type(TaskType: search/plagiarism/summarize/gost),status(TaskStatus: queued→processing→done/failed),celery_task_id,input_data(JSON),result(JSON),error,queue_position. - documents — корпус источников:
source(openalex/arxiv/cyberleninka/ user_submission),ext_id,doi,title,authors(JSON),year,lang,journal/volume/issue/pages,abstract,url,minio_key(full-text в MinIO),faiss_id. - fingerprints —
doc_id,hash_value(BIGINT, Winnowing),position— для L1. - usage_logs —
user_id,action— учёт лимитов по тарифу. - parse_sources — задания парсеров (админка): тип, query, годы, лимит, статус.
- staged_works — пользовательские загрузки на модерацию перед добавлением в корпус.
- admin_sessions — одноразовые коды входа в админку.
5. Асинхронный конвейер (Celery + RabbitMQ)
Брокер — RabbitMQ, backend результатов — Redis. Маршрутизация по префиксу задачи:
| Очередь | Задачи | Воркер |
|---|---|---|
queue.index |
index.extract_and_check, index.add_document, index.run_parser, index.enrich_full_text |
worker-indexer |
queue.gpu |
gpu.check_plagiarism, gpu.embed_documents, gpu.search_semantic |
worker-gpu |
queue.gost |
gost.format_bibliography |
worker-gost |
queue.notify |
notify.send_task_done, notify.send_verification |
worker-notifier |
Важно: API коммитит задачу в БД до send_task (иначе гонка dispatch-before-commit).
6. Ключевые потоки
Проверка плагиата. upload (api, файл→MinIO, Task) → index.extract_and_check
(извлечь текст → фрагментация → L1 Winnowing по fingerprints → L2 MinHash LSH
в Redis) → передаёт частичные совпадения в gpu.check_plagiarism (L3 FAISS/Qdrant
семантика по фрагментам → для подозрительных L4 LLM-парафраз) →
app.scoring.aggregate_results (итоговый %) → результат в Task → notify.send_task_done.
Фронтенд (HighlightedDocument.tsx) показывает исходный текст работы с подсветкой
совпадающих/процитированных фрагментов поверх обычного списка нарушений
(PlagiarismReport.tsx).
Поиск источников. api → gpu.search_semantic: эмбеддинг запроса → векторный поиск
(FAISS/Qdrant) + Elasticsearch BM25 → объединение → результат.
Список литературы. api → gost.format_bibliography: документы из БД →
app.bibliography.build_bibliography (сортировка кириллица→латиница, нумерация,
формат 7.1/7.0.5) → результат.
Наполнение корпуса. админка/CLI → index.run_parser (OpenAlex/arXiv/PMC/
КиберЛенинка, фильтр is_oa) → index.add_document (дедуп по ext_id,
fingerprints, MinHash, эмбеддинг) → index.enrich_full_text (скачать OA-PDF →
MinIO → переиндексация).
7. Детекция плагиата — 4 уровня
- L1 Winnowing (
worker-indexer/app/algorithms/winnowing.py) — точные/частичные совпадения: k-граммы → xxHash → минимум в скользящем окне → fingerprint; Jaccard. - L2 MinHash LSH (
.../minhash.py) — нечёткие совпадения: шинглы → MinHash (128 перм.) → LSH-индекс в общем Redis (префиксantiplag_lsh, upsert, graceful-фолбэк в память). - L3 семантика (
worker-gpu) — эмбеддингиbge-m3(1024d, нормированы), бэкенд переключаетсяEMBED_BACKEND(ollama— дефолт, GGUF через Vulkan на AMD GPU;sentence_transformers— локальная загрузка CUDA/CPU;cloud— облачный инференс той же модели, без локального GPU) → cosine в FAISSIndexIDMap2(IndexFlatIP)или Qdrant (VECTOR_BACKEND); порог 0.75. - L4 LLM-парафраз — оценивает пары «источник↔фрагмент» для подозрительных из
L3; порог confidence 0.7. Бэкенд переключается
LLM_BACKEND:ollama(локальная модель, нужен GPU-хост) илиopenrouter(облачный DeepSeek — геоблокировку РФ обходит SOCKS5-проксиsingbox-proxy, докер-сервис на базе sing-box/Trojan). Ни один документ сsource=user_submission(прошлые проверки пользователей) не участвует в сравнении ни на одном уровне — иначе работа матчилась бы сама с собой.
Итог: scoring.aggregate_results — доля уникальных помеченных позиций (не выше 100%).
Кандидаты, похожие семантически, но не подтверждённые LLM как парафраз, не теряются —
попадают в отдельный список «похожие по теме источники» (рекомендации, не нарушения).
8. Векторный бэкенд
Абстрагирован за worker-gpu/app/vector_store.get_backend(); VECTOR_BACKEND=faiss
(файловый синглтон, дефолт) или qdrant (сервис, снимает SPOF/конкурентную запись).
Переключение и миграция — см. README, раздел «Векторный бэкенд».
9. Инфраструктура и топология
| Компонент | Узел | Примечание |
|---|---|---|
| api + воркеры + Elasticsearch | 1.32 (app-хост) | docker-compose.prod.yml |
| Frontend (nginx, статика + TLS) | CT 102 | деплоится отдельно |
| PostgreSQL 16 | 1.38 (выделенный LXC) | UTF8; бэкап→MinIO |
| Redis 7 | 1.35 (выделенный LXC) | кэш, rate-limits, LSH |
| RabbitMQ | 192.168.20.82 | брокер Celery |
| MinIO (S3) | 1.21 | документы, full-text, бэкапы |
| Ollama (bge-m3, эмбеддинги) | 192.168.20.109 «embedding-gpu» | RX580; известный failure mode — amdgpu ring timeout вешает Vulkan-контекст, лечится systemctl restart ollama |
| OpenRouter (DeepSeek, L4 LLM) | облако | опционально вместо локальной Ollama (LLM_BACKEND=openrouter); из РФ доступен только через singbox-proxy |
| Infisical (секреты) | 192.168.20.111 «VM111» | self-hosted, infisical.jze9.ru; единственный источник правды для .env на проде |
| Qdrant / Prometheus / Grafana | 1.32 | опционально, под compose-профилями |
CT 108 (192.168.20.163, GTX1070, qwen2.5:7b) — старая LLM-нода, роль полностью заменена OpenRouter, контейнер остановлен, но не удалён.
Деплой — Gitea Actions по push в main: гейт test (ruff+mypy → 113 юнит-тестов),
затем deploy (needs: test) через scripts/deploy.sh (умная пересборка изменённых
сервисов). PG/Redis/RabbitMQ/MinIO не в compose — общая инфра берётся из .env,
который на каждом деплое генерируется заново из Infisical (см. §10).
10. Конфигурация и секреты
Приложение читает .env (пример структуры — .env.example), но на проде этот файл
не редактируется руками и не персистентен как источник правды — на каждом
деплое scripts/deploy.sh логинится в self-hosted Infisical
(https://infisical.jze9.ru, проект academ, окружение prod) через Machine
Identity (Universal Auth) и генерирует .env заново (infisical export) до синка
кода. Правки секретов/конфига — через Infisical, а не через .env на сервере
(следующий деплой затрёт локальные правки). Если Infisical недоступен или вернул
подозрительно мало ключей, деплой прерывается раньше, не трогая рабочий .env.
Обязательно менять (значения уже в Infisical, не дефолты из .env.example):
SECRET_KEY, POSTGRES_PASSWORD, MINIO_SECRET_KEY. Опции: VECTOR_BACKEND,
QDRANT_URL, EMBED_BACKEND, LLM_BACKEND, GRAFANA_ADMIN_PASSWORD.
Локальная разработка (docker-compose.test.yml) продолжает использовать обычный
.env, отдельный от прод-потока через Infisical.
11. Качество и тесты
- 113 юнит-тестов (pytest, per-service) на чистую логику L1-L4/скоринг/фрагменты/
ГОСТ/библиография; инфра замокана или не нужна. Запуск:
make test. - Гейты CI: ruff (весь Python) + mypy (доменная логика) + тесты — блокируют деплой.
make lint. Хермет-раннеры вpython:3.11-slim. - Чистая доменная логика вынесена из Celery-задач в тестируемые модули
(
scoring.py,fragments.py,bibliography.py). Подробнее — README «Тестирование».
12. Наблюдаемость и эксплуатация
- Мониторинг:
antiplag_monitor.py(cron 5 мин, 7 сервисов, email-алерт при смене статуса). Опционально — Prometheus+Grafana (профильobservability, метрики API + Flower). - Бэкапы:
pg_dump→gzip→MinIO, cron 03:00, ротация 14. Проверка восстановления —scripts/ops/pg_restore_verify.sh. HA/DR — DR-HA.md.
13. Безопасность
JWT-аутентификация, bcrypt-хэши паролей, email-верификация, отдельный вход в админку
(одноразовые коды, admin_sessions). Rate-limits по тарифу — в Redis. CORS — явные
origins. Пользователь видит только свои задачи (ownership проверяется, в т.ч. на WebSocket).
14. Раскладка репозитория
services/ api, worker-{gpu,indexer,notifier,gost}, frontend
scripts/ parsers/ (OpenAlex/arXiv/PMC/КиберЛенинка), seed_*.py, ops/,
deploy.sh, run_tests.sh, run_lint.sh
infra/ nginx/, prometheus/, grafana/, singbox/ (VPN-прокси для OpenRouter)
docs/ ARCHITECTURE.md (этот файл), DR-HA.md
.gitea/workflows/ deploy.yml (гейт test → deploy)
ruff.toml · mypy.ini · Makefile · docker-compose.prod.yml