agarwalamit081 commited on
Commit
31bf409
·
verified ·
1 Parent(s): c71b1e8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +153 -22
app.py CHANGED
@@ -1,7 +1,12 @@
1
  #!/usr/bin/env python
2
  # coding=utf-8
 
 
 
 
3
  import os
4
  import re
 
5
  import requests
6
  import yaml
7
  from typing import Optional
@@ -11,52 +16,70 @@ from Gradio_UI import GradioUI
11
  # ======================
12
  # TRAVEL TOOLS (SMOLAGENTS-COMPLIANT)
13
  # ======================
 
14
  @tool
15
  def get_weather_forecast(location: str, travel_dates: str) -> str:
16
  """
17
  Get weather forecast for destination using wttr.in API.
 
18
  Args:
19
  location: Destination city name (e.g., "Barcelona")
20
  travel_dates: Travel date range string (e.g., "October 15-19")
 
21
  Returns:
22
  Weather summary string with temperature and packing recommendations
23
  """
24
  try:
 
25
  clean = re.sub(r"[^a-zA-Z0-9\s]", "", location).replace(" ", "+")
26
  resp = requests.get(f"http://wttr.in/{clean}?format=j1", timeout=10)
27
  resp.raise_for_status()
28
  data = resp.json()
 
29
  cur = data["current_condition"][0]
30
  temp = int(cur["temp_C"])
31
  cond = cur["lang_en"][0]["value"].lower()
 
 
32
  if temp > 25:
33
  pack = "Light clothing, shorts, breathable fabrics, sunscreen, hat"
34
  elif temp > 15:
35
  pack = "Light layers, long sleeves, light jacket"
36
  else:
37
  pack = "Warm layers, jacket, beanie recommended"
 
 
38
  if "rain" in cond or "shower" in cond:
39
  pack += " + compact umbrella + waterproof jacket"
40
- return f"{location} forecast: {temp}°C, {cond}. Packing: {pack}"
41
- except Exception:
 
 
42
  return f"{location} typical weather: 15-25°C. Pack versatile layers, light jacket, and compact umbrella."
43
 
 
44
  @tool
45
  def convert_currency(amount: float, from_currency: str, to_currency: str) -> str:
46
  """
47
  Convert currency amount using Frankfurter API (no API key required).
 
48
  Args:
49
  amount: Numeric amount to convert (e.g., 1200.0)
50
  from_currency: Source currency ISO code (e.g., "USD")
51
  to_currency: Target currency ISO code (e.g., "EUR")
 
52
  Returns:
53
  Formatted string showing conversion result and exchange rate
54
  """
55
  try:
56
  from_curr = from_currency.upper()[:3]
57
  to_curr = to_currency.upper()[:3]
 
 
58
  if from_curr == to_curr:
59
  return f"{amount:,.0f} {from_curr} = {amount:,.0f} {to_curr} (1 {from_curr} = 1.00 {to_curr})"
 
 
