from features.base_feature import BaseFeature from tools.tavily_search import TavilySearchTool from tools.calculator import CalculatorTool from urllib.parse import quote as _url_quote _tavily = TavilySearchTool() _calc = CalculatorTool() class CollaborativeFeature(BaseFeature): async def get_context(self, preferences_a: dict, preferences_b: dict, user_a_id: int = None, user_b_id: int = None) -> str: """ Use Tavily to fetch REAL restaurant/activity/venue options matching both parties' preferences. Inject real names so agents cite real places. """ raw_a = preferences_a.get("raw_details", {}) raw_b = preferences_b.get("raw_details", {}) decision_type = ( raw_a.get("decision_type") or raw_b.get("decision_type") or preferences_a.get("goal", "") ) location = ( raw_a.get("location") or raw_b.get("location") or raw_a.get("city") or raw_b.get("city") or "Mumbai" ) cuisine_a = raw_a.get("cuisine") or raw_a.get("food_preference") or "" cuisine_b = raw_b.get("cuisine") or raw_b.get("food_preference") or "" budget_a = raw_a.get("budget") or raw_a.get("budget_per_person") or "" budget_b = raw_b.get("budget") or raw_b.get("budget_per_person") or "" # Build a smart Tavily query cuisine_part = f"{cuisine_a} or {cuisine_b}" if cuisine_a and cuisine_b else (cuisine_a or cuisine_b or "good") query = f"best {cuisine_part} restaurants in {location}" tavily_text = "" try: result = await _tavily.execute(query) answer = result.get("answer", "") results = result.get("results", [])[:4] place_lines = [] if answer: place_lines.append(f"AI Summary: {answer[:300]}") for r in results: title = r.get("title", "") content = r.get("content", "")[:150] if title: place_lines.append(f" • {title}: {content}") tavily_text = "\n".join(place_lines) except Exception as e: tavily_text = f"Search unavailable ({e}). Use your knowledge of {location} restaurants." lines = [ "COLLABORATIVE DECISION DOMAIN RULES:", "• ONLY recommend real venues from the search results below. Do NOT invent names.", "• Budget ceiling = the LOWER of both parties' budgets.", "• Both parties' dietary restrictions are absolute (hard constraints).", "• Aim for cuisine intersection first; if no overlap, find a multi-cuisine option.", "", f"Current decision type: {decision_type}", f"Location: {location}", ] if cuisine_a: lines.append(f"Person A prefers: {cuisine_a}") if cuisine_b: lines.append(f"Person B prefers: {cuisine_b}") if budget_a: lines.append(f"Person A budget: ₹{budget_a}/person") if budget_b: lines.append(f"Person B budget: ₹{budget_b}/person") if tavily_text: lines.append(f"\nREAL OPTIONS from web search (use these):\n{tavily_text}") return "\n".join(lines) def format_resolution( self, resolution: dict, preferences_a: dict, preferences_b: dict ) -> str: status = resolution.get("status", "resolved") final = resolution.get("final_proposal", {}) details = final.get("details", {}) rounds = resolution.get("rounds_taken", "?") summary = resolution.get("summary", "") if status == "escalated": return ( f"⚠️ *Joint Decision — Your Input Needed*\n\n" f"_{summary}_\n\n" f"Agents proposed options but couldn't finalize. " f"Please pick from the options above." ) venue = ( details.get("venue") or details.get("restaurant") or details.get("place") or details.get("recommendation") or final.get("summary", "") ) cuisine = details.get("cuisine") or details.get("food_type") or "" price = details.get("price_range") or details.get("budget") or "" why = details.get("reason") or details.get("why") or summary alternatives = details.get("alternatives") or [] lines = ["🍽 *Decision Made!*\n"] if venue: venue_str = str(venue) if not isinstance(venue, str) else venue maps_url = f"https://maps.google.com/?q={_url_quote(venue_str)}" lines.append(f"📍 *Recommendation:* [{venue_str}]({maps_url})") if cuisine: lines.append(f"🍴 *Cuisine:* {cuisine}") if price: lines.append(f"💰 *Price range:* {price}") if why: lines.append(f"\n💬 _{why}_") if alternatives and isinstance(alternatives, list): alt_text = ", ".join(str(a) for a in alternatives[:2]) lines.append(f"\n_Alternatives considered: {alt_text}_") lines.append(f"\n⏱ Decided in {rounds} round(s)") return "\n".join(lines)