RAG-система по нормам теплоэнергетики https://smgx.ru
  • JavaScript 46.4%
  • Python 29.3%
  • CSS 12.2%
  • PHP 5.9%
  • HTML 5.8%
  • Other 0.4%
Find a file
2026-11-02 23:55:26 +07:00
app Update RAG-MD dialogue, indexing and interface; protect private data 2026-11-02 20:03:17 +07:00
frontend Update RAG-MD dialogue, indexing and interface; protect private data 2026-11-02 20:03:17 +07:00
nginx Initial import of RAG-MD 2026-10-24 23:00:57 +07:00
tests Update RAG-MD dialogue, indexing and interface; protect private data 2026-11-02 20:03:17 +07:00
.dockerignore Update RAG-MD dialogue, indexing and interface; protect private data 2026-11-02 20:03:17 +07:00
.env.example Update RAG-MD dialogue, indexing and interface; protect private data 2026-11-02 20:03:17 +07:00
.gitignore Update RAG-MD dialogue, indexing and interface; protect private data 2026-11-02 20:03:17 +07:00
AGENTS.md Update RAG-MD dialogue, indexing and interface; protect private data 2026-11-02 20:03:17 +07:00
default.md Update default.md 2026-10-21 23:33:03 +07:00
docker-compose.yml Configure Ollama context length 2026-11-02 23:55:26 +07:00
Dockerfile Update RAG-MD dialogue, indexing and interface; protect private data 2026-11-02 20:03:17 +07:00
README.md Update RAG-MD dialogue, indexing and interface; protect private data 2026-11-02 20:03:17 +07:00
requirements.txt Initial import of RAG-MD 2026-10-24 23:00:57 +07:00

RAG-MD

Локальный поиск по нормативной и технической документации с генерацией ответов и ссылками на найденные фрагменты.

Приложение загружает PDF, DjVu, TXT и Markdown, извлекает текст, при необходимости использует OCR, сохраняет эмбеддинги в Qdrant и формирует ответы через Ollama.

Состав проекта

  • app/ — FastAPI, обработка документов, OCR, индексация и генерация ответов.
  • frontend/ — интерфейс на PHP, jQuery и CSS.
  • nginx/ — маршрутизация запросов и проверка авторизации через Tinyauth.
  • docker-compose.yml — Nginx, PHP-FPM, Tinyauth, RAG, Qdrant и Ollama.
  • Dockerfile, requirements.txt — окружение Python для сервиса RAG.
  • .env.example — шаблон настроек без паролей и реальных учётных данных.

Подготовка и запуск

Нужны Docker с поддержкой Compose и Linux-контейнеров, свободное место для моделей и доступ к сети для первой загрузки образов, зависимостей и моделей.

  1. Скопируйте .env.example в .env. В PowerShell:

    Copy-Item .env.example .env
    

    В Linux:

    cp .env.example .env
    
  2. Заполните TINYAUTH_USERS пользователями и bcrypt-хешами в формате Tinyauth v5. Оставляйте значение в одинарных кавычках, чтобы символы $ в хешах не интерпретировались как подстановка переменных. Пустое значение не позволит Compose запустить сервис авторизации.

  3. В nginx/default.conf настроены имена rag.example.test и auth.example.test. Для локального запуска добавьте их в файл hosts на компьютере, где открыт браузер:

    127.0.0.1 rag.example.test auth.example.test
    

    Если контейнеры работают на другом компьютере, используйте его IP. Для других доменов измените оба server_name в Nginx и TINYAUTH_APP_URL в .env. В текущем Compose внешний HTTP-порт — 8000, поэтому локальный адрес авторизации — http://auth.example.test:8000. Запросы к неизвестному имени хоста Nginx отклоняет.

  4. Создайте каталоги данных, если они ещё не существуют:

    New-Item -ItemType Directory -Force -Path docs, processed, qdrant_storage
    

    В Linux:

    mkdir -p docs processed qdrant_storage
    
  5. Проверьте конфигурацию и запустите контейнеры:

    docker compose config --quiet
    docker compose up -d --build
    
  6. Загрузите модель Ollama. Команда ниже соответствует LLM_MODEL из шаблона; при смене модели укажите её имя:

    docker compose exec ollama ollama pull qwen3.5:9b-q4_K_M
    
  7. Откройте http://rag.example.test:8000, выполните вход, загрузите документы через интерфейс. Документы, скопированные вручную в docs/, нужно проиндексировать через интерфейс. Первый запуск RAG включает загрузку модели эмбеддингов и может занять время.

Для просмотра журналов:

docker compose logs --tail=100 rag nginx tinyauth

Текущая конфигурация обслуживает HTTP. Для внешнего развёртывания настройте HTTPS и согласуйте адрес авторизации и TINYAUTH_SECURE_COOKIE с используемой схемой доступа.

Фронтэнд

Интерфейс использует локальную полную сборку jQuery 4.0.0, включая Ajax. Библиотека хранится в frontend/assets/js/jquery-4.0.0.min.js и загружается перед app.js; CDN, Node.js и сборка для запуска не нужны. DOM, события и запросы к API обрабатываются через jQuery.

