from huggingface_hub import snapshot_download import os HF_TOKEN = os.environ.get("HF_TOKEN") print("Downloading MuRIL model...") muril_path = snapshot_download( repo_id="nitz0219/bargainai-muril", repo_type="model", token=HF_TOKEN ) print("MuRIL ready!") print("Downloading BERT model...") bert_path = snapshot_download( repo_id="nitz0219/bargainai-bert", repo_type="model", token=HF_TOKEN ) print("BERT ready!") index_path = os.path.join(muril_path, "index") print(f"Index path: {index_path}") print(f"Index files: {os.listdir(index_path)}") import gradio as gr import json import numpy as np import torch import anthropic from transformers import AutoTokenizer, AutoModel, AutoModelForSequenceClassification class BargainingAgent: def __init__(self): self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") print(f"Device: {self.device}") print("Loading India model (MuRIL)...") self.muril_embeddings = np.load(os.path.join(index_path, "muril_finetuned_embeddings.npy")) with open(os.path.join(index_path, "muril_finetuned_metadata.json")) as f: self.muril_metadata = json.load(f) with open(os.path.join(muril_path, "label_map.json")) as f: lm = json.load(f) self.muril_id_to_label = {v: k for k, v in lm.items()} self.muril_tokenizer = AutoTokenizer.from_pretrained(muril_path) self.muril_classifier = AutoModelForSequenceClassification.from_pretrained(muril_path).to(self.device) self.muril_classifier.eval() self.muril_embed = AutoModel.from_pretrained(muril_path).to(self.device) self.muril_embed.eval() print("MuRIL loaded!") print("Loading Global model (BERT)...") self.bert_embeddings = np.load(os.path.join(index_path, "bert_finetuned_embeddings.npy")) with open(os.path.join(index_path, "bert_finetuned_metadata.json")) as f: self.bert_metadata = json.load(f) with open(os.path.join(bert_path, "label_map.json")) as f: lm2 = json.load(f) self.bert_id_to_label = {v: k for k, v in lm2.items()} self.bert_tokenizer = AutoTokenizer.from_pretrained(bert_path) self.bert_classifier = AutoModelForSequenceClassification.from_pretrained(bert_path).to(self.device) self.bert_classifier.eval() self.bert_embed = AutoModel.from_pretrained(bert_path).to(self.device) self.bert_embed.eval() print("BERT loaded!") self.claude = anthropic.Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY")) print("Both models ready!") def classify_intent(self, text, market): tokenizer = self.muril_tokenizer if market == "india" else self.bert_tokenizer classifier = self.muril_classifier if market == "india" else self.bert_classifier id_to_label = self.muril_id_to_label if market == "india" else self.bert_id_to_label enc = tokenizer(text, truncation=True, padding=True, max_length=128, return_tensors="pt").to(self.device) with torch.no_grad(): out = classifier(**enc) probs = torch.softmax(out.logits, dim=1) prob, idx = torch.max(probs, dim=1) return id_to_label[idx.item()], prob.item() def get_embedding(self, text, market): tokenizer = self.muril_tokenizer if market == "india" else self.bert_tokenizer embed_model = self.muril_embed if market == "india" else self.bert_embed enc = tokenizer(text, truncation=True, padding=True, max_length=128, return_tensors="pt").to(self.device) with torch.no_grad(): out = embed_model(**enc) emb = out[0].mean(dim=1) return torch.nn.functional.normalize(emb, p=2, dim=1).cpu().numpy() def retrieve_tactic(self, query, market, pillar=None): embeddings = self.muril_embeddings if market == "india" else self.bert_embeddings metadata = self.muril_metadata if market == "india" else self.bert_metadata qe = self.get_embedding(query, market) scores = np.dot(embeddings, qe.T).flatten() if pillar: filtered = [i for i, m in enumerate(metadata) if m["pillar"].lower() == pillar.lower()] fs = np.full(len(embeddings), -1.0) for i in filtered: fs[i] = scores[i] top = np.argsort(fs)[::-1][:2] else: top = np.argsort(scores)[::-1][:2] return metadata[top[0]]["text"][:300], metadata[top[0]]["book"], metadata[top[0]]["page_number"] def get_pillar(self, intent): return { "price_objection": "Negotiation", "walkaway_threat": "Negotiation", "competitor_comparison": "Sales", "quality_doubt": "Persuasion", "ready_to_buy": "Sales", "guilt_pressure": "Power", "urgent_buyer": "Sales", "value_seeker": "Persuasion", "trust_issue": "Persuasion", "neutral": "Negotiation" }.get(intent, "Negotiation") def next_price(self, intent, current, floor): drops = {"price_objection": 20, "walkaway_threat": 30, "ready_to_buy": 0} drop = drops.get(intent, 10) return max(current - drop, floor) def respond(self, customer_msg, current_offer, floor_price, mrp, product_name, market, gender, history): intent, conf = self.classify_intent(customer_msg, market) pillar = self.get_pillar(intent) tactic, book, page = self.retrieve_tactic(customer_msg, market, pillar) next_offer = self.next_price(intent, current_offer, floor_price) at_floor = next_offer == floor_price if market == "india": if gender == "Female": address = "Didi" tone = "warm, sisterly, lightly playful" example = "Arre Didi, aapki choice ekdum amazing hai! Rs.820 final kar deti hoon 😊" elif gender == "Male": address = "Bhaiya" tone = "friendly, brotherly, light humor" example = "Bhaiya aapki nazar sahi jagah padi! Rs.820 mein le jao 😄" else: address = "Aap" tone = "warm, respectful, light humor" example = "Aapke liye Rs.820 final kar deta hoon 😊" language_instruction = f"""Respond in natural Hinglish (Hindi + English mix). Address customer as {address}. Tone: {tone} - Add light humor naturally - Praise customer choice genuinely - 2-3 sentences max - 1-2 emojis only Example: '{example}'""" else: language_instruction = """Respond in natural conversational English. Warm, friendly, slightly playful. Add light compliment. 2-3 sentences max. Example: 'Great taste! Rs.820 is our best price today 😄'""" prompt = f"""You are an experienced shopkeeper negotiating on WhatsApp for {product_name}. Customer said: "{customer_msg}" Customer intent: {intent} Negotiation tactic from {book} Page {page}: {tactic} Price: Previous Rs.{current_offer} to New offer Rs.{next_offer} {'This is FINAL price. Do not go lower.' if at_floor else 'Can negotiate slightly more if needed.'} {language_instruction} Write only the WhatsApp reply.""" message = self.claude.messages.create( model="claude-haiku-4-5-20251001", max_tokens=180, messages=[{"role": "user", "content": prompt}] ) response = message.content[0].text.strip() info = f"Intent: {intent} ({conf:.0%}) | Source: {book[:35]}... Page {page} | Offer: Rs.{current_offer} to Rs.{next_offer}" history.append({"role": "user", "content": customer_msg}) history.append({"role": "assistant", "content": response}) return history, info, next_offer, "" print("Loading agent...") agent = BargainingAgent() print("Agent ready!") CSS = """ @import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500&display=swap'); body, .gradio-container { font-family: 'DM Sans', sans-serif !important; background: #0a1628 !important; color: #e2e8f0 !important; } .stack-info { background: #0f1f35; border: 0.5px solid #1e3a5f; border-radius: 8px; padding: 1rem; margin-top: 1rem; font-size: 0.78rem; color: #475569; line-height: 2; } .footer-note { text-align: center; color: #1e3a5f; font-size: 0.75rem; padding: 1rem; margin-top: 1rem; border-top: 0.5px solid #1e3a5f; } """ def chat(message, history, current_offer, floor_price, mrp, product_name, market, gender): if not message.strip(): return history, "", current_offer, "" market_key = "india" if market == "India (Hinglish)" else "global" history, info, new_offer, _ = agent.respond( message, int(current_offer), int(floor_price), int(mrp), product_name, market_key, gender, history ) return history, info, new_offer, "" def reset_chat(mrp): starting = int(float(mrp) * 0.94) return [], "Configure your product and start negotiating...", starting def update_price(x): return x def set_q1(): return "bahut mehnga hai bhaiya" def set_q2(): return "quality acchi nahi lagti" def set_q3(): return "Amazon pe sasta milega" def set_q4(): return "final price kya hai" def set_q5(): return "This is too expensive" def set_q6(): return "I can find it cheaper elsewhere" def set_q7(): return "Can you do better on price?" def set_q8(): return "Ok I will take it" with gr.Blocks(title="BargainAI", css=CSS) as demo: gr.HTML("""