02 — LangChain & LlamaIndex
Estimasi: 5 jam Tujuan: Paham 2 framework populer untuk LLM apps. Pilih satu untuk capstone.
Kenapa Materi Ini Penting?
Bisa bikin RAG manual itu skill bagus, tapi tidak akan dipakai di project nyata. Setiap startup AI Indonesia (Kata.ai, Bahasa.ai, Glair, dll) sampai perusahaan internasional bekerja di atas framework seperti LangChain atau LlamaIndex. Job posting "Junior LLM Developer" hampir selalu mensyaratkan minimal salah satu. Menguasai satu framework dengan dalam = kamu bisa kerjakan tugas production dari hari pertama: parsing dokumen kompleks, multi-turn chat, retrieval canggih, agent dengan tools. Materi ini menjembatani prototype 50-baris ke aplikasi yang siap deploy ke ratusan user.
Pembukaan
Bisa bikin RAG manual (Fase 7.1). Tapi production butuh banyak hal: streaming, caching, async, evaluation, observability, dll.
Framework menyediakan komponen siap pakai. Saving you weeks.
Analogi: LangChain = Lego untuk LLM Apps
Bayangkan kamu mau bangun rumah-rumahan. Pilihan:
- Bikin sendiri dari kayu (manual coding) — fleksibel, tapi 1 minggu cuma jadi 1 kursi
- Pakai Lego (framework) — sudah ada balok-balok standar (prompt, retriever, memory, agent), tinggal kombinasi sesuai kebutuhan
LangChain dan LlamaIndex adalah dua set Lego terbesar untuk LLM apps. LangChain set-nya luas (segala genre), LlamaIndex set-nya fokus ke RAG.
Dua dominan:
- LangChain — paling populer, ekosistem besar, bagus untuk agent
- LlamaIndex — fokus ke RAG, API lebih clean, bagus untuk document Q&A
Diagram: Komponen LangChain vs LlamaIndex
flowchart TB
subgraph LC["🦜 LangChain (luas)"]
LC1["📝 Prompts"]
LC2["🔗 Chains"]
LC3["🧠 Memory"]
LC4["🤖 Agents + Tools"]
LC5["📚 Retrievers"]
LC6["📦 Document Loaders"]
LC7["🌊 Streaming/Callbacks"]
end
subgraph LI["🦙 LlamaIndex (RAG-focused)"]
LI1["📦 Data Connectors"]
LI2["✂️ Node Parsers"]
LI3["🗂️ Indexes<br/>(Vector/Summary/Tree)"]
LI4["🔎 Query Engines"]
LI5["💬 Chat Engines"]
LI6["🎯 Re-rankers"]
end
Bagian 1 — LlamaIndex (Recommended untuk Capstone)
pip install llama-index llama-index-llms-gemini llama-index-embeddings-huggingface
Diagram: Indexing Strategies di LlamaIndex
LlamaIndex tidak hanya punya VectorStoreIndex. Setiap jenis index optimal untuk use case berbeda:
Cara Membaca Diagram:
- Ungu kiri = dokumen sumber dipecah jadi nodes.
- Cyan tengah = 4 jenis index yang bisa kamu pilih.
- Emerald kanan = jenis pertanyaan yang cocok untuk masing-masing index.
- Garis putus-putus = "cocok untuk" (use case mapping).
Walkthrough Step-by-Step:
- Documents → di-parse jadi Nodes (chunks).
- VectorStoreIndex dibangun untuk Q&A spesifik berbasis kemiripan makna.
- SummaryIndex menyimpan semua nodes untuk operasi rangkum lintas dokumen.
- TreeIndex membangun hierarki top-down untuk navigasi konsep.
- KnowledgeGraphIndex ekstrak entitas + relasi untuk query relasional.
- Pilih index yang cocok dengan tipe pertanyaan dominan user.
Analogi Sehari-hari: Seperti memilih jenis filing system di kantor. Mau cari nama klien (vector index)? Mau tahu rekap project bulan lalu (summary index)? Mau navigasi struktur perusahaan (tree index)? Mau cek siapa kerja sama dengan siapa (knowledge graph)? Beda kebutuhan, beda alat.
Diagram statis Mermaid sebagai fallback:
flowchart TB
D["📄 Documents"] --> N["✂️ Nodes (chunks)"]
N --> V["🗂️ VectorStoreIndex<br/>(retrieval semantik)"]
N --> S["📋 SummaryIndex<br/>(summarize all docs)"]
N --> T["🌳 TreeIndex<br/>(hierarchical Q&A)"]
N --> K["🕸️ KnowledgeGraphIndex<br/>(entitas + relasi)"]
V -.cocok untuk.-> Q1["Q: 'Apa kata Albert Einstein soal X?'"]
S -.cocok untuk.-> Q2["Q: 'Ringkas 100 dokumen ini'"]
T -.cocok untuk.-> Q3["Q: query yang butuh navigasi top-down"]
K -.cocok untuk.-> Q4["Q: 'Siapa yang berkolaborasi dengan X?'"]
Basic RAG dalam 10 Baris
import os
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
from llama_index.llms.gemini import Gemini
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
# Setup
Settings.llm = Gemini(api_key=os.environ["GEMINI_API_KEY"], model="models/gemini-1.5-flash")
Settings.embed_model = HuggingFaceEmbedding(model_name="paraphrase-multilingual-mpnet-base-v2")
# Index documents
documents = SimpleDirectoryReader("./data").load_data()
index = VectorStoreIndex.from_documents(documents)
# Query
query_engine = index.as_query_engine()
response = query_engine.query("Apa itu transformer?")
print(response)
# Akses source
for node in response.source_nodes:
print(node.text)
print(node.score)
10 baris, fully functional RAG.
Persist Index
# Save (1 kali)
index.storage_context.persist(persist_dir="./storage")
# Load (next time)
from llama_index.core import StorageContext, load_index_from_storage
storage_context = StorageContext.from_defaults(persist_dir="./storage")
index = load_index_from_storage(storage_context)
Custom Chunking
from llama_index.core.node_parser import SentenceSplitter
splitter = SentenceSplitter(chunk_size=512, chunk_overlap=50)
nodes = splitter.get_nodes_from_documents(documents)
index = VectorStoreIndex(nodes)
Chat Engine (Multi-turn)
chat_engine = index.as_chat_engine(chat_mode="context", verbose=True)
response = chat_engine.chat("Apa itu transformer?")
print(response)
response = chat_engine.chat("Bagaimana cara kerjanya?")
# Multi-turn — ingat konteks sebelumnya
Query Engine dengan Custom Prompt
from llama_index.core import PromptTemplate
template = """Konteks:
{context_str}
Pertanyaan: {query_str}
Jawab dalam Bahasa Indonesia, ringkas, dan kalau tidak tahu bilang "Saya tidak menemukan info itu."
"""
query_engine = index.as_query_engine(
text_qa_template=PromptTemplate(template),
similarity_top_k=3,
)
Bagian 2 — LangChain Basics
pip install langchain langchain-community langchain-google-genai langchain-chroma
Diagram: Komponen Inti LangChain
Cara Membaca Diagram:
- Ungu kiri = 3 sumber input ke chain: Prompt template, Retriever, Memory.
- Amber tengah = Chain yang compose semua komponen jadi pipeline.
- Pink = LLM yang generate jawaban.
- Cyan = Output Parser untuk transform jawaban (string → JSON, dll).
- Emerald kanan = jawaban final ke user.
Walkthrough Step-by-Step:
- Prompt template menyediakan struktur dengan slot kosong (
{context},{question}). - Retriever isi
{context}dari Vector DB (top-K chunk). - Memory isi history percakapan untuk multi-turn.
- Chain susun semua → satu prompt utuh.
- Kirim ke LLM → output mentah.
- Output Parser format ke struktur yang diinginkan (e.g., JSON dengan field "answer", "sources").
Analogi Sehari-hari: Seperti pabrik perakitan otomotif. Komponen masuk dari supplier (Prompt, Retriever, Memory), assembly line merakit (Chain), mesin diuji (LLM), QC dan packaging (Parser), kendaraan siap diserahkan (Final Answer).
Diagram statis Mermaid sebagai fallback:
flowchart LR
P["📝 Prompt<br/>Template"] --> C["🔗 Chain"]
R["📚 Retriever<br/>(Vector DB)"] --> C
M["🧠 Memory<br/>(catatan chat)"] --> C
C --> L["🤖 LLM"]
L --> O["📤 Output<br/>Parser"]
O --> A["✅ Final Answer"]
Analogi singkat:
- Prompt = template surat dengan slot kosong
- Chain = jalur pipa yang menghubungkan komponen
- Memory = buku catatan percakapan supaya chatbot ingat konteks sebelumnya
- Retriever = perpustakaan + librarian
- Output Parser = filter yang bersihkan jawaban LLM ke format struktur (JSON, list, dll)
Basic RAG
from langchain_google_genai import GoogleGenerativeAI, GoogleGenerativeAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains import RetrievalQA
# Load
loader = TextLoader("data.txt")
docs = loader.load()
# Split
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = splitter.split_documents(docs)
# Embed + Store
embeddings = GoogleGenerativeAIEmbeddings(model="models/embedding-001")
vectorstore = Chroma.from_documents(chunks, embeddings)
# Build chain
llm = GoogleGenerativeAI(model="gemini-1.5-flash")
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
retriever=vectorstore.as_retriever(search_kwargs={"k": 3}),
return_source_documents=True
)
# Query
response = qa_chain({"query": "Apa itu RAG?"})
print(response["result"])
print(response["source_documents"])
Custom Prompt di LangChain
from langchain.prompts import PromptTemplate
template = """Konteks:
{context}
Pertanyaan: {question}
Jawab dengan singkat:"""
PROMPT = PromptTemplate(
template=template,
input_variables=["context", "question"]
)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
retriever=vectorstore.as_retriever(),
chain_type_kwargs={"prompt": PROMPT}
)
Conversational Memory
from langchain.chains import ConversationalRetrievalChain
from langchain.memory import ConversationBufferMemory
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
chain = ConversationalRetrievalChain.from_llm(
llm=llm,
retriever=vectorstore.as_retriever(),
memory=memory
)
result = chain({"question": "Apa itu transformer?"})
print(result["answer"])
result = chain({"question": "Bagaimana cara kerjanya?"})
# Ingat context
Bagian 3 — LangChain Agent (Advanced)
Untuk task yang butuh tool use:
Analogi: Agent = AI yang Bisa Pakai Tools
LLM biasa = orang pintar yang ngobrol dari ingatan saja. Agent = orang pintar yang punya akses ke kalkulator, search engine, dan database. Dia bisa memutuskan sendiri kapan harus pakai tool mana.
Cara Membaca Diagram:
- Cyan kiri = pertanyaan user.
- Pink = Agent (LLM jadi otaknya).
- Amber tengah = decision point — agent reasoning tool mana yang dibutuhkan.
- Ungu = tools yang tersedia (search, calculator, RAG, code executor).
- Garis putus-putus balik ke agent = hasil tool feedback ke agent untuk reasoning lanjut.
- Emerald kanan = jawaban final.
Walkthrough Step-by-Step:
- User tanya "Berapa harga BTC × 100?".
- Agent reasoning: "Aku butuh harga BTC current → search tool".
- Search tool kembali dengan harga BTC.
- Agent reasoning lagi: "Aku butuh hitung × 100 → calculator".
- Calculator kembali dengan hasil.
- Agent compose jawaban final dan kirim ke user.
Analogi Sehari-hari: Seperti researcher senior. Tahu cara reasoning, tapi tidak hafal semua. Punya akses ke library (search), kalkulator, database internal (RAG), dan komputer (code exec). Tahu kapan harus pakai apa.
Diagram statis Mermaid sebagai fallback:
flowchart TD
Q["❓ User Question<br/>'Berapa harga BTC × 100?'"] --> AG["🤖 Agent (LLM otak)"]
AG -->|Reason| T1{"Butuh tool apa?"}
T1 -->|info terkini| S["🔍 Search Tool"]
T1 -->|hitung| C["🧮 Calculator"]
T1 -->|cari di docs| R["📚 RAG Retriever"]
S --> AG
C --> AG
R --> AG
AG --> A["✅ Final Answer"]
from langchain.agents import Tool, initialize_agent, AgentType
from langchain.tools import DuckDuckGoSearchRun
search = DuckDuckGoSearchRun()
def calculator(expression: str) -> str:
try:
return str(eval(expression))
except:
return "Invalid expression"
tools = [
Tool(name="search", func=search.run, description="Search web for current info"),
Tool(name="calculator", func=calculator, description="Evaluate math expression"),
]
agent = initialize_agent(
tools, llm,
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
verbose=True
)
agent.run("Berapa harga Bitcoin sekarang dikalikan 100?")
# Agent akan: search BTC price → calculator → answer
Bagian 4 — Document Loaders
LlamaIndex/LangChain support banyak format:
# LlamaIndex
from llama_index.core import SimpleDirectoryReader
# Auto-detect: .txt, .pdf, .docx, .md, .json, dll
docs = SimpleDirectoryReader("./data").load_data()
# Specific
from llama_index.readers.web import SimpleWebPageReader
docs = SimpleWebPageReader().load_data(["https://example.com"])
# Notion
from llama_index.readers.notion import NotionPageReader
# LangChain
from langchain_community.document_loaders import (
TextLoader, PyPDFLoader, Docx2txtLoader,
WebBaseLoader, NotionDirectoryLoader
)
loader = PyPDFLoader("paper.pdf")
docs = loader.load()
Bagian 5 — LangChain Expression Language (LCEL)
Modern API LangChain. Compose chains dengan operator |:
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
retriever = vectorstore.as_retriever()
prompt = PromptTemplate.from_template("""
Context: {context}
Question: {question}
Answer:""")
chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
response = chain.invoke("Apa itu RAG?")
Lebih clean dari API klasik.
Bagian 6 — Comparison
| Aspect | LlamaIndex | LangChain |
|---|---|---|
| Best for | RAG, Q&A | Agent, complex workflow |
| API simplicity | ✅ Cleaner | Lebih banyak abstraction |
| Documentation | Good | Good (overwhelming kadang) |
| Ecosystem | Sedang | Massive |
| Vector DB integration | ✅ | ✅ |
| Agent support | Sedang | ✅ Mature |
Tabel Detail: LangChain vs LlamaIndex
| Kriteria | LangChain | LlamaIndex |
|---|---|---|
| Filosofi | "Lego untuk LLM apps" — luas | "Framework data untuk LLM" — fokus retrieval |
| Curve belajar | Lebih curam (banyak konsep) | Lebih landai untuk RAG |
| Boilerplate | Lebih banyak | Lebih ringkas (one-liner sering cukup) |
| Document loaders | 100+ | 100+ (LlamaHub) |
| Indexing strategi | Vector + memory | Vector + Summary + Tree + KG + Composable |
| Multi-step agent | ✅ Mature (LangGraph, ReAct) | Sedang (function calling) |
| Prompt engineering tools | ✅ Lengkap | Sedang |
| Streaming | ✅ Built-in | ✅ Built-in |
| Caching | ✅ Multiple backend | ✅ Sedang |
| Evaluation | ✅ via LangSmith | ✅ via LlamaIndex eval |
| Observability | ✅ LangSmith (paid) | ✅ Arize / Phoenix |
| Komunitas | Sangat besar | Besar dan growing |
| Dipakai di production | Lebih banyak | Banyak untuk RAG-only app |
| Kapan pilih? | Workflow kompleks, agent, multi-tool | RAG-first, document Q&A |
Rekomendasi
Untuk Capstone: LlamaIndex untuk RAG-focused project.
Untuk Agent: LangChain.
Tidak perlu master keduanya. Pilih satu, dalam.
Bagian 7 — Beyond Basics
Caching
# LlamaIndex
from llama_index.core import Settings
from llama_index.core.callbacks import CallbackManager
# ... configure cache
Streaming
# LlamaIndex
query_engine = index.as_query_engine(streaming=True)
response = query_engine.query("...")
for token in response.response_gen:
print(token, end="", flush=True)
Async
# LlamaIndex
response = await query_engine.aquery("...")
Hybrid Search
# LlamaIndex
from llama_index.retrievers.bm25 import BM25Retriever
from llama_index.core.retrievers import QueryFusionRetriever
vector_retriever = index.as_retriever(similarity_top_k=5)
bm25_retriever = BM25Retriever.from_defaults(nodes=nodes, similarity_top_k=5)
retriever = QueryFusionRetriever([vector_retriever, bm25_retriever])
Re-ranking
from llama_index.core.postprocessor import SentenceTransformerRerank
reranker = SentenceTransformerRerank(
model="cross-encoder/ms-marco-MiniLM-L-12-v2",
top_n=3,
)
query_engine = index.as_query_engine(node_postprocessors=[reranker])
Common Mistakes & FAQ
Mistake 1: Pakai Embedding English untuk Dokumen Indonesia
# ❌ Hasil retrieval kacau untuk teks Indonesia
Settings.embed_model = HuggingFaceEmbedding(model_name="all-MiniLM-L6-v2")
# ✅ Pakai multilingual atau Indo-specific
Settings.embed_model = HuggingFaceEmbedding(
model_name="paraphrase-multilingual-mpnet-base-v2"
)
Mistake 2: Hardcoding API Keys
# ❌ JANGAN PERNAH
llm = Gemini(api_key="AIzaSy...") # bocor ke git history!
# ✅ Pakai environment variable
import os
from dotenv import load_dotenv
load_dotenv()
llm = Gemini(api_key=os.environ["GEMINI_API_KEY"])
Mistake 3: Lupa Persist Index
Tiap kali run app, indexing diulang dari nol = lama dan boros API call embedding.
# ✅ Save sekali
index.storage_context.persist(persist_dir="./storage")
# ✅ Load di run berikutnya
storage_context = StorageContext.from_defaults(persist_dir="./storage")
index = load_index_from_storage(storage_context)
Mistake 4: Tidak Handle Empty Retrieval
Kalau query tidak match dokumen apapun, LLM masih bisa halusinasi.
response = query_engine.query(question)
if not response.source_nodes or max(n.score for n in response.source_nodes) < 0.3:
return "Saya tidak menemukan info itu di dokumenmu."
return str(response)
Mistake 5: Chunk Size Default untuk Semua Dokumen
Code (potong di function), tabel, dan prosa butuh strategi chunking berbeda. Untuk capstone awal, mulai dengan SentenceSplitter(chunk_size=512, chunk_overlap=50), lalu eksperimen.
Mistake 6: Tidak Test Kualitas Retrieval Sebelum Tuning Prompt
Kalau jawaban kacau, bukan selalu salah prompt. Cek dulu: apakah chunk yang ter-retrieve memang berisi info yang benar? Pakai print(node.text, node.score) untuk debug.
FAQ
Q: LangChain atau LlamaIndex untuk capstone? A: LlamaIndex untuk RAG-first project. Lebih ringkas, dokumentasi RAG lebih dalam.
Q: Boleh pakai keduanya dalam 1 project? A: Bisa, tapi tidak disarankan. Naikkan kompleksitas tanpa benefit jelas.
Q: Versi LangChain berubah cepat, code-ku break. Solusinya?
A: Pin versi di requirements.txt (langchain==0.2.x). Jangan auto-update di production.
Q: Cara debug Agent yang loop atau lambat?
A: verbose=True saat init agent. Cek setiap step Reasoning + Action + Observation. Kalau loop, cek deskripsi tool yang ambigu.
Q: Bisa offline/no-internet? A: Bisa kalau pakai LLM lokal (Ollama, llama.cpp) + embedding lokal (sentence-transformers) + Chroma.
Cek Pemahaman
- Bisa setup LlamaIndex untuk RAG sederhana?
- Bisa save/load index?
- Bisa custom prompt template?
- Bisa multi-turn chat engine?
- Tahu kapan pakai LangChain vs LlamaIndex?
Challenge 7.2
Challenge 1 — RAG dengan LlamaIndex
Pakai 5 file PDF (boleh paper, buku, notes). Index dengan LlamaIndex. Test 10 pertanyaan.
Challenge 2 — Compare Frameworks
Bikin RAG yang sama dengan LlamaIndex dan LangChain. Bandingkan:
- Kemudahan setup
- Lines of code
- Kualitas response
Challenge 3 — Web RAG
Index 5 artikel blog dari web (pakai WebPageReader). Q&A.
Challenge 4 — Multi-source RAG
Combine: PDF + web articles + markdown notes. Single query engine.
Challenge 5 — Re-ranking
Implement re-ranking. Compare result sebelum dan sesudah re-rank.
Selanjutnya: 03-build-rag-chatbot.md