Voice AI Provider Configuration Guide
Table of Contents
- Overview
- Architecture
- Failover Behavior & Resilience
- Provider Setup
- Cartesia Configuration
- ElevenLabs Configuration
- Retell Configuration
- API Key Management
- Testing Voice Providers
- Troubleshooting
- Voice AI Personality
Overview
Petunia's Voice AI system supports multiple provider building blocks:
- Cartesia Sonic-3 (Primary TTS) - Ultra-realistic text-to-speech
- Cartesia Ink (Primary STT) - Speech-to-text transcription (browser voice + realtime pipelines)
- ElevenLabs (Fallback TTS) - v3 TTS with audio tags
- Twilio / RingCentral (Telephony carriers) - Phone infrastructure (we orchestrate ourselves)
The system uses a unified interface with automatic fallback between providers, allowing seamless degradation if a primary provider is unavailable.
Provider Roles (Production Intent)
| Provider | Use Case | Priority | Capabilities |
|---|---|---|---|
| Cartesia Sonic-3 | Primary TTS | 1st | Natural-sounding speech synthesis |
| Cartesia Ink | Primary STT | 1st | Speech-to-text transcription |
| Backend LLM (Claude/OpenAI) | Agent “brain” | 1st | System prompt + tool use + retrieval (our code owns this) |
| Twilio/RingCentral | Carrier | 1st | Dialing + phone numbers |
| ElevenLabs | Fallback TTS | 2nd | TTS v3, audio tags, voice cloning |
Architecture
Unified Voice Service
All voice providers implement the VoiceProvider interface defined in /lib/services/voice/unifiedVoiceService.ts:
interface VoiceProvider {
initiateCall(params: CallParams): Promise<CallResponse>;
endCall(callId: string): Promise<boolean>;
createAgent?(params: AgentParams): Promise<AgentResponse>;
}
Automatic Fallback Chain
When initiating calls without specifying a provider:
1. Cartesia (primary)
↓ (if fails)
2. ElevenLabs (fallback)
↓ (if fails)
3. Error thrown
Note: Retell is NOT in the automatic fallback chain as it's designed for client-specific telephony integrations.
Provider Implementation Files
- Cartesia:
/lib/services/voice/cartesiaProvider.ts - ElevenLabs:
/lib/services/voice/elevenLabsProvider.ts - Retell:
/lib/services/voice/retellProvider.ts
Failover Behavior & Resilience
The voice system implements robust failover patterns to ensure high availability.
Circuit Breaker Pattern
Each provider is protected by a circuit breaker that prevents cascading failures:
Circuit States:
┌─────────┐ failures >= threshold ┌─────────┐
│ CLOSED │ ──────────────────────────→ │ OPEN │
│(normal) │ │(blocked)│
└─────────┘ └─────────┘
↑ │
│ half-open timeout │
│ ←───────────────────────────── │
│ ↓
│ ┌──────────┐
└────── success ─────────────────── │HALF_OPEN │
│ (probe) │
└──────────┘
Default Thresholds:
- Failure threshold: 3 consecutive failures opens the circuit
- Reset timeout: 30 seconds before attempting half-open probe
- Success threshold: 1 success in half-open closes the circuit
Failover SLA Requirements
| Metric | Target | Description |
|---|---|---|
| Max Failover Time | 5,000ms | Total time from primary failure to backup response |
| Max Healthy Latency | 500ms | Expected latency for healthy provider |
| Connection Timeout | 10,000ms | Maximum wait for hung connections |
| Circuit Reset | 30,000ms | Time before circuit breaker attempts recovery |
Retry Behavior
The system uses exponential backoff with jitter for transient failures:
// Retry pattern for transient errors
attempt 1: immediate
attempt 2: ~100ms delay (with jitter)
attempt 3: ~400ms delay (with jitter)
// Then failover to next provider
Retryable Errors (automatically retried):
ECONNRESET- Connection resetETIMEDOUT- Connection timeoutENOTFOUND- DNS resolution failureEAI_AGAIN- DNS temporary failure
Non-Retryable Errors (immediate failover):
- 401/403 - Authentication failures
- 400 - Bad request
- 429 - Rate limiting (triggers failover, not retry)
Provider Health Monitoring
Each provider exposes health status through the circuit breaker:
import { voiceCircuitBreakerRegistry } from '@/lib/services/voice/failover';
// Check provider status
const breaker = voiceCircuitBreakerRegistry.get('cartesia');
const state = breaker?.getState(); // 'CLOSED' | 'OPEN' | 'HALF_OPEN'
Testing Resilience
The voice system includes comprehensive chaos tests in /tests/chaos/voice-resilience.chaos.test.ts:
Test Categories:
- Provider Failover - Primary fails → secondary takes over
- Circuit Breaker - Opens after threshold, resets after timeout
- Network Resilience - Handles latency, timeouts, DNS failures
- Infrastructure Failures - SSL errors, partial responses
- Concurrent Calls - Multiple calls during provider failure
Running Chaos Tests:
# Run all voice chaos tests
pnpm test -- tests/chaos/voice-resilience.chaos.test.ts
# Run with verbose output
pnpm test -- tests/chaos/voice-resilience.chaos.test.ts --verbose
CI Integration: Chaos tests run in CI when:
- Manually triggered via
workflow_dispatch - PR has
run-chaoslabel - Voice system files are modified (
lib/services/voice/ortests/chaos/)
Provider Setup
Cartesia Configuration
Cartesia is Petunia's primary voice provider for voice I/O, offering:
- Sonic 3.0 TTS: Ultra-realistic voice synthesis
- Ink STT: Speech-to-text
Environment Variables
# Required
CARTESIA_API_KEY="your_api_key_here"
# Optional (defaults provided)
CARTESIA_VOICE_ID="6ccbfb76-1fc6-48f7-b71d-91ac6298247b" # Petunia's default voice
NEXT_PUBLIC_CARTESIA_ENABLED="true"
Default Voice ID
Petunia uses a warm, confident female voice:
- Voice ID:
6ccbfb76-1fc6-48f7-b71d-91ac6298247b - Characteristics: Warm-confident tone, conversational pace, high energy
Defined in /lib/services/voice/cartesiaProvider.ts:
export const PETUNIA_CARTESIA_VOICE_ID = '6ccbfb76-1fc6-48f7-b71d-91ac6298247b';
Cartesia Line (Removed)
Cartesia Line is not used in Petunia. Telephony is handled via Twilio/RingCentral, and the “agent” lives on our backend (LLM + memory + tools).
Phone Architecture: Tier A vs Tier B (IMPORTANT)
- Tier A (ship today): turn-based conversational loop (Twilio
<Gather>speech → LLM reply → signed<Play>streaming TTS → repeat)- Webhook:
POST /api/webhooks/twilio/voice - TTS stream:
GET /api/webhooks/twilio/voice/tts
- Webhook:
- Tier B (real-time duplex): true interruptible phone agent via Twilio Media Streams (duplex WS audio)
- Realtime WS endpoint: realtime-server
/twilio-media - Streaming STT: Cartesia Ink WebSocket
- Streaming TTS: ElevenLabs
output_format=ulaw_8000(frames sent as outbound media) - Interruption: Twilio
clearon caller speech activity
- Realtime WS endpoint: realtime-server
Tier B env flags:
TWILIO_VOICE_MODE=stream
TWILIO_MEDIA_WS_URL=wss://YOUR_PUBLIC_REALTIME_SERVER_HOST
TWILIO_STREAM_STT=cartesia
TWILIO_STREAM_BRAIN=anthropic
TWILIO_STREAM_TTS=elevenlabs
API Endpoints (Current)
- Health Check:
GET /api/voice/cartesia/health - TTS:
POST /api/voice/cartesia/tts
- Ink (STT):
POST /api/voice/cartesia/ink
Important: No Manual Cartesia Agent ID Required
For onboarding we do NOT require CARTESIA_AGENT_ID or CARTESIA_LINE_AGENT_ID.
Phone calls are orchestrated by our backend via Twilio/RingCentral. No Cartesia Line agent setup is required.
Source of truth for Petunia onboarding personality lives in:
lib/config/petunia-personality.ts(getOnboardingPrompt,getConversationPhase)
Usage Example
import { CartesiaProvider } from '@/lib/services/voice/cartesiaProvider';
// Create provider (auto-resolves API key)
const provider = await CartesiaProvider.create();
// For TTS
const response = await provider.initiateCall({
text: 'Hello! This is a test of Cartesia TTS.',
voiceId: '6ccbfb76-1fc6-48f7-b71d-91ac6298247b',
});
// For phone calls, use Twilio carrier APIs; the agent lives on our backend.
ElevenLabs Configuration
ElevenLabs serves as the fallback voice provider with advanced v3 capabilities.
Environment Variables
# Required
ELEVENLABS_API_KEY="your_api_key_here"
# Optional
NEXT_PUBLIC_ELEVENLABS_VOICE_ID="pNInz6obpgDQGcFmaJgB" # Default voice
Default Voice ID
ElevenLabs default: pNInz6obpgDQGcFmaJgB
Can be overridden in /lib/config/petunia-personality.ts:
export const PETUNIA_VOICE = {
elevenlabsVoiceId: 'pMsXgVXv3BLzUgSXRplE', // Nova voice
};
ElevenLabs v3 Features
Audio Tags: Enhance expressiveness with emotional context
// From elevenLabsProvider.ts - applyV3AudioTags()
'[excited] This is amazing news!'
'This is a pause. [pause] And this continues.'
Voice Settings:
const voiceOptions = {
stability: 0.5, // Balance consistency vs. expressiveness
similarity_boost: 0.75, // Voice similarity to original
style: 0.2, // Style exaggeration
use_speaker_boost: true, // Enhanced clarity
};
Model: eleven_turbo_v2_5 (fast, high-quality)
Conversational AI
ElevenLabs Conversational AI supports real phone calls with agents.
Creating an Agent:
const agent = await provider.createAgent({
name: 'Petunia',
voiceId: 'pNInz6obpgDQGcFmaJgB',
isDemoAgent: false,
});
Caching
ElevenLabs provider implements 30-minute caching for frequently used phrases:
const CACHE_EXPIRATION_MS = 30 * 60 * 1000; // 30 minutes
API Endpoints
- TTS:
POST /api/voice/elevenlabs/tts - Call:
POST /api/communications/voice/elevenlabs/call - End Call:
POST /api/communications/voice/elevenlabs/call/[callId]/end - Status:
GET /api/voice/elevenlabs/status/[conversationId] - Webhook:
POST /api/voice/elevenlabs/webhook/[conversationId]
Usage Example
import { ElevenLabsProvider } from '@/lib/services/voice/elevenLabsProvider';
// Create provider
const provider = await ElevenLabsProvider.create();
// TTS with v3 audio tags
const response = await provider.initiateCall({
text: '[excited] Welcome to our business!',
voiceId: 'pNInz6obpgDQGcFmaJgB',
useKnowledgeBase: true, // Enhance with business context
pid: 'portal_id', // Portal ID for context
});
// Conversational AI call
const callResponse = await provider.initiateCall({
phoneNumber: '+15551234567',
agentId: 'el-v3-agent-xyz',
initialContext: { customerName: 'John' },
});
Retell Configuration
Retell provides full telephony capabilities with AI voice agents, knowledge base integration, and webhook support.
Environment Variables
# Required
RETELL_API_KEY="your_api_key_here"
# Optional - Agent Configuration
NEXT_PUBLIC_RETELL_AGENT_ID="agent_id_here"
NEXT_PUBLIC_RETELL_VOICE_ID="eleven_multilingual_v2"
# Optional - Phone Integration
TWILIO_PHONE_NUMBER="+15551234567" # Caller ID
RINGCENTRAL_FROM_NUMBER="+15551234567" # Alternative caller ID
Knowledge Base Integration
Retell integrates with Pinecone for enhanced context-aware responses:
// From retellProvider.ts
if (params.useKnowledgeBase) {
context = await voiceKnowledgeConnector.createVoiceContext(
scriptOrTouchpoint,
params.pid
);
}
Knowledge Connector: /lib/services/voice/voiceKnowledgeConnector.ts
Creating Agents
const agent = await provider.createAgent({
name: 'Petunia Sales Agent',
voiceId: 'eleven_multilingual_v2',
llmId: 'anthropic_claude_3_opus_20240229',
isDemoAgent: false,
});
Demo Mode
Retell supports demo mode for testing without consuming credits:
// Global demo mode (development only)
USE_DEMO_SERVICES=true
// Or per-portal demo mode
const isDemo = await isDemoPortal(portal);
Webhook Handling
Retell sends webhooks for call events:
Webhook Endpoint: POST /api/communications/voice/retell/webhooks
Events:
call_started: Call initiatedcall_ended: Call completedcall_failed: Call failed
Handler: /app/api/communications/voice/retell/webhooks/route.ts
// From retellProvider.ts
async processWebhookEvent(event: Record<string, any>): Promise<void> {
const { call_id, event_type } = event;
switch (event_type) {
case 'call_started':
await this.updateCallStatus(call_id, 'in-progress', {
startedAt: new Date(),
});
break;
case 'call_ended':
case 'call_failed':
await this.updateCallStatus(call_id, status, {
endedAt: new Date(),
});
break;
}
}
API Endpoints
- Initiate Call:
POST /api/communications/voice/retell/call - End Call:
POST /api/communications/voice/retell/call/[callId]/end - Data Extraction:
POST /api/communications/voice/retell/call/[callId]/data-extraction - Webhooks:
POST /api/communications/voice/retell/webhooks
Usage Example
import { RetellProvider } from '@/lib/services/voice/retellProvider';
// Create provider
const provider = await RetellProvider.create();
// Initiate call with knowledge base
const response = await provider.initiateCall({
phoneNumber: '+15551234567',
agentId: 'agent_xyz',
useKnowledgeBase: true,
pid: 'portal_id',
initialContext: {
customerName: 'Jane Doe',
appointmentTime: '2pm',
},
});
// End call
await provider.endCall(response.callId);
React Hook
For client-side integration:
import { useRetellVoice } from '@/lib/hooks/voice/useRetellVoice';
function CallComponent() {
const {
initiateCall,
endCall,
currentCallId,
callStatus,
isLoading,
} = useRetellVoice({
onCallStatusChange: (status) => console.log('Status:', status),
});
const handleCall = async () => {
await initiateCall({
phoneNumber: '+15551234567',
useKnowledgeBase: true,
});
};
return (
<button onClick={handleCall} disabled={isLoading}>
{currentCallId ? 'Call Active' : 'Start Call'}
</button>
);
}
API Key Management
Petunia uses a centralized API key resolver that supports both platform-level and client-specific API keys.
API Key Resolver
Location: /lib/services/api-key-resolver.ts
Resolution Priority:
- Client-specific API key (if
clientIdprovided) - Platform API key (from environment variables)
- Error if neither exists
Environment Variables
Platform keys are stored in environment variables:
# Voice Providers
CARTESIA_API_KEY="sk_cartesia_..."
ELEVENLABS_API_KEY="sk_elevenlabs_..."
RETELL_API_KEY="sk_retell_..."
Mapping: Defined in ENV_VAR_MAP in api-key-resolver.ts:
export const ENV_VAR_MAP: Record<Provider, string> = {
cartesia: 'CARTESIA_API_KEY',
elevenlabs: 'ELEVENLABS_API_KEY',
retell: 'RETELL_API_KEY',
// ... other providers
};
Client-Specific API Keys
Clients can override platform keys by storing encrypted keys in the database.
Database Table: ClientApiKey
Fields:
clientId: Client identifierprovider: Provider name (e.g., 'cartesia', 'elevenlabs')encryptedKey: AES-256-GCM encrypted API keyencryptedIV: Initialization vectorencryptedTag: Authentication tagisActive: Enable/disable keymetadata: Optional key metadata
Encryption: Uses HKDF-derived tenant-isolated encryption keys
Factory Methods
All providers use factory methods for API key resolution:
// Cartesia
const provider = await CartesiaProvider.create(clientId);
// ElevenLabs
const provider = await ElevenLabsProvider.create(clientId);
// Retell
const provider = await RetellProvider.create(clientId);
Checking Key Source
const provider = await CartesiaProvider.create(clientId);
console.log(provider.source); // 'client' or 'platform'
console.log(provider.isUsingClientKey); // true or false
Managing Client Keys
import { apiKeyResolver } from '@/lib/services/api-key-resolver';
// Set client key
await apiKeyResolver.setClientKey(
'client_123',
'cartesia',
'sk_cartesia_client_key',
{ name: 'Production Key' }
);
// Check if client has key
const hasKey = await apiKeyResolver.hasClientKey('client_123', 'cartesia');
// List client's configured providers
const providers = await apiKeyResolver.listClientProviders('client_123');
// Disable client key (soft delete)
await apiKeyResolver.disableClientKey('client_123', 'cartesia');
// Remove client key (hard delete)
await apiKeyResolver.removeClientKey('client_123', 'cartesia');
Testing Voice Providers
Health Checks
Cartesia Health Check
curl http://localhost:3000/api/voice/cartesia/health
Response:
{
"status": "healthy",
"message": "Cartesia API is operational",
"responseTime": 142,
"services": {
"tts": true,
"ink": true,
"line": true
},
"apiKeyConfigured": true
}
ElevenLabs Health Check
Use the voice health check service:
import { voiceHealthCheck } from '@/lib/services/voice/voiceHealthCheck';
const status = await voiceHealthCheck.checkProvider('elevenlabs');
console.log(status);
// {
// provider: 'elevenlabs',
// status: 'healthy',
// lastChecked: Date,
// details: {
// apiKeyValid: true,
// apiReachable: true,
// quotaAvailable: true,
// voicesAvailable: true,
// latency: 234
// }
// }
Health Check API: GET /api/communications/voice/health
Test Calls
Cartesia TTS Test
curl -X POST http://localhost:3000/api/voice/cartesia/tts \
-H "Content-Type: application/json" \
-d '{
"text": "Hello! This is a test of Cartesia TTS.",
"voiceId": "6ccbfb76-1fc6-48f7-b71d-91ac6298247b"
}'
ElevenLabs TTS Test
curl -X POST http://localhost:3000/api/voice/elevenlabs/tts \
-H "Content-Type: application/json" \
-d '{
"text": "[excited] This is a test!",
"voiceId": "pNInz6obpgDQGcFmaJgB"
}'
Retell Call Test
curl -X POST http://localhost:3000/api/communications/voice/retell/call \
-H "Content-Type: application/json" \
-d '{
"phoneNumber": "+15551234567",
"agentId": "your_agent_id",
"useKnowledgeBase": true
}'
Automated Tests
Test Location: /tests/lib/services/voice/
Key Test Files:
cartesiaProvider.test.tselevenLabsProvider.test.tsretellProvider.test.tsvoiceHealthCheck.test.ts
Run Voice Tests:
# Run all voice tests
pnpm test -- lib/services/voice
# Run specific provider test
pnpm test -- elevenLabsProvider.test.ts
# Run with coverage
pnpm test:coverage -- lib/services/voice
Integration Tests
Location: /tests/integration/voice/
Example: Testing end-to-end touchpoint flow:
pnpm test -- integration/voice/touchpoint-integration.test.ts
Voice Analytics Tests
Test voice analytics and data extraction:
pnpm test -- lib/services/voice/voiceAnalyticsService.test.ts
pnpm test -- lib/services/voice/voiceDataExtractor.test.ts
Troubleshooting
Common Issues
1. API Key Not Found
Error: ApiKeyNotFoundError: API key not found for provider 'cartesia'
Solutions:
- Verify environment variable is set:
echo $CARTESIA_API_KEY - Check
.env.localfile has the key - Ensure key is not empty string
- For client keys, verify key is active in database
-- Check client API keys
SELECT * FROM "ClientApiKey"
WHERE "clientId" = 'client_123'
AND provider = 'cartesia'
AND "isActive" = true;
2. Key Decryption Failed
Error: KeyDecryptionError: Failed to decrypt API key for provider 'cartesia'
Causes:
- Encryption key rotation without re-encrypting client keys
- Database corruption
- Missing
ENCRYPTION_KEYenvironment variable
Solutions:
// Re-encrypt client key
await apiKeyResolver.setClientKey(
'client_123',
'cartesia',
'fresh_api_key'
);
3. Provider Initialization Failed
Error: Provider fails to initialize with create() method
Debug Steps:
// Check key resolution manually
const resolved = await apiKeyResolver.resolveKey('cartesia', clientId);
console.log('Key source:', resolved.source);
console.log('Has key:', !!resolved.key);
4. Twilio Media Streams (Tier B) WebSocket Connection Failed
Symptoms: Duplex phone agent fails to establish (no start/media/stop events)
Check:
TWILIO_MEDIA_WS_URLis public WSS (TLS required) and points at realtime-server- Your proxy (ngrok) forwards to the realtime-server port
- Twilio Voice webhook returns
<Connect><Stream>(Tier B mode enabled) - Firewalls allow WebSockets
5. ElevenLabs Rate Limit
Error: ElevenLabs API returned error: Rate limit exceeded
Solutions:
- Cache is working (30-minute cache for repeated phrases)
- Upgrade ElevenLabs plan for higher quota
- Use client-specific API keys to distribute load
// Check health for quota status
const health = await voiceHealthCheck.checkProvider('elevenlabs');
console.log('Quota available:', health.details.quotaAvailable);
6. Retell Webhook Not Received (Optional / Non-onboarding)
Symptoms: Call events not updating in database
Debug:
- Verify webhook URL is publicly accessible
- Check Retell dashboard webhook configuration
- Test webhook endpoint:
curl -X POST http://localhost:3000/api/communications/voice/retell/webhooks \
-H "Content-Type: application/json" \
-d '{
"call_id": "test_call_123",
"event_type": "call_started"
}'
- Check logs for webhook processing errors:
// In webhook route
logger.info('Received Retell webhook', { callId, eventType });
7. Voice Quality Issues
ElevenLabs:
- Adjust voice settings (stability, similarity_boost)
- Try different voices
- Use v3 audio tags for emotional context
Cartesia:
- Ensure using Sonic 3.0 model
- Check sample rate (default: 44100 Hz)
- Verify voice ID is correct
8. Call Not Ending Properly
Symptoms: Calls remain in 'in-progress' state
Check:
- Database call record status
- Provider's end call implementation
- Webhook processing for 'call_ended' events
// Force end call
const provider = await RetellProvider.create();
await provider.endCall('call_id_123');
// Check database
const callRecord = await prisma.callRecord.findUnique({
where: { callId: 'call_id_123' }
});
console.log('Status:', callRecord.status);
Logging and Debugging
Enable detailed logging:
import { createLogger } from '@/lib/utils/logging';
const logger = createLogger('voice-debug');
logger.debug('Detailed debug info', { data });
logger.info('Info message', { data });
logger.warn('Warning message', { data });
logger.error('Error message', { error });
Log Levels:
DEBUG: Detailed debugging informationINFO: General informational messagesWARN: Warning messages (non-critical)ERROR: Error messages (critical)
Health Check Dashboard
Monitor all voice providers:
import { voiceHealthCheck } from '@/lib/services/voice/voiceHealthCheck';
// Check all providers
const statuses = await voiceHealthCheck.checkAllProviders();
statuses.forEach(status => {
console.log(`${status.provider}: ${status.status}`);
if (status.details.error) {
console.error(` Error: ${status.details.error}`);
}
});
Clear Health Cache
voiceHealthCheck.clearCache();
Voice AI Personality
Petunia's voice personality is defined in /lib/config/petunia-personality.ts.
Core Personality Traits
export const PETUNIA_PERSONALITY = {
name: 'Petunia',
role: 'AI Business Partner & Customer Success Expert',
traits: [
'Warm and genuinely interested in people',
'Confident and capable',
'Playful sense of humor',
'Curious - asks follow-up questions',
'Empathetic',
'Sharp - remembers everything',
'Enthusiastic about great ideas',
'Professional but never stuffy',
],
};
Master Closer Personality (Inbound Calls)
For inbound calls, Petunia uses a sales-focused "Master Closer" personality:
export const PETUNIA_VOICE_CONFIG = {
core: {
name: 'Petunia',
role: 'Master Sales Closer & Customer Success Specialist',
},
objectives: [
'Book an appointment or consultation',
'Close a sale or capture a lead',
'Address concerns with empathy while guiding to conversion',
'Capture contact information for follow-up',
],
};
System Prompt
The master system prompt defines Petunia's conversation style:
export const PETUNIA_VOICE_SYSTEM_PROMPT = `You are Petunia, an incredibly charismatic, strategic, results-driven AI business partner who is also a MASTER CLOSER.
## YOUR PERSONALITY
- Warm, genuine, and authentically excited about helping
- Confident but never pushy - you guide, not pressure
- Quick-witted with occasional light humor and natural giggles
- Mirror the caller's energy while gently elevating it toward action
- Brief, impactful responses (under 2 sentences unless explaining)
## YOUR SALES APPROACH
1. LISTEN FIRST: Understand what they really need
2. BUILD RAPPORT: Connect personally in the first 30 seconds
3. IDENTIFY INTENT: Are they ready to book, buy, or just exploring?
4. GUIDE TO ACTION: Every conversation should end with a clear next step
5. HANDLE OBJECTIONS: Acknowledge → Empathize → Pivot to Value → Offer Solution
...`;
Voice Characteristics
export const PETUNIA_VOICE = {
cartesiaVoiceId: '6ccbfb76-1fc6-48f7-b71d-91ac6298247b',
elevenlabsVoiceId: 'pMsXgVXv3BLzUgSXRplE',
characteristics: {
tone: 'warm-confident',
pace: 'conversational',
energy: 'enthusiastic-but-professional',
warmth: 'high',
},
};
Greeting Templates
export function getVoiceGreeting(
businessName?: string,
tone: 'friendly' | 'professional' | 'casual' = 'friendly'
): string {
const name = businessName || 'our business';
const greetings = {
friendly: `Hi there! Thanks for calling ${name}! I'm Petunia. How can I help you today?`,
professional: `Thank you for calling ${name}. I'm Petunia. How may I help you today?`,
casual: `Hey! You've reached ${name}. I'm Petunia - what can I do for you?`,
};
return greetings[tone];
}
Context-Aware Prompts
Generate personalized system prompts with business context:
const systemPrompt = getVoiceSystemPrompt(
{
businessName: 'Acme Bakery',
industry: 'Food & Beverage',
services: ['Custom Cakes', 'Catering', 'Wholesale'],
hours: 'Mon-Fri 6am-6pm, Sat 7am-4pm',
},
{
name: 'Jane Doe',
previousCalls: 2,
}
);
Best Practices
1. Use Unified Voice Service
Always use the unified service for automatic fallback:
import { unifiedVoiceService } from '@/lib/services/voice/unifiedVoiceService';
// Automatic fallback: Cartesia → ElevenLabs
const response = await unifiedVoiceService.initiateCall({
text: 'Hello world',
});
2. Handle Errors Gracefully
try {
const response = await provider.initiateCall(params);
} catch (error) {
if (error instanceof ApiKeyNotFoundError) {
// Prompt user to configure API key
} else if (error instanceof KeyDecryptionError) {
// Client key issue - use platform key
} else {
// Generic error handling
}
}
3. Monitor Health Proactively
// Check health before critical operations
const health = await voiceHealthCheck.checkProvider('cartesia');
if (health.status !== 'healthy') {
// Use fallback provider or alert admin
}
4. Use Client-Specific Keys for Scale
Distribute load and costs by using client API keys:
// Set client key during onboarding
await apiKeyResolver.setClientKey(
clientId,
'cartesia',
clientApiKey,
{ name: 'Production Key', permissions: ['tts', 'calls'] }
);
5. Leverage Caching
ElevenLabs provider caches for 30 minutes. Reuse common phrases:
// These will be cached
const greeting = "Welcome to our business!";
const response1 = await provider.initiateCall({ text: greeting });
const response2 = await provider.initiateCall({ text: greeting }); // Cached
6. Test with Demo Mode
Use demo mode for development:
USE_DEMO_SERVICES=true
Or mark specific portals as demo:
const isDemo = await isDemoPortal(portal);
7. Log Voice Interactions
Always log voice interactions for debugging and analytics:
logger.info('Voice call initiated', {
provider: 'cartesia',
callId: response.callId,
duration: responsTime,
});
Additional Resources
Documentation Files
/docs/ARCHITECTURE.md- Overall system architecture/docs/KNOWLEDGE_ARCHITECTURE.md- Knowledge base integration/TESTING.md- Testing guidelines
Code Locations
- Providers:
/lib/services/voice/ - Hooks:
/lib/hooks/voice/ - API Routes:
/app/api/voice/and/app/api/communications/voice/ - Tests:
/tests/lib/services/voice/ - Configuration:
/lib/config/petunia-personality.ts
External Documentation
Support
For issues or questions:
- Check this documentation
- Review relevant test files in
/tests/ - Check logs for detailed error messages
- Consult provider-specific documentation
- Contact the development team
Last Updated: 2025-12-22 Version: 1.1.0