ChienChung commited on
Commit
b0509a0
·
verified ·
1 Parent(s): 4d231c2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +171 -121
app.py CHANGED
@@ -452,87 +452,112 @@ weather_api_key = os.environ.get("WEATHER_API_KEY")
452
 
453
  def weather_agent_tool(query: str) -> str:
454
  try:
455
- from dateutil import parser as date_parser
456
- from zoneinfo import ZoneInfo
457
- from geopy.geocoders import Nominatim
458
- from timezonefinder import TimezoneFinder
459
-
460
  weather_api_key = os.environ.get("WEATHER_API_KEY")
461
  if not weather_api_key:
462
  return "Weather API key not found. Please set WEATHER_API_KEY env variable."
463
 
464
- # Step 1: Use LLM to extract location (Few-Shot examples included)
465
  location_prompt = f"""
466
  You are a location extractor. Given a user's query about weather, extract the location mentioned in it.
467
- If no location is found, return "London".
468
 
469
  Examples:
470
- - "What's the weather in Tokyo now?" → Tokyo
471
- - "Will it rain tomorrow in New York?" → New York
472
- - "台北會下雨?" → Taipei
473
- - "How's the weather?" → London
474
- - "Is it hot in LDN later?" → London
475
 
476
- Now extract the location from: "{query}"
477
  """
478
- location_response = llm_gpt4.invoke(location_prompt)
479
- location = location_response.content.strip() if isinstance(location_response, AIMessage) else str(location_response).strip()
480
 
481
- # Step 2: Convert location to local time
482
- geo = Nominatim(user_agent="weather_agent_demo")
483
  loc = geo.geocode(location)
 
 
484
  tf = TimezoneFinder()
485
- tz_str = tf.timezone_at(lat=loc.latitude, lng=loc.longitude) if loc else "Europe/London"
486
  local_now = datetime.now(ZoneInfo(tz_str))
487
- local_now_str = local_now.strftime("%Y-%m-%d %H:%M:%S")
488
 
489
- # Step 3: Use LLM to extract intended date (Few-Shot style prompt)
490
  time_prompt = f"""
491
- Today is {local_now_str} in {location}.
 
 
 
492
 
493
- You are a time extractor. Given a weather-related question, convert the user's time reference into a local date.
494
- Use format: YYYY-MM-DD.
495
  Examples:
496
- - "What's the weather in New York now?" → {local_now.strftime('%Y-%m-%d')}
497
- - "Will it rain in Paris tomorrow?" → {(local_now + timedelta(days=1)).strftime('%Y-%m-%d')}
498
- - "How's the weather in Tokyo in 2 hours?" → {local_now.strftime('%Y-%m-%d')}
499
- - "昨天倫敦的天氣如何?" → {(local_now - timedelta(days=1)).strftime('%Y-%m-%d')}
500
- - "Weather in Taipei this weekend?" → {local_now.strftime('%Y-%m-%d')} (or closest matching day)
501
- If unclear, assume today.
502
  Query: "{query}"
503
- Return only the date like 2025-04-04.
504
  """
505
- date_response = llm_gpt4.invoke(time_prompt)
506
- date_str = date_response.content.strip() if isinstance(date_response, AIMessage) else str(date_response).strip()
507
- query_date = date_parser.parse(date_str).date()
508
- today = local_now.date()
509
-
510
- # Step 4: Query WeatherAPI with appropriate endpoint
511
- if query_date == today:
512
- url = f"http://api.weatherapi.com/v1/current.json?key={weather_api_key}&q={location}&aqi=no"
513
- data = requests.get(url).json()
514
- cur = data["current"]
515
- return f"The current weather in {location.title()} is {cur['condition']['text']} with {cur['temp_c']}°C (feels like {cur['feelslike_c']}°C), humidity {cur['humidity']}%, wind {cur['wind_kph']} kph."
516
-
517
- elif query_date > today:
518
- url = f"http://api.weatherapi.com/v1/forecast.json?key={weather_api_key}&q={location}&days=3"
519
- data = requests.get(url).json()
520
- forecast_days = data.get("forecast", {}).get("forecastday", [])
521
- for day in forecast_days:
522
- if day["date"] == date_str:
523
- d = day["day"]
524
- condition = d['condition']['text'].lower()
525
- rain_comment = "Yes, it might rain." if "rain" in condition else "No rain expected."
526
- return f"{rain_comment} Forecast for {location.title()} on {query_date.strftime('%A, %B %d')}: {d['condition']['text']}, avg {d['avgtemp_c']}°C, humidity {d['avghumidity']}%, max {d['maxtemp_c']}°C, min {d['mintemp_c']}°C."
527
- return f"Forecast unavailable for {query_date} (WeatherAPI supports up to 3 days ahead)."
528
-
529
  else:
