Building a Customer Experience System
This tutorial demonstrates how to build a customer experience system that uses Papr Memory to store company knowledge and provide relevant information to customer service agents.
Prerequisites
Before you begin, you'll need:
- A Papr Memory API key
- An OpenAI API key
- Node.js installed
Implementation
1. Project Setup
Set up your project:
mkdir customer-experience
cd customer-experience
npm init -y
npm install express dotenv node-fetch openai cors
Create a .env
file:
PAPR_API_KEY=your_papr_api_key_here
OPENAI_API_KEY=your_openai_api_key_here
2. Knowledge Base Management
Create knowledge-base.js
:
import fetch from 'node-fetch';
import dotenv from 'dotenv';
dotenv.config();
// Function to add knowledge base article to memory
export async function addKnowledgeArticle(article) {
const { title, content, category } = article;
const response = await fetch('https://memory.papr.ai/v1/memory', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.PAPR_API_KEY,
'Accept-Encoding': 'gzip'
},
body: JSON.stringify({
content,
type: 'text',
metadata: {
type: 'knowledge_article',
title,
category,
timestamp: new Date().toISOString()
}
})
});
if (!response.ok) {
throw new Error(`Failed to add knowledge article: ${response.statusText}`);
}
return response.json();
}
// Function to add FAQ to memory
export async function addFAQ(faq) {
const { question, answer, category } = faq;
const response = await fetch('https://memory.papr.ai/v1/memory', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.PAPR_API_KEY,
'Accept-Encoding': 'gzip'
},
body: JSON.stringify({
content: `Q: ${question}\nA: ${answer}`,
type: 'text',
metadata: {
type: 'faq',
category,
timestamp: new Date().toISOString()
}
})
});
if (!response.ok) {
throw new Error(`Failed to add FAQ: ${response.statusText}`);
}
return response.json();
}
// Function to search knowledge base
export async function searchKnowledgeBase(query) {
const params = new URLSearchParams({
max_memories: '5',
max_nodes: '3'
});
const enhancedQuery = `Find information in our knowledge base or FAQs that answers the following customer question: "${query}". I need detailed information to help our customer support agent respond accurately.`;
const response = await fetch(`https://memory.papr.ai/v1/search?${params}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.PAPR_API_KEY,
'Accept-Encoding': 'gzip'
},
body: JSON.stringify({
query: enhancedQuery
})
});
if (!response.ok) {
throw new Error(`Failed to search knowledge base: ${response.statusText}`);
}
return response.json();
}
3. Customer Support Chat
Create app.js
:
import express from 'express';
import cors from 'cors';
import { OpenAI } from 'openai';
import { addKnowledgeArticle, addFAQ, searchKnowledgeBase } from './knowledge-base.js';
import fs from 'fs';
import path from 'path';
import dotenv from 'dotenv';
dotenv.config();
// Initialize Express
const app = express();
app.use(express.json());
app.use(cors());
app.use(express.static('public'));
// Initialize OpenAI
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY
});
// Knowledge base management endpoints
app.post('/api/knowledge/article', async (req, res) => {
try {
const result = await addKnowledgeArticle(req.body);
res.json(result);
} catch (error) {
console.error('Error adding knowledge article:', error);
res.status(500).json({ error: error.message });
}
});
app.post('/api/knowledge/faq', async (req, res) => {
try {
const result = await addFAQ(req.body);
res.json(result);
} catch (error) {
console.error('Error adding FAQ:', error);
res.status(500).json({ error: error.message });
}
});
// Chat endpoint with knowledge base integration
app.post('/api/chat', async (req, res) => {
try {
const { message, customerContext } = req.body;
if (!message) {
return res.status(400).json({ error: 'Message is required' });
}
// Search for relevant information in the knowledge base
const searchResults = await searchKnowledgeBase(message);
// Extract relevant context from search results
let knowledgeContext = "";
if (searchResults.data && searchResults.data.memories) {
knowledgeContext = searchResults.data.memories
.map(memory => memory.content)
.join('\n\n');
}
// Build messages for OpenAI
const messages = [
{
role: "system",
content: "You are a helpful customer support agent. Use the knowledge base information to answer customer questions accurately. If the information isn't in the knowledge base, acknowledge that and offer to escalate their question to a specialist."
}
];
// Add customer context if available
if (customerContext) {
messages.push({
role: "system",
content: `Customer context: ${customerContext}`
});
}
// Add knowledge base context if available
if (knowledgeContext) {
messages.push({
role: "system",
content: `Knowledge base information: ${knowledgeContext}`
});
}
// Add customer message
messages.push({ role: "user", content: message });
// Get response from OpenAI
const completion = await openai.chat.completions.create({
model: "gpt-3.5-turbo",
messages: messages,
});
const response = completion.choices[0].message.content;
res.json({
message: response,
sourcesUsed: searchResults.data?.memories?.length || 0
});
} catch (error) {
console.error('Error processing chat:', error);
res.status(500).json({ error: error.message });
}
});
// Create sample knowledge base data
const sampleData = {
articles: [
{
title: "Return Policy",
content: "Our standard return policy allows customers to return products within 30 days of purchase for a full refund. Products must be unused and in original packaging. For electronics, there is a 15-day return window. Customized products cannot be returned unless defective.",
category: "policies"
},
{
title: "Shipping Information",
content: "Standard shipping takes 3-5 business days. Express shipping takes 1-2 business days and costs an additional $15. International shipping is available for select countries with delivery times of 7-14 business days. All orders over $50 qualify for free standard shipping within the continental US.",
category: "shipping"
}
],
faqs: [
{
question: "How can I track my order?",
answer: "You can track your order by logging into your account and viewing 'Order History' or by clicking the tracking link in your shipping confirmation email. Our shipping partners provide real-time updates once your package is in transit.",
category: "shipping"
},
{
question: "Do you offer international shipping?",
answer: "Yes, we offer international shipping to over 30 countries. Shipping rates and delivery times vary by location. Please note that customers are responsible for any customs fees or import taxes that may apply.",
category: "shipping"
},
{
question: "How do I reset my password?",
answer: "To reset your password, click 'Forgot Password' on the login page. Enter your email address, and we'll send you a password reset link. The link expires after 24 hours. If you don't receive the email, please check your spam folder or contact customer support.",
category: "account"
}
]
};
// Create frontend files
function setupFrontend() {
fs.mkdirSync('public', { recursive: true });
// Create HTML file
fs.writeFileSync('public/index.html', `
<!DOCTYPE html>
<html>
<head>
<title>Customer Support System</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<div class="admin-panel">
<h2>Admin Panel</h2>
<div class="admin-tabs">
<button class="tab-button active" onclick="showTab('kb-article')">Add Article</button>
<button class="tab-button" onclick="showTab('kb-faq')">Add FAQ</button>
</div>
<div id="kb-article" class="tab-content active">
<h3>Add Knowledge Base Article</h3>
<form id="article-form">
<div class="form-group">
<label for="article-title">Title</label>
<input type="text" id="article-title" required>
</div>
<div class="form-group">
<label for="article-category">Category</label>
<input type="text" id="article-category" required>
</div>
<div class="form-group">
<label for="article-content">Content</label>
<textarea id="article-content" rows="6" required></textarea>
</div>
<button type="submit">Add Article</button>
</form>
</div>
<div id="kb-faq" class="tab-content">
<h3>Add FAQ</h3>
<form id="faq-form">
<div class="form-group">
<label for="faq-question">Question</label>
<input type="text" id="faq-question" required>
</div>
<div class="form-group">
<label for="faq-answer">Answer</label>
<textarea id="faq-answer" rows="4" required></textarea>
</div>
<div class="form-group">
<label for="faq-category">Category</label>
<input type="text" id="faq-category" required>
</div>
<button type="submit">Add FAQ</button>
</form>
</div>
<div id="admin-result" class="result-box"></div>
<h3>Populate Sample Data</h3>
<button id="populate-sample">Add Sample Data</button>
</div>
<div class="chat-panel">
<h2>Customer Support Chat</h2>
<div class="chat-container">
<div id="chat-messages"></div>
</div>
<div class="chat-input">
<input type="text" id="customer-context" placeholder="Customer context (optional)">
<input type="text" id="chat-input" placeholder="Type your message...">
<button id="send-button">Send</button>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
`);
// Create CSS file
fs.writeFileSync('public/style.css', `
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f5f5f5;
}
.container {
display: flex;
max-width: 1200px;
margin: 20px auto;
gap: 20px;
}
.admin-panel, .chat-panel {
flex: 1;
background: #fff;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
padding: 20px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input, textarea {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
button {
background-color: #4CAF50;
color: white;
border: none;
padding: 10px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
button:hover {
background-color: #45a049;
}
.chat-container {
height: 400px;
border: 1px solid #ddd;
border-radius: 4px;
overflow-y: auto;
padding: 10px;
margin-bottom: 15px;
background-color: #f9f9f9;
}
.chat-input {
display: flex;
flex-direction: column;
gap: 10px;
}
.chat-message {
margin-bottom: 15px;
padding: 10px;
border-radius: 4px;
}
.user-message {
background-color: #d1e7dd;
margin-left: 20px;
}
.bot-message {
background-color: #e2e3e5;
margin-right: 20px;
}
.admin-tabs {
display: flex;
margin-bottom: 15px;
border-bottom: 1px solid #ddd;
}
.tab-button {
background-color: transparent;
color: #333;
padding: 10px 15px;
margin-right: 5px;
border: none;
border-bottom: 2px solid transparent;
}
.tab-button.active {
border-bottom: 2px solid #4CAF50;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.result-box {
margin-top: 15px;
padding: 10px;
background-color: #f0f0f0;
border-radius: 4px;
display: none;
}
`);
// Create JavaScript file
fs.writeFileSync('public/script.js', `
// Tab functionality
function showTab(tabId) {
// Hide all tab contents
document.querySelectorAll('.tab-content').forEach(tab => {
tab.classList.remove('active');
});
// Remove active class from all buttons
document.querySelectorAll('.tab-button').forEach(button => {
button.classList.remove('active');
});
// Show the selected tab
document.getElementById(tabId).classList.add('active');
// Set the clicked button as active
event.target.classList.add('active');
}
// Add article form submission
document.getElementById('article-form').addEventListener('submit', async (e) => {
e.preventDefault();
const title = document.getElementById('article-title').value;
const category = document.getElementById('article-category').value;
const content = document.getElementById('article-content').value;
try {
const response = await fetch('/api/knowledge/article', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ title, category, content })
});
const result = await response.json();
showResult('Article added successfully!');
document.getElementById('article-form').reset();
} catch (error) {
showResult('Error adding article: ' + error.message);
}
});
// Add FAQ form submission
document.getElementById('faq-form').addEventListener('submit', async (e) => {
e.preventDefault();
const question = document.getElementById('faq-question').value;
const answer = document.getElementById('faq-answer').value;
const category = document.getElementById('faq-category').value;
try {
const response = await fetch('/api/knowledge/faq', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ question, answer, category })
});
const result = await response.json();
showResult('FAQ added successfully!');
document.getElementById('faq-form').reset();
} catch (error) {
showResult('Error adding FAQ: ' + error.message);
}
});
// Display result message
function showResult(message) {
const resultBox = document.getElementById('admin-result');
resultBox.textContent = message;
resultBox.style.display = 'block';
setTimeout(() => {
resultBox.style.display = 'none';
}, 3000);
}
// Chat functionality
document.getElementById('send-button').addEventListener('click', sendMessage);
document.getElementById('chat-input').addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendMessage();
}
});
async function sendMessage() {
const messageInput = document.getElementById('chat-input');
const contextInput = document.getElementById('customer-context');
const message = messageInput.value.trim();
const customerContext = contextInput.value.trim();
if (!message) return;
// Display user message
addMessage(message, 'user');
messageInput.value = '';
try {
// Display thinking indicator
const thinkingId = addMessage('Thinking...', 'bot');
// Send message to server
const response = await fetch('/api/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ message, customerContext })
});
const result = await response.json();
// Remove thinking indicator and add bot response
removeMessage(thinkingId);
addMessage(result.message, 'bot');
} catch (error) {
addMessage('Sorry, an error occurred. Please try again.', 'bot');
}
}
function addMessage(content, sender) {
const messagesContainer = document.getElementById('chat-messages');
const messageElement = document.createElement('div');
messageElement.className = 'chat-message ' + (sender === 'user' ? 'user-message' : 'bot-message');
messageElement.textContent = content;
const messageId = Date.now();
messageElement.id = 'msg-' + messageId;
messagesContainer.appendChild(messageElement);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
return messageId;
}
function removeMessage(messageId) {
const messageElement = document.getElementById('msg-' + messageId);
if (messageElement) {
messageElement.remove();
}
}
// Add sample data
document.getElementById('populate-sample').addEventListener('click', async () => {
try {
// Add sample articles
for (const article of sampleData.articles) {
await fetch('/api/knowledge/article', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(article)
});
}
// Add sample FAQs
for (const faq of sampleData.faqs) {
await fetch('/api/knowledge/faq', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(faq)
});
}
showResult('Sample data added successfully!');
} catch (error) {
showResult('Error adding sample data: ' + error.message);
}
});
`);
}
// Set up the application
setupFrontend();
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
3. Update package.json
Update your package.json to include:
{
"name": "customer-experience",
"version": "1.0.0",
"description": "Customer experience system with Papr Memory and OpenAI",
"main": "app.js",
"type": "module",
"scripts": {
"start": "node app.js"
},
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"node-fetch": "^3.3.2",
"openai": "^4.0.0"
}
}
Usage
- Start the server:
npm start
Open your browser to
http://localhost:3000
Use the Admin Panel to add knowledge base articles and FAQs, or use the "Add Sample Data" button
Use the chat interface to ask questions about policies, shipping, returns, etc.
How It Works
Knowledge Base Management:
- Company knowledge is stored as articles and FAQs in Papr Memory
- Each item includes metadata for categorization and organization
- The admin panel provides an interface for adding new knowledge
Customer Support Flow:
- Customer asks a question in the chat interface
- System searches Papr Memory for relevant information
- OpenAI receives the question and relevant knowledge base content
- OpenAI generates a helpful response using the knowledge provided
- The response is displayed to the customer
Contextual Intelligence:
- Search queries are enhanced to find the most relevant information
- Customer context can be provided for more personalized responses
- The system combines knowledge retrieval with AI-generated responses
Next Steps
- Add user authentication for admin access
- Implement knowledge base categories and tags
- Add analytics to track common customer questions
- Include conversation history for multi-turn interactions
- Integrate with existing CRM systems