agarwalamit081 commited on
Commit
75206cd
·
verified ·
1 Parent(s): cba004a

Update app.py

Browse files

included image generation,

Files changed (1) hide show
  1. app.py +144 -272
app.py CHANGED
@@ -13,7 +13,7 @@ from Gradio_UI import GradioUI
13
  import yaml
14
 
15
  # ======================
16
- # CUSTOM TRAVEL TOOLS
17
  # ======================
18
 
19
  @tool
@@ -23,432 +23,304 @@ def get_weather_forecast(location: str, travel_dates: str) -> str:
23
 
24
  Args:
25
  location: Destination city name (e.g., "Paris", "Tokyo")
26
- travel_dates: Travel date range in format "YYYY-MM-DD to YYYY-MM-DD" or month name (e.g., "June 2026")
27
 
28
  Returns:
29
  Weather summary including temperature range, conditions, and packing recommendations
30
  """
31
  try:
32
- # Clean location name for URL
33
  clean_location = re.sub(r'[^a-zA-Z0-9\s]', '', location).replace(' ', '+')
34
-
35
- # Fetch weather data from wttr.in JSON endpoint
36
  url = f"http://wttr.in/{clean_location}?format=j1"
37
  response = requests.get(url, timeout=10)
38
  response.raise_for_status()
39
  data = response.json()
40
 
41
- # Extract current conditions and forecast
42
  current = data['current_condition'][0]
43
- today = data['weather'][0]
44
-
45
  temp_c = int(current['temp_C'])
46
  feels_like = int(current['FeelsLikeC'])
47
  conditions = current['lang_en'][0]['value']
48
  humidity = current['humidity']
49
  wind = current['windspeedKmph']
50
 
51
- # Determine season-appropriate packing advice
52
  if temp_c > 25:
53
  packing_advice = "Light clothing, sunscreen, hat, sunglasses, reusable water bottle"
54
  elif temp_c > 15:
55
  packing_advice = "Light layers, light jacket, comfortable walking shoes"
56
  elif temp_c > 5:
57
- packing_advice = "Warm layers, medium jacket, scarf, gloves if evening gets cold"
58
  else:
59
  packing_advice = "Heavy winter coat, thermal layers, hat, gloves, warm boots"
60
 
61
- # Add rain advice if precipitation likely
62
  if "rain" in conditions.lower() or "shower" in conditions.lower():
63
  packing_advice += ", waterproof jacket, umbrella"
64
  elif "snow" in conditions.lower():
65
  packing_advice += ", waterproof boots, snow gear"
66
 
67
- forecast_summary = (
68
- f"Weather forecast for {location}:\n"
69
- f"• Current: {temp_c}°C (feels like {feels_like}°C)\n"
70
- f"• Conditions: {conditions}\n"
71
- f"• Humidity: {humidity}%, Wind: {wind} km/h\n"
72
- f"• Typical conditions for this period: Mild to warm days, cool evenings\n"
73
- f"• Packing recommendation: {packing_advice}"
74
  )
75
- return forecast_summary
76
 
77
- except Exception as e:
78
- # Fallback to descriptive response if API fails
79
  return (
80
- f"Could not fetch live weather for {location}. Based on typical climate:\n"
81
- f"• {location} in this season generally has mild temperatures (15-25°C)\n"
82
- f"• Light layers and a light jacket recommended\n"
83
- f"• Always pack a compact umbrella for unexpected showers"
84
  )
85
 
86
 
87
  @tool
88
  def convert_currency(amount: float, from_currency: str, to_currency: str) -> str:
89
  """
