💡 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.zip pro 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 pole format zaznamená, 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á.

Zdroje