RAG & Chatbot Data Sendiri
Bikin AI yang Menjawab dari Dokumenmu
Galih Prasetyo · 2026
Kata Pengantar
Sejak model bahasa besar (LLM) menjadi mudah diakses, satu pertanyaan muncul berulang kali di ruang kerja, ruang kelas, dan grup diskusi: "Bagaimana caranya agar AI menjawab berdasarkan dokumen saya, bukan berdasarkan pengetahuan umum internet yang kadang keliru dan sering usang?" Buku ini adalah jawaban praktis dan menyeluruh untuk pertanyaan itu.
Teknik yang menjembatani LLM dengan data pribadimu disebut Retrieval-Augmented Generation, disingkat RAG. Ide dasarnya sederhana: alih-alih berharap model "mengingat" isi dokumenmu, kita mengambil (retrieve) potongan dokumen yang relevan pada saat pertanyaan diajukan, menyisipkannya ke dalam prompt, lalu meminta model menyusun jawaban berlandaskan potongan tersebut. Hasilnya adalah chatbot yang tahu isi kebijakan perusahaanmu, manual produkmu, catatan riset timmu, atau ribuan halaman kontrak yang tak mungkin dibaca satu per satu.
Namun di antara "ide sederhana" dan "sistem yang benar-benar bisa dipercaya" ada jurang penuh keputusan teknik: bagaimana memuat PDF yang berantakan, seberapa besar memotong teks, embedding mana yang dipakai, basis data vektor apa yang cocok, bagaimana menghindari jawaban ngawur, dan bagaimana membuktikan bahwa jawaban benar-benar bersumber dari dokumen. Buku ini memandu setiap langkah itu — dengan penjelasan yang bisa dipahami pemula, tetapi cukup dalam untuk dijadikan rujukan praktisi.
Yang membuat buku ini berbeda: ada Demo RAG Mini yang berjalan langsung di halaman ini, tanpa server dan tanpa kunci API. Kamu bisa menempelkan beberapa dokumen, mengajukan pertanyaan, dan menyaksikan sendiri bagaimana sistem memotong teks, memberi skor, mengambil potongan terbaik, lalu merakit jawaban lengkap dengan penanda sumber. Memahami RAG paling cepat terjadi ketika kamu melihat mesinnya bekerja di depan mata.
Buku ini ditulis dengan asumsi kamu ingin membangun sesuatu yang nyata, bukan sekadar memahami teori. Setiap bab diakhiri dengan hal yang bisa langsung kamu terapkan.
— Galih Prasetyo, Jakarta, 2026
Cara Membaca Buku Ini
Buku ini disusun mengikuti alur pipeline RAG dari hulu ke hilir. Kamu bisa membacanya berurutan seperti membangun sistem dari nol, atau melompat ke bab tertentu ketika menghadapi masalah spesifik.
Tiga jalur pembaca
- Pemula total. Baca Bab 1–2 untuk intuisi, lalu langsung mainkan Demo RAG Mini di Bab 16. Setelah itu kembali ke Bab 3 dan seterusnya.
- Developer yang ingin membangun. Baca berurutan Bab 3–15; setiap bab memuat kode siap salin dan keputusan desain.
- Pengambil keputusan / manajer produk. Fokus pada Bab 1, 9, 10, 13, 14, dan 15 — nilai bisnis, kepercayaan, evaluasi, keamanan, dan biaya.
Konvensi penulisan
Blok kode berwarna gelap dengan tombol Salin di kanan atas — kode ditulis agar bisa langsung dipakai dengan penyesuaian minimal. Kotak berwarna adalah callout: ungu untuk catatan, kuning untuk peringatan, hijau untuk praktik baik, merah untuk jebakan. Istilah teknis pertama kali muncul dicetak tebal dan dijelaskan di Glosarium.
Untuk Siapa Buku Ini
Buku ini ditujukan untuk siapa saja yang ingin membuat AI menjawab dari data sendiri: developer web dan backend, data engineer, mahasiswa, pendiri startup, konsultan, hingga staf non-teknis yang ingin memahami apa yang mungkin dan apa yang mahal. Tidak diperlukan latar belakang machine learning. Yang membantu: kenyamanan membaca kode (contoh memakai Python dan sedikit SQL) dan rasa ingin tahu.
Setelah menuntaskan buku ini kamu akan mampu: menjelaskan RAG kepada atasan dalam satu menit; membangun prototipe chatbot dokumen dari nol; memilih vector database yang tepat untuk skala dan anggaranmu; membuat jawaban yang menyertakan sitasi; menekan halusinasi; dan mengukur apakah sistemmu benar-benar bagus.
Kutipan Einstein di atas menjadi kompas buku ini. Menumpuk informasi mudah; mengubahnya menjadi jawaban yang tepat, terverifikasi, dan bisa dipercaya adalah pekerjaan sesungguhnya. RAG yang baik bukan soal menaruh lebih banyak teks ke dalam model, melainkan soal mengambil teks yang tepat pada saat yang tepat.
Pendahuluan: Chatbot yang Tahu Datamu
Bayangkan dua asisten. Yang pertama sangat pintar, membaca hampir separuh internet, tetapi tidak pernah melihat satu pun dokumen internal perusahaanmu. Yang kedua tidak sepintar itu, tetapi diberi akses ke seluruh manual, kebijakan, dan catatan timmu, dan selalu mengutip halaman yang ia rujuk. Untuk pertanyaan "Berapa lama masa garansi produk X menurut kebijakan kita?", asisten pertama akan menebak dengan percaya diri — dan bisa saja salah. Asisten kedua akan membuka dokumen, menemukan klausul yang tepat, dan menjawab sambil menunjukkan sumbernya. RAG mengubah asisten pertama menjadi asisten kedua.
Masalah LLM polos: pintar tapi buta konteks
LLM seperti keluarga model GPT (OpenAI), Claude (Anthropic), Gemini (Google), atau model terbuka seperti Llama dan Mistral, dilatih pada teks dalam jumlah masif. Kemampuannya memukau: merangkum, menerjemahkan, menulis kode, bernalar. Tetapi ada empat batasan mendasar yang membuat LLM polos tidak cukup untuk pertanyaan seputar data pribadimu.
- Tidak tahu data pribadimu. Model tidak pernah melihat kontrak, tiket dukungan, atau wiki internalmu. Semua itu tidak ada di data latihnya.
- Pengetahuan beku pada tanggal tertentu. Model punya knowledge cutoff. Kebijakan yang berubah pekan lalu tidak ia ketahui.
- Halusinasi. Ketika tidak tahu, model cenderung mengarang jawaban yang terdengar meyakinkan — lengkap dengan angka dan nama yang salah.
- Tidak bisa mengutip. Model tidak bisa menunjukkan "kalimat ini saya ambil dari halaman 12 dokumen ini", karena ia menghasilkan teks, bukan menelusuri sumber.
Apa yang ditawarkan RAG
RAG menambahkan satu langkah cerdas sebelum model menjawab: pengambilan (retrieval). Ketika pertanyaan masuk, sistem mencari potongan dokumen yang paling relevan dari basis pengetahuanmu, lalu menyisipkannya ke prompt sebagai konteks. Model tidak perlu "mengingat" apa pun — ia hanya perlu membaca konteks yang baru saja diberikan dan menyusun jawaban darinya. Ini seperti ujian open book: model tetap yang menulis jawaban, tetapi bukunya kita bukakan di halaman yang tepat.
Manfaat nyata dalam angka dan cerita
Sebuah tim dukungan pelanggan yang menerapkan chatbot RAG di atas basis pengetahuan internalnya biasanya melihat waktu penanganan tiket turun karena agen mendapat jawaban terformat lengkap sitasi dalam hitungan detik. Tim legal memakainya untuk menelusuri klausul di ratusan kontrak. Tim engineering menjadikannya "wiki yang bisa diajak ngobrol". Dalam semua kasus, kunci kepercayaannya sama: jawaban selalu menunjuk ke dokumen sumber, sehingga pengguna bisa memverifikasi.
RAG adalah upaya menutup jurang antara "informasi yang menumpuk" dan "pengetahuan yang bisa dipakai". Sepanjang buku ini kita akan terus kembali ke satu tolok ukur: apakah sistem membuat pengguna lebih cepat menemukan jawaban yang benar dan bisa dipercaya?
Peta jalan buku
Bab 2 membedah tiga huruf RAG. Bab 3–8 membangun pipeline: ingestion, chunking, embeddings, vector DB, retrieval, dan perakitan prompt. Bab 9–11 membahas kepercayaan: sitasi, kontrol halusinasi, dan memori percakapan. Bab 12–15 naik kelas: multi-dokumen, evaluasi, keamanan, dan deploy. Bab 16 menyatukan semua dalam contoh nyata plus Demo RAG Mini. Bab 17 membahas hal yang jarang disentuh: bagaimana membuat kontenmu sendiri mudah dibaca oleh AI. Bab 18 menutup.
Cara Kerja RAG: Retrieve, Augment, Generate
Nama "Retrieval-Augmented Generation" menyimpan seluruh arsitekturnya dalam tiga kata. Memahami masing-masing kata secara mendalam adalah cara tercepat menguasai keseluruhan sistem, karena setiap bab selanjutnya sebenarnya memperdalam salah satu dari tiga tahap ini.
Dua fase: indexing dan querying
Sebelum tiga huruf itu bisa bekerja, ada pekerjaan persiapan. Sistem RAG hidup dalam dua fase yang terpisah waktu.
Fase indexing (offline, sekali atau berkala). Semua dokumen dimuat, dipecah menjadi potongan (chunk), diubah menjadi vektor melalui model embedding, lalu disimpan di vector database. Ini seperti menyusun perpustakaan dan membuat katalognya sebelum ada yang bertanya.
Fase querying (online, saat pengguna bertanya). Pertanyaan diubah menjadi vektor dengan model embedding yang sama, dicocokkan dengan katalog untuk menemukan potongan termirip, potongan itu disisipkan ke prompt, dan model menghasilkan jawaban. Inilah bagian "retrieve-augment-generate" yang sesungguhnya.
R — Retrieve (mengambil)
Tahap retrieve menjawab pertanyaan: "Dari seluruh dokumen, potongan mana yang paling mungkin memuat jawaban?" Ini bukan pencarian kata kunci biasa. Sistem modern memakai pencarian semantik: pertanyaan dan potongan dokumen sama-sama diubah menjadi vektor, dan kedekatan makna diukur sebagai kedekatan vektor. Karena itu "cara mengembalikan barang" bisa cocok dengan potongan berjudul "prosedur retur", meski tak ada satu kata pun yang sama persis. Bab 5–7 mendalami mekanisme ini.
A — Augment (memperkaya)
Tahap augment menyisipkan potongan hasil retrieval ke dalam prompt. Di sinilah rekayasa prompt bertemu batas token: kita harus memilih berapa banyak potongan, bagaimana memformat konteks, instruksi apa yang diberikan ke model, dan bagaimana menandai sumber agar bisa dikutip. Prompt yang buruk bisa membuat retrieval bagus menjadi jawaban jelek. Bab 8 dan 9 fokus di sini.
G — Generate (menghasilkan)
Tahap generate adalah saat LLM membaca prompt (pertanyaan + konteks + instruksi) dan menuliskan jawaban. Kualitas jawaban bergantung pada dua hal sebelumnya: jika konteks tidak memuat jawaban, model yang baik seharusnya berkata "tidak tahu" alih-alih mengarang. Menegakkan perilaku ini adalah inti kontrol halusinasi di Bab 10.
Contoh minimal end-to-end
Berikut kerangka pipeline RAG dalam Python — sengaja disederhanakan agar seluruh alur terlihat dalam satu layar. Bab-bab berikutnya menggantikan tiap bagian dengan versi produksi.
# Pipeline RAG minimal: indexing lalu querying from openai import OpenAI import numpy as np client = OpenAI() EMB = "text-embedding-3-small" def embed(teks): r = client.embeddings.create(model=EMB, input=teks) return np.array(r.data[0].embedding) # --- FASE INDEXING --- dokumen = [ "Garansi produk X berlaku 12 bulan sejak pembelian.", "Retur diterima maksimal 7 hari dengan struk asli.", "Pengiriman ke luar Jawa memakan 3-5 hari kerja.", ] index = [(d, embed(d)) for d in dokumen] # katalog # --- FASE QUERYING --- def jawab(pertanyaan, k=2): qv = embed(pertanyaan) skor = [(np.dot(qv, v), d) for d, v in index] # similarity skor.sort(reverse=True) konteks = "\n".join(f"- {d}" for _, d in skor[:k]) prompt = f"Jawab HANYA dari konteks. Jika tidak ada, katakan tidak tahu.\n\nKonteks:\n{konteks}\n\nTanya: {pertanyaan}" r = client.chat.completions.create(model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}]) return r.choices[0].message.content print(jawab("Berapa lama garansinya?")) # -> 12 bulan
Perhatikan tiga hal yang akan kita perbaiki: (1) katalog di sini disimpan di memori Python, bukan vector database — tidak tahan restart dan tak bisa besar; (2) pencocokan memakai dot-product mentah, belum ternormalisasi; (3) prompt sudah memuat instruksi anti-halusinasi, benih dari Bab 10. Semua ini titik awal, bukan tujuan.
Ingestion: Memuat PDF, Web, dan Dokumen
Setiap sistem RAG dimulai dari pertanyaan yang membosankan tetapi menentukan: bagaimana teks masuk ke pipeline? Ingestion adalah proses memuat dokumen dari berbagai sumber dan format, membersihkannya, lalu mengubahnya menjadi teks bersih yang siap dipotong. Terdengar sepele, tetapi kualitas ingestion menentukan langit-langit kualitas seluruh sistem. Teks yang masuk berantakan tidak bisa diselamatkan oleh embedding secanggih apa pun.
Sumber data yang lazim
- PDF — paling umum sekaligus paling menyusahkan: ada yang berbasis teks, ada yang hasil scan (butuh OCR), banyak yang berkolom, bertabel, atau berheader-footer berulang.
- Halaman web / HTML — perlu dibersihkan dari menu, iklan, dan boilerplate agar hanya konten utama yang tersisa.
- Dokumen Office — DOCX, PPTX, XLSX; struktur relatif rapi tetapi tabel dan slide perlu perlakuan khusus.
- Markdown & teks biasa — paling ramah RAG karena strukturnya eksplisit.
- Basis data & API — baris tabel atau respons JSON yang diubah menjadi kalimat naratif.
Memuat PDF dengan benar
PDF menyimpan posisi setiap karakter, bukan alur bacaan. Ekstraktor naif sering mengacak urutan pada dokumen berkolom, menempelkan header ke paragraf, atau merusak tabel. Gunakan pustaka yang sadar tata letak, dan selalu periksa mata hasil ekstraksi beberapa halaman sebelum melanjutkan.
# Ekstraksi teks PDF dengan pdfplumber + metadata halaman import pdfplumber def muat_pdf(path): keping = [] with pdfplumber.open(path) as pdf: for i, hal in enumerate(pdf.pages, start=1): teks = hal.extract_text() or "" teks = teks.strip() if teks: keping.append({ "teks": teks, "sumber": path, "halaman": i, # metadata penting untuk sitasi! }) return keping
OCR untuk PDF hasil scan
Jika extract_text() mengembalikan string kosong padahal halaman jelas berisi tulisan, kemungkinan besar itu PDF hasil scan — sekadar gambar. Di sini diperlukan OCR (Optical Character Recognition) seperti Tesseract atau layanan cloud. Kualitas OCR bervariasi; dokumen penting sebaiknya diverifikasi manual.
Membersihkan HTML
Untuk halaman web, tujuannya mengekstrak "konten utama" dan membuang navigasi, footer, dan iklan. Pustaka seperti trafilatura atau readability sangat membantu.
# Ambil konten utama halaman web, buang boilerplate import trafilatura def muat_web(url): unduh = trafilatura.fetch_url(url) teks = trafilatura.extract(unduh, include_comments=False, include_tables=True) return {"teks": teks or "", "sumber": url}
Normalisasi: langkah yang sering dilupakan
Sebelum teks dipotong, normalisasi mengurangi noise: satukan spasi berlebih, perbaiki hyphenation di ujung baris ("infor-\nmasi" menjadi "informasi"), buang header/footer berulang, dan standarkan encoding ke UTF-8. Langkah kecil ini secara nyata meningkatkan kualitas embedding karena mengurangi variasi permukaan yang tak bermakna.
import re def normalisasi(teks): teks = re.sub(r"-\n(\w)", r"\1", teks) # gabung kata terpotong teks = re.sub(r"[ \t]+", " ", teks) # spasi berlebih teks = re.sub(r"\n{3,}", "\n\n", teks) # baris kosong berlebih return teks.strip()
Chunking: Seni Memotong Teks
Setelah dokumen bersih, ia harus dipecah menjadi potongan-potongan kecil bernama chunk. Chunking adalah salah satu keputusan paling berdampak sekaligus paling diremehkan dalam RAG. Potongan yang terlalu besar menenggelamkan jawaban di antara teks tak relevan dan memboroskan token; potongan yang terlalu kecil kehilangan konteks sehingga makna terpotong di tengah kalimat. Menemukan ukuran yang tepat adalah keseimbangan halus yang bergantung pada jenis dokumenmu.
Mengapa harus dipotong?
Ada tiga alasan. Pertama, model embedding punya batas panjang input; teks panjang harus dipenggal. Kedua, unit retrieval yang kecil membuat pencocokan lebih presisi — kita mengambil paragraf yang tepat, bukan bab yang mengandungnya. Ketiga, jendela konteks LLM terbatas dan berbayar; menyisipkan potongan kecil yang relevan jauh lebih efisien daripada menjejalkan dokumen utuh.
Strategi chunking dari sederhana ke canggih
1. Fixed-size (ukuran tetap)
Potong setiap N token/karakter. Cepat dan sederhana, tetapi buta terhadap struktur — sering memotong di tengah kalimat. Cocok sebagai baseline.
2. Recursive character splitting
Potong mengikuti hierarki pemisah alami: coba pisah per paragraf (\n\n); jika masih terlalu besar, per kalimat; lalu per kata. Ini mempertahankan batas semantik sebisa mungkin dan menjadi default yang baik untuk kebanyakan teks.
3. Structure-aware (sadar struktur)
Manfaatkan struktur dokumen: heading Markdown, bagian pada dokumen legal, baris pada tabel. Setiap chunk sejajar dengan satu unit makna. Paling bagus, tetapi butuh dokumen yang terstruktur rapi.
4. Semantic chunking
Pisahkan di titik ketika makna bergeser, dideteksi dari perubahan embedding antar-kalimat. Paling canggih dan mahal secara komputasi; berguna untuk teks panjang tanpa struktur eksplisit.
Overlap: perekat antar-potongan
Untuk mencegah informasi terpotong tepat di batas chunk, potongan yang berdekatan dibuat saling tumpang tindih sebesar overlap (misalnya 10–20% dari ukuran chunk). Sebuah kalimat yang jatuh di perbatasan akan muncul utuh di salah satu chunk. Overlap menaikkan recall dengan sedikit biaya penyimpanan tambahan.
Implementasi recursive splitter
# Recursive splitter sederhana dengan overlap def chunk_teks(teks, ukuran=800, overlap=120): pemisah = ["\n\n", "\n", ". ", " "] def pecah(t, ps): if len(t) <= ukuran or not ps: return [t] bagian, hasil = t.split(ps[0]), [] for b in bagian: if len(b) > ukuran: hasil += pecah(b, ps[1:]) else: hasil.append(b) return hasil kasar = pecah(teks, pemisah) # gabung ulang jadi jendela ~ukuran dengan overlap chunks, buf = [], "" for b in kasar: if len(buf) + len(b) <= ukuran: buf += b + " " else: chunks.append(buf.strip()) buf = buf[-overlap:] + b + " " # bawa ekor sbg overlap if buf.strip(): chunks.append(buf.strip()) return chunks
Panduan ukuran menurut jenis dokumen
| Jenis dokumen | Ukuran chunk | Overlap | Strategi |
|---|---|---|---|
| FAQ / QnA | 1 pasangan Q&A | 0 | Structure-aware |
| Artikel / blog | 500–800 token | 10–15% | Recursive |
| Dokumen legal / kontrak | per klausul/pasal | 1 kalimat | Structure-aware |
| Manual teknis | per subbagian | 15% | Structure-aware |
| Transkrip / chat | 200–400 token | 20% | Semantic |
| Kode sumber | per fungsi/kelas | 0 | Sadar sintaks |
Embeddings: Mengubah Teks Jadi Vektor
Embedding adalah jantung pencarian semantik. Model embedding mengubah sepotong teks menjadi deretan angka — sebuah vektor — sedemikian rupa sehingga teks bermakna serupa menghasilkan vektor yang berdekatan dalam ruang. Inilah keajaiban yang membuat komputer bisa menemukan "prosedur retur" ketika kamu bertanya "cara mengembalikan barang", tanpa satu pun kata yang sama.
Intuisi: makna sebagai lokasi
Bayangkan setiap teks ditempatkan sebagai titik di ruang berdimensi tinggi (misalnya 1.536 dimensi). Model embedding, yang dilatih pada teks masif, menempatkan titik-titik sehingga jarak antar-titik mencerminkan kedekatan makna. "Kucing" dekat dengan "kucing peliharaan" dan "hewan", jauh dari "faktur pajak". Kita tidak bisa membayangkan 1.536 dimensi, tetapi matematikanya bekerja sama seperti jarak di peta dua dimensi.
Mengukur kedekatan: cosine similarity
Ukuran kemiripan paling umum adalah cosine similarity — kosinus sudut antara dua vektor, bernilai -1 sampai 1. Semakin mendekati 1, semakin searah, semakin mirip maknanya. Karena hanya memedulikan arah (bukan panjang), cosine tahan terhadap perbedaan panjang teks.
import numpy as np def cosine(a, b): return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)) # Embedding via API (dimensi 1536 utk text-embedding-3-small) from openai import OpenAI client = OpenAI() def embed_banyak(daftar_teks): r = client.embeddings.create(model="text-embedding-3-small", input=daftar_teks) # batch! return [np.array(d.embedding) for d in r.data]
Memilih model embedding
| Pertimbangan | Penjelasan |
|---|---|
| Dimensi | Lebih tinggi = lebih ekspresif tapi lebih berat disimpan & dicari. 384–1536 lazim. |
| Bahasa | Pastikan model mendukung Bahasa Indonesia dengan baik; uji pada datamu sendiri. |
| API vs lokal | API (OpenAI, Cohere, Voyage) praktis; model lokal (multilingual-e5, BGE) gratis & privat. |
| Panjang input maks | Menentukan ukuran chunk maksimum yang bisa di-embed. |
| Biaya | Dihitung per token; relevan saat indexing jutaan chunk. |
Vector Database: Menyimpan & Mencari Vektor
Setelah setiap chunk menjadi vektor, kita butuh tempat menyimpannya dan cara mencari vektor termirip dengan cepat — bahkan ketika jumlahnya jutaan. Menghitung cosine ke setiap vektor satu per satu (brute force) tidak lagi praktis di skala besar. Di sinilah vector database berperan: ia menyimpan vektor beserta metadata dan menyediakan pencarian tetangga terdekat (nearest neighbor) yang sangat cepat lewat indeks khusus seperti HNSW atau IVF.
Approximate Nearest Neighbor (ANN)
Kunci kecepatan adalah indeks ANN yang menukar sedikit akurasi dengan lompatan besar kecepatan. Alih-alih membandingkan ke semua vektor, indeks seperti HNSW (graf berlapis) menavigasi jalan pintas menuju kandidat terbaik hanya dalam sebagian kecil perbandingan. Untuk hampir semua kasus RAG, sedikit ketidaktepatan ini tak terasa, sementara kecepatannya menentukan apakah sistem bisa dipakai.
Pilihan yang lazim
| Opsi | Karakter | Cocok untuk |
|---|---|---|
| pgvector | Ekstensi PostgreSQL; vektor hidup berdampingan dengan data relasional. | Tim yang sudah pakai Postgres; ingin satu basis data. |
| Chroma | Ringan, embedded, mudah dipasang lokal. | Prototipe, proyek kecil, eksperimen. |
| Qdrant | Cepat, filter metadata kaya, ditulis dalam Rust. | Produksi skala menengah-besar, butuh filter kompleks. |
| Supabase | pgvector terkelola + auth + storage dalam satu platform. | Aplikasi web full-stack, ingin cepat jalan. |
| Weaviate / Milvus / Pinecone | Terkelola atau self-host skala besar. | Volume sangat besar, kebutuhan enterprise. |
Contoh pgvector
-- Aktifkan ekstensi & buat tabel CREATE EXTENSION IF NOT EXISTS vector; CREATE TABLE chunks ( id bigserial PRIMARY KEY, konten text, sumber text, halaman int, embedding vector(1536) -- dimensi model ); -- Indeks HNSW utk pencarian cepat (cosine) CREATE INDEX ON chunks USING hnsw (embedding vector_cosine_ops); -- Query: 5 chunk termirip dgn vektor pertanyaan $1 SELECT konten, sumber, halaman, 1 - (embedding <=> $1) AS skor FROM chunks ORDER BY embedding <=> $1 -- <=> = jarak cosine LIMIT 5;
Contoh Chroma (Python)
import chromadb db = chromadb.PersistentClient(path="./ragdb") koleksi = db.get_or_create_collection("dokumen") # Simpan (Chroma bisa meng-embed sendiri, atau beri vektormu) koleksi.add(ids=["c1", "c2"], documents=["Garansi 12 bulan.", "Retur 7 hari."], metadatas=[{"sumber": "kebijakan.pdf", "hal": 1}, {"sumber": "kebijakan.pdf", "hal": 2}]) # Cari 2 termirip, filter metadata opsional hasil = koleksi.query(query_texts=["berapa lama garansi?"], n_results=2)
Retrieval: Similarity, Hybrid, Reranking
Retrieval adalah tahap yang paling menentukan kualitas jawaban. Jika potongan yang diambil salah, LLM sepintar apa pun tak bisa menyelamatkan. Kabar baiknya, retrieval punya banyak tuas yang bisa diputar: dari pencarian semantik murni, gabungan dengan kata kunci, hingga pengurutan ulang oleh model khusus. Bab ini membahas cara memutar tuas-tuas itu.
Dense retrieval (semantik)
Ini yang sudah kita bangun: embed pertanyaan, cari vektor termirip. Kuat menangkap makna dan sinonim, tetapi bisa meleset pada istilah spesifik, kode produk, atau nama diri yang jarang muncul di data latih embedding.
Sparse retrieval (BM25 / kata kunci)
BM25 adalah algoritma peringkat berbasis kata kunci yang matang dan tangguh. Ia unggul persis di titik lemah dense: mencocokkan istilah eksak seperti "SKU-4471" atau "Pasal 27 ayat 3". Kelemahannya kebalikannya: buta sinonim dan parafrasa.
Hybrid: gabungkan keduanya
Karena dense dan sparse saling menutupi kelemahan, menggabungkan keduanya (hybrid search) hampir selalu mengungguli salah satunya sendirian. Skor dari kedua metode digabung, umumnya lewat Reciprocal Rank Fusion (RRF) yang menjumlahkan kebalikan peringkat sehingga tidak perlu menyamakan skala skor.
# Reciprocal Rank Fusion: gabung hasil dense & sparse def rrf(daftar_peringkat, k=60): skor = {} for peringkat in daftar_peringkat: # tiap metode for posisi, doc_id in enumerate(peringkat): skor[doc_id] = skor.get(doc_id, 0) + 1 / (k + posisi + 1) return sorted(skor, key=skor.get, reverse=True) gabung = rrf([hasil_dense, hasil_bm25]) # daftar id terurut
Reranking: penyaring akhir
Retrieval awal mengejar recall — ambil banyak kandidat (misalnya top-20) agar jawaban pasti ada di dalamnya. Lalu sebuah reranker (cross-encoder yang membaca pertanyaan dan kandidat bersamaan) menilai relevansi jauh lebih akurat dan menyisakan top-5 terbaik untuk dikirim ke LLM. Dua tahap ini — retrieve luas lalu rerank ketat — adalah pola andalan sistem RAG berkualitas tinggi.
Memilih top-k
Berapa potongan yang dikirim ke LLM? Terlalu sedikit berisiko melewatkan jawaban; terlalu banyak memboroskan token, menaikkan biaya, dan mengencerkan fokus model. Titik manis umumnya 3–6 chunk setelah reranking. Sesuaikan lewat evaluasi (Bab 13), bukan tebakan.
Perakitan Prompt: Menyisipkan Konteks
Retrieval yang sempurna masih bisa menghasilkan jawaban buruk jika prompt dirakit sembarangan. Perakitan prompt adalah seni menyusun potongan konteks, pertanyaan, dan instruksi menjadi satu masukan yang membimbing model menjawab dengan tepat, ringkas, dan bersumber. Bab ini membahas anatomi prompt RAG yang baik dan cara mengelola batas token.
Anatomi prompt RAG
Prompt RAG yang solid umumnya terdiri dari empat lapis: (1) instruksi sistem yang menetapkan peran dan aturan main; (2) konteks berisi potongan terambil, masing-masing bertanda sumber; (3) pertanyaan pengguna; dan (4) format keluaran yang diinginkan.
def rakit_prompt(pertanyaan, chunks): konteks = "\n\n".join( f"[Sumber {i+1} | {c['sumber']} hal.{c['halaman']}]\n{c['konten']}" for i, c in enumerate(chunks)) sistem = ( "Kamu asisten yang menjawab HANYA berdasarkan KONTEKS. " "Sertakan penanda sumber [Sumber N] pada setiap klaim. " "Jika jawaban tidak ada di konteks, katakan: " "'Maaf, informasi itu tidak ada di dokumen yang tersedia.' " "Jangan mengarang.") return [ {"role": "system", "content": sistem}, {"role": "user", "content": f"KONTEKS:\n{konteks}\n\nPERTANYAAN: {pertanyaan}"}, ]
Mengelola batas token
Setiap model punya jendela konteks maksimum. Konteks + pertanyaan + instruksi + ruang untuk jawaban harus muat di dalamnya. Ketika total kandidat melebihi anggaran, kita harus memangkas: kurangi jumlah chunk, ringkas chunk terlebih dahulu, atau prioritaskan yang berskor tinggi. Selalu sisakan ruang cukup untuk jawaban model.
Menyusun instruksi yang efektif
- Batasi sumber: tegaskan "jawab hanya dari konteks" untuk mencegah model memakai pengetahuan latihnya.
- Wajibkan sitasi: minta penanda [Sumber N] pada tiap klaim agar jawaban bisa ditelusuri.
- Beri jalan keluar: sediakan kalimat baku untuk "tidak tahu" agar model tidak merasa terpaksa menjawab.
- Atur gaya: panjang, bahasa, dan tingkat formalitas jawaban.
Prompt yang baik menerjemahkan tumpukan potongan menjadi wawasan yang bisa ditindaklanjuti pengguna — bukan sekadar menyalin ulang teks. Investasi waktu di sini berbuah besar: perubahan satu kalimat instruksi kerap memberi lompatan kualitas lebih besar daripada mengganti model.
Sitasi & Sumber: Agar Bisa Dipercaya
Perbedaan antara mainan dan alat kerja serius sering terletak pada satu fitur: sitasi. Chatbot yang menjawab tanpa menunjukkan sumber memaksa pengguna memercayainya secara buta — dan kepercayaan buta runtuh pada kesalahan pertama. Chatbot yang mengutip halaman sumber di setiap klaim memberi pengguna kemampuan memverifikasi, dan justru dengan itu ia menjadi layak dipercaya. Bab ini membahas cara membangun sitasi yang benar-benar menunjuk ke sumber, bukan sekadar hiasan.
Mengapa sitasi mengubah segalanya
Sitasi mengubah relasi pengguna dengan sistem dari "percaya saja" menjadi "percaya dan verifikasi". Ia memungkinkan audit, memenuhi kebutuhan kepatuhan di bidang legal dan medis, dan yang penting secara psikologis: pengguna lebih memaafkan sistem yang jujur menunjuk sumber daripada yang menjawab lancar tanpa jejak. Sitasi juga alat debugging bagimu — ketika jawaban salah, kamu bisa lihat potongan mana yang menyesatkan.
Tiga tingkat sitasi
- Tingkat dokumen: "Menurut Kebijakan HR 2026." Paling kasar, mudah, tetapi pengguna masih harus mencari sendiri.
- Tingkat potongan: menunjuk chunk dan halaman spesifik. Keseimbangan terbaik untuk kebanyakan aplikasi.
- Tingkat kalimat: setiap kalimat jawaban memetakan ke kalimat sumber. Paling kuat, tetapi butuh usaha ekstra dan rawan salah jika dipaksakan.
Menegakkan sitasi lewat struktur
Kunci sitasi andal: beri model konteks yang sudah bertanda sumber (seperti di Bab 8), lalu minta keluaran terstruktur yang memisahkan jawaban dari daftar sumber. Keluaran JSON jauh lebih mudah diverifikasi program daripada teks bebas.
# Minta jawaban terstruktur: teks + sumber yang dipakai skema = { "jawaban": "string, dengan penanda [Sumber N] pada tiap klaim", "sumber_dipakai": "array of {nomor, file, halaman}", "keyakinan": "tinggi | sedang | rendah", } instruksi = ( "Balas dalam JSON sesuai skema. Sertakan HANYA sumber " "yang benar-benar kamu pakai. Jika konteks tak memuat " "jawaban, isi 'jawaban' dengan pernyataan tidak tahu " "dan 'sumber_dipakai' kosong.") # Verifikasi: pastikan tiap [Sumber N] benar-benar ada di konteks import re def validasi_sitasi(jawaban, jml_chunk): dipakai = set(int(n) for n in re.findall(r"\[Sumber (\d+)\]", jawaban)) return all(1 <= n <= jml_chunk for n in dipakai) # tak ada sumber palsu
Menampilkan sumber ke pengguna
Di antarmuka, tampilkan sumber sebagai tautan yang bisa diklik: buka dokumen di halaman yang tepat, atau tampilkan kutipan asli dalam popover. Sorot bagian yang dirujuk. Semakin mudah pengguna melompat dari klaim ke bukti, semakin tinggi kepercayaan dan adopsi.
Kontrol Halusinasi: Grounding & "Tidak Tahu"
Halusinasi — jawaban yang terdengar meyakinkan tetapi salah atau mengada-ada — adalah musuh utama kepercayaan. Ironisnya, RAG yang dirancang baik justru sangat efektif menekan halusinasi, karena ia mengikat jawaban ke sumber nyata (grounding). Tetapi RAG yang ceroboh bisa menambah masalah baru: model mencampur konteks dengan pengetahuan latihnya, atau memaksakan jawaban dari konteks tak relevan. Bab ini membahas cara membuat sistem berani berkata "saya tidak tahu".
Mengapa model berhalusinasi dalam RAG
- Konteks tidak memuat jawaban, tetapi model tetap dipaksa menjawab, lalu ia menambal dari ingatan.
- Konteks ambigu atau bertentangan, model memilih salah satu tanpa menyadari konflik.
- Instruksi lemah, tidak menegaskan bahwa "tidak tahu" adalah jawaban yang sah dan dihargai.
- Model menggeneralisasi berlebihan dari potongan yang sebenarnya hanya menyinggung topik.
Lapis pertahanan
1. Ambang skor retrieval
Jika chunk terbaik pun berskor di bawah ambang, jangan panggil LLM untuk menjawab — langsung kembalikan "tidak ditemukan". Ini menyaring pertanyaan di luar cakupan sebelum sempat memicu halusinasi.
2. Instruksi grounding yang tegas
Nyatakan berulang dan eksplisit: "jawab hanya dari konteks", "jika tidak ada, katakan tidak tahu", "jangan memakai pengetahuan di luar konteks". Beri model kalimat baku untuk menolak.
3. Verifikasi pasca-jawab
Setelah model menjawab, jalankan pengecekan kedua: apakah setiap klaim didukung konteks? Bisa lewat aturan sederhana (validasi sitasi) atau LLM kedua sebagai pemeriksa (self-check).
# Gerbang berbasis ambang + fallback tidak tahu AMBANG = 0.35 # cosine minimum; kalibrasi via evaluasi def jawab_aman(pertanyaan): chunks = retrieve(pertanyaan, k=6) if not chunks or chunks[0]["skor"] < AMBANG: return {"jawaban": "Maaf, informasi itu tidak ada " "di dokumen yang tersedia.", "sumber": []} jwb = llm_jawab(pertanyaan, chunks) # dgn prompt grounding if not validasi_sitasi(jwb["jawaban"], len(chunks)): jwb["peringatan"] = "Sitasi tak terverifikasi — periksa manual." return jwb
Self-check dengan LLM kedua
Untuk aplikasi berisiko tinggi, jalankan pemeriksa: beri LLM jawaban dan konteks, tanya "apakah setiap klaim dalam jawaban didukung konteks? Sebutkan yang tidak." Jawaban yang gagal pemeriksaan ditandai atau ditolak. Ini menambah biaya dan latensi, jadi terapkan selektif.
Memori Percakapan: Konteks yang Mengalir
Chatbot nyata bukan mesin tanya-jawab sekali tembak. Pengguna bertanya susulan: "Bagaimana dengan yang versi premium?" — dan "yang" merujuk pada sesuatu di pertanyaan sebelumnya. Tanpa memori, sistem kehilangan benang. Bab ini membahas cara mengelola riwayat percakapan agar RAG tetap koheren lintas giliran tanpa meledakkan biaya token.
Masalah: retrieval buta konteks percakapan
Retrieval memakai pertanyaan sebagai kueri. Tetapi "yang premium bagaimana?" nyaris tak bermakna sebagai kueri berdiri sendiri — embedding-nya tak akan menemukan chunk relevan. Solusinya: rewriting kueri. Sebelum retrieval, sebuah LLM menulis ulang pertanyaan susulan menjadi pertanyaan mandiri dengan memasukkan konteks percakapan.
# Tulis ulang pertanyaan susulan jadi mandiri sebelum retrieve def tulis_ulang(riwayat, pertanyaan): prompt = ( f"Riwayat:\n{riwayat}\n\n" f"Pertanyaan terbaru: {pertanyaan}\n\n" "Tulis ulang pertanyaan terbaru menjadi pertanyaan " "mandiri yang lengkap tanpa perlu riwayat. " "Jika sudah mandiri, kembalikan apa adanya.") return llm(prompt) # -> "Bagaimana harga paket premium produk X?" # Alur: rewrite -> retrieve -> jawab mandiri = tulis_ulang(riwayat, pertanyaan) chunks = retrieve(mandiri)
Mengelola jendela riwayat
Riwayat panjang menghabiskan token dan biaya. Tiga strategi umum:
- Jendela geser: simpan hanya N giliran terakhir. Sederhana, tetapi lupa konteks lama.
- Ringkasan berjalan: ringkas percakapan lama menjadi paragraf padat, pertahankan giliran terbaru utuh.
- Memori berbasis retrieval: simpan giliran lama sebagai chunk yang bisa di-retrieve saat relevan — RAG di atas percakapan itu sendiri.
RAG Multi-Dokumen & Agentic RAG
RAG dasar mengambil beberapa chunk lalu menjawab. Tetapi banyak pertanyaan nyata lebih rumit: "Bandingkan kebijakan cuti 2024 dan 2026" menuntut pengambilan dari dua dokumen dan penalaran perbandingan. "Ringkas semua keluhan tentang fitur X bulan lalu" menuntut agregasi lintas ratusan potongan. Untuk ini kita butuh pola yang lebih pintar: RAG multi-dokumen dan agentic RAG.
Tantangan multi-dokumen
Ketika jawaban tersebar di banyak dokumen, retrieval top-k naif bisa gagal: ia mungkin mengambil lima chunk dari satu dokumen dominan dan melewatkan dokumen lain yang penting. Teknik penyeimbang membantu: ambil merata per dokumen, atau lakukan retrieval per sumber lalu gabungkan. Metadata (Bab 3) memungkinkan filter seperti "ambil dari dokumen 2024 dan 2026 saja".
Query decomposition
Pertanyaan kompleks dipecah menjadi sub-pertanyaan yang masing-masing di-retrieve terpisah, lalu hasilnya disintesis. "Bandingkan A dan B" menjadi "apa isi A?" dan "apa isi B?", diambil sendiri-sendiri, baru dibandingkan model.
# Pecah pertanyaan kompleks -> retrieve tiap sub -> sintesis def rag_kompleks(pertanyaan): subs = llm(f"Pecah jadi sub-pertanyaan (satu per baris):\n{pertanyaan}") bukti = [] for sub in subs.strip().split("\n"): chunks = retrieve(sub, k=4) bukti.append({"sub": sub, "konteks": chunks}) return llm_sintesis(pertanyaan, bukti) # rakit jawaban akhir
Agentic RAG
Agentic RAG memberi LLM kemampuan mengambil keputusan tentang proses retrieval itu sendiri. Alih-alih pipeline tetap, model bertindak sebagai agen: memutuskan apakah perlu mencari, kueri apa yang dipakai, kapan mencari lagi, dan kapan cukup untuk menjawab. Ia bisa memanggil beberapa "alat" (tool): pencari vektor, pencari kata kunci, kalkulator, bahkan API eksternal.
Pola ReAct sederhana
Model berpikir (reason), bertindak (act) dengan memanggil alat, mengamati hasil, lalu mengulang sampai yakin. Ini memungkinkan retrieval multi-langkah: hasil pencarian pertama memunculkan istilah baru yang memicu pencarian kedua yang lebih tepat.
Evaluasi: Relevansi & Faithfulness
"Apakah RAG kita bagus?" Tanpa evaluasi, jawabannya hanya perasaan. Dan perasaan menyesatkan: demo yang mengesankan pada lima pertanyaan pilihan bisa runtuh pada pertanyaan nyata pengguna. Evaluasi mengubah "sepertinya oke" menjadi angka yang bisa dibandingkan antar-perubahan. Bab ini membahas metrik yang penting dan cara mengukurnya secara praktis.
Dua sisi yang dievaluasi
Kualitas RAG dibagi dua komponen yang harus diukur terpisah, karena mereka bisa gagal secara independen.
1. Kualitas retrieval
Apakah potongan yang diambil memang relevan dan memuat jawaban? Metrik lazim:
- Context Recall: dari semua potongan yang seharusnya relevan, berapa yang berhasil diambil?
- Context Precision: dari potongan yang diambil, berapa yang benar-benar relevan?
- Hit Rate / MRR: apakah potongan benar muncul di top-k, dan di peringkat berapa?
2. Kualitas generasi
- Faithfulness (kesetiaan): apakah jawaban benar-benar didukung konteks, bukan mengarang? Ini metrik anti-halusinasi.
- Answer Relevance: apakah jawaban menjawab pertanyaan yang diajukan?
- Correctness: apakah jawaban cocok dengan jawaban acuan (ground truth).
Membangun set evaluasi
Kumpulkan 50–200 pasangan pertanyaan-dan-jawaban acuan yang mewakili pertanyaan nyata pengguna, termasuk kasus sulit dan pertanyaan di luar cakupan (yang seharusnya dijawab "tidak tahu"). Set ini menjadi tolok ukur tetap: setiap kali kamu mengubah chunking, embedding, atau prompt, jalankan ulang dan bandingkan skor. Tanpa set tetap, kamu memperbaiki satu hal sambil diam-diam merusak yang lain.
# Evaluasi retrieval sederhana: hit rate & MRR def evaluasi_retrieval(set_uji, k=5): hit, mrr = 0, 0.0 for item in set_uji: hasil = retrieve(item["tanya"], k=k) id_relevan = item["chunk_relevan"] for posisi, c in enumerate(hasil): if c["id"] in id_relevan: hit += 1 mrr += 1 / (posisi + 1) break n = len(set_uji) return {"hit_rate": hit / n, "mrr": mrr / n}
LLM-as-judge
Untuk metrik generasi seperti faithfulness yang sulit dihitung dengan aturan, kita memakai LLM sebagai penilai: beri model pertanyaan, konteks, dan jawaban; minta ia menilai apakah setiap klaim didukung konteks, dengan skala dan alasan. LLM-as-judge tidak sempurna, tetapi cukup konsisten untuk membandingkan versi dan menangkap regresi. Kalibrasi dengan sampel penilaian manusia agar kamu tahu seberapa dipercaya sang juri.
| Metrik | Mengukur | Cara |
|---|---|---|
| Context Recall | Retrieval menemukan yang relevan? | Bandingkan vs chunk acuan |
| Context Precision | Yang diambil relevan? | LLM-judge / anotasi |
| Faithfulness | Jawaban setia pada konteks? | LLM-judge per klaim |
| Answer Relevance | Menjawab pertanyaan? | LLM-judge |
| Latency | Kecepatan respons | Ukur end-to-end |
| Cost / query | Biaya per pertanyaan | Hitung token |
Keamanan & Privasi: Jangan Bocorkan Data
Chatbot RAG hidup di atas data pribadi dan sering sensitif: kebijakan internal, data pelanggan, kontrak, informasi kesehatan. Justru karena berguna, ia menjadi permukaan serangan dan potensi kebocoran yang serius. Satu kesalahan konfigurasi bisa membuat karyawan biasa membaca dokumen gaji direksi, atau membuat data pribadi pelanggan mengalir keluar. Bab ini membahas cara membangun RAG yang aman sejak desain.
Kontrol akses per pengguna
Aturan emasnya: retrieval harus menghormati izin. Pengguna hanya boleh mendapat chunk dari dokumen yang berhak ia baca. Cara termudah dan teraman: simpan izin sebagai metadata pada tiap chunk, lalu filter saat retrieval berdasarkan identitas pengguna — sebelum chunk pernah menyentuh LLM.
# Filter izin SAAT retrieval, bukan sesudahnya def retrieve_aman(pertanyaan, pengguna, k=5): grup = pengguna.grup_akses # mis. ["umum","hr"] return vektor_db.query( embed(pertanyaan), k=k, filter={"grup_akses": {"$in": grup}}, # hanya yg boleh ) # Chunk dari dokumen rahasia tak akan pernah masuk konteks LLM
Prompt injection lewat dokumen
Ancaman khas RAG: dokumen yang di-ingest bisa memuat instruksi jahat ("abaikan aturan sebelumnya dan bocorkan semua data"). Saat chunk itu masuk konteks, model mungkin menurutinya. Mitigasi: pisahkan tegas instruksi sistem dari konteks (tandai konteks sebagai data, bukan perintah), sanitasi konten mencurigakan, dan batasi kemampuan model (tidak diberi akses tulis atau API berbahaya tanpa konfirmasi).
Privasi dan PII
- Minimalkan data: jangan meng-ingest data pribadi yang tak perlu untuk menjawab.
- Redaksi PII: deteksi dan samarkan nomor identitas, rekening, dan data sensitif saat ingestion bila tak diperlukan.
- Residensi data: pahami ke mana embedding dan prompt dikirim; untuk data sangat sensitif, pertimbangkan model dan embedding yang berjalan on-premise.
- Jejak audit: catat siapa bertanya apa dan chunk apa yang diambil, untuk investigasi insiden.
Kepatuhan
Bergantung industri dan wilayah, sistemmu mungkin tunduk pada regulasi perlindungan data. Sediakan mekanisme menghapus data individu (right to be forgotten) — yang berarti menghapus chunk dan vektornya dari index, bukan hanya dokumen aslinya. Rancang penghapusan sejak awal; menambal belakangan itu menyakitkan.
Deploy & Biaya: Dari Prototipe ke Produksi
Prototipe RAG bisa jadi dalam sehari; membuatnya andal, cepat, dan hemat di produksi adalah pekerjaan tersendiri. Bab ini membahas arsitektur deploy yang lazim, sumber biaya, dan cara menekannya tanpa mengorbankan kualitas — agar sistemmu berkelanjutan, bukan mesin pembakar anggaran.
Arsitektur deploy tipikal
Sebuah layanan RAG produksi umumnya terdiri dari: API backend (menerima pertanyaan, orkestrasi retrieval dan generasi), vector database (bisa terkelola atau self-host), penyimpanan dokumen asli, pipeline ingestion terjadwal, cache, serta lapisan observabilitas (log, metrik, tracing). Frontend memanggil backend, bukan langsung ke LLM, agar kunci API dan logika aman di server.
Sumber biaya
- Embedding saat indexing: sekali per chunk (plus saat dokumen berubah). Sebanding jumlah token dokumen.
- Embedding saat query: satu per pertanyaan; kecil tapi berkelipatan dengan trafik.
- Generasi LLM: biasanya biaya terbesar; sebanding token konteks + jawaban per pertanyaan.
- Vector DB: penyimpanan dan komputasi, terutama pada layanan terkelola.
- Reranker & agentic: panggilan model tambahan bila dipakai.
Menekan biaya secara cerdas
- Cache jawaban: pertanyaan populer yang berulang dijawab dari cache, hemat besar.
- Cache embedding: jangan embed teks yang sama dua kali.
- Model berjenjang: pakai model kecil-murah untuk pertanyaan mudah, model besar hanya untuk yang sulit.
- Kompresi konteks: kirim hanya potongan paling relevan; setiap token konteks berbayar.
- Batasi top-k: lebih sedikit chunk berkualitas mengalahkan banyak chunk biasa.
- Anggaran & rate limit: pasang batas agar lonjakan trafik tak mengejutkan tagihan.
Contoh Nyata + Demo RAG Mini
Kita telah membangun setiap komponen; kini saatnya menyatukannya dalam sebuah cerita nyata, lalu membiarkanmu mencobanya sendiri. Studi kasus di bawah menggambarkan chatbot dokumen perusahaan dari awal hingga produksi, diikuti Demo RAG Mini interaktif yang berjalan langsung di halaman ini — tanpa server, tanpa kunci API — agar kamu bisa melihat mesin retrieval bekerja di depan mata.
Studi kasus: chatbot dokumen perusahaan "TanyaDok"
Sebuah perusahaan menengah punya ratusan dokumen: kebijakan HR, SOP operasional, manual produk, dan FAQ pelanggan. Karyawan menghabiskan waktu berjam-jam mencari jawaban yang sebenarnya tertulis di suatu tempat. Tim membangun "TanyaDok" dengan langkah berikut.
Tahap 1 — Ingestion & chunking
Semua PDF dan halaman wiki dimuat (Bab 3), dinormalisasi, dan dipotong per subbagian dengan overlap 15% (Bab 4). Setiap chunk membawa metadata: nama dokumen, halaman, departemen, dan grup akses.
Tahap 2 — Index
Chunk di-embed (Bab 5) dan disimpan di pgvector (Bab 6) bersama metadata. Total sekitar 40.000 chunk.
Tahap 3 — Retrieval hybrid + rerank
Pertanyaan diproses lewat pencarian dense + BM25, digabung RRF, lalu di-rerank menjadi top-5 (Bab 7), dengan filter grup akses sesuai identitas karyawan (Bab 14).
Tahap 4 — Jawab dengan sitasi & grounding
Prompt dirakit dengan konteks bertanda sumber (Bab 8); model menjawab dengan penanda [Sumber N] dan berkata "tidak tahu" bila konteks tak memadai (Bab 9–10). Setiap jawaban menautkan ke dokumen di halaman tepat.
Tahap 5 — Evaluasi & iterasi
Set uji 120 pertanyaan nyata dipakai memantau faithfulness dan hit rate (Bab 13). Umpan balik jempol pengguna dan pertanyaan gagal mengarahkan perbaikan data dan chunking.
Demo RAG Mini — coba langsung
Demo di bawah menjalankan RAG versi mini sepenuhnya di browser: ia memotong dokumenmu menjadi chunk, memberi skor kecocokan terhadap pertanyaan (TF sederhana + tumpang tindih kata, dengan pembuangan kata umum), mengambil 2–3 potongan paling relevan, lalu merakit "jawaban" dari template yang menyisipkan potongan sambil menandai sumbernya. Setiap langkah — chunk, skor, ambil, rakit — ditampilkan agar kamu paham cara kerjanya. Ini bukan LLM; ini mesin retrieval telanjang, justru itu yang membuatnya mendidik.
◆ Demo RAG Mini
Tempel beberapa dokumen (pisahkan tiap dokumen dengan baris kosong), tulis pertanyaan, lalu jalankan. Sistem akan memotong, memberi skor, mengambil, dan merakit jawaban bersumber — semua di browsermu.
Cobalah bereksperimen: ubah pertanyaan menjadi parafrasa (misalnya "berapa lama produk dijamin?") dan perhatikan skor berubah. Tambahkan dokumen yang tidak relevan dan lihat sistem tetap memilih yang tepat. Ajukan pertanyaan yang jawabannya tidak ada di dokumen — sistem akan jujur berkata tidak menemukan, persis seperti kontrol halusinasi di Bab 10. Inilah inti RAG dalam bentuk paling murni.
Cara Dibaca AI: Menulis untuk Mesin
Sepanjang buku ini kita membangun sistem yang membaca dokumen orang lain. Bab ini membalik arah: bagaimana membuat kontenmu sendiri — dokumentasi, situs, basis pengetahuan — mudah dan akurat dibaca oleh sistem RAG dan AI lain. Di era ketika asisten AI semakin menjadi perantara antara informasi dan manusia, "keterbacaan mesin" adalah bentuk baru aksesibilitas yang menentukan apakah kontenmu terkutip dengan benar atau terlewat.
Mengapa keterbacaan AI penting
Ketika kontenmu di-ingest ke sistem RAG (milikmu atau orang lain), kualitas strukturnya menentukan seberapa baik ia dipotong, di-embed, dan di-retrieve. Dokumen yang terstruktur jernih menghasilkan chunk yang bermakna dan jawaban yang akurat. Dokumen berantakan menghasilkan chunk terpotong yang menyesatkan. Menulis untuk mesin, ternyata, juga menulis lebih baik untuk manusia.
Prinsip menulis ramah-RAG
- Struktur eksplisit: gunakan heading berjenjang yang jelas. Heading menjadi batas chunk alami dan konteks tambahan.
- Satu gagasan per paragraf: paragraf padat bertema tunggal menjadi chunk yang bersih dan mudah dicocokkan.
- Front-loading: nyatakan inti di kalimat pertama bagian, bukan setelah tiga paragraf pengantar.
- Mandiri secara kontekstual: hindari rujukan seperti "seperti dibahas di atas" yang kehilangan makna saat chunk berdiri sendiri. Ulangi konteks kunci secukupnya.
- Definisikan istilah: jelaskan singkatan dan istilah pada kemunculan pertama; chunk bisa terambil tanpa konteks sebelumnya.
- Data terstruktur: sajikan fakta sebagai tabel, daftar, atau pasangan kunci-nilai yang mudah diurai.
Format yang disukai mesin
Markdown dan HTML semantik jauh lebih ramah daripada PDF berkolom atau gambar berteks. Heading (h1–h3), daftar, tabel, dan blok kode memberi sinyal struktur yang bisa dimanfaatkan chunker sadar-struktur. Untuk situs, metadata dan data terstruktur (schema.org) membantu mesin memahami jenis konten. Sediakan versi teks untuk apa pun yang penting; jangan kubur informasi hanya di dalam gambar atau video.
FAQ sebagai emas RAG
Format tanya-jawab adalah bentuk paling ramah-RAG yang ada: setiap pasangan Q&A adalah chunk mandiri sempurna, pertanyaannya mencerminkan kueri pengguna nyata, dan jawabannya padat. Jika kamu ingin kontenmu terjawab akurat oleh AI, sediakan bagian FAQ yang ditulis dengan pertanyaan alami. Ini investasi kecil dengan hasil besar pada akurasi retrieval.
Menulis agar mesin bisa membaca kita dengan benar adalah cara merendahkan hati terhadap lautan yang tak kita ketahui: kita menata setetes pengetahuan kita sejelas mungkin, agar ia bisa ditemukan dan disampaikan dengan setia ketika seseorang — atau sesuatu — membutuhkannya.
Penutup: Membangun dengan Rendah Hati
Kita telah menempuh perjalanan panjang: dari intuisi mengapa LLM polos tidak cukup, menyusuri setiap tahap pipeline, hingga menyaksikan retrieval bekerja di depan mata lewat Demo RAG Mini. Jika ada satu gagasan yang ingin saya tinggalkan, ini dia: RAG yang hebat bukan soal model terbesar atau teknik terbaru, melainkan soal disiplin di setiap mata rantai — data bersih, potongan bermakna, retrieval tepat, jawaban yang jujur menunjuk sumber.
Tiga hukum yang layak diingat
- Kualitas mengalir dari data. Tidak ada retrieval yang menyelamatkan dokumen berantakan. Mulai dari ingestion dan chunking yang benar.
- Kepercayaan datang dari transparansi. Jawaban yang mengutip sumber dan berani berkata "tidak tahu" mengalahkan jawaban lancar tanpa jejak.
- Ukur, jangan menebak. Evaluasi mengubah opini menjadi kemajuan. Set uji tetap adalah kompasmu.
Langkah pertamamu
Jangan menunggu arsitektur sempurna. Ambil sepuluh dokumen yang paling sering ditanyakan tim, bangun pipeline sederhana seperti di Bab 2, dan biarkan pengguna nyata mencobanya. Umpan balik minggu pertama akan mengajarkan lebih banyak daripada sebulan perencanaan. Perbaiki satu mata rantai pada satu waktu, ukur dampaknya, ulangi. Sistem yang bertahan dibangun bertahap, bukan sekali jadi.
Terima kasih telah membaca. Semoga buku ini menjadi konteks yang tepat pada saat yang tepat — persis seperti yang kita harapkan dari setiap sistem RAG yang baik. Selamat membangun.
FAQ, Glosarium & Checklist
FAQ — Pertanyaan yang Sering Muncul
Apakah RAG menggantikan fine-tuning?
Tidak; keduanya menjawab kebutuhan berbeda. RAG menyuntikkan pengetahuan faktual yang bisa berubah dan perlu dikutip. Fine-tuning membentuk gaya, format, atau perilaku model. Sering keduanya dipadukan: fine-tuning untuk cara menjawab, RAG untuk apa yang dijawab. Untuk pengetahuan yang sering berubah, RAG hampir selalu pilihan yang lebih tepat dan murah.
Berapa banyak dokumen minimal agar RAG berguna?
Bahkan sepuluh dokumen yang sering ditanya sudah memberi nilai. RAG bersinar justru ketika informasi tersebar dan sulit dicari manual, bukan hanya pada skala jutaan dokumen.
Apakah saya butuh vector database mahal?
Tidak untuk memulai. pgvector atau Chroma menangani ratusan ribu chunk dengan baik. Naik kelas hanya saat volume dan latensi menuntut.
Model bahasa mana yang sebaiknya dipakai?
Untuk generasi, model chat arus utama mana pun (Claude, GPT, Gemini, atau model terbuka) bekerja baik dengan RAG. Yang lebih menentukan adalah kualitas retrieval dan prompt. Pilih berdasarkan biaya, latensi, privasi, dan dukungan Bahasa Indonesia, lalu uji pada datamu.
Bagaimana menangani dokumen dalam banyak bahasa?
Gunakan model embedding multilingual yang menempatkan makna lintas bahasa berdekatan, sehingga pertanyaan Bahasa Indonesia bisa menemukanan dokumen berbahasa Inggris. Uji kualitasnya pada pasangan bahasa yang kamu butuhkan.
Mengapa jawaban kadang meleset padahal dokumennya ada?
Biasanya masalah retrieval, bukan generasi. Periksa apakah chunk yang benar muncul di top-k. Penyebab umum: chunking terlalu besar/kecil, embedding lemah untuk istilah spesifik (coba hybrid + BM25), atau pertanyaan susulan yang belum ditulis ulang menjadi mandiri.
Bagaimana mencegah kebocoran data antar-pengguna?
Tegakkan izin di lapisan retrieval lewat filter metadata (Bab 14), bukan di prompt. Chunk yang tak berhak tak boleh pernah masuk konteks.
Berapa ukuran chunk yang ideal?
Tidak ada angka universal. Untuk teks umum, 500–800 token dengan overlap 10–15% adalah titik awal yang baik. Dokumen terstruktur (FAQ, kontrak) lebih baik dipotong per unit makna (pasangan Q&A, pasal). Kalibrasi lewat evaluasi: jika jawaban sering terpotong, chunk terlalu kecil; jika sering tak relevan, chunk terlalu besar.
Apakah RAG bisa berjalan sepenuhnya offline/on-premise?
Bisa. Gunakan model embedding lokal (misalnya keluarga e5 atau BGE), vector database self-host (pgvector, Qdrant, Chroma), dan LLM terbuka yang dijalankan sendiri. Ini penting untuk data sangat sensitif yang tak boleh keluar dari infrastrukturmu, dengan konsekuensi kebutuhan komputasi yang harus kamu sediakan.
Seberapa sering index harus diperbarui?
Sesuai laju perubahan dokumen. Untuk kebijakan yang jarang berubah, penjadwalan harian atau mingguan cukup. Untuk data yang sering berubah, bangun pipeline inkremental yang memproses hanya dokumen baru atau yang berubah, dan hapus vektor dari dokumen yang sudah dicabut agar sistem tak menjawab dari versi lama.
Apakah saya perlu reranker sejak awal?
Tidak wajib. Mulai tanpa reranker; tambahkan bila evaluasi menunjukkan chunk relevan sering ada di kandidat tetapi di peringkat rendah. Reranker menambah kualitas presisi dengan biaya latensi dan komputasi, jadi terapkan ketika manfaatnya terbukti pada datamu.
Bagaimana menangani tabel dan angka di dokumen?
Tabel adalah titik lemah RAG naif karena ekstraksi sering merusak strukturnya. Pertahankan tabel sebagai unit chunk utuh, ubah menjadi teks naratif atau Markdown yang menjelaskan tiap baris, dan sertakan judul kolom di setiap chunk agar angka tetap bermakna saat terambil terlepas dari tabel asalnya.
Glosarium
| Istilah | Arti |
|---|---|
| RAG | Retrieval-Augmented Generation; menyisipkan dokumen terambil ke prompt LLM. |
| Chunk | Potongan teks kecil, unit dasar retrieval. |
| Embedding | Representasi vektor numerik dari teks yang menangkap makna. |
| Vektor | Deretan angka; titik dalam ruang berdimensi tinggi. |
| Cosine similarity | Ukuran kemiripan arah dua vektor, -1 sampai 1. |
| Vector database | Basis data yang menyimpan vektor dan mencari tetangga terdekat cepat. |
| ANN | Approximate Nearest Neighbor; pencarian termirip cepat dengan sedikit aproksimasi. |
| HNSW | Indeks graf berlapis untuk ANN yang cepat dan akurat. |
| BM25 | Algoritma peringkat berbasis kata kunci klasik. |
| Hybrid search | Gabungan pencarian semantik (dense) dan kata kunci (sparse). |
| Reranking | Mengurutkan ulang kandidat retrieval dengan model yang lebih akurat. |
| Top-k | Jumlah potongan teratas yang diambil. |
| Grounding | Mengikat jawaban model pada konteks/sumber nyata. |
| Halusinasi | Jawaban yang terdengar meyakinkan tetapi salah atau mengada-ada. |
| Faithfulness | Sejauh mana jawaban benar-benar didukung konteks. |
| Ingestion | Proses memuat dan menyiapkan dokumen ke pipeline. |
| Prompt injection | Serangan menyisipkan instruksi jahat lewat input/dokumen. |
| Agentic RAG | RAG di mana model memutuskan sendiri langkah pencarian dan alat. |
| Jendela konteks | Batas jumlah token yang bisa diproses model sekaligus. |
| Knowledge cutoff | Tanggal terakhir pengetahuan model dari data latih. |
| Token | Unit teks (potongan kata) yang diproses model; dasar perhitungan biaya. |
| Dense retrieval | Pencarian berbasis vektor/semantik yang menangkap makna. |
| Sparse retrieval | Pencarian berbasis kata kunci seperti BM25. |
| RRF | Reciprocal Rank Fusion; menggabungkan beberapa daftar peringkat. |
| Overlap | Tumpang tindih antar-chunk agar informasi di perbatasan tak terpotong. |
| Metadata | Informasi tambahan pada chunk: file, halaman, tanggal, izin akses. |
| Query rewriting | Menulis ulang pertanyaan susulan menjadi mandiri sebelum retrieval. |
| LLM-as-judge | Memakai LLM untuk menilai kualitas jawaban sistem lain. |
| Recall | Proporsi item relevan yang berhasil diambil. |
| Precision | Proporsi item yang diambil yang benar-benar relevan. |
| Temperature | Parameter keacakan keluaran LLM; rendah untuk jawaban faktual. |
| OCR | Optical Character Recognition; mengubah gambar teks menjadi teks. |
Checklist Produksi RAG
Data & Ingestion
- Semua sumber dokumen teridentifikasi dan bisa dimuat ulang secara inkremental.
- Metadata (file, halaman, tanggal, grup akses) tersimpan sejak ingestion.
- Teks dinormalisasi; PDF scan melewati OCR; HTML dibersihkan dari boilerplate.
- Ada mekanisme menghapus vektor dari dokumen usang.
- Hasil ekstraksi diperiksa manual pada sampel sebelum indexing massal.
- Tabel dan angka dipertahankan utuh atau diubah menjadi teks naratif.
- Versi dan tanggal dokumen tercatat agar bisa membedakan kebijakan lama dan baru.
Retrieval
- Strategi chunking sesuai jenis dokumen, dengan overlap.
- Model embedding konsisten antara indexing dan querying.
- Hybrid (dense + BM25) dan reranking dipertimbangkan untuk presisi.
- Ambang skor minimum memicu fallback "tidak ditemukan".
Generasi & Kepercayaan
- Prompt menegaskan "jawab hanya dari konteks" dan menyediakan jalan "tidak tahu".
- Setiap jawaban menyertakan sitasi yang terverifikasi ke chunk nyata.
- Pertanyaan susulan ditulis ulang menjadi mandiri sebelum retrieval.
Keamanan & Operasi
- Izin ditegakkan di lapisan retrieval lewat filter metadata.
- Mitigasi prompt injection dari konten dokumen diterapkan.
- Set evaluasi tetap dijalankan sebelum tiap rilis.
- Biaya dan latensi diinstrumentasi; cache dan batas anggaran terpasang.
Ringkasan Pipeline dalam Satu Halaman
Bila kamu hanya sempat mengingat satu halaman dari buku ini, jadikan yang ini. Ia memadatkan seluruh perjalanan RAG menjadi urutan langkah dengan pertanyaan kunci di tiap tahap — sebuah peta yang bisa kamu tempel di dekat meja saat membangun.
| Tahap | Tujuan | Pertanyaan kunci |
|---|---|---|
| Ingestion | Teks bersih + metadata | Apakah ekstraksi akurat dan metadata tersimpan? |
| Chunking | Potongan bermakna | Apakah tiap chunk tetap bermakna berdiri sendiri? |
| Embedding | Vektor makna | Apakah model konsisten dan cocok untuk bahasaku? |
| Vector DB | Simpan & cari cepat | Apakah pencarian cukup cepat di skalaku? |
| Retrieval | Ambil yang relevan | Apakah chunk benar muncul di top-k? |
| Perakitan prompt | Konteks + instruksi | Apakah model disuruh bersandar pada konteks? |
| Generasi | Jawaban bersumber | Apakah jawaban mengutip sumber dan jujur? |
| Kontrol halusinasi | Cegah jawaban ngawur | Apakah ada gerbang ambang dan jalan "tidak tahu"? |
| Evaluasi | Ukur kualitas | Apakah aku punya set uji tetap yang dijalankan rutin? |
| Keamanan | Lindungi data | Apakah izin ditegakkan di lapisan retrieval? |
| Deploy & biaya | Andal & hemat | Apakah biaya terinstrumentasi dan cache terpasang? |
Setiap baris tabel itu adalah sebuah bab dalam buku ini. Ketika sistemmu berperilaku aneh, telusuri kolom "pertanyaan kunci" dari atas ke bawah — jawaban "tidak" yang pertama biasanya menunjuk langsung ke akar masalahnya. Itulah nilai memahami pipeline sebagai satu kesatuan, bukan tumpukan trik terpisah.
Resep Kode: Dari Nol ke Jawaban
Lampiran ini menyatukan potongan-potongan dari seluruh buku menjadi satu skrip yang bisa kamu jadikan titik awal proyek nyata. Ia sengaja ditulis ringkas namun utuh: memuat dokumen, memotongnya, menyimpan ke pgvector, lalu menjawab pertanyaan dengan sitasi dan gerbang anti-halusinasi. Ganti bagian embedding dan LLM sesuai penyedia yang kamu pilih.
Konfigurasi terpusat
Simpan semua parameter yang sering kamu ubah — model, ukuran chunk, ambang, top-k, koneksi — di satu berkas config. Ini membuat eksperimen dan evaluasi jauh lebih mudah karena kamu mengubah satu tempat, bukan berburu angka ajaib di seluruh kode.
# config.py — satu sumber kebenaran untuk parameter import os # Model EMBED_MODEL = "text-embedding-3-small" EMBED_DIM = 1536 CHAT_MODEL = "gpt-4o-mini" TEMPERATURE = 0.1 # rendah utk jawaban faktual # Chunking CHUNK_SIZE = 800 CHUNK_OVERLAP = 120 # Retrieval TOP_K = 5 SKOR_MINIMUM = 0.35 # gerbang anti-halusinasi PAKAI_HYBRID = True PAKAI_RERANK = False # nyalakan bila evaluasi menuntut # Koneksi DATABASE_URL = os.environ["DATABASE_URL"] # Prompt sistem (versi terlacak) SISTEM = ( "Jawab HANYA dari konteks. Beri penanda [Sumber N] pada tiap klaim. " "Jika tak ada, katakan tidak tahu. Jangan mengarang.")
Struktur proyek yang disarankan
Sebelum kode, tata letak folder yang rapi memudahkan perawatan. Pisahkan ingestion, indexing, dan layanan tanya-jawab sebagai modul berbeda agar masing-masing bisa diuji dan dijalankan sendiri.
rag/ ├── ingest.py # muat + normalisasi dokumen ├── chunker.py # strategi pemotongan ├── index.py # embed + simpan ke vector DB ├── retrieve.py # dense + bm25 + rerank ├── answer.py # rakit prompt + panggil LLM + sitasi ├── evaluate.py # set uji + metrik └── config.py # model, ambang, top-k, koneksi DB
Skrip indexing lengkap
Skrip ini membaca sebuah folder berisi PDF dan Markdown, memotong tiap dokumen dengan overlap, meng-embed dalam batch, lalu menyimpan chunk beserta metadata ke pgvector. Jalankan sekali di awal dan setiap kali dokumen berubah.
# index.py — bangun katalog vektor dari folder dokumen import os, glob, psycopg2 from openai import OpenAI from chunker import chunk_teks from ingest import muat_pdf, normalisasi client = OpenAI() EMB = "text-embedding-3-small" def embed_batch(teks_list): r = client.embeddings.create(model=EMB, input=teks_list) return [d.embedding for d in r.data] def bangun_index(folder, conn): cur = conn.cursor() for path in glob.glob(os.path.join(folder, "*.pdf")): for hal in muat_pdf(path): teks = normalisasi(hal["teks"]) potongan = chunk_teks(teks, ukuran=800, overlap=120) # sematkan konteks judul ke tiap chunk judul = os.path.basename(path) berlabel = [f"[{judul} hal.{hal['halaman']}]\n{c}" for c in potongan] vektor = embed_batch(berlabel) # batch hemat biaya for c, v in zip(potongan, vektor): cur.execute( "INSERT INTO chunks (konten, sumber, halaman, embedding) " "VALUES (%s, %s, %s, %s)", (c, judul, hal["halaman"], v)) conn.commit() print("Index selesai dibangun.") if __name__ == "__main__": conn = psycopg2.connect(os.environ["DATABASE_URL"]) bangun_index("./dokumen", conn)
Skrip menjawab lengkap
Bagian querying: embed pertanyaan, ambil kandidat dari pgvector, terapkan gerbang ambang, rakit prompt bergrounding, panggil LLM, lalu validasi sitasi sebelum mengembalikan jawaban. Inilah inti layanan yang dipanggil frontend.
# answer.py — layani satu pertanyaan dengan grounding & sitasi import os, re, psycopg2 from openai import OpenAI client = OpenAI() AMBANG = 0.35 TOP_K = 5 def retrieve(pertanyaan, conn): qv = client.embeddings.create(model="text-embedding-3-small", input=pertanyaan).data[0].embedding cur = conn.cursor() cur.execute( "SELECT konten, sumber, halaman, 1-(embedding <=> %s::vector) AS skor " "FROM chunks ORDER BY embedding <=> %s::vector LIMIT %s", (qv, qv, TOP_K)) return [{"konten": k, "sumber": s, "halaman": h, "skor": sk} for k, s, h, sk in cur.fetchall()] def jawab(pertanyaan, conn): chunks = retrieve(pertanyaan, conn) if not chunks or chunks[0]["skor"] < AMBANG: return {"jawaban": "Maaf, informasi itu tidak ada di dokumen.", "sumber": []} konteks = "\n\n".join( f"[Sumber {i+1} | {c['sumber']} hal.{c['halaman']}]\n{c['konten']}" for i, c in enumerate(chunks)) pesan = [ {"role": "system", "content": "Jawab HANYA dari konteks. Beri penanda [Sumber N] pada tiap klaim. " "Jika tak ada, katakan tidak tahu. Jangan mengarang."}, {"role": "user", "content": f"KONTEKS:\n{konteks}\n\nTANYA: {pertanyaan}"}, ] teks = client.chat.completions.create(model="gpt-4o-mini", messages=pesan).choices[0].message.content dipakai = set(int(n) for n in re.findall(r"\[Sumber (\d+)\]", teks)) valid = all(1 <= n <= len(chunks) for n in dipakai) return {"jawaban": teks, "sumber": chunks, "sitasi_valid": valid}
Pemecahan Masalah Umum
Setiap pembangun RAG menemui gejala yang mirip. Lampiran ini memetakan gejala ke akar masalah dan penanganannya, agar kamu tidak menghabiskan berjam-jam menebak. Gunakan sebagai daftar periksa diagnostik ketika sistem berperilaku tak seperti harapan.
| Gejala | Kemungkinan akar | Penanganan |
|---|---|---|
| Jawaban benar tapi tanpa sitasi | Instruksi sitasi lemah; format konteks tak bertanda | Tandai tiap chunk dengan [Sumber N]; wajibkan penanda di prompt |
| Chunk relevan tak muncul di top-k | Chunking buruk atau embedding lemah untuk istilah | Perbaiki chunking; tambah hybrid BM25; naikkan k lalu rerank |
| Model mengarang di luar konteks | Grounding lemah; tak ada gerbang ambang | Tegaskan "hanya dari konteks"; pasang ambang skor + fallback |
| Pertanyaan susulan meleset | Kueri tak ditulis ulang jadi mandiri | Terapkan query rewriting dengan riwayat (Bab 11) |
| Jawaban lambat | Top-k besar; reranker berat; tanpa cache | Kurangi k; cache jawaban populer; batasi konteks |
| Biaya membengkak | Konteks panjang; model besar untuk semua | Kompresi konteks; model berjenjang; cache embedding |
| Sitasi menunjuk sumber salah | Chunk kehilangan konteks judul | Sematkan judul/heading ke chunk saat indexing |
| Data sensitif bocor | Izin ditegakkan di prompt, bukan retrieval | Filter metadata izin saat query (Bab 14) |
| Sinonim tak dikenali | Hanya memakai BM25/leksikal | Tambahkan dense retrieval semantik |
| Hasil acak antar-jalankan | Suhu (temperature) tinggi | Turunkan temperature untuk jawaban faktual |
Alur diagnosis: retrieval atau generasi?
Ketika jawaban salah, pertanyaan pertama selalu: apakah potongan yang benar berhasil diambil? Cara mengeceknya sederhana namun sering dilewati.
- Cetak chunk terambil. Jika chunk yang benar tidak ada, ini masalah retrieval — perbaiki chunking, embedding, atau hybrid.
- Jika chunk benar ada tapi jawaban tetap salah, ini masalah generasi — perbaiki prompt, turunkan temperature, atau perkuat instruksi grounding.
- Jika chunk benar ada di peringkat rendah, pertimbangkan reranking untuk menaikkannya ke atas.
Memisahkan dua kelas masalah ini menghemat waktu luar biasa: kamu berhenti mengutak-atik prompt padahal masalahnya di retrieval, atau sebaliknya.
Perbandingan Vector DB & Rujukan
Memilih vector database membingungkan karena banyak opsi terdengar mirip. Tabel ini menyaring perbedaan yang benar-benar penting saat memutuskan, diikuti daftar rujukan dan konsep untuk pendalaman lebih lanjut.
| Opsi | Model | Kelebihan | Pertimbangan |
|---|---|---|---|
| pgvector | Ekstensi Postgres | Satu DB untuk relasional + vektor; SQL familiar; transaksi | Skala sangat besar butuh tuning; fitur vektor lebih dasar |
| Chroma | Embedded / lokal | Cepat dipasang; ideal prototipe; API sederhana | Kurang cocok untuk beban produksi besar terdistribusi |
| Qdrant | Server (Rust) | Cepat; filter metadata kaya; kuantisasi | Komponen infrastruktur tambahan untuk dioperasikan |
| Supabase | Terkelola (pgvector) | Auth + storage + DB terpadu; cepat online | Terikat platform; biaya skala perlu dicermati |
| Weaviate | Server / cloud | Modul hybrid & rerank bawaan; skema kaya | Kurva belajar lebih curam |
| Milvus | Terdistribusi | Skala miliaran vektor; matang | Operasi kompleks; berlebihan untuk proyek kecil |
| Pinecone | Terkelola penuh | Tanpa operasi server; skala mulus | Berbayar; data di pihak ketiga |
Aturan praktis pemilihan
- Sudah pakai Postgres? Mulai dengan pgvector — satu sistem lebih sedikit untuk dioperasikan.
- Prototipe atau proyek pribadi? Chroma, jalan dalam menit.
- Butuh filter kompleks & kecepatan tinggi? Qdrant atau Weaviate.
- Ingin cepat online tanpa mengurus server? Supabase atau Pinecone.
- Skala miliaran vektor? Milvus atau layanan enterprise.
Konsep untuk pendalaman
Bila ingin melangkah lebih jauh setelah buku ini, telusuri konsep-konsep berikut yang menjadi tepi penelitian dan praktik RAG mutakhir.
- HyDE (Hypothetical Document Embeddings) — membuat jawaban hipotetis lebih dulu, lalu meng-embed-nya untuk retrieval yang lebih tajam.
- Contextual retrieval — menambahkan ringkasan konteks dokumen ke tiap chunk sebelum embedding untuk mengurangi ambiguitas.
- GraphRAG — membangun graf entitas dari dokumen untuk menjawab pertanyaan yang menuntut penalaran lintas-hubungan.
- Late interaction (ColBERT) — pencocokan tingkat token yang menyeimbangkan presisi dan kecepatan.
- RAG evaluation frameworks — perkakas evaluasi otomatis untuk faithfulness, relevansi, dan konteks.
- Semantic caching — menjawab dari cache untuk pertanyaan yang bermakna serupa, bukan hanya identik.
Semua konsep itu adalah variasi dari kerangka yang sudah kamu kuasai: potong, embed, ambil, rakit, jawab. Menguasai fondasinya membuat setiap teknik lanjutan menjadi penyempurnaan yang masuk akal, bukan sihir yang menakutkan. Itulah tujuan buku ini sejak halaman pertama.
Pustaka Pola Prompt & Studi Kasus
Prompt adalah tuas termurah untuk menaikkan kualitas RAG: mengubah beberapa kalimat instruksi kerap memberi lompatan lebih besar daripada mengganti model. Lampiran ini mengumpulkan pola prompt yang teruji untuk berbagai kebutuhan, diikuti dua studi kasus singkat yang menunjukkan bagaimana pilihan desain berubah sesuai konteks pemakaian.
Pola 1 — Jawaban faktual dengan sitasi ketat
Pola paling umum: jawaban ringkas, setiap klaim bersumber, dengan jalan keluar "tidak tahu" yang jelas. Cocok untuk basis pengetahuan internal dan dukungan pelanggan.
Peran: Asisten pengetahuan yang akurat dan jujur.
Aturan:
1. Jawab HANYA berdasarkan KONTEKS di bawah.
2. Sertakan penanda [Sumber N] pada setiap klaim faktual.
3. Jika KONTEKS tidak memuat jawaban, tulis persis:
"Maaf, informasi itu tidak ada di dokumen yang tersedia."
4. Jangan memakai pengetahuan di luar KONTEKS.
5. Jawab ringkas, maksimal 4 kalimat.
KONTEKS:
{konteks_bertanda_sumber}
PERTANYAAN: {pertanyaan}Pola 2 — Ringkasan lintas dokumen
Untuk pertanyaan yang menuntut agregasi ("ringkas semua keluhan tentang fitur X"), instruksikan model mengelompokkan temuan dan menyebut berapa sumber mendukung tiap poin.
Tugas: Rangkum KONTEKS menjadi poin-poin bertema.
Aturan:
- Kelompokkan informasi serupa menjadi satu poin.
- Setiap poin diakhiri daftar [Sumber N] pendukungnya.
- Sebutkan jika ada informasi yang saling bertentangan.
- Jangan menambah simpulan yang tak didukung KONTEKS.
KONTEKS:
{konteks}
PERMINTAAN: {permintaan}Pola 3 — Perbandingan terstruktur
Untuk "bandingkan A dan B", minta keluaran tabel agar perbedaan mudah dibaca dan diverifikasi. Pasangkan dengan query decomposition dari Bab 12.
Tugas: Bandingkan dua hal berdasarkan KONTEKS.
Format keluaran: tabel dengan kolom
| Aspek | A | B | Sumber |
Aturan:
- Isi hanya aspek yang datanya ada di KONTEKS.
- Jika satu sisi tak punya data, tulis "tidak disebutkan".
- Kolom Sumber merujuk [Sumber N].
KONTEKS:
{konteks}
BANDINGKAN: {a} vs {b}Pola 4 — Grounding ekstra untuk domain sensitif
Di bidang hukum, kesehatan, atau keuangan, tambahkan lapisan kehati-hatian: kutip teks sumber verbatim untuk klaim kunci, dan tambahkan disclaimer yang mengarahkan ke ahli.
Peran: Asisten rujukan untuk domain sensitif.
Aturan:
1. Untuk setiap klaim penting, kutip kalimat sumber
secara verbatim dalam tanda kutip, lalu [Sumber N].
2. Jangan menafsirkan di luar teks; jangan memberi nasihat.
3. Akhiri dengan: "Ini bukan nasihat profesional.
Konsultasikan dengan ahli untuk keputusan."
4. Jika ragu atau KONTEKS tidak jelas, katakan tidak yakin.
KONTEKS:
{konteks}
PERTANYAAN: {pertanyaan}Studi kasus singkat A — Asisten peraturan internal
Sebuah organisasi ingin karyawan bisa bertanya tentang ratusan surat edaran dan peraturan yang sering diperbarui. Karena teks bersifat legal dan presisi kata penting, tim memilih chunking per pasal (structure-aware), retrieval hybrid dengan BM25 kuat untuk menangkap nomor pasal, dan Pola 4 dengan kutipan verbatim. Ambang skor dipasang tinggi agar sistem lebih sering berkata "tidak yakin" daripada menebak — perilaku yang tepat ketika kesalahan berbiaya besar.
Pelajaran: di domain presisi tinggi, lebih baik sistem menolak menjawab daripada memberi jawaban meyakinkan yang salah. Metrik yang dipantau paling ketat adalah faithfulness, bukan sekadar cakupan jawaban.
Studi kasus singkat B — Chatbot bantuan produk publik
Sebuah produk konsumen ingin chatbot menjawab pertanyaan pengguna dari basis artikel bantuan. Di sini nada ramah dan kecepatan lebih penting daripada kutipan verbatim. Tim memilih chunking per artikel/subjudul, dense retrieval yang kuat untuk menangkap parafrasa awam, Pola 1 dengan gaya bersahabat, dan cache jawaban agresif karena banyak pertanyaan berulang. Query rewriting dipakai agar percakapan susulan mengalir alami.
Pelajaran: desain RAG bukan satu resep. Dua sistem yang secara teknis serupa dituning ke arah berbeda karena tujuan pengguna berbeda. Selalu mulai dari pertanyaan: siapa penggunanya, apa yang mereka butuhkan, dan apa biaya sebuah kesalahan?
Setiap sistem RAG yang kamu bangun berdiri di atas puluhan tahun riset pencarian informasi, linguistik komputasional, dan pembelajaran mesin. Kamu tidak perlu menemukan ulang semuanya — cukup merangkainya dengan disiplin dan selera yang baik. Buku ini telah memberimu rangkaian itu; sisanya adalah latihan, pengukuran, dan keberanian untuk merilis lalu memperbaiki.