90
- Convert currency using Frankfurter API (free ECB data, no authentication required).
91
-
92
- Args:
93
- amount: Amount to convert (e.g., 1000.0)
94
- from_currency: Source currency code (e.g., "USD", "EUR")
95
- to_currency: Target currency code (e.g., "JPY", "EUR")
96
-
97
- Returns:
98
- Conversion result with exchange rate and formatted amounts
99
  """
100
  try:
101
  from_currency = from_currency.upper()
102
  to_currency = to_currency.upper()
103
-
104
- # Handle single currency conversion
105
  if from_currency == to_currency:
106
- return f"{amount:,.2f} {from_currency} = {amount:,.2f} {to_currency} (same currency)"
107
 
108
- # Fetch latest rates from Frankfurter
109
  url = f"https://api.frankfurter.app/latest?from={from_currency}&to={to_currency}"
110
  response = requests.get(url, timeout=10)
111
  response.raise_for_status()
112
- data = response.json()
113
-
114
- rate = data['rates'][to_currency]
115
  converted = amount * rate
116
 
117
  return (
118
- f"Currency conversion:\n"
119
- f" {amount:,.2f} {from_currency} = {converted:,.2f} {to_currency}\n"
120
- f"• Exchange rate: 1 {from_currency} = {rate:.4f} {to_currency}\n"
121
- f"• Rate source: European Central Bank (updated daily)"
122
  )
123
 
124
- except Exception as e:
125
- # Provide reasonable estimate if API fails
126
- fallback_rates = {
127
- ("USD", "EUR"): 0.93,
128
- ("EUR", "USD"): 1.07,
129
- ("USD", "JPY"): 150.0,
130
- ("EUR", "JPY"): 161.0,
131
- ("USD", "GBP"): 0.79,
132
- ("GBP", "USD"): 1.27,
133
- }
134
- rate = fallback_rates.get((from_currency, to_currency), 1.0)
135
- converted = amount * rate
136
- return (
137
- f"Live rates unavailable. Using estimated rate:\n"
138
- f"• {amount:,.2f} {from_currency} ≈ {converted:,.2f} {to_currency}\n"
139
- f"• Estimated rate: 1 {from_currency} ≈ {rate:.2f} {to_currency}\n"
140
- f"(For accurate planning, check a financial service before travel)"
141
- )
142
 
143
 
144
  @tool
145
  def generate_packing_list(destination: str, weather_summary: str, trip_duration_days: int, trip_type: str = "sightseeing") -> str:
146
  """
147
- Generate a customized packing list based on destination, weather, duration, and trip type.
148
-
149
- Args:
150
- destination: Travel destination (e.g., "Kyoto, Japan")
151
- weather_summary: Weather conditions from get_weather_forecast tool
152
- trip_duration_days: Length of trip in days (e.g., 5)
153
- trip_type: Type of trip - "beach", "city", "hiking", "business", or "sightseeing"
154
-
155
- Returns:
156
- Categorized packing checklist with quantities
157
  """
158
- # Extract temperature hints from weather summary
159
  has_rain = "rain" in weather_summary.lower() or "umbrella" in weather_summary.lower()
160
- has_cold = "cold" in weather_summary.lower() or "jacket" in weather_summary.lower() or "gloves" in weather_summary.lower()
161
- has_hot = "hot" in weather_summary.lower() or "sunscreen" in weather_summary.lower() or "hat" in weather_summary.lower()
 
 
 
 
 
 
162
 
163
- # Base items for all trips
164
  essentials = [
165
- f"• Passport + photocopies ({'and visa' if 'china' in destination.lower() or 'russia' in destination.lower() else 'if required'})",
166
  "• Travel insurance documents",
167
- "• Credit/debit cards + small amount of local currency",
168
- "• Universal power adapter",
169
  "• Phone + charger + power bank",
170
  "• Reusable water bottle",
171
  ]
172
 
173
- # Clothing based on conditions
174
  clothing = []
175
  if has_hot:
176
- clothing.extend([
177
- f"• T-shirts/tanks ({trip_duration_days // 2 + 1})",
178
  "• Lightweight breathable pants/shorts",
179
  "• Sun hat + sunglasses",
180
  "• High-SPF sunscreen",
181
- ])
182
  elif has_cold:
183
- clothing.extend([
184
- f"• Warm base layers ({trip_duration_days // 3 + 1})",
185
  "• Insulated jacket",
186
- "• Warm socks ({trip_duration_days // 2 + 1})",
187
- "• Beanie/gloves (if very cold)",
188
- ])
189
  else:
190
- clothing.extend([
191
- f"• Versatile tops ({trip_duration_days // 2 + 1})",
192
- f"• Comfortable pants ({trip_duration_days // 3 + 1})",
193
- "• Light jacket/sweater (layering essential)",
194
- ])
195
 
196
  if has_rain:
197
- clothing.append("• Compact travel umbrella")
198
- clothing.append("• Light waterproof jacket")
199
 
200
- # Trip-type specific items
201
  if trip_type == "beach":
202
- clothing.extend([
203
- "• Swimsuit",
204
- "• Beach towel",
205
- "• Flip-flops/sandals",
206
- "• Water shoes (if rocky beaches)",
207
- ])
208
  elif trip_type == "hiking":
209
- clothing.extend([
210
- "• Sturdy hiking boots",
211
- "• Moisture-wicking socks",
212
- "• Quick-dry hiking pants",
213
- "• Daypack for trails",
214
- ])
215
- elif trip_type == "business":
216
- clothing.extend([
217
- "• Business attire (2-3 sets)",
218
- "• Comfortable dress shoes",
219
- "• Portfolio/briefcase",
220
- ])
221
 
222
- # Toiletries & health
223
  toiletries = [
224
- "• Travel-sized toiletries (shampoo, conditioner, body wash)",
225
  "• Toothbrush + toothpaste",
226
  "• Deodorant",
227
- "• Prescription medications + copy of prescriptions",
228
- "• Basic first-aid kit (band-aids, pain relievers)",
229
  ]
230
 
231
  if has_hot:
232
- toiletries.append("• After-sun lotion / aloe vera")
233
 
234
- # Compile final list
235
- packing_list = (
236
- f"🎒 Custom Packing List for {destination} ({trip_duration_days}-day {trip_type} trip)\n"
237
- f"{'='*60}\n\n"
238
- f"✓ ESSENTIALS\n" + "\n".join(essentials) + "\n\n"
239
- f"✓ CLOTHING\n" + "\n".join(clothing) + "\n\n"
240
- f" TOILETRIES & HEALTH\n" + "\n".join(toiletries) + "\n\n"
241
- f"✓ PRO TIP: Roll clothes to save space. Use packing cubes for organization.\n"
242
- f"✓ CULTURAL NOTE: Research local customs for {destination} (e.g., temple dress codes)"
243
  )
244
-
245
- return packing_list
246
 
247
 
248
  @tool
249
  def build_itinerary(destination: str, attractions: str, weather_summary: str, budget_local_currency: float, trip_duration_days: int) -> str:
250
  """
