Skip to content
All work

Applied AI · Guardrails

AI assistant for business data

A Telegram assistant that answers business questions in plain language, where every figure comes from a real query.

Repo
mercaldas-rag
Context
Mercaldas
Role
Sole engineer
Status
In active development

The problem

Every ad-hoc question about sales, inventory or margin went through the one person who could write the query. Pointing a model at the warehouse is the obvious fix and the obvious trap: an invented number looks exactly like an answer.

Stack

  • Python
  • Groq
  • Open-weight LLMs
  • SQL Server
  • Telegram

What it does

  • No invented figures

    Every number comes from an executed query, enforced by design and not by prompt

  • Two answer paths

    Closed SQL recipes for known questions, validated model-written SQL for the rest

  • Validated before it runs

    Generated SQL passes lexical checks and a dry run before touching real data

  • One paid entry point

    Spend control lives in code, with a test that fails if a second door appears

Try it yourself

Interactive
Ask the data assistantmercaldas-rag · demo buildSimulation · synthetic data

Pick a question

Choose one of the three questions above. The assistant will show which of its two paths handled it, what SQL ran, and how many figures in the reply came from that query.

demo

The real assistant runs over Telegram against a documented data warehouse. Here the three questions, the SQL and the answers are fixed and nothing is executed, but the routing, the validation steps and the counter below reproduce how the real one behaves.

Context

Every ad-hoc question about the business, sales, inventory, margin, went through the one person who could write the query. That is a bottleneck for management and a poor use of an analyst, and it is the textbook case for putting a language model in front of the warehouse.

It is also the textbook case for getting it wrong. A model that answers "roughly 340 million" when the real figure is 290 million has not made an error anyone can detect. It produced something that looks exactly like an answer, in a channel where people make decisions.

So the constraint came before the design: no figure the user sees is generated by the model. Every number comes from a query that ran.

One more thing shaped it. This is a pure consumer of a warehouse that a sibling project already models and documents. Domain knowledge is not re-derived inside the bot, so a metric definition is wrong in one place or not at all.

Architecture

Two answer paths, and the split is the architecture.

Two answer paths, one source of numbersA question from Telegram hits an intent router that sends it down one of two paths. The certified path runs closed SQL recipes into a deterministic metric composer. The dynamic path has a language model write SQL, which is lexically validated and dry-run before it touches the warehouse. Both paths read from the same data warehouse, and every model call goes through a single paid-provider entry point.CHANNELROUTINGPATHEXECUTIONANSWERcallsQuestionTelegram, plain languageIntent routerpath mix = health signalPaid-provider gateone entry point,test-enforcedCertified pathclosed SQL recipesDynamic pathLLM writes the SQLMetric composerassembled in codeValidate · dry-runbounded retryData warehousethe only source ofnumbersAnswerevery figure has a query
Two answer paths, one source of numbers

The certified path handles the questions that get asked constantly. Closed SQL recipes, written and reviewed ahead of time, feeding a deterministic composer. No model writes anything here. For these questions the assistant is a natural-language front end to known-correct queries, which is exactly what it should be.

The dynamic path takes everything else. A model writes SQL, and that SQL then passes lexical validation, a dry run, and a bounded retry before it goes near real data. The model is an untrusted contributor whose work gets reviewed, not an oracle.

Path mix is a thermometer, not a target. The share of questions landing on each path is tracked as a health signal and deliberately not optimised. The moment "more traffic on the certified path" becomes a goal, the incentive is to widen recipes until they answer questions they were not written for, which reintroduces the wrong answer through the door marked safe.

Provider selection followed the same discipline: accuracy and latency thresholds, plus a quarterly review, written down before the measurements. Choosing thresholds after seeing results is how you justify the provider you already liked.

The hard part

The interesting failure here is one of mine.

Model calls cost money, so I built a guardrail: a hook intercepting shell commands, blocking the ones that would reach a paid provider. It worked in testing. Then I audited it adversarially, actively trying to get a billable call past it, and most ways of invoking a command went straight through. Aliases. A different quoting style. Environment variables. A wrapper script.

The lesson was not that the pattern list needed more patterns. It was that the approach could not work. A shell is a complete programming language. Deciding by text inspection whether an arbitrary command will eventually spend money is not a hard problem, it is the halting problem wearing a hat.

So the control moved. The code now has exactly one entry point to the paid provider, and an automated test fails the build if a second one appears. The guarantee is no longer "we thought of every invocation", it is "there is one door, and CI notices if someone cuts another".

That generalises, which is why it leads this project: when a control sits where it is convenient to enforce rather than where the thing it controls happens, it looks like it works right up until someone checks. In AI systems that convenient place is usually the prompt.

Trade-offs

The certified path costs maintenance. Recipes have to be written and kept in step with the warehouse. Accepted, because the alternative puts the highest-trust questions on the least deterministic machinery.

Open-weight models over frontier models. Fast and cheap enough to make the dynamic path viable at all, at some cost in SQL quality. The validation layer is what makes that trade acceptable: the floor is set by the checks, not by the model.

Where it stands

In active development. The guardrails were built before any of the conversational product, which is the ordering I would defend as the point of the project. The core carries no dependencies beyond the standard library, a deliberately small failure surface, with dozens of tests covering SQL validation through to the spend controls themselves.

Business figures are published as ranges. The company is named, the exact numbers are not mine to publish.

Technical metrics

  • Standard library

    Core carries no external dependencies by design

  • Dozens

    Automated tests, from SQL validation to the guardrails

  • Pre-committed

    Model switching thresholds set before measuring

Mercaldas is named as the employer, but business figures on this page are published as ranges, orders of magnitude or relative percentages. Exact revenue, margin and volume figures, source code and screenshots of real operating data are not published.