Building a Personal Shopper Agent: Style Profiles, Curated Selections, and Wish Lists
Learn how to build an AI personal shopper agent that creates style profiles, curates product selections based on preferences, manages wish lists, and sends personalized alerts for new arrivals and price drops.
What Makes a Great Personal Shopper Agent
A human personal shopper remembers your preferences, anticipates your needs, and curates selections you would not have found on your own. An AI personal shopper agent replicates this by building a structured style profile, matching products against it, managing a wish list with price tracking, and proactively alerting customers to relevant new arrivals or sales.
Building the Style Profile System
The style profile captures explicit preferences (stated by the customer) and implicit signals (derived from browsing and purchase history).
from agents import Agent, Runner, function_tool
from typing import Optional
import json
# Style profile storage
STYLE_PROFILES = {}
@function_tool
def create_style_profile(customer_id: str, preferred_colors: str,
preferred_styles: str, budget_range: str,
sizes: str, avoid: str = "") -> str:
"""Create or update a customer's style profile."""
profile = {
"colors": [c.strip() for c in preferred_colors.split(",")],
"styles": [s.strip() for s in preferred_styles.split(",")],
"budget": budget_range,
"sizes": {s.split(":")[0].strip(): s.split(":")[1].strip()
for s in sizes.split(",")},
"avoid": [a.strip() for a in avoid.split(",") if a.strip()],
"purchase_history": [],
"wish_list": [],
}
STYLE_PROFILES[customer_id] = profile
return (
f"Style profile created for {customer_id}.\n"
f"Colors: {', '.join(profile['colors'])}\n"
f"Styles: {', '.join(profile['styles'])}\n"
f"Budget: {profile['budget']}\n"
f"Sizes: {profile['sizes']}\n"
f"Avoiding: {', '.join(profile['avoid']) if profile['avoid'] else 'nothing specified'}"
)
@function_tool
def update_style_preferences(customer_id: str,
field: str, value: str) -> str:
"""Update a specific field in the customer's style profile."""
profile = STYLE_PROFILES.get(customer_id)
if not profile:
return "No style profile found. Let us create one first."
if field == "colors":
profile["colors"] = [c.strip() for c in value.split(",")]
elif field == "styles":
profile["styles"] = [s.strip() for s in value.split(",")]
elif field == "budget":
profile["budget"] = value
elif field == "avoid":
profile["avoid"] = [a.strip() for a in value.split(",")]
else:
return f"Unknown field: {field}. Valid: colors, styles, budget, avoid."
return f"Updated {field} to: {value}"
Product Curation Engine
The curation tool scores products against the customer's style profile and returns ranked matches.
See AI Voice Agents Handle Real Calls
Book a free demo or calculate how much you can save with AI voice automation.
PRODUCT_CATALOG = [
{"id": "P-101", "name": "Navy Linen Blazer", "price": 159.99,
"colors": ["navy"], "style": "classic", "category": "tops",
"new_arrival": True},
{"id": "P-102", "name": "Black Slim Jeans", "price": 89.99,
"colors": ["black"], "style": "modern", "category": "bottoms",
"new_arrival": False},
{"id": "P-103", "name": "Olive Chino Shorts", "price": 59.99,
"colors": ["olive", "green"], "style": "casual", "category": "bottoms",
"new_arrival": True},
{"id": "P-104", "name": "White Oxford Shirt", "price": 79.99,
"colors": ["white"], "style": "classic", "category": "tops",
"new_arrival": False},
{"id": "P-105", "name": "Burgundy Wool Sweater", "price": 129.99,
"colors": ["burgundy", "red"], "style": "classic", "category": "tops",
"new_arrival": True},
]
@function_tool
def curate_selections(customer_id: str,
category: str = "all",
occasion: str = "") -> str:
"""Curate product selections based on the customer's style profile."""
profile = STYLE_PROFILES.get(customer_id)
if not profile:
return "No style profile found. Please create one first."
scored_products = []
for product in PRODUCT_CATALOG:
if category != "all" and product["category"] != category:
continue
score = 0
reasons = []
# Color match
color_match = any(c in profile["colors"]
for c in product["colors"])
if color_match:
score += 3
reasons.append("matches your color preferences")
# Style match
if product["style"] in profile["styles"]:
score += 3
reasons.append(f"fits your {product['style']} style")
# Avoid filter
if any(a.lower() in product["name"].lower()
for a in profile["avoid"]):
continue
# New arrival bonus
if product["new_arrival"]:
score += 1
reasons.append("new arrival")
# Budget check
budget_parts = profile["budget"].replace("$", "").split("-")
if len(budget_parts) == 2:
budget_max = float(budget_parts[1])
if product["price"] <= budget_max:
score += 1
if score > 0:
scored_products.append({
"product": product,
"score": score,
"reasons": reasons,
})
scored_products.sort(key=lambda x: x["score"], reverse=True)
if not scored_products:
return "No products match your current preferences."
lines = ["Curated selections for you:"]
for sp in scored_products[:5]:
p = sp["product"]
why = ", ".join(sp["reasons"])
lines.append(
f" {p['id']}: {p['name']} - ${p['price']:.2f} "
f"({why})"
)
return "\n".join(lines)
Wish List Management with Price Alerts
@function_tool
def add_to_wish_list(customer_id: str, product_id: str,
target_price: Optional[float] = None) -> str:
"""Add a product to the customer's wish list with optional price alert."""
profile = STYLE_PROFILES.get(customer_id)
if not profile:
return "No profile found."
product = next((p for p in PRODUCT_CATALOG if p["id"] == product_id), None)
if not product:
return "Product not found."
wish_entry = {
"product_id": product_id,
"product_name": product["name"],
"current_price": product["price"],
"target_price": target_price,
"added_date": "2026-03-17",
}
profile["wish_list"].append(wish_entry)
msg = f"Added {product['name']} to your wish list."
if target_price:
msg += f" You will be notified when the price drops to ${target_price:.2f}."
return msg
@function_tool
def view_wish_list(customer_id: str) -> str:
"""View the customer's wish list with current prices."""
profile = STYLE_PROFILES.get(customer_id)
if not profile:
return "No profile found."
if not profile["wish_list"]:
return "Your wish list is empty."
lines = ["Your Wish List:"]
for item in profile["wish_list"]:
price_info = f"${item['current_price']:.2f}"
if item.get("target_price"):
price_info += f" (alert at ${item['target_price']:.2f})"
lines.append(f" {item['product_name']} - {price_info}")
return "\n".join(lines)
@function_tool
def check_new_arrivals(customer_id: str) -> str:
"""Check for new arrivals that match the customer's profile."""
profile = STYLE_PROFILES.get(customer_id)
if not profile:
return "No profile found."
new_items = [p for p in PRODUCT_CATALOG if p["new_arrival"]]
matching = []
for product in new_items:
color_match = any(c in profile["colors"] for c in product["colors"])
style_match = product["style"] in profile["styles"]
if color_match or style_match:
matching.append(product)
if not matching:
return "No new arrivals match your style profile right now."
lines = ["New arrivals matching your style:"]
for p in matching:
lines.append(f" {p['id']}: {p['name']} - ${p['price']:.2f}")
return "\n".join(lines)
Assembling the Personal Shopper Agent
shopper_agent = Agent(
name="Personal Shopper",
instructions="""You are a personal shopping assistant.
First interaction: Build a style profile by asking about colors,
styles (classic, modern, casual, bohemian), budget range, sizes
by category (tops:M, bottoms:32), and anything they want to avoid.
Ongoing interactions:
- Curate selections tailored to their profile
- Suggest complete outfits for specific occasions
- Manage their wish list with price drop alerts
- Notify about new arrivals matching their taste
- Learn from feedback to refine recommendations
Be opinionated but not pushy. Explain why you recommend
each item. If they dislike a suggestion, update preferences.""",
tools=[create_style_profile, update_style_preferences,
curate_selections, add_to_wish_list,
view_wish_list, check_new_arrivals],
)
FAQ
How do I improve curation accuracy over time?
Track three signals: explicit feedback (customer says "I don't like this"), implicit positive signals (items added to cart or wish list), and implicit negative signals (items shown but ignored). Use these to adjust scoring weights in the curation engine. After 10 to 15 interactions, the agent should have enough data to significantly outperform generic recommendations.
Should the agent suggest items outside the customer's stated preferences?
Yes, occasionally. Introduce a "discovery" slot in curated selections — one item that stretches beyond stated preferences but scores well on complementary attributes. For example, if a customer prefers classic styles, occasionally suggest a modern piece that matches their color and budget preferences. Frame it as a suggestion rather than a recommendation to manage expectations.
How do I handle seasonal transitions in the style profile?
Build season awareness into the curation engine. Tag products with seasonality (spring, summer, fall, winter) and prioritize in-season items. Do not delete off-season preferences — instead, reduce their weight temporarily. When a customer interacts at the start of a new season, proactively ask if their preferences have changed and suggest seasonal updates to their profile.
#PersonalShopper #StyleAI #ProductCuration #WishList #RetailPersonalization #AgenticAI #LearnAI #AIEngineering
CallSphere Team
Expert insights on AI voice agents and customer communication automation.
Try CallSphere AI Voice Agents
See how AI voice agents work for your industry. Live demo available -- no signup required.