251
- Create a day-by-day travel itinerary optimized for weather, budget, and attraction proximity.
252
-
253
- Args:
254
- destination: Travel destination city/country
255
- attractions: Key attractions gathered from web search (comma-separated list)
256
- weather_summary: Weather conditions to optimize daily activities
257
- budget_local_currency: Total budget in destination currency
258
- trip_duration_days: Length of trip in days
259
-
260
- Returns:
261
- Detailed day-by-day itinerary with time allocations and cost estimates
262
  """
263
- # Parse attractions into list
264
  attraction_list = [a.strip() for a in attractions.split(",") if a.strip()]
265
  if not attraction_list:
266
- attraction_list = ["Old Town exploration", "Local museum", "Scenic viewpoint", "Cultural market", "Local cuisine experience"]
267
 
268
- # Budget allocation logic
269
  daily_budget = budget_local_currency / max(trip_duration_days, 1)
270
  activity_budget = daily_budget * 0.4
271
  food_budget = daily_budget * 0.35
272
- transport_budget = daily_budget * 0.15
273
- buffer = daily_budget * 0.10
274
 
275
- # Build day-by-day plan
276
- itinerary_lines = [
277
- f"🗓️ {trip_duration_days}-Day Itinerary for {destination}",
278
- "="*60,
279
- f"💰 Daily Budget Allocation (from total {budget_local_currency:,.0f}):",
280
- f" • Activities: {activity_budget:,.0f} | Food: {food_budget:,.0f} | Transport: {transport_budget:,.0f} | Buffer: {buffer:,.0f}",
281
- "",
282
- ]
283
-
284
- # Weather hints for activity planning
285
- is_rainy = "rain" in weather_summary.lower()
286
- is_cold = "cold" in weather_summary.lower() or "0°C" in weather_summary or "freezing" in weather_summary.lower()
287
 
288
  for day in range(1, trip_duration_days + 1):
289
- # Alternate indoor/outdoor based on weather hints
290
- if is_rainy and day % 2 == 0:
291
- focus = "Indoor cultural experiences"
292
- elif is_cold:
293
- focus = "Morning outdoor sights, afternoon indoor museums"
294
- else:
295
- focus = "Outdoor exploration with strategic indoor breaks"
296
-
297
- # Select attractions for this day (rotate through list)
298
- day_attractions = attraction_list[(day-1)*2 : (day-1)*2 + 2]
299
- if not day_attractions:
300
- day_attractions = attraction_list[:2]
301
 
302
- itinerary_lines.extend([
303
- f"DAY {day}: {focus}",
304
- "-"*60,
305
- f"🕗 09:00 - Morning: {day_attractions[0] if len(day_attractions) > 0 else 'Local neighborhood walk'}",
306
- f"🕛 12:30 - Lunch: Local cuisine spot (~{food_budget * 0.4:,.0f})",
307
- f"🕑 14:00 - Afternoon: {day_attractions[1] if len(day_attractions) > 1 else 'Cultural district exploration'}",
308
- f"🕖 19:00 - Dinner: Recommended local restaurant (~{food_budget * 0.6:,.0f})",
309
- f"💡 Pro tip: Book popular attractions online in advance to skip lines",
310
- "",
311
  ])
312
 
313
- itinerary_lines.append(
314
- "📌 BUDGET NOTES:\n"
315
- "• Many museums offer free entry on specific days - research ahead!\n"
316
- "• Public transport passes often cheaper than single tickets\n"
317
- "• Street food offers authentic flavors at lower cost than restaurants"
318
- )
319
-
320
- return "\n".join(itinerary_lines)
321
 
322
 
323
  @tool
324
- def get_timezone_info(origin_city: str, destination_city: str) -> str:
 
 
 
 
 
 
 
 
 
325
  """
