Asking the database in plain language

First working version
5 h

Goal

An agent that understands in plain language what you need to know from the sales database, picks the function that fits, does the arithmetic in code and returns the figure along with the query that produced it.

How it works

First the user asks what they need to know, written the way they would say it out loud. The model never touches the database: it picks from a closed catalogue of nine Python functions with typed signatures, and every one of them is executed in a terminal. “Which sales rep has the best euros-per-kilometre ratio” becomes a single call: the ranking function, the sales-rep dimension, the euros-per-kilometre metric.

Then every parameter is validated, before anything runs. Dimensions, metrics, filters and periods come from closed lists. If you ask for sales “by salesperson” and that is not one of the seven dimensions, the call fails and returns the seven that are valid. If you filter by a client called “restaurante” and three clients match, it refuses and names all three so you can pick. If you ask for kilometres per product, it explains that route metrics only make sense per sales rep. The model reads the error, corrects, and retries once.

There are seven dimensions (product, category, client, sales rep, region, channel and client type) and eight metrics (revenue, units, order count, average order value, kilometres, visits, euros per kilometre and euros per visit). Route metrics are only accepted per sales rep, because attributing kilometres to a product is meaningless, and the function says so.

The arithmetic is done by the code, not by the model. The function runs a parameterised query, and summing, averaging and dividing happen in Python over the rows the database returns. The answer in the chat is built from that result and nothing else, so the figure you read is exactly the one that came out of the calculation. Free text never reaches the database either: there is no SQL parameter, no expression field, no escape hatch. The model decides what to ask; it can never decide how to query.

Writes work the same way. “Raise the price of the 500 ml extra virgin to 12.50” goes through the update function, which only accepts a per-table whitelist of columns and returns the record before and after the change, so the edit is visible rather than asserted.

To finish, the answer arrives inside the chat as a table with the bars drawn on the line itself, and as an HTML report with its chart when one is asked for. Every call is appended to a log with its arguments, the number of rows it returned and the milliseconds it took, so “how did you do that?” is answered by reading that log rather than rebuilding it from memory.

Agent steps
Deliverable
  • The answer inside the chat: a table with inline bar charts, generated by the script and pasted verbatim, so the numbers cannot drift between the query and the reply.
  • A self-contained HTML report on request, with the table and an SVG chart drawn in pure Python. Chart type is chosen by the renderer from the shape of the data, never by the model.
  • A trace log of every call, with arguments, row counts and timings, which doubles as the audit trail.
Stack
  • Claude Cowork
  • Python 3, standard library
  • SQLite
  • SVG charts in Python
  • Trace log

On video

The agent answering questions about salesOpen↗

Why it matters

Every company already has the data. What they do not have is a way to ask it a question without going through someone who writes SQL. And conversational analytics keeps failing not because models cannot write queries: it is that when they are allowed to, nobody can tell a correct answer from a confident one.

What could come next

  1. 01Point the same catalogue at the real warehouse or ERP. The functions define the vocabulary; only the queries underneath change.
  2. 02Add an approval step before writes, so edits are proposed with their before-and-after and committed by a person.

Worth knowing

  • dependency

    The working version runs on a synthetic dataset built for it: around 2,000 sales across 18 months, with plausible seasonality. Swapping it for a real database is a change of queries, not of architecture.

  • future

    Anything the nine functions do not cover cannot be answered, and the system says so instead of improvising. That is the feature, not a gap to be patched with a generic SQL escape hatch.

  • dependency

    Writes are limited to a whitelist of columns per table, and always come back with the record before and after the change.

  • cost

    A query resolves in under 30 milliseconds. What the user waits for is the model choosing the function, not the database.

Contact

Let's have a call. In 30 minutes I can tell you whether I can help.

Email address (enable JavaScript to reveal)LinkedIn