💡 Contoh lengkap yang berfungsi tersedia di GitHub:
document-to-markdown-rag-chunks-java
Masalah Meja Klaim: Mengapa Ingesti RAG Gagal pada File Nyata
Sebuah pipeline ingesti RAG yang dijalankan di tempat adalah lapisan parsing dokumen yang mengubah file yang sudah dimiliki tim menjadi teks yang dapat dicari dan dikutip tanpa mengirimnya ke pihak ketiga. Operasi asuransi menunjukkan mengapa hal ini penting. Seorang penangan klaim menanyakan kepada asisten internal “apa yang dikatakan penilai tentang kerusakan air pada polis ini?”, dan jawabannya berada dalam email dari Maret lalu, di dalam laporan PDF yang terlampir, di bawah heading yang disebut “Findings”. Asisten hanya dapat menemukan jawaban itu jika proses ingesti membaca lampiran, mempertahankan heading, dan mengingat email mana yang menyertakan laporan tersebut.
Sebagian besar pekerjaan ingesti melakukan salah satu dari tiga hal tersebut. Ekstraktor teks biasa mengembalikan isi badan email dan mengabaikan lampiran. Pemisah token tetap 512 memotong laporan tepat di tengah “Findings”. Dan parser yang dihosting menyelesaikan keduanya, tetapi hanya setelah file klaim, dengan detail pribadi dan medis, diunggah ke server pihak lain dan dikenakan biaya per halaman.
Apa yang dibutuhkan tim bersifat sempit dan konkret: membaca file Word, PDF, EML, dan ZIP di dalam layanan Java yang sudah memiliki data, mempertahankan struktur mereka sebagai Markdown, memisah pada heading penulis, dan melampirkan sumber serta induk ke setiap potongan.
Market snapshot: panduan 2026 tentang “document parsing for RAG” yang kami tinjau membandingkan parser Python sumber terbuka dengan API cloud yang dibebankan per halaman. Sebuah pustaka Java yang berjalan di perangkat keras Anda sendiri hampir tidak dibahas dalam percakapan itu.
Mengapa Pipeline RAG Membutuhkan Markdown Daripada Teks Biasa?
Markdown mempertahankan bagian dokumen yang memberi tahu pengambil kembali (retriever) di mana satu ide berakhir dan yang berikutnya dimulai. Heading menjadi baris #, tabel tetap sebagai baris dan kolom, dan daftar mempertahankan itemnya. Sebuah chunker dapat memisah pada heading tersebut, dan LLM yang membaca chunk melihat kerangka yang sama seperti yang dilihat orang. Teks biasa menghilangkan kerangka itu, meninggalkan ukuran sebagai satu‑satunya sinyal untuk pemisahan.
Mengapa Solusi Umum Tidak Berfungsi di Sini
Tim biasanya mencoba salah satu dari tiga hal sebelum membangun lapisan ingesti yang tepat:
- Ekstraksi teks biasa plus pemisah token: cepat disiapkan, tetapi chunk mencampur bagian dan kehilangan heading yang ingin Anda kutip.
- API parsing cloud: struktur bagus, tetapi file meninggalkan jaringan dan tagihan meningkat dengan setiap halaman yang di‑re‑index.
- Parser buatan sendiri per format: satu pustaka untuk DOCX, satu lagi untuk PDF, parser MIME untuk email, dan
java.util.zipuntuk arsip, masing‑masing dengan kasus tepi tersendiri.
GroupDocs.Parser untuk Java mencakup keempat format tersebut di balik satu kelas Parser dan berjalan dalam proses.
Solusi: GroupDocs.Parser dalam Alur Kerja Ingesti Klaim
GroupDocs.Parser untuk Java membaca dokumen dan dapat mengembalikan teksnya sebagai Markdown melalui getFormattedText. Email dan arsip diekspos sebagai kontainer yang item‑nya dibuka sebagai instance Parser baru, sehingga PDF yang terlampir pada email diproses persis seperti PDF di disk. Proyek contoh membungkus itu dalam enam metode statis dan menulis hasilnya sebagai JSON Lines, yang dapat dimuat oleh pekerjaan embedding atau penyimpanan vektor mana pun. Tidak ada kerangka kerja RAG yang terlibat; format output adalah titik integrasi.
Untuk mengikuti contoh ini Anda memerlukan:
- JDK 11 atau lebih baru untuk
pom.xmlyang disertakan (kode sendiri tetap pada fitur Java 8) - GroupDocs.Parser untuk Java 26.10 (get a temporary licence)
- Sekelompok file DOCX, PDF, EML, atau ZIP
pom.xml proyek mendeklarasikan repositori https://releases.groupdocs.com/java/repo/ dan satu ketergantungan, sehingga proses build hanya satu perintah:
mvn compile exec:java
Menerapkan Lapisan Ingesti Langkah demi Langkah
Langkah‑langkah di bawah ini mengikuti satu file klaim dari kotak masuk hingga indeks. Setiap langkah dipetakan ke satu atau dua metode dalam RagChunksDemo.java. Kami menyimpannya sebagai metode statis biasa dengan sengaja, sehingga Anda dapat mengambil salah satunya ke dalam pekerja yang sudah ada tanpa harus mengadopsi yang lainnya.
Langkah 1 - Buka Setiap File dan Arahkan
Setiap file yang diunggah mendapatkan Parser‑nya masing‑masing. Nama file menjadi ID sumber, dan file tingkat atas tidak memiliki induk.
// 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 kemudian mengekstrak isi, memecahnya, dan menelusuri lampiran selama kedalaman di bawah 2.
Langkah 2 - Minta Markdown, Terima Teks Biasa
Langkah ekstraksi memeriksa apa yang didukung format alih‑alih meng‑hard‑code daftar ekstensi.
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
Mengapa pengaturan ini penting untuk data klaim:
FormattedTextMode.Markdown: heading “Findings” milik penilai muncul ke chunker sebagai## Findings, sehingga dapat menjadi titik pemisahan dan kutipan.isFormattedText(): dalam contoh, DOCX dan email kembali sebagai Markdown sementara PDF kembali ke teks biasa, dan bidangformatmencatat apa yang terjadi.- Hasil
none: foto klaim di dalam ZIP tidak menghasilkan chunk tanpa OCR, yang jujur dan mudah dipantau.
Langkah 3 - Bagi pada Heading Penilai
Chunker menyimpan stack heading saat ini pada masing‑masing dari enam level Markdown. Heading baru menutup bagian yang sedang berjalan dan mengatur ulang semua level di bawahnya.
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');
}
Bagian yang lebih dari 1500 karakter dipotong pada jeda paragraf, sehingga bagian “findings” yang panjang menjadi beberapa chunk yang berbagi satu jalur heading. Pada file Word yang disertakan contoh ini menghasilkan jalur seperti Heading 1 > Heading 1.2 > Heading 1.2.1.
Langkah 4 - Ikuti Lampiran dan Tulis Baris JSON
Ini adalah langkah yang paling penting bagi meja klaim. getContainer() menampilkan lampiran email dan entri ZIP; setiap item dibuka sebagai dokumen anak dan mewarisi ID induk.
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 kemudian menulis satu baris UTF‑8 per chunk dengan id, source, parent, heading_path, format, dan text. Dengan empat masukan contoh, proses menghasilkan 22 baris. Chunk dari lampiran email contoh adalah email-with-attachment.eml/attachment.txt#0, dan parent‑nya adalah email-with-attachment.eml. Pasangan ini memungkinkan asisten mengatakan “dari laporan yang terlampir pada email 14 Maret” alih‑alih “dari entah mana dalam arsip”.
Alur Kerja Nyata: Re‑Index Klaim Mingguan
Bayangkan tim operasi klaim yang mengekspor kotak surat klaim tertutup minggu itu setiap Jumat sebagai file EML dan bundel ZIP dokumen pendukung. Sebuah pekerja Java sudah memindahkan ekspor tersebut ke penyimpanan dokumen. Menambahkan ingesti berarti memanggil chunkDocument untuk setiap file dan menambahkan output JSON Lines ke antrean embedding.
Langkah 4 adalah yang menjaga laporan cadangan tetap terlampir pada email yang mengirimkannya. Langkah 3 berarti ketika asisten mengutip “Findings”, kutipan menyebutkan nama file dan heading alih‑alih nomor chunk semata. Aturan praktis saya untuk indeks semacam ini: jika sebuah chunk tidak dapat dikutip kembali ke file dan heading, maka chunk tersebut tidak seharusnya berada di indeks sama sekali.
Dampak Bisnis: Apa yang Diubah
| Aspek | Pengaturan tipikal sebelumnya | Dengan pipeline GroupDocs.Parser |
|---|---|---|
| Di mana file diparse | Endpoint pihak ketiga | Di dalam JVM Anda sendiri |
| Model biaya parsing | Per halaman, pada setiap re‑index | Tanpa biaya cloud per halaman |
| Batasan chunk | Jendela token tetap | Heading penulis, batas 1500 karakter |
| Lampiran email | Sering dilewati atau diratakan | Diparse sebagai dokumen anak |
| Data kutipan | Nama file paling baik | source, parent, dan heading_path |
| Dependensi runtime | SDK plus akses jaringan | Satu artefak Maven |
Efek gabungan adalah indeks yang dapat disetujui tim kepatuhan dan asisten yang jawabannya merujuk pada dokumen serta bagian yang nyata.
Skenario Asuransi Lain di Mana GroupDocs.Parser Cocok
Setelah lapisan ingesti ada, kode yang sama melayani pekerjaan lain yang berat dokumen:
- Pencarian teks polis: dokumen polis Word dipisah berdasarkan heading klausa, sehingga jawaban mengutip bagian yang tepat.
- Pengajuan broker: unggahan ZIP dengan PDF campuran dan spreadsheet diindeks anggota per anggota, masing‑masing terikat pada pengajuan.
- Penanganan keluhan: email pelanggan dan lampirannya diindeks bersama, dengan email sebagai induk setiap chunk lampiran.
Memulai di Lingkungan Klaim Anda
Klon repositori contoh, jalankan terhadap beberapa email ekspor Anda sendiri, dan buka output/chunks.jsonl untuk memeriksa jalur heading serta ID induk sebelum menghubungkannya ke pekerjaan embedding Anda. Catatan arsitektur tahap‑per‑tahap ada di kasus penggunaan: Siapkan dokumen untuk RAG dalam Java, dan [referensi output Markdown][MARKDOWN_URL] mencantumkan format apa yang dipertahankan mode tersebut.