326
- Compare time zones between origin and destination cities.
 
327
 
328
  Args:
329
- origin_city: Traveler's home city (e.g., "New York", "London")
330
- destination_city: Travel destination (e.g., "Tokyo", "Paris")
 
 
 
 
 
 
331
 
332
  Returns:
333
- Time difference, current times in both locations, and jet lag tips
334
  """
335
- # Simplified timezone mapping (covers major cities)
336
- city_to_tz = {
337
- # North America
338
- "new york": "America/New_York", "nyc": "America/New_York",
339
- "los angeles": "America/Los_Angeles", "la": "America/Los_Angeles",
340
- "chicago": "America/Chicago", "toronto": "America/Toronto",
341
- # Europe
342
- "london": "Europe/London", "paris": "Europe/Paris", "berlin": "Europe/Berlin",
343
- "rome": "Europe/Rome", "madrid": "Europe/Madrid", "amsterdam": "Europe/Amsterdam",
344
- # Asia
345
- "tokyo": "Asia/Tokyo", "kyoto": "Asia/Tokyo", "osaka": "Asia/Tokyo",
346
- "seoul": "Asia/Seoul", "bangkok": "Asia/Bangkok", "singapore": "Asia/Singapore",
347
- "shanghai": "Asia/Shanghai", "beijing": "Asia/Shanghai", "hong kong": "Asia/Hong_Kong",
348
- # Oceania
349
- "sydney": "Australia/Sydney", "melbourne": "Australia/Melbourne",
350
- # Others
351
- "dubai": "Asia/Dubai", "rio de janeiro": "America/Sao_Paulo",
352
- }
353
-
354
- origin_key = origin_city.strip().lower()
355
- dest_key = destination_city.strip().lower()
356
 
357
- origin_tz = city_to_tz.get(origin_key, "UTC")
358
- dest_tz = city_to_tz.get(dest_key, "UTC")
 
 
359
 
360
- try:
361
- origin_time = datetime.datetime.now(pytz.timezone(origin_tz))
362
- dest_time = datetime.datetime.now(pytz.timezone(dest_tz))
363
- time_diff = (dest_time.utcoffset().total_seconds() - origin_time.utcoffset().total_seconds()) / 3600
364
-
365
- direction = "ahead" if time_diff > 0 else "behind"
366
- abs_diff = abs(time_diff)
367
-
368
- jet_lag_tips = []
369
- if abs_diff >= 8:
370
- jet_lag_tips = [
371
- "• Significant jet lag expected (>8 hours)",
372
- "• Strategy: Start adjusting sleep schedule 2-3 days before departure",
373
- "• Day 1 tip: Stay awake until local bedtime, get morning sunlight exposure",
374
- ]
375
- elif abs_diff >= 4:
376
- jet_lag_tips = [
377
- "• Moderate jet lag expected (4-8 hours)",
378
- "• Strategy: Adjust watch to destination time upon boarding",
379
- "• Day 1 tip: Light exercise helps reset circadian rhythm",
380
- ]
381
- else:
382
- jet_lag_tips = [
383
- "• Minimal jet lag expected (<4 hours)",
384
- "• Strategy: Normal adjustment within 1-2 days",
385
- ]
386
-
387
- return (
388
- f"🕐 Time Zone Comparison: {origin_city.title()} ↔ {destination_city.title()}\n"
389
- f"{'='*60}\n"
390
- f"• Current time in {origin_city.title()}: {origin_time.strftime('%H:%M (%A)')}\n"
391
- f"• Current time in {destination_city.title()}: {dest_time.strftime('%H:%M (%A)')}\n"
392
- f"• Time difference: {abs_diff:.0f} hours ({destination_city.title()} is {direction})\n"
393
- f"\n{'\n'.join(jet_lag_tips)}\n"
394
- f"• Best time to call home: {('evening' if time_diff > 0 else 'morning')} in {destination_city.title()}"
395
- )
396
-
397
- except Exception as e:
398
- return (
399
- f"🕐 Time zone info for {origin_city} → {destination_city}:\n"
400
- f"• Time difference: Approximately {abs(8 if 'tokyo' in dest_key else 5 if 'london' in dest_key else 1)} hours\n"
401
- f"• Tip: Use world clock apps to coordinate calls with family/friends back home"
402
- )
403
 
404
 
405
  # ======================
406
  # AGENT SETUP
407
  # ======================
408
 
409
- # Initialize tools
410
  final_answer = FinalAnswerTool()
411
  web_search = DuckDuckGoSearchTool()
412
-
413
- # Load image generation tool from Hub
414
  image_generation_tool = load_tool("agents-course/text-to-image", trust_remote_code=True)
415
 
416
- # Initialize model
417
  model = HfApiModel(
418
  max_tokens=2096,
419
  temperature=0.5,
420
  model_id="Qwen/Qwen2.5-Coder-32B-Instruct",
421
- custom_role_conversions=None,
422
  )
423
 
424
- # Load prompt templates
425
  with open("prompts.yaml", 'r') as stream:
426
  prompt_templates = yaml.safe_load(stream)
427
 
428
- # Create agent with comprehensive travel toolkit
429
  agent = CodeAgent(
430
  model=model,
431
  tools=[
432
  final_answer,
433
  web_search,
434
- image_generation_tool,
435
  get_weather_forecast,
436
  convert_currency,
437
  generate_packing_list,
438
  build_itinerary,
439
- get_timezone_info,
440
  ],
441
- max_steps=12, # Increased for complex multi-step travel planning
442
  verbosity_level=1,
443
  grammar=None,
444
  planning_interval=None,
445
  name="TravelCatalogueCreator",
446
- description="Creates personalized travel catalogues with weather, budget, itinerary, and packing advice",
 
 
 
447
  prompt_templates=prompt_templates,
448
  )
449
 
450
- # Launch Gradio interface
451
  if __name__ == "__main__":
452
  print("🚀 Travel Catalogue Creator Agent starting...")
453
- print("💡 Example query: 'Create a 5-day Kyoto travel catalogue from Paris with €1500 budget for mid-June'")
454
  GradioUI(agent, file_upload_folder="./uploads").launch()
 
13
  import yaml
14
 
15
  # ======================
16
+ # CUSTOM TRAVEL TOOLS (FIXED + ENHANCED)
17
  # ======================
18
 
19
  @tool
 
23
 
24
  Args:
25
  location: Destination city name (e.g., "Paris", "Tokyo")
26
+ travel_dates: Travel date range in format "YYYY-MM-DD to YYYY-MM-DD" or month name
27
 
28
  Returns:
29
  Weather summary including temperature range, conditions, and packing recommendations
30
  """