Основная точка входа — frontend/index.php через Nginx и PHP-FPM. Страница app/static/index.html, которую отдаёт FastAPI при прямом обращении, использует те же CSS и JS из frontend/assets/; при изменении разметки обновляйте обе страницы. При работе через Compose изменения в frontend/ видны через подключённый каталог. Для обновления ресурсов внутри образа FastAPI пересоберите сервис: docker compose up -d --build rag.

Для браузерной проверки нужны Node.js и Chromium, Chrome или Edge. Запустите node tests/frontend-smoke.cjs; при необходимости задайте путь к браузеру через CHROME_PATH. Проверка поднимает временный локальный сервер с тестовым API и проверяет поиск, Markdown, сноски, загрузку файлов и обработку ошибок. В PowerShell команда $env:FRONTEND_ENTRY='php'; node tests/frontend-smoke.cjs проверяет разметку PHP-страницы без выполнения PHP. Эти проверки не запускают OCR, Qdrant или Ollama.

Данные и настройки

Диалоги и контекст

Интерфейс использует компоновку с историей слева, перепиской в центре и панелью источников и файлов справа. Каждый ответ сохраняет собственные сноски и таблицу источников. Панель релевантности переключается кнопкой «Проверить источники»; таблица и диаграмма сортируются по убыванию исходного score поиска. Значение сходства не является вероятностью правильного ответа.

«Релевантность источников» свёрнута в пустом диалоге и раскрывается при отправке запроса или выборе готового ответа. Её можно свернуть и раскрыть вручную. «Загрузка файлов» свёрнута по умолчанию и открывается нажатием на заголовок; база файлов остаётся раскрытой.

Полные диалоги с ответами и метаданными источников хранятся в IndexedDB этого браузера: до 50 диалогов, срок хранения — 30 дней с последнего сообщения. Есть удаление одного диалога и очистка истории. localStorage хранит только идентификатор открытого диалога. Тексты извлечённых фрагментов документов в историю не записываются. При недоступном хранилище переписка работает в памяти с видимым уведомлением. История привязана к адресу приложения и профилю браузера; синхронизации устройств и серверной истории по учётным записям в этой версии нет.

POST /ask сохраняет совместимость с одиночным вопросом и принимает дополнительный массив history из завершённых пар {role: "user", content: "…"}, {role: "assistant", content: "…"}. Системную роль, незавершённые пары и выход за ограничения API отклоняет. Неуспешные ответы не передаются как контекст следующего вопроса.

Сервер восстанавливает самостоятельный поисковый запрос из уточнения и истории, заново ищет документы и отправляет историю вместе с новыми фрагментами в Ollama /api/chat. На уточнение требуется два обращения к модели: восстановление темы и ответ. Старые номера сносок из контекста удаляются, чтобы не смешивать источники разных ответов.

Весь диалог остаётся доступен в интерфейсе, но память модели ограничена: браузер передаёт до 50 последних завершённых пар, сервер выбирает до 12 последних сообщений в пределах 5000 байт UTF-8 и сокращает каждое до 1800 байт. Для большого текущего вопроса бюджет истории уменьшается. Если часть истории не поместилась, интерфейс показывает уведомление. Это окно последних сообщений, без автоматического резюмирования старой переписки.

LLM_CONTEXT_SIZE задаёт окно Ollama, по умолчанию 16384 токена, минимум 8192. Увеличение окна требует больше памяти. Ответ ограничен 1536 токенами; бюджет входа консервативно рассчитывается по UTF-8, место для текущего вопроса, источников и системных правил резервируется отдельно.

Модель по умолчанию — qwen3.5:9b-q4_K_M (около 6,6 ГБ для загрузки; для работы нужна дополнительная память). Генерация ответа и восстановление поискового запроса используют think: false, чтобы рассуждения не расходовали бюджет ответа. Для Qwen3.5 используются параметры режима без рассуждений: temperature 0.7, top_p 0.8, top_k 20, min_p 0, presence_penalty 1.5 и repeat_penalty 1. Требуется версия Ollama с поддержкой Qwen3.5. При смене LLM_MODEL загрузите модель и пересоздайте RAG: docker compose up -d --build rag. Смена генеративной модели не требует переиндексации документов.

Индексация работает как фоновая задача и отображается в правой панели. «Обновить индекс базы» запускает /api/indexing/start, загрузка — /api/indexing/upload. /api/indexing/status возвращает состояние, обработанные файлы, текущую страницу OCR или пакет эмбеддингов, активное время и примерное время до окончания. Индикатор показывает долю завершённых файлов. Уже обработанные неизменённые документы пропускаются.

«Пауза» вызывает /api/indexing/{id}/pause, «Продолжить» — /api/indexing/{id}/resume. Пауза кооперативная: текущий вызов OCR, вычисление пакета или запись в Qdrant завершается, после чего поток ждёт продолжения. До подтверждения интерфейс показывает «Приостанавливаем…». Контрольные точки есть между страницами PDF/OCR, чтениями при хешировании, пакетами эмбеддингов и перед записью. Уже вычисленные пакеты не вычисляются заново после продолжения. Пауза останавливает учёт активного времени.

