База знаний для RAG
pacod-kb
Превращайте файлы и веб-страницы в версионированные знания с поиском — со стабильным контрактом для моделей, которые их читают.
pacod-kb загружает файлы и веб-страницы, преобразует их в канонический Markdown, индексирует для гибридного семантического и ключевого поиска, хранит каждую версию и передаёт моделям ниже по цепочке чанки со стабильным контрактом метаданных. Работает только на CPU.
- Apache-2.0
- Версия 1.0.0
- Стабильная
Возможности
-
Загрузка чего угодно
PDF, DOCX, HTML, Markdown, текст и JSON — загруженные вручную, положенные в папку, полученные по URL или отслеживаемые на изменения.
-
Канонический Markdown
Распознавание структуры заголовков для юридических текстов на восьми языках, научных работ и статей; шаблонный текст отфильтровывается.
-
Безопасность контента
Многоуровневая проверка на prompt injection и спам блокирует документы или отправляет их в карантин до индексации; результаты изолируются как недоверенные данные.
-
Гибридный поиск
Плотные векторы BGE-M3 в Qdrant плюс ключевые слова BM25, объединённые методом reciprocal rank fusion.
-
Все версии сохраняются
Замещённые версии остаются доступными для поиска через запросы на момент времени; git-архив фиксирует каждое изменение.
-
Откалиброванная релевантность
Необязательный реранкер на cross-encoder выдаёт откалиброванную оценку релевантности от 0 до 1.
-
Только CPU
Эмбеддинги, OCR и поиск остаются отзывчивыми на одном CPU, даже во время загрузки документов.
-
UI и CLI
Веб-интерфейс и CLI для документов, проектов, задач, использования AI и его стоимости, а также участников с ролями viewer, editor и admin.
Место в экосистеме
Зачем он нужен
Большая часть работы в retrieval-augmented generation происходит до того, как модель прочтёт хоть слово: загрузить документы, поддерживать их в чистоте, найти нужный фрагмент и сообщить модели, откуда он взят. pacod-kb — именно эта часть, выделенная в отдельный сервис.
Его сформировали три требования. Ответы должны быть цитируемыми, поэтому каждый чанк несёт свой путь по заголовкам и смещения в байтах в стабильном тексте Markdown. Источники меняются, поэтому ничего не перезаписывается: новая версия замещает старую, а та остаётся доступной для поиска по дате. А загруженный текст — недоверенный ввод для модели, поэтому перед индексацией он проверяется на prompt injection, а на выходе ограждается как данные.
От документов к ответам
Документ проходит один и тот же путь, был ли он загружен, положен в папку dropzone, получен по URL или повторно получен из отслеживаемого веб-источника:
- Приём. SHA-256 байтов источника — это идентификатор документа, поэтому один и тот же файл никогда не загружается дважды.
- Разбор. PDF (docling, с необязательным OCR для отсканированных страниц), DOCX, HTML, Markdown, текст и JSON превращаются в Markdown с front matter на YAML.
- Проверка языка. Принимаются английский, немецкий, болгарский, русский, сербский, македонский, хорватский и словенский; остальные отклоняются.
- Проверка детерминированного разбора на prompt injection и спам — до того, как текст увидит какая-либо LLM.
- Преобразование (необязательно). Проход LLM через pacod-gw приводит Markdown в порядок. Обрезанный, отказной или пустой ответ оставляет детерминированный разбор.
- Очистка и структурирование. Фильтр шума убирает номера страниц, колонтитулы и шаблонный текст; повышение заголовков распознаёт структуру юридических текстов, научных статей и публикаций.
- Сохранение
document.md, манифеста и снимка источника. - Индексация. Чанки до 500 токенов, учитывающие заголовки, встраиваются BGE-M3 в Qdrant и записываются в индекс ключевых слов — в собственной коллекции проекта.
Архитектура
pacod-kb — это два сервиса, kb-pipeline и kb-ui, а также модель и хранилище, которыми они пользуются:
| Часть | Роль |
|---|---|
Pipeline API (kb-pipeline, :8000) |
Загрузка, индексация, поиск, задачи, настройки; единственная часть, которая обращается к AI-шлюзу |
Веб-интерфейс (kb-ui, :8080) |
Вход через pacod-id, сессии, участники, страницы; обращается к конвейеру с API-ключом |
| BGE-M3 | Эмбеддинги — в собственном процессе конвейера или через общий сервис эмбеддингов |
| Qdrant | Плотные векторы, встроенный в конвейер или отдельным сервером, которым могут пользоваться и другие продукты |
| SQLite FTS5 или PostgreSQL | Индекс ключевых слов BM25 |
| Файловая система или PostgreSQL | Документы, манифесты и журнал использования и стоимости AI |
Такое разделение сделано намеренно. Конвейер разбирает недоверенные документы и загружает
произвольные URL, поэтому не получает ни одного из секретов pacod-id; интерфейс, в свою очередь,
никогда не видит ключ шлюза. Все контейнеры подключены к одной общей сети Docker, pacod-net,
так что разделение держится на секретах, а не на сетях: каждый контейнер получает явный список
переменных окружения, и это проверяет тест.
Каждый вызов из интерфейса несёт X-KB-Actor — идентификатор вошедшего человека. Конвейер доверяет
ему только вместе с API-ключом, записывает его в задачи, изменения настроек и учёт использования и
передаёт шлюзу. Когда pacod-id удаляет учётную запись, kb повсюду заменяет этот идентификатор на
deleted-user и только потом подтверждает удаление.
Модель безопасности
Безопасность содержимого. Проверка нормализует текст, чтобы обойти обфускацию, сверяет его с многоязычным набором правил и выносит один из трёх вердиктов:
- block: одна находка высокой серьёзности — инъекция или обфускация — или высокий показатель угрозы. Загрузка завершается ошибкой, ничего не сохраняется.
- quarantine: документ сохраняется со статусом
reviewи никогда не индексируется. Один только спам никогда не приводит к блокировке. - clean: документ индексируется.
Необязательное второе мнение LLM через шлюз запрашивается, только если правила не заблокировали
документ; на него отводится 15 секунд, и при сбое оно не блокирует документ. Заведомо безопасный
материал может обойти проверку с kb ingest --trusted. На выходе /query ограждает каждый чанк
как недоверенные данные.
Загрузка по URL. WEB_FETCH_ALLOW_PRIVATE=false, рекомендуемое в продакшене, отклоняет
загрузку по URL, которые разрешаются в частные адреса, — защита от server-side request forgery.
Люди. Интерфейс выполняет вход через pacod-id по OIDC с PKCE и хранит токены в зашифрованной серверной сессии; cookie содержит только случайный секрет. Роли поступают из потока авторизации pacod-id. Если копия интерфейса старше 60 секунд, страницы отвечают 503, а не гадают, кому что разрешено.
| Роль | Может |
|---|---|
| viewer | Читать и искать |
| editor | Загружать, переиндексировать и удалять документы; управлять проектами |
| admin | AI Management, промпты, переключатели во время работы, участники |
Поиск, подтверждённый измерениями
Запрос выполняется в Qdrant и в индексе ключевых слов, а два ранжирования объединяются методом
reciprocal rank fusion (k = 60). Фильтры по дате и истории применяются внутри обоих поисков.
Необязательный cross-encoder, BAAI/bge-reranker-v2-m3, переупорядочивает лучших кандидатов и
даёт оценку релевантности от 0 до 1, сопоставимую между запросами, так что min_score может
отсеивать запросы без ответа.
Значения по умолчанию получены на стенде оценки поиска: 14 документов на шести языках (GDPR, Директива о защите данных в правоохранительной сфере, четыре конституции, статьи Википедии) и 92 написанных вручную запроса — перефразировки, точные термины, межъязыковые, на родном языке и без ответа. Измерено на процессоре машины разработчика — документация подчёркивает, что это не продакшен-цифры:
- Без реранкера нужный раздел оказывается в первой пятёрке для 98,7 % запросов, у которых есть ответ; R@1 — 0,83, примерно за 50 мс.
- С реранкером R@1 растёт до 0,91, примерно за 2 с на запрос.
- При высокой нагрузке от загрузки поиск раньше ждал около 24 с целый пакет встраивания. Теперь запросы идут впереди работы по загрузке и ждут не больше одного небольшого микропакета: около 0,4 с (p50), когда задачи загрузки выполняются одна за другой, как их выполняет конвейер.
- Пакеты, отсортированные по длине, ускоряют встраивание при загрузке примерно в 2,2 раза при неизменных векторах.
Реранкер по умолчанию выключен: его выигрыш немного не дотянул до порога на отложенной части запросов, а его задержку ещё нужно подтвердить на продакшен-процессоре.
Что получает модель
POST /query возвращает чанки по стабильному документированному контракту метаданных:
{"query": "maximum fine for a data breach", "top_k": 5, "as_of": "2025-12-30"}
Ответ — {"results": [...], "reranked": false, "score_kind": "rrf"}. Каждый результат содержит:
| Поле | Для чего |
|---|---|
chunk_id |
Стабильный идентификатор, кодирующий структуру заголовков |
source_id |
Идентификатор документа: SHA-256 байтов источника |
heading_path |
Цепочка заголовков вроде ["Chapter 1", "§ 5"] — для цитирования и фильтров по разделам |
byte_start, byte_end |
Смещения UTF-8 в сохранённом тексте Markdown |
valid_until |
Когда эта версия была замещена, или null, пока она актуальна |
text |
Встроенный текст, огороженный как недоверенные данные |
score, rrf_score, rerank_score |
Оценка для упорядочивания и её составляющие |
У каждого документа есть и манифест: заголовок, источник, тип документа, статус, проект, парсер
или модель, создавшие текст, и журнал жизненного цикла (ingested, superseded,
security_flagged, llm_usage и другие).
Версии, а не перезапись
Новая версия, загруженная с --supersedes, или изменившийся отслеживаемый источник помечает
старую версию полем valid_until — одновременно во front matter, в манифесте, в индексе ключевых
слов и в данных векторов. Поиск по умолчанию возвращает актуальные чанки; as_of — то, что
действовало в заданный день; include_historical — всё. Если отслеживаемая страница вернулась к
прежнему виду, более ранняя версия восстанавливается. При замещении и пересборке векторы
неизменившихся чанков используются повторно.
Эксплуатация
kb следует общему сетевому соглашению pacod: каждый контейнер подключён к одной сети Docker,
pacod-net. docker-compose.yml содержит собственные сервисы kb, kb-pipeline и kb-ui.
docker-compose.standalone.yml содержит копии общих компонентов для kb, которые включаются
профилями Docker Compose: Qdrant, PostgreSQL, pacod-id с базой данных рядом с базой kb,
перехватчик почты и шлюз для разработки, чьи фиктивные провайдеры не выпускают ничего за пределы
машины. make up загружает оба файла. На одной машине может работать только одна копия каждого
общего компонента. В продакшене kb направляют на настоящий pacod-gw, ставят pacod-id за TLS и
убирают профили для разработки.
pacod-demo, работающий на demo.pacod.io, запускает kb вместе с остальными проектами pacod. Он загружает только файл приложения kb: kb хранит свою базу данных в общем PostgreSQL, а коллекции — в общем Qdrant, строит эмбеддинги через общий сервис эмбеддингов, а его интерфейс обслуживается за общим входным Caddy.
- Хранилище.
KB_STORAGE_BACKEND—filesystemилиdatabase; переключение не переносит документы. - Данные. Каталоги данных задаются переменными (
KB_ROOT,KB_MODEL_CACHE,KB_QDRANT_DATA,KB_POSTGRES_DATA). Имя без косой черты означает том Docker; на macOSmake setupиспользует тома. - Процессор.
TORCH_NUM_THREADS=0следует квоте процессора контейнера.EMBED_BATCH_TOKENS(1024) ограничивает, сколько запрос может ждать за загрузкой. - Модели офлайн. Запустите один раз с доступом к сети, чтобы заполнить кеш моделей, затем
задайте
HF_HUB_OFFLINE=1. - Общие модели. С
EMBEDDINGS_URLконвейер строит эмбеддинги через общий сервис эмбеддингов, а не загружает BGE-M3 сам. Векторы те же, поэтому ничего не нужно встраивать заново, а конвейеру требуется примерно на 2 ГБ меньше памяти.RERANKER_URLделает то же самое для реранкера. - Общий Qdrant. На Qdrant, которым пользуются и другие продукты, страницы Qdrant в интерфейсе
показывают и затрагивают только коллекции kb (
KB_QDRANT_COLLECTION_PREFIX, по умолчаниюkb_). - Переключатели во время работы. Панель включает и выключает проверку содержимого, AI-преобразование и проверку языка без перезапуска; при следующем запуске снова действуют переменные окружения.
- AI. Администраторы выбирают модели и правят промпты в AI Management. Каждый вызов несёт непрозрачную атрибуцию (функция, задача, проект, человек), а kb записывает токены вместе со стоимостью, рассчитанной шлюзом. Если шлюз не настроен, AI выключен, а всё остальное работает.
Резервные копии охватывают KB_ROOT или базу данных kb в PostgreSQL, хранилище Qdrant (или его
можно пересобрать командой kb rebuild --all-projects), базу данных pacod-id в том же PostgreSQL
вместе с её ключом шифрования ключей и .env.
Ограничения и что почитать
- Конвейер и интерфейс работают с одним worker каждый: встроенный Qdrant держит блокировку файла, а работа интерфейса с сессиями рассчитана на один процесс.
- Реранкер в собственном процессе конвейера примерно удваивает потребление им памяти; с
RERANKER_URLконвейер его не загружает. - Сербский принимается, но у детектора языка нет профиля для сербского, поэтому сербский текст может быть определён неверно или отклонён.
- Задокументировано для следующей переиндексации, но не выпущено: разреженные векторы BGE-M3 для ключевой половины поиска, доработка разбиения на чанки и пересборка в теневую коллекцию с последующим переключением.
Подробнее:
- Архитектура: контейнеры, вход, шлюз, кто что сделал.
- Контракт метаданных: каждое поле чанка и манифеста.
- Оценка поиска: полные цифры и решения, стоящие за значениями по умолчанию.
- Руководство по эксплуатации: разработка, продакшен, эксплуатация и устранение неполадок.
- Миграции: обновление
существующей установки, включая переход на
pacod-net.
Технологии
- Языки
- Python
- Технологии
- FastAPI
- Qdrant
- BGE-M3
- BM25
- SQLite
- PostgreSQL
- HTMX
- Docker
Быстрый старт
make setupmake upopen http://localhost:8080/kbuv syncmake checkmake evalРаботает с
-
pacod-id
Пользователи входят через pacod-id; роли viewer, editor и admin назначаются там.
-
pacod-gw
Необязательный проход очистки через LLM идёт через шлюз, который тарифицирует и атрибутирует каждый вызов.
-
pacod-ap
Работая рядом с pacod-ap, kb может строить эмбеддинги и переранжировать через его общий сервис эмбеддингов — с теми же векторами BGE-M3.
Лицензия
Apache-2.0