agarwalamit081's picture
Update app.py
0633293 verified
#!/usr/bin/env python
# coding=utf-8
"""
Travel Catalogue Creator - AI Agent for Personalized Travel Planning
Creates comprehensive travel guides with weather, itineraries, packing lists & images
"""
import os
import re
import json
import requests
import yaml
from typing import Optional
from smolagents import CodeAgent, DuckDuckGoSearchTool, HfApiModel, tool
from Gradio_UI import GradioUI
# ======================
# TRAVEL TOOLS (SMOLAGENTS-COMPLIANT)
# ======================
@tool
def get_weather_forecast(location: str, travel_dates: str) -> str:
"""
Get weather forecast for destination using wttr.in API.
Args:
location: Destination city name (e.g., "Barcelona")
travel_dates: Travel date range string (e.g., "October 15-19")
Returns:
Weather summary string with temperature and packing recommendations
"""
try:
# Clean location name for API
clean = re.sub(r"[^a-zA-Z0-9\s]", "", location).replace(" ", "+")
resp = requests.get(f"http://wttr.in/{clean}?format=j1", timeout=10)
resp.raise_for_status()
data = resp.json()
cur = data["current_condition"][0]
temp = int(cur["temp_C"])
cond = cur["lang_en"][0]["value"].lower()
# Generate packing recommendations based on temperature
if temp > 25:
pack = "Light clothing, shorts, breathable fabrics, sunscreen, hat"
elif temp > 15:
pack = "Light layers, long sleeves, light jacket"
else:
pack = "Warm layers, jacket, beanie recommended"
# Add rain gear if needed
if "rain" in cond or "shower" in cond:
pack += " + compact umbrella + waterproof jacket"
return f"{location} forecast ({travel_dates}): {temp}°C, {cond}. Packing: {pack}"
except Exception as e:
# Fallback to generic forecast
return f"{location} typical weather: 15-25°C. Pack versatile layers, light jacket, and compact umbrella."
@tool
def convert_currency(amount: float, from_currency: str, to_currency: str) -> str:
"""
Convert currency amount using Frankfurter API (no API key required).
Args:
amount: Numeric amount to convert (e.g., 1200.0)
from_currency: Source currency ISO code (e.g., "USD")
to_currency: Target currency ISO code (e.g., "EUR")
Returns:
Formatted string showing conversion result and exchange rate
"""
try:
from_curr = from_currency.upper()[:3]
to_curr = to_currency.upper()[:3]
# Handle same currency
if from_curr == to_curr:
return f"{amount:,.0f} {from_curr} = {amount:,.0f} {to_curr} (1 {from_curr} = 1.00 {to_curr})"
# Get exchange rate
resp = requests.get(
f"https://api.frankfurter.app/latest",
params={"from": from_curr, "to": to_curr},
timeout=10
)
resp.raise_for_status()
rate = resp.json()["rates"][to_curr]
converted = amount * rate
return f"{amount:,.0f} {from_curr} = {converted:,.0f} {to_curr} (1 {from_curr} = {rate:.2f} {to_curr})"
except Exception as e:
# Fallback to showing amount without conversion
return f"{amount:,.0f} {from_currency} = {amount:,.0f} {to_currency} (rate unavailable)"
@tool
def get_time_difference(origin_city: str, destination_city: str) -> str:
"""
Calculate time difference between origin and destination cities using heuristic mapping.
Args:
origin_city: Traveler's home city name (e.g., "New York")
destination_city: Travel destination city name (e.g., "Paris")
Returns:
Human-readable string describing time difference and jet lag advice
"""
# Extended timezone mapping (UTC offsets)
city_tz = {
"new york": -5, "london": 0, "paris": 1, "tokyo": 9, "sydney": 10,
"los angeles": -8, "chicago": -6, "mumbai": 5.5, "dubai": 4,
"singapore": 8, "berlin": 1, "rome": 1, "barcelona": 1, "madrid": 1,
"amsterdam": 1, "vienna": 1, "prague": 1, "budapest": 1, "warsaw": 1,
"stockholm": 1, "oslo": 1, "copenhagen": 1, "helsinki": 2, "athens": 2,
"istanbul": 3, "cairo": 2, "bangkok": 7, "seoul": 9, "beijing": 8,
"shanghai": 8, "hong kong": 8, "manila": 8, "kuala lumpur": 8,
"jakarta": 7, "delhi": 5.5, "rio de janeiro": -3, "sao paulo": -3,
"buenos aires": -3, "mexico city": -6, "toronto": -5, "vancouver": -8,
"lisbon": 0, "porto": 0, "brussels": 1, "zurich": 1, "geneva": 1,
"milan": 1, "florence": 1, "venice": 1, "naples": 1, "dublin": 0,
"edinburgh": 0, "glasgow": 0, "manchester": 0, "birmingham": 0,
"krakow": 1, "gdansk": 1, "wroclaw": 1, "riga": 2, "vilnius": 2,
"tallinn": 2, "sofia": 2, "bucharest": 2, "zagreb": 1, "ljubljana": 1,
"belgrade": 1, "sarajevo": 1, "nicosia": 2, "valletta": 1, "reykjavik": 0,
}
origin_key = origin_city.lower().strip()
dest_key = destination_city.lower().strip()
origin_tz = city_tz.get(origin_key, 0)
dest_tz = city_tz.get(dest_key, 0)
diff = dest_tz - origin_tz
if diff == 0:
return f"No time difference between {origin_city} and {destination_city}."
elif diff > 0:
return f"{destination_city} is {diff} hours ahead of {origin_city}. Prepare for eastward jet lag (may cause fatigue)."
else:
return f"{destination_city} is {abs(diff)} hours behind {origin_city}. Prepare for westward jet lag (may cause insomnia)."
@tool
def generate_packing_list(destination: str, weather_summary: str, trip_days: int, trip_type: str) -> str:
"""
Generate customized packing checklist based on destination, weather, duration and trip type.
Args:
destination: Destination city or region name (e.g., "Barcelona")
weather_summary: Weather forecast string containing temperature and conditions
trip_days: Total number of travel days (integer >= 1)
trip_type: Type of trip: "city", "beach", "mountain", or "mixed"
Returns:
Formatted multi-section packing checklist as string
"""
weather_lower = weather_summary.lower()
# Analyze weather conditions
cold = any(w in weather_lower for w in ["cold", "cool", "10°c", "11°c", "12°c", "13°c", "14°c", "jacket", "below 15"])
rain = any(w in weather_lower for w in ["rain", "shower", "drizzle", "umbrella", "precipitation"])
hot = any(w in weather_lower for w in ["hot", "warm", "25°c", "26°c", "27°c", "28°c", "29°c", "30°c", "above 25"])
# Calculate clothing quantities
tops = min(trip_days, trip_days // 2 + 2)
bottoms = min(trip_days // 2 + 1, 4)
# Build clothing list
clothes = [f"• Tops ({tops})", f"• Bottoms ({bottoms})"]
if cold:
clothes.extend([
"• Warm jacket/coat",
"• Long underwear (if very cold)",
"• Warm socks (x3)"
])
elif hot:
clothes.extend([
"• Light breathable fabrics",
"• Sun hat",
"• Sunglasses",
"• Reef-safe sunscreen (SPF 50+)"
])
else:
clothes.append("• Light jacket or sweater (essential)")
if rain:
clothes.extend([
"• Compact travel umbrella",
"• Quick-dry clothing",
"• Waterproof shoes or sandals"
])
# Add trip-type specific items
if trip_type == "beach":
clothes.extend([
"• Swimsuit (x2)",
"• Beach towel",
"• Flip-flops/sandals",
"• Beach bag"
])
elif trip_type == "mountain":
clothes.extend([
"• Sturdy hiking shoes",
"• Moisture-wicking base layers",
"• Trekking socks (x3)",
"• Daypack"
])
return (
f"🎒 SMART PACKING LIST ({trip_days}-day {trip_type} trip to {destination})\n"
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
"ESSENTIALS\n"
"• Passport + photocopies + digital scans in cloud\n"
"• Credit/debit cards + small amount of local currency\n"
"• Universal power adapter\n"
"• Phone + portable charger (10,000mAh+) + cables\n"
"• Travel insurance documents (digital + physical)\n"
"\nCLOTHING\n" + "\n".join(clothes) + "\n"
"\nHEALTH & HYGIENE\n"
"• Prescription medications (in original packaging + doctor's note)\n"
"• Basic first-aid kit (bandages, antiseptic, pain relievers)\n"
"• Hand sanitizer (60ml travel size)\n"
"• Travel-sized toiletries (toothbrush, toothpaste, deodorant)\n"
"\n💡 Pro tip: Roll clothes to save space. Pack one versatile outfit per day + 1 extra day buffer."
)
@tool
def build_itinerary(destination: str, attractions: str, budget_local: float, days: int) -> str:
"""
Create realistic day-by-day travel itinerary with time allocations and budget guidance.
Args:
destination: Destination city name (e.g., "Barcelona")
attractions: Comma-separated list of attraction names (e.g., "Sagrada Familia, Park Guell")
budget_local: Daily budget in local currency (float)
days: Number of full travel days (integer >= 1)
Returns:
Formatted multi-day itinerary with time slots and daily budget allocation
"""
# Parse attractions list
att_list = [a.strip() for a in attractions.split(",") if a.strip()]
if not att_list:
att_list = ["Old Town exploration", "Local museum", "Scenic viewpoint", "Local market"]
lines = [
f"🗓️ {days}-DAY REALISTIC ITINERARY: {destination}",
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
]
# Generate day-by-day schedule
for d in range(1, days + 1):
primary = att_list[(d - 1) % len(att_list)]
secondary = att_list[d % len(att_list)] if len(att_list) > 1 else "Leisurely café break"
lines.extend([
f"\nDAY {d} | Budget: ~{budget_local:,.0f} local currency",
f" 09:00 - 12:00 {primary} (arrive early to avoid crowds)",
f" 12:30 - 14:00 Lunch at local spot (budget: ~{budget_local * 0.25:,.0f})",
f" 14:30 - 17:00 {secondary}",
f" 19:00+ Dinner + evening stroll (budget: ~{budget_local * 0.35:,.0f})"
])
lines.append("\n💡 Pro tips: Book major attractions online in advance. Use public transport day passes for 30%+ savings.")
return "\n".join(lines)
@tool
def generate_travel_images(destination: str) -> str:
"""
Generate two travel image URLs using free stock photo services.
Args:
destination: Destination city name (e.g., "Lisbon")
Returns:
JSON-formatted string containing two image URLs with keys "landmark_image" and "street_scene_image"
"""
# Map popular destinations to actual quality travel photos
# Using Pixabay's CDN which hosts free travel images
destination_lower = destination.lower().strip()
# High-quality travel images for popular destinations
image_map = {
"barcelona": {
"landmark": "https://cdn.pixabay.com/photo/2017/01/31/21/23/architecture-2025080_960_720.jpg",
"street": "https://cdn.pixabay.com/photo/2016/11/14/04/45/elephant-1822636_960_720.jpg"
},
"paris": {
"landmark": "https://cdn.pixabay.com/photo/2015/10/06/18/26/eiffel-tower-975004_960_720.jpg",
"street": "https://cdn.pixabay.com/photo/2016/11/18/15/44/paris-1835941_960_720.jpg"
},
"london": {
"landmark": "https://cdn.pixabay.com/photo/2014/11/13/23/34/london-530055_960_720.jpg",
"street": "https://cdn.pixabay.com/photo/2016/11/29/04/19/bridge-1867744_960_720.jpg"
},
"rome": {
"landmark": "https://cdn.pixabay.com/photo/2016/11/14/05/21/rome-1822559_960_720.jpg",
"street": "https://cdn.pixabay.com/photo/2016/03/02/20/13/rome-1232437_960_720.jpg"
},
"tokyo": {
"landmark": "https://cdn.pixabay.com/photo/2019/04/20/11/39/japan-4141578_960_720.jpg",
"street": "https://cdn.pixabay.com/photo/2017/01/28/02/24/japan-2014616_960_720.jpg"
},
"new york": {
"landmark": "https://cdn.pixabay.com/photo/2017/06/07/15/47/new-york-city-2380683_960_720.jpg",
"street": "https://cdn.pixabay.com/photo/2016/01/19/17/41/statue-of-liberty-1149887_960_720.jpg"
},
"amsterdam": {
"landmark": "https://cdn.pixabay.com/photo/2017/07/31/11/59/canal-2558009_960_720.jpg",
"street": "https://cdn.pixabay.com/photo/2016/11/29/12/13/architecture-1869547_960_720.jpg"
},
"lisbon": {
"landmark": "https://cdn.pixabay.com/photo/2018/11/29/21/19/hamburg-3846525_960_720.jpg",
"street": "https://cdn.pixabay.com/photo/2019/08/28/10/37/portugal-4436951_960_720.jpg"
},
"berlin": {
"landmark": "https://cdn.pixabay.com/photo/2016/11/29/03/37/architecture-1867187_960_720.jpg",
"street": "https://cdn.pixabay.com/photo/2016/02/17/23/03/germany-1206011_960_720.jpg"
},
"madrid": {
"landmark": "https://cdn.pixabay.com/photo/2020/06/15/01/06/architecture-5299558_960_720.jpg",
"street": "https://cdn.pixabay.com/photo/2019/10/20/09/03/madrid-4563537_960_720.jpg"
},
"prague": {
"landmark": "https://cdn.pixabay.com/photo/2016/11/23/15/32/prague-1853890_960_720.jpg",
"street": "https://cdn.pixabay.com/photo/2017/09/05/10/19/prague-2717166_960_720.jpg"
},
"dubai": {
"landmark": "https://cdn.pixabay.com/photo/2016/11/22/23/40/burj-khalifa-1851204_960_720.jpg",
"street": "https://cdn.pixabay.com/photo/2020/02/08/10/37/dubai-4829188_960_720.jpg"
},
"singapore": {
"landmark": "https://cdn.pixabay.com/photo/2016/03/27/21/43/singapore-1284628_960_720.jpg",
"street": "https://cdn.pixabay.com/photo/2017/12/10/17/40/singapore-3010803_960_720.jpg"
},
"istanbul": {
"landmark": "https://cdn.pixabay.com/photo/2018/04/25/09/14/istanbul-3349451_960_720.jpg",
"street": "https://cdn.pixabay.com/photo/2020/06/28/14/20/istanbul-5349952_960_720.jpg"
},
"athens": {
"landmark": "https://cdn.pixabay.com/photo/2018/10/30/16/06/water-3784022_960_720.jpg",
"street": "https://cdn.pixabay.com/photo/2019/09/15/00/10/athens-4477350_960_720.jpg"
},
"vienna": {
"landmark": "https://cdn.pixabay.com/photo/2017/06/28/10/53/schonbrunn-palace-2450019_960_720.jpg",
"street": "https://cdn.pixabay.com/photo/2018/05/30/00/24/thunderstorm-3440450_960_720.jpg"
}
}
# Check if we have specific images for this destination
if destination_lower in image_map:
images = image_map[destination_lower]
return json.dumps({
"landmark_image": images["landmark"],
"street_scene_image": images["street"]
})
# Generic high-quality travel images as fallback
return json.dumps({
"landmark_image": "https://cdn.pixabay.com/photo/2016/11/29/12/13/architecture-1869547_960_720.jpg",
"street_scene_image": "https://cdn.pixabay.com/photo/2016/11/22/19/15/hand-1850120_960_720.jpg"
})
@tool
def assemble_catalogue(
destination: str,
origin: str,
dates: str,
budget_summary: str,
weather: str,
timezone_info: str,
itinerary: str,
packing_list: str,
image_urls_json: str
) -> str:
"""
Compile all travel research into a beautiful, structured Markdown travel catalogue.
Args:
destination: Destination city name (e.g., "Lisbon")
origin: Traveler's home city (e.g., "London")
dates: Travel date range (e.g., "Sep 20-22, 2026")
budget_summary: Formatted budget conversion string
weather: Weather forecast with packing advice
timezone_info: Time difference and jet lag guidance
itinerary: Day-by-day schedule with time allocations
packing_list: Complete customized packing checklist
image_urls_json: JSON string with "landmark_image" and "street_scene_image" URLs
Returns:
Complete Markdown-formatted travel catalogue with embedded images
"""
# Parse image URLs
try:
images = json.loads(image_urls_json)
img1 = images.get("landmark_image", "https://picsum.photos/800/600?random=1")
img2 = images.get("street_scene_image", "https://picsum.photos/800/600?random=2")
except:
img1 = "https://picsum.photos/800/600?random=1"
img2 = "https://picsum.photos/800/600?random=2"
# Count days in itinerary
day_count = len(re.findall(r'DAY\s+\d+', itinerary))
# Build catalogue with proper Gradio-compatible Markdown
catalogue = f"""# 🌍 {destination} Travel Catalogue
*Planned from {origin}{dates}{budget_summary}*
---
## ⏰ Time Zone Adjustment
{timezone_info}
---
## 🌤️ Weather Forecast & Packing Guidance
{weather}
---
## 🗓️ Your Personalized {day_count}-Day Itinerary
{itinerary}
---
## 🎒 Complete Packing Checklist
{packing_list}
---
## 📸 Visual Inspiration
![{destination} Landmark]({img1})
*Iconic {destination} landmark*
![{destination} Street Scene]({img2})
*Vibrant local atmosphere*
---
> 💡 **Travel Pro Tips**
>
> • Download offline Google Maps before departure
> • Learn 5 basic phrases in the local language
> • Keep digital copies of passport/insurance in cloud storage
> • Budget 10-15% extra for spontaneous experiences
> • Public transport passes often save 30%+ vs single tickets
---
**Happy Travels! ✈️**
"""
return catalogue
# ======================
# AGENT SETUP
# ======================
def create_agent():
"""Initialize and return the Travel Catalogue Creator agent"""
print("🚀 Initializing Travel Catalogue Creator...")
# Load system prompt from YAML
with open("prompts.yaml", "r") as f:
prompt_config = yaml.safe_load(f)
# Pre-instantiate DuckDuckGo search tool
web_search = DuckDuckGoSearchTool()
# Configure model with error handling
model = HfApiModel(
max_tokens=2048,
temperature=0.3,
model_id="Qwen/Qwen2.5-Coder-32B-Instruct",
)
# Create agent with all tools
agent = CodeAgent(
model=model,
tools=[
web_search, # Pre-instantiated search tool
get_weather_forecast,
convert_currency,
get_time_difference,
generate_packing_list,
build_itinerary,
generate_travel_images,
assemble_catalogue,
],
max_steps=15,
verbosity_level=1,
name="TravelCatalogueCreator",
description="Creates comprehensive, personalized travel catalogues with weather forecasts, custom itineraries, packing lists and visual inspiration"
)
print("✅ Agent initialized successfully!")
return agent
if __name__ == "__main__":
# Create agent
agent = create_agent()
# Create uploads directory
os.makedirs("./uploads", exist_ok=True)
# Launch Gradio UI
print("🌐 Launching Gradio interface...")
ui = GradioUI(agent, file_upload_folder="./uploads")
ui.launch(share=False, server_name="0.0.0.0", server_port=7860)