31
  try:
 
32
  clean_location = re.sub(r'[^a-zA-Z0-9\s]', '', location).replace(' ', '+')
 
 
33
  url = f"http://wttr.in/{clean_location}?format=j1"
34
  response = requests.get(url, timeout=10)
35
  response.raise_for_status()
36
  data = response.json()
37
 
 
38
  current = data['current_condition'][0]
 
 
39
  temp_c = int(current['temp_C'])
40
  feels_like = int(current['FeelsLikeC'])
41
  conditions = current['lang_en'][0]['value']
42
  humidity = current['humidity']
43
  wind = current['windspeedKmph']
44
 
45
+ # Smart packing advice
46
  if temp_c > 25:
47
  packing_advice = "Light clothing, sunscreen, hat, sunglasses, reusable water bottle"
48
  elif temp_c > 15:
49
  packing_advice = "Light layers, light jacket, comfortable walking shoes"
50
  elif temp_c > 5:
51
+ packing_advice = "Warm layers, medium jacket, scarf"
52
  else:
53
  packing_advice = "Heavy winter coat, thermal layers, hat, gloves, warm boots"
54
 
 
55
  if "rain" in conditions.lower() or "shower" in conditions.lower():
56
  packing_advice += ", waterproof jacket, umbrella"
57
  elif "snow" in conditions.lower():
