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.
What We’re Building
Our project combines two powerful components into a seamless travel planning experience:
- AI Trip Planner: Converts natural language requests into structured travel plans and stores them intelligently for future reference
- Weather Integration: Provides accurate weather forecasts and historical data for any destination and date range
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.
The Architecture: How It All Fits Together
Before diving into the implementation, let’s understand the key technologies powering our system:
- Pydantic AI: Handles natural language processing and structured data extraction
- Qdrant Vector Database: Stores trip plans as searchable vectors for intelligent retrieval
- OpenAI GPT-4: Powers the conversational AI and planning logic
- WeatherAPI: Provides real-time forecasts and historical weather data
- Sentence Transformers: Creates semantic embeddings for trip storage and search
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.
Part 1: The AI Trip Planner
Understanding the User Experience
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.
How the Magic Happens
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())
The Power of Structured Data
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.
Part 2: Weather Intelligence Integration
Why Weather Matters in Trip 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.
Smart Weather Queries
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))
Intelligent Date Handling
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.
Forecast vs. Historical Data
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.
Setting Up Your Development Environment
Getting this powerful system running on your machine is straightforward. Here’s everything you need:
Step 1: Clone the Repository
git clone https://github.com/piyushghughu-superteams-dotcom/trip_planner_weather_report_ai_agent.git
cd trip_planner_weather_report_ai_agent
Step 2: Launch Qdrant Database
Qdrant runs beautifully in Docker, making setup effortless:
docker run -p 6333:6333 qdrant/qdrant
Step 3: Create Python Environment
python -m venv venv
source venv/bin/activate
Step 4: Install Dependencies
pip install -r requirements.txt
Step 5: Configure API Keys
Create a .env file with your credentials:
OPENAI_API_KEY=your_openai_key
WEATHER_API_KEY=your_weatherapi_key
Step 6: Run the Applications
For trip planning:
python backend/data_loading.py
For weather reports:
python backend/weather_report.py
Real-World Applications and Use Cases
This system isn’t just a technical demonstration—it solves real problems for various user groups:
- Travel Agencies can use it to quickly generate initial trip proposals for clients, then refine them based on weather insights and preferences.
- Individual Travelers benefit from having a personal AI travel assistant that remembers their preferences and provides weather-informed recommendations.
- Corporate Travel Managers can streamline business trip planning while ensuring travelers are prepared for destination weather conditions.
- Travel Bloggers and Influencers can use the historical weather data to plan content around seasonal destinations and optimal visiting times.
Technical Deep Dive: Why These Choices Matter
Vector Database Selection
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.
Pydantic for Data Validation
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.
Async Processing
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.
Future Enhancement Opportunities
This foundation opens up numerous possibilities for expansion:
- Social Features: Allow users to share trip plans and discover popular destinations through the vector database.
- Budget Optimization: Integrate flight and hotel pricing APIs to suggest cost-saving alternatives while maintaining trip quality.
- Real-time Updates: Add monitoring for weather changes and automatic trip adjustment suggestions.
- Mobile Integration: Build mobile apps that can access the stored trip data and provide location-aware recommendations.
- Machine Learning: Use the accumulated trip data to train models that can predict user preferences and suggest personalized destinations.
Key Takeaways
Building AI-powered applications requires thoughtful architecture that balances user experience with technical capabilities. Our trip planner demonstrates several important principles:
- Structure First: Using Pydantic models ensures data consistency and makes the system easier to extend and maintain.
- Smart Storage: Vector databases like Qdrant enable semantic search capabilities that traditional databases can’t match.
- API Integration: Combining multiple data sources (AI, weather, potentially flights/hotels) creates more valuable user experiences than any single source alone.
- Natural Language Processing: Modern AI can bridge the gap between how people naturally describe their needs and how systems process structured data.
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.