W RAG konwertuj PDF na Markdown, gdy chcesz mieć fragmenty zgodne z sekcjami dokumentu: każdy nagłówek staje się punktem podziału, a nagłówki nad fragmentem podróżują z nim jako metadane. Przed indeksowaniem sprawdź nagłówki, tabele i tekst z OCR, a potem przetestuj wyszukiwanie na pytaniach, na które znasz już odpowiedzi.
Ten poradnik jest dla programistów budujących wyszukiwanie w prywatnych dokumentach: politykach, podręcznikach, umowach, badaniach. Zakłada, że masz już na myśli model embeddingów i bazę wektorową.
Dlaczego nagłówki dają lepsze fragmenty
Dzielnik o stałym rozmiarze tnie tekst na kawałki o zadanej długości, bez względu na to, gdzie wypadnie cięcie. Fragment może zacząć się w środku sekcji i stracić nagłówek, który mówi, czego dotyczy. „Żądania są ograniczone do 100 na minutę” znaczy co innego w sekcji „Sandbox”, a co innego w „Production”. Bez nagłówka ani wyszukiwarka, ani model nie rozpozna, o którą chodzi.
Markdown zachowuje strukturę dokumentu jako wiersze zaczynające się od #, ## i ###. Dzielnik oparty na nagłówkach tnie w tych wierszach, więc każdy fragment obejmuje jedną sekcję, i zapisuje nagłówki nad fragmentem: jego ścieżkę sekcji. Poradnik LangChain o dzieleniu Markdown opisuje tę samą ideę: trzymaj tekst razem z nagłówkami, do których należy.
Sekcje różnią się długością, więc długa sekcja nadal wymaga drugiego podziału według rozmiaru. Ścieżka sekcji dotyczy każdego jej kawałka.
Konwersja PDF i sprawdzenie nagłówków
W MD Factory dodaj folder z plikami PDF. Każdy plik Markdown jest zapisywany obok swojego PDF pod tą samą nazwą, z obrazami w folderze obok. Zeskanowane pliki PDF przechodzą przez OCR na Twoim komputerze. Pliki Word i inne formaty w folderze są konwertowane tak samo.
Zanim napiszesz jakikolwiek kod dzielący, otwórz kilka wyników:
- Upewnij się, że rozdziały, sekcje i podsekcje wychodzą jako #, ## i ###. PDF często oznacza nagłówek tylko większą lub pogrubioną czcionką, więc każdy konwerter musi zgadywać, gdzie zaczynają się sekcje i jak się zagnieżdżają. Popraw poziomy tam, gdzie są błędne.
- W plikach Word nagłówki są przenoszone, gdy autor użył stylów nagłówków Word. Tekst, który jest tylko pogrubiony lub duży, pozostaje zwykłym akapitem.
- Poszukaj nagłówków i stopek bieżących oraz numerów stron w środku tekstu.
- Kliknij Wymaga sprawdzenia obok pliku, aby zobaczyć, co sprawdzić. W przypadku PDF lista może zawierać strony, na których OCR nie odczytał części tekstu albo nie znalazł tekstu wcale.
Popraw problemy w pliku Markdown, zanim go zaindeksujesz. Każdy fragment je odziedziczy.
Dzielenie według nagłówków w kodzie
MarkdownHeaderTextSplitter z LangChain dzieli według wymienionych poziomów nagłówków i wkłada każdy nagłówek do metadanych fragmentu. Drugi dzielnik ogranicza rozmiar w obrębie każdej sekcji:
from pathlib import Path
from langchain_text_splitters import (
MarkdownHeaderTextSplitter,
RecursiveCharacterTextSplitter,
)
corpus = Path("corpus")
by_heading = MarkdownHeaderTextSplitter(
[("#", "h1"), ("##", "h2"), ("###", "h3")], strip_headers=False
)
by_size = RecursiveCharacterTextSplitter(chunk_size=1500, chunk_overlap=150)
chunks = []
for path in sorted(corpus.rglob("*.md")):
sections = by_heading.split_text(path.read_text(encoding="utf-8"))
for chunk in by_size.split_documents(sections):
chunk.metadata["source"] = path.relative_to(corpus).as_posix()
chunk.metadata["section"] = " > ".join(
chunk.metadata[h] for h in ("h1", "h2", "h3") if h in chunk.metadata
)
chunks.append(chunk)
Rozmiar fragmentu i nakładanie się są tu punktem wyjścia; dostosuj je do swojego modelu embeddingów. Aby zachować wiersze nagłówków w tekście fragmentu, tak żeby osadzany tekst też wymieniał swoją sekcję, przykład ustawia strip_headers=False.
MarkdownNodeParser z LlamaIndex robi ten sam podział według nagłówków. Gdy wczytujesz pliki przez SimpleDirectoryReader, każdy węzeł niesie też nazwę pliku:
from llama_index.core import SimpleDirectoryReader
from llama_index.core.node_parser import MarkdownNodeParser
documents = SimpleDirectoryReader(
"corpus", recursive=True, required_exts=[".md"]
).load_data()
nodes = MarkdownNodeParser().get_nodes_from_documents(documents)
print(nodes[0].metadata["file_name"], nodes[0].metadata["header_path"])
header_path wymienia nagłówki nad węzłem, na przykład /Employee handbook/Expenses/; własny nagłówek węzła jest pierwszym wierszem jego tekstu.
Źródło przy każdym fragmencie
Przy każdym fragmencie zapisz co najmniej trzy rzeczy:
- Plik. Plik Markdown ma tę samą nazwę co jego PDF, więc fragment z handbook.md prowadzi do handbook.pdf. Zapisz ścieżkę względną wobec folderu zbioru, bo dwa pliki o nazwie terms.md w różnych folderach to różne dokumenty.
- Ścieżkę sekcji, aby odpowiedź mogła wskazać użytą sekcję, a czytelnik mógł przejść prosto do fragmentu.
- Wydanie lub datę. Gdy polityka się zmienia, usuwasz fragmenty starego pliku i indeksujesz nowe, zamiast serwować obie wersje.
Jeśli ten sam indeks będą odpytywać osoby o różnych uprawnieniach dostępu, zapisz też, kto może czytać każdy dokument, i filtruj po tym w czasie zapytania. Wyszukiwarka zwraca każdy fragment pasujący do pytania.
Markdown tworzony przez MD Factory nie ma numerów stron. Jeśli odpowiedzi muszą wskazywać strony, sprawdź fragment w oryginalnym PDF.
Ostrożnie z tabelami i tekstem z OCR
Tabela Markdown ma wiersz nagłówka tylko raz, na górze. Gdy tabela jest dłuższa niż rozmiar fragmentu, dzielnik rozmiaru ją tnie, a późniejsze kawałki trafiają bez nazw kolumn. W naszym teście z LangChain (langchain-text-splitters 1.1.3) tabela o 40 wierszach podzielona na fragmenty po 300 znaków dała cztery kawałki i tylko pierwszy zawierał wiersz nagłówka.
Są dwa sposoby, aby to naprawić: trzymaj każdą tabelę w jednym fragmencie, nawet jeśli jest dłuższa od pozostałych, albo powtarzaj wiersz nagłówka na początku każdego kawałka.
W plikach Word i Excel MD Factory oznacza dokument statusem Wymaga sprawdzenia, gdy scalone lub złożone komórki zostały uproszczone. Tabele PDF nie dostają takiej flagi, więc porównaj kilka z nich z oryginałem, zwłaszcza tabele ze scalonymi nagłówkami.
Tekst z OCR wymaga własnej kontroli. Błędnie odczytane liczby, nazwy i kody trafiają do odpowiedzi bez zmian, a nic we fragmencie nie wygląda źle. Porównaj liczby w kilku fragmentach ze skanem i pomiń oznaczone strony w indeksie, dopóki ich nie poprawisz. Na co zwracać uwagę, omawia nasz poradnik sprawdzania OCR w zeskanowanych PDF.
Testowanie wyszukiwania na prawdziwych pytaniach
Zapisz pytania, które ludzie rzeczywiście będą zadawać, a przy każdym plik i sekcję, w której jest odpowiedź. Zacznij od kilkunastu lub dwóch tuzinów.
- Przepuść każde pytanie przez swoją wyszukiwarkę i obejrzyj najlepsze wyniki.
- Zanotuj, czy właściwa sekcja jest wśród nich.
- Przy każdym chybieniu porównaj zwrócone fragmenty z tym, który powinien się znaleźć. Typowe przyczyny: zły poziom nagłówka, tabela odcięta od nagłówka, błąd OCR w kluczowym terminie albo sekcja tak długa, że odpowiedź w niej ginie.
- Zmień jedną rzecz, na przykład rozmiar fragmentu, poziomy nagłówków albo sposób dzielenia tabel, i uruchom te same pytania ponownie.
Trzymaj pytania w pliku obok zbioru i uruchamiaj je ponownie za każdym razem, gdy zmieniają się dokumenty lub kod dzielący.
Kiedy użyć MD Factory, a kiedy biblioteki
MD Factory to aplikacja desktopowa dla Windows. Pasuje do prywatnego zbioru, który chcesz przekonwertować na własnym komputerze i sprawdzić na oko: dodaj folder, obejrzyj statusy, otwórz pliki wymagające sprawdzenia, popraw je, a potem wskaż folder swojemu skryptowi indeksującemu.
MD Factory trzyma dokumenty na Twoim komputerze podczas konwersji. Jeśli potem osadzasz fragmenty przez hostowane API embeddingów albo wysyłasz je do hostowanego modelu, ich tekst trafia do tego dostawcy. Jeśli dokumenty nie mogą opuścić Twojej sieci, użyj modelu lokalnego.
Jeśli dokumenty napływają cały czas, działają na serwerze albo muszą być konwertowane bez nadzoru człowieka, użyj w kodzie biblioteki do konwersji. Opcje opisuje nasze porównanie konwerterów PDF do Markdown.