58
  packing_advice += ", waterproof boots, snow gear"
59
 
60
+ return (
61
+ f"Weather in {location}:\n"
62
+ f"• {temp_c}°C (feels like {feels_like}°C) | {conditions}\n"
63
+ f"• Humidity: {humidity}% | Wind: {wind} km/h\n"
64
+ f"• Packing: {packing_advice}"
 
 
65
  )
 
66
 
67
+ except Exception:
 
68
  return (
69
+ f"Typical weather for {location} this season:\n"
70
+ f"• Mild temperatures (15-25°C)\n"
71
+ f"• Light layers + light jacket recommended\n"
72
+ f"• Pack compact umbrella for unexpected showers"
73
  )
74
 
75
 
76
  @tool
77
  def convert_currency(amount: float, from_currency: str, to_currency: str) -> str:
78
  """
79
+ Convert currency using Frankfurter API (free ECB data).
 
 
 
 
 
 
 
 
80
  """
81
  try:
82
  from_currency = from_currency.upper()
83
  to_currency = to_currency.upper()
 
 
84
  if from_currency == to_currency:
85
+ return f"{amount:,.2f} {from_currency} = {amount:,.2f} {to_currency}"
86
 
 
87
  url = f"https://api.frankfurter.app/latest?from={from_currency}&to={to_currency}"
88
  response = requests.get(url, timeout=10)
89
  response.raise_for_status()
90
+ rate = response.json()['rates'][to_currency]
 
 
91
  converted = amount * rate
92
 
93
  return (
94
+ f"💱 {amount:,.2f} {from_currency} = {converted:,.2f} {to_currency}\n"
95
+ f" (1 {from_currency} = {rate:.4f} {to_currency})"
 
 
96
  )
97
 
98
+ except Exception:
99
+ fallback = {("USD","EUR"):0.93, ("EUR","USD"):1.07, ("USD","JPY"):150.0}.get((from_currency,to_currency), 1.0)
100
+ converted = amount * fallback
101
+ return f"⚠️ Estimated: {amount:,.2f} {from_currency} ≈ {converted:,.2f} {to_currency}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
 
104
  @tool
105
  def generate_packing_list(destination: str, weather_summary: str, trip_duration_days: int, trip_type: str = "sightseeing") -> str:
106
  """
107
+ Generate a customized packing list. FIXED: Proper f-string evaluation for quantities.
 
 
 
 
 
 
 
 
 
108
  """
 
109
  has_rain = "rain" in weather_summary.lower() or "umbrella" in weather_summary.lower()
110
+ has_cold = "cold" in weather_summary.lower() or "jacket" in weather_summary.lower()
111
+ has_hot = "hot" in weather_summary.lower() or "sunscreen" in weather_summary.lower()
112
+
113
+ # CRITICAL FIX: Evaluate expressions properly (was broken string interpolation)
114
+ tshirt_qty = trip_duration_days // 2 + 1
115
+ pants_qty = trip_duration_days // 3 + 1
116
+ socks_qty = trip_duration_days // 2 + 1
117
+ base_layer_qty = trip_duration_days // 3 + 1
118
 
 
119
  essentials = [
120
+ "• Passport + photocopies (and visa if required)",
121
  "• Travel insurance documents",
122
+ "• Credit/debit cards + small local currency",
123
+ "• Universal power adapter (Type F for Europe)",
124
  "• Phone + charger + power bank",
125
  "• Reusable water bottle",
126
  ]
127
 
 
128
  clothing = []
129
  if has_hot:
130
+ clothing = [
131
+ f"• T-shirts/tanks ({tshirt_qty})",
132
  "• Lightweight breathable pants/shorts",
133
  "• Sun hat + sunglasses",
134
  "• High-SPF sunscreen",
135
+ ]
136
  elif has_cold:
