← Back to documentation

product-comparison-chatbot

Tutorial 4: Create a Product Comparison Chatbot

Build a chatbot that helps users compare products across multiple dimensions: price, features, ratings, and value for money.

What You'll Build

An interactive chatbot that:

  1. Accepts product comparison queries
  2. Fetches multi-product data from BuyWhere
  3. Presents side-by-side comparisons
  4. Makes recommendations based on user priorities

Prerequisites

  • BuyWhere API key
  • Python 3.10+

Step 1: Set Up the Chatbot Framework

Install dependencies:

pip install buywhere rich

Create comparison_chatbot.py:

import os
import requests
from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from rich.text import Text

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"
}

console = Console()

def search_products(query: str, limit: int = 20) -> dict:
    """Search for products."""
    response = requests.get(
        f"{BUYWHERE_BASE_URL}/v2/products",
        headers=HEADERS,
        params={"q": query, "limit": limit},
        timeout=30
    )
    response.raise_for_status()
    return response.json()

Step 2: Build the Comparison Engine

def compare_products(product_names: list[str]) -> dict:
    """
    Compare multiple specific products by fetching details.
    """
    results = []
    
    for name in product_names:
        search_result = search_products(name, limit=5)
        items = search_result.get("items", [])
        
        if not items:
            continue
        
        best_match = items[0]
        
        results.append({
            "name": best_match["name"],
            "price": float(best_match.get("price_sgd", best_match["price"])),
            "currency": best_match["currency"],
            "source": best_match["source"],
            "rating": best_match.get("rating"),
            "review_count": best_match.get("review_count"),
            "availability": "In Stock" if best_match.get("is_available") else "Out of Stock",
            "url": best_match.get("affiliate_url", best_match["buy_url"]),
            "category": best_match.get("category"),
            "brand": best_match.get("brand"),
            "raw": best_match
        })
    
    if not results:
        return {"error": "No products found"}
    
    prices = [r["price"] for r in results]
    min_price, max_price = min(prices), max(prices)
    avg_price = sum(prices) / len(prices)
    
    for r in results:
        r["price_vs_avg_pct"] = ((r["price"] - avg_price) / avg_price) * 100
        r["price_vs_min_pct"] = ((r["price"] - min_price) / min_price) * 100 if min_price > 0 else 0
        r["value_score"] = _calculate_value_score(r, avg_price)
    
    return {
        "products": sorted(results, key=lambda x: x["price"]),
        "summary": {
            "lowest_price": min_price,
            "highest_price": max_price,
            "average_price": avg_price,
            "price_spread": max_price - min_price
        }
    }

def _calculate_value_score(product: dict, avg_price: float) -> float:
    """Calculate a simple value score based on rating and price."""
    rating = product.get("rating") or 3.0
    price_ratio = avg_price / product["price"] if product["price"] > 0 else 0
    return rating * price_ratio

Step 3: Display Comparison with Rich

def display_comparison(result: dict):
    """Display comparison in a formatted table."""
    if "error" in result:
        console.print(f"[red]Error: {result['error']}[/red]")
        return
    
    summary = result["summary"]
    products = result["products"]
    
    console.print("\n[bold cyan]πŸ“Š Product Comparison Results[/bold cyan]\n")
    
    table = Table(show_header=True, header_style="bold magenta")
    table.add_column("Product", style="cyan", no_wrap=False)
    table.add_column("Price", justify="right")
    table.add_column("vs Avg", justify="right")
    table.add_column("Rating", justify="center")
    table.add_column("Reviews", justify="right")
    table.add_column("Source", justify="center")
    
    for p in products:
        price_str = f"SGD {p['price']:.2f}"
        vs_avg = f"{p['price_vs_avg_pct']:+.1f}%"
        vs_avg_style = "green" if p['price_vs_avg_pct'] < 0 else "red"
        rating_str = f"⭐ {p['rating']}" if p['rating'] else "N/A"
        reviews_str = f"{p['review_count']:,}" if p['review_count'] else "N/A"
        
        table.add_row(
            p["name"][:40] + "..." if len(p["name"]) > 40 else p["name"],
            price_str,
            f"[{vs_avg_style}]{vs_avg}[/{vs_avg_style}]",
            rating_str,
            reviews_str,
            p["source"]
        )
    
    console.print(table)
    
    console.print(f"\n[dim]Price range: SGD {summary['lowest_price']:.2f} - "
                  f"SGD {summary['highest_price']:.2f} "
                  f"(spread: SGD {summary['price_spread']:.2f})[/dim]")
    
    best_value = max(products, key=lambda x: x["value_score"])
    console.print(f"\n[bold green]πŸ’‘ Best Value:[/bold green] {best_value['name']}")
    console.print(f"   Price: SGD {best_value['price']:.2f} | "
                  f"Rating: {best_value['rating']} | "
                  f"Value Score: {best_value['value_score']:.2f}")

