Подготовка данных для RAG без chunking, embeddings и vector DB.
Пайплайн делает только подготовительные этапы:
- загрузка источников через LlamaIndex
SimpleDirectoryReader; - парсинг
pdf,txt,htmlчерез Unstructured и строковый парсингcsvчерез структурированный CSV parser; - очистка текста regex-правилами;
- нормализация Unicode/регистра и sentence statistics через spaCy;
- дедупликация exact hash + near-duplicate MinHash LSH через datasketch;
- структурирование в
text + metadataи LlamaIndexDocument; - экспорт в JSON и JSONL;
- orchestration через Prefect и логирование артефактов/метрик в MLflow.
Выходной формат подготовлен для следующих этапов RAG: chunking, embeddings и vector DB. Эти этапы здесь намеренно не реализованы, но metadata уже содержит lineage, hierarchy и quality signals, чтобы downstream-процессы могли сохранять связь чанков с исходниками и элементами.
Зависимости и локальный пакет уже установлены в глобальный Python. Запуск из корня проекта:
rag-prep --config config/default.yamlПрямой запуск без Prefect, но с теми же классами этапов:
rag-prep --config config/default.yaml --no-prefectРезультаты пишутся в data/prepared/:
documents.jsondocuments.jsonlmanifest.json
MLflow tracking по умолчанию пишется в mlruns/.
В manifest.json сохраняются параметры запуска, числовые счётчики и диагностический блок parse_failures для файлов, которые не удалось разобрать при parser.fail_on_error: false.
Файлы результата пишутся через временный файл и атомарную замену, поэтому частично записанный JSON/JSONL не остаётся на месте целевого файла.
Проект рассчитан на установку в текущий глобальный Python, без создания отдельного окружения. Команды нужно выполнять из корня проекта.
Обновить зависимости:
python -m pip install --upgrade -r requirements.txtУстановить локальный пакет в editable-режиме, чтобы команда rag-prep была доступна после изменения исходников:
python -m pip install -e . --no-depsФлаг --no-deps уместен, если зависимости уже установлены через requirements.txt. Если проект переносится на новую машину, сначала ставится requirements.txt, затем локальный пакет.
Проверить целостность глобального окружения:
python -m pip checkНа Windows для Unstructured дополнительно установлен python-magic-bin, чтобы unstructured.partition.auto корректно определял типы файлов. Для OCR-режимов PDF могут понадобиться системные Tesseract и Poppler, но для текстовых PDF достаточно parser.strategy: fast.
Команда rag-prep перед запуском Prefect выставляет NO_PROXY=*, чтобы локальный временный сервер Prefect не ломался из-за системных proxy-настроек. Импорт rag_prep.flow сам по себе переменные окружения не меняет.
В data/raw/ лежат русскоязычные примеры, похожие на реальные документы организации:
sample.txt- регламент обработки заявок в ИТ-службе;sample.html- инструкция по передаче документов в электронный архив;sample.csv- табличный реестр этапов service desk, где каждая строка становится отдельным элементом, а в конце есть exact и near-дубли для проверки дедупликации;sample.pdf- памятка по подготовке комплекта документов к архивированию.
Эти файлы нужны для smoke-проверки пайплайна. Для своих данных можно заменить содержимое data/raw/ или указать другой paths.input_dir в config/default.yaml.
Каждая запись:
{
"text": "...",
"metadata": {
"id": "stable-document-id",
"source": "absolute/path/to/file",
"section": "full_document",
"file_name": "sample.txt",
"file_type": "txt",
"source_hash": "...",
"text_hash": "...",
"parent_ids": ["source-id"],
"origin_element_ids": ["element-id-1", "element-id-2"],
"lineage": {
"source_id": "source-id",
"source_hash": "...",
"origin_element_ids": ["element-id-1", "element-id-2"],
"element_range": [0, 3],
"pipeline_stage": "prepared_document"
},
"hierarchy": {
"section_path": ["Раздел", "Подраздел"],
"section_depth": 2,
"document_order": 0
},
"element_start": 0,
"element_end": 3,
"element_types": ["NarrativeText", "Title"],
"page_number": null,
"char_count": 100,
"word_count": 16,
"sentence_count": 2,
"pipeline_run_id": "...",
"parsed_at": "...",
"extra": {}
}
}Поля, которые нужны следующим этапам без реализации самих этапов:
metadata.id- стабильный идентификатор подготовленного документа;metadata.parent_ids- родительские сущности, сейчас это идентификатор source;metadata.origin_element_ids- элементы парсинга, из которых собран документ;metadata.lineage- цепочкаsource -> parsed elements -> prepared document;metadata.hierarchy.section_path- путь секции для будущего document tree;metadata.hierarchy.document_order- порядок документа внутри исходника;metadata.extra.quality- диагностические scores для boilerplate/OCR garbage/menu leftovers.
Quality scoring не удаляет документы сам по себе. Он даёт сигнал будущим этапам chunking/retrieval, чтобы можно было фильтровать мусор, расследовать retrieval misses и объяснять происхождение ответа.
Основные параметры находятся в config/default.yaml:
paths.input_dirиpaths.output_dir;loader.allowed_extensions;parser.strategy:fast,auto,hi_res,ocr_only;parser.fail_on_error:falseпропускает проблемный файл и пишет failure в manifest,trueостанавливает пайплайн;cleaning.drop_patternsдля удаления boilerplate;normalization.spacy_language;deduplication.threshold,num_perm,shingle_size;structuring.group_by_section;logging.mlflow_enabled.
Относительные пути в paths.* считаются относительно корня проекта, если конфиг лежит в папке config/. Если конфиг расположен в другой папке, относительные пути считаются относительно папки этого YAML-файла.
Для OCR PDF на Windows могут дополнительно понадобиться системные Tesseract/Poppler. Для текстовых PDF используется strategy: fast.
Второй пайплайн делает только chunking подготовленных документов. Он не считает embeddings, не пишет vector DB и не запускает retrieval.
Вход берётся из результата первого пайплайна:
data/prepared/documents.jsonl- структурированные документыtext + metadata;- lineage, hierarchy, source metadata и quality signals переносятся в каждый чанк.
Выход пишется в data/chunks/:
chunks.jsonchunks.jsonlmanifest.json
- LlamaIndex
SentenceSplitter- основной semantic-aware splitter, который старается не резать текст внутри предложений; - LlamaIndex
TokenTextSplitter- альтернативная стратегия для строгого token-based режима; tiktoken- подсчёт токенов подtext-embedding-3-small;- Pydantic - схема чанка и metadata;
- Prefect - воспроизводимый orchestration;
- MLflow - параметры запуска, метрики размера чанков и артефакты экспорта.
Пайплайн не режет весь документ одной плоской строкой. Он работает в таком порядке:
- читает
PreparedDocumentиз первого пайплайна; - восстанавливает semantic blocks внутри секции по границам абзацев/элементов, которые были сохранены как
\n\n; - упаковывает блоки в chunk до
chunk_size; - применяет
chunk_overlapтолько внутри текущей секции; - режет LlamaIndex splitter'ом только oversized-блок, который сам больше token budget;
- считает chunk-level quality signals и сохраняет span metadata.
Так чанки не пересекают границы prepared document/section, а table/list/code-like блоки и повторяющиеся абзацы получают стабильные semantic_block_ids. Для обычных block-aware чанков offsets берутся из заранее посчитанных block spans, без глобального text.find() по всему документу. Если fallback splitter не сможет точно найти подстроку для oversized-блока, чанк получит offset_strategy с estimated_*, warning в логах и отдельный estimated_offsets_count в validation.
Перед запуском chunking должен существовать файл data/prepared/documents.jsonl.
Через Prefect:
rag-prep chunk --config config/chunking.yamlПрямой запуск без Prefect:
rag-prep chunk --config config/chunking.yaml --no-prefectСтарые команды подготовки данных остаются рабочими:
rag-prep prepare --config config/default.yaml
rag-prep prepare --config config/default.yaml --no-prefectLegacy-форма тоже сохранена:
rag-prep --config config/default.yamlПараметры находятся в config/chunking.yaml:
paths.input_jsonl- JSONL из первого пайплайна;paths.output_dir- директория экспорта чанков;chunking.strategy-sentenceилиtoken;chunking.chunk_size- целевой размер чанка в токенах;chunking.chunk_overlap- overlap в токенах, должен быть меньшеchunk_size;chunking.tokenizer_model- модель токенизатора, по умолчаниюtext-embedding-3-small;chunking.embedding_model- модель будущих embeddings, записывается в metadata, сами embeddings не считаются;chunking.preserve_section_boundaries- не смешивать разные prepared documents/sections;chunking.preserve_block_boundaries- сначала упаковывать semantic blocks и резать только oversized-блоки;chunking.max_chunk_tokens- validation guardrail перед embeddings;chunking.min_quality_score- порог для диагностического счётчика low-quality chunks;chunking.fail_on_validation_error- останавливать пайплайн при пустых, слишком маленьких, слишком больших, low-quality чанках, estimated offsets или проблемах lineage.
Каждая запись в chunks.jsonl готова к следующему embeddings-пайплайну:
{
"text": "...",
"metadata": {
"id": "stable-chunk-id",
"document_id": "prepared-document-id",
"source": "absolute/path/to/file",
"section": "Раздел",
"position": 0,
"chunk_start_char": 0,
"chunk_end_char": 430,
"chunk_token_count": 128,
"chunk_size": 220,
"chunk_overlap": 40,
"chunking_strategy": "sentence",
"tokenizer_model": "text-embedding-3-small",
"embedding_model": "text-embedding-3-small",
"semantic_block_ids": ["semantic-block-id-1"],
"semantic_block_start": 0,
"semantic_block_end": 0,
"offset_strategy": "semantic_block_span",
"parent_ids": ["prepared-document-id"],
"origin_element_ids": ["element-id-1"],
"lineage": {
"document_id": "prepared-document-id",
"chunk_id": "stable-chunk-id",
"chunk_position": 0,
"semantic_block_ids": ["semantic-block-id-1"],
"semantic_block_range": [0, 0],
"pipeline_stage": "chunk"
},
"hierarchy": {
"section_path": ["Раздел"],
"document_order": 0,
"chunk_position": 0,
"semantic_block_count": 1,
"semantic_block_range": [0, 0]
},
"source_hash": "...",
"document_text_hash": "...",
"text_hash": "...",
"file_name": "sample.txt",
"file_type": "txt",
"quality": {
"token_density": 0.37,
"language_confidence": 0.99,
"ocr_noise_score": 0.0,
"structure_score": 0.82,
"unique_token_ratio": 0.74,
"semantic_block_count": 1,
"is_low_quality_chunk": false
},
"chunked_at": "..."
}
}position, chunk_start_char, chunk_end_char, semantic_block_ids, parent_ids, origin_element_ids и lineage нужны для отладки retrieval misses, hallucinations и обратной трассировки ответа к исходному документу. embedding_model фиксируется заранее, чтобы следующий этап мог проверить совместимость чанков с выбранной моделью embeddings.