Spaces:
Sleeping
Sleeping
File size: 4,635 Bytes
6380b21 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
import json
from typing import List, Dict, Optional, Tuple
import google.generativeai as genai
from .config import get_gemini_key
from .utils import is_iata_code
# Initialize once
_gen_inited = False
def _ensure_init():
global _gen_inited
if not _gen_inited:
genai.configure(api_key=get_gemini_key())
_gen_inited = True
def _model():
_ensure_init()
return genai.GenerativeModel(
model_name="gemini-2.0-flash-lite",
generation_config={
"temperature": 0.2,
"top_p": 0.9,
"top_k": 40,
"max_output_tokens": 600,
},
)
def resolve_city_to_iata_ai(city: str, country_hint: Optional[str] = None) -> Tuple[str, str, str]:
"""Use the LLM to resolve a human-readable city to an IATA code.
Returns (code, canonical_name, kind) where kind is 'CITY' or 'AIRPORT'.
Raises RuntimeError on failure.
"""
m = _model()
prompt = f"""
You convert a city name into a 3-letter IATA code for flight search.
Rules:
- If the metro area has multiple major airports with an aggregate IATA city code, return the CITY code (e.g., New York→NYC, London→LON, Paris→PAR, Tokyo→TYO).
- If the city typically uses a single primary commercial airport, return that AIRPORT code (e.g., Dubai→DXB, Doha→DOH, Lahore→LHE, Karachi→KHI, Istanbul→IST).
- Return empty code "" if no suitable airport exists.
- The code MUST be exactly 3 uppercase letters A–Z.
- Respond with ONLY a compact JSON object, no additional text, using this schema:
{{"code":"XXX","name":"Canonical City or Airport Name","kind":"CITY|AIRPORT","alternates":["AAA","BBB"]}}
Input:
- city: {city}
- country_hint: {country_hint or 'unknown'}
"""
resp = m.generate_content(prompt)
text = (resp.text or "").strip()
# Extract JSON block
mjson = None
if "{" in text:
try:
start = text.find("{")
end = text.rfind("}") + 1
mjson = json.loads(text[start:end])
except Exception:
pass
if not mjson or not isinstance(mjson, dict):
raise RuntimeError("AI could not produce a valid JSON mapping for the city.")
code = str(mjson.get("code", "")).strip().upper()
name = str(mjson.get("name", city)).strip()
kind = str(mjson.get("kind", "")).strip().upper()
if not is_iata_code(code):
raise RuntimeError(f"AI returned an invalid IATA code: {code}")
if kind not in ("CITY", "AIRPORT"):
kind = "AIRPORT"
return code, name, kind
def rank_accommodations(accommodations: List[Dict], prefs: str = "") -> List[Dict]:
"""Add an 'llm_score' to each accommodation and sort by it."""
if not accommodations:
return []
m = _model()
lines = "\n".join(
[
f"- {a.get('name','(no name)')} | rate:{a.get('rate')} | dist:{int(a.get('dist',0))}m | kinds:{a.get('kinds','')}"
for a in accommodations[:50]
]
)
prompt = f"""Rank the following accommodations for a tourist trip. Prefer central, well-rated options.
User preferences: {prefs or 'not specified'}.
Return a JSON array of objects with 'name' and an integer 'score' 1-100.
Items:
{lines}
"""
resp = m.generate_content(prompt)
try:
text = resp.text or ""
if "```json" in text:
data = json.loads(text.split("```json")[-1].split("```")[0])
else:
data = json.loads(text)
except Exception:
data = []
score_map = {d.get("name", ""): int(d.get("score", 50)) for d in data if isinstance(d, dict)}
for a in accommodations:
a["llm_score"] = score_map.get(a.get("name", ""), 50)
return sorted(accommodations, key=lambda x: x.get("llm_score", 50), reverse=True)
def generate_itinerary(
city: str,
start_date: str,
days: int,
selected_attractions: List[Dict],
selected_stay: Dict | None,
weather_summary: str,
) -> str:
m = _model()
attractions_text = "\n".join([f"- {a.get('name')} ({a.get('kinds','')})" for a in selected_attractions])
hotel_text = selected_stay.get("name") if selected_stay else "TBD"
prompt = f"""
Create a practical, day-by-day itinerary for a trip.
City: {city}
Start Date: {start_date}
Days: {days}
Hotel: {hotel_text}
Weather (summary): {weather_summary}
Attractions to consider:
{attractions_text}
Constraints: group nearby sights, account for weather, add meal/time suggestions, include commute notes.
Return Markdown with sections Day 1, Day 2, ..., and a brief daily plan (morning/afternoon/evening).
"""
resp = m.generate_content(prompt)
return resp.text
|