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:

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