Bot WA AI untuk FAQ Dynamic

Cara membuat bot AI WhatsApp dengan FAQ dynamic. Jawab pertanyaan dari knowledge base, update real-time. Tutorial lengkap!

Bot WA AI untuk FAQ Dynamic
Bot WA AI untuk FAQ Dynamic

FAQ dynamic = Selalu up-to-date!

Berbeda dengan FAQ static yang harus update manual, AI FAQ bisa jawab dari knowledge base, handle variasi pertanyaan, dan update otomatis.


Static vs Dynamic FAQ

📊 PERBANDINGAN:

STATIC FAQ:
- Keyword exact match
- "harga" → response A
- Tidak paham variasi
- Update manual

DYNAMIC AI FAQ:
- Understand intent
- "brp duit", "mahal ga" → sama
- Jawab dari knowledge base
- Update real-time

Arsitektur

🏗️ ARCHITECTURE:

┌─────────────┐
│   Customer  │
│   Message   │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│  Intent     │
│  Detection  │
└──────┬──────┘
       │
       ▼
┌─────────────┐     ┌─────────────┐
│  Knowledge  │◄────│   Admin     │
│    Base     │     │   Panel     │
└──────┬──────┘     └─────────────┘
       │
       ▼
┌─────────────┐
│     AI      │
│  Response   │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│   Answer    │
└─────────────┘

Knowledge Base Structure

Database Schema:

javascript

// FAQ Categories
const categorySchema = {
    id: String,
    name: String,
    description: String,
    priority: Number,
    active: Boolean
};

// FAQ Items
const faqSchema = {
    id: String,
    categoryId: String,
    question: String,          // Main question
    variations: [String],      // Alternative phrasings
    answer: String,            // Answer template
    keywords: [String],        // Search keywords
    metadata: {
        lastUpdated: Date,
        viewCount: Number,
        helpfulCount: Number,
        notHelpfulCount: Number
    },
    active: Boolean
};

// Example data
const faqData = [
    {
        id: 'faq-001',
        categoryId: 'shipping',
        question: 'Berapa lama pengiriman?',
        variations: [
            'kapan sampai',
            'brp hari kirim',
            'estimasi pengiriman',
            'lama delivery'
        ],
        answer: `Estimasi pengiriman:
        
📦 Jabodetabek: 1-2 hari
📦 Jawa: 2-3 hari
📦 Luar Jawa: 3-5 hari
📦 Indonesia Timur: 5-7 hari

Menggunakan kurir JNE, J&T, dan SiCepat.`,
        keywords: ['kirim', 'pengiriman', 'sampai', 'hari', 'lama'],
        active: true
    },
    {
        id: 'faq-002',
        categoryId: 'payment',
        question: 'Metode pembayaran apa saja?',
        variations: [
            'bayar gimana',
            'bisa transfer',
            'terima gopay',
            'payment method'
        ],
        answer: `Metode pembayaran yang tersedia:

💳 Transfer Bank: BCA, Mandiri, BNI
📱 E-Wallet: GoPay, OVO, DANA, ShopeePay
🏪 Minimarket: Alfamart, Indomaret
💵 COD: Khusus area tertentu`,
        keywords: ['bayar', 'payment', 'transfer', 'gopay', 'ovo'],
        active: true
    }
];

Implementation

System Prompt dengan FAQ:

javascript

async function buildFAQPrompt() {
    // Load active FAQs from database
    const faqs = await db.faqs.find({ active: true }).toArray();
    
    let faqContent = 'KNOWLEDGE BASE FAQ:\n\n';
    
    for (const faq of faqs) {
        faqContent += `Q: ${faq.question}\n`;
        faqContent += `Keywords: ${faq.keywords.join(', ')}\n`;
        faqContent += `A: ${faq.answer}\n\n`;
    }
    
    const systemPrompt = `Kamu adalah CS AI untuk [BRAND].

TUGAS:
1. Jawab pertanyaan customer berdasarkan knowledge base
2. Jika tidak ada di knowledge base, bilang akan cek ke tim
3. Jangan membuat informasi yang tidak ada

${faqContent}

ATURAN:
- Jawab dalam Bahasa Indonesia casual
- Gunakan info dari knowledge base
- Jika pertanyaan mirip tapi tidak exact, tetap jawab
- Jika tidak yakin, bilang "Saya cek dulu ke tim ya kak"
- Ramah dengan emoji secukupnya`;

    return systemPrompt;
}