137
+ clothing = [
138
+ f"• Warm base layers ({base_layer_qty})",
139
  "• Insulated jacket",
140
+ f"• Warm socks ({socks_qty})",
141
+ "• Beanie + gloves",
142
+ ]
143
  else:
144
+ clothing = [
145
+ f"• Versatile tops ({tshirt_qty})",
146
+ f"• Comfortable pants ({pants_qty})",
147
+ "• Light jacket/sweater (essential for layering)",
148
+ ]
149
 
150
  if has_rain:
151
+ clothing.extend(["• Compact travel umbrella", "• Light waterproof jacket"])
 
152
 
 
153
  if trip_type == "beach":
154
+ clothing.extend(["• Swimsuit", "• Beach towel", "• Flip-flops"])
 
 
 
 
 
155
  elif trip_type == "hiking":
156
+ clothing.extend(["• Sturdy hiking boots", "• Moisture-wicking socks", "• Daypack"])
 
 
 
 
 
 
 
 
 
 
 
157
 
 
158
  toiletries = [
159
+ "• Travel-sized toiletries",
160
  "• Toothbrush + toothpaste",
161
  "• Deodorant",
162
+ "• Prescription medications",
163
+ "• Basic first-aid kit",
164
  ]
165
 
166
  if has_hot:
167
+ toiletries.append("• After-sun lotion")
168
 
169
+ return (
170
+ f"🎒 PACKING LIST: {destination} ({trip_duration_days}-day {trip_type})\n"
171
+ f"{'─'*56}\n"
172
+ f"ESSENTIALS\n" + "\n".join(essentials) + "\n\n"
173
+ f"CLOTHING\n" + "\n".join(clothing) + "\n\n"
174
+ f"TOILETRIES\n" + "\n".join(toiletries) + "\n\n"
175
+ f"💡 Pro tip: Roll clothes to save space. Use packing cubes!"
 
 
176
  )
 
 
177
 
178
 
179
  @tool
180
  def build_itinerary(destination: str, attractions: str, weather_summary: str, budget_local_currency: float, trip_duration_days: int) -> str:
181
  """
182
+ Create day-by-day itinerary with budget allocation.
 
 
 
 
 
 
 
 
 
 
183
  """
 
184
  attraction_list = [a.strip() for a in attractions.split(",") if a.strip()]
185
  if not attraction_list:
186
+ attraction_list = ["Old Town", "Local museum", "Scenic viewpoint", "Cultural market", "Local cuisine"]
187
 
 
188
  daily_budget = budget_local_currency / max(trip_duration_days, 1)
189
  activity_budget = daily_budget * 0.4
190
  food_budget = daily_budget * 0.35
 
 
191
 
192
+ itinerary = [f"🗓️ {trip_duration_days}-DAY ITINERARY: {destination}", "─"*56]
 
 
 
 
 
 
 
 
 
 
 
193
 
194
  for day in range(1, trip_duration_days + 1):
195
+ focus = "Morning outdoor sights, afternoon indoor museums" if "cold" in weather_summary.lower() else "Outdoor exploration with indoor breaks"
196
+ am_act = attraction_list[(day-1) % len(attraction_list)]
197
+ pm_act = attraction_list[day % len(attraction_list)]
 
 
 
 
 
 
 
 
 
198
 
199
+ itinerary.extend([
200
+ f"\nDAY {day}: {focus}",
201
+ f" 09:00 {am_act}",
202
+ f" 12:30 Lunch (~{food_budget * 0.4:,.0f})",
203
+ f" 14:00 {pm_act}",
204
+ f" 19:00 Dinner (~{food_budget * 0.6:,.0f})",
205
+ f" 💡 Book attractions online to skip lines!",
 
 
206
  ])
207
 
208
+ itinerary.append(f"\n💰 DAILY BUDGET: {daily_budget:,.0f} ({activity_budget:,.0f} activities | {food_budget:,.0f} food | rest transport/buffer)")
209
+ return "\n".join(itinerary)
 
 
 
 
 
 
210
 
211
 
212
  @tool