530
- url = f"http://api.weatherapi.com/v1/history.json?key={weather_api_key}&q={location}&dt={date_str}"
531
- data = requests.get(url).json()
532
- hist = data.get("forecast", {}).get("forecastday", [{}])[0].get("day", {})
533
- if not hist:
534
- return f"Historical weather not found for {location.title()} on {query_date}."
535
- return f"The weather in {location.title()} on {query_date.strftime('%A, %B %d')}: {hist['condition']['text']}, avg {hist['avgtemp_c']}°C, humidity {hist['avghumidity']}%, max {hist['maxtemp_c']}°C, min {hist['mintemp_c']}°C."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
536
 
537
  except Exception as e:
538
  return f"Weather Agent Error: {e}"
@@ -540,89 +565,114 @@ Return only the date like 2025-04-04.
540
 
541
  @tool("weather")
542
  def weather_tool(query: str) -> str:
543
- """Weather Agent: Provide real-time, future, or historical weather info (max 3 days forecast, 7 days history)."""
544
  try:
545
- from dateutil import parser as date_parser
546
- from zoneinfo import ZoneInfo
547
- from geopy.geocoders import Nominatim
548
- from timezonefinder import TimezoneFinder
549
-
550
  weather_api_key = os.environ.get("WEATHER_API_KEY")
551
  if not weather_api_key:
552
  return "Weather API key not found. Please set WEATHER_API_KEY env variable."
553
 
554
- # Step 1: Use LLM to extract location (Few-Shot examples included)
555
  location_prompt = f"""
556
  You are a location extractor. Given a user's query about weather, extract the location mentioned in it.
557
- If no location is found, return "London".
558
 
559
  Examples:
560
- - "What's the weather in Tokyo now?" → Tokyo
561
- - "Will it rain tomorrow in New York?" → New York
562
- - "台北會下雨?" → Taipei
563
- - "How's the weather?" → London
564
- - "Is it hot in LDN later?" → London
565
 
566
- Now extract the location from: "{query}"
567
  """
568
- location_response = llm_gpt4.invoke(location_prompt)
569
- location = location_response.content.strip() if isinstance(location_response, AIMessage) else str(location_response).strip()
570
 
571
- # Step 2: Convert location to local time
572
- geo = Nominatim(user_agent="weather_agent_demo")
573
  loc = geo.geocode(location)
 
 
574
  tf = TimezoneFinder()
575
- tz_str = tf.timezone_at(lat=loc.latitude, lng=loc.longitude) if loc else "Europe/London"
576
  local_now = datetime.now(ZoneInfo(tz_str))
577
- local_now_str = local_now.strftime("%Y-%m-%d %H:%M:%S")
578
 
579
- # Step 3: Use LLM to extract intended date (Few-Shot style prompt)
580
  time_prompt = f"""
581
- Today is {local_now_str} in {location}.
 
 
 
582
 
583
- You are a time extractor. Given a weather-related question, convert the user's time reference into a local date.
584
- Use format: YYYY-MM-DD.
585
  Examples:
586
- - "What's the weather in New York now?" → {local_now.strftime('%Y-%m-%d')}
587
- - "Will it rain in Paris tomorrow?" → {(local_now + timedelta(days=1)).strftime('%Y-%m-%d')}
588
- - "How's the weather in Tokyo in 2 hours?" → {local_now.strftime('%Y-%m-%d')}
589
- - "昨天倫敦的天氣如何?" → {(local_now - timedelta(days=1)).strftime('%Y-%m-%d')}
590
- - "Weather in Taipei this weekend?" → {local_now.strftime('%Y-%m-%d')} (or closest matching day)
591
- If unclear, assume today.
592
  Query: "{query}"
593
- Return only the date like 2025-04-04.
594
  """