Оценка оставшегося времени приблизительная: учитывает активное время, размеры файлов и пройденные этапы. До появления первого измерения OCR, эмбеддингов или завершённого файла показано «Оцениваем время…». При разной сложности документов оценка пересчитывается; это не обещанное время завершения. На паузе она сохраняется и относится ко времени после продолжения.

Задача продолжает работать при закрытии или перезагрузке страницы, после открытия интерфейс восстанавливает её состояние. Одновременно разрешена одна задача индексации общей базы; конфликт возвращает HTTP 409, без перезаписи обрабатываемого файла. Совместимые /upload, /reindex и /reindex-file/… используют ту же очередь и ждут итогового результата. Контроллер рассчитан на один процесс Uvicorn, как в текущем Dockerfile. Состояние задач хранится в памяти сервера: после перезапуска контейнера незавершённую индексацию нужно запустить снова, ранее записанные документы будут пропущены.

Иконки интерфейса — общий набор контурных SVG в frontend/assets/icons.svg, соответствующий выбранному макету №2; отдельные цвета документов и папок заданы в CSS.

В блоке индексации постоянно показывается «проиндексировано / всего документов». /api/indexing/summary считает уникальные пути документов, представленные в текущей коллекции Qdrant, и сопоставляет их с существующими исходными файлами. Несколько фрагментов одного документа считаются одним документом; удалённые файлы и временные результаты OCR не учитываются. Сводка обновляется раз в 10 секунд и после завершения задачи. При недоступном Qdrant показывается неизвестное значение, а не ноль.

Список файлов, общая индексация и сводка используют один обход исходных документов: каталоги _ocr_*, временные загрузки и TXT, созданные DjVu OCR рядом с PPM, исключены. Кэш OCR сохраняется и продолжает использоваться при индексации исходного документа.

Высота списка файлов меняется нижней ручкой: перетаскиванием мышью/касанием либо стрелками вверх и вниз при фокусе на ручке. Размер сохраняется в localStorage и восстанавливается после перезагрузки; поиск, сворачивание и автоматическое обновление списка его не сбрасывают. Home возвращает стандартную высоту.

После обновления исходников пересоберите RAG, чтобы сервер принял новый формат контекста:

docker compose up -d --build rag

Проверки диалогов

node tests/frontend-smoke.cjs проверяет интерфейс в Chromium: форматирование, безопасность вывода, загрузку, обновление индекса, продолжение диалога, восстановление после перезагрузки, изоляцию разных диалогов и сноски старых ответов. FRONTEND_ENTRY=php выбирает разметку PHP без исполнения PHP. UI_SCREENSHOTS=1 дополнительно сохраняет настольный и мобильный скриншоты в output/interface-concepts/implemented/ и проверяет отсутствие горизонтального переполнения страницы.

Для тестов API установите небольшое отдельное окружение: python -m pip install -r tests/requirements.txt, затем python tests/test_dialogue.py и python tests/test_indexing.py. Можно установить зависимости через --target .test-deps; этот каталог исключён из Git. Проверяются реальные обработчики FastAPI, контракт сообщений Ollama, остановка и продолжение фонового потока, исключение времени паузы из ETA, защита от параллельной записи и контрольные точки настоящего конвейера индексации. Тяжёлые эмбеддинги, Qdrant, OCR и транспорт модели подменяются; эти проверки не заменяют запуск с вашей моделью и документами. Браузерные проверки дополнительно проверяют SVG, паузу, продолжение и отображение ETA; при UI_SCREENSHOTS=1 сохраняются состояния indexing-running.png и indexing-paused.png.

Эти файлы и каталоги остаются локально и исключены из Git:

Путь Содержимое
.env Настройки конкретного сервера и учётные данные
docs/ Загруженные документы и промежуточные файлы OCR
processed/ Кэш извлечённого текста
qdrant_storage/ Векторная база
backup/ Локальные резервные копии
dist/ Архивы исходников для передачи

Модели Ollama, кэш Hugging Face и база Tinyauth хранятся в именованных Docker-томах. Они также не входят в репозиторий. На новом сервере модели нужно загрузить заново, документы перенести отдельно и проиндексировать. GitHub хранит исходный код; запуск контейнеров требует отдельного сервера.

Загрузка на GitHub

Папка подготовлена для ветки main. Перед первым коммитом укажите свои имя и email в Git, если они ещё не настроены:

git config user.name "YOUR_NAME"
git config user.email "YOUR_GITHUB_EMAIL"
git add .
git diff --cached --stat
git commit -m "Initial import of RAG-MD"

Создайте пустой репозиторий на GitHub, скопируйте его HTTPS-адрес и подставьте вместо YOUR_REPOSITORY_URL:

git remote add origin YOUR_REPOSITORY_URL
git push -u origin main

Для загрузки через веб-интерфейс можно распаковать dist/rag-md-github.zip и загрузить содержимое архива в репозиторий. Сам ZIP не заменяет размещение исходных файлов. Архив не содержит .git, локальных документов, базы и .env.