May 12, 2026 · 8 min read · Use cases

Building a travel chatbot that doesn't hallucinate.

The pattern is boring and well-known: retrieval first, generation second. The hard part is having a retrieval substrate the model can actually trust. Here's the shape of a travel chatbot that gets Indian destinations right.

The naive shape

Most teams ship version one as a single LLM call. User says "tell me about the Buddhist Circuit," the model writes 400 words from pretraining memory, and ships. It works for the five Instagram cities and falls apart everywhere else: invented inscription years, fictional stops, the wrong "Hyderabad."

The retrieval-first shape

Add one step before generation. Parse the user message into an intent, hit a structured travel API, and pass the result as grounded context to the model.

// 1. User: "what's the Buddhist Circuit and where do I start?"
const r = await fetch(
  "https://api.travelminds.ai/v1/circuits/buddhist-circuit",
  { headers: { Authorization: `Bearer ${process.env.TMAI_KEY}` } }
);
const circuit = await r.json();

// circuit.stops is an ordered array:
// [{ name: "Bodh Gaya", lat: 24.6961, lon: 84.9911, sequence: 1, ... },
//  { name: "Rajgir",    lat: 25.0277, lon: 85.4197, sequence: 2, ... },
//  ... 7 stops total ]

The prompt template

The prompt is short and rule-driven. The retrieved JSON goes in verbatim, and the model is told it may only mention entities that appear in it.

You are a travel assistant.

Answer the user using ONLY the entities in CONTEXT below.
- Do not invent monuments, stops, or dates.
- If CONTEXT has no answer, reply: "I don't have that information."
- Cite stop names exactly as written.

CONTEXT:
{circuit_json}

USER:
{user_message}

With this template, the model writes fluent prose about Bodh Gaya, Rajgir, Nalanda, Vaishali, Kushinagar, Sarnath, and Lumbini — in that order, because that's the sequence the API returned. It does not extrapolate to "Buddhist temples in Goa," because Goa is not in CONTEXT.

Routing intents to endpoints

A working chatbot routes queries to different endpoints based on parsed intent. A simple table is enough for most products:

For the entity-extraction step, a small model with a tools schema works fine. You don't need a frontier model to recognize "Buddhist Circuit" as a circuit slug.

Worked example: end-to-end

async function answer(userMsg) {
  const intent = await classify(userMsg);
  let context;

  if (intent.type === "circuit") {
    const r = await tmai(`/v1/circuits/${intent.slug}`);
    context = { circuit: r };
  } else if (intent.type === "city") {
    const r = await tmai(`/v1/cities/${intent.cityId}/context`);
    context = { city: r };
  } else {
    return "I can answer questions about cities and circuits.";
  }

  return llm({
    system: PROMPT_TEMPLATE,
    user: userMsg,
    context: JSON.stringify(context),
  });
}

What you stop seeing

Once the retrieval layer is in place, four classes of error disappear: invented heritage status, confused regional namespaces (Hyderabad-the-city vs Hyderabad-the-region), outdated transport infrastructure, and fictional itineraries. They stop happening because the model never has the chance to fabricate — every entity it mentions came from a database row with a primary key.

What you still need

You still need the model to handle paraphrase, conversation memory, and tone. None of that is hard. The hard part — the part that takes 18 engineer-months to do well — is the structured travel substrate. That's what TravelMindsAI is.

Free tier is 1,000 calls/month, no credit card. Hook it into your chatbot, point your model at the result, and watch the hallucination rate fall.

Sign up — free See India coverage →