By Sagar Shankaran, Founder of CallSphere
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.
Key takeaways
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.
The style profile captures explicit preferences (stated by the customer) and implicit signals (derived from browsing and purchase history).
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
flowchart LR
INPUT(["User intent"])
PARSE["Parse plus<br/>classify"]
PLAN["Plan and tool<br/>selection"]
AGENT["Agent loop<br/>LLM plus tools"]
GUARD{"Guardrails<br/>and policy"}
EXEC["Execute and<br/>verify result"]
OBS[("Trace and metrics")]
OUT(["Outcome plus<br/>next action"])
INPUT --> PARSE --> PLAN --> AGENT --> GUARD
GUARD -->|Pass| EXEC --> OUT
GUARD -->|Fail| AGENT
AGENT --> OBS
style AGENT fill:#4f46e5,stroke:#4338ca,color:#fff
style GUARD fill:#f59e0b,stroke:#d97706,color:#1f2937
style OBS fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
style OUT fill:#059669,stroke:#047857,color:#fff
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}"
The curation tool scores products against the customer's style profile and returns ranked matches.
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)
@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)
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],
)
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.
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.
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
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

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
How generative AI produces verified dbt models for data migration — from scratch and incrementally — with SME validation and strict data governance.
See how Circini's automated incident management pipeline turns alert emails into triaged Jira tickets using Snowflake Cortex AI, GPT-4.1, Airflow & MS Teams.
Anthropic's Claude Fable 5 and Mythos 5 explained: pricing, availability, frontier benchmarks, the dual-model safeguard architecture, and what they mean for AI agents.
Where Claude Code, MCP, and multi-agent systems are taking GTM engineering next, and how to prepare your team now for standing and multi-agent workflows.
Where Claude Cowork and the Claude agent ecosystem are heading next — standing agents, MCP, skills as a moat — and the concrete moves to prepare your team now.
The metrics, leading signals, and anti-metrics that prove Claude Cowork is working — acceptance rate, time-to-outcome, and why usage counts mislead.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco