Salah satu pekerjaan paling menjengkelkan saat membangun aplikasi berbasis LLM atau retrieval-augmented generation (RAG) adalah “parsing” dokumen dunia nyata: laporan Word, slide PowerPoint, spreadsheet Excel, sampai PDF. Setiap format menyimpan teks dengan cara berbeda, sehingga biasanya kamu butuh banyak library sekaligus — dan hasilnya pun tidak konsisten. Di tutorial ini kamu akan belajar memakai anydoc, library konversi dokumen berbahasa Rust dari Firecrawl yang punya binding Python, untuk mengubah hampir semua format dokumen kantor menjadi Markdown bersih dalam hitungan milidetik, lalu menyiapkannya untuk pipeline RAG.

Kenapa topik ini relevan sekarang? anydoc (repositori firecrawl/anydoc) melesat jadi salah satu proyek terpanas di GitHub karena menjawab kebutuhan inti era AI: mengubah dokumen kantor menjadi teks siap-LLM dengan satu output yang konsisten. Cocok untuk RAG, chatbot dokumen, dan agent AI.

Apa Itu anydoc dan Kenapa Lebih Cepat?

anydoc adalah library konversi dokumen yang ditulis murni dalam Rust dan tersedia untuk Python, Node.js, browser (WebAssembly), hingga CLI. Ia mendukung 14 format sekaligus: Word (.doc, .docx, .docm), PowerPoint (.ppt, .pptx), Excel (.xls, .xlsx), OpenDocument (.odt, .ods, .odp), RTF, EPUB, CSV, dan PDF.

Rahasia kecepatannya: setiap format diparse ke dalam satu model dokumen bersama, lalu dirender melalui satu serializer Markdown yang sama. Artinya, tabel, heading, daftar, dan catatan kaki selalu tampil konsisten apa pun format masukannya. Dalam benchmark resminya, anydoc mengonversi satu dokumen dalam median ±4,4 milidetik — jauh lebih cepat daripada LibreOffice (1.129 ms) atau library Python lain — sekaligus meraih skor kualitas tertinggi di semua format yang diuji.

Prasyarat

Sebelum mulai, pastikan kamu sudah punya:

  • Python 3.9 atau lebih baru (tutorial ini memakai Python 3.11).
  • pip untuk menginstal package.
  • Satu atau beberapa dokumen contoh: .docx, .xlsx, .pdf, atau .csv.
  • (Opsional) python-docx dan openpyxl jika kamu ingin membuat dokumen contoh sendiri.
Langkah 1: Instalasi firecrawl-anydoc

Paketnya terpasang dengan nama firecrawl-anydoc, tetapi diimpor sebagai anydoc. Sebaiknya gunakan virtual environment agar tidak mengotori instalasi Python global.

bash
# (Opsional) Buat dan aktifkan virtual environment
python3 -m venv venv
source venv/bin/activate   # Windows: venv\Scripts\activate

# Pasang library anydoc dari PyPI
pip install firecrawl-anydoc

Untuk memastikan instalasi berhasil, jalankan:

bash
python -c "import anydoc; print('anydoc siap digunakan')"
Langkah 2: Konversi Satu File dengan to_markdown()

Cara paling sederhana adalah memanggil anydoc.to_markdown() dengan path file. Fungsinya mengembalikan string Markdown dalam format GitHub-Flavored Markdown (GFM).

python
import anydoc

# Konversi satu file langsung dari path
markdown = anydoc.to_markdown("laporan.docx")
print(markdown)

Misalkan laporan.docx berisi judul, paragraf, dan sebuah daftar bullet, hasilnya kurang lebih seperti ini:

python
# Laporan Tahunan

Ini adalah paragraf pertama laporan.

## Ringkasan

- Penjualan naik 20% dibanding tahun lalu.
Langkah 3: Konversi dari Bytes (untuk Upload Web & API)

Kalau kamu menerima file lewat upload di framework web seperti Django atau FastAPI, biasanya yang kamu pegang adalah bytes (bukan path di disk). Gunakan to_markdown_bytes(). Format akan dideteksi otomatis dari isi bytes-nya, sehingga file yang salah label ekstensinya pun tetap terkonversi dengan benar.

python
import anydoc

# Baca file sebagai bytes — misalnya dari request upload di web/API
with open("laporan.docx", "rb") as f:
    data = f.read()

# Format otomatis dideteksi dari isi bytes
markdown = anydoc.to_markdown_bytes(data)

# CSV tidak punya penanda format, jadi sebutkan formatnya eksplisit
markdown_csv = anydoc.to_markdown_bytes(data_csv, "csv")

CSV tidak punya penanda format di dalam isinya, sehingga deteksi otomatis mengembalikan None. Untuk file CSV, selalu sebutkan formatnya secara eksplisit lewat argumen kedua: to_markdown_bytes(data, "csv").

Langkah 4: Deteksi Format Otomatis

anydoc membaca format dari isi file (header PDF, ZIP mimetype, dan seterusnya), bukan dari ekstensinya. Ini berguna saat kamu perlu memvalidasi atau merutekan file sebelum konversi.

python
import anydoc

# Deteksi dari isi file (bukan dari ekstensi)
print(anydoc.format_from_bytes(data))         # 'docx' atau None jika tidak dikenali

# Deteksi dari ekstensi / path
print(anydoc.format_from_extension(".pptm"))  # 'pptx'
print(anydoc.format_from_path("report.odt"))  # 'odt'
Langkah 5: Membangun Script Konversi Massal