Step 4: Handle Different Query Types

def handle_comparison_query(query: str) -> dict:
    """
    Handle various comparison query formats.
    """
    query_lower = query.lower()
    
    if "vs" in query_lower or " versus " in query_lower or " or " in query_lower:
        products = _extract_product_names(query)
        return compare_products(products)
    
    elif "best" in query_lower and ("under" in query_lower or "$" in query_lower):
        max_price = _extract_budget(query)
        return find_best_under_budget(
            _extract_product_category(query),
            max_price
        )
    
    elif "top" in query_lower:
        count = _extract_count(query)
        return get_top_rated(_extract_product_category(query), count)
    
    else:
        return search_products(query, limit=10)

def _extract_product_names(query: str) -> list[str]:
    """Extract product names from comparison query."""
    import re
    
    query = query.replace(" vs ", " or ").replace(" versus ", " or ")
    
    parts = re.split(r"\s+or\s+", query, flags=re.IGNORECASE)
    
    products = []
    for part in parts:
        part = re.sub(r"(best|top|compare|vs|versus|\$?\d+)", "", part, flags=re.IGNORECASE)
        part = part.strip()
        if len(part) > 2:
            products.append(part)
    
    return products[:5]

def _extract_budget(query: str) -> float:
    """Extract budget from query."""
    import re
    match = re.search(r"\$?(\d+)", query)
    return float(match.group(1)) if match else 100.0

def _extract_product_category(query: str) -> str:
    """Extract product category from query."""
    query = query.lower()
    
    categories = ["laptop", "phone", "headphones", "watch", "camera", 
                  "tablet", "earbuds", "speaker", "keyboard", "mouse"]
    
    for cat in categories:
        if cat in query:
            return cat
    
    return query.split()[0] if query.split() else "product"

def _extract_count(query: str) -> int:
    """Extract count from 'top N' queries."""
    import re
    match = re.search(r"top\s*(\d+)", query, re.IGNORECASE)
    return int(match.group(1)) if match else 5

def get_top_rated(category: str, count: int = 5) -> dict:
    """Get top-rated products in a category."""
    result = search_products(category, limit=50)
    items = result.get("items", [])
    
    rated = [p for p in items if p.get("rating")]
    sorted_by_rating = sorted(rated, key=lambda x: x["rating"], reverse=True)
    
    return {
        "query": category,
        "products": sorted_by_rating[:count],
        "type": "top_rated"
    }

def find_best_under_budget(category: str, budget: float) -> dict:
    """Find best products under a specific budget."""
    result = search_products(category, limit=50)
    items = result.get("items", [])
    
    under_budget = []
    for p in items:
        price = float(p.get("price_sgd", p["price"]))
        if price <= budget:
            score = (p.get("rating") or 3.0) / (price / budget + 0.1)
            under_budget.append((p, score))
    
    under_budget.sort(key=lambda x: x[1], reverse=True)
    
    return {
        "query": category,
        "budget": budget,
        "products": [p[0] for p in under_budget[:10]],
        "type": "under_budget"
    }

