UNPKG

contextual-agent-sdk

Version:

SDK for building AI agents with seamless voice-text context switching

266 lines (219 loc) 7.39 kB
// Example: Using Customer Phone Number as Session ID with Twilio // This maintains context across multiple calls and texts from the same number const express = require('express'); const { ContextualAgent } = require('contextual-agent-sdk'); const twilio = require('twilio'); const app = express(); app.use(express.urlencoded({ extended: true })); // Initialize your Contextual Agent const agent = new ContextualAgent({ name: 'Customer Service Agent', mode: 'conversation', systemPrompt: 'You are a helpful customer service representative. Remember previous conversations with customers.', capabilities: { voiceEnabled: true, textEnabled: true, contextBridging: true, memoryRetention: true, emotionRecognition: true, taskExecution: true }, contextSettings: { maxHistoryLength: 50, // Remember more for phone sessions contextWindowSize: 8000, relevanceThreshold: 0.7, memoryRetentionDays: 30, // Keep customer context for 30 days modalitySwitchSensitivity: 0.8 }, // Persistent storage for phone sessions storage: { type: 'redis', // or 'database' for production config: { host: process.env.REDIS_HOST || 'localhost', port: process.env.REDIS_PORT || 6379 } } }); /** * Normalize phone number to consistent format for session ID */ function normalizePhoneNumber(phoneNumber) { // Remove all non-digits const digits = phoneNumber.replace(/\D/g, ''); // Ensure it starts with country code if (digits.length === 10) { return `+1${digits}`; // US/Canada } else if (digits.length === 11 && digits.startsWith('1')) { return `+${digits}`; } return `+${digits}`; } /** * VOICE CALLS: Handle incoming voice calls */ app.post('/voice', async (req, res) => { const customerPhone = req.body.From; // e.g., "+15551234567" const sessionId = normalizePhoneNumber(customerPhone); console.log(`📞 Incoming call from ${customerPhone} (session: ${sessionId})`); // Check if returning customer const existingSession = await agent.getSession(sessionId); const twiml = new twilio.twiml.VoiceResponse(); if (existingSession) { // Returning customer - acknowledge previous conversation twiml.say({ voice: 'Polly.Joanna' }, `Welcome back! I remember our previous conversation. How can I help you today?`); } else { // New customer twiml.say({ voice: 'Polly.Joanna' }, `Hello! I'm your AI assistant. How can I help you today?`); } // Start conversation twiml.gather({ input: 'speech', action: '/voice/process', method: 'POST', speechTimeout: 3, enhanced: true }); res.type('text/xml'); res.send(twiml.toString()); }); /** * VOICE PROCESSING: Handle speech input */ app.post('/voice/process', async (req, res) => { const customerPhone = req.body.From; const speechResult = req.body.SpeechResult || ''; const sessionId = normalizePhoneNumber(customerPhone); console.log(`🗣️ Customer said: "${speechResult}"`); try { // Process with context bridging const response = await agent.processMessage( speechResult, 'voice', // Target modality sessionId, customerPhone // User ID ); const twiml = new twilio.twiml.VoiceResponse(); // Respond to customer twiml.say({ voice: 'Polly.Joanna' }, response.data.message.content); // Continue conversation or end if (response.data.message.content.toLowerCase().includes('goodbye') || response.data.message.content.toLowerCase().includes('that\'s all')) { twiml.say({ voice: 'Polly.Joanna' }, 'Thank you for calling! Have a great day!'); twiml.hangup(); } else { // Keep listening twiml.gather({ input: 'speech', action: '/voice/process', method: 'POST', speechTimeout: 3, enhanced: true }); twiml.say({ voice: 'Polly.Joanna' }, 'Is there anything else I can help you with?'); } res.type('text/xml'); res.send(twiml.toString()); } catch (error) { console.error('Error processing voice:', error); const twiml = new twilio.twiml.VoiceResponse(); twiml.say({ voice: 'Polly.Joanna' }, 'I apologize, I had trouble understanding. Could you please repeat that?'); twiml.gather({ input: 'speech', action: '/voice/process', method: 'POST', speechTimeout: 3, enhanced: true }); res.type('text/xml'); res.send(twiml.toString()); } }); /** * SMS: Handle incoming text messages */ app.post('/sms', async (req, res) => { const customerPhone = req.body.From; const messageBody = req.body.Body || ''; const sessionId = normalizePhoneNumber(customerPhone); console.log(`💬 SMS from ${customerPhone}: "${messageBody}"`); try { // Process with context bridging (voice ↔ text seamlessly) const response = await agent.processMessage( messageBody, 'text', // Target modality sessionId, customerPhone ); const twiml = new twilio.twiml.MessagingResponse(); twiml.message(response.data.message.content); res.type('text/xml'); res.send(twiml.toString()); } catch (error) { console.error('Error processing SMS:', error); const twiml = new twilio.twiml.MessagingResponse(); twiml.message('I apologize, I had trouble processing your message. Could you please try again?'); res.type('text/xml'); res.send(twiml.toString()); } }); /** * STATUS WEBHOOK: Track call/message status */ app.post('/status', (req, res) => { const customerPhone = req.body.From; const status = req.body.CallStatus || req.body.MessageStatus; console.log(`📊 Status update from ${customerPhone}: ${status}`); // You could track metrics, update session metadata, etc. res.sendStatus(200); }); /** * ADMIN: Get session info for debugging */ app.get('/admin/session/:phone', async (req, res) => { const sessionId = normalizePhoneNumber(req.params.phone); try { const session = await agent.getSession(sessionId); if (!session) { return res.status(404).json({ error: 'Session not found' }); } res.json({ sessionId, totalMessages: session.totalMessages, lastActivity: session.lastActivity, currentModality: session.currentModality, conversationSummary: session.context.summary, recentMessages: session.context.conversationHistory.slice(-5) }); } catch (error) { res.status(500).json({ error: error.message }); } }); // Event listeners for monitoring agent.on('session_started', (event) => { console.log(`🆕 New session started: ${event.sessionId}`); }); agent.on('modality_switched', (event) => { console.log(`🔄 Modality switch in ${event.sessionId}: ${event.data.from}${event.data.to}`); }); agent.on('context_bridged', (event) => { console.log(`🌉 Context bridged in ${event.sessionId}: ${event.data.bridgeType}`); }); const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`🚀 Contextual Agent Twilio server running on port ${PORT}`); console.log(`📞 Voice webhook: http://localhost:${PORT}/voice`); console.log(`💬 SMS webhook: http://localhost:${PORT}/sms`); }); module.exports = app;