595
- date_response = llm_gpt4.invoke(time_prompt)
596
- date_str = date_response.content.strip() if isinstance(date_response, AIMessage) else str(date_response).strip()
597
- query_date = date_parser.parse(date_str).date()
598
- today = local_now.date()
599
-
600
- # Step 4: Query WeatherAPI with appropriate endpoint
601
- if query_date == today:
602
- url = f"http://api.weatherapi.com/v1/current.json?key={weather_api_key}&q={location}&aqi=no"
603
- data = requests.get(url).json()
604
- cur = data["current"]
605
- return f"The current weather in {location.title()} is {cur['condition']['text']} with {cur['temp_c']}°C (feels like {cur['feelslike_c']}°C), humidity {cur['humidity']}%, wind {cur['wind_kph']} kph."
606
-
607
- elif query_date > today:
608
- url = f"http://api.weatherapi.com/v1/forecast.json?key={weather_api_key}&q={location}&days=3"
609
- data = requests.get(url).json()
610
- forecast_days = data.get("forecast", {}).get("forecastday", [])
611
- for day in forecast_days:
612
- if day["date"] == date_str:
613
- d = day["day"]
614
- condition = d['condition']['text'].lower()
615
- rain_comment = "Yes, it might rain." if "rain" in condition else "No rain expected."
616
- return f"{rain_comment} Forecast for {location.title()} on {query_date.strftime('%A, %B %d')}: {d['condition']['text']}, avg {d['avgtemp_c']}°C, humidity {d['avghumidity']}%, max {d['maxtemp_c']}°C, min {d['mintemp_c']}°C."
617
- return f"Forecast unavailable for {query_date} (WeatherAPI supports up to 3 days ahead)."
618
-
619
  else:
620
- url = f"http://api.weatherapi.com/v1/history.json?key={weather_api_key}&q={location}&dt={date_str}"
621
- data = requests.get(url).json()
622
- hist = data.get("forecast", {}).get("forecastday", [{}])[0].get("day", {})
623
- if not hist:
624
- return f"Historical weather not found for {location.title()} on {query_date}."
625
- return f"The weather in {location.title()} on {query_date.strftime('%A, %B %d')}: {hist['condition']['text']}, avg {hist['avgtemp_c']}°C, humidity {hist['avghumidity']}%, max {hist['maxtemp_c']}°C, min {hist['mintemp_c']}°C."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
626
 
627
  except Exception as e:
628
  return f"Weather Agent Error: {e}"
 
452
 
453
  def weather_agent_tool(query: str) -> str:
454
  try:
 
 
 
 
 
455
  weather_api_key = os.environ.get("WEATHER_API_KEY")
456
  if not weather_api_key:
457
  return "Weather API key not found. Please set WEATHER_API_KEY env variable."
458
 
459
+ # Step 1: Extract location
460
  location_prompt = f"""
461
  You are a location extractor. Given a user's query about weather, extract the location mentioned in it.
462
+ If not found, return "London".
463
 
464
  Examples:
465
+ - "Is it gonna rain in Tokyo?" → Tokyo
466
+ - "Will it be hot in New York later?" → New York
467
+ - "下午高雄不會下雨?" → Kaohsiung
468
+ - "Hows the weather?" → London
 
469
 
470
+ Query: "{query}"
471
  """
472
+ location_resp = llm_gpt4.invoke(location_prompt)
473
+ location = location_resp.content.strip() if isinstance(location_resp, AIMessage) else str(location_resp).strip()
474
 
475
+ # Step 2: Get timezone
476
+ geo = Nominatim(user_agent="weather_agent")
477
  loc = geo.geocode(location)
478
+ if not loc:
479
+ return f"Unable to resolve location: {location}"
480
  tf = TimezoneFinder()
481
+ tz_str = tf.timezone_at(lat=loc.latitude, lng=loc.longitude) or "Europe/London"
482
  local_now = datetime.now(ZoneInfo(tz_str))
 
483
 
484
+ # Step 3: Extract intended datetime
485
  time_prompt = f"""
486
+ Today is {local_now.strftime('%Y-%m-%d %H:%M:%S')} in {location}.
487
+
488
+ You are a time extractor. Given a user's question about weather, extract the local date and hour it refers to.
489
+ Return format: YYYY-MM-DD HH:00 (24h format)
490
 
 
 
491
  Examples:
492
+ - "Is it gonna rain in Paris in 2 hours?" → {(local_now + timedelta(hours=2)).strftime('%Y-%m-%d %H:00')}
493
+ - "Will it be cold tomorrow?" → {(local_now + timedelta(days=1)).strftime('%Y-%m-%d 12:00')}
494
+ - "今天下午高雄會熱嗎?" → {(local_now.replace(hour=15)).strftime('%Y-%m-%d %H:00')}
495
+ - "What's the weather?" → {local_now.strftime('%Y-%m-%d %H:00')}
496
+
 
497
  Query: "{query}"
 
498
  """
