Головна / Статті

Інтеграція RAG із урахуванням якості: маршрутизація документів за рівнем читабельності

Декларативний потік обробки документів, який оцінює рівень мовності, кількість дублікатів та рівень читабельності за шкалою Флеша, а потім надсилає документи до двох індексів OpenSearch для пошуку серед клієнтів та розробників.

Видавець: REACTAPP.TOP8 хв читання1893 слів

Джерела: Architecting a Multi-Tier RAG Pipeline: Quality-Aware Document Routing with docling-pipelines

Перенесення бази знань служби підтримки клієнтів у систему пошуку на основі ШІ зазвичай закінчується невдачею через одну й ту саму проблему: кожен файл обробляється однаково. Реалістичний корпус даних — це суміш різних елементів: організовані часто запитувані питання, структуровані посібники з продуктами, детальні посібники для розробників, дубльований юридичний текст та кілька пошкоджених файлів, у яких зовсім немає читабельних слів.

Якщо всі ці елементи об’єднати в одну систему типу RAG для розмовного формату, якість пошуку погіршується. Клієнт, який хоче отримати просту інструкцію, отримує фрагменти інженерного посібника. Надсилання непотрібного тексту чи журналів системи до дорогої моделі для стиснення чи перекладу — це також марна трата грошей.

Рішенням є сортування документів за їхньою якістю. Дворівнева схема передбачає перегляд кожного файлу, видалення порожніх сторінок, оцінку читабельності, а потім розділення корпусу даних на категорії.

  1. Рівень 1, пошук для споживачів. Документи, які легко читаються, отримують структурні метадані, такі як кількість слів, щоб підтримувати пошук у форматі розмови для клієнтів.
  2. Рівень 2, пошук для розробників та довідкові пошуки. Інтенсивні технічні матеріали потрапляють у власний індекс для складних технічних запитань.

docling-pipelines робить це практичним. Це фреймворк з відкритим кодом під ліцензією MIT, підтримуваний IBM, який створює та запускає графи обробки документів безпосередньо з конфігурації у форматі JSON. Не потрібен власний Python-код, оскільки оператори передають документи один одному у вигляді таблиць Apache Arrow у оперативній пам’яті, тож проміжні перетворення та перевірки маршрутизації майже не впливають на ресурси CPU та пам’яті.

Форма конвеєра

Усе знаходиться в одному файлі формату JSON, який описує орієнтований ациклічний граф:

  1. ingest читає файли PDF, TXT, DOCX та HTML з диска.
  2. extract витягує структурований текст за допомогою бібліотеки Docling.
  3. detect_language визначає мову кожного документа.
  4. dedup використовує хеші контенту для видалення дублікатних записів.
  5. doc_quality обчислює структурні показники, такі як загальна кількість слів.
  6. readability розраховує оцінки складності тексту.
  7. 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 вказує оператору, які показники необхідно обчислити та додати як стовпці. Маршрутизація ґрунтується на оцінці легкості читання за шкалою Flesch, яка коливається від 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, отриманого на етапі виявлення мови. Оскільки це складна для обробки на CPU частина процесу, вона виконується лише з текстом, придатним для читання. Параметр 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 отримує чисті, налаштовані індекси для кожної аудиторії.

    Ідея розгалуження працює з будь-яким показником, обчисленим раніше у процесі. Можна змінити критерії маршрутизації за мовою, рівнем ризику персональних даних чи міткою власного класифікатора. Оскільки логіка потоку залишається окремою від коду оркестрації, команди можуть розширювати потік без збільшення обсягу роботи з підтримкою.