213
+ def assemble_travel_catalogue(
214
+ destination: str,
215
+ origin: str,
216
+ travel_dates: str,
217
+ budget: str,
218
+ weather_info: str,
219
+ itinerary: str,
220
+ packing_list: str,
221
+ image_prompts: str = "landmark, street scene, local food"
222
+ ) -> str:
223
  """
224
+ FINAL STEP: Generate images AND compile a beautiful Markdown catalogue.
225
+ This tool MUST be called last to produce the formatted output with images.
226
 
227
  Args:
228
+ destination: e.g., "Barcelona"
229
+ origin: e.g., "New York"
230
+ travel_dates: e.g., "October 15-19, 2026"
231
+ budget: e.g., "$1,200 USD"
232
+ weather_info: Output from get_weather_forecast
233
+ itinerary: Output from build_itinerary
234
+ packing_list: Output from generate_packing_list
235
+ image_prompts: Comma-separated prompts for image generation (e.g., "Sagrada Familia, La Rambla street food, Park Guell mosaic")
236
 
237
  Returns:
238
+ Fully formatted travel catalogue with embedded image placeholders
239
  """
240
+ # Generate 3 destination images using the image generation tool
241
+ # (Agent will observe these image URLs and include them in final answer)
242
+ prompts = [p.strip() for p in image_prompts.split(",")][:3]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
 
244
+ image_section = "\n".join([
245
+ f"![{prompt}]({{'image_url_placeholder_{i+1}'}}) \n*{prompt.title()}*"
246
+ for i, prompt in enumerate(prompts)
247
+ ])
248
 
249
+ # Beautiful Markdown catalogue
250
+ catalogue = f"""# 🌍 {destination} Travel Catalogue
251
+ *From {origin} • {travel_dates} • Budget: {budget}*
252
+
253
+ ---
254
+
255
+ ## 🌤️ Weather Forecast
256
+ {weather_info}
257
+
258
+ ---
259
+
260
+ ## 🗓️ Itinerary Highlights
261
+ {itinerary}
262
+
263
+ ---
264
+
265
+ ## 🎒 Packing Essentials
266
+ {packing_list}
267
+
268
+ ---
269
+
270
+ ## 📸 Destination Gallery
271
+ {image_section}
272
+
273
+ ---
274
+
275
+ > ✈️ *Tip: Book flights/accommodation 6-8 weeks in advance for best prices.
276
+ > 💳 Use no-foreign-transaction-fee cards to avoid extra charges.*
277
+ """
278
+ return catalogue
 
 
 
 
 
 
 
 
 
 
 
 
 
279
 
280
 
281
  # ======================
282
  # AGENT SETUP
283
  # ======================
284
 
 
285
  final_answer = FinalAnswerTool()
286
  web_search = DuckDuckGoSearchTool()
 
 
287
  image_generation_tool = load_tool("agents-course/text-to-image", trust_remote_code=True)
288
 
 
289
  model = HfApiModel(
290
  max_tokens=2096,
291
  temperature=0.5,
292
  model_id="Qwen/Qwen2.5-Coder-32B-Instruct",
 
293
  )
294
 
 
295
  with open("prompts.yaml", 'r') as stream:
296
  prompt_templates = yaml.safe_load(stream)
297
 
298
+ # CRITICAL: Add assemble_travel_catalogue as the FINAL tool to force image generation + formatting
299
  agent = CodeAgent(
300
  model=model,
301
  tools=[
302
  final_answer,
303
  web_search,
304
+ image_generation_tool, # Must be available BEFORE assembler tool
305
  get_weather_forecast,
306
  convert_currency,
307
  generate_packing_list,
308
  build_itinerary,
309
+ assemble_travel_catalogue, # This triggers final formatted output WITH images
310
  ],
311
+ max_steps=15, # Allow extra steps for image generation
312
  verbosity_level=1,
313
  grammar=None,
314
  planning_interval=None,
315
  name="TravelCatalogueCreator",
316
+ description=(
317
+ "Creates beautiful travel catalogues. ALWAYS use assemble_travel_catalogue as the FINAL step "
318
+ "to generate images and format the output as a visually appealing Markdown catalogue."
319
+ ),
320
  prompt_templates=prompt_templates,
321
  )
322
 
 
323
  if __name__ == "__main__":
324
  print("🚀 Travel Catalogue Creator Agent starting...")
325
+ print("💡 Example query: 'Create a 4-day Barcelona travel catalogue from New York with $1200 budget for October 15-19'")
326
  GradioUI(agent, file_upload_folder="./uploads").launch()