Step 5: Interactive Chat Loop

def run_chatbot():
    """Run the interactive comparison chatbot."""
    console.print(Panel.fit(
        "[bold cyan]πŸ›’ BuyWhere Product Comparison Bot[/bold cyan]\n\n"
        "I can help you compare products:\n"
        "β€’ Type 'compare X vs Y vs Z' to compare specific products\n"
        "β€’ Type 'best laptop under $500' to find options\n"
        "β€’ Type 'top 5 headphones' for top-rated items\n"
        "β€’ Type 'quit' to exit",
        border_style="cyan"
    ))
    
    while True:
        try:
            query = console.input("\n[bold green]You:[/bold green] ").strip()
            
            if query.lower() in ["quit", "exit", "q"]:
                console.print("[yellow]Goodbye![/yellow]")
                break
            
            if not query:
                continue
            
            with console.status("[bold cyan]Fetching comparison data...[/bold cyan]"):
                result = handle_comparison_query(query)
            
            if "error" in result:
                console.print(f"[red]{result['error']}[/red]")
            elif result.get("type") == "top_rated":
                display_top_rated(result)
            elif result.get("type") == "under_budget":
                display_under_budget(result)
            else:
                display_comparison(result)
                
        except KeyboardInterrupt:
            console.print("\n[yellow]Goodbye![/yellow]")
            break
        except Exception as e:
            console.print(f"[red]Error: {e}[/red]")

def display_top_rated(result: dict):
    """Display top-rated products."""
    console.print(f"\n[bold cyan]⭐ Top Rated: {result['query'].title()}[/bold cyan]\n")
    
    for i, p in enumerate(result["products"], 1):
        console.print(
            f"{i}. [bold]{p['name']}[/bold]\n"
            f"   SGD {p['price']} | ⭐ {p['rating']} | "
            f"{p['review_count']:,} reviews | {p['source']}"
        )

def display_under_budget(result: dict):
    """Display products under budget."""
    console.print(
        f"\n[bold cyan]πŸ’° Best {result['query'].title()} Under SGD {result['budget']:.2f}[/bold cyan]\n"
    )
    
    for i, p in enumerate(result["products"], 1):
        console.print(
            f"{i}. [bold]{p['name']}[/bold]\n"
            f"   SGD {p['price']} | ⭐ {p.get('rating', 'N/A')} | {p['source']}"
        )

Step 6: Run the Chatbot

export BUYWHERE_API_KEY="bw_live_your_key_here"
python comparison_chatbot.py

Example session:

╔══════════════════════════════════════════════════════════════╗
β•‘  πŸ›’ BuyWhere Product Comparison Bot                          β•‘
β•‘                                                              β•‘
β•‘  I can help you compare products:                            β•‘
β•‘  β€’ Type 'compare X vs Y vs Z' to compare specific products  β•‘
β•‘  β€’ Type 'best laptop under $500' to find options            β•‘
β•‘  β€’ Type 'top 5 headphones' for top-rated items              β•‘
β•‘  β€’ Type 'quit' to exit                                       β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•

You: compare sony wh-1000xm5 vs apple airpods max

πŸ“Š Product Comparison Results

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Product                          β”‚    Priceβ”‚  vs Avgβ”‚  Ratingβ”‚  Reviewsβ”‚ Source   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Sony WH-1000XM5                  β”‚ SGD 449 β”‚  -5.2% β”‚  ⭐ 4.8β”‚   1,284 β”‚ shopee_sgβ”‚
β”‚ Apple AirPods Max               β”‚ SGD 599 β”‚ +26.1% β”‚  ⭐ 4.7 β”‚     856 β”‚ lazada_sgβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Price range: SGD 449.00 - SGD 599.00 (spread: SGD 150.00)

πŸ’‘ Best Value: Sony WH-1000XM5
   Price: SGD 449.00 | Rating: 4.8 | Value Score: 5.23

Next Steps