agarwalamit081 commited on
Commit
69c721a
·
verified ·
1 Parent(s): cf395a7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +216 -158
app.py CHANGED
@@ -3,89 +3,62 @@
3
  import os
4
  import re
5
  import requests
6
- from datetime import datetime, timedelta
7
  from typing import Optional
8
  from smolagents import CodeAgent, DuckDuckGoSearchTool, HfApiModel, load_tool, tool
9
  from Gradio_UI import GradioUI
10
 
11
  # ======================
12
- # TRAVEL TOOLS (PRODUCTION-GRADE)
13
  # ======================
14
 
15
  @tool
16
  def get_weather_forecast(location: str, travel_dates: str) -> str:
17
  """
18
- Get weather forecast using OpenWeatherMap (fallback to wttr.in).
 
19
  Args:
20
- location: Destination city (e.g., "Barcelona")
21
- travel_dates: Date range (e.g., "2026-10-15 to 2026-10-19")
 
22
  Returns:
23
- Weather summary with packing advice
24
  """
25
- api_key = os.environ.get("OPENWEATHER_API_KEY")
26
  try:
27
- # Parse dates to get start date
28
- start_date = re.search(r"(\d{4}-\d{2}-\d{2})", travel_dates)
29
- if not start_date:
30
- raise ValueError("Invalid date format")
31
-
32
- # Try OpenWeatherMap first (more reliable)
33
- if api_key:
34
- city = re.sub(r"[^a-zA-Z\s]", "", location).strip()
35
- resp = requests.get(
36
- f"https://api.openweathermap.org/data/2.5/forecast",
37
- params={"q": city, "appid": api_key, "units": "metric"},
38
- timeout=10
39
- )
40
- if resp.status_code == 200:
41
- data = resp.json()
42
- day1 = data["list"][0]
43
- temp = day1["main"]["temp"]
44
- cond = day1["weather"][0]["description"]
45
- rain = "rain" in cond.lower() or day1.get("rain", {}).get("3h", 0) > 1
46
- return _generate_packing_advice(location, temp, cond, rain)
47
-
48
- # Fallback to wttr.in
49
  clean = re.sub(r"[^a-zA-Z0-9\s]", "", location).replace(" ", "+")
50
  resp = requests.get(f"http://wttr.in/{clean}?format=j1", timeout=10)
51
- if resp.status_code == 200:
52
- data = resp.json()
53
- cur = data["current_condition"][0]
54
- temp = int(cur["temp_C"])
55
- cond = cur["lang_en"][0]["value"]
56
- rain = "rain" in cond.lower()
57
- return _generate_packing_advice(location, temp, cond, rain)
58
-
 
 
 
 
 
 
 
 
 
59
  except Exception:
60
- pass
61
-
62
- # Safe fallback
63
- return f"{location} typical weather: 15-25°C. Pack light layers, light jacket, and compact umbrella."
64
-
65
- def _generate_packing_advice(location: str, temp: float, condition: str, rain: bool) -> str:
66
- """Helper to generate weather-based packing advice"""
67
- if temp > 25:
68
- clothes = "Light clothing, shorts, breathable fabrics"
69
- elif temp > 15:
70
- clothes = "Light layers, long sleeves, light jacket"
71
- else:
72
- clothes = "Warm layers, jacket, beanie recommended"
73
-
74
- if rain:
75
- clothes += " + waterproof jacket + compact umbrella"
76
-
77
- return f"{location} forecast: {temp:.0f}°C, {condition}. Packing: {clothes}"
78
 
79
  @tool
80
  def convert_currency(amount: float, from_currency: str, to_currency: str) -> str:
81
  """
82
- Convert currency using Frankfurter API (no key required).
 
83
  Args:
84
- amount: Amount to convert
85
- from_currency: Source currency (e.g., "USD")
86
- to_currency: Target currency (e.g., "EUR")
 
87
  Returns:
88
- Formatted conversion result
89
  """
90
  try:
91
  from_curr = from_currency.upper()[:3]
