Implementation of AI Upsell Recommendation System for Sales
AI upsell recommends a more expensive or extended version of what the customer is already viewing. Difference from cross-sell: we offer the same product class higher, not a complement. ML model determines the moment of offer, personalizes argumentation, and chooses the correct price step.
Upsell Model with Contextual Bandit
import numpy as np
import pandas as pd
from anthropic import Anthropic
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.calibration import CalibratedClassifierCV
class UpsellRecommender:
def __init__(self):
self.llm = Anthropic()
self.model = None
self.product_catalog = {}
def train(self, sessions_df: pd.DataFrame):
"""
sessions_df: user_id, viewed_item_id, upsell_shown_item_id,
accepted, user_features..., item_features...
"""
features = self._extract_features(sessions_df)
X = features.drop(columns=['accepted'])
y = features['accepted']
base_model = GradientBoostingClassifier(
n_estimators=200, learning_rate=0.05,
max_depth=5, random_state=42
)
# Calibrate probabilities for correct threshold
self.model = CalibratedClassifierCV(base_model, cv=3, method='isotonic')
self.model.fit(X, y)
def _extract_features(self, df: pd.DataFrame) -> pd.DataFrame:
"""Feature engineering for upsell model"""
features = pd.DataFrame()
# Price gap between current and upsell item
features['price_delta'] = df['upsell_price'] - df['current_price']
features['price_ratio'] = df['upsell_price'] / df['current_price'].clip(0.01)
# User features
features['user_avg_order'] = df['user_avg_order_value']
features['user_premium_ratio'] = df['user_premium_purchases'] / df['user_total_purchases'].clip(1)
features['session_depth'] = df['pages_viewed']
features['cart_value'] = df['current_cart_value']
# Product features
features['upsell_rating_delta'] = df['upsell_rating'] - df['current_rating']
features['current_category_encoded'] = df['category'].astype('category').cat.codes
features['accepted'] = df['accepted']
return features
def recommend_upsell(self, user: dict, current_item: str) -> dict:
"""Upsell recommendation with explanation"""
candidates = self._get_upsell_candidates(current_item)
if not candidates:
return None
# Score candidates
best_candidate = None
best_prob = 0
for candidate in candidates:
features = self._build_prediction_features(user, current_item, candidate)
if self.model:
prob = self.model.predict_proba([features])[0][1]
else:
prob = 0.3 if candidate['price'] < user.get('avg_order', 0) * 1.5 else 0.15
if prob > best_prob and prob > 0.2: # Show only with sufficient probability
best_prob = prob
best_candidate = (candidate, prob)
if not best_candidate:
return None
candidate, prob = best_candidate
# AI generates personalized offer
pitch = self._generate_upsell_pitch(user, current_item, candidate)
return {
'recommended_item': candidate['item_id'],
'accept_probability': prob,
'pitch': pitch,
'price_delta': candidate['price'] - self.product_catalog.get(current_item, {}).get('price', 0)
}
def _get_upsell_candidates(self, item_id: str) -> list[dict]:
"""Products in same category but more expensive"""
current = self.product_catalog.get(item_id, {})
current_price = current.get('price', 0)
current_category = current.get('category', '')
return [
item for item in self.product_catalog.values()
if item.get('category') == current_category
and current_price * 1.1 <= item.get('price', 0) <= current_price * 2.5
and item.get('rating', 0) >= current.get('rating', 0) - 0.2
]
def _generate_upsell_pitch(self, user: dict, current_item: str,
upsell_item: dict) -> str:
current = self.product_catalog.get(current_item, {})
response = self.llm.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=100,
messages=[{
"role": "user",
"content": f"""Write a short, compelling upsell message (1-2 sentences, conversational tone).
Customer is viewing: {current.get('name', current_item)} (${current.get('price', 0)})
Upsell option: {upsell_item.get('name', '')} (${upsell_item.get('price', 0)})
Key difference: {upsell_item.get('upgrade_feature', 'better quality')}
Customer history: avg order ${user.get('avg_order', 0):.0f}
Be direct, mention the specific benefit, not generic praise."""
}]
)
return response.content[0].text
Typical upsell acceptance rate with properly configured model: 8–15% (without ML: 3–5%). Key insight: optimal price step for upsell — 20–40% higher than current price. Above 50% — conversion drops by half.







