Spaces:
Sleeping
Sleeping
Update tools and itinerary generation
Browse files- app.py +65 -30
- models/itinerary_model.py +2 -3
- tools/google_maps_tool.py +2 -2
- tools/route_planner_tool.py +51 -43
- tools/semantic_ranking_tool.py +2 -2
- tools/weather_tool.py +8 -3
app.py
CHANGED
|
@@ -12,11 +12,10 @@ load_dotenv()
|
|
| 12 |
print("🔍 GOOGLE_MAPS_API_KEY found:", bool(os.getenv("GOOGLE_MAPS_API_KEY")))
|
| 13 |
print("🔍 OPENAI_API_KEY found:", bool(os.getenv("OPENAI_API_KEY")))
|
| 14 |
|
| 15 |
-
# ✅ Ensure LiteLLM knows you’re using OpenAI models
|
| 16 |
os.environ["LITELLM_PROVIDER"] = "openai"
|
| 17 |
os.environ["OPENAI_API_BASE"] = "https://api.openai.com/v1"
|
| 18 |
|
| 19 |
-
#
|
| 20 |
if not os.getenv("OPENAI_API_KEY"):
|
| 21 |
raise ValueError("Missing OPENAI_API_KEY")
|
| 22 |
if not os.getenv("GOOGLE_MAPS_API_KEY"):
|
|
@@ -56,7 +55,7 @@ retriever_agent = Agent(
|
|
| 56 |
tools=[maps_tool],
|
| 57 |
llm="gpt-4o-mini",
|
| 58 |
temperature=0.2,
|
| 59 |
-
verbose=
|
| 60 |
)
|
| 61 |
|
| 62 |
weather_agent = Agent(
|
|
@@ -66,7 +65,7 @@ weather_agent = Agent(
|
|
| 66 |
tools=[weather_tool],
|
| 67 |
llm="gpt-4o-mini",
|
| 68 |
temperature=0.2,
|
| 69 |
-
verbose=
|
| 70 |
)
|
| 71 |
|
| 72 |
route_agent = Agent(
|
|
@@ -74,9 +73,9 @@ route_agent = Agent(
|
|
| 74 |
goal="Calculate walking, driving, cycling, public transport times between trip locations to optimize daily routes.",
|
| 75 |
backstory="A navigtion expert skilled at minimizing time and building efficient routes.",
|
| 76 |
tools=[route_tool],
|
| 77 |
-
llm="gpt-
|
| 78 |
temperature=0.25,
|
| 79 |
-
verbose=
|
| 80 |
)
|
| 81 |
|
| 82 |
planner_agent = Agent(
|
|
@@ -91,7 +90,7 @@ planner_agent = Agent(
|
|
| 91 |
temperature=0.3,
|
| 92 |
output_schema=ItineraryModel,
|
| 93 |
reasoning=True,
|
| 94 |
-
verbose=
|
| 95 |
)
|
| 96 |
|
| 97 |
writer_agent = Agent(
|
|
@@ -108,15 +107,20 @@ writer_agent = Agent(
|
|
| 108 |
),
|
| 109 |
llm="gpt-4o",
|
| 110 |
temperature=0.7,
|
| 111 |
-
verbose=
|
| 112 |
)
|
| 113 |
|
| 114 |
# Core logic
|
| 115 |
def generate_itinerary(location, start_date, end_date, preferences, transport_modes ):
|
| 116 |
-
|
| 117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
|
| 119 |
-
#Define the tasks
|
| 120 |
|
| 121 |
retrieval_task = Task(
|
| 122 |
description=f"Gather restaurants, landmarks, and activities for the trip in {location}.",
|
|
@@ -133,13 +137,18 @@ def generate_itinerary(location, start_date, end_date, preferences, transport_mo
|
|
| 133 |
)
|
| 134 |
|
| 135 |
route_task = Task(
|
| 136 |
-
description=(
|
| 137 |
-
"
|
| 138 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
),
|
| 140 |
-
expected_output="A list of routes with mode,
|
| 141 |
agent=route_agent,
|
| 142 |
-
|
| 143 |
)
|
| 144 |
|
| 145 |
planning_task = Task(
|
|
@@ -174,9 +183,40 @@ def generate_itinerary(location, start_date, end_date, preferences, transport_mo
|
|
| 174 |
" - weather_forecast (if applicable)\n"
|
| 175 |
" - distance_from_prev (km)\n"
|
| 176 |
" - travel_duration_min (if applicable)\n\n"
|
| 177 |
-
"🔟
|
| 178 |
-
"
|
| 179 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
),
|
| 181 |
expected_output="A structured JSON itinerary with complete metadata and travel-aware timestamps for each activity.",
|
| 182 |
context=[retrieval_task, weather_task, route_task],
|
|
@@ -190,9 +230,9 @@ def generate_itinerary(location, start_date, end_date, preferences, transport_mo
|
|
| 190 |
description=(
|
| 191 |
"You are a professional travel writer. Given a structured itinerary JSON with detailed fields "
|
| 192 |
"(rating, reasoning, distance_from_prev, weather_forecast), write an engaging Markdown itinerary.\n\n"
|
| 193 |
-
"Make sure the itinerary is covering the period {start_date} to {end_date}.\n\n"
|
| 194 |
"At the top of the Markdown output, include a summary line like:\n"
|
| 195 |
-
"'**Trip Dates:** {start_date} → {end_date} \n"
|
| 196 |
"Each day must include:\n"
|
| 197 |
"- The date and weather summary from the JSON.\n"
|
| 198 |
"- Chronological itinerary entries with start and end times.\n"
|
|
@@ -229,15 +269,10 @@ def generate_itinerary(location, start_date, end_date, preferences, transport_mo
|
|
| 229 |
})
|
| 230 |
|
| 231 |
# Safely extract the result
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
# Ensure final output is always a string
|
| 238 |
-
if not isinstance(markdown_itinerary, str):
|
| 239 |
-
markdown_itinerary = str(markdown_itinerary)
|
| 240 |
-
|
| 241 |
return markdown_itinerary
|
| 242 |
|
| 243 |
# Gradio UI
|
|
|
|
| 12 |
print("🔍 GOOGLE_MAPS_API_KEY found:", bool(os.getenv("GOOGLE_MAPS_API_KEY")))
|
| 13 |
print("🔍 OPENAI_API_KEY found:", bool(os.getenv("OPENAI_API_KEY")))
|
| 14 |
|
|
|
|
| 15 |
os.environ["LITELLM_PROVIDER"] = "openai"
|
| 16 |
os.environ["OPENAI_API_BASE"] = "https://api.openai.com/v1"
|
| 17 |
|
| 18 |
+
# Confirm key is available
|
| 19 |
if not os.getenv("OPENAI_API_KEY"):
|
| 20 |
raise ValueError("Missing OPENAI_API_KEY")
|
| 21 |
if not os.getenv("GOOGLE_MAPS_API_KEY"):
|
|
|
|
| 55 |
tools=[maps_tool],
|
| 56 |
llm="gpt-4o-mini",
|
| 57 |
temperature=0.2,
|
| 58 |
+
verbose=False
|
| 59 |
)
|
| 60 |
|
| 61 |
weather_agent = Agent(
|
|
|
|
| 65 |
tools=[weather_tool],
|
| 66 |
llm="gpt-4o-mini",
|
| 67 |
temperature=0.2,
|
| 68 |
+
verbose=False
|
| 69 |
)
|
| 70 |
|
| 71 |
route_agent = Agent(
|
|
|
|
| 73 |
goal="Calculate walking, driving, cycling, public transport times between trip locations to optimize daily routes.",
|
| 74 |
backstory="A navigtion expert skilled at minimizing time and building efficient routes.",
|
| 75 |
tools=[route_tool],
|
| 76 |
+
llm="gpt-4o-mini",
|
| 77 |
temperature=0.25,
|
| 78 |
+
verbose=False
|
| 79 |
)
|
| 80 |
|
| 81 |
planner_agent = Agent(
|
|
|
|
| 90 |
temperature=0.3,
|
| 91 |
output_schema=ItineraryModel,
|
| 92 |
reasoning=True,
|
| 93 |
+
verbose=False
|
| 94 |
)
|
| 95 |
|
| 96 |
writer_agent = Agent(
|
|
|
|
| 107 |
),
|
| 108 |
llm="gpt-4o",
|
| 109 |
temperature=0.7,
|
| 110 |
+
verbose=False
|
| 111 |
)
|
| 112 |
|
| 113 |
# Core logic
|
| 114 |
def generate_itinerary(location, start_date, end_date, preferences, transport_modes ):
|
| 115 |
+
days, date_list = expand_dates(start_date, end_date)
|
| 116 |
+
trip_duration_days = days
|
| 117 |
+
|
| 118 |
+
if isinstance(transport_modes, str):
|
| 119 |
+
transport_modes = [transport_modes]
|
| 120 |
+
|
| 121 |
+
transport_modes_str = ", ".join(transport_modes)
|
| 122 |
|
| 123 |
+
#Define the tasks
|
| 124 |
|
| 125 |
retrieval_task = Task(
|
| 126 |
description=f"Gather restaurants, landmarks, and activities for the trip in {location}.",
|
|
|
|
| 137 |
)
|
| 138 |
|
| 139 |
route_task = Task(
|
| 140 |
+
description=(
|
| 141 |
+
f"From the RetrieverTask output, build a destinations list (strings) using the "
|
| 142 |
+
f"top 10 places across categories (prefer formatted address if present, else name + city). "
|
| 143 |
+
f"Use origin='{location}'. Then call Route Planner Tool exactly once with:\n"
|
| 144 |
+
f"- origin: '{location}'\n"
|
| 145 |
+
f"- destinations: <that list>\n"
|
| 146 |
+
f"- modes: {transport_modes}\n"
|
| 147 |
+
f"Return the tool output as JSON."
|
| 148 |
),
|
| 149 |
+
expected_output="A JSON list of routes with mode, distance_km, duration_min, destination.",
|
| 150 |
agent=route_agent,
|
| 151 |
+
context=[retrieval_task]
|
| 152 |
)
|
| 153 |
|
| 154 |
planning_task = Task(
|
|
|
|
| 183 |
" - weather_forecast (if applicable)\n"
|
| 184 |
" - distance_from_prev (km)\n"
|
| 185 |
" - travel_duration_min (if applicable)\n\n"
|
| 186 |
+
"🔟 Output format (MUST match ItineraryModel exactly):\n"
|
| 187 |
+
"{\n"
|
| 188 |
+
' "destination": "<city/region>",\n'
|
| 189 |
+
' "trip_duration_days": <int>,\n'
|
| 190 |
+
' "transport_mode": ["walking", "public_transport", "driving", "cycling"],\n'
|
| 191 |
+
' "start_date": "YYYY-MM-DD",\n'
|
| 192 |
+
' "end_date": "YYYY-MM-DD",\n'
|
| 193 |
+
' "traveler_profile": "<short preference summary>",\n'
|
| 194 |
+
' "days": [\n'
|
| 195 |
+
" {\n"
|
| 196 |
+
' "date": "YYYY-MM-DD",\n'
|
| 197 |
+
' "weather_summary": "<string>",\n'
|
| 198 |
+
' "summary": "<string>",\n'
|
| 199 |
+
' "activities": [\n'
|
| 200 |
+
" {\n"
|
| 201 |
+
' "name": "<place name>",\n'
|
| 202 |
+
' "category": "<breakfast|lunch|dinner|museum|park|landmark|...>",\n'
|
| 203 |
+
' "start_time": "HH:MM",\n'
|
| 204 |
+
' "end_time": "HH:MM",\n'
|
| 205 |
+
' "location": "<address>",\n'
|
| 206 |
+
' "map_url": "<optional>",\n'
|
| 207 |
+
' "rating": <optional float>,\n'
|
| 208 |
+
' "reasoning": "<optional>",\n'
|
| 209 |
+
' "weather_forecast": "<optional>",\n'
|
| 210 |
+
' "distance_from_prev": <optional float>,\n'
|
| 211 |
+
' "duration_minutes": <optional int>\n'
|
| 212 |
+
" }\n"
|
| 213 |
+
" ]\n"
|
| 214 |
+
" }\n"
|
| 215 |
+
" ],\n"
|
| 216 |
+
' "total_distance_km": <optional float>,\n'
|
| 217 |
+
' "notes": "<optional>"\n'
|
| 218 |
+
"}\n"
|
| 219 |
+
"⚠️ Use field name 'activities' (NOT events). Use 'duration_minutes' (NOT travel_duration_min)."
|
| 220 |
),
|
| 221 |
expected_output="A structured JSON itinerary with complete metadata and travel-aware timestamps for each activity.",
|
| 222 |
context=[retrieval_task, weather_task, route_task],
|
|
|
|
| 230 |
description=(
|
| 231 |
"You are a professional travel writer. Given a structured itinerary JSON with detailed fields "
|
| 232 |
"(rating, reasoning, distance_from_prev, weather_forecast), write an engaging Markdown itinerary.\n\n"
|
| 233 |
+
f"Make sure the itinerary is covering the period {start_date} to {end_date}.\n\n"
|
| 234 |
"At the top of the Markdown output, include a summary line like:\n"
|
| 235 |
+
f"'**Trip Dates:** {start_date} → {end_date} \n"
|
| 236 |
"Each day must include:\n"
|
| 237 |
"- The date and weather summary from the JSON.\n"
|
| 238 |
"- Chronological itinerary entries with start and end times.\n"
|
|
|
|
| 269 |
})
|
| 270 |
|
| 271 |
# Safely extract the result
|
| 272 |
+
markdown_itinerary = (
|
| 273 |
+
result if isinstance(result, str)
|
| 274 |
+
else getattr(result, "raw", None) or str(result)
|
| 275 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
return markdown_itinerary
|
| 277 |
|
| 278 |
# Gradio UI
|
models/itinerary_model.py
CHANGED
|
@@ -14,10 +14,10 @@ class Activity(BaseModel):
|
|
| 14 |
rating: Optional[float] = Field(None, description="Average rating score (if available)")
|
| 15 |
reasoning: Optional[str] = Field(None, description="Reason why this activity was selected")
|
| 16 |
weather_forecast: Optional[str] = Field(None, description="Expected weather at this time/location")
|
|
|
|
| 17 |
distance_from_prev: Optional[float] = Field(None, description="Distance from previous activity in kilometers")
|
| 18 |
duration_minutes: Optional[int] = Field(None, description="Automatically computed duration in minutes.")
|
| 19 |
|
| 20 |
-
|
| 21 |
# 📅 Daily itinerary
|
| 22 |
class DayPlan(BaseModel):
|
| 23 |
date: str = Field(..., description="Date of the plan, ISO format YYYY-MM-DD")
|
|
@@ -25,12 +25,11 @@ class DayPlan(BaseModel):
|
|
| 25 |
summary: Optional[str] = Field(None, description="High-level overview of the day")
|
| 26 |
activities: List[Activity] = Field(..., description="Ordered list of daily activities and meals")
|
| 27 |
|
| 28 |
-
|
| 29 |
# 🌍 Entire trip plan
|
| 30 |
class ItineraryModel(BaseModel):
|
| 31 |
destination: str = Field(..., description="City or region of the trip")
|
| 32 |
trip_duration_days: int = Field(..., description="Number of days in the itinerary")
|
| 33 |
-
|
| 34 |
start_date: Optional[str] = Field(None, description="Trip start date (ISO format)")
|
| 35 |
end_date: Optional[str] = Field(None, description="End date (YYYY-MM-DD).")
|
| 36 |
traveler_profile: Optional[str] = Field(None, description="Short description of traveler preferences.")
|
|
|
|
| 14 |
rating: Optional[float] = Field(None, description="Average rating score (if available)")
|
| 15 |
reasoning: Optional[str] = Field(None, description="Reason why this activity was selected")
|
| 16 |
weather_forecast: Optional[str] = Field(None, description="Expected weather at this time/location")
|
| 17 |
+
travel_mode: Optional[str] = Field(None, description="Mode used to reach this activity (walking/public_transport/driving/cycling)")
|
| 18 |
distance_from_prev: Optional[float] = Field(None, description="Distance from previous activity in kilometers")
|
| 19 |
duration_minutes: Optional[int] = Field(None, description="Automatically computed duration in minutes.")
|
| 20 |
|
|
|
|
| 21 |
# 📅 Daily itinerary
|
| 22 |
class DayPlan(BaseModel):
|
| 23 |
date: str = Field(..., description="Date of the plan, ISO format YYYY-MM-DD")
|
|
|
|
| 25 |
summary: Optional[str] = Field(None, description="High-level overview of the day")
|
| 26 |
activities: List[Activity] = Field(..., description="Ordered list of daily activities and meals")
|
| 27 |
|
|
|
|
| 28 |
# 🌍 Entire trip plan
|
| 29 |
class ItineraryModel(BaseModel):
|
| 30 |
destination: str = Field(..., description="City or region of the trip")
|
| 31 |
trip_duration_days: int = Field(..., description="Number of days in the itinerary")
|
| 32 |
+
transport_modes: List[str] = Field(default_factory=list, description="Allowed modes: walking, public_transport, driving, cycling")
|
| 33 |
start_date: Optional[str] = Field(None, description="Trip start date (ISO format)")
|
| 34 |
end_date: Optional[str] = Field(None, description="End date (YYYY-MM-DD).")
|
| 35 |
traveler_profile: Optional[str] = Field(None, description="Short description of traveler preferences.")
|
tools/google_maps_tool.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
import os
|
| 2 |
import httpx
|
| 3 |
from typing import List, Dict, Optional
|
| 4 |
-
from
|
| 5 |
|
| 6 |
-
class GoogleMapsTool(
|
| 7 |
"""
|
| 8 |
CrewAI compatible toool for querying Google Places Api
|
| 9 |
"""
|
|
|
|
| 1 |
import os
|
| 2 |
import httpx
|
| 3 |
from typing import List, Dict, Optional
|
| 4 |
+
from crewai.tools import BaseTool
|
| 5 |
|
| 6 |
+
class GoogleMapsTool(BaseTool):
|
| 7 |
"""
|
| 8 |
CrewAI compatible toool for querying Google Places Api
|
| 9 |
"""
|
tools/route_planner_tool.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
| 1 |
import os
|
| 2 |
import httpx
|
| 3 |
from typing import List, Dict, Union, Optional
|
| 4 |
-
from
|
| 5 |
|
| 6 |
|
| 7 |
-
class RoutePlannerTool(
|
| 8 |
"""
|
| 9 |
CrewAI-compatible tool for computing optimal routes between locations
|
| 10 |
using Google Directions API with multi-mode transport support.
|
|
@@ -47,49 +47,57 @@ class RoutePlannerTool(RagTool):
|
|
| 47 |
base_url = "https://maps.googleapis.com/maps/api/directions/json"
|
| 48 |
results = []
|
| 49 |
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
if
|
| 74 |
-
best_route
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
"""Fetch and parse route data from Google Directions API."""
|
| 91 |
try:
|
| 92 |
-
response =
|
| 93 |
data = response.json()
|
| 94 |
|
| 95 |
if data["status"] != "OK" or not data.get("routes"):
|
|
|
|
| 1 |
import os
|
| 2 |
import httpx
|
| 3 |
from typing import List, Dict, Union, Optional
|
| 4 |
+
from crewai.tools import BaseTool
|
| 5 |
|
| 6 |
|
| 7 |
+
class RoutePlannerTool(BaseTool):
|
| 8 |
"""
|
| 9 |
CrewAI-compatible tool for computing optimal routes between locations
|
| 10 |
using Google Directions API with multi-mode transport support.
|
|
|
|
| 47 |
base_url = "https://maps.googleapis.com/maps/api/directions/json"
|
| 48 |
results = []
|
| 49 |
|
| 50 |
+
with httpx.Client(timeout=20.0) as client:
|
| 51 |
+
for dest in destinations[:max_results]:
|
| 52 |
+
best_route = None
|
| 53 |
+
|
| 54 |
+
# Try walking first
|
| 55 |
+
if "walking" in modes:
|
| 56 |
+
walk_params = {"origin": origin, "destination": dest, "mode": "walking", "key": api_key}
|
| 57 |
+
walk_data = self._fetch_route(base_url, walk_params)
|
| 58 |
+
if walk_data:
|
| 59 |
+
best_route = {**walk_data, "mode": "walking"}
|
| 60 |
+
|
| 61 |
+
# Try public transport if walking route > 2km or not available
|
| 62 |
+
if ("public_transport" in modes or "transit" in modes) and (
|
| 63 |
+
not best_route or best_route["distance_km"] > 2
|
| 64 |
+
):
|
| 65 |
+
transit_params = {
|
| 66 |
+
"origin": origin,
|
| 67 |
+
"destination": dest,
|
| 68 |
+
"mode": "transit",
|
| 69 |
+
"transit_mode": "bus|subway|train|tram",
|
| 70 |
+
"key": api_key
|
| 71 |
+
}
|
| 72 |
+
transit_data = self._fetch_route(base_url, transit_params)
|
| 73 |
+
if transit_data:
|
| 74 |
+
if not best_route or transit_data["duration_min"] < best_route["duration_min"]:
|
| 75 |
+
best_route = {**transit_data, "mode": "public_transport"}
|
| 76 |
+
|
| 77 |
+
# Cycling if included
|
| 78 |
+
if "cycling" in modes and not best_route:
|
| 79 |
+
bike_params = {"origin": origin, "destination": dest, "mode": "bicycling", "key": api_key}
|
| 80 |
+
bike_data = self._fetch_route(client, base_url, bike_params)
|
| 81 |
+
if bike_data:
|
| 82 |
+
best_route = {**bike_data, "mode": "cycling"}
|
| 83 |
+
|
| 84 |
+
# Try driving if included and others unavailable
|
| 85 |
+
if "driving" in modes and not best_route:
|
| 86 |
+
drive_params = {"origin": origin, "destination": dest, "mode": "driving", "key": api_key}
|
| 87 |
+
drive_data = self._fetch_route(base_url, drive_params)
|
| 88 |
+
if drive_data:
|
| 89 |
+
best_route = {**drive_data, "mode": "driving"}
|
| 90 |
+
|
| 91 |
+
if best_route:
|
| 92 |
+
best_route["destination"] = dest
|
| 93 |
+
results.append(best_route)
|
| 94 |
+
|
| 95 |
+
return results
|
| 96 |
+
|
| 97 |
+
def _fetch_route(self, client: httpx.Client, base_url: str, params: Dict) -> Optional[Dict]:
|
| 98 |
"""Fetch and parse route data from Google Directions API."""
|
| 99 |
try:
|
| 100 |
+
response = client.get(base_url, params=params, timeout=20.0)
|
| 101 |
data = response.json()
|
| 102 |
|
| 103 |
if data["status"] != "OK" or not data.get("routes"):
|
tools/semantic_ranking_tool.py
CHANGED
|
@@ -2,13 +2,13 @@ import os
|
|
| 2 |
import numpy as np
|
| 3 |
from typing import List, Dict, Any, Optional
|
| 4 |
from openai import OpenAI
|
| 5 |
-
from
|
| 6 |
from dotenv import load_dotenv
|
| 7 |
|
| 8 |
load_dotenv()
|
| 9 |
os.environ["CHROMA_OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY")
|
| 10 |
|
| 11 |
-
class SemanticRankingTool(
|
| 12 |
"""
|
| 13 |
CrewAI-compatible tool that semantically ranks and filters candidate places
|
| 14 |
based on user preferences, ratings, popularity, and optional distance.
|
|
|
|
| 2 |
import numpy as np
|
| 3 |
from typing import List, Dict, Any, Optional
|
| 4 |
from openai import OpenAI
|
| 5 |
+
from crewai.tools import BaseTool
|
| 6 |
from dotenv import load_dotenv
|
| 7 |
|
| 8 |
load_dotenv()
|
| 9 |
os.environ["CHROMA_OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY")
|
| 10 |
|
| 11 |
+
class SemanticRankingTool(BaseTool):
|
| 12 |
"""
|
| 13 |
CrewAI-compatible tool that semantically ranks and filters candidate places
|
| 14 |
based on user preferences, ratings, popularity, and optional distance.
|
tools/weather_tool.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
import httpx
|
| 2 |
-
from
|
| 3 |
from pydantic import BaseModel, Field
|
| 4 |
from typing import Dict, Optional, Any, Type
|
| 5 |
from datetime import date, timedelta
|
|
@@ -11,10 +11,11 @@ class WeatherToolInput(BaseModel):
|
|
| 11 |
city: Optional[str] = Field(None, description="City name, e.g. 'Florence, Italy'.")
|
| 12 |
latitude: Optional[float] = Field(None, description="Latitude in decimal degrees.")
|
| 13 |
longitude: Optional[float] = Field(None, description="Longitude in decimal degrees.")
|
| 14 |
-
|
|
|
|
| 15 |
|
| 16 |
# 🌤️ CrewAI Tool
|
| 17 |
-
class WeatherTool(
|
| 18 |
"""Fetches multi-day weather forecasts using the Open-Meteo API."""
|
| 19 |
|
| 20 |
name: str = "Weather Forecast Tool"
|
|
@@ -65,6 +66,10 @@ class WeatherTool(RagTool):
|
|
| 65 |
"timezone": "auto",
|
| 66 |
}
|
| 67 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
resp = httpx.get(base_url, params=params, timeout=10.0)
|
| 69 |
data = resp.json()
|
| 70 |
days = data.get("daily", {})
|
|
|
|
| 1 |
import httpx
|
| 2 |
+
from crewai.tools import BaseTool
|
| 3 |
from pydantic import BaseModel, Field
|
| 4 |
from typing import Dict, Optional, Any, Type
|
| 5 |
from datetime import date, timedelta
|
|
|
|
| 11 |
city: Optional[str] = Field(None, description="City name, e.g. 'Florence, Italy'.")
|
| 12 |
latitude: Optional[float] = Field(None, description="Latitude in decimal degrees.")
|
| 13 |
longitude: Optional[float] = Field(None, description="Longitude in decimal degrees.")
|
| 14 |
+
start_date: Optional[str] = Field(None, description="YYYY-MM-DD")
|
| 15 |
+
end_date: Optional[str] = Field(None, description="YYYY-MM-DD")
|
| 16 |
|
| 17 |
# 🌤️ CrewAI Tool
|
| 18 |
+
class WeatherTool(BaseTool):
|
| 19 |
"""Fetches multi-day weather forecasts using the Open-Meteo API."""
|
| 20 |
|
| 21 |
name: str = "Weather Forecast Tool"
|
|
|
|
| 66 |
"timezone": "auto",
|
| 67 |
}
|
| 68 |
|
| 69 |
+
if start_date and end_date:
|
| 70 |
+
params["start_date"] = start_date
|
| 71 |
+
params["end_date"] = end_date
|
| 72 |
+
|
| 73 |
resp = httpx.get(base_url, params=params, timeout=10.0)
|
| 74 |
data = resp.json()
|
| 75 |
days = data.get("daily", {})
|