Прием данных в RAG с учетом качества: маршрутизация документов по степени читаемости
Декларативный поток обработки документов, который оценивает уровень языка, наличие дубликатов и показатель читаемости по шкале Флеша, затем отправляет документы в два индекса OpenSearch для поиска среди клиентов и разработчиков.
Источники: Architecting a Multi-Tier RAG Pipeline: Quality-Aware Document Routing with docling-pipelines
Перевод базы знаний службы поддержки клиентов в систему поиска на основе ИИ обычно проваливается по одной и той же причине: ко всем файлам применяется одинаковый подход. Реалистичный корпус данных представляет собой смешение различных элементов — организованных часто задаваемых вопросов, структурированных руководств по продуктам, объемных руководств для разработчиков, дублированных юридических текстов, а также нескольких поврежденных файлов, в которых совсем нет читаемого текста.
Если поместить все это в одну систему типа RAG для диалогов, качество поиска снижается. Клиент, желающий получить простое руководство, может наткнуться на фрагменты технического руководства. Отправка бесполезного текста или системных логов дорогостоящей модели для краткого изложения или перевода также является расточительством средств.
Решение заключается в сортировке документов по качеству. Двухуровневая схема предполагает анализ каждого файла, удаление пустых страниц, оценку читаемости, после чего корпус данных делят на группы.
- Уровень 1: поиск для потребителей. Хорошо читаемые документы получают структурные метаданные, такие как количество слов, чтобы обеспечить диалоговый поиск для клиентов.
- Уровень 2: поиск для разработчиков и справочный поиск. Сложные технические материалы попадают в отдельный индекс для решения продвинутых технических вопросов.
docling-pipelines делает это практически осуществимым. Это фреймворк с открытым исходным кодом под лицензией MIT, поддерживаемый IBM, который создает и запускает графы обработки документов непосредственно на основе конфигурации в формате JSON. Не требуется специальный Python-код, поскольку операторы передают документы друг другу в виде таблиц Apache Arrow в оперативной памяти, поэтому промежуточные преобразования и проверки маршрутизации почти не влияют на использование CPU и памяти.
Структура конвейера
Все данные находятся в одном JSON-файле потока, который описывает ориентированный ациклический граф:
ingestсчитывает файлы PDF, TXT, DOCX и HTML с диска.extractизвлекает структурированный текст с помощью библиотеки Docling.detect_languageопределяет язык каждого документа.dedupиспользует хеши содержимого для удаления дублирующихся записей.doc_qualityрассчитывает структурные показатели, такие как общее количество слов.readabilityвычисляет оценки сложности текста.quality_branchingвыбирает направление на основе оценки Flesch Reading Ease: если показатель выше 60, используется ветка для высококачественных документов, если 60 и ниже — ветка для низкокачественных документов.
ml_enrichment_high_quality, embeddings_high_quality и vectordb_high_quality. Он добавляет языковые признаки, основанные на машинном обучении, создаёт векторные представления и записывает их в индекс high_quality_docs.sql_filter_low_quality, embeddings_low_quality и vectordb_low_quality. Он удаляет пустые строки, преобразует оставшиеся данные в векторные представления и записывает их в low_quality_docs.Анализ перед разделением
Каждому документу необходим профиль перед тем, как его можно будет направить дальше. После того как ingest считывает данные и extract преобразует структуру в текст, все последующие операторы действуют как трансформаторы: они анализируют таблицу и добавляют новые столбцы в ту же таблицу Arrow в памяти. Сначала выполняются простые проверки, требующие только процессорных ресурсов: определение языка, выявление дубликатов и оценка сложности текста.
Обнаружение языка
Поскольку более сложные модели машинного обучения и формулы для оценки читаемости нуждаются в информации о языке, сначала производится его обнаружение.
{
"name": "detect_language",
"type": "lang_detect",
"config": {
"doc_column": "content"
},
"depends_on": [
"extract"
]
}
Оператор считывает столбец content и добавляет к нему lang_name (код по стандарту ISO 639-1) и lang_score. Размещение этих данных сразу после извлечения позволяет всем последующим этапам, чувствительным к языку, использовать их результаты.
Устранение дубликатов
Дублирующиеся документы тратят вычислительные ресурсы на обработку и искажают результаты поиска.
{
"name": "dedup",
"type": "ededup",
"config": {
"doc_column": "content",
"doc_id_column": "doc_id_hash"
},
"depends_on": [
"detect_language"
]
}
Этот процесс относится к хеш-значению содержимого, сохранённому в doc_id_hash, и к необработанному тексту в content, после чего удаляются дублирующиеся строки из таблицы. Такой подход заранее позволяет избежать ненужной работы на более поздних этапах.
Качество документа
Базовые структурные проверки выявляют пустые страницы и необычную компоновку.
{
"name": "doc_quality",
"type": "doc_quality",
"config": {
"doc_column": "content",
"doc_content_column": "content",
"text_lang": "en"
},
"depends_on": [
"dedup"
]
}
Это добавляет 11 структурных столбцов, все из которых имеют префикс docq_. Важным здесь является столбец docq_total_words, отражающий общее количество слов в каждом документе.
Читаемость
Формулы оценки читаемости определяют уровень сложности на основе длины слов, слогов и структуры предложений.
{
"name": "readability",
"type": "readability",
"config": {
"doc_column": "content",
"readability_score_list": [
"flesch_reading_ease",
"flesch_kincaid_grade",
"gunning_fog",
"difficult_words",
"dale_chall_readability_score"
]
},
"depends_on": [
"doc_quality"
]
}
Массив readability_score_list указывает оператору, какие показатели необходимо вычислить и добавить в виде столбцов. Для маршрутизации используется индекс легкости чтения Флеша, который варьируется от 0 до 100:
- Значение выше 60 означает простой, доступный язык, подходящий для широкой аудитории центра помощи.
- Значение 60 и ниже означает сложный, технический стиль, предполагающий наличие специальных знаний.
Шаг разветвления
Оператор разветвления делит набор данных Arrow на отдельные пути обработки.
{
"name": "quality_branching",
"type": "branching",
"config": {
"branches": {
"high_quality_branch": {
"link_name": "High Quality Documents",
"criteria_json": {
"criteria_list": [
{
"variable": "flesch_reading_ease",
"operator": ">",
"value": 60
}
],
"logical_operator": "AND"
}
},
"low_quality_branch": {
"link_name": "Low Quality Documents",
"criteria_json": {
"criteria_list": [
{
"variable": "flesch_reading_ease",
"operator": "<=",
"value": 60
}
],
"logical_operator": "AND"
}
}
}
},
"depends_on": [
"readability"
]
}
Маршрутизация контролируется декларативным блоком criteria_json, содержащим четыре поля:
variable— имя столбца для проверки, напримерflesch_reading_ease.operator— оператор сравнения, такой как>или<=.value— пороговое значение, здесь60.logical_operator— соединяет несколько условий с помощьюANDилиOR.
При правиле flesch_reading_ease > 60 движок нативно оценивает условия, разделяет таблицу в памяти и запускает каждый из последующих вариантов обработки.
Высококачественный путь: обогащение, встраивание, индексация
Документы, направляемые по пути high_quality_branch, сначала проходят обогащение с использованием методов языковых ИИ.
{
"name": "ml_enrichment_high_quality",
"type": "ml_enrichment",
"config": {
"doc_column": "content",
"lang_column": "lang_name",
"output_column_prefix": "ml_",
"error_column_name": "enrichment_error"
},
"depends_on": [
"quality_branching.high_quality_branch"
]
}
ml_enrichment анализирует текст с использованием моделей, учитывающих язык, и генерирует метаданные, такие как количество предложений, характеристики слогов и списки существительных, всё это направлено на улучшение процесса поиска. Он зависит от столбца lang_name, полученного на этапе обнаружения языка. Поскольку это задача, требующая значительных ресурсов процессора, она выполняется только с текстом, предназначенным для чтения. Параметр output_column_prefix добавляет префикс ml_ к новым столбцам, включая ml_num_words.
Далее текст преобразуется в 2560-мерные эмбеддинги. Используемая модель — openai/granite4:latest, которая предоставляется локальным сервером Ollama через LiteLLM:
{
"name": "embeddings_high_quality",
"type": "embeddings",
"config": {
"embeddings_column": "embeddings",
"overlap_ratio": 0.2,
"doc_column": "content",
"provider_config": {
"api_base": "http://localhost:11434/v1",
"api_key": "${OLLAMA_API_KEY}",
"model_id": "openai/granite4:latest"
},
"provider": "litellm"
},
"depends_on": [
"ml_enrichment_high_quality"
]
}
В заключение документы индексируются в OpenSearch, причем feature_mappings связывают столбцы Arrow с полями OpenSearch:
{
"name": "vectordb_high_quality",
"type": "vectordb",
"config": {
"provider": "opensearch",
"doc_id_column": "doc_id_hash",
"embeddings_column": "embeddings",
"vector_dimension": 2560,
"create_index": "true",
"provider_config": {
"index_name": "high_quality_docs",
"host": "localhost",
"port": 9200,
"username": "${OPENSEARCH_USERNAME}",
"password": "${OPENSEARCH_PASSWORD}",
"use_ssl": false,
"verify_certs": false,
"batch_size": 100,
"engine": "faiss",
"algorithm": "hnsw",
"space_type": "l2"
},
"feature_mappings": [
{"feature_name": "doc_id_hash", "mapped_column_name": "pk"},
{"feature_name": "name", "mapped_column_name": "doc_name"},
{"feature_name": "content", "mapped_column_name": "text"},
{"feature_name": "embeddings", "mapped_column_name": "vector_embeddings"},
{"feature_name": "lang_name", "mapped_column_name": "language"},
{"feature_name": "flesch_reading_ease", "mapped_column_name": "readability_score"},
{"feature_name": "ml_num_words", "mapped_column_name": "word_count"}
]
},
"depends_on": [
"embeddings_high_quality"
]
}
Пути низкого качества: фильтрация, встраивание, индексация
Документы на low_quality_branch обходят дорогостоящий шаг ml_enrichment. Некоторые из них после обработки могут не содержать читаемых слов — например, поврежденные файлы или пустые сканы, — и встраивание пустой строки лишь добавляет шум в векторное пространство. Фильтр удаляет такие документы:
{
"name": "sql_filter_low_quality",
"type": "sql_filter",
"config": {
"criteria_json": {
"criteria_list": [
{
"variable": "docq_total_words",
"operator": ">",
"value": 0
}
],
"logical_operator": "AND"
}
},
"depends_on": [
"quality_branching.low_quality_branch"
]
}
Фильтр проверяет столбец docq_total_words из таблицы doc_quality и удаляет все документы, в которых количество слов равно нулю.
Оставшиеся документы, в основном технический материал, обрабатываются с использованием той же модели granite4 и сохраняются в отдельный индекс OpenSearch:
{
"name": "vectordb_low_quality",
"type": "vectordb",
"config": {
"provider": "opensearch",
"doc_id_column": "doc_id_hash",
"embeddings_column": "embeddings",
"vector_dimension": 2560,
"create_index": "true",
"provider_config": {
"index_name": "low_quality_docs",
"host": "localhost",
"port": 9200,
"username": "${OPENSEARCH_USERNAME}",
"password": "${OPENSEARCH_PASSWORD}",
"use_ssl": false,
"verify_certs": false,
"batch_size": 100,
"engine": "faiss",
"algorithm": "hnsw",
"space_type": "l2"
},
"feature_mappings": [
{"feature_name": "doc_id_hash", "mapped_column_name": "pk"},
{"feature_name": "name", "mapped_column_name": "doc_name"},
{"feature_name": "content", "mapped_column_name": "text"},
{"feature_name": "embeddings", "mapped_column_name": "vector_embeddings"},
{"feature_name": "lang_name", "mapped_column_name": "language"},
{"feature_name": "flesch_reading_ease", "mapped_column_name": "readability_score"},
{"feature_name": "difficult_words", "mapped_column_name": "difficult_words"}
]
},
"depends_on": [
"embeddings_low_quality"
]
}
Поскольку эта ветка пропустила процедуру обогащения данных, для этих документов отсутствует показатель word_count. Вместо этого используется рассчитанное значение difficult_words, что способствует обработке технических запросов.
Два индекса, два стиля поиска
Отдельные индексы для разных уровней читаемости позволяют разным аудиториям выполнять поиск по-разному.
Поиск в центре помощи
Для конечных пользователей запрос направляется в индекс high_quality_docs. В нем сохраняются только легко читаемые руководства (readability_score > 60) с содержательным материалом (word_count > 100):
curl -XGET "http://localhost:9200/high_quality_docs/_search" \
-H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must": [
{
"knn": {
"vector_embeddings": {
"vector": [0.015, -0.023, 0.004],
"k": 10
}
}
}
],
"filter": [
{
"range": {
"readability_score": {
"gt": 60
}
}
},
{
"range": {
"word_count": {
"gt": 100
}
}
}
]
}
}
}'
Поиск по справочным материалам для разработчиков
Для опытных пользователей, ищущих технические характеристики, запрос направлен на low_quality_docs. Система анализирует сложные материалы и сохраняет те участки, в которых много сложных технических терминов (difficult_words > 5):
curl -XGET "http://localhost:9200/low_quality_docs/_search" \
-H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must": [
{
"knn": {
"vector_embeddings": {
"vector": [0.015, -0.023, 0.004],
"k": 10
}
}
}
],
"filter": [
{
"range": {
"difficult_words": {
"gt": 5
}
}
}
]
}
}
}'
Запуск
В репозитории предоставлен скрипт настройки, который создаёт виртуальную среду, устанавливает зависимости, запускает локальные контейнеры OpenSearch и регистрирует модели Ollama.
Шаг 1: настройка среды
Клонируйте репозиторий и запустите скрипт:
git clone https://github.com/IBM/docling-pipelines.git
cd docling-pipelines
./scripts/setup_docling_pipelines_environment.sh
Или установите непосредственно из PyPI:
pip install docling-pipelines
Шаг 2: настройка переменных среды
export OLLAMA_API_KEY=ollama
export OPENSEARCH_USERNAME=admin
export OPENSEARCH_PASSWORD=<your-password>
Шаг 3: запуск процесса
Запустите конвейер из командной строки:
docling-pipelines --flow-file sample_flows/advanced/branching_quality_routing.json
Что выводит запуск
Вывод отслеживает поток, причем каждое преобразование и решение о маршрутизации фиксируются:
$ docling-pipelines --flow-file sample_flows/advanced/branching_quality_routing.json
[INFO] [ingest] Loading files from tests/fixtures/customer_support_docs/
[INFO] [ingest] Ingested 12 files.
[INFO] [extract] Running extract_operator using docling_library...
[INFO] [extract] Extracted text from 12 documents.
[INFO] [detect_language] Running lang_detect...
[INFO] [detect_language] Complete. lang_name assigned.
[INFO] [dedup] Running ededup...
[INFO] [dedup] Removed 2 duplicate documents. 10 documents remaining.
[INFO] [doc_quality] Running doc_quality...
[INFO] [doc_quality] Computed docq_* features for 10 documents.
[INFO] [readability] Running readability...
[INFO] [readability] Computed readability features for 10 documents.
[INFO] [quality_branching] Branching 10 documents...
[INFO] [quality_branching] Routed 6 documents to link 'High Quality Documents' (flesch_reading_ease > 60).
[INFO] [quality_branching] Routed 4 documents to link 'Low Quality Documents' (flesch_reading_ease <= 60).
[INFO] [ml_enrichment_high_quality] Running ml_enrichment on 6 documents...
[INFO] [embeddings_high_quality] Generating embeddings with model 'openai/granite4:latest'...
[INFO] [vectordb_high_quality] Uploaded 6 documents to OpenSearch index 'high_quality_docs'.
[INFO] [sql_filter_low_quality] Running sql_filter on 4 documents...
[INFO] [sql_filter_low_quality] Filtered out 0 empty documents. 4 documents remaining.
[INFO] [embeddings_low_quality] Generating embeddings with model 'openai/granite4:latest'...
[INFO] [vectordb_low_quality] Uploaded 4 documents to OpenSearch index 'low_quality_docs'.
[INFO] Flow execution finished successfully in 4.23s.
Перенос показателей качества и правил маршрутизации из пользовательского кода на Python в формат декларативной конфигурации позволяет ускорить обработку данных и облегчить их обслуживание. Стоимость обработки зависит от значения документа, а приложение RAG получает чистые, адаптированные индексы для каждой аудитории.
Идея разветвления работает с любыми показателями, рассчитанными на ранних этапах процесса. Можно изменить критерии маршрутизации в зависимости от языка, уровня риска персональных данных или метки пользовательского классификатора. Поскольку логика обработки остается отдельной от кода оркестрации, команды могут расширять процесс обработки без увеличения нагрузки на его обслуживание.