@@ -99,133 +72,156 @@ def convert_currency(amount: float, from_currency: str, to_currency: str) -> str
99
  params={"from": from_curr, "to": to_curr},
100
  timeout=10
101
  )
102
- if resp.status_code == 200:
103
- rate = resp.json()["rates"][to_curr]
104
- converted = amount * rate
105
- return f"{amount:,.0f} {from_curr} = {converted:,.0f} {to_curr} (1 {from_curr} = {rate:.2f} {to_curr})"
106
  except Exception:
107
- pass
108
-
109
- return f"{amount:,.0f} {from_currency} (conversion rate unavailable)"
110
 
111
  @tool
112
- def get_time_difference(origin: str, destination: str) -> str:
113
  """
114
- Get time difference between origin and destination cities.
 
115
  Args:
116
- origin: Home city
117
- destination: Travel destination
 
118
  Returns:
119
- Time difference description
120
  """
121
- try:
122
- # Simple heuristic-based approach (no external API required)
123
- major_cities = {
124
- "new york": -5, "london": 0, "paris": 1, "tokyo": 9, "sydney": 10,
125
- "los angeles": -8, "chicago": -6, "mumbai": 5.5, "dubai": 4,
126
- "singapore": 8, "berlin": 1, "rome": 1, "barcelona": 1, "madrid": 1
127
- }
128
-
129
- origin_tz = major_cities.get(origin.lower(), 0)
130
- dest_tz = major_cities.get(destination.lower(), 0)
131
- diff = dest_tz - origin_tz
132
-
133
- if diff == 0:
134
- return f"No time difference between {origin} and {destination}"
135
- elif diff > 0:
136
- return f"{destination} is {diff} hours ahead of {origin} (adjust sleep schedule accordingly)"
137
- else:
138
- return f"{destination} is {abs(diff)} hours behind {origin} (prepare for jet lag)"
139
- except:
140
- return f"Time difference information unavailable. Check world clock apps before travel."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
142
  @tool
143
  def generate_packing_list(destination: str, weather_summary: str, trip_days: int, trip_type: str) -> str:
144
  """
145
- Generate customized packing list.
 
146
  Args:
147
- destination: Destination name
148
- weather_summary: Weather conditions
149
- trip_days: Duration in days
150
- trip_type: "city", "beach", "mountain", or "mixed"
 
151
  Returns:
152
- Formatted packing checklist
153
  """
154
- cold = any(w in weather_summary.lower() for w in ["cold", "cool", "jacket", "10°c", "11°c", "12°c", "13°c", "14°c"])
155
- rain = any(w in weather_summary.lower() for w in ["rain", "shower", "umbrella", "drizzle"])
156
- hot = any(w in weather_summary.lower() for w in ["hot", "warm", "25°c", "26°c", "27°c", "28°c", "29°c", "30°c"])
 
157
 