499
+ time_resp = llm_gpt4.invoke(time_prompt)
500
+ time_str = time_resp.content.strip() if isinstance(time_resp, AIMessage) else str(time_resp).strip()
501
+ target_dt = date_parser.parse(time_str)
502
+
503
+ # Step 4: Call WeatherAPI
504
+ if target_dt.date() < local_now.date() - timedelta(days=7):
505
+ return "Only supports up to 7 days of historical data."
506
+ elif target_dt.date() > local_now.date() + timedelta(days=2):
507
+ return "Only supports up to 3 days of forecast."
508
+
509
+ if target_dt.date() < local_now.date():
510
+ # Use history API
511
+ url = f"http://api.weatherapi.com/v1/history.json?key={weather_api_key}&q={location}&dt={target_dt.strftime('%Y-%m-%d')}"
 
 
 
 
 
 
 
 
 
 
 
512
  else:
513
+ # Use forecast API (for today or future)
514
+ url = f"http://api.weatherapi.com/v1/forecast.json?key={weather_api_key}&q={location}&days=3&aqi=no&alerts=no"
515
+
516
+ data = requests.get(url).json()
517
+ forecast_hours = []
518
+ if "forecast" in data:
519
+ for day in data["forecast"]["forecastday"]:
520
+ for hour in day["hour"]:
521
+ forecast_hours.append(hour)
522
+ elif "forecastday" in data:
523
+ forecast_hours = data["forecastday"][0]["hour"]
524
+ else:
525
+ return "No forecast data available."
526
+
527
+ # Step 5: Find closest hour
528
+ min_diff = float("inf")
529
+ closest_hour = None
530
+ for hour_data in forecast_hours:
531
+ hour_dt = date_parser.parse(hour_data["time"]).astimezone(ZoneInfo(tz_str))
532
+ diff = abs((hour_dt - target_dt).total_seconds())
533
+ if diff < min_diff:
534
+ min_diff = diff
535
+ closest_hour = hour_data
536
+
537
+ if not closest_hour:
538
+ return f"No hourly data found for {target_dt.strftime('%Y-%m-%d %H:%M')}."
539
+
540
+ # Step 6: Generate summary
541
+ condition = closest_hour["condition"]["text"]
542
+ temp = closest_hour["temp_c"]
543
+ feels = closest_hour["feelslike_c"]
544
+ humidity = closest_hour["humidity"]
545
+ chance_rain = closest_hour.get("chance_of_rain", 0)
546
+ hour_str = closest_hour["time"].split(" ")[1]
547
+
548
+ summary_prompt = f"""
549
+ Summarise this weather forecast naturally:
550
+ Location: {location}
551
+ Time: {target_dt.strftime('%Y-%m-%d')} at {hour_str}
552
+ Condition: {condition}
553
+ Temp: {temp}°C (Feels like {feels}°C)
554
+ Humidity: {humidity}%
555
+ Chance of rain: {chance_rain}%
556
+
557
+ Make it short, friendly, and human-style.
558
+ """
559
+ response = llm_gpt4.invoke(summary_prompt)
560
+ return response.content.strip() if isinstance(response, AIMessage) else str(response)
561
 
562
  except Exception as e:
563
  return f"Weather Agent Error: {e}"
 
565
 
566
  @tool("weather")
567
  def weather_tool(query: str) -> str:
568
+ """Weather Agent: Return current, hourly, or historical weather info using WeatherAPI."""
569
  try:
 
 
 
 
 
570
  weather_api_key = os.environ.get("WEATHER_API_KEY")
571
  if not weather_api_key:
572
  return "Weather API key not found. Please set WEATHER_API_KEY env variable."
573
 
574
+ # Step 1: Extract location
575
  location_prompt = f"""
576
  You are a location extractor. Given a user's query about weather, extract the location mentioned in it.
577
+ If not found, return "London".
578
 
579
  Examples:
580
+ - "Is it gonna rain in Tokyo?" → Tokyo
581
+ - "Will it be hot in New York later?" → New York
582
+ - "��午高雄不會下雨?" → Kaohsiung
583
+ - "Hows the weather?" → London
 
584
 
585
+ Query: "{query}"
586
  """
587
+ location_resp = llm_gpt4.invoke(location_prompt)
588
+ location = location_resp.content.strip() if isinstance(location_resp, AIMessage) else str(location_resp).strip()
589
 
590
+ # Step 2: Get timezone
591
+ geo = Nominatim(user_agent="weather_agent")
592
  loc = geo.geocode(location)
