Most travel websites use a generic chatbot that knows nothing about their actual packages, pricing, or local expertise. We decided to do something different — build our own AI, running entirely on our own server, trained on every page of keralaholidays.ai. No ChatGPT API calls. No data sent to third parties. No hallucinated prices.
This is the story of how we built it, what we indexed, and what we learned along the way.
In This Article
Why Self-Hosted AI?
The obvious option was to plug in the OpenAI or Gemini API. Fast, easy, well-documented. We tried it — and immediately hit three problems:
- It doesn't know our data. GPT-4 has no idea what our Munnar packages cost this season, which homestays we recommend, or what our current houseboat rates are. It would confidently make up numbers.
- Every query costs money. At scale, API costs add up fast — especially for a travel site where users ask long, multi-part questions.
- Customer data leaves our server. Every question a traveller asks gets sent to a US server. For privacy-conscious users, that matters.
The self-hosted approach solves all three. Our AI knows exactly what's on our website, costs nothing per query, and every conversation stays on our server in Kerala.
The Architecture: RAG Explained Simply
We used a technique called RAG — Retrieval-Augmented Generation. Here's what that means without the jargon:
The beauty of RAG is that the AI doesn't need to memorise everything. It just needs to be good at reading and summarising — which modern small language models do very well.
What We Indexed — All 805 Chunks
We indexed three types of data:
1. All HTML Pages (102 pages)
Every destination guide, package listing, blog post, wellness page, food guide, events calendar, and travel tips article on the site. The indexer strips navigation, footers, and scripts — keeping only the actual content. Each page is broken into overlapping chunks of roughly 600 characters so no information gets cut off at a boundary.
2. Live Pricing Data (rates.json)
Our pricing file is updated regularly with current rates for hotels, houseboats, vehicles, activities and packages. The indexer reads this JSON and converts it into natural language sentences — "Deluxe Houseboat 2 nights: ₹18,000 per couple" — so the AI can quote actual prices when asked.
3. Pricing Spreadsheets (Excel files)
Detailed pricing spreadsheets with package breakdowns, seasonal rates, and group discounts are also indexed. The indexer reads each sheet, maps column headers to row values, and creates readable sentences the AI can retrieve.
The Tech Stack
Every component runs on our own server — a bare metal machine with 48 CPU cores and 251 GB RAM:
- Llama 3.2 3B — Meta's open-source language model, 3 billion parameters. Small enough to run fast on CPU, smart enough to give genuinely useful travel answers.
- Ollama — runs the language model locally, handles inference, keeps the model hot in RAM between requests.
- ChromaDB — vector database that stores the 805 content embeddings and retrieves the most relevant ones for each question.
- sentence-transformers (all-MiniLM-L6-v2) — 80MB embedding model that converts text into vectors. Runs locally, no API needed.
- FastAPI + Python — lightweight API server that handles requests, runs retrieval, and streams responses back to the browser.
- Apache reverse proxy — routes requests from
keralaholidays.ai/api/ai/to the Python server, so everything runs under the same domain over HTTPS. - Docker — both Ollama and the Python server run in Docker containers, which solved a server OS compatibility issue cleanly.
How It Works, Step by Step
When a visitor types a question into the chat on keralaholidays.ai, here's what happens in the background:
Step 1 — Embedding the question (under 100ms)
The question is converted into a 384-dimensional vector using the MiniLM embedding model running locally. This vector represents the semantic meaning of the question — "What's the price of a Munnar honeymoon package?" gets a vector close to "Munnar couple package cost" even though the words are different.
Step 2 — Vector search (under 200ms)
ChromaDB compares the question vector against all 805 stored chunk vectors using cosine similarity. The 4 closest chunks are retrieved — typically the exact package pages, pricing data, and destination guides most relevant to the question.
Step 3 — LLM inference with context (1–3 seconds to first token)
The 4 retrieved chunks are passed to Llama 3.2 along with a system prompt that instructs it to answer only about Kerala travel, use the provided context for pricing, and direct booking queries to WhatsApp. The model starts streaming its response within 2–3 seconds.
Step 4 — Streaming to the browser
Tokens stream from Ollama → FastAPI → Apache → browser using Server-Sent Events (SSE). The user sees words appearing in real time, just like ChatGPT — not a blank screen followed by a wall of text.
# Simplified version of the retrieval + streaming logic def retrieve_context(question): results = collection.query( query_texts=[question], n_results=4 ) return "\n\n---\n\n".join(results["documents"][0]) async def stream_answer(question): context = retrieve_context(question) async with client.stream("POST", OLLAMA_URL, json={ "model": "llama3.2:3b", "messages": [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": context + question} ], "stream": True, "keep_alive": -1 }) as resp: async for line in resp.aiter_lines(): token = json.loads(line)["message"]["content"] yield f"data: {json.dumps({'token': token})}\n\n"
Auto Re-Indexing for Live Pricing
One of the most practical features: when we update the pricing spreadsheet or rates.json, the AI automatically re-indexes those files within seconds using a file watcher service running in the background. No manual steps. No stale pricing data in the AI's answers.
The watcher monitors the site folder for changes to any .json, .xlsx, or .xls file and triggers a targeted re-index — only of the changed pricing data, not the entire site. The full index takes a couple of minutes; a pricing-only re-index takes under 30 seconds.
Results and Performance
After tuning — switching from Llama 3.1 8B to Llama 3.2 3B, increasing CPU threads from 8 to 24, and adding keep_alive: -1 to keep the model permanently in RAM — here's where we landed:
- Time to first token: ~2 seconds
- Streaming speed: smooth, continuous token flow
- Answer quality: accurate pricing, correct package details, proper destination information
- Cost per query: ₹0 (runs on existing server)
- Privacy: 100% — no data leaves the server
What's Next
The current system is a solid foundation. On the roadmap:
- Adding Kerala-specific knowledge — local transport options, lesser-known destinations, detailed Ayurveda treatment information
- Multi-turn conversation memory — so the AI remembers earlier parts of a conversation
- Booking intent detection — recognising when a user is ready to book and routing them directly to WhatsApp
- Voice input on the chat page for mobile users
Try It Yourself
The AI is live on keralaholidays.ai right now. Ask it anything about Kerala travel — packages, pricing, destinations, best time to visit, what to pack. It answers from real website data, streams its response in real time, and costs you nothing to use.
🤖 Chat With Our Kerala AI
Ask about packages, pricing, destinations, or anything Kerala travel. Powered by Llama 3.2, running on our own server.
💬 Open AI Chat 🗺️ Plan My Trip 📱 WhatsApp UsIf you're a developer building something similar for a travel or hospitality business, the architecture here scales well to any domain-specific knowledge base. The key insight: you don't need GPT-4 when your use case is narrow and your data is well-structured. A 3B parameter model with good retrieval beats a massive generic model with no grounding every time.