RAG Fundamentals

4 jam17 min baca
Tujuan

Paham apa itu RAG, kenapa powerful, dan komponen-komponennya. Sebelum coding, paham konsepnya dulu.

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)

  1. Ambil dokumen (PDF, txt, web, dll)
  2. Pecah jadi chunk kecil (~500 char)
  3. Convert tiap chunk → embedding vector
  4. Simpan di vector database (Chroma, Pinecone, FAISS)

Phase 2: Querying (setiap user tanya)

  1. User tanya: "Apa itu transformer?"
  2. Convert pertanyaan → embedding
  3. Cari di vector DB → ambil 3-5 chunk paling mirip
  4. Bangun prompt: "Berdasarkan konteks berikut: [chunks], jawab: [pertanyaan]"
  5. Kirim ke LLM
  6. 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:

  1. Ambil dokumen panjang (misal 500 halaman) sebagai input.
  2. Pecah jadi chunk ~500 karakter, atur overlap ~50 karakter di tepi tiap chunk.
  3. Embed tiap chunk → dapat vektor (768D atau 1536D).
  4. Simpan vektor + metadata (sumber, halaman) ke Vector DB.
  5. 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

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-v2 atau LazarusNLP/all-indo-e5-small-v4 cukup.


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:

  1. Query masuk → di-embed jadi vektor 768D atau 1536D.
  2. Untuk tiap chunk di DB, hitung cosine similarity ke query vector.
  3. Urutkan chunk berdasarkan score similarity (descending).
  4. Ambil K teratas (umumnya 3-5).
  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:

  1. Query datang. Jalankan vector search → top-20 chunk berdasar makna.
  2. Pararel: jalankan BM25 keyword search → top-20 chunk berdasar exact word match.
  3. Gabungkan dengan reciprocal rank fusion → ~30 chunk kandidat.
  4. Re-ranker pakai cross-encoder untuk scoring ulang tiap (query, chunk).
  5. 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):

  1. Documents: 5 paragraf tentang AI
  2. Compute embedding pakai sentence-transformers
  3. Cosine similarity untuk retrieval
  4. Prompt LLM dengan top-1 chunk
  5. 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