--- title: "Customer Support Bot" description: "Build an intelligent support system that remembers customer history and provides personalized help" --- Create a customer support system that remembers every interaction, tracks issues across conversations, and provides personalized support based on customer history and preferences. ## What You'll Build A customer support bot that: - **Remembers customer history** across all conversations and channels - **Tracks ongoing issues** and follows up automatically - **Provides personalized responses** based on customer tier and preferences - **Escalates complex issues** to human agents with full context - **Learns from resolutions** to improve future responses ## Prerequisites - Node.js 18+ or Python 3.8+ - Supermemory API key - OpenAI API key - Customer database or CRM integration - Basic understanding of customer support workflows ## Implementation ### Step 1: Customer Context Management ```typescript lib/customer-context.ts import { Supermemory } from 'supermemory' const client = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY! }) interface Customer { id: string email: string name: string tier: 'free' | 'pro' | 'enterprise' joinDate: string preferences?: Record } interface SupportTicket { id: string customerId: string subject: string status: 'open' | 'pending' | 'resolved' | 'closed' priority: 'low' | 'medium' | 'high' | 'urgent' category: string createdAt: string updatedAt: string assignedAgent?: string } export class CustomerContextManager { private getContainerTag(customerId: string): string { return `customer_${customerId}` } async addInteraction(customerId: string, interaction: { type: 'chat' | 'email' | 'phone' | 'ticket' content: string channel: string outcome?: 'resolved' | 'escalated' | 'pending' agentId?: string metadata?: Record }) { try { const result = await client.add({ content: `${interaction.type.toUpperCase()}: ${interaction.content}`, containerTag: this.getContainerTag(customerId), metadata: { type: 'customer_interaction', interactionType: interaction.type, channel: interaction.channel, outcome: interaction.outcome, agentId: interaction.agentId, timestamp: new Date().toISOString(), ...interaction.metadata } }) return result } catch (error) { console.error('Failed to add customer interaction:', error) throw error } } async getCustomerHistory(customerId: string, limit: number = 10) { try { const memories = await client.documents.list({ containerTags: [this.getContainerTag(customerId)], limit, sort: 'updatedAt', order: 'desc' }) return memories.memories.map(memory => ({ id: memory.id, content: memory.content, type: memory.metadata?.interactionType || 'unknown', channel: memory.metadata?.channel, outcome: memory.metadata?.outcome, timestamp: memory.metadata?.timestamp || memory.createdAt, agentId: memory.metadata?.agentId })) } catch (error) { console.error('Failed to get customer history:', error) throw error } } async searchCustomerContext(customerId: string, query: string) { try { const results = await client.search.memories({ q: query, containerTag: this.getContainerTag(customerId), threshold: 0.6, limit: 5, rerank: true }) return results.results.map(result => ({ content: result.memory, similarity: result.similarity, metadata: result.metadata })) } catch (error) { console.error('Failed to search customer context:', error) throw error } } async trackIssue(customerId: string, issue: { subject: string description: string category: string priority: 'low' | 'medium' | 'high' | 'urgent' status: 'open' | 'pending' | 'resolved' }) { try { const issueContent = `ISSUE: ${issue.subject}\n\nDescription: ${issue.description}\nCategory: ${issue.category}\nPriority: ${issue.priority}\nStatus: ${issue.status}` const result = await client.add({ content: issueContent, containerTag: this.getContainerTag(customerId), metadata: { type: 'support_issue', subject: issue.subject, category: issue.category, priority: issue.priority, status: issue.status, createdAt: new Date().toISOString() } }) return result } catch (error) { console.error('Failed to track issue:', error) throw error } } async updateIssueStatus(issueId: string, status: 'open' | 'pending' | 'resolved' | 'closed', resolution?: string) { try { // Note: In a real implementation, you'd update the memory // For now, we'll add a status update const memory = await client.documents.get(issueId) const customerId = memory.containerTags?.[0]?.replace('customer_', '') || '' const updateContent = `ISSUE UPDATE: ${memory.metadata?.subject}\nStatus changed to: ${status}${resolution ? `\nResolution: ${resolution}` : ''}` return await this.addInteraction(customerId, { type: 'ticket', content: updateContent, channel: 'internal', outcome: status === 'resolved' ? 'resolved' : 'pending', metadata: { originalIssueId: issueId, statusUpdate: true } }) } catch (error) { console.error('Failed to update issue status:', error) throw error } } } ``` ```python customer_context.py from supermemory import Supermemory import os from typing import Dict, List, Any, Optional from datetime import datetime from enum import Enum class InteractionType(Enum): CHAT = "chat" EMAIL = "email" PHONE = "phone" TICKET = "ticket" class Priority(Enum): LOW = "low" MEDIUM = "medium" HIGH = "high" URGENT = "urgent" class Status(Enum): OPEN = "open" PENDING = "pending" RESOLVED = "resolved" CLOSED = "closed" class CustomerContextManager: def __init__(self): self.client = Supermemory(api_key=os.getenv("SUPERMEMORY_API_KEY")) def _get_container_tag(self, customer_id: str) -> str: return f"customer_{customer_id}" def add_interaction(self, customer_id: str, interaction: Dict[str, Any]) -> Dict: """Add a customer interaction to memory""" try: content = f"{interaction['type'].upper()}: {interaction['content']}" result = self.client.add( content=content, container_tag=self._get_container_tag(customer_id), metadata={ 'type': 'customer_interaction', 'interactionType': interaction['type'], 'channel': interaction['channel'], 'outcome': interaction.get('outcome'), 'agentId': interaction.get('agentId'), 'timestamp': datetime.now().isoformat(), **interaction.get('metadata', {}) } ) return result except Exception as e: print(f"Failed to add customer interaction: {e}") raise def get_customer_history(self, customer_id: str, limit: int = 10) -> List[Dict]: """Get customer interaction history""" try: memories = self.client.documents.list( container_tags=[self._get_container_tag(customer_id)], limit=limit, sort='updatedAt', order='desc' ) return [ { 'id': memory.id, 'content': memory.content, 'type': memory.metadata.get('interactionType', 'unknown') if memory.metadata else 'unknown', 'channel': memory.metadata.get('channel') if memory.metadata else None, 'outcome': memory.metadata.get('outcome') if memory.metadata else None, 'timestamp': memory.metadata.get('timestamp', memory.created_at) if memory.metadata else memory.created_at, 'agentId': memory.metadata.get('agentId') if memory.metadata else None } for memory in memories.memories ] except Exception as e: print(f"Failed to get customer history: {e}") raise def search_customer_context(self, customer_id: str, query: str) -> List[Dict]: """Search customer's interaction history""" try: results = self.client.search.memories( q=query, container_tag=self._get_container_tag(customer_id), threshold=0.6, limit=5, rerank=True ) return [ { 'content': result.memory, 'similarity': result.similarity, 'metadata': result.metadata } for result in results.results ] except Exception as e: print(f"Failed to search customer context: {e}") raise def track_issue(self, customer_id: str, issue: Dict[str, str]) -> Dict: """Track a customer support issue""" try: issue_content = f"""ISSUE: {issue['subject']} Description: {issue['description']} Category: {issue['category']} Priority: {issue['priority']} Status: {issue['status']}""" result = self.client.add( content=issue_content, container_tag=self._get_container_tag(customer_id), metadata={ 'type': 'support_issue', 'subject': issue['subject'], 'category': issue['category'], 'priority': issue['priority'], 'status': issue['status'], 'createdAt': datetime.now().isoformat() } ) return result except Exception as e: print(f"Failed to track issue: {e}") raise def update_issue_status(self, issue_id: str, status: str, resolution: Optional[str] = None) -> Dict: """Update the status of a support issue""" try: # Get original issue memory = self.client.documents.get(issue_id) customer_id = (memory.container_tags[0] if memory.container_tags else '').replace('customer_', '') update_content = f"ISSUE UPDATE: {memory.metadata.get('subject', 'Unknown')}\nStatus changed to: {status}" if resolution: update_content += f"\nResolution: {resolution}" return self.add_interaction(customer_id, { 'type': 'ticket', 'content': update_content, 'channel': 'internal', 'outcome': 'resolved' if status == 'resolved' else 'pending', 'metadata': { 'originalIssueId': issue_id, 'statusUpdate': True } }) except Exception as e: print(f"Failed to update issue status: {e}") raise ``` ### Step 2: Support API with Context ```typescript app/api/support/chat/route.ts import { streamText } from 'ai' import { createOpenAI } from '@ai-sdk/openai' import { CustomerContextManager } from '@/lib/customer-context' const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY! }) const contextManager = new CustomerContextManager() interface Customer { id: string name: string email: string tier: 'free' | 'pro' | 'enterprise' joinDate: string } export async function POST(request: Request) { const { message, customerId, customer, conversationHistory = [], agentId } = await request.json() try { // Get customer history and context const [history, contextResults] = await Promise.all([ contextManager.getCustomerHistory(customerId, 5), contextManager.searchCustomerContext(customerId, message) ]) // Build customer context const customerContext = ` CUSTOMER PROFILE: - Name: ${customer.name} - Email: ${customer.email} - Tier: ${customer.tier.toUpperCase()} - Member since: ${customer.joinDate} RECENT INTERACTIONS (Last 5): ${history.map(h => `- ${h.timestamp}: ${h.type.toUpperCase()} - ${h.content.substring(0, 100)}...`).join('\n')} RELEVANT CONTEXT: ${contextResults.map(c => `- ${c.content.substring(0, 150)}... (${(c.similarity * 100).toFixed(1)}% relevant)`).join('\n')} `.trim() // Determine if escalation is needed const escalationKeywords = ['angry', 'frustrated', 'cancel', 'refund', 'legal', 'complaint', 'manager', 'supervisor'] const needsEscalation = escalationKeywords.some(keyword => message.toLowerCase().includes(keyword) ) || customer.tier === 'enterprise' const systemPrompt = `You are a helpful customer support agent with access to complete customer history and context. CUSTOMER CONTEXT: ${customerContext} SUPPORT GUIDELINES: 1. **Personalization**: Address the customer by name and reference their tier/history when relevant 2. **Context Awareness**: Use previous interactions to inform your response 3. **Tier-Specific Service**: - Free: Standard support, guide to self-service resources - Pro: Priority support, detailed explanations, proactive suggestions - Enterprise: White-glove service, immediate escalation path, dedicated attention 4. **Issue Tracking**: If this is a new issue, categorize it (billing, technical, account, product) 5. **Escalation**: ${needsEscalation ? 'This interaction may need human agent escalation - provide helpful response but prepare escalation summary' : 'Handle directly unless customer specifically requests human agent'} RESPONSE STYLE: - Professional but friendly - Reference specific details from customer history when relevant - Provide actionable next steps - Include relevant links or resources for their tier level If you cannot resolve the issue completely, prepare a clear summary for escalation to human agents.` const messages = [ { role: 'system' as const, content: systemPrompt }, ...conversationHistory, { role: 'user' as const, content: message } ] const result = await streamText({ model: openai('gpt-5'), messages, temperature: 0.3, maxTokens: 800, onFinish: async (completion) => { // Store this interaction await contextManager.addInteraction(customerId, { type: 'chat', content: `Customer: ${message}\nAgent: ${completion.text}`, channel: 'web_chat', outcome: needsEscalation ? 'escalated' : 'resolved', agentId, metadata: { customerTier: customer.tier, needsEscalation, responseLength: completion.text.length } }) // If this looks like a new issue, track it if (message.length > 50 && !contextResults.some(c => c.similarity > 0.8)) { const issueCategory = categorizeIssue(message) const priority = determinePriority(customer.tier, message) await contextManager.trackIssue(customerId, { subject: message.substring(0, 100), description: message, category: issueCategory, priority, status: needsEscalation ? 'pending' : 'open' }) } } }) return result.toAIStreamResponse({ data: { needsEscalation, customerTier: customer.tier, contextCount: contextResults.length } }) } catch (error) { console.error('Support chat error:', error) return Response.json( { error: 'Failed to process support request', details: error.message }, { status: 500 } ) } } function categorizeIssue(message: string): string { const categories = { billing: ['bill', 'charge', 'payment', 'refund', 'price', 'cost'], technical: ['error', 'bug', 'broken', 'not working', 'crash', 'slow'], account: ['login', 'password', 'access', 'settings', 'profile'], product: ['feature', 'how to', 'tutorial', 'help', 'guide'] } const messageLower = message.toLowerCase() for (const [category, keywords] of Object.entries(categories)) { if (keywords.some(keyword => messageLower.includes(keyword))) { return category } } return 'general' } function determinePriority(tier: string, message: string): 'low' | 'medium' | 'high' | 'urgent' { const urgentKeywords = ['urgent', 'critical', 'emergency', 'down', 'broken'] const highKeywords = ['important', 'asap', 'soon', 'problem'] const messageLower = message.toLowerCase() if (urgentKeywords.some(keyword => messageLower.includes(keyword))) { return 'urgent' } if (tier === 'enterprise') { return highKeywords.some(keyword => messageLower.includes(keyword)) ? 'urgent' : 'high' } if (tier === 'pro') { return highKeywords.some(keyword => messageLower.includes(keyword)) ? 'high' : 'medium' } return 'low' } ``` ```python support_api.py from fastapi import FastAPI, HTTPException from fastapi.responses import StreamingResponse from pydantic import BaseModel from typing import List, Dict, Any, Optional import openai from customer_context import CustomerContextManager import json import os import re app = FastAPI() openai_client = openai.AsyncOpenAI(api_key=os.getenv("OPENAI_API_KEY")) context_manager = CustomerContextManager() class Customer(BaseModel): id: str name: str email: str tier: str joinDate: str class SupportRequest(BaseModel): message: str customerId: str customer: Customer conversationHistory: List[Dict[str, str]] = [] agentId: Optional[str] = None def categorize_issue(message: str) -> str: """Categorize support issue based on message content""" categories = { 'billing': ['bill', 'charge', 'payment', 'refund', 'price', 'cost'], 'technical': ['error', 'bug', 'broken', 'not working', 'crash', 'slow'], 'account': ['login', 'password', 'access', 'settings', 'profile'], 'product': ['feature', 'how to', 'tutorial', 'help', 'guide'] } message_lower = message.lower() for category, keywords in categories.items(): if any(keyword in message_lower for keyword in keywords): return category return 'general' def determine_priority(tier: str, message: str) -> str: """Determine issue priority based on tier and message content""" urgent_keywords = ['urgent', 'critical', 'emergency', 'down', 'broken'] high_keywords = ['important', 'asap', 'soon', 'problem'] message_lower = message.lower() if any(keyword in message_lower for keyword in urgent_keywords): return 'urgent' if tier == 'enterprise': return 'urgent' if any(keyword in message_lower for keyword in high_keywords) else 'high' if tier == 'pro': return 'high' if any(keyword in message_lower for keyword in high_keywords) else 'medium' return 'low' @app.post("/support/chat") async def support_chat(request: SupportRequest): try: # Get customer history and context history = context_manager.get_customer_history(request.customerId, 5) context_results = context_manager.search_customer_context(request.customerId, request.message) # Build customer context customer_context = f""" CUSTOMER PROFILE: - Name: {request.customer.name} - Email: {request.customer.email} - Tier: {request.customer.tier.upper()} - Member since: {request.customer.joinDate} RECENT INTERACTIONS (Last 5): {chr(10).join([f"- {h['timestamp']}: {h['type'].upper()} - {h['content'][:100]}..." for h in history])} RELEVANT CONTEXT: {chr(10).join([f"- {c['content'][:150]}... ({c['similarity']*100:.1f}% relevant)" for c in context_results])} """.strip() # Determine if escalation is needed escalation_keywords = ['angry', 'frustrated', 'cancel', 'refund', 'legal', 'complaint', 'manager', 'supervisor'] needs_escalation = any(keyword in request.message.lower() for keyword in escalation_keywords) or request.customer.tier == 'enterprise' system_prompt = f"""You are a helpful customer support agent with access to complete customer history and context. CUSTOMER CONTEXT: {customer_context} SUPPORT GUIDELINES: 1. **Personalization**: Address the customer by name and reference their tier/history when relevant 2. **Context Awareness**: Use previous interactions to inform your response 3. **Tier-Specific Service**: - Free: Standard support, guide to self-service resources - Pro: Priority support, detailed explanations, proactive suggestions - Enterprise: White-glove service, immediate escalation path, dedicated attention 4. **Issue Tracking**: If this is a new issue, categorize it (billing, technical, account, product) 5. **Escalation**: {'This interaction may need human agent escalation - provide helpful response but prepare escalation summary' if needs_escalation else 'Handle directly unless customer specifically requests human agent'} RESPONSE STYLE: - Professional but friendly - Reference specific details from customer history when relevant - Provide actionable next steps - Include relevant links or resources for their tier level If you cannot resolve the issue completely, prepare a clear summary for escalation to human agents.""" messages = [ {"role": "system", "content": system_prompt}, *request.conversationHistory, {"role": "user", "content": request.message} ] response = await openai_client.chat.completions.create( model="gpt-5", messages=messages, temperature=0.3, max_tokens=800, stream=True ) async def generate(): full_response = "" async for chunk in response: if chunk.choices[0].delta.content: content = chunk.choices[0].delta.content full_response += content yield f"data: {json.dumps({'content': content})}\n\n" # Store interaction after completion context_manager.add_interaction(request.customerId, { 'type': 'chat', 'content': f"Customer: {request.message}\nAgent: {full_response}", 'channel': 'web_chat', 'outcome': 'escalated' if needs_escalation else 'resolved', 'agentId': request.agentId, 'metadata': { 'customerTier': request.customer.tier, 'needsEscalation': needs_escalation, 'responseLength': len(full_response) } }) # Track new issues if len(request.message) > 50 and not any(c['similarity'] > 0.8 for c in context_results): issue_category = categorize_issue(request.message) priority = determine_priority(request.customer.tier, request.message) context_manager.track_issue(request.customerId, { 'subject': request.message[:100], 'description': request.message, 'category': issue_category, 'priority': priority, 'status': 'pending' if needs_escalation else 'open' }) yield f"data: {json.dumps({'done': True, 'needsEscalation': needs_escalation})}\n\n" return StreamingResponse(generate(), media_type="text/plain") except Exception as e: raise HTTPException(status_code=500, detail=f"Support chat error: {str(e)}") if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000) ``` ### Step 3: Support Dashboard Interface ```tsx app/support/page.tsx 'use client' import { useState, useEffect } from 'react' import { useChat } from 'ai/react' import { CustomerContextManager } from '@/lib/customer-context' interface Customer { id: string name: string email: string tier: 'free' | 'pro' | 'enterprise' joinDate: string } interface SupportTicket { id: string subject: string status: 'open' | 'pending' | 'resolved' | 'closed' priority: 'low' | 'medium' | 'high' | 'urgent' category: string createdAt: string } export default function SupportDashboard() { const [selectedCustomer, setSelectedCustomer] = useState(null) const [customerHistory, setCustomerHistory] = useState([]) const [tickets, setTickets] = useState([]) const [showEscalation, setShowEscalation] = useState(false) const [agentId] = useState('agent_001') // In real app, get from auth const contextManager = new CustomerContextManager() const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({ api: '/api/support/chat', body: { customerId: selectedCustomer?.id, customer: selectedCustomer, agentId }, onFinish: (message, { data }) => { if (data?.needsEscalation) { setShowEscalation(true) } // Refresh customer history if (selectedCustomer) { loadCustomerHistory(selectedCustomer.id) } } }) // Mock customers - in real app, fetch from your customer database const mockCustomers: Customer[] = [ { id: 'cust_001', name: 'Sarah Johnson', email: 'sarah@example.com', tier: 'pro', joinDate: '2023-06-15' }, { id: 'cust_002', name: 'TechCorp Inc', email: 'support@techcorp.com', tier: 'enterprise', joinDate: '2022-03-20' }, { id: 'cust_003', name: 'Mike Chen', email: 'mike@startup.com', tier: 'free', joinDate: '2024-01-10' } ] const loadCustomerHistory = async (customerId: string) => { try { const history = await contextManager.getCustomerHistory(customerId, 10) setCustomerHistory(history) } catch (error) { console.error('Failed to load customer history:', error) } } const handleCustomerSelect = async (customer: Customer) => { setSelectedCustomer(customer) await loadCustomerHistory(customer.id) setShowEscalation(false) } const getTierColor = (tier: string) => { switch (tier) { case 'enterprise': return 'bg-purple-100 text-purple-800' case 'pro': return 'bg-blue-100 text-blue-800' case 'free': return 'bg-gray-100 text-gray-800' default: return 'bg-gray-100 text-gray-800' } } const getPriorityColor = (priority: string) => { switch (priority) { case 'urgent': return 'bg-red-100 text-red-800' case 'high': return 'bg-orange-100 text-orange-800' case 'medium': return 'bg-yellow-100 text-yellow-800' case 'low': return 'bg-green-100 text-green-800' default: return 'bg-gray-100 text-gray-800' } } return (
{/* Customer List Sidebar */}