60
  resp = requests.get(
61
  f"https://api.frankfurter.app/latest",
62
  params={"from": from_curr, "to": to_curr},
@@ -65,20 +88,26 @@ def convert_currency(amount: float, from_currency: str, to_currency: str) -> str
65
  resp.raise_for_status()
66
  rate = resp.json()["rates"][to_curr]
67
  converted = amount * rate
 
68
  return f"{amount:,.0f} {from_curr} = {converted:,.0f} {to_curr} (1 {from_curr} = {rate:.2f} {to_curr})"
69
- except Exception:
 
70
  return f"{amount:,.0f} {from_currency} = {amount:,.0f} {to_currency} (rate unavailable)"
71
 
 
72
  @tool
73
  def get_time_difference(origin_city: str, destination_city: str) -> str:
74
  """
75
  Calculate time difference between origin and destination cities using heuristic mapping.
 
76
  Args:
77
  origin_city: Traveler's home city name (e.g., "New York")
78
  destination_city: Travel destination city name (e.g., "Paris")
 
79
  Returns:
80
  Human-readable string describing time difference and jet lag advice
81
  """
 
82
  city_tz = {
83
  "new york": -5, "london": 0, "paris": 1, "tokyo": 9, "sydney": 10,
84
  "los angeles": -8, "chicago": -6, "mumbai": 5.5, "dubai": 4,
@@ -96,11 +125,13 @@ def get_time_difference(origin_city: str, destination_city: str) -> str:
96
  "tallinn": 2, "sofia": 2, "bucharest": 2, "zagreb": 1, "ljubljana": 1,
97
  "belgrade": 1, "sarajevo": 1, "nicosia": 2, "valletta": 1, "reykjavik": 0,
98
  }
 
99
  origin_key = origin_city.lower().strip()
100
  dest_key = destination_city.lower().strip()
101
  origin_tz = city_tz.get(origin_key, 0)
102
  dest_tz = city_tz.get(dest_key, 0)
103
  diff = dest_tz - origin_tz
 
104
  if diff == 0:
105
  return f"No time difference between {origin_city} and {destination_city}."
106
  elif diff > 0:
@@ -108,37 +139,73 @@ def get_time_difference(origin_city: str, destination_city: str) -> str:
108
  else:
109
  return f"{destination_city} is {abs(diff)} hours behind {origin_city}. Prepare for westward jet lag (may cause insomnia)."
110
 
 
111
  @tool
112
  def generate_packing_list(destination: str, weather_summary: str, trip_days: int, trip_type: str) -> str:
113
  """
114
  Generate customized packing checklist based on destination, weather, duration and trip type.
 
115
  Args:
116
  destination: Destination city or region name (e.g., "Barcelona")
117
  weather_summary: Weather forecast string containing temperature and conditions
118
  trip_days: Total number of travel days (integer >= 1)
119
  trip_type: Type of trip: "city", "beach", "mountain", or "mixed"
 
120
  Returns:
121
  Formatted multi-section packing checklist as string
122
  """
123
  weather_lower = weather_summary.lower()
 
 
124
  cold = any(w in weather_lower for w in ["cold", "cool", "10°c", "11°c", "12°c", "13°c", "14°c", "jacket", "below 15"])
125
  rain = any(w in weather_lower for w in ["rain", "shower", "drizzle", "umbrella", "precipitation"])
126
  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"])
 
 
127
  tops = min(trip_days, trip_days // 2 + 2)
128
  bottoms = min(trip_days // 2 + 1, 4)
 
 
129
  clothes = [f"• Tops ({tops})", f"• Bottoms ({bottoms})"]
 
130
  if cold:
131
- clothes.extend(["• Warm jacket/coat", "• Long underwear (if very cold)", "• Warm socks (x3)"])
 
 
 
 
132
  elif hot:
133
- clothes.extend(["• Light breathable fabrics", "• Sun hat", "• Sunglasses", "• Reef-safe sunscreen (SPF 50+)"])
 
 
 
 
 
134
  else:
135
  clothes.append("• Light jacket or sweater (essential)")
 
136
  if rain:
137
- clothes.extend(["• Compact travel umbrella", "• Quick-dry clothing", "• Waterproof shoes or sandals"])
 
 
 
 
 
 
138
  if trip_type == "beach":
139
- clothes.extend(["• Swimsuit (x2)", "• Beach towel", "• Flip-flops/sandals", "• Beach bag"])
 
 
 
 
 
140
  elif trip_type == "mountain":
141
- clothes.extend(["• Sturdy hiking shoes", "• Moisture-wicking base layers", "• Trekking socks (x3)", "• Daypack"])
 
 
 
 
 
142
 
143
  return (
144
  f"🎒 SMART PACKING LIST ({trip_days}-day {trip_type} trip to {destination})\n"
@@ -158,25 +225,36 @@ def generate_packing_list(destination: str, weather_summary: str, trip_days: int
158
  "\n💡 Pro tip: Roll clothes to save space. Pack one versatile outfit per day + 1 extra day buffer."
159
  )
160
 
 
161
  @tool
162
  def build_itinerary(destination: str, attractions: str, budget_local: float, days: int) -> str:
163
  """
164
  Create realistic day-by-day travel itinerary with time allocations and budget guidance.
 
165
  Args:
166
  destination: Destination city name (e.g., "Barcelona")
167
  attractions: Comma-separated list of attraction names (e.g., "Sagrada Familia, Park Guell")
168
  budget_local: Daily budget in local currency (float)
169
  days: Number of full travel days (integer >= 1)
 
170
  Returns:
171
  Formatted multi-day itinerary with time slots and daily budget allocation
172
  """
 
173
  att_list = [a.strip() for a in attractions.split(",") if a.strip()]
174
  if not att_list:
175
  att_list = ["Old Town exploration", "Local museum", "Scenic viewpoint", "Local market"]
176
- lines = [f"🗓️ {days}-DAY REALISTIC ITINERARY: {destination}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"]
 
 
 
 
 
 
177
  for d in range(1, days + 1):
178
  primary = att_list[(d - 1) % len(att_list)]
179
  secondary = att_list[d % len(att_list)] if len(att_list) > 1 else "Leisurely café break"
 
180
  lines.extend([
181
  f"\nDAY {d} | Budget: ~{budget_local:,.0f} local currency",
182
  f" 09:00 - 12:00 {primary} (arrive early to avoid crowds)",
@@ -184,25 +262,40 @@ def build_itinerary(destination: str, attractions: str, budget_local: float, day
184
  f" 14:30 - 17:00 {secondary}",
185
  f" 19:00+ Dinner + evening stroll (budget: ~{budget_local * 0.35:,.0f})"
186
  ])
 
187
  lines.append("\n💡 Pro tips: Book major attractions online in advance. Use public transport day passes for 30%+ savings.")
188
  return "\n".join(lines)
189
 
 
190
  @tool
191
  def generate_travel_images(destination: str) -> str:
192
  """
193
  Generate two realistic placeholder image URLs for the destination using Unsplash API (no key required).
 
194
  Args:
195
  destination: Destination city name (e.g., "Lisbon")
 
196
  Returns:
197
  JSON-formatted string containing two image URLs with keys "landmark_image" and "street_scene_image"
198
  """
199
  try:
 
200
  clean_dest = re.sub(r"[^a-zA-Z\s]", "", destination).strip().replace(" ", "%20")
 
201
  landmark_url = f"https://source.unsplash.com/800x600/?{clean_dest},landmark,architecture,travel"
202
  street_url = f"https://source.unsplash.com/800x600/?{clean_dest},street,people,culture,travel"
203
- return f'{{"landmark_image": "{landmark_url}", "street_scene_image": "{street_url}"}}'
204
- except Exception:
205
- return '{"landmark_image": "https://source.unsplash.com/800x600/?europe,landmark,travel", "street_scene_image": "https://source.unsplash.com/800x600/?europe,street,people"}'
 
 
 
 
 
 
 
 
 
206
 
207
  @tool
208
  def assemble_catalogue(
@@ -218,6 +311,7 @@ def assemble_catalogue(
218
  ) -> str:
219
  """
220
  Compile all travel research into a beautiful, structured Markdown travel catalogue.
 
221
  Args:
222
  destination: Destination city name (e.g., "Lisbon")
223
  origin: Traveler's home city (e.g., "London")
@@ -228,10 +322,11 @@ def assemble_catalogue(
228
  itinerary: Day-by-day schedule with time allocations
229
  packing_list: Complete customized packing checklist
230
  image_urls_json: JSON string with "landmark_image" and "street_scene_image" URLs
 
231
  Returns:
232
  Complete Markdown-formatted travel catalogue with embedded images
233
  """
234
- import json
235
  try:
236
  images = json.loads(image_urls_json)
237
  img1 = images.get("landmark_image", "https://source.unsplash.com/800x600/?travel,landmark")
@@ -240,25 +335,36 @@ def assemble_catalogue(
240
  img1 = "https://source.unsplash.com/800x600/?travel,landmark"
241
  img2 = "https://source.unsplash.com/800x600/?travel,street"
242
 
243
- import re
244
  day_count = len(re.findall(r'DAY\s+\d+', itinerary))
245
 
246
  return f"""# 🌍 {destination} Travel Catalogue
247
  *Planned from {origin} • {dates} • {budget_summary}*
 
248
  ---
 
249
  ## ⏰ Time Zone Adjustment
250
  {timezone_info}
 
251
  ---
 
252
  ## 🌤️ Weather Forecast & Packing Guidance
253
  {weather}
 
254
  ---
 
255
  ## 🗓️ Your Personalized {day_count}-Day Itinerary
256
  {itinerary}
 
257
  ---
 
258
  ## 🎒 Complete Packing Checklist
259
  {packing_list}
 
260
  ---
 
261
  ## 📸 Visual Inspiration
 
262
  <div style="display: flex; gap: 20px; margin: 20px 0;">
263
  <div style="flex: 1; text-align: center;">
264
  <img src="{img1}" alt="{destination} landmark" style="max-width: 100%; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);">
@@ -269,36 +375,49 @@ def assemble_catalogue(
269
  <p><em>Vibrant local atmosphere</em></p>
270
  </div>
271
  </div>
 
272
  ---
 
273
  > 💡 **Travel Pro Tips**
274
  > • Download offline Google Maps before departure
275
  > • Learn 5 basic phrases in the local language
276
  > • Keep digital copies of passport/insurance in cloud storage
277
  > • Budget 10-15% extra for spontaneous experiences
278
  > • Public transport passes often save 30%+ vs single tickets
 
 
 
 
279
  """
280
 
 
281
  # ======================
282
  # AGENT SETUP
283
  # ======================
284
- if __name__ == "__main__":
 
 
285
  print("🚀 Initializing Travel Catalogue Creator...")
 
286
  # Load system prompt from YAML
287
  with open("prompts.yaml", "r") as f:
288
  prompt_config = yaml.safe_load(f)
289
 
290
- # Pre-instantiate tools (CRITICAL: tools must be pre-instantiated)
291
  web_search = DuckDuckGoSearchTool()
 
 
292
  model = HfApiModel(
293
  max_tokens=2048,
294
  temperature=0.3,
295
  model_id="Qwen/Qwen2.5-Coder-32B-Instruct",
296
  )
297
 
 
298
  agent = CodeAgent(
299
  model=model,
300
  tools=[
301
- web_search, # Pre-instantiated search tool (called as "web_search" in code)
302
  get_weather_forecast,
303
  convert_currency,
304
  get_time_difference,
@@ -307,13 +426,25 @@ if __name__ == "__main__":
307
  generate_travel_images,
308
  assemble_catalogue,
309
  ],
310
- max_steps=15, # Reduced from 25 to prevent runaway execution
311
  verbosity_level=1,
312
  name="TravelCatalogueCreator",
313
- description="Creates comprehensive, personalized travel catalogues with images",
314
- prompt_templates=prompt_config,
315
  )
316
 
317
- # Launch UI
 
 
 
 
 
 
 
 
318
  os.makedirs("./uploads", exist_ok=True)
319
- GradioUI(agent, file_upload_folder="./uploads").launch()
 
 
 
 
 
1
  #!/usr/bin/env python
2
  # coding=utf-8
3
+ """
4
+ Travel Catalogue Creator - AI Agent for Personalized Travel Planning
5
+ Creates comprehensive travel guides with weather, itineraries, packing lists & images
6
+ """
7
  import os
8
  import re
9
+ import json
10
  import requests
11
  import yaml
12
  from typing import Optional
 
16
  # ======================
17
  # TRAVEL TOOLS (SMOLAGENTS-COMPLIANT)
18
  # ======================
19
+
20
  @tool
21
  def get_weather_forecast(location: str, travel_dates: str) -> str:
22
  """
23
  Get weather forecast for destination using wttr.in API.
24
+
25
  Args:
26
  location: Destination city name (e.g., "Barcelona")
27
  travel_dates: Travel date range string (e.g., "October 15-19")
28
+
29
  Returns:
30
  Weather summary string with temperature and packing recommendations
31
  """
32
  try:
33
+ # Clean location name for API
34
  clean = re.sub(r"[^a-zA-Z0-9\s]", "", location).replace(" ", "+")
35
  resp = requests.get(f"http://wttr.in/{clean}?format=j1", timeout=10)
36
  resp.raise_for_status()
37
  data = resp.json()
38
+
39
  cur = data["current_condition"][0]
40
  temp = int(cur["temp_C"])
41
  cond = cur["lang_en"][0]["value"].lower()
42
+
43
+ # Generate packing recommendations based on temperature
44
  if temp > 25:
45
  pack = "Light clothing, shorts, breathable fabrics, sunscreen, hat"
46
  elif temp > 15:
47
  pack = "Light layers, long sleeves, light jacket"
48
  else:
49
  pack = "Warm layers, jacket, beanie recommended"
50
+
51
+ # Add rain gear if needed
52
  if "rain" in cond or "shower" in cond:
53
  pack += " + compact umbrella + waterproof jacket"
54
+
55
+ return f"{location} forecast ({travel_dates}): {temp}°C, {cond}. Packing: {pack}"
56
+ except Exception as e:
57
+ # Fallback to generic forecast
58
  return f"{location} typical weather: 15-25°C. Pack versatile layers, light jacket, and compact umbrella."
59
 
60
+
61
  @tool
62
  def convert_currency(amount: float, from_currency: str, to_currency: str) -> str:
63
  """
64
  Convert currency amount using Frankfurter API (no API key required).
65
+
66
  Args:
67
  amount: Numeric amount to convert (e.g., 1200.0)
68
  from_currency: Source currency ISO code (e.g., "USD")
69
  to_currency: Target currency ISO code (e.g., "EUR")
70
+
71
  Returns:
72
  Formatted string showing conversion result and exchange rate
73
  """
74
  try:
75
  from_curr = from_currency.upper()[:3]
76
  to_curr = to_currency.upper()[:3]
77
+
78
+ # Handle same currency
79
  if from_curr == to_curr:
80
  return f"{amount:,.0f} {from_curr} = {amount:,.0f} {to_curr} (1 {from_curr} = 1.00 {to_curr})"
81
+
82
+ # Get exchange rate
83
  resp = requests.get(
84
  f"https://api.frankfurter.app/latest",
85
  params={"from": from_curr, "to": to_curr},
 
88
  resp.raise_for_status()
89
  rate = resp.json()["rates"][to_curr]
90
  converted = amount * rate
91
+
92
  return f"{amount:,.0f} {from_curr} = {converted:,.0f} {to_curr} (1 {from_curr} = {rate:.2f} {to_curr})"
93
+ except Exception as e:
94
+ # Fallback to showing amount without conversion
95
  return f"{amount:,.0f} {from_currency} = {amount:,.0f} {to_currency} (rate unavailable)"
96
 
97
+
98
  @tool
99
  def get_time_difference(origin_city: str, destination_city: str) -> str:
100
  """
101
  Calculate time difference between origin and destination cities using heuristic mapping.
102
+
103
  Args:
104
  origin_city: Traveler's home city name (e.g., "New York")
105
  destination_city: Travel destination city name (e.g., "Paris")
106
+
107
  Returns:
108
  Human-readable string describing time difference and jet lag advice
109
  """
110
+ # Extended timezone mapping (UTC offsets)
111
  city_tz = {
112
  "new york": -5, "london": 0, "paris": 1, "tokyo": 9, "sydney": 10,
113
  "los angeles": -8, "chicago": -6, "mumbai": 5.5, "dubai": 4,
 
125
  "tallinn": 2, "sofia": 2, "bucharest": 2, "zagreb": 1, "ljubljana": 1,
126
  "belgrade": 1, "sarajevo": 1, "nicosia": 2, "valletta": 1, "reykjavik": 0,
127
  }
128
+
129
  origin_key = origin_city.lower().strip()
130
  dest_key = destination_city.lower().strip()
131
  origin_tz = city_tz.get(origin_key, 0)
132
  dest_tz = city_tz.get(dest_key, 0)
133
  diff = dest_tz - origin_tz
134
+
135
  if diff == 0:
136
  return f"No time difference between {origin_city} and {destination_city}."
137
  elif diff > 0:
 
139
  else:
140
  return f"{destination_city} is {abs(diff)} hours behind {origin_city}. Prepare for westward jet lag (may cause insomnia)."
141
 
142
+
143
  @tool
144
  def generate_packing_list(destination: str, weather_summary: str, trip_days: int, trip_type: str) -> str:
145
  """
146
  Generate customized packing checklist based on destination, weather, duration and trip type.
147
+
148
  Args:
149
  destination: Destination city or region name (e.g., "Barcelona")
150
  weather_summary: Weather forecast string containing temperature and conditions
151
  trip_days: Total number of travel days (integer >= 1)
152
  trip_type: Type of trip: "city", "beach", "mountain", or "mixed"
153
+
154
  Returns:
155
  Formatted multi-section packing checklist as string
156
  """
157
  weather_lower = weather_summary.lower()
158
+
159
+ # Analyze weather conditions
160
  cold = any(w in weather_lower for w in ["cold", "cool", "10°c", "11°c", "12°c", "13°c", "14°c", "jacket", "below 15"])
161
  rain = any(w in weather_lower for w in ["rain", "shower", "drizzle", "umbrella", "precipitation"])
162
  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"])
163
+
164
+ # Calculate clothing quantities
165
  tops = min(trip_days, trip_days // 2 + 2)
166
  bottoms = min(trip_days // 2 + 1, 4)
167
+
168
+ # Build clothing list
169
  clothes = [f"• Tops ({tops})", f"• Bottoms ({bottoms})"]
170
+
171
  if cold:
172
+ clothes.extend([
173
+ "• Warm jacket/coat",
174
+ "• Long underwear (if very cold)",
175
+ "• Warm socks (x3)"
176
+ ])
177
  elif hot:
178
+ clothes.extend([
179
+ "• Light breathable fabrics",
180
+ "• Sun hat",
181
+ "• Sunglasses",
182
+ "• Reef-safe sunscreen (SPF 50+)"
183
+ ])
184
  else:
185
  clothes.append("• Light jacket or sweater (essential)")
186
+
187
  if rain:
188
+ clothes.extend([
189
+ "• Compact travel umbrella",
190
+ "• Quick-dry clothing",
191
+ "• Waterproof shoes or sandals"
192
+ ])
193
+
194
+ # Add trip-type specific items
195
  if trip_type == "beach":
196
+ clothes.extend([
197
+ "• Swimsuit (x2)",
198
+ "• Beach towel",
199
+ "• Flip-flops/sandals",
200
+ "• Beach bag"
201
+ ])
202
  elif trip_type == "mountain":
203
+ clothes.extend([
204
+ "• Sturdy hiking shoes",
205
+ "• Moisture-wicking base layers",
206
+ "• Trekking socks (x3)",
207
+ "• Daypack"
208
+ ])
209
 
210
  return (
211
  f"🎒 SMART PACKING LIST ({trip_days}-day {trip_type} trip to {destination})\n"
 
225
  "\n💡 Pro tip: Roll clothes to save space. Pack one versatile outfit per day + 1 extra day buffer."
226
  )
227
 
228
+
229
  @tool
230
  def build_itinerary(destination: str, attractions: str, budget_local: float, days: int) -> str:
231
  """
232
  Create realistic day-by-day travel itinerary with time allocations and budget guidance.
233
+
234
  Args:
235
  destination: Destination city name (e.g., "Barcelona")
236
  attractions: Comma-separated list of attraction names (e.g., "Sagrada Familia, Park Guell")
237
  budget_local: Daily budget in local currency (float)
238
  days: Number of full travel days (integer >= 1)
239
+
240
  Returns:
241
  Formatted multi-day itinerary with time slots and daily budget allocation
242
  """
243
+ # Parse attractions list
244
  att_list = [a.strip() for a in attractions.split(",") if a.strip()]
245
  if not att_list:
246
  att_list = ["Old Town exploration", "Local museum", "Scenic viewpoint", "Local market"]
247
+
248
+ lines = [
249
+ f"🗓️ {days}-DAY REALISTIC ITINERARY: {destination}",
250
+ "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
251
+ ]
252
+
253
+ # Generate day-by-day schedule
254
  for d in range(1, days + 1):
255
  primary = att_list[(d - 1) % len(att_list)]
256
  secondary = att_list[d % len(att_list)] if len(att_list) > 1 else "Leisurely café break"
257
+
258
  lines.extend([
259
  f"\nDAY {d} | Budget: ~{budget_local:,.0f} local currency",
260
  f" 09:00 - 12:00 {primary} (arrive early to avoid crowds)",
 
262
  f" 14:30 - 17:00 {secondary}",
263
  f" 19:00+ Dinner + evening stroll (budget: ~{budget_local * 0.35:,.0f})"
264
  ])
265
+
266
  lines.append("\n💡 Pro tips: Book major attractions online in advance. Use public transport day passes for 30%+ savings.")
267
  return "\n".join(lines)
268
 
269
+
270
  @tool
271
  def generate_travel_images(destination: str) -> str:
272
  """
273
  Generate two realistic placeholder image URLs for the destination using Unsplash API (no key required).
274
+
275
  Args:
276
  destination: Destination city name (e.g., "Lisbon")
277
+
278
  Returns:
279
  JSON-formatted string containing two image URLs with keys "landmark_image" and "street_scene_image"
280
  """
281
  try:
282
+ # Clean destination name for URL
283
  clean_dest = re.sub(r"[^a-zA-Z\s]", "", destination).strip().replace(" ", "%20")
284
+
285
  landmark_url = f"https://source.unsplash.com/800x600/?{clean_dest},landmark,architecture,travel"
286
  street_url = f"https://source.unsplash.com/800x600/?{clean_dest},street,people,culture,travel"
287
+
288
+ return json.dumps({
289
+ "landmark_image": landmark_url,
290
+ "street_scene_image": street_url
291
+ })
292
+ except Exception as e:
293
+ # Fallback to generic travel images
294
+ return json.dumps({
295
+ "landmark_image": "https://source.unsplash.com/800x600/?europe,landmark,travel",
296
+ "street_scene_image": "https://source.unsplash.com/800x600/?europe,street,people"
297
+ })
298
+
299
 
300
  @tool
301
  def assemble_catalogue(
 
311
  ) -> str:
312
  """
313
  Compile all travel research into a beautiful, structured Markdown travel catalogue.
314
+
315
  Args:
316
  destination: Destination city name (e.g., "Lisbon")
317
  origin: Traveler's home city (e.g., "London")
 
322
  itinerary: Day-by-day schedule with time allocations
323
  packing_list: Complete customized packing checklist
324
  image_urls_json: JSON string with "landmark_image" and "street_scene_image" URLs
325
+
326
  Returns:
327
  Complete Markdown-formatted travel catalogue with embedded images
328
  """
329
+ # Parse image URLs
330
  try:
331
  images = json.loads(image_urls_json)
332
  img1 = images.get("landmark_image", "https://source.unsplash.com/800x600/?travel,landmark")
 
335
  img1 = "https://source.unsplash.com/800x600/?travel,landmark"
336
  img2 = "https://source.unsplash.com/800x600/?travel,street"
337
 
338
+ # Count days in itinerary
339
  day_count = len(re.findall(r'DAY\s+\d+', itinerary))
340
 
341
  return f"""# 🌍 {destination} Travel Catalogue
342
  *Planned from {origin} • {dates} • {budget_summary}*
343
+
344
  ---
345
+
346
  ## ⏰ Time Zone Adjustment
347
  {timezone_info}
348
+
349
  ---
350
+
351
  ## 🌤️ Weather Forecast & Packing Guidance
352
  {weather}
353
+
354
  ---
355
+
356
  ## 🗓️ Your Personalized {day_count}-Day Itinerary
357
  {itinerary}
358
+
359
  ---
360
+
361
  ## 🎒 Complete Packing Checklist
362
  {packing_list}
363
+
364
  ---
365
+
366
  ## 📸 Visual Inspiration
367
+
368
  <div style="display: flex; gap: 20px; margin: 20px 0;">
369
  <div style="flex: 1; text-align: center;">
370
  <img src="{img1}" alt="{destination} landmark" style="max-width: 100%; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);">
 
375
  <p><em>Vibrant local atmosphere</em></p>
376
  </div>
377
  </div>
378
+
379
  ---
380
+
381
  > 💡 **Travel Pro Tips**
382
  > • Download offline Google Maps before departure
383
  > • Learn 5 basic phrases in the local language
384
  > • Keep digital copies of passport/insurance in cloud storage
385
  > • Budget 10-15% extra for spontaneous experiences
386
  > • Public transport passes often save 30%+ vs single tickets
387
+
388
+ ---
389
+
390
+ **Happy Travels! ✈️**
391
  """
392
 
393
+
394
  # ======================
395
  # AGENT SETUP
396
  # ======================
397
+
398
+ def create_agent():
399
+ """Initialize and return the Travel Catalogue Creator agent"""
400
  print("🚀 Initializing Travel Catalogue Creator...")
401
+
402
  # Load system prompt from YAML
403
  with open("prompts.yaml", "r") as f:
404
  prompt_config = yaml.safe_load(f)
405
 
406
+ # Pre-instantiate DuckDuckGo search tool
407
  web_search = DuckDuckGoSearchTool()
408
+
409
+ # Configure model with error handling
410
  model = HfApiModel(
411
  max_tokens=2048,
412
  temperature=0.3,
413
  model_id="Qwen/Qwen2.5-Coder-32B-Instruct",
414
  )
415
 
416
+ # Create agent with all tools
417
  agent = CodeAgent(
418
  model=model,
419
  tools=[
420
+ web_search, # Pre-instantiated search tool
421
  get_weather_forecast,
422
  convert_currency,
423
  get_time_difference,
 
426
  generate_travel_images,
427
  assemble_catalogue,
428
  ],
429
+ max_steps=15,
430
  verbosity_level=1,
431
  name="TravelCatalogueCreator",
432
+ description="Creates comprehensive, personalized travel catalogues with weather forecasts, custom itineraries, packing lists and visual inspiration",
433
+ system_prompt=prompt_config.get("system_prompt", "")
434
  )
435
 
436
+ print("✅ Agent initialized successfully!")
437
+ return agent
438
+
439
+
440
+ if __name__ == "__main__":
441
+ # Create agent
442
+ agent = create_agent()
443
+
444
+ # Create uploads directory
445
  os.makedirs("./uploads", exist_ok=True)
446
+
447
+ # Launch Gradio UI
448
+ print("🌐 Launching Gradio interface...")
449
+ ui = GradioUI(agent, file_upload_folder="./uploads")
450
+ ui.launch(share=False, server_name="0.0.0.0", server_port=7860)