Przyjmowanie danych RAG z uwzględnieniem jakości: kierowanie dokumentami według stopnia czytelności
Deklaratywny przepływ docling-pipelines, który ocenia poziom języka, liczba duplikatów oraz wskaźnik czytelności Flescha, a następnie wysyła dokumenty do dwóch indeksów OpenSearch w celu wyszukiwania przez klientów i programistów.
Źródła: Architecting a Multi-Tier RAG Pipeline: Quality-Aware Document Routing with docling-pipelines
Przenoszenie bazy wiedzy obsługi klienta do wyszukiwarki opartej na AI zazwyczaj kończy się niepowodzeniem w tym samym miejscu: każdy plik jest traktowany tak samo. Realistyczny korpus to mieszanka różnych elementów – zawiera uporządkowane FAQ, ustrukturyzowane przewodniki produktowe, obszerne podręczniki dla programistów, powtarzające się teksty prawnicze oraz kilka uszkodzonych plików, w których w ogóle nie ma czytelnych słów.
Gdy wszystko to umieści się w jednym systemie RAG do rozmów, jakość wyszukiwania spada. Klient, który chce prostej instrukcji, otrzymuje fragmenty podręcznika inżynieryjnego. Wysyłanie bezwartościowych tekstów lub logów systemowych do drogiego modelu w celu stworzenia streszczeń lub tłumaczeń to również marnowanie pieniędzy.
Rozwiązaniem jest kierowanie dokumentów według ich jakości. Projekt dwuwarstwowy polega na przeanalizowaniu każdego pliku, usunięciu pustych stron, ocenie czytelności, a następnie podziale korpusu na odpowiednie grupy.
- Poziom 1, wyszukiwanie przez konsumentów. Bardzo łatwe do odczytania dokumenty otrzymują metadane strukturalne, takie jak liczba słów, aby umożliwić wyszukiwanie w formie rozmowy skierowane do klientów.
- Poziom 2, wyszukiwanie dla programistów i jako materiał referencyjny. Zawarte w dokumentach materiały techniczne trafiają do osobnego indeksu, aby umożliwić odpowiadanie na zaawansowane pytania techniczne.
docling-pipelines sprawia, że to staje się praktyczne. Jest to framework o otwartym kodzie źródłowym, licencjonowany na zasadach MIT i wspierany przez IBM, który buduje oraz uruchamia grafy przetwarzania dokumentów bezpośrednio na podstawie konfiguracji w formacie JSON. Nie jest potrzebny żaden specjalny kod w Pythonie, ponieważ operatory przekazują sobie dokumenty jako tabele Apache Arrow w pamięci, dzięki czemu pośrednie transformacje i sprawdzania routingu kosztują prawie nic pod względem zasobów CPU i pamięci.
Kształt pipeline’a
Wszystko znajduje się w jednym pliku JSON opisującym graf skierowany acykliczny:
ingestodczytuje pliki PDF, TXT, DOCX i HTML z dysku.extractwydobywa tekst ustrukturyzowany za pomocą biblioteki Docling.detect_languageidentyfikuje język każdego dokumentu.dedupwykorzystuje hashe treści, aby usunąć duplikatowe wiersze.doc_qualityoblicza metryki strukturalne, takie jak całkowita liczba słów.readabilitywyznacza wyniki mierzące złożoność tekstu.quality_branchingkieruje proces na podstawie wyniku Flesch Reading Ease: wartości powyżej 60 trafiają na ścieżkę wysokiej jakości, a 60 lub mniej – na ścieżkę niskiej jakości.
ml_enrichment_high_quality, embeddings_high_quality oraz vectordb_high_quality. Dodaje cechy językowe oparte na sztucznej inteligencji, tworzy wektory embeddingów i zapisuje je do indeksu high_quality_docs.sql_filter_low_quality, embeddings_low_quality oraz vectordb_low_quality. Usuwa puste wiersze, embeduje to, co pozostało, i zapisuje wyniki do low_quality_docs.Profilowanie przed podziałem
Każdy dokument wymaga profilu, zanim zostanie skierowany dalej. Po tym, jak ingest odczyta dane, a extract przekształci strukturę w tekst, każdy kolejny operator działa jako transformator: sprawdza tabelę i dodaje nowe kolumny do tej samej tabeli Arrow w pamięci. Najpierw przeprowadzane są tanie operacje wykonywane wyłącznie na procesorze: identyfikacja języka, wykrywanie duplikatów oraz ocena złożoności tekstu.
Identyfikacja języka
Późniejsze modele ML oraz formuły mierzące czytelność wymagają informacji o języku, dlatego najpierw przeprowadzana jest jego identyfikacja.
{
"name": "detect_language",
"type": "lang_detect",
"config": {
"doc_column": "content"
},
"depends_on": [
"extract"
]
}
Operator czyta kolumnę content i dodaje lang_name (kod ISO 639-1) oraz lang_score. Umieszczenie ich tuż po ekstrakcji oznacza, że każdy krok wymagający uwzględnienia języka może korzystać z ich wyników.
Deduplikacja
Dokumenty duplikowane marnują zasoby obliczeniowe potrzebne do embedowania i zakłócają wyniki wyszukiwania.
{
"name": "dedup",
"type": "ededup",
"config": {
"doc_column": "content",
"doc_id_column": "doc_id_hash"
},
"depends_on": [
"detect_language"
]
}
Odnosi się do hasza zawartości przechowywanego w doc_id_hash oraz do surowego tekstu w content, a następnie usuwa powtarzające się wiersze z tabeli. Robienie tego wcześnie pozwala uniknąć niepotrzebnej pracy na późniejszych etapach.
Jakość dokumentu
Bazowe sprawdzenia strukturalne wykrywają puste strony oraz nietypową kompozycję.
{
"name": "doc_quality",
"type": "doc_quality",
"config": {
"doc_column": "content",
"doc_content_column": "content",
"text_lang": "en"
},
"depends_on": [
"dedup"
]
}
To dodaje 11 kolumn strukturalnych, wszystkie z prefiksem docq_. Najważniejsza z nich to docq_total_words, która przedstawia łączną liczbę słów w każdym dokumencie.
Czytelność
Wzory obliczające czytelność oceniają trudność na podstawie długości słów, sylab oraz struktury zdań.
{
"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"
]
}
Tablica readability_score_list wskazuje operatorowi, jakie metryki należy obliczyć i dodać jako kolumny. Ustalanie tras polega na ocenie Flesch Reading Ease, która wynosi od 0 do 100:
- Ponad 60 oznacza prosty, dostępny język odpowiedni dla ogólnej publiczności centrum pomocy.
- 60 lub mniej oznacza złożoną, techniczną prozę wymagającą wiedzy specjalistycznej.
Krok rozgałęziania
Operator rozgałęziania dzieli zbiór danych Arrow na oddzielne ścieżki przetwarzania.
{
"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"
]
}
Ukierunkowanie jest kontrolowane przez deklaratywny blok criteria_json zawierający cztery pola:
variableokreśla kolumnę do sprawdzenia, na przykładflesch_reading_ease.operatorwskazuje operację porównania, taką jak>lub<=.valueto próg wartości, w tym przypadku60.logical_operatorłączy kilka warunków za pomocąANDlubOR.
Gdy regułą jest flesch_reading_ease > 60, silnik ocenia warunki bezpośrednio, dzieli tabelę w pamięci i uruchamia każdą z następnych ścieżek.
Szeroka jakość: wzbogacanie, włączanie, indeksowanie
Dokumenty przekazywane do ścieżki high_quality_branch najpierw przechodzą proces wzbogacania za pomocą algorytmów ML językowych.
{
"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 analizuje tekst za pomocą modeli rozumiejących język i tworzy metadane takie jak liczba zdań, właściwości sylab oraz listy rzeczowników, wszystko to w celu lepszego wyszukiwania. Zależy od kolumny lang_name z etapu wykrywania. Ponieważ jest to część procesu wymagająca dużych zasobów CPU, działa tylko na tekstach czytelnych. Ustawienie output_column_prefix dodaje przedrostek ml_ do nowych kolumn, w tym do ml_num_words.
Następnie tekst przekształca się w wektory o wymiarze 2560. Modelem jest openai/granite4:latest, dostarczany przez lokalny serwer Ollama za pośrednictwem 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"
]
}
Ostatnią krokiem jest indeksowanie dokumentów w OpenSearch, przy czym feature_mappings łączą kolumny Arrow z polami 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"
]
}
Szlak niskiej jakości: filtr, wgranie, indeksowanie
Dokumenty znajdujące się na low_quality_branch omijają kosztowny krok ml_enrichment. Niektóre z nich po ekstrakcji mogą nie zawierać żadnych czytelnych słów, na przykład uszkodzone pliki lub puste skany, a wgranie pustej strony tylko dodaje szum do przestrzeni wektorowej. Filtr eliminuje je:
{
"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"
]
}
Filtr sprawdza kolumnę docq_total_words z tabeli doc_quality i odrzuca wszystkie dokumenty zawierające zero słów.
Dokumenty, które przetrwają, głównie materiały techniczne, są włączane do tego samego modelu granite4 i zapisywane w oddzielnym indeksie 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"
]
}
Ponieważ ta gałąź pominęła proces wzbogacania, dla tych dokumentów nie istnieje word_count. Zamiast tego wykorzystywana jest obliczona liczba difficult_words, co pomaga przy zapytaniach technicznych.
Dwa indeksy, dwa style zapytań
Odrębne indeksy dla poszczególnych poziomów czytelności umożliwiają różne sposoby wyszukiwania przez odbiorców.
Wyszukiwanie w centrum pomocy
Dla użytkowników końcowych zapytanie trafia do high_quality_docs. Zawiera ono jedynie łatwe do przeczytania poradniki (readability_score > 60) o rzeczywistej treści (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
}
}
}
]
}
}
}'
Wyszukiwanie referencji dla programistów
Dla zaawansowanych użytkowników szukających specyfikacji zapytanie skierowane jest na low_quality_docs. Analizuje ono złożone materiały i zachowuje fragmenty zawierające wiele trudnych terminów technicznych (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
}
}
}
]
}
}
}'
Rozruch
W repozytorium znajduje się skrypt konfiguracyjny, który tworzy środowisko wirtualne, instaluje zależności, uruchamia lokalne kontenery OpenSearch oraz rejestruje modele Ollamy.
Krok 1: konfiguracja środowiska
Klonuj repozytorium i uruchom skrypt:
git clone https://github.com/IBM/docling-pipelines.git
cd docling-pipelines
./scripts/setup_docling_pipelines_environment.sh
Lub zainstaluj bezpośrednio z PyPI:
pip install docling-pipelines
Krok 2: ustawienie zmiennych środowiskowych
export OLLAMA_API_KEY=ollama
export OPENSEARCH_USERNAME=admin
export OPENSEARCH_PASSWORD=<your-password>
Krok 3: uruchomienie procesu
Zainicjuj pipeline z linii poleceń:
docling-pipelines --flow-file sample_flows/advanced/branching_quality_routing.json
Co wyświetla wykonywanie
Wynik odzwierciedla przepływ, przy czym każda transformacja oraz decyzja o kierowaniu są rejestrowane:
$ 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.
Przeniesienie metryk jakości i reguł routingu z niestandardowego kodu Python na deklaratywną konfigurację sprawia, że proces integracji jest szybki i łatwy w utrzymaniu. Koszt przetwarzania zależy od wartości dokumentu, a aplikacja RAG otrzymuje czyste, dostosowane indeksy dla każdej grupy odbiorców.
Koncepcja rozgałęziania działa z dowolną metryką obliczoną wcześniej w procesie. Można zmienić kryteria routingu na język, wynik oceny ryzyka PII lub niestandardową etykietę klasyfikatora. Ponieważ logika pipeline jest oddzielona od kodu orkiestracji, zespoły mogą rozwijać pipeline bez zwiększania obciążenia związanego z jego utrzymaniem.
Literatura pokrewna
- Docling Pipelines: Budowanie rurociągów danych RAG o poziomie enterprise — Jak docling-pipelines zastępuje kruche skrypty łączące elementy komponentami budulcowymi oraz strumieniami JSON: pobieranie danych, wydobywanie informacji, kontrola jakości, tworzenie embeddingów oraz przechowywanie wektorowe.
- Budowanie systemu RAG multi-tenant na Amazon S3 Vectors: rurociąg przetwarzania — Projekt indeksu dostosowanego do poszczególnych użytkowników w S3 Vectors: uprawnienia STS dostosowane do zakresu użytkownika, usuwanie danych na podstawie kluczy, audyt CloudTrail oraz ograniczenia izolacji nadal obecne w kodzie aplikacji.