Spaces:
Sleeping
Sleeping
Update planner_agent_centralized.py
Browse files- planner_agent_centralized.py +36 -10
planner_agent_centralized.py
CHANGED
|
@@ -2,22 +2,33 @@ import os
|
|
| 2 |
from datetime import datetime, timedelta
|
| 3 |
from typing import Dict, Any, List
|
| 4 |
from urllib.parse import urlencode
|
|
|
|
| 5 |
|
| 6 |
from tavily import TavilyClient
|
| 7 |
|
| 8 |
|
| 9 |
GENERIC_KEYWORDS = [
|
| 10 |
"best", "top", "guide", "things", "visit", "attractions",
|
| 11 |
-
"all you must know", "the 10 best", "the best"
|
| 12 |
]
|
| 13 |
BLOCKED_SOURCES = [
|
| 14 |
-
"reddit", "quora", "tripadvisor", "yelp", "forum", "forums", "community"
|
|
|
|
| 15 |
]
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
|
| 18 |
def is_generic_title(title: str) -> bool:
|
| 19 |
t = title.lower()
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
|
| 23 |
def normalize_title(title: str) -> str:
|
|
@@ -28,6 +39,18 @@ def normalize_title(title: str) -> str:
|
|
| 28 |
return cleaned if cleaned else title
|
| 29 |
|
| 30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
class PlannerAgentCentralized:
|
| 32 |
def __init__(self, tavily_api_key: str | None = None):
|
| 33 |
api_key = tavily_api_key or os.environ.get("TAVILY_API_KEY")
|
|
@@ -40,6 +63,7 @@ class PlannerAgentCentralized:
|
|
| 40 |
departure_date = request.get("departure_date", "")
|
| 41 |
return_date = request.get("return_date", "")
|
| 42 |
preferences = request.get("preferences", [])
|
|
|
|
| 43 |
total_budget = float(request.get("budget", 0) or 0)
|
| 44 |
hotel_budget = float(request.get("hotel_budget", 0) or 0)
|
| 45 |
activity_budget = float(request.get("activity_budget", 0) or 0)
|
|
@@ -49,7 +73,7 @@ class PlannerAgentCentralized:
|
|
| 49 |
activity_budget = round(total_budget * 0.4, 2)
|
| 50 |
|
| 51 |
hotel_names, hotel_reason = self._search_hotel_names(destination)
|
| 52 |
-
poi_items, poi_reason = self._search_pois(destination, preferences)
|
| 53 |
|
| 54 |
if not poi_items:
|
| 55 |
poi_items = self._default_activities(destination)
|
|
@@ -97,7 +121,7 @@ class PlannerAgentCentralized:
|
|
| 97 |
except Exception as e:
|
| 98 |
return [], f"Hotel search failed: {e}"
|
| 99 |
|
| 100 |
-
def _search_pois(self, destination: str, preferences: List[str]) -> tuple[List[Dict[str, Any]], str]:
|
| 101 |
pref_part = (", ".join(preferences)) if preferences else "attractions"
|
| 102 |
query = f"{pref_part} in {destination}"
|
| 103 |
try:
|
|
@@ -111,6 +135,8 @@ class PlannerAgentCentralized:
|
|
| 111 |
if is_generic_title(raw):
|
| 112 |
continue
|
| 113 |
name = normalize_title(raw)
|
|
|
|
|
|
|
| 114 |
if name and not any(x.get("name") == name for x in items):
|
| 115 |
items.append({"name": name, "url": url})
|
| 116 |
reason = f"Collected {len(items)} itinerary items via Tavily for {pref_part}."
|
|
@@ -120,11 +146,11 @@ class PlannerAgentCentralized:
|
|
| 120 |
|
| 121 |
def _default_activities(self, destination: str) -> List[Dict[str, Any]]:
|
| 122 |
return [
|
| 123 |
-
{"name": f"
|
| 124 |
-
{"name": f"
|
| 125 |
-
{"name": f"
|
| 126 |
-
{"name": f"
|
| 127 |
-
{"name": f"
|
| 128 |
]
|
| 129 |
|
| 130 |
def _build_daily_schedule(self, start_date: datetime, num_days: int, items: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
|
|
| 2 |
from datetime import datetime, timedelta
|
| 3 |
from typing import Dict, Any, List
|
| 4 |
from urllib.parse import urlencode
|
| 5 |
+
import re
|
| 6 |
|
| 7 |
from tavily import TavilyClient
|
| 8 |
|
| 9 |
|
| 10 |
GENERIC_KEYWORDS = [
|
| 11 |
"best", "top", "guide", "things", "visit", "attractions",
|
| 12 |
+
"all you must know", "the 10 best", "the best", "how to", "tips"
|
| 13 |
]
|
| 14 |
BLOCKED_SOURCES = [
|
| 15 |
+
"reddit", "quora", "tripadvisor", "yelp", "forum", "forums", "community",
|
| 16 |
+
"wikipedia.org", "wikivoyage.org", "blog", "list of"
|
| 17 |
]
|
| 18 |
+
ARTICLE_PATTERNS = [r"^list of ", r"^how to ", r"^why ", r"^what "]
|
| 19 |
+
|
| 20 |
+
PROPER_NOUN_RE = re.compile(r"\b([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)\b")
|
| 21 |
|
| 22 |
|
| 23 |
def is_generic_title(title: str) -> bool:
|
| 24 |
t = title.lower()
|
| 25 |
+
if any(k in t for k in GENERIC_KEYWORDS):
|
| 26 |
+
return True
|
| 27 |
+
if any(s in t for s in BLOCKED_SOURCES):
|
| 28 |
+
return True
|
| 29 |
+
if any(re.match(p, t) for p in ARTICLE_PATTERNS):
|
| 30 |
+
return True
|
| 31 |
+
return t.startswith("r/")
|
| 32 |
|
| 33 |
|
| 34 |
def normalize_title(title: str) -> str:
|
|
|
|
| 39 |
return cleaned if cleaned else title
|
| 40 |
|
| 41 |
|
| 42 |
+
def actionize(name: str, fallback_category: str | None = None) -> str:
|
| 43 |
+
# Convert generic/statement titles into imperative activity suggestions
|
| 44 |
+
n = name.strip().rstrip(".,! ")
|
| 45 |
+
# If contains a proper noun (probable place), keep it as is
|
| 46 |
+
if PROPER_NOUN_RE.search(n):
|
| 47 |
+
return n
|
| 48 |
+
# If looks like a topic, make it actionable
|
| 49 |
+
if fallback_category:
|
| 50 |
+
return f"Explore {fallback_category.lower()} – {n}" if len(n) < 40 else f"Explore {fallback_category.lower()} in the area"
|
| 51 |
+
return f"Explore: {n}" if len(n) < 40 else "Explore local highlights"
|
| 52 |
+
|
| 53 |
+
|
| 54 |
class PlannerAgentCentralized:
|
| 55 |
def __init__(self, tavily_api_key: str | None = None):
|
| 56 |
api_key = tavily_api_key or os.environ.get("TAVILY_API_KEY")
|
|
|
|
| 63 |
departure_date = request.get("departure_date", "")
|
| 64 |
return_date = request.get("return_date", "")
|
| 65 |
preferences = request.get("preferences", [])
|
| 66 |
+
pref_hint = preferences[0] if preferences else None
|
| 67 |
total_budget = float(request.get("budget", 0) or 0)
|
| 68 |
hotel_budget = float(request.get("hotel_budget", 0) or 0)
|
| 69 |
activity_budget = float(request.get("activity_budget", 0) or 0)
|
|
|
|
| 73 |
activity_budget = round(total_budget * 0.4, 2)
|
| 74 |
|
| 75 |
hotel_names, hotel_reason = self._search_hotel_names(destination)
|
| 76 |
+
poi_items, poi_reason = self._search_pois(destination, preferences, pref_hint)
|
| 77 |
|
| 78 |
if not poi_items:
|
| 79 |
poi_items = self._default_activities(destination)
|
|
|
|
| 121 |
except Exception as e:
|
| 122 |
return [], f"Hotel search failed: {e}"
|
| 123 |
|
| 124 |
+
def _search_pois(self, destination: str, preferences: List[str], pref_hint: str | None) -> tuple[List[Dict[str, Any]], str]:
|
| 125 |
pref_part = (", ".join(preferences)) if preferences else "attractions"
|
| 126 |
query = f"{pref_part} in {destination}"
|
| 127 |
try:
|
|
|
|
| 135 |
if is_generic_title(raw):
|
| 136 |
continue
|
| 137 |
name = normalize_title(raw)
|
| 138 |
+
# Make action-friendly
|
| 139 |
+
name = actionize(name, pref_hint)
|
| 140 |
if name and not any(x.get("name") == name for x in items):
|
| 141 |
items.append({"name": name, "url": url})
|
| 142 |
reason = f"Collected {len(items)} itinerary items via Tavily for {pref_part}."
|
|
|
|
| 146 |
|
| 147 |
def _default_activities(self, destination: str) -> List[Dict[str, Any]]:
|
| 148 |
return [
|
| 149 |
+
{"name": f"Morning walking tour of {destination}"},
|
| 150 |
+
{"name": f"Lunch at a local market in {destination}"},
|
| 151 |
+
{"name": f"Afternoon museum visit in {destination}"},
|
| 152 |
+
{"name": f"Sunset viewpoint in {destination}"},
|
| 153 |
+
{"name": f"Dinner in {destination} city center"}
|
| 154 |
]
|
| 155 |
|
| 156 |
def _build_daily_schedule(self, start_date: datetime, num_days: int, items: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|