By Sagar Shankaran, Founder of CallSphere
Build an AI gift recommendation agent that gathers recipient preferences through conversation, searches a product catalog, filters by budget, and personalizes suggestions — the perfect gift-finding assistant.
Key takeaways
Finding the right gift requires understanding the recipient's interests, respecting your budget, avoiding duplicates, and navigating thousands of product options. Most people default to generic gifts because the research effort is too high. A gift recommendation agent solves this by conducting a structured preference interview, searching a product catalog, applying budget constraints, and providing personalized recommendations with purchase links.
This tutorial builds a complete gift recommendation system with preference gathering, product search, budget filtering, and personalized scoring.
mkdir gift-agent && cd gift-agent
python -m venv venv && source venv/bin/activate
pip install openai-agents pydantic
mkdir -p src
touch src/__init__.py src/preferences.py src/catalog.py
touch src/recommender.py src/agent.py
The preference model captures structured information about the gift recipient.
flowchart LR
CALLER(["Shopper"])
subgraph TEL["Telephony"]
SIP["Twilio SIP and PSTN"]
end
subgraph BRAIN["E-commerce AI Agent"]
STT["Streaming STT<br/>Deepgram or Whisper"]
NLU{"Intent and<br/>Entity Extraction"}
TOOLS["Tool Calls"]
TTS["Streaming TTS<br/>ElevenLabs or Rime"]
end
subgraph DATA["Live Data Plane"]
CRM[("CRM and Notes")]
CAL[("Calendar and<br/>Schedule")]
KB[("Knowledge Base<br/>and Policies")]
end
subgraph OUT["Outcomes"]
O1(["Order status answered"])
O2(["Return RMA created"])
O3(["Specialist handoff"])
end
CALLER --> SIP --> STT --> NLU
NLU -->|Lookup| TOOLS
TOOLS <--> CRM
TOOLS <--> CAL
TOOLS <--> KB
NLU --> TTS --> SIP --> CALLER
NLU -->|Resolved| O1
NLU -->|Schedule| O2
NLU -->|Escalate| O3
style CALLER fill:#f1f5f9,stroke:#64748b,color:#0f172a
style NLU fill:#4f46e5,stroke:#4338ca,color:#fff
style O1 fill:#059669,stroke:#047857,color:#fff
style O2 fill:#0ea5e9,stroke:#0369a1,color:#fff
style O3 fill:#f59e0b,stroke:#d97706,color:#1f2937
# src/preferences.py
from pydantic import BaseModel
class RecipientProfile(BaseModel):
name: str = ""
relationship: str = "" # friend, partner, parent, colleague
age_range: str = "" # child, teen, young adult, adult, senior
interests: list[str] = []
hobbies: list[str] = []
dislikes: list[str] = []
occasion: str = "" # birthday, holiday, anniversary, thank you
budget_min: float = 0
budget_max: float = 100
class PreferenceManager:
def __init__(self):
self.profiles: dict[str, RecipientProfile] = {}
def create_profile(self, name: str) -> str:
self.profiles[name.lower()] = RecipientProfile(name=name)
return f"Created profile for {name}"
def update_profile(self, name: str, **kwargs) -> str:
profile = self.profiles.get(name.lower())
if not profile:
return f"No profile found for {name}"
for key, value in kwargs.items():
if hasattr(profile, key):
if isinstance(getattr(profile, key), list):
current = getattr(profile, key)
if isinstance(value, list):
current.extend(value)
else:
current.append(value)
else:
setattr(profile, key, value)
return f"Updated {name}'s profile: {kwargs}"
def get_profile(self, name: str) -> RecipientProfile | None:
return self.profiles.get(name.lower())
def get_profile_summary(self, name: str) -> str:
profile = self.profiles.get(name.lower())
if not profile:
return f"No profile for {name}"
lines = [
f"Name: {profile.name}",
f"Relationship: {profile.relationship or 'not set'}",
f"Age range: {profile.age_range or 'not set'}",
f"Interests: {', '.join(profile.interests) or 'none yet'}",
f"Hobbies: {', '.join(profile.hobbies) or 'none yet'}",
f"Dislikes: {', '.join(profile.dislikes) or 'none yet'}",
f"Occasion: {profile.occasion or 'not set'}",
f"Budget: ${profile.budget_min}-${profile.budget_max}",
]
return "\n".join(lines)
pref_manager = PreferenceManager()
# src/catalog.py
from pydantic import BaseModel
class Product(BaseModel):
id: str
name: str
category: str
price: float
tags: list[str] # interest/hobby tags for matching
description: str
url: str
rating: float # 1.0 to 5.0
PRODUCTS: list[Product] = [
Product(id="p001", name="Wireless Noise-Canceling Headphones",
category="electronics", price=89.99,
tags=["music", "technology", "travel", "podcasts"],
description="Premium sound quality with 30-hour battery",
url="https://example.com/headphones", rating=4.7),
Product(id="p002", name="Gourmet Coffee Sampler Box",
category="food", price=34.99,
tags=["coffee", "cooking", "foodie"],
description="12 single-origin coffees from around the world",
url="https://example.com/coffee-sampler", rating=4.5),
Product(id="p003", name="Leather-Bound Journal",
category="stationery", price=28.00,
tags=["writing", "reading", "journaling", "art"],
description="Handcrafted journal with 240 acid-free pages",
url="https://example.com/journal", rating=4.8),
Product(id="p004", name="Smart Fitness Tracker",
category="electronics", price=59.99,
tags=["fitness", "health", "running", "technology"],
description="Heart rate, sleep tracking, GPS, waterproof",
url="https://example.com/fitness-tracker", rating=4.4),
Product(id="p005", name="Indoor Herb Garden Kit",
category="home", price=45.00,
tags=["gardening", "cooking", "plants", "home"],
description="Self-watering planter with basil, mint, cilantro seeds",
url="https://example.com/herb-garden", rating=4.6),
Product(id="p006", name="Board Game Collection",
category="games", price=39.99,
tags=["games", "family", "social", "strategy"],
description="Set of 3 award-winning strategy board games",
url="https://example.com/board-games", rating=4.7),
Product(id="p007", name="Portable Watercolor Paint Set",
category="art", price=32.00,
tags=["art", "painting", "creative", "travel"],
description="24 colors in a travel-friendly tin case",
url="https://example.com/watercolor-set", rating=4.5),
Product(id="p008", name="Bluetooth Book Light",
category="electronics", price=24.99,
tags=["reading", "technology", "books"],
description="Rechargeable clip-on light with warm and cool modes",
url="https://example.com/book-light", rating=4.3),
Product(id="p009", name="Yoga Mat and Block Set",
category="fitness", price=42.00,
tags=["yoga", "fitness", "health", "wellness"],
description="Non-slip mat with cork block and carrying strap",
url="https://example.com/yoga-set", rating=4.6),
Product(id="p010", name="Personalized Star Map",
category="decor", price=55.00,
tags=["romantic", "art", "home", "personalized"],
description="Custom star map for any date and location",
url="https://example.com/star-map", rating=4.9),
Product(id="p011", name="Cooking Masterclass Subscription",
category="subscription", price=49.99,
tags=["cooking", "foodie", "learning"],
description="3-month access to online cooking classes",
url="https://example.com/cooking-class", rating=4.4),
Product(id="p012", name="Noise Machine with Nature Sounds",
category="home", price=35.00,
tags=["sleep", "wellness", "relaxation", "health"],
description="20 sound options with timer and night light",
url="https://example.com/noise-machine", rating=4.5),
]
def search_products(
tags: list[str] | None = None,
category: str = "",
min_price: float = 0,
max_price: float = 9999,
) -> list[Product]:
results = PRODUCTS
if min_price > 0 or max_price < 9999:
results = [
p for p in results
if min_price <= p.price <= max_price
]
if category:
results = [
p for p in results
if p.category.lower() == category.lower()
]
if tags:
tag_set = {t.lower() for t in tags}
results = [
p for p in results
if tag_set & {t.lower() for t in p.tags}
]
return results
The recommender scores products against the recipient profile.
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
# src/recommender.py
from src.catalog import Product, search_products
from src.preferences import RecipientProfile
def score_product(
product: Product, profile: RecipientProfile,
) -> float:
score = 0.0
all_interests = set(
i.lower() for i in profile.interests + profile.hobbies
)
product_tags = set(t.lower() for t in product.tags)
overlap = all_interests & product_tags
score += len(overlap) * 2.0
dislikes = set(d.lower() for d in profile.dislikes)
if dislikes & product_tags:
score -= 10.0
score += product.rating * 0.5
if profile.budget_min <= product.price <= profile.budget_max:
score += 1.0
return round(score, 2)
def get_recommendations(
profile: RecipientProfile, top_n: int = 5,
) -> list[dict]:
products = search_products(
tags=profile.interests + profile.hobbies,
min_price=profile.budget_min,
max_price=profile.budget_max,
)
if not products:
products = search_products(
min_price=profile.budget_min,
max_price=profile.budget_max,
)
scored = []
for product in products:
s = score_product(product, profile)
if s > 0:
scored.append({"product": product, "score": s})
scored.sort(key=lambda x: x["score"], reverse=True)
return scored[:top_n]
def format_recommendations(recs: list[dict]) -> str:
if not recs:
return "No matching products found."
lines = ["=== Gift Recommendations ===\n"]
for i, rec in enumerate(recs, 1):
p = rec["product"]
lines.append(f"{i}. {p.name}")
lines.append(f" Price: {p.price:.2f} USD | Rating: {p.rating}/5")
lines.append(f" {p.description}")
lines.append(f" Why: matches tags {', '.join(p.tags)}")
lines.append(f" Buy: {p.url}")
lines.append(f" Match score: {rec['score']}\n")
return "\n".join(lines)
# src/agent.py
import asyncio
import json
from agents import Agent, Runner, function_tool
from src.preferences import pref_manager
from src.recommender import get_recommendations, format_recommendations
@function_tool
def create_recipient(name: str) -> str:
"""Create a new gift recipient profile."""
return pref_manager.create_profile(name)
@function_tool
def set_recipient_details(
name: str,
relationship: str = "",
age_range: str = "",
interests: str = "",
hobbies: str = "",
dislikes: str = "",
occasion: str = "",
budget_min: float = 0,
budget_max: float = 100,
) -> str:
"""Update recipient profile details."""
kwargs: dict = {}
if relationship:
kwargs["relationship"] = relationship
if age_range:
kwargs["age_range"] = age_range
if interests:
kwargs["interests"] = [
i.strip() for i in interests.split(",")
]
if hobbies:
kwargs["hobbies"] = [
h.strip() for h in hobbies.split(",")
]
if dislikes:
kwargs["dislikes"] = [
d.strip() for d in dislikes.split(",")
]
if occasion:
kwargs["occasion"] = occasion
if budget_min > 0:
kwargs["budget_min"] = budget_min
if budget_max != 100:
kwargs["budget_max"] = budget_max
return pref_manager.update_profile(name, **kwargs)
@function_tool
def view_recipient(name: str) -> str:
"""View a recipient's profile."""
return pref_manager.get_profile_summary(name)
@function_tool
def find_gifts(name: str, top_n: int = 5) -> str:
"""Get gift recommendations for a recipient."""
profile = pref_manager.get_profile(name)
if not profile:
return f"No profile found for {name}"
recs = get_recommendations(profile, top_n)
return format_recommendations(recs)
gift_agent = Agent(
name="Gift Recommendation Agent",
instructions="""You are a thoughtful gift recommendation agent.
Help users find the perfect gift by gathering information about
the recipient through friendly conversation. Ask about:
1. Who the gift is for (relationship, age)
2. Their interests and hobbies
3. Things they dislike or already have
4. The occasion and budget
After gathering enough info, use the find_gifts tool to
generate personalized recommendations. Explain why each
suggestion matches the recipient. Be warm and helpful.""",
tools=[
create_recipient, set_recipient_details,
view_recipient, find_gifts,
],
)
async def main():
result = await Runner.run(
gift_agent,
"I need a gift for my friend Sarah. She's into yoga, "
"cooking, and reading. Budget is 30 to 50 dollars. "
"It's for her birthday. She doesn't like electronics.",
)
print(result.final_output)
if __name__ == "__main__":
asyncio.run(main())
The agent creates Sarah's profile, records her interests and dislikes, applies the budget filter, and recommends matching products — excluding electronics because of her stated dislike — with purchase links and explanations for each suggestion.
Real product data. Replace the static catalog with API calls to Amazon Product Advertising API, Etsy, or a web scraping service. The scoring and filtering logic remains the same.
Gift history. Add a past_gifts field to the profile and filter out previously given items. This prevents the agent from recommending something the recipient already has.
Seasonal awareness. Add seasonal product tags and boost scores for seasonally appropriate gifts. A cozy blanket scores higher in December than in July.
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.
The algorithm assigns points based on three factors: tag overlap between the recipient's interests and the product's tags (2 points per match), product rating (0.5 multiplied by rating), and budget fit (1 bonus point if the price falls within the stated budget). Products matching any of the recipient's dislikes receive a 10-point penalty, effectively removing them from recommendations.
Yes. Add a rate_gift tool that records whether the recipient liked a previous gift. Store these ratings and use them to adjust the scoring weights over time. If the recipient consistently loves cooking-related gifts, boost the weight for the "cooking" tag. This creates a personalized scoring model that improves with each gift-giving occasion.
The PreferenceManager already supports multiple profiles keyed by name. Ask the agent to find gifts for each person in sequence, and it will maintain separate profiles and generate independent recommendations. You could add a compare_gifts tool that ensures no two recipients get the same item if you are buying for a group event.
#GiftRecommendation #AIAgent #Python #ECommerce #Personalization #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.
A 2026 market read on Indonesian SMBs across Jakarta, Surabaya, and Bali — a mobile-first, Bahasa-speaking, WhatsApp-native market — and why CallSphere AI voice and chat agents fit the moment.
South African retailers and online stores lose sales to unanswered order, stock and delivery calls. Here is how CallSphere AI voice and chat agents plug the leak 24/7, in local languages.
Market data on Korea's retail and online-selling boom, and how CallSphere AI agents help sellers in Seoul, Busan and Incheon handle order, sizing and return calls 24/7 in Korean.
A 2026 market read on Malaysian SMBs across Kuala Lumpur, Penang, and Johor Bahru — juggling Malay, English, and Mandarin customers on WhatsApp — and why CallSphere AI voice and chat agents are the fix.
A practical playbook for Kuwait online sellers and retailers in Kuwait City and Hawalli to recover abandoned orders using a CallSphere AI voice and chat agent that answers 24/7 in Arabic, English and expat languages.
Step-by-step build of a working agent with the OpenAI Agents SDK — Agent class, tools, handoffs, tracing — plus an eval pipeline that catches regressions before merge.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.