Meningkatkan RAG dari tahap dasar ke level produksi: strategi chunking lanjutan, reranking, parent-child document, dan GraphRAG untuk data ber-relasi. Episode ini juga membuka multimodal RAG — image & table extraction, vision RAG, dan integrasi OCR — untuk dokumen yang bukan sekadar teks.

Setelah di episode 7 kalian membangun RAG dasar yang berfungsi, episode ini menjawab pertanyaan yang muncul begitu aplikasi berhadapan dengan dunia nyata: bagaimana kalau datanya dokumen tebal dengan tabel, gambar, dan hubungan antar-konsep? RAG naif — chunk lalu embed — akan mulai tersendat di sini.
Mengapa episode ini penting? Karena di produksi, dokumen perusahaan jarang berupa teks polos yang rapi. Ada PDF dengan tabel yang harus dibaca kolom-per-kolom, slide dengan diagram yang hanya masuk akal sebagai gambar, dan basis pengetahuan di mana jawaban tersebar di banyak halaman yang saling terkait. RAG lanjutan adalah seperangkat alat untuk menangani semua itu.
Daripada memotong teks buta per karakter, hormati struktur dokumen: pecah per heading (#), pertahankan tabel utuh, dan ikuti batas paragraf. Hasilnya chunk yang koheren secara makna, bukan potongan kalimat di tengah.
def chunk_by_headings(text):
sections, current, title = [], [], "root"
for line in text.splitlines():
if line.startswith("## "):
sections.append((title, "\n".join(current)))
title, current = line[3:], []
else:
current.append(line)
sections.append((title, "\n".join(current)))
return sectionsIdea cerdas untuk menjawab pertanyaan yang butuh konteks lebih luas: simpan child chunk kecil (akurat untuk retrieval) yang punya referensi ke parent chunk besar (lengkap untuk konteks LLM). Retrieve child → tarik parent → kirim parent ke LLM.
def ask_parent_child(question):
qv = embed(question)
child = col.query(query_embeddings=[qv], n_results=3)
# untuk tiap child, ambil parent (chunk besar) yang sama sumber & bagiannya
parents = [parent_of(c["id"]) for c in child["ids"][0]]
return llm_answer(question, context="\n\n".join(parents))Parent-child adalah perbaikan paling "murah" dalam hal efektivitas — sering menaikkan skor faithfulness secara signifikan.
Vector similarity bagus sebagai penyaring awal, tapi bisa salah urut — misal chunk yang cocok secara "makna" tapi tidak menjawab pertanyaan sebenarnya. Reranker adalah model kedua yang menilai ulang pasangan (query, dokumen) secara lebih akurat dan mahal, lalu mengurutkan ulang.
import cohere
co = cohere.Client(api_key=os.environ["CO_API_KEY"])
# 1. Ambil 20 kandidat murah dengan vector search
candidates = col.query(query_embeddings=[qv], n_results=20)
# 2. Rerank dengan model yang lebih teliti
reranked = co.rerank(
query=question,
documents=candidates["documents"][0],
top_n=4,
model="rerank-multilingual-v3.0",
)
# 3. Kirim hanya 4 terbaik ke LLM
final = [candidates["documents"][0][i] for i in
[r.index for r in reranked.results]]Pola retrieve-then-rerank adalah standar produksi 2026: vektor menyaring cepat, reranker menyaring akurat — biaya rerank hanya untuk puluhan kandidat, bukan seluruh korpus.
Untuk pertanyaan yang menghubungkan banyak konsep ("siapa kolaborator yang paling sering muncul dengan tim X?"), RAG chunk polos gagal karena informasi tersebar. GraphRAG membangun knowledge graph: entitas (orang, organisasi, konsep) sebagai node dan relasi sebagai edge, lalu retrieval berjalan di atas grafik itu.
Langkahnya: ekstrak entitas & relasi dari dokumen (bisa via LLM), simpan sebagai graph (misal Neo4j), lalu jawab pertanyaan dengan traversing graf + LLM. Biayanya lebih mahal untuk dibangun, tetapi unggul untuk pertanyaan global (whole-corpus) dan analisis relasi.
Note
GraphRAG bukan pengganti RAG vector biasa — ia pelengkap. Untuk pertanyaan lokal ("apa isi bagian X?"), vector RAG sudah cukup; GraphRAG unggul untuk pertanyaan yang melintasi banyak dokumen. Banyak produksi memakai keduanya: vector untuk lookup, graph untuk relasi.
Dokumen nyata penuh tabel dan gambar. Dua pendekatan:
markdown dari tabel PDF) lalu di-embed seperti teks biasa.import base64
def image_b64(path):
with open(path, "rb") as f:
return base64.b64encode(f.read()).decode()
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": [
{"type": "text", "text": "Apa isi tabel pada slide ini? Format jawaban markdown."},
{"type": "image_url",
"image_url": {"url": f"data:image/png;base64,{image_b64('slide.png')}"}},
]}],
)
print(resp.choices[0].message.content)Untuk PDF hasil scan (gambar halaman), wajib OCR dulu — misal pytesseract atau PaddleOCR — sebelum chunking. Ingat: OCR dulu, chunking kedua, embed ketiga. Melewatkan OCR = seluruh isi dokumen hilang.
pip install pytesseract pdf2imageimport pytesseract
from pdf2image import convert_from_path
pages = convert_from_path("scan.pdf", dpi=200)
text = "\n".join(pytesseract.image_to_string(p) for p in pages)
print(text[:200])Tabel yang di-OCR sebagai teks datar sering rusak. Tool seperti pdfplumber atau unstructured bisa mengekstrak tabel per-sel dan menyimpannya sebagai markdown — sehingga retrieval "berapa penjualan Q3?" bisa menemukan tabelnya.
pip install unstructured pdfplumberimport pdfplumber
with pdfplumber.open("laporan.pdf") as pdf:
table = pdf.pages[0].extract_table()
for row in table:
print(" | ".join(cell or "" for cell in row))Inti yang harus dibawa pulang:
Di episode 9 selanjutnya kita akan membangun Function Calling & Tool Use — structured tool schema, tool retry/error handling, dan agen yang bisa query database, kalkulasi, atau memanggil API eksternal. Sampai jumpa di episode 9!