01 — RAG Fundamentals
Estimasi: 4 jam Tujuan: Paham apa itu RAG, kenapa powerful, dan komponen-komponennya. Sebelum coding, paham konsepnya dulu.
Kenapa Materi Ini Penting?
RAG adalah skill LLM paling dicari di industri saat ini. Kenapa? Karena LLM vanilla punya batas: tidak tahu data internal perusahaan, bisa halusinasi, dan knowledge-nya statis. RAG menyelesaikan semua itu. Di bootcamp nanti, capstone project kemungkinan besar melibatkan RAG — dan perusahaan yang hiring AI engineer hampir pasti butuh orang yang bisa build sistem "chat with your documents". Menguasai RAG berarti kamu punya skill yang langsung bisa dijual ke market, bukan cuma teori di kepala.
Pembukaan
RAG (Retrieval-Augmented Generation) = teknik untuk menjawab dari dokumen spesifik menggunakan LLM.
Analogi: AI yang Punya Akses ke Perpustakaan
Bayangkan kamu tanya ke teman yang baca banyak buku.
- LLM tanpa RAG = teman yang cuma jawab dari ingatan. Kalau lupa, dia ngarang dengan percaya diri (halusinasi).
- LLM dengan RAG = teman yang sebelum jawab, lari ke perpustakaan dulu, ambil 3 buku paling relevan, baca bagian yang penting, baru jawab pakai info itu.
Hasilnya: jawaban grounded di sumber, bisa cek halaman, dan up-to-date dengan koleksi terbaru.
Tanpa RAG:
- ChatGPT cuma tahu data sampai cutoff training
- Tidak tahu data privat kamu
- Bisa halusinasi tentang fakta
Dengan RAG:
- Bisa jawab dari dokumen spesifik (PDF, database, notes)
- Up-to-date (kalau dokumen di-update)
- Lebih reliable (jawaban grounded di sumber)
Bagian 1 — Bagaimana RAG Bekerja?
Diagram Big Picture
Catatan: Diagram di atas interaktif — drag node, zoom, dan pan. Diagram statis Mermaid sebagai fallback:
flowchart TD
subgraph Indexing["📥 Indexing (offline, sekali)"]
D["📄 Documents<br/>(PDF, TXT, MD)"] --> C["✂️ Chunk<br/>(potong jadi 500-char)"]
C --> E["🔢 Embed<br/>(teks → vektor)"]
E --> V["💾 Vector DB<br/>(Chroma/FAISS)"]
end
subgraph Querying["🔍 Querying (online, tiap pertanyaan)"]
Q["❓ User Question"] --> QE["🔢 Embed query"]
QE --> S["🔎 Similarity Search"]
V --> S
S --> R["📑 Top-K chunks"]
R --> P["📝 Build prompt<br/>(context + question)"]
Q --> P
P --> L["🤖 LLM"]
L --> A["✅ Grounded Answer"]
end
Diagram Sederhana (ASCII)
[Dokumen] [User Query]
↓ ↓
[Chunking] [Convert to Embedding]
↓ ↓
[Generate Embeddings] [Search Vector DB]
↓ ↓
[Store in Vector DB] [Retrieve Top-K Chunks]
↓
[Inject as Context]
↓
[LLM]
↓
[Answer]
Step by Step
Phase 1: Indexing (sekali)
- Ambil dokumen (PDF, txt, web, dll)
- Pecah jadi chunk kecil (~500 char)
- Convert tiap chunk → embedding vector
- Simpan di vector database (Chroma, Pinecone, FAISS)
Phase 2: Querying (setiap user tanya)
- User tanya: "Apa itu transformer?"
- Convert pertanyaan → embedding
- Cari di vector DB → ambil 3-5 chunk paling mirip
- Bangun prompt: "Berdasarkan konteks berikut: [chunks], jawab: [pertanyaan]"
- Kirim ke LLM
- LLM jawab grounded di konteks
Bagian 2 — Kenapa Embedding?
Sudah dibahas di Fase 3 dan Fase 6. Recap:
Embedding = representasi vektor dari teks (768D, 1536D, dll).
Analogi: Koordinat Makna di Peta
Bayangkan peta semantik raksasa. Setiap kata atau kalimat punya koordinat di peta itu (vektor 768 angka).
- Kata yang maknanya mirip = lokasinya berdekatan di peta
- Kata yang berbeda topik = lokasinya jauh
flowchart LR
subgraph Space["🗺️ Embedding Space (disederhanakan jadi 2D)"]
K["🐱 kucing<br/>(2.1, 5.3)"]
A["🐶 anjing<br/>(2.3, 5.5)"]
H["🐰 kelinci<br/>(1.9, 5.4)"]
M["🪑 meja<br/>(8.2, 1.1)"]
L["🛋️ kursi<br/>(8.4, 1.3)"]
Q["❓ 'hewan peliharaan'<br/>(2.0, 5.4)"]
end
Q -.dekat.-> K
Q -.dekat.-> A
Q -.dekat.-> H
Q -.jauh.-> M
Q -.jauh.-> L
Dalam diagram di atas, query "hewan peliharaan" punya koordinat dekat dengan kucing/anjing/kelinci, walaupun kata "kucing" tidak muncul di pertanyaan. Itu yang membedakan semantic search dari keyword search.
Property:
similarity(vec("kucing"), vec("anjing")) = tinggi (sama-sama hewan)
similarity(vec("kucing"), vec("meja")) = rendah
Saat user tanya "Apa itu hewan peliharaan?", kita convert ke embedding dan cari chunk yang vektornya mirip. Hasilnya: chunk tentang kucing/anjing/dll, walaupun teks pertanyaan tidak mengandung kata "kucing".
Itu kuncinya — semantic search, bukan keyword search.
Bagian 3 — Chunking Strategy
Cara memecah dokumen sangat berpengaruh ke kualitas RAG.
Analogi: Memotong Buku Jadi Bab dan Paragraf
Bayangkan kamu nge-print 1 buku 500 halaman jadi 1 lembaran kertas raksasa. Susah dicari isinya. Solusinya: potong jadi bab → paragraf → kalimat.
- Chunk terlalu besar (1 bab) = kalau retrieve, dapat banyak info irrelevant (banyak noise)
- Chunk terlalu kecil (1 kalimat) = info terpotong-potong, kehilangan konteks
- Chunk ideal = 1 paragraf utuh (~500 char), dengan sedikit overlap supaya makna mengalir
Cara Membaca Diagram:
- Kotak ungu kiri = dokumen sumber yang utuh (belum dipotong).
- Kotak cyan tengah = potongan-potongan kecil hasil chunking.
- Garis putus-putus pink antar chunk = overlap (sengaja tumpang tindih supaya makna tidak terpotong).
- Kotak amber = embedding step (tiap chunk diubah jadi vektor).
- Kotak emerald kanan = chunks yang sudah ter-index di Vector DB, siap dicari.
Walkthrough Step-by-Step:
- Ambil dokumen panjang (misal 500 halaman) sebagai input.
- Pecah jadi chunk ~500 karakter, atur overlap ~50 karakter di tepi tiap chunk.
- Embed tiap chunk → dapat vektor (768D atau 1536D).
- Simpan vektor + metadata (sumber, halaman) ke Vector DB.
- Indexing selesai. Sekarang siap menerima query.
Analogi Sehari-hari: Seperti memotong roti tawar untuk sandwich — kalau terlalu tebal susah dimakan, terlalu tipis ambruk. Sweet spot di tengah, dan biarkan ujung-ujungnya sedikit overlap dengan irisan sebelahnya supaya isian tidak tumpah.
Diagram statis Mermaid sebagai fallback:
flowchart LR
B["📕 Buku Penuh<br/>(500 halaman)"] --> CH1["✂️ Chunk 1<br/>~500 char"]
B --> CH2["✂️ Chunk 2<br/>~500 char"]
B --> CH3["✂️ Chunk 3<br/>~500 char"]
B --> CH4["✂️ Chunk 4<br/>~500 char"]
CH1 -.overlap 50.-> CH2
CH2 -.overlap 50.-> CH3
CH3 -.overlap 50.-> CH4
Fixed Size Chunking
def chunk_fixed(text, size=500, overlap=50):
chunks = []
for i in range(0, len(text), size - overlap):
chunk = text[i:i + size]
chunks.append(chunk)
return chunks
✅ Simple ❌ Bisa potong di tengah kalimat
Recursive Chunking (Recommended)
Coba split di paragraf dulu, kalau masih besar, split di kalimat, dst.
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", ". ", " ", ""]
)
chunks = splitter.split_text(text)
Semantic Chunking
Pecah berdasarkan perubahan topik (deteksi dengan embedding).
# Lebih advanced, tapi hasil lebih bagus
from llama_index.core.node_parser import SemanticSplitterNodeParser
Aturan
- Chunk terlalu kecil (50 char): kurang konteks
- Chunk terlalu besar (5000 char): noise tinggi, kurang relevance
- Sweet spot: 300-1000 karakter, dengan overlap 10-20%
Bagian 4 — Embedding Model
Pilihan untuk embedding:
OpenAI Embedding
from openai import OpenAI
client = OpenAI()
response = client.embeddings.create(
input="Halo dunia",
model="text-embedding-3-small" # 1536 dim, $0.02/1M tokens
)
embedding = response.data[0].embedding
Sentence Transformers (Open Source)
from sentence_transformers import SentenceTransformer
# Bahasa Inggris
model = SentenceTransformer("all-MiniLM-L6-v2") # 384 dim, FREE
# Multilingual (untuk Indonesia)
model = SentenceTransformer("paraphrase-multilingual-mpnet-base-v2") # 768 dim
embeddings = model.encode(["Saya suka kucing", "Anjing itu lucu"])
Indonesia-Specific
# IndoBERT untuk Bahasa Indonesia
model = SentenceTransformer("LazarusNLP/all-indo-e5-small-v4")
Aturan: mulai dengan free + multilingual. Kalau butuh quality lebih, switch ke OpenAI.
Tabel Perbandingan Embedding Model
| Model | Dimensi | Bahasa | Harga | Speed | Catatan |
|---|---|---|---|---|---|
text-embedding-3-small (OpenAI) |
1536 | Multilingual | $0.02/1M tokens | Cepat (API) | Kualitas top, tapi butuh internet |
text-embedding-3-large (OpenAI) |
3072 | Multilingual | $0.13/1M tokens | Sedang | Quality maksimum, mahal |
models/embedding-001 (Gemini) |
768 | Multilingual | Free tier ada | Cepat | Generous free quota |
all-MiniLM-L6-v2 |
384 | Inggris fokus | Free (lokal) | Sangat cepat | Default untuk POC English |
paraphrase-multilingual-mpnet-base-v2 |
768 | 50+ bahasa | Free (lokal) | Sedang | Recommended untuk Indonesia |
LazarusNLP/all-indo-e5-small-v4 |
384 | Indonesia | Free (lokal) | Cepat | Optimal untuk Bahasa Indonesia |
BAAI/bge-m3 |
1024 | Multilingual | Free (lokal) | Sedang | SOTA open-source 2024 |
Rule of thumb: Untuk capstone Indonesia,
paraphrase-multilingual-mpnet-base-v2atauLazarusNLP/all-indo-e5-small-v4cukup.
Bagian 5 — Vector Database
Analogi: Perpustakaan dengan Sistem Cari Berdasarkan Kemiripan
Perpustakaan tradisional pakai katalog kartu — kamu cari berdasarkan judul atau penulis (keyword exact). Perpustakaan modern pakai rak vektor: setiap buku ditaruh di "lokasi" sesuai topik. Kalau kamu cari topik "AI", librarian langsung lari ke rak yang dekat dengan koordinat AI dan ambil semua buku di sekitar situ.
Cara Membaca Diagram:
- Kotak cyan kiri = query yang sudah di-embed jadi vektor.
- Kotak ungu = semua chunk yang sudah ter-index, masing-masing punya vektor.
- Kotak amber tengah = perhitungan cosine similarity untuk tiap pasangan (query, chunk).
- Angka di edge (0.97, 0.85, 0.12, ...) = score similarity (1 = identik, 0 = tidak mirip).
- Kotak emerald kanan = top-K chunk dengan score tertinggi.
Walkthrough Step-by-Step:
- Query masuk → di-embed jadi vektor 768D atau 1536D.
- Untuk tiap chunk di DB, hitung cosine similarity ke query vector.
- Urutkan chunk berdasarkan score similarity (descending).
- Ambil K teratas (umumnya 3-5).
- Kirim chunk-chunk ini sebagai context ke LLM untuk generation.
Analogi Sehari-hari: Seperti aplikasi dating yang ranking match. Kamu (query) punya "vektor" preferensi, tiap profil punya vektor karakteristik, sistem hitung kemiripan, dan tampilkan top-5 match. Vector DB melakukan hal yang sama — tapi untuk teks.
Diagram statis Mermaid sebagai fallback:
flowchart LR
Q["❓ Query embedding<br/>[0.12, 0.88, ...]"] --> VDB
subgraph VDB["💾 Vector Database"]
I1["📑 chunk 1<br/>vec [0.11, 0.87, ...]"]
I2["📑 chunk 2<br/>vec [0.13, 0.85, ...]"]
I3["📑 chunk 3<br/>vec [0.91, 0.05, ...]"]
I4["📑 chunk 4<br/>vec [0.10, 0.89, ...]"]
I5["📑 chunk 5<br/>vec [0.92, 0.02, ...]"]
end
VDB --> R["🔎 Hitung cosine similarity<br/>untuk semua chunk"]
R --> T["📥 Top-K<br/>(chunk 1, 4, 2)"]
Itulah inti vector database: storage + algoritma similarity search yang cepat (ANN — Approximate Nearest Neighbor).
Chroma (Termudah)
pip install chromadb
import chromadb
client = chromadb.PersistentClient(path="./chroma_db")
collection = client.get_or_create_collection(name="my_docs")
# Add documents
collection.add(
documents=["doc1 text...", "doc2 text..."],
embeddings=[[0.1, 0.2, ...], [0.3, 0.4, ...]], # opsional, auto-generate kalau tidak
metadatas=[{"source": "file1.pdf"}, {"source": "file2.pdf"}],
ids=["id1", "id2"]
)
# Query
results = collection.query(
query_texts=["what is X?"],
n_results=3
)
print(results)
FAISS (Cepat, Lokal, Facebook)
pip install faiss-cpu
import faiss
import numpy as np
# Build index
embeddings = np.random.rand(1000, 768).astype("float32")
index = faiss.IndexFlatL2(768)
index.add(embeddings)
# Query
query = np.random.rand(1, 768).astype("float32")
distances, indices = index.search(query, k=3)
Pinecone (Cloud, Production)
Cloud-based, scaleable. Mulai gratis tier.
Qdrant, Weaviate
Alternatif lain. Semua mirip API.
Untuk capstone: pakai Chroma. Persistent, simple, gratis, tidak butuh server.
Tabel Perbandingan Vector DB
| Vector DB | Tipe | Setup | Skala | Free Tier | Kapan Dipakai |
|---|---|---|---|---|---|
| Chroma | Embedded / Local | pip install |
Sampai jutaan vektor | 100% gratis (lokal) | Capstone, prototype, single-user app |
| FAISS | Library (in-memory) | pip install faiss-cpu |
Sangat cepat, jutaan | 100% gratis | High-performance, butuh persist manual |
| Pinecone | Cloud-managed | Akun + API key | Milyaran vektor | 1 index, ~100k vektor | Production, multi-user, scaleable |
| Weaviate | Self-host / Cloud | Docker / Cloud | Milyaran | Cloud free tier ada | Hybrid search built-in, GraphQL |
| Qdrant | Self-host / Cloud | Docker / Cloud | Milyaran | 1GB cluster gratis | Production, filtering canggih |
| pgvector | Postgres extension | Existing DB | Sedang | Tergantung Postgres | Sudah pakai Postgres, mau tambah vektor |
| Milvus | Self-host / Cloud | Docker / Cloud | Milyaran | Open-source | Enterprise scale, distributed |
Tips: Mulai dari Chroma (gratis, embedded). Migrate ke Pinecone/Qdrant kalau sudah scale.
Bagian 6 — Retrieval Strategy
Analogi: Librarian yang Ambilkan Buku Relevan
Setelah perpustakaan punya rak vektor, kamu butuh librarian yang tahu cara mengambil buku. Strategi librarian inilah yang disebut retrieval.
- Top-K vanilla = librarian ambil K buku terdekat dari koordinat query
- MMR = librarian ambil K buku, tapi pastikan tidak ada 5 buku yang isinya mirip semua
- Hybrid = librarian gabung 2 jenis pencarian: kemiripan makna + cocok kata kunci
- Re-ranking = librarian ambil 20 buku dulu, lalu sortir ulang hasil pencarian dari yang paling cocok dengan model lebih akurat
Cara Membaca Diagram:
- Cyan kiri = query user.
- Dua jalur paralel ungu = vector search (semantic) dan BM25 (keyword) yang berjalan bersamaan.
- Amber tengah = fusion yang menggabungkan score dari kedua jalur.
- Pink = re-ranker (cross-encoder) yang scoring lebih akurat tapi lebih lambat.
- Emerald kanan = top-3 chunk final yang dikirim ke LLM.
Walkthrough Step-by-Step:
- Query datang. Jalankan vector search → top-20 chunk berdasar makna.
- Pararel: jalankan BM25 keyword search → top-20 chunk berdasar exact word match.
- Gabungkan dengan reciprocal rank fusion → ~30 chunk kandidat.
- Re-ranker pakai cross-encoder untuk scoring ulang tiap (query, chunk).
- Ambil top-3 final → kirim ke LLM.
Analogi Sehari-hari: Seperti hiring kandidat. Pertama screening kasar (HR baca CV cepat — vector search). Kedua filter keyword tertentu (BM25). Lalu interview mendalam dengan kandidat shortlist (re-ranker). Pasti lebih akurat dibanding cuma keyword filter saja.
Diagram statis Mermaid sebagai fallback:
flowchart LR
Q["❓ Query"] --> R1["🔎 Vector Search<br/>top-20"]
Q --> R2["🔤 BM25 keyword<br/>top-20"]
R1 --> F["🔀 Fusion<br/>(combine scores)"]
R2 --> F
F --> RR["🎯 Re-ranker<br/>(cross-encoder)"]
RR --> T["📥 Top-3 final"]
Top-K Retrieval
Ambil K chunk paling mirip:
results = vector_db.query(query_embedding, k=5)
Trade-off:
- K kecil (3): fokus, mungkin miss konteks
- K besar (10): banyak konteks, mungkin noise + boros token
Default: 3-5.
MMR (Maximal Marginal Relevance)
Balance similarity + diversity. Hindari ambil 5 chunk yang isinya mirip.
# LangChain support out of box
docs = retriever.invoke(query, search_type="mmr")
Hybrid Search (Semantic + Keyword)
Kombinasi:
- Semantic search (embedding) → tangkap makna
- BM25 (keyword) → tangkap exact match (nama, angka spesifik)
Re-rank gabungan.
Re-ranking
Step 2: ambil top-20 dari vector search, lalu re-rank dengan model lebih akurat (cross-encoder).
from sentence_transformers import CrossEncoder
reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-12-v2")
scores = reranker.predict([(query, chunk) for chunk in candidates])
top_chunks = [c for _, c in sorted(zip(scores, candidates), reverse=True)][:3]
Boost akurasi ~10-20% di top results.
Bagian 7 — Prompt Construction
Setelah dapat chunk relevan, build prompt:
def build_rag_prompt(query, chunks):
context = "\n\n".join([f"[Source {i+1}]\n{chunk}" for i, chunk in enumerate(chunks)])
prompt = f"""Jawab pertanyaan berikut HANYA berdasarkan konteks yang diberikan.
Kalau jawaban tidak ada di konteks, bilang "Saya tidak menemukan info itu di dokumen."
Konteks:
{context}
Pertanyaan: {query}
Jawaban:"""
return prompt
Tips
- Sebut "HANYA berdasarkan konteks" — kurangi halusinasi
- Bilang ke model untuk akui kalau tidak tahu
- Sertakan source number — model bisa cite source
- Format output spesifik kalau perlu
Bagian 8 — RAG Pipeline Lengkap
flowchart TD
subgraph Class["🧩 Class SimpleRAG"]
I["__init__<br/>llm + embedder + db"]
IDX["index(documents)<br/>→ chunk → embed → store"]
QRY["query(question, k=3)<br/>→ embed → search → prompt → LLM"]
end
I --> IDX
IDX --> QRY
class SimpleRAG:
def __init__(self, llm, embedder, vector_db):
self.llm = llm
self.embedder = embedder
self.db = vector_db
def index(self, documents):
chunks = self._chunk(documents)
embeddings = self.embedder.encode(chunks)
self.db.add(chunks, embeddings)
def query(self, question, k=3):
# Embed query
q_emb = self.embedder.encode([question])[0]
# Retrieve
chunks = self.db.search(q_emb, k=k)
# Build prompt
prompt = self._build_prompt(question, chunks)
# Generate
answer = self.llm.generate(prompt)
return {
"answer": answer,
"sources": chunks
}
Detail implementasinya di file 03.
Runnable Mini-RAG (full, tanpa framework)
Versi yang bisa kamu copy-paste dan jalankan sekarang juga (butuh sentence-transformers + Gemini API key):
import os
import numpy as np
from sentence_transformers import SentenceTransformer
import google.generativeai as genai
# 1) Setup
genai.configure(api_key=os.environ["GEMINI_API_KEY"])
embedder = SentenceTransformer("paraphrase-multilingual-mpnet-base-v2")
llm = genai.GenerativeModel("gemini-1.5-flash")
# 2) Mini "perpustakaan"
docs = [
"Transformer adalah arsitektur neural network yang diperkenalkan paper 'Attention is All You Need' (2017).",
"RAG menggabungkan retrieval dari knowledge base dengan generative model untuk jawaban yang grounded.",
"Embedding adalah representasi vektor dari teks; teks dengan makna mirip punya vektor berdekatan.",
"Chroma adalah vector database open-source yang mudah dipakai dan persistent ke disk.",
"Cosine similarity mengukur sudut antara dua vektor; nilai 1 = identik, 0 = tegak lurus."
]
# 3) Indexing
doc_embeddings = embedder.encode(docs, normalize_embeddings=True)
# 4) Retrieval
def retrieve(query: str, k: int = 2) -> list[str]:
q_emb = embedder.encode([query], normalize_embeddings=True)[0]
scores = doc_embeddings @ q_emb # cosine karena sudah normalized
top_k = np.argsort(scores)[::-1][:k]
return [docs[i] for i in top_k]
# 5) Generation
def rag_answer(question: str) -> str:
chunks = retrieve(question)
context = "\n\n".join(f"[{i+1}] {c}" for i, c in enumerate(chunks))
prompt = f"""Jawab HANYA berdasarkan konteks berikut.
Kalau tidak ada di konteks, bilang "Saya tidak menemukan info itu."
Konteks:
{context}
Pertanyaan: {question}
Jawaban:"""
return llm.generate_content(prompt).text
# 6) Test
print(rag_answer("Apa itu transformer?"))
print(rag_answer("Bagaimana cosine similarity bekerja?"))
Itu RAG functional dalam ~40 baris. Tidak ada framework, semua plain Python.
Bagian 9 — Evaluasi RAG
Manual Evaluation
Buat 20-50 pertanyaan + jawaban ground truth. Measure:
- Faithfulness — apakah jawaban grounded di konteks? (tidak halusinasi)
- Relevance — apakah jawaban relevant ke pertanyaan?
- Coverage — apakah info penting ter-retrieve?
Automated (RAGAS)
pip install ragas
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_relevancy
result = evaluate(dataset, metrics=[faithfulness, answer_relevancy])
Iterate
Improvement bisa di banyak titik:
- Better chunking
- Better embedding model
- Re-ranking
- Prompt engineering
- Tools (web search untuk fresh data)
Bagian 10 — Common Pitfalls
1. Chunk Terlalu Kecil/Besar
Test dengan beberapa chunk size, pilih yang optimal untuk dataset kamu.
2. Embedding Model Mismatch Bahasa
Pakai English embedder untuk dokumen Indonesia → kualitas drop. Pakai multilingual.
3. Tidak Ada Re-ranking
Top-K vector search saja sering ambil chunk semi-relevant. Re-ranker boost akurasi.
4. Prompt Tidak Defensive
Tanpa "kalau tidak tahu, bilang", model akan halusinasi.
5. Tidak Track Source
User mau verify? Selalu return source kalau bisa.
FAQ Pemula RAG
Q: Berapa chunk size yang optimal? A: Tidak ada angka ajaib. Mulai dari 500 char + overlap 50, lalu test 256/512/1024 untuk dataset kamu. Ukur dengan 10-20 query ground truth.
Q: Kenapa retrieval-ku hasilnya kacau padahal embedding sudah benar? A: Cek 3 hal: (1) bahasa embedding model match dengan bahasa dokumen, (2) chunk size tidak terlalu kecil sampai kalimat terpotong, (3) query user terlalu pendek/ambigu.
Q: Apa bedanya RAG dengan fine-tuning? A: RAG = LLM tetap sama, kita tambah konteks di prompt. Fine-tuning = ubah parameter LLM. RAG lebih murah, lebih fleksibel update data, dan lebih cocok untuk fakta/dokumen. Fine-tuning untuk style/format/domain knowledge yang dalam.
Q: Kalau dokumen di-update, harus re-index semua? A: Tidak, hanya chunk yang berubah. Vector DB modern support upsert by ID. Tapi untuk capstone awal, full rebuild paling simple.
Q: Kenapa jawaban kadang panjang dan ngelantur? A: Cek prompt template. Pakai instruksi tegas: "Jawab ringkas dalam 2-3 kalimat" + "Jangan tambah info di luar konteks".
Q: Berapa biaya jalankan RAG? A: Embedding sekali (gratis kalau pakai sentence-transformers lokal). Per query: 1 panggilan embedding + 1 panggilan LLM. Pakai Gemini Flash atau GPT-4o-mini untuk cost efisien.
Q: Apakah retrieval bisa "miss" jawabannya padahal info ada di dokumen? A: Iya, sering. Mitigasi: top-K lebih besar (5-10), tambah re-ranker, atau hybrid search (BM25 + vector).
Q: Bagaimana handle kalau retrieval kosong? A: Cek skor similarity. Kalau di bawah threshold (misal 0.3), langsung return "Saya tidak menemukan info" — jangan dikirim ke LLM (LLM bisa halusinasi dari konteks kosong).
Cek Pemahaman
- Bisa jelaskan flow RAG (indexing + querying)?
- Tahu kenapa embedding penting?
- Tahu chunking strategy yang baik?
- Bisa pilih embedding model untuk Indonesia?
- Tahu apa itu vector database?
- Tahu pattern prompt anti-halusinasi?
Challenge 7.1
Challenge 1 — Manual RAG Mini
Bikin RAG sederhana manual (tanpa framework):
- Documents: 5 paragraf tentang AI
- Compute embedding pakai sentence-transformers
- Cosine similarity untuk retrieval
- Prompt LLM dengan top-1 chunk
- Test 5 pertanyaan
Challenge 2 — Eksperimen Chunking
Pakai 1 dokumen panjang. Test 3 chunk size: 200, 500, 1000. Bandingkan kualitas retrieval untuk 5 query.
Challenge 3 — Multilingual Test
Compare 3 embedding model untuk Indonesia:
- all-MiniLM-L6-v2 (English-focused)
- paraphrase-multilingual-mpnet-base-v2
- LazarusNLP/all-indo-e5-small-v4
Pakai 5 query Indonesia. Lihat top-3 retrieval.
Selanjutnya: 02-langchain-llamaindex.md