Spaces:
Sleeping
Sleeping
| import os | |
| import requests | |
| from src.rag.router import classify_query, extract_filters | |
| from src.rag.retriever import retrieve_faq, load_vector_store | |
| from src.rag.searcher import ( | |
| search_properties, get_leases_expiring, | |
| get_leases_vacant_or_pending | |
| ) | |
| GROQ_MODEL = "llama-3.3-70b-versatile" | |
| SYSTEM_PROMPT = """You are a helpful AI assistant for a resale real estate business in Mumbai, India. | |
| RULES: | |
| - Answer ONLY from the data provided to you in this prompt. | |
| - Never invent prices, features, lease status, or property details. | |
| - If you cannot find the info, say: "I can't find this in our system right now. Please check with our sales team." | |
| - Be friendly, short, and clear. | |
| - Use Indian number formatting: ₹1,20,00,000 (1.2 crore) or ₹85 lakh (₹85,00,000). | |
| - After property listings, always add: "For a site visit or more details, WhatsApp us or contact our sales team." | |
| - After FAQ answers, optionally add: "If you need more info, feel free to ask." | |
| - For manager queries, be factual and structured with tables. | |
| """ | |
| def _fmt_price(price_inr: float) -> str: | |
| """Format price in Indian notation.""" | |
| if price_inr >= 10000000: | |
| return f"₹{price_inr/10000000:.2f} Cr" | |
| else: | |
| return f"₹{price_inr/100000:.1f} L" | |
| def format_properties(props: list[dict]) -> str: | |
| if not props: | |
| return "We don't have any properties matching your filters in current listings." | |
| lines = [] | |
| for i, p in enumerate(props, 1): | |
| lines.append( | |
| f"{i}. **{p['title']}**\n" | |
| f" • {p['bhk']} BHK | {p['property_type']} | {p['area_sqft']} sq ft\n" | |
| f" • Price: {_fmt_price(p['price_inr'])} | Floor: {p['floor']}/{p['total_floors']}\n" | |
| f" • Location: {p['location']}, {p['society']}\n" | |
| f" • Furnishing: {p['furnishing']} | Parking: {p['parking']} | Grade: {p['condition_grade']}\n" | |
| f" • Amenities: {p['amenities']}" | |
| ) | |
| return "\n\n".join(lines) | |
| def format_lease_table(records: list[dict]) -> str: | |
| if not records: | |
| return "No records found for this query." | |
| lines = [ | |
| "| Prop ID | Property | Location | Rent/mo | Status | Lease End | Follow-up |", | |
| "|---------|----------|----------|---------|--------|-----------|-----------|" | |
| ] | |
| for r in records: | |
| rent = f"₹{r['monthly_rent']:,}" if r.get('monthly_rent') else "—" | |
| lease_end = r.get('lease_end') or "—" | |
| lines.append( | |
| f"| {r['property_id']} | {r['title']} ({r['bhk']}BHK) " | |
| f"| {r['location']} | {rent} " | |
| f"| {r['lease_status']} | {lease_end} | {r['followup_person']} |" | |
| ) | |
| return "\n".join(lines) | |
| def call_groq(api_key: str, messages: list) -> str: | |
| url = "https://api.groq.com/openai/v1/chat/completions" | |
| headers = { | |
| "Authorization": f"Bearer {api_key}", | |
| "Content-Type": "application/json" | |
| } | |
| payload = { | |
| "model": GROQ_MODEL, | |
| "messages": messages, | |
| "max_tokens": 1024, | |
| "temperature": 0.3 | |
| } | |
| resp = requests.post(url, json=payload, headers=headers, timeout=30) | |
| if resp.status_code != 200: | |
| raise ValueError(f"Groq API error {resp.status_code}: {resp.text[:200]}") | |
| return resp.json()["choices"][0]["message"]["content"] | |
| def chat(query: str, role: str = "customer", history: list = None) -> dict: | |
| query_type = classify_query(query, role) | |
| context = "" | |
| properties = [] | |
| lease_records = [] | |
| if query_type == "property_filter": | |
| filters = extract_filters(query) | |
| properties = search_properties(**filters) | |
| prop_text = format_properties(properties) | |
| context = f"PROPERTY INVENTORY RESULTS:\n{prop_text}" | |
| elif query_type == "lease" and role == "manager": | |
| q_lower = query.lower() | |
| if "vacant" in q_lower or "expired" in q_lower or "pending" in q_lower: | |
| lease_records = get_leases_vacant_or_pending() | |
| else: | |
| days = 30 | |
| if "15 days" in q_lower: | |
| days = 15 | |
| elif "week" in q_lower: | |
| days = 7 | |
| elif "month" in q_lower or "30 days" in q_lower: | |
| days = 30 | |
| elif "60 days" in q_lower or "2 months" in q_lower: | |
| days = 60 | |
| lease_records = get_leases_expiring(days) | |
| table = format_lease_table(lease_records) | |
| context = f"LEASE DATA:\n{table}" | |
| elif query_type == "faq": | |
| faq_context = retrieve_faq(query) | |
| context = f"FAQ KNOWLEDGE BASE:\n{faq_context}" | |
| # Build messages for Groq (OpenAI-compatible) | |
| messages = [{"role": "system", "content": SYSTEM_PROMPT}] | |
| if history: | |
| for h in history[-6:]: | |
| msg_role = "user" if h["role"] == "user" else "assistant" | |
| messages.append({"role": msg_role, "content": h["content"]}) | |
| user_content = f"CONTEXT:\n{context}\n\nUSER QUERY: {query}" | |
| messages.append({"role": "user", "content": user_content}) | |
| try: | |
| api_key = os.environ.get("GROQ_API_KEY", "").strip() | |
| if not api_key: | |
| raise ValueError( | |
| "GROQ_API_KEY is not set. Go to Space Settings → Repository Secrets and add GROQ_API_KEY." | |
| ) | |
| reply = call_groq(api_key, messages) | |
| except Exception as e: | |
| reply = f"⚠️ AI unavailable: {str(e)[:200]}" | |
| return { | |
| "reply": reply, | |
| "query_type": query_type, | |
| "properties": properties, | |
| "lease_records": lease_records, | |
| } | |