ProductsDocsBlogConsultingAboutContactGet Started
Back to BlogA grounded AI chatbot retrieving real product rows from a Google Sheet and Magento catalog before answering, with a refusal path when the data isn't found
10 min readMageSheet Team

How to Stop an AI Chatbot Hallucinating: Ground It on Your Catalog Data

AIChatbotApps ScriptMagentoGoogle SheetsRAGGroundingHallucination

An AI chatbot hallucinates — invents a price, a delivery date, or a confident "yes, that's in stock" — because a language model predicts plausible text, not your facts. It has no built-in connection to your catalog, so when it doesn't know, it generates an answer that sounds right. The fix is not a smarter or more expensive model. It is grounding: at answer time you retrieve the real rows that match the question, pass only those rows into the prompt, and constrain the model to answer from them or say "I don't have that." That single change drops hallucination on factual product questions from above 10% to roughly 1-2%.

This post is the depth guide on grounding specifically. If you're still deciding whether to run AI chat versus live agents, read AI chatbot vs live chat. If you want the end-to-end install path on Magento — picking a widget, connecting a provider, going live — read how to add AI chat to your Magento 2 store. This page is the one objection neither of those fully solves: how to make the bot answer only from your real data so it never makes things up. It sits under our hub on Google Workspace AI automation.

Why models hallucinate in commerce

A large language model is a next-token predictor trained on a vast slice of the internet. It is extraordinarily good at producing fluent, plausible language — and that is exactly the problem. Ask it "how much is the navy raincoat and do you have a medium?" and it will happily answer, because producing a confident sentence is what it does. It has no access to your inventory table, your current price list, or your returns policy unless you hand those to it in the prompt.

Two categories of fact are dangerous in different ways:

  • Stable-ish facts (material, dimensions, "is this dishwasher safe"). The model might guess right from training data, but on your specific SKU it has no idea, so it confabulates a believable answer.
  • Volatile facts (price, stock level, lead time, promo eligibility). These change by the hour. Even a model fine-tuned on last week's catalog will quote a stale number. These must never come from the model's memory — only from a live lookup.

The reputation problem here is real and asymmetric. One invented "yes, in stock" costs you an order and the trust, because the customer finds out at checkout or, worse, after paying. The job of grounding is to make the model structurally unable to do that.

The four grounding patterns, on Apps Script

Grounding is not one trick; it's a stack of four, and they reinforce each other. All four run comfortably inside a single Google Apps Script project with your catalog in a Sheet, or pulled live from Magento.

1. Retrieve-then-answer (the core move)

Never send the customer's question straight to the model and hope. First retrieve the rows that are actually relevant — by SKU, product name, or keyword — from your source. Then pass only those rows as context and instruct the model to answer strictly from them.

For a small or mid-size catalog you usually don't need a vector database. A keyword or SKU filter over your Sheet (or a Magento catalog query) finds the right rows, and a handful of rows is far cheaper to send than the whole catalog. The model's job shrinks from "know everything about our store" to "read these three rows and answer the question." That reframing is where most of the accuracy gain comes from.

2. Structured constraints (model proposes, code verifies)

Don't let the model's prose be the final word on a number. Ask it to return structured output — the product ID and the price it used — alongside its answer. Your Apps Script code then looks that ID up in the source and compares the price. If they don't match, you don't show the model's number; you substitute the real one or refuse.

This "model proposes, code verifies" loop is the highest-leverage guardrail in commerce, because it makes quoting an invented price structurally impossible — the only price that can reach the customer is one your code confirmed against the source. Tool use and JSON/structured-output modes make this clean; we go deep on the mechanics in Claude API tool use and prompt caching on Apps Script.

3. Live lookups for volatile fields

Price and stock get their own rule: fetch them at answer time, never from the model. Even if your prompt includes a "price" column from the sheet, treat it as a hint, not the truth. For anything the customer will act on, have Apps Script call your Magento API (or POS) for the current value the moment the question is asked, and let that number be the one displayed.

We build these as fixed-scope projects. If you already have a chatbot that's occasionally embarrassing you with a wrong price or a phantom "in stock," grounding it against your real catalog is usually a contained, well-bounded job — not a rebuild. Happy to scope yours on a free 30-minute feasibility review; we'll tell you straight whether your data is clean enough to ground well, or whether the catalog needs a tidy-up first.

4. Guardrail prompt + an explicit refusal path

The system prompt has to give the model permission to say "I don't know." Models default to helpfulness, which in an ungrounded setting becomes confident invention. Spell it out: if the answer is not in the provided data, do not guess — say you'll check with a human. Then make that refusal route somewhere useful: a handoff to a person, a "notify me" capture, or a fallback to search.

