Bot WA AI dengan Memory/Context
Cara membuat bot AI WhatsApp yang ingat percakapan sebelumnya. Context management, long-term memory. Tutorial lengkap!
Bot yang ingat = Customer experience lebih baik!
Bot tanpa memory terasa kaku dan frustasi. Dengan memory management yang baik, bot bisa ingat preferensi, history pembelian, dan konteks percakapan.
Jenis Memory
🧠 TIPE MEMORY:
1. SHORT-TERM (Conversation)
• Dalam satu sesi chat
• Context window AI
• Hilang setelah sesi
2. LONG-TERM (Persistent)
• Antar sesi/hari
• Disimpan di database
• Preferensi, history
3. EPISODIC (Events)
• Kejadian spesifik
• "Kemarin order dress merah"
• Timestamp-based
4. SEMANTIC (Facts)
• Fakta tentang user
• "Customer VIP", "Suka warna pink"
• Tidak time-boundShort-Term Memory
Basic Context Window:
javascript
// Simpan conversation history per user
const conversations = new Map();
async function chat(userId, userMessage) {
// Get or create conversation
if (!conversations.has(userId)) {
conversations.set(userId, []);
}
const history = conversations.get(userId);
// Add user message
history.push({ role: 'user', content: userMessage });
// Keep only last N messages (context window)
const recentHistory = history.slice(-20);
// Call AI with context
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [
{ role: 'system', content: SYSTEM_PROMPT },
...recentHistory
]
});
const aiResponse = response.choices[0].message.content;
// Add AI response to history
history.push({ role: 'assistant', content: aiResponse });
// Update stored history
conversations.set(userId, history.slice(-20));
return aiResponse;
}Dengan Timeout Reset:
javascript
const conversationTimeout = 30 * 60 * 1000; // 30 menit
const conversations = new Map();
function getConversation(userId) {
const conv = conversations.get(userId);
if (!conv) {
return createNewConversation(userId);
}
// Check if expired
if (Date.now() - conv.lastActivity > conversationTimeout) {
// Reset conversation tapi keep user data
return createNewConversation(userId, conv.userData);
}
// Update last activity
conv.lastActivity = Date.now();
return conv;
}
function createNewConversation(userId, userData = null) {
const conv = {
userId,
messages: [],
lastActivity: Date.now(),
userData: userData || {}
};
conversations.set(userId, conv);
return conv;
}Long-Term Memory (Database)
Schema:
javascript
// MongoDB Schema
const userMemorySchema = {
oderId
userId: String, // WhatsApp ID
// Profile info
profile: {
name: String,
location: String,
language: String,
joinedAt: Date
},
// Preferences
preferences: {
favoriteCategories: [String],
preferredColors: [String],
sizeInfo: Object,
communicationStyle: String
},
// Purchase history
purchaseHistory: [{
orderId: String,
products: [String],
total: Number,
date: Date
}],
// Important notes
notes: [{
content: String,
addedAt: Date,
source: String // 'ai' or 'human'
}],
// Conversation summaries
conversationSummaries: [{
date: Date,
summary: String,
topics: [String]
}],
// Stats
stats: {
totalOrders: Number,
totalSpent: Number,
lastContact: Date,
lastPurchase: Date
}
};Save & Retrieve:
javascript
async function getUserMemory(userId) {
let memory = await db.userMemory.findOne({ oderId
userId });
if (!memory) {
memory = await db.userMemory.create({
oderId
userId,
profile: {},
preferences: {},
purchaseHistory: [],
notes: [],
conversationSummaries: [],
stats: {
totalOrders: 0,
totalSpent: 0,
lastContact: new Date()
}
});
}
return memory;
}
async function updateUserMemory(userId, updates) {
await db.userMemory.updateOne(
{ oderId
userId },
{
$set: {
...updates,
'stats.lastContact': new Date()
}
}
);
}
// Add note about user
async function addUserNote(userId, note) {
await db.userMemory.updateOne(
{ oderId
userId },
{
$push: {
notes: {
content: note,
addedAt: new Date(),
source: 'ai'
}
}
}
);
}AI Memory Integration
System Prompt dengan Memory:
javascript
async function buildPromptWithMemory(userId) {
const memory = await getUserMemory(userId);
let memoryContext = '';
if (memory.profile.name) {
memoryContext += `Nama customer: ${memory.profile.name}\n`;
}
if (memory.preferences.favoriteCategories?.length) {
memoryContext += `Kategori favorit: ${memory.preferences.favoriteCategories.join(', ')}\n`;
}
if (memory.purchaseHistory?.length) {
const recentPurchases = memory.purchaseHistory.slice(-3);
memoryContext += `Pembelian terakhir:\n`;
recentPurchases.forEach(p => {
memoryContext += `- ${p.products.join(', ')} (${p.date})\n`;
});
}
if (memory.notes?.length) {
const recentNotes = memory.notes.slice(-5);
memoryContext += `Notes:\n`;
recentNotes.forEach(n => {
memoryContext += `- ${n.content}\n`;
});
}
const systemPrompt = `${BASE_SYSTEM_PROMPT}
INFORMASI CUSTOMER INI:
${memoryContext || 'Customer baru, belum ada history.'}
Gunakan informasi di atas untuk personalisasi response.
Jika ada info baru tentang customer (nama, preferensi, dll),
sebutkan untuk disimpan dengan format: [SAVE: kategori: info]`;
return systemPrompt;
}Extract & Save New Info:
javascript
async function processAndSaveMemory(userId, aiResponse, userMessage) {
// Check for save commands from AI
const saveMatches = aiResponse.matchAll(/\[SAVE: (\w+): (.+?)\]/g);
for (const match of saveMatches) {
const [, category, info] = match;
switch (category) {
case 'name':
await updateUserMemory(userId, { 'profile.name': info });
break;
case 'preference':
await db.userMemory.updateOne(
{ oderId
userId },
{ $addToSet: { 'preferences.favoriteCategories': info } }
);
break;
case 'note':
await addUserNote(userId, info);
break;
}
}
// Auto-detect info from conversation
await autoExtractInfo(userId, userMessage, aiResponse);
}
async function autoExtractInfo(userId, userMessage, aiResponse) {
// Simple pattern matching
const namePattern = /nama saya (\w+)/i;
const nameMatch = userMessage.match(namePattern);
if (nameMatch) {
await updateUserMemory(userId, { 'profile.name': nameMatch[1] });
}
// Or use AI for extraction
const extraction = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{
role: 'user',
content: `Extract any personal info from this message.
Return JSON with fields: name, location, preferences, notes.
If no info found, return empty object.
Message: "${userMessage}"`
}],
response_format: { type: 'json_object' }
});
const extracted = JSON.parse(extraction.choices[0].message.content);
if (Object.keys(extracted).length > 0) {
// Save extracted info
if (extracted.name) {
await updateUserMemory(userId, { 'profile.name': extracted.name });
}
// ... other fields
}
}Conversation Summarization
javascript
async function summarizeConversation(userId) {
const conv = conversations.get(userId);
if (!conv || conv.messages.length < 10) return;
// Summarize dengan AI
const summary = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{
role: 'user',
content: `Summarize this customer conversation in 2-3 sentences.
Include: main topics, outcomes, any important info about customer.
Conversation:
${conv.messages.map(m => `${m.role}: ${m.content}`).join('\n')}`
}]
});
// Save summary
await db.userMemory.updateOne(
{ oderId
userId },
{
$push: {
conversationSummaries: {
date: new Date(),
summary: summary.choices[0].message.content,
topics: []
}
}
}
);
// Clear short-term memory but keep summary reference
conv.messages = [];
}
// Run summarization periodically or on conversation end
setInterval(async () => {
for (const [userId, conv] of conversations.entries()) {
const timeSinceLastActivity = Date.now() - conv.lastActivity;
// If inactive for 1 hour, summarize
if (timeSinceLastActivity > 60 * 60 * 1000) {
await summarizeConversation(userId);
}
}
}, 15 * 60 * 1000); // Check every 15 minutesContoh Percakapan dengan Memory
=== PERCAKAPAN PERTAMA ===
👤: Halo, mau tanya soal dress
🤖: Hai! Selamat datang di [BRAND]! 😊
Mau cari dress untuk acara apa kak?
[Customer baru]
👤: Untuk kondangan, nama saya Dina btw
🤖: Hai Kak Dina! Salam kenal ya 😊
Untuk kondangan, kami punya beberapa pilihan:
[SAVE: name: Dina]
...
=== PERCAKAPAN KEDUA (Seminggu kemudian) ===
👤: Hai lagi
🤖: Hai Kak Dina! Welcome back! 😊
Gimana dress kondangannya kemarin, cocok?
Ada yang bisa dibantu hari ini?
[Memory: Dina, pernah cari dress kondangan]
👤: Bagus! Sekarang mau cari dress casual
🤖: Wah senang dengarnya cocok kak! 💕
Untuk casual dress, warna favoritnya apa kak?
Kemarin kan ambil yang [warna X] ya.
[Memory: Previous purchase reference]Best Practices
DO ✅
- Simpan info yang relevan saja
- Update memory secara berkala
- Gunakan memory untuk personalisasi
- Summarize long conversations
- Provide option to "forget"DON'T ❌
- Simpan semua tanpa filter
- Unlimited context window (costly)
- Expose memory to user directly
- Store sensitive info (password, etc)
- Forget to handle privacyFAQ
Berapa lama simpan memory?
Active customers: unlimited. Inactive > 1 tahun: bisa archive/delete.
Apakah bikin AI lebih mahal?
Sedikit. Tapi memory context biasanya < 500 tokens, worth it untuk better experience.
Kesimpulan
Memory = Personalized experience!
| No Memory | With Memory |
|---|---|
| Generic | Personalized |
| Ask again | Remember |
| Transactional | Relationship |