In this tutorial, learn to build trip planning system that combines the power of large language models, vector databases, and weather APIs
Planning the perfect trip involves juggling countless details, from destinations and budgets to weather forecasts and daily itineraries. What if you could streamline this entire process using AI? In this comprehensive guide, we'll walk you through building a sophisticated trip planning system that combines the power of large language models, vector databases, and weather APIs to create truly intelligent travel recommendations.
Our project combines two powerful components into a seamless travel planning experience:
The magic happens when these systems work together: you can plan your trip with AI and immediately get weather insights to make informed decisions about activities, packing, and timing.
Before diving into the implementation, let's understand the key technologies powering our system:
The workflow is elegantly simple: users describe their trip in natural language, AI structures this into detailed plans, everything gets saved to a vector database for future reference, and weather data enhances the planning with location-specific insights.
The trip planner transforms casual requests into comprehensive travel plans. A user might simply type:
"I want a 5-day trip to Paris in October with 2 people, budget around $3000"
Within seconds, they receive a detailed itinerary, budget breakdown, and personalized tips, all generated by AI and saved for future reference.
The system operates through a carefully orchestrated process:
Step 1: Capture User Intent: The AI first extracts structured information from natural language input, identifying key details like destination, dates, traveler count, and budget constraints.
Step 2: Generate Comprehensive Plans: Using the extracted details, the system creates detailed itineraries including day-by-day activities, budget allocations, and contextual recommendations.
Step 3: Vector Storage: Each trip plan is converted into vector embeddings and stored in vectorDB Qdrant, enabling semantic search and intelligent recommendations for future trips.
Step 4: Human-Readable Output: The structured data is transformed into beautifully formatted summaries that travelers can actually use.
Here's the complete implementation:
import os
import asyncio
import uuid
from qdrant_client.http import models
from dotenv import load_dotenv
from pydantic import BaseModel
from pydantic_ai import Agent
from qdrant_client import QdrantClient
from qdrant_client.models import VectorParams, Distance
from sentence_transformers import SentenceTransformer
import json
load_dotenv()
user_input_example = input("Enter your trip request: ")
client = QdrantClient(host="localhost", port=6333)
model = SentenceTransformer("all-MiniLM-L6-v2")
if not client.collection_exists("trips"):
client.create_collection(
collection_name="trips",
vectors_config=models.VectorParams(size=384, distance=models.Distance.COSINE),
)
def get_embedding(text: str):
return model.encode(text).tolist()
class TripBooking(BaseModel):
destination: str
date: str
travelers: int
budget: str | None = None
notes: str | None = None
duration_days: int | None = None
class ItineraryDay(BaseModel):
day: int
title: str
activities: list[str]
class TripPlan(BaseModel):
destination: str
date: dict # {"start": str, "end": str}
travelers: int
budget: int | None
duration_days: int
overview: str
itinerary: list[ItineraryDay]
budget_breakdown: dict # {"flights": int, "accommodation": int, "attractions": int, "dining": int, "total": int}
tips: list[str]
def save_trip(trip: TripBooking, plan: TripPlan):
client.upsert(
collection_name="trips",
points=[
models.PointStruct(
id=str(uuid.uuid4()),
vector=get_embedding(trip.destination),
payload={
"destination": trip.destination,
"date": trip.date,
"travelers": trip.travelers,
"budget": trip.budget,
"notes": trip.notes,
"duration_days": trip.duration_days,
"plan": plan.model_dump() # Save clean structured plan
}
)
]
)
print(f"Trip for {trip.destination} saved in Qdrant")
booking_agent = Agent("openai:gpt-4o-mini")
user_input = user_input_example
async def main():
# Step 1: Extract trip details
structured_prompt = f"""
Extract trip details from the following request and return **only valid JSON**
matching this schema:
{{
"destination": str,
"date": str,
"travelers": int,
"budget": str,
"notes": str,
"duration_days": int
}}
Request: "{user_input}"
"""
result = await booking_agent.run(structured_prompt)
raw_text = result.output.strip()
try:
trip = TripBooking.model_validate_json(raw_text)
except Exception:
json_str = raw_text[raw_text.find("{"): raw_text.rfind("}")+1]
trip = TripBooking.model_validate_json(json_str)
# Step 2: Generate structured plan
itinerary_prompt = f"""
Create a structured JSON itinerary with this schema:
{{
"destination": str,
"date": {{"start": str, "end": str}},
"travelers": int,
"budget": int,
"duration_days": int,
"overview": str,
"itinerary": [{{"day": int, "title": str, "activities": [str]}}],
"budget_breakdown": {{"flights": int, "accommodation": int, "attractions": int, "dining": int, "total": int}},
"tips": [str]
}}
Details:
Destination: {trip.destination}
Date: {trip.date}
Travelers: {trip.travelers}
Budget: {trip.budget}
Duration: {trip.duration_days or "N/A"} days
Notes: {trip.notes or "N/A"}
"""
response = await booking_agent.run(itinerary_prompt)
raw_plan = response.output.strip()
try:
plan = TripPlan.model_validate_json(raw_plan)
except Exception:
json_str = raw_plan[raw_plan.find("{"): raw_plan.rfind("}")+1]
plan = TripPlan.model_validate_json(json_str)
save_trip(trip, plan)
# print("\n Trip saved in Qdrant!")
# print(json.dumps(plan.model_dump(), indent=2))
def trip_plan_to_text(plan: TripPlan) -> str:
summary = f"Trip to {plan.destination}\n"
summary += f"Dates: {plan.date['start']} to {plan.date['end']}\n"
summary += f"Travelers: {plan.travelers}\n"
summary += f"Duration: {plan.duration_days} days\n"
summary += f"Budget: {plan.budget or 'Not provided'}\n\n"
summary += "Overview:\n"
summary += f"{plan.overview}\n\n"
summary += "Itinerary:\n"
for day in plan.itinerary:
summary += f"Day {day.day}: {day.title}\n"
for act in day.activities:
summary += f" - {act}\n"
summary += "\n"
summary += "Budget Breakdown:\n"
for key, value in plan.budget_breakdown.items():
summary += f" {key.capitalize()}: {value if value is not None else 'N/A'}\n"
summary += "\n"
summary += "Tips:\n"
for tip in plan.tips:
summary += f" - {tip}\n"
return summary
text_summary = trip_plan_to_text(plan)
print(text_summary)
if __name__ == "__main__":
asyncio.run(main())
One of the key innovations in this system is how it handles data transformation. By using Pydantic models, we ensure that every piece of information is properly validated and structured. This not only prevents errors but also makes the data highly portable and searchable.
The trip plan model captures everything a traveler needs: destinations, dates, budgets, detailed itineraries, and practical tips. When stored as vectors in Qdrant, these plans become part of an intelligent knowledge base that can suggest similar trips or help with future planning.
Weather can make or break a travel experience. Our weather integration goes beyond simple forecasts—it provides historical context, helps with packing decisions, and can even suggest optimal activity timing based on conditions.
The weather system interprets natural language requests and converts them into precise API calls. Whether someone asks, "What will the weather be like in London tomorrow?" or, "Show me historical weather for Tokyo last December," the system understands and responds appropriately.
Here's the complete weather integration:
from pydantic_ai import Agent
from pydantic import BaseModel
from dotenv import load_dotenv
import requests, os, json
from typing import List
load_dotenv()
WEATHER_API_KEY = os.getenv("WEATER_API_KEY")
class WeatherQuery(BaseModel):
location: str
dates: List[str]
agent = Agent("openai:gpt-4o-mini")
class WeatherReport(BaseModel):
location: str
date: str
temperature_celsius: float
condition: str
humidity: int
wind_kph: float
def fetch_weather(location: str, target_dates: List[str]) -> List[WeatherReport]:
reports = []
# Forecast endpoint
forecast_url = "http://api.weatherapi.com/v1/forecast.json"
forecast_params = {"key": WEATHER_API_KEY, "q": location, "days": 10}
forecast_resp = requests.get(forecast_url, params=forecast_params).json()
# Check if API returned error
if "error" in forecast_resp:
print("API error:", forecast_resp["error"]["message"])
return []
forecast_days = forecast_resp["forecast"]["forecastday"]
loc_name = forecast_resp["location"]["name"]
for date in target_dates:
report_data = None
# Check if date is in forecast
for day in forecast_days:
if day["date"] == date:
report_data = day
break
# If date not in forecast, use history endpoint
if not report_data:
history_url = "http://api.weatherapi.com/v1/history.json"
history_params = {"key": WEATHER_API_KEY, "q": location, "dt": date}
history_resp = requests.get(history_url, params=history_params).json()
if "error" in history_resp:
print(f"API error for date {date}: {history_resp['error']['message']}")
continue
report_data = history_resp["forecast"]["forecastday"][0]
# Build structured WeatherReport
reports.append(
WeatherReport(
location=loc_name,
date=report_data["date"],
temperature_celsius=report_data["day"]["avgtemp_c"],
condition=report_data["day"]["condition"]["text"],
humidity=report_data["day"]["avghumidity"],
wind_kph=report_data["day"]["maxwind_kph"],
)
)
return reports
nlq = input("What do you know about weather: ")
strict_prompt = f"""
Extract the weather query from the user's request.
Return ONLY JSON, nothing else, no text, no markdown, not even a single work extra.
most importantly convert the date into YYYY-MM-DD format.
if the user provides a date as today or tomorrow or yesterday convert it to the actual date.
JSON schema:
{json.dumps(WeatherQuery.model_json_schema(), indent=2)}
User request: "{nlq}"
"""
result = agent.run_sync(strict_prompt)
parsed_json = result.output.strip()
query = WeatherQuery.model_validate_json(parsed_json)
reports = fetch_weather(query.location, query.dates)
# for report in reports:
# print(report.model_dump_json(indent=2))
def json_report_to_nlq(reports: List[WeatherReport]) -> str:
if not reports:
return "No weather data available."
summary = f"Weather report for {reports[0].location}:\n"
for report in reports:
summary += (
f"Date: {report.date}\n"
f"Temperature: {report.temperature_celsius}°C\n"
f"Condition: {report.condition}\n"
f"Humidity: {report.humidity}%\n"
f"Wind: {report.wind_kph} kph\n\n"
)
return summary
print(json_report_to_nlq(reports))
One of the sophisticated features of our weather system is its natural language date processing. The AI automatically converts relative dates like "tomorrow," "next week," or "last month" into precise ISO format dates that the weather API can understand. This seamless translation makes the system incredibly user-friendly.
The system intelligently determines whether to fetch forecast data for future dates or historical data for past dates. This dual capability means users can both plan upcoming trips and analyze weather patterns from previous travel periods to make better decisions.
Getting this powerful system running on your machine is straightforward. Here's everything you need:
git clone https://github.com/piyushghughu-superteams-dotcom/trip_planner_weather_report_ai_agent.git
cd trip_planner_weather_report_ai_agent
Qdrant runs beautifully in Docker, making setup effortless:
docker run -p 6333:6333 qdrant/qdrant
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
Create a .env file with your credentials:
OPENAI_API_KEY=your_openai_key
WEATHER_API_KEY=your_weatherapi_key
For trip planning:
python backend/data_loading.py
For weather reports:
python backend/weather_report.py
This system isn't just a technical demonstration—it solves real problems for various user groups:
Qdrant was chosen for its excellent performance with embedding similarity searches. When users search for "trips like my Paris adventure," the system can find semantically similar trips even if the exact words don't match.
Using Pydantic ensures that all data flowing through our system is properly structured and validated. This prevents the kind of data inconsistencies that can plague AI applications and makes debugging much easier.
The trip planner uses asyncio for handling AI API calls efficiently. This becomes crucial when scaling to handle multiple concurrent users or when integrating additional data sources.
This foundation opens up numerous possibilities for expansion:
Building AI-powered applications requires thoughtful architecture that balances user experience with technical capabilities. Our trip planner demonstrates several important principles:
The complete source code for this project is available on GitHub, ready for you to explore, modify, and extend for your own applications.
Whether you're building travel applications, exploring AI integration patterns, or learning about vector databases, this project provides a solid foundation and demonstrates practical approaches to common challenges in modern software development.
To learn more, speak to us.