Bot WA AI dengan RAG (Retrieval Augmented Generation)
Cara membuat bot AI WhatsApp dengan RAG. Retrieval dari knowledge base, dokumen, FAQ. Tutorial lengkap!
RAG = AI yang jawab dari data kamu!
RAG (Retrieval Augmented Generation) memungkinkan AI jawab dari knowledge base kamu sendiri - produk, kebijakan, FAQ - bukan dari training data umum.
Kenapa RAG?
📊 MASALAH AI TANPA RAG:
- Hallucination (ngarang jawaban)
- Tidak tau info spesifik bisnis
- Data training outdated
- Tidak bisa update real-time
RAG SOLUSINYA:
- Jawab dari data yang ada
- Akurat sesuai knowledge base
- Update kapan saja
- Verifiable (bisa cek sumber)Cara Kerja RAG
🔄 RAG FLOW:
1. USER QUERY
"Berapa harga produk X?"
│
▼
2. EMBEDDING
Query → Vector
│
▼
3. RETRIEVAL
Cari dokumen relevan
di vector database
│
▼
4. AUGMENTATION
Gabungkan query +
dokumen relevan
│
▼
5. GENERATION
AI generate jawaban
dari konteks
│
▼
6. RESPONSE
Jawaban akurat + sourceSetup Knowledge Base
1. Prepare Documents:
javascript
// Contoh knowledge base
const documents = [
{
id: 'product-001',
type: 'product',
title: 'Dress Brukat Elegan',
content: `Dress Brukat Elegan adalah dress formal dengan bahan brukat premium.
Harga: Rp 350.000
Ukuran: S, M, L, XL
Warna: Navy, Maroon, Black
Bahan: Brukat premium dengan furing
Cocok untuk: Kondangan, pesta, wisuda
Perawatan: Dry clean atau cuci tangan`,
metadata: {
category: 'dress',
price: 350000,
lastUpdated: new Date()
}
},
{
id: 'policy-001',
type: 'policy',
title: 'Kebijakan Retur',
content: `Kebijakan Retur [BRAND]:
Syarat retur:
- Maksimal 7 hari setelah diterima
- Produk belum dipakai/dicuci
- Tag masih terpasang
- Kemasan original lengkap
Cara retur:
1. Hubungi CS via WhatsApp
2. Kirim foto produk
3. Tunggu approval
4. Kirim balik dengan label kami
5. Refund diproses 3-5 hari kerja
Pengecualian:
- Produk sale/diskon tidak bisa retur
- Underwear tidak bisa retur`,
metadata: {
category: 'policy',
lastUpdated: new Date()
}
},
// ... more documents
];2. Create Embeddings:
javascript
const OpenAI = require('openai');
const openai = new OpenAI();
async function createEmbedding(text) {
const response = await openai.embeddings.create({
model: 'text-embedding-3-small',
input: text
});
return response.data[0].embedding;
}
async function indexDocuments(documents) {
for (const doc of documents) {
// Create embedding from content
const embedding = await createEmbedding(doc.content);
// Store in database with embedding
await db.knowledgeBase.updateOne(
{ id: doc.id },
{
$set: {
...doc,
embedding,
indexedAt: new Date()
}
},
{ upsert: true }
);
}
console.log(`Indexed ${documents.length} documents`);
}3. Vector Search:
javascript
// MongoDB Atlas Vector Search
async function searchKnowledgeBase(query, topK = 3) {
// Create query embedding
const queryEmbedding = await createEmbedding(query);
// Vector search
const results = await db.knowledgeBase.aggregate([
{
$vectorSearch: {
index: 'knowledge_vector_index',
path: 'embedding',
queryVector: queryEmbedding,
numCandidates: 50,
limit: topK
}
},
{
$project: {
id: 1,
title: 1,
content: 1,
type: 1,
score: { $meta: 'vectorSearchScore' }
}
}
]).toArray();
return results;
}
// Alternative: Pinecone
const { Pinecone } = require('@pinecone-database/pinecone');
const pinecone = new Pinecone();
async function searchPinecone(query, topK = 3) {
const queryEmbedding = await createEmbedding(query);
const index = pinecone.Index('knowledge-base');
const results = await index.query({
vector: queryEmbedding,
topK,
includeMetadata: true
});
return results.matches;
}RAG Chat Implementation
javascript
const RAG_SYSTEM_PROMPT = `Kamu adalah AI customer service untuk [BRAND].
PENTING:
- Jawab HANYA berdasarkan konteks yang diberikan
- Jika tidak ada di konteks, bilang "Saya tidak memiliki informasi tersebut"
- Jangan membuat informasi yang tidak ada
- Selalu akurat dan helpful
- Bahasa Indonesia casual dan ramah
FORMAT JAWABAN:
- Langsung jawab pertanyaan
- Jika ada harga, sebutkan dengan jelas
- Jika ada syarat/ketentuan, jelaskan
- Akhiri dengan tawaran bantuan lain`;
async function ragChat(userId, userMessage) {
// 1. Retrieve relevant documents
const relevantDocs = await searchKnowledgeBase(userMessage, 3);
// 2. Check if we found relevant information
const minScore = 0.7;
const goodDocs = relevantDocs.filter(d => d.score >= minScore);
if (goodDocs.length === 0) {
return `Maaf kak, aku tidak menemukan informasi spesifik tentang itu.
Mau aku hubungkan dengan tim CS untuk bantuan lebih lanjut?`;
}
// 3. Build context from retrieved documents
const context = goodDocs.map(doc =>
`[${doc.type.toUpperCase()}] ${doc.title}\n${doc.content}`
).join('\n\n---\n\n');
// 4. Generate response with context
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [
{ role: 'system', content: RAG_SYSTEM_PROMPT },
{
role: 'user',
content: `KONTEKS DARI KNOWLEDGE BASE:
${context}
---
PERTANYAAN CUSTOMER:
${userMessage}
Jawab berdasarkan konteks di atas.`
}
]
});
const answer = response.choices[0].message.content;
// 5. Log for analytics
await logRAGQuery({
userId,
query: userMessage,
retrievedDocs: goodDocs.map(d => d.id),
response: answer
});
return answer;
}Chunking Strategy
javascript
// Untuk dokumen panjang, perlu di-chunk
function chunkDocument(document, chunkSize = 500, overlap = 50) {
const text = document.content;
const chunks = [];
let start = 0;
let chunkIndex = 0;
while (start < text.length) {
let end = start + chunkSize;
// Find natural break point (sentence end)
if (end < text.length) {
const nextPeriod = text.indexOf('.', end);
const nextNewline = text.indexOf('\n', end);
if (nextPeriod !== -1 && nextPeriod < end + 100) {
end = nextPeriod + 1;
} else if (nextNewline !== -1 && nextNewline < end + 100) {
end = nextNewline + 1;
}
}
chunks.push({
id: `${document.id}-chunk-${chunkIndex}`,
parentId: document.id,
title: document.title,
content: text.slice(start, end).trim(),
chunkIndex,
metadata: document.metadata
});
start = end - overlap;
chunkIndex++;
}
return chunks;
}
// Index with chunks
async function indexDocumentWithChunks(document) {
const chunks = chunkDocument(document);
for (const chunk of chunks) {
const embedding = await createEmbedding(chunk.content);
await db.knowledgeChunks.updateOne(
{ id: chunk.id },
{ $set: { ...chunk, embedding } },
{ upsert: true }
);
}
}Hybrid Search
javascript
// Combine vector search + keyword search
async function hybridSearch(query, topK = 5) {
// Vector search
const vectorResults = await searchKnowledgeBase(query, topK);
// Keyword search (BM25 style)
const keywords = extractKeywords(query);
const keywordResults = await db.knowledgeBase.find({
$text: { $search: keywords.join(' ') }
}, {
score: { $meta: 'textScore' }
}).sort({ score: { $meta: 'textScore' } }).limit(topK).toArray();
// Merge and re-rank
const combined = mergeResults(vectorResults, keywordResults);
return combined.slice(0, topK);
}
function mergeResults(vectorResults, keywordResults) {
const scoreMap = new Map();
// Vector scores (weighted 0.7)
vectorResults.forEach((r, i) => {
scoreMap.set(r.id, {
...r,
finalScore: r.score * 0.7
});
});
// Keyword scores (weighted 0.3)
keywordResults.forEach((r, i) => {
const existing = scoreMap.get(r.id);
const keywordScore = 1 - (i / keywordResults.length); // Rank-based
if (existing) {
existing.finalScore += keywordScore * 0.3;
} else {
scoreMap.set(r.id, {
...r,
finalScore: keywordScore * 0.3
});
}
});
return Array.from(scoreMap.values())
.sort((a, b) => b.finalScore - a.finalScore);
}Auto-Update Knowledge Base
javascript
// Webhook untuk update otomatis
app.post('/webhook/product-update', async (req, res) => {
const { productId, action, data } = req.body;
if (action === 'create' || action === 'update') {
const document = {
id: `product-${productId}`,
type: 'product',
title: data.name,
content: formatProductContent(data),
metadata: {
category: data.category,
price: data.price,
lastUpdated: new Date()
}
};
await indexDocuments([document]);
}
if (action === 'delete') {
await db.knowledgeBase.deleteOne({ id: `product-${productId}` });
}
res.json({ success: true });
});
function formatProductContent(product) {
return `${product.name}
Harga: Rp ${product.price.toLocaleString()}
Kategori: ${product.category}
Deskripsi: ${product.description}
Ukuran: ${product.sizes.join(', ')}
Warna: ${product.colors.join(', ')}
${product.features ? 'Fitur: ' + product.features.join(', ') : ''}
Stok: ${product.stock > 0 ? 'Tersedia' : 'Habis'}`;
}Best Practices
DO ✅
- Chunk documents appropriately
- Use hybrid search
- Set minimum relevance threshold
- Keep knowledge base updated
- Include metadata for filtering
- Log queries for improvementDON'T ❌
- Index without chunking
- Rely only on vector search
- Accept low relevance results
- Static knowledge base
- Ignore query patterns
- Skip source attributionFAQ
RAG vs Fine-tuning?
RAG untuk data yang sering berubah (products, prices). Fine-tuning untuk behavior/style yang tetap.
Berapa ukuran chunk ideal?
300-500 tokens dengan 50-100 token overlap. Adjust berdasarkan content type.
Kesimpulan
RAG = AI yang akurat dari data kamu!
| Plain AI | RAG AI |
|---|---|
| Hallucinate | Accurate |
| Generic | Specific |
| Outdated | Real-time |
| Unverifiable | Traceable |