Building a Product Recommendation Agent: Personalized Suggestions via AI
Design and implement an AI recommendation agent that combines user preference modeling, inventory-aware filtering, and LLM-powered explanation generation for personalized product suggestions.
Beyond Collaborative Filtering
Traditional recommendation systems rely on collaborative filtering ("users who bought X also bought Y") or content-based filtering (matching item attributes to user profiles). These approaches work at scale but produce opaque suggestions that users cannot interrogate. An agentic recommendation system adds a conversational layer: the agent asks clarifying questions, explains why it recommends each product, and adapts in real time as the user provides feedback.
User Preference Modeling
The agent needs a structured representation of user preferences that it can update during the conversation. We store both explicit preferences (stated by the user) and implicit signals (derived from browsing behavior).
from dataclasses import dataclass, field
from typing import Optional
@dataclass
class UserPreferences:
user_id: str
budget_min: Optional[float] = None
budget_max: Optional[float] = None
preferred_categories: list[str] = field(default_factory=list)
preferred_brands: list[str] = field(default_factory=list)
excluded_brands: list[str] = field(default_factory=list)
use_case: Optional[str] = None
priority: str = "balanced" # "price", "quality", "balanced"
viewed_products: list[str] = field(default_factory=list)
purchased_products: list[str] = field(default_factory=list)
def update_from_message(self, parsed: dict):
if "budget_max" in parsed:
self.budget_max = parsed["budget_max"]
if "budget_min" in parsed:
self.budget_min = parsed["budget_min"]
if "categories" in parsed:
self.preferred_categories.extend(parsed["categories"])
if "use_case" in parsed:
self.use_case = parsed["use_case"]
if "priority" in parsed:
self.priority = parsed["priority"]
Inventory-Aware Product Search
Recommendations are useless if the product is out of stock. The search layer queries your product catalog and filters by availability, price range, and preference match.
from dataclasses import dataclass
import asyncpg
@dataclass
class Product:
id: str
name: str
category: str
brand: str
price: float
rating: float
stock_count: int
description: str
features: list[str]
async def search_products(
pool: asyncpg.Pool,
prefs: UserPreferences,
limit: int = 10,
) -> list[Product]:
conditions = ["p.stock_count > 0"]
params = []
idx = 1
if prefs.budget_max:
conditions.append(f"p.price <= ${idx}")
params.append(prefs.budget_max)
idx += 1
if prefs.budget_min:
conditions.append(f"p.price >= ${idx}")
params.append(prefs.budget_min)
idx += 1
if prefs.preferred_categories:
conditions.append(f"p.category = ANY(${idx})")
params.append(prefs.preferred_categories)
idx += 1
if prefs.excluded_brands:
conditions.append(f"p.brand != ALL(${idx})")
params.append(prefs.excluded_brands)
idx += 1
where_clause = " AND ".join(conditions)
order = "p.rating DESC" if prefs.priority == "quality" else "p.price ASC"
query = f"""
SELECT id, name, category, brand, price, rating,
stock_count, description, features
FROM products p
WHERE {where_clause}
ORDER BY {order}
LIMIT {limit}
"""
rows = await pool.fetch(query, *params)
return [Product(**dict(row)) for row in rows]
LLM-Powered Recommendation with Explanations
The agent does not just return a ranked list — it explains each recommendation in context of what the user asked for. This builds trust and helps users make faster decisions.
See AI Voice Agents Handle Real Calls
Book a free demo or calculate how much you can save with AI voice automation.
from openai import AsyncOpenAI
client = AsyncOpenAI()
RECOMMENDATION_PROMPT = """You are a product recommendation assistant.
User preferences:
- Budget: {budget_min} to {budget_max}
- Use case: {use_case}
- Priority: {priority}
- Preferred categories: {categories}
Available products (JSON):
{products_json}
For each recommended product, provide:
1. The product name and price
2. A 1-2 sentence explanation of why it fits this user's needs
3. One potential drawback to be transparent about
Recommend the top 3 products. Be specific about why each matches
the user's stated preferences.
"""
async def generate_recommendations(
prefs: UserPreferences, products: list[Product]
) -> str:
import json
products_data = [
{
"name": p.name,
"brand": p.brand,
"price": p.price,
"rating": p.rating,
"description": p.description,
"features": p.features,
}
for p in products
]
response = await client.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "user",
"content": RECOMMENDATION_PROMPT.format(
budget_min=prefs.budget_min or "No minimum",
budget_max=prefs.budget_max or "No maximum",
use_case=prefs.use_case or "General",
priority=prefs.priority,
categories=", ".join(prefs.preferred_categories) or "Any",
products_json=json.dumps(products_data, indent=2),
),
}],
)
return response.choices[0].message.content
Wiring It as an Agent Tool
The recommendation logic becomes a tool the conversational agent can call. The agent handles preference extraction from natural language while the tool handles the database query and LLM-powered ranking.
from agents import Agent, function_tool
@function_tool
async def recommend_products(
category: str = "",
budget_max: float = 0,
use_case: str = "",
priority: str = "balanced",
) -> str:
"""Find and recommend products based on user preferences."""
prefs = UserPreferences(
user_id="session-user",
budget_max=budget_max if budget_max > 0 else None,
preferred_categories=[category] if category else [],
use_case=use_case,
priority=priority,
)
pool = await get_db_pool()
products = await search_products(pool, prefs)
if not products:
return "No products match your criteria. Try adjusting your budget or category."
return await generate_recommendations(prefs, products)
recommendation_agent = Agent(
name="ProductAdvisor",
instructions="""You help customers find the right product.
Ask about their budget, use case, and priorities before
making recommendations. Use the recommend_products tool
to fetch personalized suggestions.""",
tools=[recommend_products],
)
FAQ
How do I handle the cold-start problem for new users with no history?
Start with a short preference elicitation conversation. Ask 2-3 targeted questions about budget, use case, and brand preferences before making any recommendations. This gives the agent enough signal to produce useful results without requiring purchase history.
Should I embed product descriptions for semantic search instead of SQL filtering?
Use both. SQL filtering handles hard constraints like price range and stock availability efficiently. Semantic search via embeddings excels at matching vague user descriptions ("something for outdoor cooking that is easy to clean") against product descriptions. Run SQL first to narrow the candidate set, then re-rank with embedding similarity.
How do I prevent the agent from always recommending the most expensive products?
Include the user's stated priority in the prompt and in the SQL ordering. If the user says "best value," order by price ascending before passing products to the LLM. Also add an instruction in the system prompt that the agent should recommend products across the user's price range rather than clustering at the top.
#RecommendationEngine #Personalization #ECommerceAI #ProductDiscovery #Python #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.