Belajar LangChain - Advanced RAG
Episode 12 of 23

Belajar LangChain - Advanced RAG

Episode ini menaikkan level pipeline RAG: multi-query retrieval, query rewriting, hybrid retrieval vektor plus full-text, reranking dengan cross-encoder, contextual retrieval, hingga strategi mengoptimalkan context window seperti prompt caching dan deduplikasi.

AI Agent
AI AgentAugust 3, 2026
0 views
5 min read

Pendahuluan

Di episode 11 kalian membangun RAG dasar yang berfungsi: buat index, jadikan retriever, tarik konteks dengan similarity_search, lalu gabungkan dengan LCEL menjadi pipeline retrieve-generate. Pipeline itu nyata dan bisa jalan — sampai diuji dengan pertanyaan yang frasanya tidak persis sama dengan isi dokumen, atau korpusnya sudah puluhan ribu chunk.

Episode 12 adalah upgrade bertahap. Kita membedah mengapa retrieval sederhana gagal, lalu memperbaikinya satu per satu: multi-query retrieval, query rewriting, hybrid retrieval (vektor + full-text), reranking dengan cross-encoder, contextual retrieval, dan terakhir mengelola context window agar anggaran token tetap sehat.

Kenapa RAG Dasar Ternyata Belum Cukup

Retrieval vektor murni bekerja dengan satu asumsi: kalimat query dan dokumen yang relevan itu dekat di ruang embedding. Asumsi ini meleset karena beberapa alasan:

  • Vocabulary mismatch — dokumen menyebut "baterai laptop tahan 8 jam", kalian bertanya "berapa lama daya tahan notebook". Maknanya sama, kosakatanya berbeda.
  • Top-k yang kakuk tetap (misalnya 4) berarti selalu mengambil 4 chunk terdekat, padahal beberapa bisa hampir tidak relevan.
  • Pertanyaan kompleks — satu pertanyaan bisa menyentuh beberapa bagian dokumen yang tersebar; satu kueri embedding tidak akan pernah mengenai semuanya.

Karena itu produksi memakai rangkaian teknik yang saling menambal, bukan satu trik ajaib.

Multi-Query Retrieval

Ide multi-query retrieval sederhana: minta LLM menghasilkan beberapa parafrase kueri yang menangkap sudut pandang berbeda, tarik dokumen untuk tiap kueri, lalu gabungkan hasilnya. Query "berapa lama daya tahan notebook" bisa diperluas menjadi "durasi baterai laptop", "kapasitas baterai jam pemakaian", dan seterusnya — menutup celah vocabulary mismatch. Di LangChain, ini sudah dibungkus dalam MultiQueryRetriever:

PythonMulti-query retrieval
from langchain_openai import ChatOpenAI
from langchain.retrievers.multi_query import MultiQueryRetriever
 
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.2)
retriever = MultiQueryRetriever.from_llm(
    retriever=vector_store.as_retriever(search_kwargs={"k": 4}),
    llm=llm,
)
results = await retriever.ainvoke("berapa lama daya tahan notebook?")

MultiQueryRetriever tetap menerima invoke dan ainvoke, jadi ia drop-in menggantikan retriever biasa di chain kalian. k dipakai per kueri dan hasil semua kueri digabung, jadi jumlah dokumennya bisa lebih banyak dari k; LangChain mencatat kueri-kueri yang dihasilkan sebagai metadata tiap dokumen — berguna saat men-debug dengan LangSmith.

Rephrase Query

Di aplikasi chat, pertanyaan sering bergantung pada konteks sebelumnya: "kalau begitu gimana cara evaluasinya?" tidak bisa langsung dicari di index tanpa tahu "itu" merujuk ke mana. Query rewriting mengubah pertanyaan mentah menjadi kueri pencarian yang mandiri dan spesifik sebelum dikirim ke retriever. Buat chain rewriting kecil dengan ChatPromptTemplate dan StrOutputParser:

PythonQuery rewriting sebelum retrieval
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
 
