This document outlines the implementation plan for replacing placeholder tools with real API integrations to provide actual, up-to-date travel information.
All tools currently return deterministic placeholder strings. The LLM generates itineraries based solely on its training data, using tools only as semantic markers for workflow structure.
Current Output:
"Key info for {destination}: mild weather, popular sights, local etiquette."Proposed APIs:
-
OpenWeatherMap API (Primary)
- Endpoint:
api.openweathermap.org/data/2.5/forecast - Features: 5-day forecast, current conditions, UV index
- Cost: Free tier: 1000 calls/day
- API Key Required: Yes
- Endpoint:
-
WeatherAPI (Alternative)
- Endpoint:
api.weatherapi.com/v1/forecast.json - Features: 14-day forecast, historical weather, astronomy data
- Cost: Free tier: 1M calls/month
- API Key Required: Yes
- Endpoint:
Implementation:
import httpx
from datetime import datetime
from typing import Dict, Any
@tool
async def get_weather_info(destination: str, travel_dates: Optional[str] = None) -> str:
"""Get real weather forecast and conditions for destination."""
api_key = os.getenv("OPENWEATHER_API_KEY")
# Geocode destination first
geocode_url = f"http://api.openweathermap.org/geo/1.0/direct"
params = {"q": destination, "limit": 1, "appid": api_key}
async with httpx.AsyncClient() as client:
geo_response = await client.get(geocode_url, params=params)
if geo_response.status_code == 200:
location = geo_response.json()[0]
lat, lon = location['lat'], location['lon']
# Get weather forecast
weather_url = f"http://api.openweathermap.org/data/2.5/forecast"
weather_params = {
"lat": lat,
"lon": lon,
"appid": api_key,
"units": "metric"
}
weather_response = await client.get(weather_url, params=weather_params)
if weather_response.status_code == 200:
data = weather_response.json()
# Process and format weather data
temps = [item['main']['temp'] for item in data['list'][:5]]
avg_temp = sum(temps) / len(temps)
conditions = data['list'][0]['weather'][0]['description']
return f"""Weather for {destination}:
- Current conditions: {conditions}
- Average temperature: {avg_temp:.1f}°C ({avg_temp*9/5+32:.1f}°F)
- Pack for: {get_packing_suggestion(avg_temp)}
- Best time to visit outdoor attractions: {get_best_times(data)}"""
return f"Weather data temporarily unavailable for {destination}"Current Output:
"Budget for {destination} over {duration}: lodging, food, transit, attractions."Proposed APIs:
-
Numbeo API (Cost of Living)
- Features: Restaurant prices, transport costs, accommodation estimates
- Cost: Paid API ($600/year for commercial use)
- Alternative: Web scraping (with permission)
-
Budget Your Trip API
- Features: Average daily costs by destination and travel style
- Cost: Free tier available
- API Key Required: Yes
Implementation:
@tool
async def get_budget_breakdown(
destination: str,
duration: str,
budget: Optional[str] = None,
travel_style: Optional[str] = "mid-range"
) -> str:
"""Get real budget breakdown based on destination costs."""
# Parse duration to days
days = parse_duration_to_days(duration)
# Get cost data from Numbeo or fallback source
costs = await fetch_destination_costs(destination)
# Calculate budget breakdown
daily_costs = {
"budget": {
"accommodation": costs.get("hostel_price", 25),
"food": costs.get("cheap_meal", 10) * 3,
"transport": costs.get("public_transport_day", 5),
"activities": 10
},
"mid-range": {
"accommodation": costs.get("hotel_3star", 80),
"food": costs.get("mid_meal", 25) * 3,
"transport": costs.get("taxi_day", 20),
"activities": 30
},
"luxury": {
"accommodation": costs.get("hotel_5star", 250),
"food": costs.get("fine_dining", 50) * 3,
"transport": costs.get("car_rental", 80),
"activities": 100
}
}
style_costs = daily_costs.get(travel_style, daily_costs["mid-range"])
total_per_day = sum(style_costs.values())
total_trip = total_per_day * days
return f"""Budget breakdown for {destination} ({duration}/{days} days):
Daily costs ({travel_style}):
- Accommodation: ${style_costs['accommodation']}/night
- Meals: ${style_costs['food']}/day
- Local transport: ${style_costs['transport']}/day
- Activities & entrance fees: ${style_costs['activities']}/day
Total daily: ${total_per_day}
Total trip estimate: ${total_trip}
Money-saving tips:
- Book accommodation in advance for better rates
- Eat where locals eat for authentic and affordable meals
- Use public transport or walk when possible
- Look for free walking tours and activities"""Current Output:
"Local experiences for {destination}: authentic food, culture, and {interests or 'top picks'}."Proposed APIs:
-
TripAdvisor API (via RapidAPI)
- Features: Top attractions, restaurants, activities by location
- Cost: Free tier: 500 calls/month
- API Key Required: Yes
-
Google Places API
- Features: Detailed place information, reviews, photos, opening hours
- Cost: $17 per 1000 requests (after free tier)
- API Key Required: Yes
-
Foursquare Places API
- Features: Venue recommendations, tips, categories
- Cost: Free tier: 100,000 calls/month
- API Key Required: Yes
Implementation:
@tool
async def get_local_recommendations(
destination: str,
interests: Optional[str] = None,
limit: int = 10
) -> str:
"""Get real local recommendations from multiple sources."""
foursquare_key = os.getenv("FOURSQUARE_API_KEY")
# Parse interests into Foursquare categories
categories = map_interests_to_categories(interests)
async with httpx.AsyncClient() as client:
# Search for places
url = "https://api.foursquare.com/v3/places/search"
headers = {
"Authorization": foursquare_key,
"Accept": "application/json"
}
params = {
"near": destination,
"categories": ",".join(categories),
"limit": limit,
"sort": "RATING"
}
response = await client.get(url, headers=headers, params=params)
if response.status_code == 200:
places = response.json()['results']
recommendations = []
for place in places[:5]:
rec = {
"name": place['name'],
"category": place['categories'][0]['name'],
"address": place['location'].get('formatted_address', 'Address not available'),
"rating": place.get('rating', 'Not rated'),
"tip": await get_venue_tip(place['fsq_id'], client, headers)
}
recommendations.append(rec)
return format_recommendations(destination, interests, recommendations)
return f"Local recommendations temporarily unavailable for {destination}"
def format_recommendations(destination: str, interests: str, recommendations: list) -> str:
"""Format recommendations into readable text."""
output = f"Top local experiences in {destination}"
if interests:
output += f" for {interests}:\n\n"
else:
output += ":\n\n"
for i, rec in enumerate(recommendations, 1):
output += f"{i}. {rec['name']} ({rec['category']})\n"
output += f" 📍 {rec['address']}\n"
if rec['rating']:
output += f" ⭐ Rating: {rec['rating']}/10\n"
if rec['tip']:
output += f" 💡 Tip: {rec['tip']}\n"
output += "\n"
return outputCurrent Output:
"Attraction prices in {destination}: Museum: $10-$40, Historic Site: $10-$40, Viewpoint: $10-$40"Proposed APIs:
-
GetYourGuide API
- Features: Tour prices, attraction tickets, availability
- Cost: Affiliate commission-based
- API Key Required: Yes (Partner account)
-
Viator API (TripAdvisor)
- Features: Tours, activities, prices, availability
- Cost: Affiliate commission-based
- API Key Required: Yes (Partner account)
Implementation:
@tool
async def get_attraction_pricing(
destination: str,
attractions: Optional[List[str]] = None
) -> str:
"""Get real attraction prices and ticket information."""
viator_key = os.getenv("VIATOR_API_KEY")
if not attractions:
# Get top attractions for destination
attractions = await get_top_attractions(destination)
pricing_info = []
async with httpx.AsyncClient() as client:
for attraction in attractions[:5]:
# Search for tours/tickets
search_url = "https://viator.com/api/search"
params = {
"destination": destination,
"query": attraction,
"currency": "USD"
}
headers = {"api-key": viator_key}
response = await client.get(search_url, headers=headers, params=params)
if response.status_code == 200:
results = response.json()
if results['products']:
product = results['products'][0]
pricing_info.append({
"name": product['title'],
"price": product['price']['amount'],
"currency": product['price']['currency'],
"duration": product.get('duration', 'Variable'),
"includes": product.get('inclusions', [])
})
return format_pricing_info(destination, pricing_info)Current Output:
"Visa guidance for {destination}: check your nationality's embassy site."Proposed APIs:
-
Sherpa API (Visa & Travel Requirements)
- Features: Visa requirements by nationality, COVID restrictions, travel documents
- Cost: Enterprise pricing
- API Key Required: Yes
-
IATA Travel Centre API
- Features: Entry requirements, health requirements, customs
- Cost: Paid service
- Alternative: Scrape public Timatic data
Implementation:
@tool
async def get_visa_requirements(
destination_country: str,
nationality: str = "US",
trip_duration: Optional[int] = 30
) -> str:
"""Get real visa and entry requirements."""
# For demonstration, using a simplified approach
# In production, would integrate with Sherpa or IATA
visa_db = {
# Simplified visa database
("Thailand", "US"): {
"visa_required": False,
"visa_on_arrival": True,
"max_stay": 30,
"requirements": ["Valid passport (6+ months)", "Return ticket", "Proof of accommodation"]
},
("Japan", "US"): {
"visa_required": False,
"visa_on_arrival": False,
"max_stay": 90,
"requirements": ["Valid passport", "Return ticket"]
},
# ... more entries
}
key = (destination_country, nationality)
if key in visa_db:
info = visa_db[key]
return f"""Visa requirements for {nationality} citizens visiting {destination_country}:
Visa required: {'Yes' if info['visa_required'] else 'No'}
Visa on arrival: {'Available' if info['visa_on_arrival'] else 'Not available'}
Maximum stay: {info['max_stay']} days
Requirements:
{format_requirements(info['requirements'])}
⚠️ Always verify with official sources before travel."""
return f"Please check official embassy website for {destination_country} visa requirements"Proposed APIs:
-
Amadeus API
- Features: Flight search, pricing, availability
- Cost: Free tier: 500 calls/month
- API Key Required: Yes
-
Skyscanner API
- Features: Flight search, price comparison
- Cost: RapidAPI pricing tiers
- API Key Required: Yes
Implementation:
@tool
async def get_flight_options(
origin: str,
destination: str,
departure_date: Optional[str] = None,
return_date: Optional[str] = None,
budget: Optional[int] = None
) -> str:
"""Get real flight options and pricing."""
amadeus_key = os.getenv("AMADEUS_API_KEY")
amadeus_secret = os.getenv("AMADEUS_API_SECRET")
# Get access token
token = await get_amadeus_token(amadeus_key, amadeus_secret)
# Search for flights
search_url = "https://api.amadeus.com/v2/shopping/flight-offers"
headers = {"Authorization": f"Bearer {token}"}
params = {
"originLocationCode": get_airport_code(origin),
"destinationLocationCode": get_airport_code(destination),
"departureDate": departure_date or get_next_month_date(),
"adults": 1,
"max": 5
}
if return_date:
params["returnDate"] = return_date
async with httpx.AsyncClient() as client:
response = await client.get(search_url, headers=headers, params=params)
if response.status_code == 200:
flights = response.json()['data']
return format_flight_options(flights, budget)
return "Flight information temporarily unavailable"-
Weather API (OpenWeatherMap)
- Essential for trip planning
- Free tier sufficient for MVP
-
Local Recommendations (Foursquare)
- Rich venue data
- Generous free tier
-
Budget Data (Numbeo scraping or API)
- Critical for budget planning
- Consider caching strategies
-
Attraction Pricing (Viator/GetYourGuide)
- Affiliate revenue potential
- Real booking capability
-
Flight Search (Amadeus)
- Complete trip planning
- Price comparison features
-
Visa Requirements (Build database or Sherpa API)
- Essential for international travel
- Consider subscription cost
- Hotel Recommendations (Booking.com API)
- Restaurant Reservations (OpenTable API)
- Car Rental Options (RentalCars API)
- Travel Insurance (Partner APIs)
# .env file structure
OPENWEATHER_API_KEY=xxx
FOURSQUARE_API_KEY=xxx
VIATOR_API_KEY=xxx
AMADEUS_API_KEY=xxx
AMADEUS_API_SECRET=xxx
GOOGLE_PLACES_API_KEY=xxxfrom functools import lru_cache
from datetime import datetime, timedelta
import redis
# Redis for distributed caching
redis_client = redis.Redis(host='localhost', port=6379, db=0)
def cache_api_response(key: str, data: Any, ttl: int = 3600):
"""Cache API responses to reduce API calls and costs."""
redis_client.setex(key, ttl, json.dumps(data))
def get_cached_response(key: str) -> Optional[Any]:
"""Retrieve cached API response."""
data = redis_client.get(key)
return json.loads(data) if data else None
# Rate limiting decorator
def rate_limit(calls_per_minute: int):
def decorator(func):
last_called = []
async def wrapper(*args, **kwargs):
now = time.time()
# Remove calls older than 1 minute
last_called[:] = [t for t in last_called if now - t < 60]
if len(last_called) >= calls_per_minute:
wait_time = 60 - (now - last_called[0])
await asyncio.sleep(wait_time)
last_called.append(now)
return await func(*args, **kwargs)
return wrapper
return decoratorclass APIError(Exception):
"""Custom exception for API errors."""
pass
async def fetch_with_fallback(
primary_func,
fallback_func,
*args,
**kwargs
):
"""Try primary API, fall back to secondary if failed."""
try:
return await primary_func(*args, **kwargs)
except (APIError, httpx.RequestError):
logger.warning(f"Primary API failed, using fallback")
return await fallback_func(*args, **kwargs)Convert all tools to async for better performance:
# Update agent functions to handle async tools
async def research_agent(state: TripState) -> TripState:
# ... agent logic with await for async tools
pass| API | Free Tier | Paid Tier | Monthly Est. |
|---|---|---|---|
| OpenWeatherMap | 1000/day | $0 | $0 |
| Foursquare | 100k/month | $299/month | $0 (free tier) |
| Google Places | $200 credit | $17/1000 | ~$50 |
| Amadeus | 500/month | Variable | $0 (free tier) |
| Viator | Commission | Commission | $0 (commission) |
| Total | ~$50/month |
- Aggressive Caching: Cache responses for 24-48 hours
- Batch Requests: Combine multiple queries when possible
- Conditional Fetching: Only call APIs when data is needed
- Tiered Service: Premium users get real-time data, free users get cached
- Fallback to LLM: Use GPT knowledge when API limits reached
- Never expose keys in client-side code
- Use environment variables
- Implement key rotation
- Monitor usage for anomalies
- Implement per-user rate limits
- Use Redis for distributed rate limiting
- Queue system for high load
- Don't store personal travel data longer than necessary
- Implement GDPR compliance
- Anonymize analytics data
class MockWeatherAPI:
async def get_weather(self, destination: str):
return {
"temp": 22,
"condition": "sunny",
"forecast": "clear skies"
}
# Use mocks in test environment
if os.getenv("ENVIRONMENT") == "test":
weather_api = MockWeatherAPI()
else:
weather_api = RealWeatherAPI()- Test each API integration separately
- Test fallback mechanisms
- Test rate limiting behavior
- Test caching functionality
- Simulate concurrent users
- Test API rate limits
- Monitor response times
- Test cache performance
def track_api_usage(api_name: str, endpoint: str, status: str):
"""Track API usage for monitoring and optimization."""
metrics = {
"api": api_name,
"endpoint": endpoint,
"status": status,
"timestamp": datetime.now().isoformat()
}
# Send to monitoring service (e.g., Datadog, CloudWatch)
send_metrics(metrics)- Track API calls per user
- Monitor approaching limits
- Alert on unusual usage
- Monthly cost reports
- API response times
- Cache hit rates
- Error rates by API
- User experience metrics
- Keep placeholder tools
- Add real API tools alongside
- Feature flag to switch between them
- Test with internal users
- Roll out to 10% of users
- Monitor performance and costs
- Full rollout when stable
- Remove placeholder tools
- Update documentation
- Monitor for issues
This specification provides a roadmap for transforming the AI Trip Planner from a demo system with placeholder data to a production-ready application with real, actionable travel information. The phased approach allows for gradual implementation while managing costs and complexity.
Key benefits of real API integration:
- Accurate Information: Real-time weather, prices, and availability
- Better User Experience: Actionable recommendations with current data
- Revenue Potential: Affiliate commissions from bookings
- Competitive Advantage: Superior to generic AI-only solutions
Next steps:
- Prioritize APIs based on user needs
- Set up API accounts and keys
- Implement Phase 1 APIs
- Test with real users
- Iterate based on feedback