Counter-intuitively, a bot that sometimes says "let me check that for you" is more trusted than one that answers everything instantly, because customers have been burned by confident-but-wrong bots since 2018. A clean refusal is a feature.

A realistic Apps Script sketch: retrieve → constrain → answer

Here's the shape of a grounded answer flow. It's illustrative — not a full app — but the structure is the point: retrieve from the Sheet, pass only those rows, demand structured output, and verify before returning.

/**
 * Grounded product answer. Retrieves matching catalog rows from a Sheet,
 * constrains the model to them, and verifies the price/ID before returning.
 */
function answerProductQuestion(userQuestion) {
  // 1. RETRIEVE — pull only the rows relevant to this question.
  const rows = findCatalogRows_(userQuestion); // keyword/SKU match over the sheet
  if (rows.length === 0) {
    return { type: 'refusal', text: "I couldn't find that in our catalog — let me get a human to help." };
  }

  // 2. CONSTRAIN — pass ONLY those rows, demand structured output.
  const system =
    "You are a store assistant. Answer ONLY from the PRODUCTS provided. " +
    "If the answer is not in the data, set \"grounded\": false and do not guess. " +
    "Return JSON: { answer, grounded, productId, priceUsed }.";

  const payload = {
    model: 'claude-sonnet-4-6',
    max_tokens: 400,
    system: system,
    messages: [{
      role: 'user',
      content: `PRODUCTS:\n${JSON.stringify(rows)}\n\nQUESTION: ${userQuestion}`
    }]
  };

  const res = UrlFetchApp.fetch('https://api.anthropic.com/v1/messages', {
    method: 'post',
    contentType: 'application/json',
    headers: {
      'x-api-key': PropertiesService.getScriptProperties().getProperty('ANTHROPIC_API_KEY'),
      'anthropic-version': '2023-06-01'
    },
    payload: JSON.stringify(payload),
    muteHttpExceptions: true
  });

  const out = JSON.parse(JSON.parse(res.getContentText()).content[0].text);

  // 3. REFUSE — the model itself flagged it couldn't ground the answer.
  if (!out.grounded) {
    return { type: 'refusal', text: "I'm not certain on that — I'll pass you to a colleague." };
  }

  // 4. VERIFY — never trust the model's price. Check it against the source.
  const livePrice = getLivePrice_(out.productId); // live Magento/POS lookup
  if (livePrice == null) {
    return { type: 'refusal', text: "Let me confirm that price with the team." };
  }
  if (Number(out.priceUsed) !== Number(livePrice)) {
    // Model drifted — substitute the verified price, don't show its number.
    out.answer = out.answer.replace(String(out.priceUsed), String(livePrice));
  }

  return { type: 'answer', text: out.answer, productId: out.productId, price: livePrice };
}

Three things make this grounded rather than just "an AI call":

  • The model only ever sees rows — the slice you retrieved — not your whole catalog or its own training memory.
  • It must return grounded and productId, so refusal is a first-class outcome and verification is possible.
  • getLivePrice_ is the source of truth for price. The model's number is treated as a draft until code confirms it.

