Memberi model pengetahuan domain tanpa retraining: memahami embedding dan similarity search, membangun pipeline RAG (ingest, chunking, retrieval, generation), memilih vector database, serta menerapkan hybrid search dan evaluasi retrieval yang ketat

Setelah di episode 15 model LLM kalian melayani aplikasi lewat vLLM, muncul keterbatasan model itu sendiri: model hanya tahu apa yang ada di training data. Tanya soal kebijakan perusahaan terbaru, dokumen internal, atau produk baru yang rilis kemarin — jawabannya salah atau mengarang. Di sinilah RAG (Retrieval-Augmented Generation) menjadi jawaban standar 2026.
RAG menggabungkan retriever (mencari dokumen relevan dari basis pengetahuan) dengan generator (LLM yang menjawab berdasarkan dokumen). Karena pengetahuan diambil dari database saat runtime, RAG memberi keunggulan yang tidak bisa ditandingi fine-tuning: informasi selalu bisa diperbarui tanpa retraining, dan jawaban bisa diberi sumber. Episode ini membangun pipeline RAG lengkap, dari embedding sampai hybrid search.
Retrieval dimulai dari representasi: embedding mengubah teks menjadi vektor numerik di mana teks yang bermakna mirip punya vektor berdekatan.
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("intfloat/multilingual-e5-small")
vec = model.encode("Prosedur klaim asuransi kesehatan")
print(vec.shape) # misal (384,) — teks jadi vektorPemilihan model embedding sangat menentukan kualitas retrieval:
| Pertimbangan | Saran |
|---|---|
| Bahasa | Pilih model multilingual (e5, bge-multilingual) untuk dokumen Indonesia |
| Dimensi | 384-1024: besar = lebih presisi tapi lebih lambat & boros |
| Konsistensi | Model embedding sama harus dipakai saat ingest & query |
| Normalisasi | Normalisasi vektor agar cosine similarity sederhana |
Dua fase dengan karakter berbeda:
Chunking menentukan unit retrieval. Chunk terlalu kecil → konteks terpotong; terlalu besar → pencarian tidak presisi dan memakan context window.
def chunk_text(text: str, size: int = 512, overlap: int = 64) -> list[str]:
chunks = []
start = 0
while start < len(text):
chunks.append(text[start:start + size])
start += size - overlap
return chunksOverlap menjaga kalimat yang terpotong di tengah chunk tetap ada di chunk berikutnya. Untuk dokumen terstruktur (markdown, HTML, PDF), chunk per heading/section biasanya jauh lebih baik daripada chunk per jumlah karakter — konteks semantik tetap utuh.
Pilihan vector DB populer 2026: pgvector (tambahan PostgreSQL — bagus jika sudah pakai Postgres), ChromaDB (simpel untuk prototyping), Qdrant / Weaviate / Milvus (platform standalone skala besar). Semua menyediakan pencarian kNN / HNSW — menemukan K vektor terdekat.
Namun, pencarian vektor murni punya kelemahan: tidak paham kata eksak & keyword. Pertanyaan "SOP klaim BPJS" mungkin paling cocok dengan dokumen yang mengandung kata "klaim" persis — sesuatu yang bisa dilewatkan embedding. Solusinya hybrid search: gabungkan pencarian vektor dengan pencarian keyword (BM25/Full-text), lalu gabungkan skornya.
# Ilustrasi konsep — gabungkan dua peringkat menjadi satu
vector_scores = vector_search(query_embedding, top_k=20)
keyword_scores = keyword_search(query, top_k=20)
combined = merge_ranked(vector_scores, keyword_scores, alpha=0.6)Parameter alpha menentukan bobot masing-masing (0.6 vektor / 0.4 keyword adalah titik awal umum). Reranker (cross-encoder) bisa ditambahkan setelah hybrid search untuk mengurutkan ulang hasil dengan presisi lebih tinggi — tapi lebih lambat, jadi hanya untuk top-K kecil.
from openai import OpenAI
llm = OpenAI(base_url="http://localhost:8000/v1", api_key="x")
def rag_answer(question: str, top_k: int = 3) -> str:
q_vec = embed_model.encode(question)
hits = vector_db.query(q_vec, top_k=top_k) # + hybrid search
context = "\n\n".join(h["text"] for h in hits)
prompt = f"""Jawab berdasarkan konteks berikut saja.
Jika konteks tidak memuat jawaban, katakan tidak tahu.
Konteks:
{context}
Pertanyaan: {question}"""
resp = llm.chat.completions.create(
model="Qwen/Qwen2.5-7B-Instruct",
messages=[{"role": "user", "content": prompt}],
temperature=0.1,
)
return resp.choices[0].message.contentPerhatikan dua disiplin: temperature rendah (jawaban harus faktual, bukan kreatif) dan instruksi "katakan tidak tahu" (mencegah hallucination saat konteks tidak memadai).
RAG tanpa evaluasi = sistem yang diam-diam menjawab salah. Evaluasi dua lapisan:
| Lapisan | Metrik | Mengukur |
|---|---|---|
| Retrieval | Recall@K, MRR, hit rate | Apakah dokumen relevan ketemu? |
| Generation | Correctness, faithfulness, groundedness | Apakah jawaban benar & bersumber konteks? |
Praktik paling penting untuk retrieval: bangun golden set — sekumpulan pasangan pertanyaan → dokumen yang seharusnya ter-retrieve. Lalu ukur recall@K. Teknik evaluasi modern (LLM-as-judge, RAGAS) akan kita dalami di episode 24.
Warning
Dua kegagalan RAG paling umum: chunking yang mengabaikan struktur dokumen (konteks jadi puing-puing) dan tanpa evaluasi retrieval (sistem terlihat jalan tapi jawaban sering salah). Perbaiki keduanya sebelum pindah ke masalah yang lebih "menarik".
| Pitfall | Dampak | Pencegahan |
|---|---|---|
| Embedding model beda saat ingest & query | Retrieval kacau | Satu model embedding untuk semuanya |
| Chunk tanpa overlap / tanpa struktur | Konteks terpotong | Chunk per section + overlap |
| Hanya vector search | Keyword eksak terlewat | Hybrid search + reranker |
| RAG tanpa sumber | Jawaban tidak bisa diverifikasi | Sertakan reference doc pada output |
| Tanpa eval retrieval | Salah diam-diam | Golden set + recall@K |
Pada episode 16 ini, kalian telah membangun knowledge system:
Di episode 17 selanjutnya kita akan membahas Data Pipelines untuk ML — ingestion, processing, dan feature store: bagaimana data mengalir dari sumber mentah menjadi dataset training yang bersih dan feature yang konsisten untuk training maupun serving. Sampai jumpa di episode 17!