Dalam proyek nyata kamu jarang mengonversi satu file saja. Berikut script lengkap yang berjalan rekursif ke seluruh subfolder, mengonversi semua dokumen yang didukung, dan menyimpan hasilnya ke folder markdown/.

python
# konversi_batch.py
import anydoc
from pathlib import Path

DIREKTORI_SUMBER = Path("dokumen")
DIREKTORI_TUJUAN = Path("markdown")

EXTENSI_DIDUKUNG = {
    ".doc", ".docx", ".docm",
    ".ppt", ".pps", ".pot", ".pptx", ".pptm", ".ppsx", ".ppsm",
    ".xls", ".xlsx", ".xlsm", ".xlsb",
    ".odt", ".ods", ".odp",
    ".rtf", ".epub", ".csv", ".pdf",
}

def konversi_semua(sumber, tujuan):
    tujuan.mkdir(parents=True, exist_ok=True)
    total, sukses, gagal = 0, 0, []

    for path in sumber.rglob("*"):
        if path.suffix.lower() not in EXTENSI_DIDUKUNG:
            continue
        total += 1
        keluaran = tujuan / (path.stem + ".md")
        try:
            markdown = anydoc.to_markdown(str(path))
            keluaran.write_text(markdown, encoding="utf-8")
            sukses += 1
        except anydoc.ConvertError as e:
            # Catat kegagalan, lanjut ke file berikutnya
            gagal.append((path.name, type(e).__name__))

    print(f"Selesai: {sukses}/{total} dokumen berhasil dikonversi.")
    for nama, alasan in gagal:
        print(f"  - {nama}: {alasan}")

if __name__ == "__main__":
    konversi_semua(DIREKTORI_SUMBER, DIREKTORI_TUJUAN)

Jalankan dengan python konversi_batch.py. Jika folder dokumen/ berisi tiga file, output-nya kira-kira seperti ini:

python
Selesai: 3/3 dokumen berhasil dikonversi.
Langkah 6: Menangani Error dengan Elegan

anydoc melempar exception hanya ketika tidak ada Markdown bermakna yang bisa dihasilkan. Semua exception konversi merupakan turunan dari anydoc.ConvertError, dengan subclass yang menjelaskan penyebabnya:

  • EncryptedError — file terenkripsi atau berpassword.
  • UnsupportedError — format tidak dikenal atau PDF hanya gambar (butuh OCR).
  • MalformedError — struktur file rusak/tidak bisa diekstrak.
  • ResourceLimitError — melewati batas keamanan (dekompresi, nesting, jumlah node).
  • MissingPartError — bagian penting dokumen hilang.
python
import anydoc

def konversi_aman(path):
    try:
        return anydoc.to_markdown(path)
    except (anydoc.EncryptedError, anydoc.UnsupportedError) as e:
        # Tidak ada Markdown yang bisa dihasilkan — catat dan lanjut
        print(f"Dilewati: {path} ({type(e).__name__})")
        return None
    except anydoc.ConvertError as e:
        print(f"Gagal: {path} ({type(e).__name__}): {e}")
        return None

md = konversi_aman("laporan_terkunci.docx")
Langkah 7: Integrasi ke Pipeline RAG

Markdown keluaran anydoc langsung bisa dipecah menjadi chunk, di-embed, lalu disimpan ke vector store untuk retrieval. Berikut contoh fungsi chunking sederhana berdasarkan paragraf yang bisa kamu pasangkan dengan langkah konversi.

python
import anydoc
import re

def chunk_markdown(teks, ukuran=500):
    """Membagi Markdown menjadi chunk berdasarkan paragraf."""
    paragraf = [p.strip() for p in re.split(r"\n\s*\n", teks) if p.strip()]
    chunks = []
    saat_ini = ""
    for p in paragraf:
        if not saat_ini or len(saat_ini) + len(p) + 2 <= ukuran:
            saat_ini = (saat_ini + "\n\n" + p).strip()
        else:
            chunks.append(saat_ini)
            saat_ini = p
    if saat_ini:
        chunks.append(saat_ini)
    return chunks

# 1. Konversi dokumen
markdown = anydoc.to_markdown("laporan.docx")

# 2. Pecah menjadi chunk siap embedding
chunks = chunk_markdown(markdown)
for i, c in enumerate(chunks, 1):
    print(f"Chunk {i} ({len(c)} karakter): {c[:50]}...")

Dari sini, setiap chunk tinggal di-embed (misalnya dengan sentence-transformers atau API embedding OpenAI/DeepSeek) lalu disimpan ke vector store seperti Chroma, Pinecone, atau pgvector. Saat pengguna bertanya, kamu mengambil chunk paling relevan dan memasukkannya ke prompt LLM sebagai konteks.

Tips performa: anydoc melepas GIL (Global Interpreter Lock) saat konversi, jadi kamu bisa memproses banyak dokumen secara paralel dengan concurrent.futures.ThreadPoolExecutor tanpa saling memblokir thread utama.

Kesimpulan

anydoc menyederhanakan tahap paling membosankan dari pipeline RAG: menyeragamkan dokumen yang beragam menjadi Markdown bersih dan konsisten dalam hitungan milidetik. Kamu sudah belajar mengonversi file tunggal maupun massal, membaca dari bytes, mendeteksi format, menangani error, dan menyiapkan chunk untuk RAG. Selanjutnya, coba pakai di proyek chatbot dokumen atau agent AI milikmu — lalu pasangkan dengan vector store dan model embedding favoritmu.