rewrite_prompt = ChatPromptTemplate.from_messages([
    ("system", "Tulis ulang pertanyaan menjadi kueri pencarian mandiri, "
               "tanpa kata ganti ambigu, dan pertahankan istilah teknisnya."),
    ("human", "{question}"),
])
rewriter = rewrite_prompt | llm | StrOutputParser()
query = rewriter.invoke({
    "question": "kalau begitu, gimana cara evaluasinya?"
})
docs = retriever.invoke(query)

Hasil rewriting adalah string, bukan kalimat jawaban — itulah gunanya StrOutputParser. Urutan pipeline menjadi: rewrite dahulu, lalu retrieve, lalu generate. Cek riwayat dialog dari episode 7 bila ingin pipeline yang lebih rapi.

Hybrid Retrieval: Vektor + Full-Text

Embedding hebat untuk menangkap makna, tapi buruk untuk kecocokan istilah yang presisi: nomor seri, kode error, nama fungsi. Sebaliknya, full-text search seperti BM25 unggul di kecocokan token persis tapi tidak paham sinonim. Hybrid retrieval menggabungkan keduanya; EnsembleRetriever menggabungkan hasilnya via Reciprocal Rank Fusion:

PythonHybrid retrieval dengan EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
from langchain.retrievers import EnsembleRetriever
 
bm25 = BM25Retriever.from_documents(docs, k=4)
vector = vector_store.as_retriever(search_kwargs={"k": 4})
hybrid = EnsembleRetriever(
    retrievers=[bm25, vector],
    weights=[0.5, 0.5],
)
docs = hybrid.invoke("kode error E401 muncul saat build")

Bobot 0.5 dan 0.5 adalah titik awal yang sehat; jika korpus lebih banyak istilah teknis eksak, geser ke arah BM25. BM25Retriever butuh list Document asli sebagai argumen, jadi simpan selama pembangunan index. Untuk corpus besar, ganti BM25 in-memory dengan Elasticsearch atau PostgreSQL full-text lewat integrasi yang sama-sama mengembalikan objek Retriever.

Reranking dengan Cross-Encoder

Retrieval murah (bi-encoder) sering menghasilkan banyak dokumen yang "cukup dekat" tapi tidak benar-benar relevan. Reranker berarsitektur cross-encoder menilai pasangan query-dokumen secara bersamaan — jauh lebih akurat, tapi mahal, jadi hanya dijalankan pada kandidat yang sudah sedikit. Gabungkan dengan ContextualCompressionRetriever:

PythonReranking kandidat hasil retrieval
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import CrossEncoderReranker
from langchain_community.cross_encoders import HuggingFaceCrossEncoder
 
compressor = CrossEncoderReranker(
    model=HuggingFaceCrossEncoder(model_name="BAAI/bge-reranker-v2-m3"),
    top_n=3,
)
compression_retriever = ContextualCompressionRetriever(
    base_compressor=compressor,
    base_retriever=hybrid,
)
final_docs = compression_retriever.invoke("apa perbedaan RAG dan fine-tuning?")

Pipeline retrieval ideal di produksi: ambil banyak kandidat murah (misalnya k sebesar 20) dari hybrid retrieval, lalu rerank hingga top_n kecil (3-5) sebelum masuk prompt. Konteks yang dikirim ke model jadi lebih padat, relevan, dan hemat token. Model bge-reranker-v2-m3 berjalan lokal tanpa API key — pas untuk eksperimen.

Warning

Jangan rerank seluruh korpus — reranker kalah cepat dari bi-encoder. Prinsipnya: retrieval cepat untuk mempersempit kandidat, rerank akurat untuk memilih yang terbaik.

Contextual Retrieval

Ketika dokumen dipecah menjadi chunk kecil, konteks global ikut terpotong — chunk tentang "tabel tersebut" kehilangan informasi bahwa itu tabel konfigurasi jaringan. Contextual retrieval (dipopulerkan riset OpenAI) menambahkan header konteks: sebelum menyimpan chunk ke index, LLM meringkas konteks dokumen dan menempelkannya di depan chunk.

PythonMenambahkan header konteks pada tiap chunk
from langchain_core.documents import Document
 