Semantic Search untuk FAQ:

javascript

const OpenAI = require('openai');
const openai = new OpenAI();

// Generate embeddings untuk FAQ
async function generateFAQEmbeddings() {
    const faqs = await db.faqs.find({ active: true }).toArray();
    
    for (const faq of faqs) {
        const textToEmbed = `${faq.question} ${faq.variations.join(' ')} ${faq.keywords.join(' ')}`;
        
        const embedding = await openai.embeddings.create({
            model: 'text-embedding-3-small',
            input: textToEmbed
        });
        
        await db.faqs.updateOne(
            { id: faq.id },
            { $set: { embedding: embedding.data[0].embedding } }
        );
    }
}

// Find relevant FAQs using semantic search
async function findRelevantFAQs(userQuestion, topK = 3) {
    // Get embedding for user question
    const questionEmbedding = await openai.embeddings.create({
        model: 'text-embedding-3-small',
        input: userQuestion
    });
    
    const queryVector = questionEmbedding.data[0].embedding;
    
    // Search in database (MongoDB with vector search)
    const results = await db.faqs.aggregate([
        {
            $vectorSearch: {
                index: 'faq_vector_index',
                path: 'embedding',
                queryVector: queryVector,
                numCandidates: 50,
                limit: topK
            }
        },
        {
            $project: {
                question: 1,
                answer: 1,
                score: { $meta: 'vectorSearchScore' }
            }
        }
    ]).toArray();
    
    return results;
}

Full Chat Handler:

javascript

async function handleFAQChat(userId, userMessage) {
    // 1. Find relevant FAQs
    const relevantFAQs = await findRelevantFAQs(userMessage, 3);
    
    // 2. Build context
    let faqContext = '';
    if (relevantFAQs.length > 0) {
        faqContext = 'RELEVANT FAQ:\n';
        for (const faq of relevantFAQs) {
            faqContext += `Q: ${faq.question}\nA: ${faq.answer}\n\n`;
        }
    }
    
    // 3. Generate response
    const response = await openai.chat.completions.create({
        model: 'gpt-4o',
        messages: [
            {
                role: 'system',
                content: `Kamu adalah CS AI untuk [BRAND].
                
${faqContext}

Jawab pertanyaan customer berdasarkan FAQ di atas.
Jika tidak relevan, bilang akan cek ke tim.
Bahasa Indonesia casual, ramah.`
            },
            { role: 'user', content: userMessage }
        ]
    });
    
    // 4. Track FAQ usage
    if (relevantFAQs.length > 0 && relevantFAQs[0].score > 0.8) {
        await db.faqs.updateOne(
            { id: relevantFAQs[0].id },
            { $inc: { 'metadata.viewCount': 1 } }
        );
    }
    
    return response.choices[0].message.content;
}

Admin Panel untuk Update FAQ

javascript

// API untuk admin manage FAQ
const express = require('express');
const router = express.Router();

// List all FAQs
router.get('/faqs', async (req, res) => {
    const faqs = await db.faqs.find({}).toArray();
    res.json(faqs);
});

// Add new FAQ
router.post('/faqs', async (req, res) => {
    const { question, variations, answer, keywords, categoryId } = req.body;
    
    const faq = {
        id: generateId(),
        categoryId,
        question,
        variations: variations || [],
        answer,
        keywords: keywords || [],
        metadata: {
            lastUpdated: new Date(),
            viewCount: 0,
            helpfulCount: 0,
            notHelpfulCount: 0
        },
        active: true
    };
    
    await db.faqs.insertOne(faq);
    
    // Regenerate embedding
    await generateEmbeddingForFAQ(faq.id);
    
    res.json({ success: true, faq });
});

