🤖
AI & Technology

How We Built a Private AI for Kerala Tourism — Indexed 800+ Pages, No ChatGPT

By Quick Kerala Holidays  ·  June 7, 2026  ·  8 min read

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

  1. Why self-hosted AI?
  2. The architecture: RAG explained simply
  3. What we indexed — all 805 chunks
  4. The tech stack
  5. How it works, step by step
  6. Results and speed
  7. Try it yourself

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:

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:

1
Index all website contentEvery page, package detail, price list and destination guide gets broken into small chunks and converted into "embeddings" — mathematical representations of meaning stored in a vector database.
2
User asks a questionThe question is also converted into an embedding and compared against all stored chunks to find the most relevant pieces of content.
3
Top chunks sent to the LLMThe 4 most relevant content chunks are passed to the language model as context — like handing it a cheat sheet before answering.
4
Answer streams backThe model generates a response grounded in actual website content — not invented facts — and streams it token by token to the user.

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.

Pages indexed include: Munnar, Alleppey, Wayanad, Thekkady, Kochi, Kovalam, Varkala, Kumarakom and 14 other destination guides — plus all 50 tour packages, 10 blog articles, food guide, Ayurveda guide, wildlife guide, Kerala art forms, festivals, and shopping guide.

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.

102Pages Indexed
805Content Chunks
3Data Sources
0₹Cost Per Query

The Tech Stack

Every component runs on our own server — a bare metal machine with 48 CPU cores and 251 GB RAM:

Why Docker? Our server runs CentOS 7 (Linux kernel from 2014). Modern AI software needs a newer C library (GLIBC 2.28+) that CentOS 7 doesn't have. Docker containers bring their own environment, so the AI runs perfectly on old infrastructure — no OS upgrade needed.

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:

What questions does it answer well? Package prices, best time to visit specific destinations, itinerary suggestions, houseboat booking details, Ayurveda resort recommendations, festival dates, and food recommendations. It answers from actual website content — no hallucinations.

What's Next

The current system is a solid foundation. On the roadmap:

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 Us

If 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.