Voice Agents That Suggest What's Next: Proactive Recommendations
Table of Contents
Good voice agents answer questions.
Great voice agents suggest what you didn’t know to ask.
“You’re flying into Denver in winter. Would you like me to add checked bag for ski equipment?”
That’s not pushy. That’s helpful. Because the agent knows:
- You’re flying
- Destination is Denver
- Season is winter
- Ski equipment needs checked bags
Context + timing = good suggestions.
This is proactive recommendation in voice agents, and it’s the difference between reactive and anticipatory UX.
The Problem With Reactive-Only Systems
Most voice agents wait for explicit requests:
User: "Book a flight to Denver"
Agent: "Flight booked"
[End]
But users don’t know what they don’t know:
- Checked bag needed for ski equipment?
- Rental car for mountain access?
- Hotel near ski resorts?
Reactive systems make users think of everything. Proactive systems help.
Context-Aware Suggestions
Good suggestions require context:
graph TD
A[User request] --> B[Extract context]
B --> C{Analyze situation}
C --> D[Historical data]
C --> E[Current conditions]
C --> F[User preferences]
D --> G[Generate suggestions]
E --> G
F --> G
G --> H{Relevance check}
H -->|Relevant| I[Present suggestion]
H -->|Not relevant| J[Skip]
I --> K[User responds]
K -->|Accepts| L[Apply suggestion]
K -->|Declines| M[Remember preference]
Context layers:
- Transaction context: What user is doing now
- Historical context: What they’ve done before
- Environmental context: Time, location, season
- Preference context: Known likes/dislikes
Real-World Example: Travel Booking
User books flight to Denver in February:
class TravelSuggestionEngine {
constructor() {
this.suggestionRules = [
{
trigger: 'winter_mountain_destination',
conditions: {
destination: ['Denver', 'Salt Lake City', 'Bozeman'],
months: ['Nov', 'Dec', 'Jan', 'Feb', 'Mar'],
no_checked_bag: true
},
suggestion: {
text: "You're flying into {destination} in winter. Would you like me to add a checked bag for ski equipment?",
value: 'add_checked_bag',
priority: 'high'
}
},
{
trigger: 'mountain_resort_access',
conditions: {
destination: ['Denver', 'Salt Lake City'],
no_rental_car: true,
trip_duration: '>2_days'
},
suggestion: {
text: "Most ski resorts are 1-2 hours from {destination}. Would you like to add a rental car?",
value: 'add_rental_car',
priority: 'medium'
}
}
];
}
generateSuggestions(bookingContext) {
return this.suggestionRules
.filter(rule => this.matchesConditions(rule.conditions, bookingContext))
.map(rule => this.formatSuggestion(rule.suggestion, bookingContext))
.sort((a, b) => this.priorityScore(b.priority) - this.priorityScore(a.priority));
}
matchesConditions(conditions, context) {
if (conditions.destination && !conditions.destination.includes(context.destination)) {
return false;
}
if (conditions.months && !conditions.months.includes(context.month)) {
return false;
}
if (conditions.no_checked_bag && context.has_checked_bag) {
return false;
}
return true;
}
priorityScore(priority) {
return { 'high': 3, 'medium': 2, 'low': 1 }[priority] || 0;
}
}
// Usage
const engine = new TravelSuggestionEngine();
const booking = {
destination: 'Denver',
month: 'Feb',
has_checked_bag: false,
has_rental_car: false,
trip_duration: 4
};
const suggestions = engine.generateSuggestions(booking);
// Returns:
// [
// { text: "You're flying into Denver in winter. Would you like me to add a checked bag for ski equipment?", priority: 'high' },
// { text: "Most ski resorts are 1-2 hours from Denver. Would you like to add a rental car?", priority: 'medium' }
// ]
Agent presents suggestions naturally:
Agent: "Your flight to Denver is booked for February 15th.
By the way, you're flying into Denver in winter—would you like me to add a checked bag for ski equipment? It's $35.
Also, most ski resorts are about 90 minutes from Denver. Would a rental car help?"
Timing Matters
Good suggestions come at the right moment:
Too early:
User: "I want to book a flight"
Agent: "Would you like hotel and rental car too?"
[User hasn't even said destination yet]
Too late:
User: "Thanks, I'm all set"
Agent: "Oh wait, did you need a hotel?"
[Conversation is over]
Right time:
User: "Book flight to Denver, February 15th"
Agent: "Flight booked. You're set for February 15th.
One thing—you're flying into Denver in winter. Would you like me to add a checked bag for ski equipment?"
[Transaction complete, now suggesting relevant add-on]
Timing rules:
class SuggestionTiming:
def should_suggest_now(self, conversation_state, suggestion):
# Don't suggest during critical flow
if conversation_state['in_payment_flow']:
return False
# Don't suggest if user is rushing
if conversation_state['user_tone'] == 'hurried':
return False
# Don't suggest if already declined similar
if suggestion.type in conversation_state['declined_suggestions']:
return False
# Suggest after primary action complete
if conversation_state['primary_action_complete']:
return True
return False
Personalization From History
Users who always book hotels probably want hotel suggestions:
class PersonalizedSuggestions {
constructor(userHistory) {
this.history = userHistory;
}
shouldSuggestHotel(destination) {
// Check past behavior
const pastBookings = this.history.filter(b => b.type === 'flight');
const hotelBookings = this.history.filter(b => b.type === 'hotel');
const hotelRate = hotelBookings.length / pastBookings.length;
if (hotelRate > 0.7) {
// User almost always books hotels
return {
suggest: true,
confidence: 'high',
message: `Based on your usual travel style, should I look for hotels near ${destination}?`
};
} else if (hotelRate > 0.3) {
// Sometimes books hotels
return {
suggest: true,
confidence: 'medium',
message: `Would you like me to show hotel options in ${destination}?`
};
} else {
// Rarely books hotels
return {
suggest: false,
reason: 'User typically doesn\'t book hotels'
};
}
}
}
Learn from responses:
class SuggestionLearning:
def __init__(self):
self.acceptance_data = []
def log_suggestion_result(self, suggestion_type, context, accepted):
self.acceptance_data.append({
'type': suggestion_type,
'context': context,
'accepted': accepted
})
def should_suggest_type(self, suggestion_type, context):
# Find similar past suggestions
similar = [
d for d in self.acceptance_data
if d['type'] == suggestion_type and self.is_similar_context(d['context'], context)
]
if len(similar) < 3:
# Not enough data, suggest anyway
return True
acceptance_rate = sum(1 for s in similar if s['accepted']) / len(similar)
# Only suggest if >30% acceptance rate
return acceptance_rate > 0.3
Avoiding Pushy Suggestions
Pushy:
"You NEED to add insurance"
"Most people buy the protection plan"
"Are you SURE you don't want to upgrade?"
Helpful:
"Would travel insurance help? It covers cancellations."
"There's a protection plan available if you're interested."
"The upgrade includes priority boarding—let me know if you'd like it."
Key differences:
- Optional language (“would you like” vs “you need”)
- Brief explanation (why it might help)
- Easy decline (no guilt trip)
const formatHelpfulSuggestion = (suggestion) => {
return {
phrasing: `Would ${suggestion.item} help? ${suggestion.brief_benefit}`,
easy_decline: "No problem! Your booking is all set.",
no_repeat: true // Don't ask again this session
};
};
// Example
formatHelpfulSuggestion({
item: 'travel insurance',
brief_benefit: 'It covers cancellations and delays.'
});
// Returns: "Would travel insurance help? It covers cancellations and delays."
Multi-Suggestion Handling
Sometimes multiple suggestions are relevant:
Flight to Denver in winter:
1. Checked bag for ski equipment
2. Rental car for mountain access
3. Hotel near ski resorts
Don’t dump them all:
Agent: "Would you like checked bag, rental car, hotel, airport parking, and travel insurance?"
[Overwhelming]
Present sequentially:
Agent: "Your flight is booked.
One thing—you're flying into Denver in winter. Would you like me to add a checked bag for ski equipment?"
[User responds]
Agent: "Got it. Most ski resorts are about 90 minutes from Denver. Would a rental car help?"
[User responds]
Agent: "Perfect. You're all set!"
Priority order:
class SuggestionPrioritization:
def prioritize_suggestions(self, suggestions, context):
scored = []
for suggestion in suggestions:
score = 0
# High-value suggestions first
if suggestion['value'] > 50:
score += 3
# Time-sensitive suggestions
if suggestion.get('time_sensitive'):
score += 2
# Historically accepted suggestions
if suggestion['historical_acceptance_rate'] > 0.5:
score += 1
scored.append((score, suggestion))
# Sort by score, return suggestions
return [s for _, s in sorted(scored, reverse=True)]
Measuring Suggestion Effectiveness
Track these metrics:
class SuggestionMetrics {
constructor() {
this.suggestions_made = 0;
this.suggestions_accepted = 0;
this.suggestions_ignored = 0;
this.suggestions_declined = 0;
}
logResult(result) {
this.suggestions_made++;
if (result === 'accepted') {
this.suggestions_accepted++;
} else if (result === 'declined') {
this.suggestions_declined++;
} else {
this.suggestions_ignored++;
}
}
getMetrics() {
return {
acceptance_rate: this.suggestions_accepted / this.suggestions_made,
decline_rate: this.suggestions_declined / this.suggestions_made,
ignore_rate: this.suggestions_ignored / this.suggestions_made
};
}
}
// Target metrics:
// Acceptance rate: >25%
// Decline rate: <40% (if higher, suggestions are off)
// Ignore rate: <35% (if higher, timing is off)
Real production data:
- Checked bag suggestion: 42% acceptance, 38% decline, 20% ignore
- Rental car suggestion: 31% acceptance, 45% decline, 24% ignore
- Hotel suggestion: 28% acceptance, 50% decline, 22% ignore
Best performing: Checked bag (contextually obvious need)
Worst performing: Hotel (users often have arrangements)
Implementation Example
Here’s a production-ready suggestion engine:
import { OpenAI } from 'openai';
class ContextAwareSuggestionEngine {
constructor() {
this.client = new OpenAI();
this.suggestionRules = this.loadSuggestionRules();
this.userHistory = new Map();
}
async generateSuggestions(bookingContext, userId) {
// Get user history
const history = this.userHistory.get(userId) || [];
// Match suggestion rules
const candidateSuggestions = this.suggestionRules
.filter(rule => this.matchesConditions(rule.conditions, bookingContext))
.filter(rule => this.shouldSuggestBasedOnHistory(rule, history));
// Prioritize
const prioritized = this.prioritizeSuggestions(candidateSuggestions, bookingContext, history);
// Format for voice
return prioritized.map(s => this.formatForVoice(s, bookingContext));
}
shouldSuggestBasedOnHistory(rule, history) {
const pastResponses = history.filter(h => h.suggestion_type === rule.type);
if (pastResponses.length === 0) {
return true; // No history, suggest
}
const recentDeclines = pastResponses
.slice(-3)
.filter(h => h.response === 'declined');
// Don't suggest if declined 3 times in a row
return recentDeclines.length < 3;
}
formatForVoice(suggestion, context) {
let message = suggestion.template;
// Replace placeholders
Object.keys(context).forEach(key => {
message = message.replace(`{${key}}`, context[key]);
});
return {
message,
type: suggestion.type,
value: suggestion.value,
easy_decline: "No problem!"
};
}
logSuggestionResult(userId, suggestionType, result) {
const history = this.userHistory.get(userId) || [];
history.push({
timestamp: Date.now(),
suggestion_type: suggestionType,
response: result
});
this.userHistory.set(userId, history);
}
}
// Usage
const engine = new ContextAwareSuggestionEngine();
const suggestions = await engine.generateSuggestions({
destination: 'Denver',
month: 'Feb',
has_checked_bag: false
}, 'user_123');
// Returns:
// [
// {
// message: "You're flying into Denver in winter. Would you like me to add a checked bag for ski equipment?",
// type: 'checked_bag',
// value: 35,
// easy_decline: "No problem!"
// }
// ]
Why This Matters
Revenue impact:
- 25% suggestion acceptance rate × $40 average add-on value × 10,000 bookings/month
- = $100,000/month additional revenue
User experience:
- Users appreciate relevant suggestions: 78% positive feedback
- Users dislike irrelevant suggestions: 82% negative feedback
The difference: Context + timing.
Next Steps
- Start with high-value, obvious suggestions: Checked bags for ski destinations
- Track acceptance rates: Learn what works
- Personalize over time: Use historical data
- Never be pushy: Make declining easy
Good suggestions feel helpful, not salesy.
And that’s what proactive voice agents do.
Learn More: