Tutorial 3: Build a Gift Recommender

Create an agent that suggests gifts based on recipient personality, occasion, and budget constraints.

What You'll Build

A Python-based gift recommender that takes recipient details and budget, then queries BuyWhere to find appropriate product recommendations across multiple categories.

Prerequisites

  • BuyWhere API key
  • Python 3.10+

Step 1: Define Gift Categories and Mappings

Create a gift_recommender.py:

import os
import requests
from typing import Optional

BUYWHERE_API_KEY = os.environ["BUYWHERE_API_KEY"]
BUYWHERE_BASE_URL = "https://api.buywhere.ai"

HEADERS = {
    "Authorization": f"Bearer {BUYWHERE_API_KEY}",
    "Content-Type": "application/json"
}

OCCASION_CATEGORIES = {
    "birthday": ["Electronics", "Fashion", "Beauty", "Sports", "Books"],
    "anniversary": ["Jewelry", "Fashion", "Beauty", "Home & Living"],
    "graduation": ["Electronics", "Books", "Fashion", "Sports"],
    "wedding": ["Home & Living", "Kitchen", "Jewelry"],
    "christmas": ["Electronics", "Toys & Games", "Beauty", "Fashion"],
    "mother's_day": ["Beauty", "Fashion", "Jewelry", "Home & Living"],
    "father's_day": ["Electronics", "Sports", "Fashion", "Tools"],
    "valentines": ["Jewelry", "Beauty", "Fashion", "Flowers"],
}

INTEREST_KEYWORDS = {
    "tech": ["laptop", "headphones", "smartwatch", "tablet", "wireless earbuds"],
    "fashion": ["watch", "bag", "shoes", "wallet", "sunglasses"],
    "fitness": ["running shoes", "yoga mat", "dumbbells", "fitness tracker", "sports wear"],
    "gaming": ["console", "controller", "gaming headset", "gaming chair", "keyboard"],
    "cooking": ["blender", "air fryer", "knife set", "coffee maker", "stand mixer"],
    "reading": ["kindle", "books", "bookshelf", "reading light", "book stand"],
    "beauty": ["skincare", "makeup", "perfume", "hair dryer", "beauty device"],
}

def search_products(query: str, category: str = None, 
                    max_price: float = None, limit: int = 10) -> dict:
    """Search for products with filters."""
    params = {"q": query, "limit": limit}
    if category:
        params["category"] = category
    if max_price:
        params["max_price"] = max_price
    
    response = requests.get(
        f"{BUYWHERE_BASE_URL}/v2/products",
        headers=HEADERS,
        params=params,
        timeout=30
    )
    response.raise_for_status()
    return response.json()

Step 2: Build the Recommendation Engine

def get_gift_recommendations(
    occasion: str,
    budget: float,
    interests: list[str] = None,
    recipient_age: int = None,
    exclude_categories: list[str] = None
) -> dict:
    """
    Get personalized gift recommendations.
    
    Args:
        occasion: Birthday, anniversary, graduation, etc.
        budget: Maximum budget in SGD
        interests: List of interests (tech, fashion, fitness, etc.)
        recipient_age: Age of recipient
        exclude_categories: Categories to avoid
    """
    categories = OCCASION_CATEGORIES.get(occasion.lower(), ["Electronics", "Fashion"])
    
    recommendations = []
    seen_products = set()
    
    if interests:
        for interest in interests[:3]:
            keywords = INTEREST_KEYWORDS.get(interest.lower(), [interest])
            for keyword in keywords[:2]:
                result = search_products(keyword, max_price=budget, limit=15)
                recommendations.extend(_filter_and_rank(
                    result.get("items", []),
                    budget,
                    exclude_categories
                ))
    else:
        for category in categories[:3]:
            result = search_products(category, max_price=budget, limit=15)
            recommendations.extend(_filter_and_rank(
                result.get("items", []),
                budget,
                exclude_categories
            ))
    
    unique_recommendations = []
    for rec in recommendations:
        if rec["id"] not in seen_products:
            seen_products.add(rec["id"])
            unique_recommendations.append(rec)
    
    unique_recommendations.sort(key=lambda x: (
        x.get("rating", 0) or 0,
        -(float(x.get("price_sgd", x["price"])))
    ), reverse=True)
    
    return {
        "occasion": occasion,
        "budget": budget,
        "recommendations": unique_recommendations[:10],
        "total_found": len(unique_recommendations)
    }

def _filter_and_rank(products: list, budget: float, 
                     exclude: list[str] = None) -> list:
    """Filter products by budget and rank by rating."""
    exclude = exclude or []
    filtered = []
    
    for p in products:
        price = float(p.get("price_sgd", p["price"]))
        if price > budget:
            continue
        if p.get("category") in exclude:
            continue
        if not p.get("is_available", True):
            continue
        
        p["_sort_score"] = (
            (p.get("rating") or 3.0) * 
            (1.0 / (price / budget + 0.1))
        )
        filtered.append(p)
    
    return filtered

Step 3: Add Gift Card and Bundle Suggestions

def suggest_gift_bundles(primary_gift: dict, budget: float) -> list[dict]:
    """Suggest complementary items to bundle with a primary gift."""
    primary_price = float(primary_gift.get("price_sgd", primary_gift["price"]))
    remaining_budget = budget - primary_price
    
    if remaining_budget < 10:
        return []
    
    category = primary_gift.get("category", "")
    bundle_keywords = {
        "Electronics": ["phone case", "screen protector", "power bank", "cable"],
        "Fashion": ["wallet", "belt", "scarf", "cufflinks"],
        "Beauty": ["makeup bag", "perfume", "skincare set"],
        "Sports": ["water bottle", "gym bag", "sports socks"],
        "Books": ["bookmark", "reading light", "book stand"],
    }
    
    keywords = bundle_keywords.get(category, ["accessory"])
    suggestions = []
    
    for keyword in keywords[:3]:
        result = search_products(keyword, max_price=remaining_budget, limit=5)
        for item in result.get("items", []):
            item_price = float(item.get("price_sgd", item["price"]))
            if item_price <= remaining_budget:
                suggestions.append({
                    **item,
                    "bundle_with": primary_gift["name"],
                    "total_cost": primary_price + item_price
                })
                if len(suggestions) >= 3:
                    break
    
    return suggestions

Step 4: Format Recommendations for Display

def format_recommendation(recommendation: dict, rank: int) -> str:
    """Format a single recommendation for display."""
    name = recommendation["name"]
    price = recommendation["price"]
    currency = recommendation["currency"]
    source = recommendation["source"]
    rating = recommendation.get("rating", "N/A")
    url = recommendation.get("affiliate_url", recommendation["buy_url"])
    
    return (
        f"{rank}. **{name}**\n"
        f"   šŸ’° {currency} {price} | ⭐ {rating} | {source}\n"
        f"   šŸ”— {url}"
    )

def print_recommendations(result: dict):
    """Print formatted recommendations."""
    print(f"\nšŸŽ Gift Recommendations for {result['occasion']}")
    print(f"   Budget: SGD {result['budget']:.2f}")
    print("=" * 60)
    
    for i, rec in enumerate(result["recommendations"], 1):
        print(format_recommendation(rec, i))
    
    print(f"\nFound {result['total_found']} options")

Step 5: Run the Gift Recommender

if __name__ == "__main__":
    result = get_gift_recommendations(
        occasion="birthday",
        budget=150.0,
        interests=["tech", "fitness"],
        recipient_age=28
    )
    
    print_recommendations(result)
    
    if result["recommendations"]:
        primary = result["recommendations"][0]
        bundles = suggest_gift_bundles(primary, result["budget"])
        
        if bundles:
            print("\n" + "=" * 60)
            print("šŸ“¦ Bundle Suggestions:")
            for bundle in bundles:
                print(
                    f"   + {bundle['name']}: SGD {bundle['price']} "
                    f"(total: SGD {bundle['total_cost']:.2f})"
                )

Output:

šŸŽ Gift Recommendations for birthday
   Budget: SGD 150.00
============================================================
1. **Sony WH-CH520 Wireless Headphones**
   šŸ’° SGD 129.00 | ⭐ 4.5 | shopee_sg
   šŸ”— https://api.buywhere.ai/v1/track/abc123

2. **Anker Soundcore Life Q20 Headphones**
   šŸ’° SGD 89.00 | ⭐ 4.6 | lazada_sg
   šŸ”— https://api.buywhere.ai/v1/track/def456

...

Found 10 options

============================================================
šŸ“¦ Bundle Suggestions:
   + Anker PowerCore 10000 Power Bank: SGD 35.00 (total: SGD 164.00)
   + JBL Tune 510BT Headphones: SGD 79.00 (total: SGD 208.00)

Step 6: Create a Gift Advisor Agent Class

class GiftAdvisor:
    def __init__(self, api_key: str):
        self.api_key = api_key
    
    def ask(self, description: str) -> dict:
        """
        Parse a natural language gift request.
        
        Example: "I need a birthday gift for my 35-year-old brother 
                  who likes gaming and cooking, under $200"
        """
        prompt = f"""
        Parse this gift request and extract:
        - occasion
        - budget
        - recipient_age
        - interests
        - any constraints
        
        Request: {description}
        
        Return JSON with these fields.
        """
        
        import json
        # In production, use an LLM to parse the natural language request
        # For now, we'll use simple keyword extraction
        return self._simple_parse(description)
    
    def _simple_parse(self, description: str) -> dict:
        """Simple rule-based parsing for gift requests."""
        result = {
            "occasion": "birthday",
            "budget": 100.0,
            "interests": [],
            "recipient_age": None
        }
        
        desc_lower = description.lower()
        
        if "birthday" in desc_lower:
            result["occasion"] = "birthday"
        elif "anniversary" in desc_lower:
            result["occasion"] = "anniversary"
        elif "graduation" in desc_lower:
            result["occasion"] = "graduation"
        
        import re
        budget_match = re.search(r"under\s*\$?(\d+)", desc_lower)
        if budget_match:
            result["budget"] = float(budget_match.group(1))
        
        age_match = re.search(r"(\d+)[- ]year[- ]old", desc_lower)
        if age_match:
            result["recipient_age"] = int(age_match.group(1))
        
        for interest in ["gaming", "tech", "fitness", "cooking", "fashion", "beauty"]:
            if interest in desc_lower:
                result["interests"].append(interest)
        
        return result

Next Steps