Bot WA AI dengan RAG (Retrieval Augmented Generation)

Cara membuat bot AI WhatsApp dengan RAG. Retrieval dari knowledge base, dokumen, FAQ. Tutorial lengkap!

Bot WA AI dengan RAG (Retrieval Augmented Generation)
Bot WA AI dengan RAG (Retrieval Augmented Generation)

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 + source

Setup 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`);
}

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 }
        );
    }
}

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 improvement

DON'T ❌

- Index without chunking
- Rely only on vector search
- Accept low relevance results
- Static knowledge base
- Ignore query patterns
- Skip source attribution

FAQ

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 AIRAG AI
HallucinateAccurate
GenericSpecific
OutdatedReal-time
UnverifiableTraceable

Setup RAG Bot →


Artikel Terkait