wowpixels/Perfume-recommender-agent
0
1import os2import json3import socket4import requests5import gradio as gr6from huggingface_hub import login7from smolagents import CodeAgent, InferenceClientModel, DuckDuckGoSearchTool, VisitWebpageTool8 9# Auth10hf_token = os.getenv("HF_TOKEN")11if hf_token:12 login(token=hf_token)13 print("HF login OK")14else:15 print("No HF_TOKEN set")16 17# Model18MODEL_ID = "Qwen/Qwen2.5-7B-Instruct"19 20def build_agent(enable_tools: bool):21 model = InferenceClientModel(model_id=MODEL_ID, token=hf_token)22 tools = [DuckDuckGoSearchTool(), VisitWebpageTool()] if enable_tools else []23 return CodeAgent(tools=tools, model=model, max_steps=8)24 25# Perfume data and logic26CATALOG = [27 {"name": "Dior Sauvage EDT", "notes": ["bergamot", "ambroxan", "pepper"], "season": ["spring", "summer"], "style": "fresh", "price": 95, "gender": "masculine"},28 {"name": "Chanel Bleu de Chanel EDP", "notes": ["citrus", "incense", "cedar"], "season": ["all"], "style": "fresh-woody", "price": 120, "gender": "masculine"},29 {"name": "Creed Aventus", "notes": ["pineapple", "birch", "musk"], "season": ["spring", "summer", "fall"], "style": "fruity-woody", "price": 365, "gender": "masculine"},30 {"name": "Maison Francis Kurkdjian Baccarat Rouge 540", "notes": ["saffron", "ambergris", "cedar"], "season": ["fall", "winter"], "style": "ambery", "price": 325, "gender": "unisex"},31 {"name": "Le Labo Santal 33", "notes": ["sandalwood", "leather", "violet"], "season": ["fall", "winter", "spring"], "style": "woody", "price": 220, "gender": "unisex"},32 {"name": "Tom Ford Black Orchid", "notes": ["truffle", "patchouli", "chocolate"], "season": ["fall", "winter", "night"], "style": "gourmand", "price": 160, "gender": "unisex"},33 {"name": "Byredo Gypsy Water", "notes": ["juniper", "lemon", "vanilla"], "season": ["spring", "summer"], "style": "fresh-woody", "price": 205, "gender": "unisex"},34 {"name": "Chanel Chance Eau Tendre", "notes": ["grapefruit", "jasmine", "musk"], "season": ["spring", "summer"], "style": "fresh-floral", "price": 120, "gender": "feminine"},35 {"name": "YSL Libre EDP", "notes": ["lavender", "orange blossom", "vanilla"], "season": ["fall", "winter"], "style": "floral-amber", "price": 130, "gender": "feminine"},36 {"name": "Jo Malone Wood Sage & Sea Salt", "notes": ["sage", "ambrette", "sea salt"], "season": ["summer", "spring"], "style": "fresh-aromatic", "price": 165, "gender": "unisex"},37]38 39def shortlist_catalog(preferred_notes, season, budget, gender_pref):40 notes = [n.strip().lower() for n in preferred_notes.split(",") if n.strip()] if preferred_notes else []41 season = (season or "all").lower()42 gender_pref = (gender_pref or "unisex").lower()43 max_price = float(budget) if budget else 9999.044 45 def score(item):46 s = 047 if season in item["season"] or "all" in item["season"]:48 s += 149 if gender_pref in ["any", "unisex"] or gender_pref == item["gender"]:50 s += 151 s += sum(1 for n in notes if n in item["notes"])52 if item["price"] <= max_price:53 s += 154 return s55 56 ranked = sorted(CATALOG, key=score, reverse=True)57 top = [p for p in ranked if score(p) > 0][:5]58 return top or ranked[:3]59 60def recommend_perfumes(preferred_notes, season, occasion, budget, gender_pref, use_web_tools):61 agent = build_agent(enable_tools=use_web_tools)62 shortlist = shortlist_catalog(preferred_notes, season, budget, gender_pref)63 64 prompt = (65 "Recommend three perfumes, ranked 1 to 3.\n"66 f"Preferred notes: {preferred_notes or 'not specified'}\n"67 f"Season: {season or 'any'}\n"68 f"Occasion: {occasion or 'any'}\n"69 f"Budget cap (USD): {budget or 'no cap'}\n"70 f"Gender preference: {gender_pref or 'any'}\n\n"71 "Candidates you can consider:\n"72 f"{json.dumps(shortlist, indent=2)}\n\n"73 "Return a short list with brand and perfume name, brief notes and vibe, why it fits, and approx price or where to sample. Keep it under 120 words."74 )75 76 try:77 return str(agent.run(prompt))78 except Exception as e:79 lines = [f"{i+1}. {p['name']} — ${p['price']} • {', '.join(p['notes'])}" for i, p in enumerate(shortlist[:3])]80 return "Local shortlist:\n" + "\n".join(lines) + f"\nError: {e}"81 82# GAIA test83GAIA_BASE = "https://agents-course-unit4-scoring.hf.space"84 85def fetch_random_question():86 r = requests.get(f"{GAIA_BASE}/random-question", timeout=15)87 r.raise_for_status()88 return r.json()89 90def _check_match(agent_answer, expected_answer):91 a = str(agent_answer).strip().lower()92 e = str(expected_answer).strip().lower()93 if a == e:94 return "Exact match"95 if e and e in a:96 return "Partial match"97 return "No match"98 99def run_agent_on_gaia():100 try:101 q = fetch_random_question()102 except Exception as e:103 return f"Error fetching GAIA question: {e}"104 105 question_text = q.get("question") or ""106 expected_answer = q.get("final_answer") or ""107 108 try:109 agent = build_agent(enable_tools=True)110 agent_answer = str(agent.run(question_text))111 except Exception as e:112 agent_answer = f"Error running agent: {e}"113 114 return (115 "### Question\n"116 f"{question_text}\n\n"117 "### Agent answer\n"118 f"{agent_answer}\n\n"119 "### Expected answer\n"120 f"{expected_answer}\n\n"121 "### Match\n"122 f"{_check_match(agent_answer, expected_answer)}"123 )124 125# Diagnostics126def net_diagnostics():127 results = {}128 hosts = {129 "gaia": "agents-course-unit4-scoring.hf.space",130 "huggingface": "huggingface.co",131 "google": "www.google.com",132 }133 for name, host in hosts.items():134 try:135 ip = socket.gethostbyname(host)136 results[f"dns_{name}"] = f"OK {host} -> {ip}"137 except Exception as e:138 results[f"dns_{name}"] = f"FAIL {host} -> {e}"139 140 urls = {141 "gaia_random_question": f"{GAIA_BASE}/random-question",142 "hf_home": "https://huggingface.co",143 }144 for key, url in urls.items():145 try:146 r = requests.get(url, timeout=10)147 results[f"http_{key}"] = f"{r.status_code} {len(r.content)} bytes"148 except Exception as e:149 results[f"http_{key}"] = f"FAIL {e}"150 151 return "```\n" + json.dumps(results, indent=2) + "\n```"152 153# UI154with gr.Blocks(title="Perfume Recommender and GAIA Test") as demo:155 with gr.Tab("Perfume"):156 with gr.Row():157 with gr.Column():158 notes = gr.Textbox(label="Preferred notes (comma separated)")159 season = gr.Dropdown(label="Season", choices=["any","spring","summer","fall","winter","night"], value="any")160 occasion = gr.Dropdown(label="Occasion", choices=["any","work","date","party","gym","formal"], value="any")161 budget = gr.Textbox(label="Budget cap in USD")162 gender = gr.Dropdown(label="Gender preference", choices=["any","masculine","feminine","unisex"], value="any")163 use_tools = gr.Checkbox(label="Use web tools", value=False)164 go = gr.Button("Recommend")165 with gr.Column():166 rec_out = gr.Markdown()167 go.click(recommend_perfumes, [notes, season, occasion, budget, gender, use_tools], [rec_out], show_progress="full")168 169 with gr.Tab("GAIA"):170 gaia_btn = gr.Button("Get random question and answer")171 gaia_out = gr.Markdown()172 gaia_btn.click(run_agent_on_gaia, None, [gaia_out], show_progress="full")173 174 with gr.Tab("Diagnostics"):175 diag_btn = gr.Button("Network diagnostics")176 diag_out = gr.Markdown()177 diag_btn.click(net_diagnostics, None, [diag_out], show_progress="minimal")178 179demo.queue().launch()180 