The travel industry faces a persistent challenge: travelers want personalized itineraries but planning is time-consuming and complex. When Sarah, a product manager at a travel startup, approached our team, she needed to build a conversational AI assistant that could understand user preferences, search for real-time flight and hotel data, and generate customized day-by-day travel plans—all through natural multi-turn dialogue.
In this comprehensive guide, we'll build a production-ready travel planning assistant using HolySheep AI's function calling capabilities, achieving sub-50ms latency while saving 85%+ compared to enterprise alternatives (¥1=$1 rate).
System Architecture Overview
Our travel planning system consists of three core components working in harmony:
- Conversation Manager: Maintains dialogue state and context across multiple turns
- Tool Registry: Centralized hub for weather, flights, hotels, and attractions APIs
- Itinerary Generator: Constructs coherent travel plans based on collected preferences
Setting Up the HolySheep AI Client
import os
import json
from typing import TypedDict, List, Optional, Dict, Any
from openai import OpenAI
HolySheep AI Configuration
Rate: ¥1=$1 — saves 85%+ vs ¥7.3 alternatives
Supports WeChat/Alipay for Chinese payment, <50ms latency
HOLYSHEEP_API_KEY = os.environ.get("HOLYSHEEP_API_KEY", "YOUR_HOLYSHEEP_API_KEY")
HOLYSHEEP_BASE_URL = "https://api.holysheep.ai/v1"
class TravelPreferences(TypedDict):
destination: str
start_date: str
end_date: str
budget: str
interests: List[str]
travel_style: str
class ConversationState:
def __init__(self):
self.messages: List[Dict[str, Any]] = []
self.preferences: Optional[TravelPreferences] = None
self.confirmed_tools: List[str] = []
def add_message(self, role: str, content: str, tool_calls: Optional[List] = None):
message = {"role": role, "content": content}
if tool_calls:
message["tool_calls"] = tool_calls
self.messages.append(message)
client = OpenAI(
api_key=HOLYSHEEP_API_KEY,
base_url=HOLYSHEEP_BASE_URL
)
Initialize conversation state
conversation = ConversationState()
print("HolySheep AI client initialized successfully")
print(f"Connected to: {HOLYSHEEP_BASE_URL}")
Defining Tool Functions for Travel Data
Travel planning requires real-time data from multiple sources. We'll define structured tools that the AI can call during conversation.
# Tool definitions for function calling
TRAVEL_TOOLS = [
{
"type": "function",
"function": {
"name": "search_flights",
"description": "Search for available flights between two cities",
"parameters": {
"type": "object",
"properties": {
"origin": {"type": "string", "description": "Origin city code (e.g., NYC, LAX)"},
"destination": {"type": "string", "description": "Destination city code"},
"departure_date": {"type": "string", "description": "Departure date in YYYY-MM-DD format"},
"return_date": Optional[str],
"passengers": {"type": "integer", "default": 1}
},
"required": ["origin", "destination", "departure_date"]
}
}
},
{
"type": "function",
"function": {
"name": "search_hotels",
"description": "Find hotels at destination matching budget and preferences",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"},
"checkin": {"type": "string", "description": "Check-in date YYYY-MM-DD"},
"checkout": {"type": "string", "description": "Check-out date YYYY-MM-DD"},
"budget_range": {"type": "string", "enum": ["budget", "mid-range", "luxury"]},
"amenities": {"type": "array", "items": {"type": "string"}}
},
"required": ["city", "checkin", "checkout"]
}
}
},
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather forecast for travel planning",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"},
"start_date": {"type": "string"},
"end_date": {"type": "string"}
},
"required": ["city", "start_date", "end_date"]
}
}
},
{
"type": "function",
"function": {
"name": "search_attractions",
"description": "Find tourist attractions and activities",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"},
"interests": {"type": "array", "items": {"type": "string"}},
"duration_days": {"type": "integer"}
},
"required": ["city"]
}
}
},
{
"type": "function",
"function": {
"name": "generate_itinerary",
"description": "Create detailed day-by-day travel plan",
"parameters": {
"type": "object",
"properties": {
"destination": {"type": "string"},
"duration": {"type": "integer", "description": "Number of days"},
"interests": {"type": "array"},
"flights": {"type": "object"},
"hotels": {"type": "object"},
"weather": {"type": "object"}
},
"required": ["destination", "duration", "interests"]
}
}
}
]
def execute_tool(tool_name: str, arguments: Dict) -> str:
"""Execute tool and return formatted results"""
# Simulated API responses - replace with actual API calls
mock_results = {
"search_flights": {
"status": "success",
"flights": [
{"airline": "SkyWings", "price": 420, "duration": "8h 30m", "stops": 1},
{"airline": "Global Air", "price": 580, "duration": "7h 15m", "stops": 0}
]
},
"search_hotels": {
"status": "success",
"hotels": [
{"name": "Seaside Resort", "rating": 4.5, "price_per_night": 85, "amenities": ["pool", "spa", "wifi"]},
{"name": "Urban Stay", "rating": 4.2, "price_per_night": 65, "amenities": ["wifi", "breakfast"]}
]
},
"get_weather": {
"status": "success",
"forecast": [
{"date": "2026-03-15", "condition": "Sunny", "temp_high": 24, "temp_low": 16},
{"date": "2026-03-16", "condition": "Partly Cloudy", "temp_high": 22, "temp_low": 15}
]
},
"search_attractions": {
"status": "success",
"attractions": [
{"name": "Ancient Temple", "rating": 4.8, "category": "cultural", "estimated_time": "3h"},
{"name": "Night Market", "rating": 4.6, "category": "food", "estimated_time": "2h"}
]
}
}
return json.dumps(mock_results.get(tool_name, {"status": "error", "message": "Unknown tool"}))
Building the Multi-turn Conversation Handler
The core intelligence lies in managing context across multiple turns. Our conversation handler maintains state and determines when to gather more information versus when to execute tools.
SYSTEM_PROMPT = """You are TravelBot, an expert travel planning assistant powered by HolySheep AI.
Your goal is to help users plan perfect trips through natural conversation. Follow these guidelines:
1. **Information Gathering**: Ask for one piece of information at a time. Essential info: destination, dates, budget, interests.
2. **Tool Usage**: When you have enough information, use available tools to fetch real data:
- Use search_flights, search_hotels, get_weather, and search_attractions to gather data
- Call generate_itinerary only after collecting all preferences and tool data
3. **Confirmation**: Always confirm gathered information before proceeding.
4. **Follow-up**: After generating an itinerary, offer adjustments or additional details.
Conversation style: Friendly, concise, and knowledgeable. Ask clarifying questions when needed.
"""
class TravelPlanner:
def __init__(self):
self.state = ConversationState()
self.tools = TRAVEL_TOOLS
self.required_fields = ["destination", "start_date", "end_date", "budget", "interests"]
def process_message(self, user_message: str) -> str:
"""Main entry point for processing user messages"""
self.state.add_message("user", user_message)
# Make initial API call
response = client.chat.completions.create(
model="gpt-4.1",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
*self.state.messages
],
tools=self.tools,
tool_choice="auto"
)
assistant_message = response.choices[0].message
response_content = assistant_message.content or ""
# Handle tool calls
if assistant_message.tool_calls:
tool_results = []
for tool_call in assistant_message.tool_calls:
tool_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
print(f"Executing tool: {tool_name} with args: {arguments}")
result = execute_tool(tool_name, arguments)
tool_results.append({
"tool_call_id": tool_call.id,
"output": result
})
# Add assistant message with tool calls
self.state.add_message(
"assistant",
response_content,
tool_calls=[tc.model_dump() for tc in assistant_message.tool_calls]
)
# Add tool results as messages
for result in tool_results:
self.state.messages.append({
"role": "tool",
"tool_call_id": result["tool_call_id"],
"content": result["output"]
})
# Get follow-up response with tool results
follow_up = client.chat.completions.create(
model="gpt-4.1",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
*self.state.messages
],
tools=self.tools
)
response_content = follow_up.choices[0].message.content or response_content
else:
self.state.add_message("assistant", response_content)
return response_content
Demo conversation
planner = TravelPlanner()
Simulate multi-turn conversation
conversation_turns = [
"I want to plan a trip to Tokyo in March",
"My budget is around $1500 and I love food and photography",
"Perfect, I'll be there from March 15-20",
"What about hotels?"
]
for turn in conversation_turns:
print(f"\n👤 User: {turn}")
response = planner.process_message(turn)
print(f"🤖 TravelBot: {response}")
Handling Edge Cases and User Preferences
Real users provide incomplete information, change their minds, or ask unexpected questions. Our system gracefully handles these scenarios.
class RobustTravelPlanner(TravelPlanner):
"""Enhanced planner with edge case handling"""
def extract_date_mentions(self, text: str) -> Dict[str, Optional[str]]:
"""Extract date patterns from natural language"""
import re
patterns = {
"start_date": r"(?:from|starting|begin|depart(?:ing)?)\s+(?:on\s+)?(\w+\s+\d{1,2}|\d{1,2}(?:st|nd|rd|th)?\s+\w+)",
"end_date": r"(?:to|until|returning)\s+(?:on\s+)?(\w+\s+\d{1,2}|\d{1,2}(?:st|nd|rd|th)?\s+\w+)"
}
dates = {}
for key, pattern in patterns.items():
match = re.search(pattern, text, re.IGNORECASE)
dates[key] = match.group(1) if match else None
return dates
def normalize_budget(self, budget_str: str) -> str:
"""Convert budget mentions to standardized categories"""
budget_lower = budget_str.lower()
if any(word in budget_lower for word in ["cheap", "budget", "low", "$500", "$1000"]):
return "budget"
elif any(word in budget_lower for word in ["luxury", "premium", "high-end", "$3000", "$5000"]):
return "luxury"
return "mid-range"
def parse_interests(self, text: str) -> List[str]:
"""Extract interest keywords from message"""
interest_keywords = {
"food": ["food", "cuisine", "eating", "restaurant", "gastronomy"],
"photography": ["photo", "photography", "picture", "instagram"],
"culture": ["museum", "temple", "history", "cultural", "traditional"],
"nature": ["nature", "park", "hiking", "beach", "outdoor"],
"shopping": ["shopping", "mall", "market", "souvenir"],
"nightlife": ["nightlife", "bar", "club", "night", "evening"]
}
found_interests = []
text_lower = text.lower()
for interest, keywords in interest_keywords.items():
if any(kw in text_lower for kw in keywords):
found_interests.append(interest)
return found_interests
def handle_clarification(self, missing_field: str) -> str:
"""Generate appropriate clarification prompts"""
prompts = {
"destination": "Where would you like to travel to?",
"start_date": "When do you plan to start your trip?",
"end_date": "When will you be returning?",
"budget": "What's your total budget for this trip?",
"interests": "What are your interests? (e.g., food, culture, nature, nightlife)"
}
return prompts.get(missing_field, "Could you provide more details?")
Generating the Final Itinerary
After gathering preferences and collecting tool data, we generate a comprehensive travel plan.
def format_itinerary(itinerary_data: Dict, preferences: TravelPreferences) -> str:
"""Format collected data into a readable itinerary"""
days = itinerary_data.get("days", [])
output = f"""
🌍 **{preferences['destination']} Travel Plan**
📅 {preferences['start_date']} - {preferences['end_date']}
💰 Budget: {preferences['budget']}
🎯 Interests: {', '.join(preferences['interests'])}
"""
for day in days:
output += f"""
**Day {day['number']}** ({day['date']})
Weather: {day.get('weather', 'N/A')}
"""
for activity in day.get('activities', []):
output += f" ⏰ {activity['time']}: {activity['name']}\n"
output += f" 📍 {activity['location']} ({activity['duration']})\n"
if 'cost' in activity:
output += f" 💵 ${activity['cost']}\n"
output += "\n"
output += """
📝 **Summary**
"""
if 'flights' in itinerary_data:
output += f"- ✈️ Round-trip flights: ${itinerary_data['flights']['total']}\n"
if 'hotels' in itinerary_data:
output += f"- 🏨 Accommodation: ${itinerary_data['hotels']['total']}\n"
return output
Example generated itinerary
sample_itinerary = {
"days": [
{
"number": 1,
"date": "March 15, 2026",
"weather": "Sunny, 24°C",
"activities": [
{"time": "09:00", "name": "Arrive at Narita Airport", "location": "Terminal 1", "duration": "2h"},
{"time": "12:00", "name": "Ramen Tasting Tour", "location": "Shinjuku", "duration": "3
Related Resources
Related Articles