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:
- Accepts product comparison queries
- Fetches multi-product data from BuyWhere
- Presents side-by-side comparisons
- 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