(In production you'd wrap the response parsing in a try/catch — the line above assumes the model returns clean JSON in its first text block, which is exactly what structured-output or tool-use mode guarantees and what you should switch to before shipping.)

The same pattern works whether your data lives in a Sheet, in Magento, or both — and whether the channel is a website widget or WhatsApp. We use this exact retrieve-constrain-verify shape in our WhatsApp AI CRM on Google Sheets and in invoice extraction via WhatsApp AI vision, where the cost of a hallucinated field is equally unacceptable.

Honest caveats (where grounding stops)

Grounding earns trust precisely because we're honest about its edges.

  • It reduces, it doesn't eliminate. Even with retrieval and verification, a model can blend two products, misread an attribute, or hallucinate inside a gap in your data. The verification step catches price drift; it can't catch a confidently wrong claim about a material your sheet never recorded. Keep a human-handoff path live — always.
  • Garbage in, grounded garbage out. If your catalog rows are wrong, the bot will faithfully repeat wrong answers. In the stores we've instrumented, the autonomous-resolution rate tracks catalog completeness more than any prompt tuning. Clean source data is the highest-ROI work before any grounding goes live.
  • Retrieval has a cost. Every answer now does a lookup and (for volatile fields) a live API call before the model runs. That's a few hundred milliseconds and, at volume, real quota usage. Apps Script's 6-minute execution limit and UrlFetchApp daily quotas and retry handling are the two ceilings to design around — comfortable for normal chat traffic, worth planning for at high concurrency.
  • Latency vs. accuracy is a real dial. Verifying live price on every message is safest but slower. A common compromise: ground all answers, but only do the live price/stock call when the question actually touches price or availability.

None of these are reasons to skip grounding — they're reasons to do it deliberately, with a handoff path and clean data, rather than bolting a raw model onto your storefront and hoping.

The takeaway

An accurate storefront assistant is not a model problem; it's a data-plumbing problem. The model supplies fluent language; your retrieved rows supply the facts; your code verifies the numbers before they reach a customer. Get that pipeline right and the bot is trustworthy enough to put in front of buyers. Skip it and you've automated the act of inventing prices.

The good part: this entire pipeline runs inside the Google account you already pay for. No monthly grounding SaaS, no per-conversation vendor fee — your catalog in Sheets or Magento, the retrieval and verification logic in Apps Script you own, and only the model tokens as a running cost. Built once, owned forever.

If you want an AI assistant that's accurate enough to trust on your storefront — grounded on your live catalog, with verification and a human handoff baked in — that's exactly the kind of project we scope on a free feasibility review. We'll look at your data, tell you honestly whether it's ready to ground, and quote a fixed scope.

Frequently Asked Questions

What does it mean to 'ground' an AI chatbot?

Grounding means forcing the model to answer only from your real, current data instead of from its training memory. At answer time you retrieve the specific rows that match the customer's question — a SKU, a product name, a policy — pass only those rows into the prompt as context, and instruct the model to answer strictly from them and say 'I don't have that' otherwise. The model stops being a knowledge source and becomes a language layer over your verified facts. Done well, this drops hallucination on factual product questions from above 10% to roughly 1-2%.

Why does an AI chatbot make up prices and stock levels?

Because a language model predicts the most plausible next words, not the true ones. If you ask 'is the blue jacket in stock and how much is it', the model has no live connection to your inventory — so it generates a fluent, confident answer that *sounds* right: a believable price, a 'yes, in stock'. It isn't lying; it has no mechanism to know your stock unless you give it one. Volatile fields like price and inventory are the most dangerous because they change hourly, so even a model trained on your catalog would be wrong. The fix is to fetch those fields live at answer time and never let the model recite them from memory.

Is grounding the same as RAG (retrieval-augmented generation)?

Grounding is the goal; RAG is the most common technique to achieve it. RAG means you retrieve relevant documents (or in commerce, catalog rows) before generating, and inject them into the prompt. For a small or mid-size catalog you often don't even need a vector database — a keyword or SKU lookup against your Sheet or Magento catalog is enough to find the right rows. Grounding also includes the parts RAG doesn't name: constraining the model to the provided data, verifying the price/ID it used against the source before display, and a hard refusal path. RAG retrieves; grounding retrieves and constrains and verifies.

Can grounding eliminate hallucination completely?

No, and any vendor who claims 100% is overselling. Grounding sharply reduces hallucination on factual questions because the model answers from rows you supplied, but a model can still misread context, blend two products, or hallucinate inside a gap in your data. That's why production builds add two safety nets: a verification step that checks the price and product ID the model returned against the source before showing it, and a human-handoff path for anything it can't ground. Treat grounding as the thing that makes the bot trustworthy 98% of the time and the handoff as what protects the other 2%.

Can I ground a chatbot on a Google Sheet, or do I need a real database?

A Google Sheet is a perfectly good grounding source for small and mid-size catalogs — up to roughly tens of thousands of rows. Apps Script reads the sheet, filters to the matching rows, and passes them to the model, all inside your own Google account with no per-task SaaS fee. For volatile fields like live stock and price you can have Apps Script call your Magento or POS API at answer time instead of trusting the sheet. The limits to respect are Apps Script's 6-minute execution ceiling per run and UrlFetchApp's daily call quotas — both fine for normal chat volume, but worth designing around if you expect heavy concurrency.

How do I verify the AI didn't change the price before showing it to the customer?

Ask the model to return structured output — the product ID and the price it used — rather than just prose. Your Apps Script code then looks that ID up in the source (Sheet or Magento) and compares: if the returned price doesn't match the source price, you don't render the model's number, you substitute the real one or refuse. This 'model proposes, code verifies' pattern is the single highest-leverage guardrail for commerce, because it makes it structurally impossible to quote a price the model invented. Tool use and structured-output modes in Claude, OpenAI, and Gemini make this clean to implement.

Stay Updated

Get the latest insights on AI, e-commerce, and Magento delivered to your inbox.