context_prompt = ChatPromptTemplate.from_messages([
    ("system", "Ringkas dokumen menjadi 3-5 kalimat konteks yang menjelaskan "
               "posisi dan topik keseluruhan dokumen."),
    ("human", "{document}"),
])
context_chain = context_prompt | llm | StrOutputParser()
 
contextual_docs = []
for doc in docs:
    context = context_chain.invoke({"document": doc.page_content})
    contextual_docs.append(Document(
        page_content=f"Konteks dokumen: {context}\n\n{doc.page_content}",
        metadata=doc.metadata,
    ))

Embedding dan simpan contextual_docs ke vector store seperti biasa. Biaya ekstra ada di proses indexing (satu panggilan LLM per chunk), jadi jalankan sekali di pipeline ingestion, bukan per request. Saat retrieval, header konteks membuat chunk yang terisolasi tetap masuk akal secara mandiri.

Mengoptimalkan Context Window

Semakin banyak teknik yang dipasang, semakin besar risiko context window meledak dan biaya per request membengkak. Empat pengendali utama:

  1. Prompt caching — sebagian besar biaya adalah prompt prefix yang sama diulang. Banyak provider menawarkan cache otomatis atau manual untuk bagian prompt yang statis (system prompt, instruksi, daftar dokumen); token yang di-cache dihitung jauh lebih murah.
  2. Deduplikasi — hybrid retrieval dan multi-query sering mengembalikan dokumen yang sama berkali-kali. Buang duplikat berbasis hash isi sebelum dokumen masuk prompt.
  3. Token budget — tetapkan anggaran maksimum konteks dan isi secara berurutan sampai penuh; sisanya dibuang.
  4. Pemotongan strategis — jika dokumen terlalu panjang, potong ke bagian yang paling relevan, bukan membuang seluruhnya.

Contoh pengendalian budget bersama deduplikasi:

PythonDeduplikasi dan budget token
def dedupe_documents(docs):
    seen = set()
    unique = []
    for doc in docs:
        key = hash(doc.page_content)
        if key not in seen:
            seen.add(key)
            unique.append(doc)
    return unique
 
TOKEN_BUDGET = 6000
selected = []
used = 0
for doc in dedupe_documents(final_docs):
    n = llm.get_num_tokens(doc.page_content)
    if used + n > TOKEN_BUDGET:
        continue
    selected.append(doc)
    used += n

get_num_tokens menghitung token offline memakai tokenizer model — tanpa panggilan jaringan — jadi cocok untuk menyaring sebelum request. Mulai dari budget 40-60 persen dari context window, sisakan ruang untuk pertanyaan dan jawaban. Ingat urutannya: deduplikasi dulu, baru budget.

Penutup

Episode ini menaikkan RAG kalian dari "berfungsi" menjadi "tahan banting": multi-query retrieval menutup celah kosakata, query rewriting membuat kueri mandiri, hybrid retrieval menggabungkan makna dan kecocokan eksak, reranking memilih kandidat terbaik, contextual retrieval menjaga konteks dokumen, serta prompt caching, deduplikasi, dan budget token menekan biaya. Semakin banyak lapisan, semakin baik kualitas jawaban dengan harga retrieval yang masih masuk akal.

Inti yang harus dibawa pulang:

  • Satu kueri embedding tidak cukup; perbanyak sudut pandang dengan multi-query dan rewriting.
  • Hybrid retrieval menyatukan kekuatan embedding dan BM25; seimbangkan bobot sesuai korpus.
  • Ambil banyak kandidat murah, lalu rerank dengan cross-encoder hingga top_n kecil.
  • Contextual retrieval mempertahankan konteks dokumen pada tiap chunk, tapi dijalankan saat indexing.
  • Kendalikan biaya dengan prompt caching, deduplikasi berbasis hash, dan budget token sebelum memanggil model.

RAG yang optimal hanyalah alat untuk memberi model "ingatan". Di episode 13 kita memberinya kemampuan untuk bertindak: agents dengan LangGraph — agen yang memutuskan sendiri tool apa yang dipakai, dengan state yang bisa dijeda dan dilanjutkan (human-in-the-loop). Sampai jumpa di sana!

Belajar LangChain - Advanced RAG | Belajar LangChain