Все проекты

База знаний для 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.

Место в экосистеме

Люди и сервисыLLM-провайдерыAPpacod-apCMpacod-cmsKBpacod-kbGWpacod-gwIDpacod-id
Люди и сервисыLLM-провайдерыAPpacod-apCMpacod-cmsKBpacod-kbGWpacod-gwIDpacod-id
Схема: как связаны проекты pacod Вызовы LLM Нажмите на проект, чтобы увидеть, с чем он связан.

Зачем он нужен

Большая часть работы в retrieval-augmented generation происходит до того, как модель прочтёт хоть слово: загрузить документы, поддерживать их в чистоте, найти нужный фрагмент и сообщить модели, откуда он взят. pacod-kb — именно эта часть, выделенная в отдельный сервис.

Его сформировали три требования. Ответы должны быть цитируемыми, поэтому каждый чанк несёт свой путь по заголовкам и смещения в байтах в стабильном тексте Markdown. Источники меняются, поэтому ничего не перезаписывается: новая версия замещает старую, а та остаётся доступной для поиска по дате. А загруженный текст — недоверенный ввод для модели, поэтому перед индексацией он проверяется на prompt injection, а на выходе ограждается как данные.

От документов к ответам

Документ проходит один и тот же путь, был ли он загружен, положен в папку dropzone, получен по URL или повторно получен из отслеживаемого веб-источника:

  1. Приём. SHA-256 байтов источника — это идентификатор документа, поэтому один и тот же файл никогда не загружается дважды.
  2. Разбор. PDF (docling, с необязательным OCR для отсканированных страниц), DOCX, HTML, Markdown, текст и JSON превращаются в Markdown с front matter на YAML.
  3. Проверка языка. Принимаются английский, немецкий, болгарский, русский, сербский, македонский, хорватский и словенский; остальные отклоняются.
  4. Проверка детерминированного разбора на prompt injection и спам — до того, как текст увидит какая-либо LLM.
  5. Преобразование (необязательно). Проход LLM через pacod-gw приводит Markdown в порядок. Обрезанный, отказной или пустой ответ оставляет детерминированный разбор.
  6. Очистка и структурирование. Фильтр шума убирает номера страниц, колонтитулы и шаблонный текст; повышение заголовков распознаёт структуру юридических текстов, научных статей и публикаций.
  7. Сохранение document.md, манифеста и снимка источника.
  8. Индексация. Чанки до 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; на macOS make 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 для ключевой половины поиска, доработка разбиения на чанки и пересборка в теневую коллекцию с последующим переключением.

Подробнее:

Технологии

Языки
  • Python
Технологии
  • FastAPI
  • Qdrant
  • BGE-M3
  • BM25
  • SQLite
  • PostgreSQL
  • HTMX
  • Docker

Быстрый старт

Запуск в Docker
make setupmake upopen http://localhost:8080/kb
Разработка и оценка
uv syncmake checkmake eval

Работает с

  • pacod-id

    Пользователи входят через pacod-id; роли viewer, editor и admin назначаются там.

  • pacod-gw

    Необязательный проход очистки через LLM идёт через шлюз, который тарифицирует и атрибутирует каждый вызов.

  • pacod-ap

    Работая рядом с pacod-ap, kb может строить эмбеддинги и переранжировать через его общий сервис эмбеддингов — с теми же векторами BGE-M3.

Лицензия

Apache-2.0