💡 Plně funkční příklad dostupný na GitHubu:
document-to-markdown-rag-chunks-java
Problém s oddělením pojistných událostí: Proč ingestování RAG selhává u reálných souborů
On‑premise pipeline pro ingestování RAG je vrstva pro parsování dokumentů, která převádí soubory, jež tým již má, na prohledávatelný, citovatelný text, aniž by je posílala třetí straně. Pojišťovací operace ukazují, proč je to důležité. Správce pojistných událostí se zeptá interního asistenta „co řekl likvidátor o vodních škodách na této pojistce?“, a odpověď se nachází v e‑mailu z minulého března, uvnitř přiložené PDF zprávy, pod nadpisem „Findings“. Asistent ji může najít jen tehdy, pokud ingestování přečte přílohu, zachová nadpis a zapamatuje si, ke kterému e‑mailu zpráva patří.
Většina ingestovacích úloh udělá jednu z těchto tří věcí špatně. Extraktor prostého textu vrátí tělo e‑mailu a ignoruje přílohu. Pevný rozdělovač na 512 tokenů rozstřihne zprávu uprostřed „Findings“. Hostovaný parser vyřeší obojí, ale až poté, co byl soubor pojistné události s osobními a zdravotními údaji nahrán na servery někoho jiného a účtováno za každou stránku.
Co tým potřebuje, je úzké a konkrétní: číst soubory Word, PDF, EML a ZIP uvnitř Java služby, která data již vlastní, zachovat jejich strukturu jako Markdown, rozdělovat podle nadpisů autora a připojit zdroj a rodiče ke každému chunku.
Market snapshot: průvodci z roku 2026 o „document parsing for RAG“, které jsme přezkoumali, porovnávají open‑source Python parsery s cloudovými API účtovanými za stránku. Java knihovna běžící na vašem vlastním hardware chybí ve většině těchto diskusí.
Proč RAG pipeline potřebují Markdown místo prostého textu?
Markdown zachovává části dokumentu, které říkají retrieveru, kde jedna myšlenka končí a další začíná. Nadpisy se mění na řádky s #, tabulky zůstávají jako řádky a sloupce a seznamy si uchovávají své položky. Chunker může rozdělovat podle těchto nadpisů a LLM čtoucí chunk vidí stejný obrys, jaký by viděl člověk. Prostý text tento obrys zahodí a velikost zůstane jediným signálem pro rozdělení.
Proč obecná řešení zde nefungují
Týmy obvykle před vytvořením správné ingestovací vrstvy vyzkouší jednu ze tří věcí:
- Plain-text extraction plus a token splitter: rychlé nastavení, ale chunky míchají sekce a ztrácejí nadpis, který byste citovali.
- A cloud parsing API: dobrá struktura, ale soubory opustí síť a účet roste s každou stránkou, která je znovu indexována.
- A home-grown parser per format: jedna knihovna pro DOCX, další pro PDF, MIME parser pro e‑mail a
java.util.zippro archivy, každá se svými okrajovými případy.
GroupDocs.Parser for Java pokrývá všechny čtyři formáty pomocí jedné třídy Parser a běží v‑processu.
Řešení: GroupDocs.Parser v workflow ingestování pojistných událostí
GroupDocs.Parser for Java načte dokument a může vrátit jeho text jako Markdown pomocí getFormattedText. E‑maily a archivy jsou vystaveny jako kontejnery, jejichž položky se otevírají jako nové instance Parser, takže PDF přiložené k e‑mailu je zpracováno přesně jako PDF na disku. Ukázkový projekt to zabalí do šesti statických metod a výsledek zapíše jako JSON Lines, který může načíst libovolná embedding úloha nebo vektorové úložiště. Není zapojen žádný RAG framework; výstupní formát je integrační bod.
Abyste mohli sledovat postup, potřebujete:
- JDK 11 nebo novější pro dodaný
pom.xml(kód samotný používá funkce Java 8) - GroupDocs.Parser for Java 26.10 (získat dočasnou licenci)
- Několik souborů DOCX, PDF, EML nebo ZIP
Projektový pom.xml deklaruje repozitář https://releases.groupdocs.com/java/repo/ a jedinou závislost, takže sestavení je jeden příkaz:
mvn compile exec:java
Implementace ingestovací vrstvy krok za krokem
Níže uvedené kroky sledují jeden soubor pojistné události od příchozí poštu až po index. Každý krok mapuje na jednu nebo dvě metody v RagChunksDemo.java. Zachovali jsme je jako čisté statické metody úmyslně, abyste je mohli snadno přenést do existujícího workeru bez nutnosti adoptovat zbytek.
Krok 1 – Otevřít každý soubor a nasměrovat jej
Každý nahraný soubor získá svůj vlastní Parser. Název souboru se stane ID zdroje a soubor na nejvyšší úrovni nemá rodiče.
// One call per uploaded claim document
String sourceId = new File(path).getName();
try (Parser parser = new Parser(path)) {
return chunkParser(parser, sourceId, null, 0); // null parent: top-level file
} catch (UnsupportedDocumentFormatException ex) {
// An unknown format is logged and skipped, the batch keeps going
System.out.println("skipped (unsupported format): " + sourceId);
return new ArrayList<>();
}
chunkParser pak extrahuje tělo, rozdělí jej a sestoupí do příloh, dokud je hloubka menší než 2.
Krok 2 – Požádat o Markdown, přijmout prostý text
Extrahovací krok zjišťuje, co formát podporuje, místo aby pevně kódoval seznam přípon.
if (parser.getFeatures().isFormattedText()) {
// Headings, lists and tables survive as Markdown
FormattedTextOptions options =
new FormattedTextOptions(FormattedTextMode.Markdown);
try (TextReader reader = parser.getFormattedText(options)) {
if (reader != null) {
return new ExtractedText("markdown", reader.readToEnd());
}
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
try (TextReader reader = parser.getText()) {
if (reader != null) { // fallback for formats without formatted text
return new ExtractedText("plain", reader.readToEnd());
}
} catch (IOException ex) {
throw new RuntimeException(ex);
}
return new ExtractedText("none", ""); // e.g. a photo with no OCR
Proč jsou tato nastavení důležitá pro data pojistných událostí:
FormattedTextMode.Markdown: nadpis likvidátora „Findings“ dorazí do chunkeru jako## Findings, takže může sloužit jako bod rozdělení a citace.isFormattedText(): ve vzorovém běhu se DOCX a e‑mail vrátí jako Markdown, zatímco PDF přejde na prostý text, a poleformatzaznamená, co se stalo.- Výsledek
none: fotografie pojistné události uvnitř ZIPu nevytváří žádné chunky bez OCR, což je poctivé a snadno monitorovatelné.
Krok 3 – Rozdělit podle nadpisů likvidátora
Chunker udržuje zásobník aktuálního nadpisu na každé ze šesti úrovní Markdownu. Nový nadpis uzavře běžící sekci a resetuje vše pod svou úrovní.
for (String line : markdown.split("\\r?\\n", -1)) {
Matcher heading = HEADING.matcher(line); // "#" to "######" + title
if (heading.matches()) {
// Close the previous section under the old path
addSection(chunks, section.toString(), sourceId, parentId, path, format);
section.setLength(0);
int level = heading.group(1).length();
stack[level - 1] = heading.group(2).trim();
for (int i = level; i < stack.length; i++) {
stack[i] = null; // forget deeper headings
}
path = joinPath(stack); // "Heading 1 > Heading 1.2"
}
section.append(line).append('\n');
}
Sekce delší než 1500 znaků jsou ořezány na koncích odstavců, takže dlouhá sekce findings se stane několika chunky, které sdílejí stejnou cestu nadpisu. U přiloženého Word souboru to vytváří cesty jako Heading 1 > Heading 1.2 > Heading 1.2.1.
Krok 4 – Sledovat přílohy a zapisovat JSON Lines
Toto je krok, na který si oddělení pojistných událostí nejvíce zakládá. getContainer() vypisuje přílohy e‑mailů a položky ZIP; každá položka se otevře jako podřízený dokument a zdědí ID rodiče.
Iterable<ContainerItem> items = parser.getContainer();
if (items == null) {
return chunks; // not an email or archive
}
for (ContainerItem item : items) {
// "claim-0412.eml/report.pdf" style ids keep the lineage readable
String childId = sourceId + "/" + item.getFilePath();
try (Parser child = item.openParser()) {
chunks.addAll(chunkParser(child, childId, sourceId, depth + 1));
} catch (UnsupportedDocumentFormatException ex) {
System.out.println("skipped (unsupported format): " + childId);
}
}
writeJsonl pak zapíše jeden řádek UTF‑8 na chunk s poli id, source, parent, heading_path, format a text. Se čtyřmi vzorovými vstupy běh vytvoří 22 řádků. Chunk z přílohy vzorového e‑mailu je email-with-attachment.eml/attachment.txt#0 a jeho parent je email-with-attachment.eml. Tento pár umožní asistentovi říci „z reportu přiloženého k e‑mailu ze dne 14. března“ místo „z něčeho v archivu“.
Reálný workflow: Týdenní přeindexování pojistných událostí
Představte si tým operací pojistných událostí, který každé pátky exportuje uzavřené poštovní schránky jako EML soubory a ZIP balíčky podpůrných dokumentů. Java worker již přesouvá tyto exporty do úložiště dokumentů. Přidání ingestování znamená volat chunkDocument pro každý soubor a připojit výstup JSON Lines do fronty pro embedding.
Krok 4 je to, co udržuje rezervační zprávu připojenou k e‑mailu, který ji doručil. Krok 3 znamená, že když asistent cituje „Findings“, citace uvádí soubor a nadpis místo pouhého čísla chunku. Moje pravidlo pro tento typ indexu: pokud chunk nelze citovat zpět na soubor a nadpis, neměl by být v indexu vůbec.
Obchodní dopad: Co se tím mění
| Aspekt | Typické nastavení před | S pipeline GroupDocs.Parser |
|---|---|---|
| Kde jsou soubory parsovány | Koncový bod třetí strany | Uvnitř vaší vlastní JVM |
| Model nákladů na parsování | Za stránku, při každém přeindexování | Žádný poplatek za stránku v cloudu |
| Hranice chunků | Pevná okna tokenů | Nadpisy autora, limit 1500 znaků |
| Přílohy e‑mailů | Často přeskočeny nebo zploštěny | Parsovány jako podřízené dokumenty |
| Data citace | Jméno souboru nejvýše | source, parent a heading_path |
| Runtime závislosti | SDK plus síťový přístup | Jeden Maven artefakt |
Kombinovaný efekt je index, který může compliance tým schválit, a asistent, jehož odpovědi ukazují na skutečný dokument a sekci.
Další pojišťovací scénáře, kde GroupDocs.Parser zapadá
Jakmile existuje ingestovací vrstva, stejný kód slouží i dalším úlohám s velkým objemem dokumentů:
- Policy wording search: Wordové pojistné dokumenty rozdělené podle nadpisů klauzulí, takže odpovědi citují přesnou sekci.
- Broker submissions: ZIP nahrávky s mixem PDF a tabulek indexované položka po položce, každá svázaná se zasláním.
- Complaint handling: zákaznické e‑maily a jejich přílohy indexované společně, přičemž e‑mail je rodičem každého chunku přílohy.
Začínáme ve vašem prostředí pojistných událostí
Naklonujte ukázku, spusťte ji proti několika vlastním exportovaným e‑mailům a otevřete output/chunks.jsonl, abyste zkontrolovali cesty nadpisů a ID rodičů, než jej zapojíte do své embedding úlohy. Poznámky k architektuře krok za krokem jsou v případě použití dokumentace pro tento pipeline, a referenci výstupu Markdown uvádí, které formátování režim zachovává.