158
- # Clothing calculations
159
  tops = min(trip_days, trip_days // 2 + 2)
160
  bottoms = min(trip_days // 2 + 1, 4)
161
 
162
  clothes = [f"• Tops ({tops})", f"• Bottoms ({bottoms})"]
163
  if cold:
164
- clothes.extend(["• Warm jacket", "• Long underwear (if very cold)"])
165
  elif hot:
166
- clothes.extend(["• Light breathable fabrics", "• Sun hat"])
167
  else:
168
- clothes.append("• Light jacket (essential)")
169
 
170
  if rain:
171
- clothes.extend(["• Compact umbrella", "• Quick-dry clothing"])
172
 
173
  if trip_type == "beach":
174
- clothes.extend(["• Swimsuit (x2)", "• Flip-flops", "• Reef-safe sunscreen"])
175
  elif trip_type == "mountain":
176
- clothes.extend(["• Sturdy hiking shoes", "• Moisture-wicking layers", "• Trekking poles (optional)"])
177
 
178
  return (
179
  f"🎒 SMART PACKING LIST ({trip_days}-day {trip_type} trip to {destination})\n"
180
  "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
181
  "ESSENTIALS\n"
182
- "• Passport + photocopies + digital scans\n"
183
- "• Credit/debit cards + local currency (small amount)\n"
184
  "• Universal power adapter\n"
185
- "• Phone + portable charger + cables\n"
186
- "• Travel insurance documents\n"
187
  "\nCLOTHING\n" + "\n".join(clothes) + "\n"
188
  "\nHEALTH & HYGIENE\n"
189
- "• Prescription medications (in original packaging)\n"
190
- "• Basic first-aid kit\n"
191
- "• Hand sanitizer + tissues\n"
192
- "• Travel-sized toiletries\n"
193
- "\n💡 Pro tip: Roll clothes to save space. Pack one versatile outfit per day + 1 extra."
194
  )
195
 
196
  @tool
197
  def build_itinerary(destination: str, attractions: str, budget_local: float, days: int) -> str:
198
  """
199
- Create realistic day-by-day itinerary.
 
200
  Args:
201
- destination: City name
202
- attractions: Comma-separated attractions
203
- budget_local: Daily budget in local currency
204
- days: Trip duration
 
205
  Returns:
206
- Formatted itinerary
207
  """
208
- atts = [a.strip() for a in attractions.split(",") if a.strip()]
209
- if not atts:
210
- atts = ["Old Town exploration", "Local museum", "Scenic viewpoint", "Local market"]
211
 
212
  daily_budget = budget_local / max(days, 1)
213
  lines = [f"🗓️ {days}-DAY REALISTIC ITINERARY: {destination}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"]
214
 
215
  for d in range(1, days + 1):
216
- # Alternate between major and minor attractions
217
- primary = atts[(d - 1) % len(atts)]
218
- secondary = atts[d % len(atts)] if len(atts) > 1 else "Leisurely café break"
219
 
220
  lines.extend([
221
  f"\nDAY {d} | Budget: ~{daily_budget:,.0f} local currency",
222
- f" 09:00 - 12:00 {primary}",
223
- f" 12:30 - 14:00 Lunch at local spot",
224
  f" 14:30 - 17:00 {secondary}",
225
- f" 19:00+ Dinner + evening stroll"
226
  ])
227
 
228
- lines.append("\n💡 Book major attractions online in advance to skip lines!")
229
  return "\n".join(lines)
230
 
231
  @tool
@@ -237,15 +233,30 @@ def assemble_catalogue(
237
  weather: str,
238
  timezone_info: str,
239
  itinerary: str,
240
- packing: str,
241
  image_url_1: str,
242
  image_url_2: str
243
  ) -> str:
244
  """
245
- Compile final travel catalogue with embedded images.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
  """
247
  return f"""# 🌍 {destination} Travel Catalogue
248
- *From {origin} • {dates} • {budget_summary}*
249
 
250
  ---
251
 
@@ -254,22 +265,23 @@ def assemble_catalogue(
254
 
255
  ---
256
 
257
- ## 🌤️ Weather & Packing Guidance
258
  {weather}
259
 
260
  ---
261
 
262
- ## 🗓️ Your Personalized Itinerary
263
  {itinerary}
264
 
265
  ---
266
 
267
- ## 🎒 Packing Checklist
268
- {packing}
269
 
270
  ---
271
 
272
  ## 📸 Visual Inspiration
 
273
  <div style="display: flex; gap: 20px; margin: 20px 0;">
274
  <div style="flex: 1; text-align: center;">
275
  <img src="{image_url_1}" alt="{destination} landmark" style="max-width: 100%; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);">
@@ -277,18 +289,19 @@ def assemble_catalogue(
277
  </div>
278
  <div style="flex: 1; text-align: center;">
279
  <img src="{image_url_2}" alt="{destination} street scene" style="max-width: 100%; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);">
280
- <p><em>Vibrant street life</em></p>
281
  </div>
282
  </div>
283
 
284
  ---
285
 
286
  > 💡 **Travel Pro Tips**
287
- > • Download offline maps before departure
288
- > • Learn 5 basic phrases in the local language
289
- > • Keep digital copies of important documents in cloud storage
290
- > • Budget 10-15% extra for unexpected experiences
291
- > • Public transport passes often save 30%+ vs single tickets
 
292
  """
293
 
294
  # ======================
@@ -298,24 +311,69 @@ if __name__ == "__main__":
298
  print("🚀 Initializing Travel Catalogue Creator...")
299
 
300
  # Load tools
 
 
 
301
  try:
302
  image_gen = load_tool("black-forest-labs/FLUX.1-schnell", trust_remote_code=True)
303
- except:
304
- # Fallback to stable tool
305
- image_gen = load_tool("stabilityai/stable-diffusion-3-medium", trust_remote_code=True)
 
 
306
 
307
- web_search = DuckDuckGoSearchTool()
308
  model = HfApiModel(
309
  max_tokens=2048,
310
- temperature=0.3, # Lower for reliable tool usage
311
  model_id="Qwen/Qwen2.5-Coder-32B-Instruct",
312
  )
313
 
314
- # System prompt enforces strict workflow
315
- with open("prompts.yaml", "r") as f:
316
- import yaml
317
- prompt_templates = yaml.safe_load(f)
318
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319
  agent = CodeAgent(
320
  model=model,
321
  tools=[
@@ -328,11 +386,11 @@ if __name__ == "__main__":
328
  build_itinerary,
329
  assemble_catalogue,
330
  ],
331
- max_steps=25, # Allow room for image generation + retries
332
  verbosity_level=1,
333
  name="TravelCatalogueCreator",
334
  description="Creates comprehensive, personalized travel catalogues with images",
335
- prompt_templates=prompt_templates,
336
  )
337
 
338
  # Launch UI
 
3
  import os
4
  import re
5
  import requests
6
+ from datetime import datetime
7
  from typing import Optional
8
  from smolagents import CodeAgent, DuckDuckGoSearchTool, HfApiModel, load_tool, tool
9
  from Gradio_UI import GradioUI
10
 
11
  # ======================
12
+ # TRAVEL TOOLS (SMOLAGENTS-COMPLIANT DOCSTRINGS)
13
  # ======================
14
 
15
  @tool
16
  def get_weather_forecast(location: str, travel_dates: str) -> str:
17
  """
18
+ Get weather forecast for destination using wttr.in API.
19
+
20
  Args:
21
+ location: Destination city name (e.g., "Barcelona")
22
+ travel_dates: Travel date range string (e.g., "October 15-19")
23
+
24
  Returns:
25
+ Weather summary string with temperature and packing recommendations
26
  """
 
27
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  clean = re.sub(r"[^a-zA-Z0-9\s]", "", location).replace(" ", "+")
29
  resp = requests.get(f"http://wttr.in/{clean}?format=j1", timeout=10)
30
+ resp.raise_for_status()
31
+ data = resp.json()
32
+ cur = data["current_condition"][0]
33
+ temp = int(cur["temp_C"])
34
+ cond = cur["lang_en"][0]["value"].lower()
35
+
36
+ if temp > 25:
37
+ pack = "Light clothing, shorts, breathable fabrics, sunscreen, hat"
38
+ elif temp > 15:
39
+ pack = "Light layers, long sleeves, light jacket"
40
+ else:
41
+ pack = "Warm layers, jacket, beanie recommended"
42
+
43
+ if "rain" in cond or "shower" in cond:
44
+ pack += " + compact umbrella + waterproof jacket"
45
+
46
+ return f"{location} forecast: {temp}°C, {cond}. Packing: {pack}"
47
  except Exception:
48
+ return f"{location} typical weather: 15-25°C. Pack versatile layers, light jacket, and compact umbrella."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
  @tool
51
  def convert_currency(amount: float, from_currency: str, to_currency: str) -> str:
52
  """
53
+ Convert currency amount using Frankfurter API (no API key required).
54
+
55
  Args:
56
+ amount: Numeric amount to convert (e.g., 1200.0)
57
+ from_currency: Source currency ISO code (e.g., "USD")
58
+ to_currency: Target currency ISO code (e.g., "EUR")
59
+
60
  Returns:
61
+ Formatted string showing conversion result and exchange rate
62
  """
63
  try:
64
  from_curr = from_currency.upper()[:3]
 
72
  params={"from": from_curr, "to": to_curr},
73
  timeout=10
74
  )
75
+ resp.raise_for_status()
76
+ rate = resp.json()["rates"][to_curr]
77
+ converted = amount * rate
78
+ return f"{amount:,.0f} {from_curr} = {converted:,.0f} {to_curr} (1 {from_curr} = {rate:.2f} {to_curr})"
79
  except Exception:
80
+ return f"{amount:,.0f} {from_currency} (conversion rate unavailable)"
 
 
81
 
82
  @tool
83
+ def get_time_difference(origin_city: str, destination_city: str) -> str:
84
  """
85
+ Calculate time difference between origin and destination cities using heuristic mapping.
86
+
87
  Args:
88
+ origin_city: Traveler's home city name (e.g., "New York")
89
+ destination_city: Travel destination city name (e.g., "Paris")
90
+
91
  Returns:
92
+ Human-readable string describing time difference and jet lag advice
93
  """
94
+ city_tz = {
95
+ "new york": -5, "london": 0, "paris": 1, "tokyo": 9, "sydney": 10,
96
+ "los angeles": -8, "chicago": -6, "mumbai": 5.5, "dubai": 4,
97
+ "singapore": 8, "berlin": 1, "rome": 1, "barcelona": 1, "madrid": 1,
98
+ "amsterdam": 1, "vienna": 1, "prague": 1, "budapest": 1, "warsaw": 1,
99
+ "stockholm": 1, "oslo": 1, "copenhagen": 1, "helsinki": 2, "athens": 2,
100
+ "istanbul": 3, "cairo": 2, "bangkok": 7, "seoul": 9, "beijing": 8,
101
+ "shanghai": 8, "hong kong": 8, "manila": 8, "kuala lumpur": 8,
102
+ "jakarta": 7, "delhi": 5.5, "rio de janeiro": -3, "sao paulo": -3,
103
+ "buenos aires": -3, "mexico city": -6, "toronto": -5, "vancouver": -8,
104
+ "montreal": -5, "miami": -5, "las vegas": -8, "san francisco": -8,
105
+ "seattle": -8, "denver": -7, "dallas": -6, "houston": -6, "atlanta": -5,
106
+ "boston": -5, "philadelphia": -5, "washington dc": -5, "orlando": -5,
107
+ "phoenix": -7, "minneapolis": -6, "detroit": -5, "pittsburgh": -5,
108
+ "cleveland": -5, "cincinnati": -5, "st louis": -6, "kansas city": -6,
109
+ "new orleans": -6, "memphis": -6, "nashville": -6, "charlotte": -5,
110
+ "raleigh": -5, "baltimore": -5, "milwaukee": -6, "indianapolis": -5,
111
+ "louisville": -5, "portland": -8, "salt lake city": -7, "albuquerque": -7,
112
+ "tucson": -7, "fresno": -8, "sacramento": -8, "oakland": -8, "san jose": -8,
113
+ "las palmas": 0, "tenerife": 0, "lisbon": 0, "madrid": 1, "barcelona": 1,
114
+ "valencia": 1, "seville": 1, "malaga": 1, "bilbao": 1, "zaragoza": 1,
115
+ "granada": 1, "alicante": 1, "murcia": 1, "palma": 1, "ibiza": 1,
116
+ "menorca": 1, "fuerteventura": 0, "lanzarote": 0, "tenerife": 0,
117
+ "gran canaria": 0, "la palma": 0, "el hierro": 0, "la gomera": 0,
118
+ "santa cruz de tenerife": 0, "las palmas de gran canaria": 0,
119
+ }
120
+
121
+ origin_key = origin_city.lower().strip()
122
+ dest_key = destination_city.lower().strip()
123
+
124
+ origin_tz = city_tz.get(origin_key, 0)
125
+ dest_tz = city_tz.get(dest_key, 0)
126
+ diff = dest_tz - origin_tz
127
+
128
+ if diff == 0:
129
+ return f"No time difference between {origin_city} and {destination_city}."
130
+ elif diff > 0:
131
+ return f"{destination_city} is {diff} hours ahead of {origin_city}. Prepare for eastward jet lag (may cause fatigue)."
132
+ else:
133
+ return f"{destination_city} is {abs(diff)} hours behind {origin_city}. Prepare for westward jet lag (may cause insomnia)."
134
 
135
  @tool
136
  def generate_packing_list(destination: str, weather_summary: str, trip_days: int, trip_type: str) -> str:
137
  """
138
+ Generate customized packing checklist based on destination, weather, duration and trip type.
139
+
140
  Args:
141
+ destination: Destination city or region name (e.g., "Barcelona")
142
+ weather_summary: Weather forecast string containing temperature and conditions
143
+ trip_days: Total number of travel days (integer >= 1)
144
+ trip_type: Type of trip: "city", "beach", "mountain", or "mixed"
145
+
146
  Returns:
147
+ Formatted multi-section packing checklist as string
148
  """
149
+ weather_lower = weather_summary.lower()
150
+ cold = any(w in weather_lower for w in ["cold", "cool", "10°c", "11°c", "12°c", "13°c", "14°c", "jacket", "below 15"])
151
+ rain = any(w in weather_lower for w in ["rain", "shower", "drizzle", "umbrella", "precipitation"])
152
+ 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"])
153
 
 
154
  tops = min(trip_days, trip_days // 2 + 2)
155
  bottoms = min(trip_days // 2 + 1, 4)
156
 
157
  clothes = [f"• Tops ({tops})", f"• Bottoms ({bottoms})"]
158
  if cold:
159
+ clothes.extend(["• Warm jacket/coat", "• Long underwear (if very cold)", "• Warm socks (x3)"])
160
  elif hot:
161
+ clothes.extend(["• Light breathable fabrics", "• Sun hat", "• Sunglasses", "• Reef-safe sunscreen (SPF 50+)"])
162
  else:
163
+ clothes.append("• Light jacket or sweater (essential)")
164
 
165
  if rain:
166
+ clothes.extend(["• Compact travel umbrella", "• Quick-dry clothing", "• Waterproof shoes or sandals"])
167
 
168
  if trip_type == "beach":
169
+ clothes.extend(["• Swimsuit (x2)", "• Beach towel", "• Flip-flops/sandals", "• Beach bag"])
170
  elif trip_type == "mountain":
171
+ clothes.extend(["• Sturdy hiking shoes", "• Moisture-wicking base layers", "• Trekking socks (x3)", "• Daypack"])
172
 
173
  return (
174
  f"🎒 SMART PACKING LIST ({trip_days}-day {trip_type} trip to {destination})\n"
175
  "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
176
  "ESSENTIALS\n"
177
+ "• Passport + photocopies + digital scans in cloud\n"
178
+ "• Credit/debit cards + small amount of local currency\n"
179
  "• Universal power adapter\n"
180
+ "• Phone + portable charger (10,000mAh+) + cables\n"
181
+ "• Travel insurance documents (digital + physical)\n"
182
  "\nCLOTHING\n" + "\n".join(clothes) + "\n"
183
  "\nHEALTH & HYGIENE\n"
184
+ "• Prescription medications (in original packaging + doctor's note)\n"
185
+ "• Basic first-aid kit (bandages, antiseptic, pain relievers)\n"
186
+ "• Hand sanitizer (60ml travel size)\n"
187
+ "• Travel-sized toiletries (toothbrush, toothpaste, deodorant)\n"
188
+ "\n💡 Pro tip: Roll clothes to save space. Pack one versatile outfit per day + 1 extra day buffer."
189
  )
190
 
191
  @tool
192
  def build_itinerary(destination: str, attractions: str, budget_local: float, days: int) -> str:
193
  """
194
+ Create realistic day-by-day travel itinerary with time allocations and budget guidance.
195
+
196
  Args:
197
+ destination: Destination city name (e.g., "Barcelona")
198
+ attractions: Comma-separated list of attraction names (e.g., "Sagrada Familia, Park Guell")
199
+ budget_local: Total budget converted to local currency (float)
200
+ days: Number of full travel days (integer >= 1)
201
+
202
  Returns:
203
+ Formatted multi-day itinerary with time slots and daily budget allocation
204
  """
205
+ att_list = [a.strip() for a in attractions.split(",") if a.strip()]
206
+ if not att_list:
207
+ att_list = ["Old Town exploration", "Local museum", "Scenic viewpoint", "Local market"]
208
 
209
  daily_budget = budget_local / max(days, 1)
210
  lines = [f"🗓️ {days}-DAY REALISTIC ITINERARY: {destination}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"]
211
 
212
  for d in range(1, days + 1):
213
+ primary = att_list[(d - 1) % len(att_list)]
214
+ secondary = att_list[d % len(att_list)] if len(att_list) > 1 else "Leisurely café break"
 
215
 
216
  lines.extend([
217
  f"\nDAY {d} | Budget: ~{daily_budget:,.0f} local currency",
218
+ f" 09:00 - 12:00 {primary} (arrive early to avoid crowds)",
219
+ f" 12:30 - 14:00 Lunch at local spot (budget: ~{daily_budget * 0.25:,.0f})",
220
  f" 14:30 - 17:00 {secondary}",
221
+ f" 19:00+ Dinner + evening stroll (budget: ~{daily_budget * 0.35:,.0f})"
222
  ])
223
 
224
+ lines.append("\n💡 Pro tips: Book major attractions online in advance. Use public transport day passes for 30%+ savings.")
225
  return "\n".join(lines)
226
 
227
  @tool
 
233
  weather: str,
234
  timezone_info: str,
235
  itinerary: str,
236
+ packing_list: str,
237
  image_url_1: str,
238
  image_url_2: str
239
  ) -> str:
240
  """
241
+ Compile all travel research into a beautiful, structured Markdown travel catalogue.
242
+
243
+ Args:
244
+ destination: Destination city name (e.g., "Barcelona")
245
+ origin: Traveler's home city (e.g., "New York")
246
+ dates: Travel date range (e.g., "Oct 15-19, 2026")
247
+ budget_summary: Formatted budget conversion string
248
+ weather: Weather forecast with packing advice
249
+ timezone_info: Time difference and jet lag guidance
250
+ itinerary: Day-by-day schedule with time allocations
251
+ packing_list: Complete customized packing checklist
252
+ image_url_1: URL of first generated destination image (landmark)
253
+ image_url_2: URL of second generated destination image (street scene)
254
+
255
+ Returns:
256
+ Complete Markdown-formatted travel catalogue with embedded images
257
  """
258
  return f"""# 🌍 {destination} Travel Catalogue
259
+ *Planned from {origin} • {dates} • {budget_summary}*
260
 
261
  ---
262
 
 
265
 
266
  ---
267
 
268
+ ## 🌤️ Weather Forecast & Packing Guidance
269
  {weather}
270
 
271
  ---
272
 
273
+ ## 🗓️ Your Personalized {len([d for d in itinerary.split('DAY ') if d.strip() and d[0].isdigit()])}-Day Itinerary
274
  {itinerary}
275
 
276
  ---
277
 
278
+ ## 🎒 Complete Packing Checklist
279
+ {packing_list}
280
 
281
  ---
282
 
283
  ## 📸 Visual Inspiration
284
+
285
  <div style="display: flex; gap: 20px; margin: 20px 0;">
286
  <div style="flex: 1; text-align: center;">
287
  <img src="{image_url_1}" alt="{destination} landmark" style="max-width: 100%; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);">
 
289
  </div>
290
  <div style="flex: 1; text-align: center;">
291
  <img src="{image_url_2}" alt="{destination} street scene" style="max-width: 100%; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);">
292
+ <p><em>Vibrant local atmosphere</em></p>
293
  </div>
294
  </div>
295
 
296
  ---
297
 
298
  > 💡 **Travel Pro Tips**
299
+ > • Download offline Google Maps before departure
300
+ > • Learn 5 basic phrases in the local language (hello, thank you, please, excuse me, bill please)
301
+ > • Keep digital copies of passport/insurance in cloud storage + email to yourself
302
+ > • Budget 10-15% extra for spontaneous experiences
303
+ > • Public transport passes often save 30%+ vs single tickets
304
+ > • Always carry small local currency for markets/tips
305
  """
306
 
307
  # ======================
 
311
  print("🚀 Initializing Travel Catalogue Creator...")
312
 
313
  # Load tools
314
+ web_search = DuckDuckGoSearchTool()
315
+
316
+ # Try to load image generation tool (fallback if unavailable)
317
  try:
318
  image_gen = load_tool("black-forest-labs/FLUX.1-schnell", trust_remote_code=True)
319
+ except Exception:
320
+ try:
321
+ image_gen = load_tool("stabilityai/stable-diffusion-3-medium", trust_remote_code=True)
322
+ except Exception as e:
323
+ raise RuntimeError(f"Failed to load image generation tool: {e}")
324
 
 
325
  model = HfApiModel(
326
  max_tokens=2048,
327
+ temperature=0.3,
328
  model_id="Qwen/Qwen2.5-Coder-32B-Instruct",
329
  )
330
 
331
+ # System prompt enforcing strict 8-step workflow with mandatory image generation
332
+ system_prompt = """
333
+ You are TravelCatalogueCreator, an expert travel planner. ALWAYS follow this EXACT 8-step workflow:
334
+
335
+ 1. RESEARCH: Use DuckDuckGoSearchTool to find:
336
+ - Top 4-5 attractions in {destination}
337
+ - Local customs and etiquette tips
338
+ - Safety considerations
339
+ - Typical food/transport prices
340
+
341
+ 2. WEATHER: Use get_weather_forecast with {destination} and {travel_dates}
342
+ → Capture temperature range and precipitation likelihood
343
+
344
+ 3. CURRENCY: Use convert_currency to convert {budget_amount} {home_currency} to {local_currency}
345
+ → Note the exchange rate for daily budgeting
346
+
347
+ 4. TIMEZONE: Use get_time_difference with {origin_city} and {destination}
348
+ → Calculate jet lag impact
349
+
350
+ 5. ITINERARY: Use build_itinerary with:
351
+ - destination={destination}
352
+ - attractions=[top attractions from Step 1]
353
+ - budget_local=[daily budget from Step 3]
354
+ - days=[trip duration]
355
+
356
+ 6. PACKING: Use generate_packing_list with:
357
+ - destination={destination}
358
+ - weather_summary=[output from Step 2]
359
+ - trip_days=[duration]
360
+ - trip_type="city" (default unless beach/mountain specified)
361
+
362
+ 7. IMAGES: CALL image_generation_tool EXACTLY TWICE:
363
+ First call: "{destination} iconic landmark photorealistic travel photo, golden hour, 4k"
364
+ Second call: "{destination} vibrant street scene with locals, authentic atmosphere, travel photography"
365
+
366
+ 8. ASSEMBLE: Use assemble_catalogue with ALL previous outputs including BOTH image URLs
367
+
368
+ CRITICAL RULES:
369
+ • NEVER skip image generation (Step 7 is mandatory - 2 calls required)
370
+ • NEVER call final_answer directly – always use assemble_catalogue first
371
+ • If a tool fails, retry ONCE with simplified parameters before proceeding
372
+ • Always extract concrete values (temps, rates, times) from tool outputs
373
+ • Budget must be converted to local currency before itinerary planning
374
+ • You MUST complete all 8 steps before finishing
375
+ """
376
+
377
  agent = CodeAgent(
378
  model=model,
379
  tools=[
 
386
  build_itinerary,
387
  assemble_catalogue,
388
  ],
389
+ max_steps=25,
390
  verbosity_level=1,
391
  name="TravelCatalogueCreator",
392
  description="Creates comprehensive, personalized travel catalogues with images",
393
+ prompt_templates={"system_prompt": system_prompt},
394
  )
395
 
396
  # Launch UI