593
+ if not loc:
594
+ return f"Unable to resolve location: {location}"
595
  tf = TimezoneFinder()
596
+ tz_str = tf.timezone_at(lat=loc.latitude, lng=loc.longitude) or "Europe/London"
597
  local_now = datetime.now(ZoneInfo(tz_str))
 
598
 
599
+ # Step 3: Extract intended datetime
600
  time_prompt = f"""
601
+ Today is {local_now.strftime('%Y-%m-%d %H:%M:%S')} in {location}.
602
+
603
+ You are a time extractor. Given a user's question about weather, extract the local date and hour it refers to.
604
+ Return format: YYYY-MM-DD HH:00 (24h format)
605
 
 
 
606
  Examples:
607
+ - "Is it gonna rain in Paris in 2 hours?" → {(local_now + timedelta(hours=2)).strftime('%Y-%m-%d %H:00')}
608
+ - "Will it be cold tomorrow?" → {(local_now + timedelta(days=1)).strftime('%Y-%m-%d 12:00')}
609
+ - "今天下午高雄會熱嗎?" → {(local_now.replace(hour=15)).strftime('%Y-%m-%d %H:00')}
610
+ - "What's the weather?" → {local_now.strftime('%Y-%m-%d %H:00')}
611
+
 
612
  Query: "{query}"
 
613
  """
614
+ time_resp = llm_gpt4.invoke(time_prompt)
615
+ time_str = time_resp.content.strip() if isinstance(time_resp, AIMessage) else str(time_resp).strip()
616
+ target_dt = date_parser.parse(time_str)
617
+
618
+ # Step 4: Call WeatherAPI
619
+ if target_dt.date() < local_now.date() - timedelta(days=7):
620
+ return "Only supports up to 7 days of historical data."
621
+ elif target_dt.date() > local_now.date() + timedelta(days=2):
622
+ return "Only supports up to 3 days of forecast."
623
+
624
+ if target_dt.date() < local_now.date():
625
+ # Use history API
626
+ url = f"http://api.weatherapi.com/v1/history.json?key={weather_api_key}&q={location}&dt={target_dt.strftime('%Y-%m-%d')}"
 
 
 
 
 
 
 
 
 
 
 
627
  else:
628
+ # Use forecast API (for today or future)
629
+ url = f"http://api.weatherapi.com/v1/forecast.json?key={weather_api_key}&q={location}&days=3&aqi=no&alerts=no"
630
+
631
+ data = requests.get(url).json()
632
+ forecast_hours = []
633
+ if "forecast" in data:
634
+ for day in data["forecast"]["forecastday"]:
635
+ for hour in day["hour"]:
636
+ forecast_hours.append(hour)
637
+ elif "forecastday" in data:
638
+ forecast_hours = data["forecastday"][0]["hour"]
639
+ else:
640
+ return "No forecast data available."
641
+
642
+ # Step 5: Find closest hour
643
+ min_diff = float("inf")
644
+ closest_hour = None
645
+ for hour_data in forecast_hours:
646
+ hour_dt = date_parser.parse(hour_data["time"]).astimezone(ZoneInfo(tz_str))
647
+ diff = abs((hour_dt - target_dt).total_seconds())
648
+ if diff < min_diff:
649
+ min_diff = diff
650
+ closest_hour = hour_data
651
+
652
+ if not closest_hour:
653
+ return f"No hourly data found for {target_dt.strftime('%Y-%m-%d %H:%M')}."
654
+
655
+ # Step 6: Generate summary
656
+ condition = closest_hour["condition"]["text"]
657
+ temp = closest_hour["temp_c"]
658
+ feels = closest_hour["feelslike_c"]
659
+ humidity = closest_hour["humidity"]
660
+ chance_rain = closest_hour.get("chance_of_rain", 0)
661
+ hour_str = closest_hour["time"].split(" ")[1]
662
+
663
+ summary_prompt = f"""
664
+ Summarise this weather forecast naturally:
665
+ Location: {location}
666
+ Time: {target_dt.strftime('%Y-%m-%d')} at {hour_str}
667
+ Condition: {condition}
668
+ Temp: {temp}°C (Feels like {feels}°C)
669
+ Humidity: {humidity}%
670
+ Chance of rain: {chance_rain}%
671
+
672
+ Make it short, friendly, and human-style.
673
+ """
674
+ response = llm_gpt4.invoke(summary_prompt)
675
+ return response.content.strip() if isinstance(response, AIMessage) else str(response)
676
 
677
  except Exception as e:
678
  return f"Weather Agent Error: {e}"