// Update FAQ
router.put('/faqs/:id', async (req, res) => {
    const { id } = req.params;
    const updates = req.body;
    
    updates.metadata = updates.metadata || {};
    updates.metadata.lastUpdated = new Date();
    
    await db.faqs.updateOne({ id }, { $set: updates });
    
    // Regenerate embedding if content changed
    if (updates.question || updates.variations || updates.keywords) {
        await generateEmbeddingForFAQ(id);
    }
    
    res.json({ success: true });
});

// Delete FAQ
router.delete('/faqs/:id', async (req, res) => {
    const { id } = req.params;
    await db.faqs.deleteOne({ id });
    res.json({ success: true });
});

// Analytics
router.get('/faqs/analytics', async (req, res) => {
    const stats = await db.faqs.aggregate([
        {
            $group: {
                _id: '$categoryId',
                totalViews: { $sum: '$metadata.viewCount' },
                totalHelpful: { $sum: '$metadata.helpfulCount' },
                count: { $sum: 1 }
            }
        }
    ]).toArray();
    
    res.json(stats);
});

Auto-Learn dari Conversations

javascript

// Track unanswered questions
async function trackUnansweredQuestion(userMessage, aiResponse) {
    // Check if AI couldn't answer
    const unsurePatterns = [
        'saya cek dulu',
        'akan konfirmasi',
        'tidak ada informasi',
        'hubungi admin'
    ];
    
    const isUnanswered = unsurePatterns.some(p => 
        aiResponse.toLowerCase().includes(p)
    );
    
    if (isUnanswered) {
        await db.unansweredQuestions.insertOne({
            question: userMessage,
            aiResponse,
            timestamp: new Date(),
            resolved: false
        });
    }
}

// Admin review unanswered questions
router.get('/faqs/unanswered', async (req, res) => {
    const questions = await db.unansweredQuestions
        .find({ resolved: false })
        .sort({ timestamp: -1 })
        .limit(50)
        .toArray();
    
    res.json(questions);
});

// Convert unanswered to FAQ
router.post('/faqs/from-unanswered', async (req, res) => {
    const { unansweredId, answer, keywords, categoryId } = req.body;
    
    const unanswered = await db.unansweredQuestions.findOne({ 
        _id: unansweredId 
    });
    
    // Create new FAQ
    const faq = {
        id: generateId(),
        categoryId,
        question: unanswered.question,
        variations: [],
        answer,
        keywords,
        active: true
    };
    
    await db.faqs.insertOne(faq);
    await generateEmbeddingForFAQ(faq.id);
    
    // Mark as resolved
    await db.unansweredQuestions.updateOne(
        { _id: unansweredId },
        { $set: { resolved: true, resolvedAs: faq.id } }
    );
    
    res.json({ success: true, faq });
});

Best Practices

DO ✅

- Update FAQ regularly
- Track unanswered questions
- Use semantic search
- Monitor FAQ performance
- Allow admin easy updates
- Version control answers

DON'T ❌

- Set and forget
- Ignore unanswered patterns
- Keyword-only matching
- No analytics
- Hard-coded FAQs
- No review process

FAQ

Seberapa sering update FAQ?

Weekly review untuk unanswered questions. Monthly untuk full audit.

Perlu berapa FAQ untuk mulai?

Minimum 20-30 FAQ untuk coverage dasar. Tambah seiring waktu.


Kesimpulan

Dynamic FAQ = Always relevant!

Static FAQDynamic AI FAQ
Exact matchSemantic match
Manual updateAuto-learn
LimitedComprehensive

Setup Dynamic FAQ →


Artikel Terkait