Customers

{mockCustomers.map((customer) => (
handleCustomerSelect(customer)} className={`p-4 cursor-pointer hover:bg-gray-50 ${ selectedCustomer?.id === customer.id ? 'bg-blue-50 border-r-2 border-blue-500' : '' }`} >
{customer.name}
{customer.tier}
{customer.email}
Member since {customer.joinDate}
))}
{/* Main Content */}
{selectedCustomer ? ( <> {/* Customer Header */}

{selectedCustomer.name}

{selectedCustomer.email}

{selectedCustomer.tier.toUpperCase()} Customer {showEscalation && (
Needs Escalation
)}
{/* Chat Area */}
{/* Messages */}
{messages.length === 0 && (
Welcome to Support Chat

Start a conversation with {selectedCustomer.name}

Customer Tier: {selectedCustomer.tier}

Join Date: {selectedCustomer.joinDate}

)} {messages.map((message) => (
{message.role === 'user' ? selectedCustomer.name : 'Support Agent'} {new Date().toLocaleTimeString()}
{message.content}
))} {isLoading && (
Agent is typing...
)}
{/* Chat Input */}
{/* Customer History Sidebar */}

Customer History

{customerHistory.map((interaction, index) => (
{interaction.type} {new Date(interaction.timestamp).toLocaleDateString()}

{interaction.content.length > 100 ? `${interaction.content.substring(0, 100)}...` : interaction.content }

{interaction.outcome && (
{interaction.outcome}
)}
))} {customerHistory.length === 0 && (

No previous interactions

)}
) : (
Customer Support

Select a customer to start a support conversation

)}
) } ``` ## Testing Your Support System ### Step 4: Test Support Scenarios 1. **Test Customer Tiers**: - Free tier: Basic responses, self-service guidance - Pro tier: Detailed help, proactive suggestions - Enterprise: White-glove service, escalation readiness 2. **Test Memory & Context**: - Ask about a previous issue - Reference customer preferences - Follow up on unresolved tickets 3. **Test Escalation Triggers**: - Use keywords like "angry", "manager", "refund" - Test enterprise customer automatic escalation This comprehensive customer support recipe provides the foundation for building intelligent, context-aware support systems that improve customer satisfaction through personalized service. --- *Customize this recipe based on your specific support workflows and customer needs.*