{"slug": "put-the-model-last-a-cheaper-safer-chatbot", "title": "Put the Model Last: A Cheaper, Safer Chatbot", "summary": "A developer built The Copper Larder, a demo chatbot for a British bistro that routes each request through eight pipeline stages with the language model placed last, so only stage seven incurs a token cost. Ahead of the model sit session caps, a complaint handoff, scripted intercepts and an exact-match cache, while a post-model guardrail blocks replies that promise table availability and prevents them from being cached. The bot also stores dietary constraints in session state rather than trusting the model to remember them, and returns a branded fallback message with a callback card when the model is unavailable.", "body_md": "Treat the model as the last stage of your pipeline, not the first. Cheap code should answer what it can, refuse what it must, and cache what repeats. Whatever is left goes to the model, and its reply is checked before anyone sees it.\n\nA chatbot behind a login has known users and a bounded audience. A chatbot on a public website is an open endpoint that costs money every time someone sends a message. People will ask it the same five questions all day, try to make it say something embarrassing, and now and then paste a novel into it.\n\nThe Copper Larder is a demo front of house host for a British bistro. It answers menu questions, takes callback requests and streams its replies. The interesting part is everything wrapped around the model. The request goes through 8 stages, and only stage 7 costs a token.\n\nAhead of the model sit session caps, a handoff for complaints, scripted intercepts and an exact match cache. The cheapest and most certain checks go first.\n\n``` js\nconst intercepts = [\n  { test: /\\b(opening|open)\\s+(hours|times)\\b|\\bwhen are you open\\b/i, answer: () => hoursCard(restaurant) },\n  { test: /\\bparking\\b/i, answer: () => infoCard(restaurant.parking) },\n]\n\nexport function tryIntercept(message) {\n  const hit = intercepts.find((rule) => rule.test.test(message))\n  return hit ? hit.answer() : null\n}\n\nconst normalise = (text) => text.toLowerCase().replace(/[^a-z0-9 ]/g, '').replace(/\\s+/g, ' ').trim()\n```\n\nNotice that the answers come from restaurant data and are not typed by hand into the rules. The dish and info cards are built only from typed restaurant data, so they can only show what that data says.\n\nIf someone says “I’m vegan” in message 2, that must still hold in message 20. Models forget, and long histories get trimmed. The Copper Larder treats it as a conversation wide dietary lock. A sturdy way to build one is to store the constraint in session state, inject it into every turn and filter the menu cards in code, so the model is never trusted to remember a constraint that matters.\n\nThe model is the one stage that can say anything, so its output is checked. In this bot the important rule is that it must never confirm availability, because it cannot know it. If a reply promises that a table is free, a guardrail catches it, rewrites it with a correction, and, importantly, blocks that reply from entering the cache. A simple version looks like this. A cached mistake is a mistake you serve for free to every future visitor.\n\n``` js\nconst promisesAvailability = /\\b(we have|there is|i can confirm|you are booked|table is available)\\b/i\n\nexport function checkReply(reply) {\n  if (promisesAvailability.test(reply)) {\n    return {\n      reply: reply + '\\n\\nI can’t see live availability, so please leave a callback request and the team will confirm.',\n      cacheable: false,\n    }\n  }\n  return { reply, cacheable: true }\n}\n```\n\nThe model will be down, or rate limited, or the key will be missing in some environment. Every one of those paths returns a warm, on brand message and a callback card, so the visitor can still leave their number. A broken demo is a bad impression. A polite “I’ll have someone call you” is just a slightly different product.\n\nTwo smaller habits round it out. Replies stream over server sent events, with aria live regions so screen readers announce text as it arrives. And the app stores no raw IP addresses, so there is nothing sensitive to leak. See the [Copper Larder case study](https://ahmadhsn1.github.io/work/copper-larder/) for the rest.\n\n*Originally published at [ahmadhsn1.github.io](https://ahmadhsn1.github.io/blog/llm-chatbot-pipeline-cost-guardrails/).*", "url": "https://wpnews.pro/news/put-the-model-last-a-cheaper-safer-chatbot", "canonical_source": "https://dev.to/ahmad_hassan_6433415b38c6/put-the-model-last-a-cheaper-safer-chatbot-3a95", "published_at": "2026-10-15 11:41:44+00:00", "updated_at": "2026-10-15 11:48:51.584959+00:00", "lang": "en", "topics": ["large-language-models", "ai-agents", "ai-tools", "developer-tools"], "entities": ["The Copper Larder"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/put-the-model-last-a-cheaper-safer-chatbot", "markdown": "https://wpnews.pro/news/put-the-model-last-a-cheaper-safer-chatbot.md", "text": "https://wpnews.pro/news/put-the-model-last-a-cheaper-safer-chatbot.txt", "jsonld": "https://wpnews.pro/news/put-the-model-last-a-cheaper-safer-chatbot.jsonld"}}