Draft — awaiting review. This article is written from the source repository and has not yet been reviewed and approved by Sajid Islam. It is served with noindex so search engines do not list it, and it is deliberately absent from sitemap.xml and llms.txt. Everything below describes code that exists in the linked repository; anything not yet built is labelled proposed.

Building Catalogue-Grounded AI Replies for WhatsApp Commerce

Draft written Est. 8 min read

The short answer: don't let the model look things up. In the WhatsApp Commerce Copilot, a deterministic pipeline searches the store's catalogue and hands the LLM at most five candidate products. The model may only interpret language and phrase a reply over that closed set — and if it returns a product ID that was not in the set, the response is thrown away and the deterministic reply is sent instead. Grounding is enforced by code, not requested in a prompt.

The problem: a fluent model is a liability in commerce

A clothing brand's customer asks whether a shirt comes in medium. If a general-purpose model answers from its own weights, it will produce something plausible: a size, a price, a delivery window. Plausible is worthless here. A wrong size commits the brand to an order it cannot fill, and in a Cash-on-Delivery market that ends as a parcel coming back.

The usual mitigation is to put the catalogue in the prompt and ask the model nicely to stick to it. That reduces the error rate. It does not bound it, and a bounded error rate is what a shop owner actually needs before handing a bot their customers.

The approach: retrieval decides, the model phrases

The copilot inverts the usual arrangement. The deterministic layer is authoritative for everything that touches data; the model is a language interface bolted on the front and back. Concretely, a message moves through this order:

  1. Normalise the text — lowercasing, whitespace, alias expansion.
  2. Detect the language. Not with langdetect, which fails on Roman Urdu, but with a keyword heuristic over a curated Roman Urdu vocabulary.
  3. Detect intent with a regex and keyword classifier.
  4. Extract entities: product, colour, size, quantity, SKU, budget, exclusions.
  5. Search the catalogue — SKU, context ID, alias, description and category tokens, structured variant and price filters, with a fuzzy fallback.
  6. Match store policies: COD, delivery, returns, exchange.
  7. Build a grounded response carrying its own sources.

Only then, and only sometimes, does an LLM get involved.

Where the model is actually allowed to run

With a provider configured, two LangChain chains exist, and they have very different privileges.

The first classifies every inbound message using an LCEL chain with Pydantic structured output. Its result is advisory: the deterministic classifier remains authoritative for retrieval and for anything that mutates an order.

The second is the response chain, and it runs only when the deterministic layer is unsure — an ambiguous query, an unrecognised language, or a low-confidence match. It receives recent conversation history and at most five retrieved catalogue candidates. Nothing else. The model never issues a query; it cannot reach the database at all.

Then comes the part that makes this enforcement rather than etiquette: any product or variant ID in the model's output that is not in the candidate set is rejected, and the deterministic response is used in its place. The model cannot name a product that retrieval did not already put in front of it, no matter what it generates.

Every reply carries matched_product_id and sources, so an answer can be traced back to the rows that produced it after the fact.

The no-key path is the default path

With no API key configured, the LLM layer simply does not run and the deterministic catalogue pipeline answers on its own. This is worth stating plainly because it inverts how most AI features are built: the AI is the enhancement, and the system is expected to work without it. A misconfigured key degrades answer phrasing. It does not take the product down.

Tradeoffs

Limitations

What I would take to the next system

Grounding beats fluency, and validation beats instruction. The single highest-leverage line in this codebase is the one that discards a generated response containing an unknown product ID. Prompt wording was tuned repeatedly and moved answer quality a little. That check moved the failure mode from "sometimes invents a product" to "cannot invent a product", which is a different category of guarantee — and it is the kind an LLM feature can actually be shipped on.

Related reading

All writing Projects