Metering, model routing and the regression suite that stops your agent rotting
Two questions decide whether an AI feature is still running a year after launch, and neither of them is about the model.
Does each request make money or lose money? And when somebody edits a prompt on a Thursday afternoon, will you find out if it broke something?
Both have unglamorous answers. Getting them wrong is how AI features die - not dramatically, but by quietly costing more than they earn while slowly becoming less correct than they were at launch.
Meter server-side, route by difficulty, cache identical work, and write a regression suite that asserts question-to-metric mappings.
- Metering runs on the hosted brain, so an on-prem component cannot be tampered with to get free requests.
- Two-tier model routing - cheap for routine reports, strong for advice - is where the unit economics actually move.
- An append-only credit ledger in Postgres, debited per request, with a soft wall rather than a hard cut.
- A fixed suite of "when a user asks X, the correct metric is Y" cases runs on every deploy. It is the only thing that catches a silent behavioural regression.
Why the usual pricing instinct fails
Ordinary SaaS has a marginal cost near zero. One more user is some rows and a little CPU, so per-seat pricing works: revenue scales, cost barely does.
An LLM feature does not behave like that. Every request costs real money, and the distribution of usage is heavily skewed. Charge a flat fee and a handful of enthusiastic customers consume the margin of everyone else. Charge purely per request and, in this market, the customer stops using it - which is the worst outcome available, because usage is what creates the value in the first place.
So: a flat monthly add-on with an included credit bucket, then metered overage. Predictable for the shop, capped for us. The credit sizing is derived from measured cost, which requires actually measuring it.
Meter where the customer cannot reach
Rai ships a connector to the client's own server. The metering does not live there.
This is not about distrust. It is that a counter running on someone else's machine is a counter they can edit, and once one client discovers that, the economics of the product are a rumour. Keeping the ledger server-side means nobody has to think about it again - including us.
The ledger is append-only in Postgres. Every request writes a debit row; balance is a sum, never a mutable column. That makes disputes answerable ("show me the requests") and makes double-charging a bug you can detect rather than a possibility you argue about.
CREATE TABLE credit_ledger (
id bigserial PRIMARY KEY,
tenant_id uuid NOT NULL REFERENCES tenant(id),
request_id uuid NOT NULL UNIQUE,
delta integer NOT NULL, -- negative = debit
reason text NOT NULL, -- 'report' | 'advice' | 'topup'
model text,
input_tokens integer,
output_tokens integer,
created_at timestamptz NOT NULL DEFAULT now()
);
The UNIQUE on request_id is the whole idempotency story. A retried request debits once.
The three levers on margin
Route by difficulty. Generating a routine report - the metric is defined, the parameters are extracted, the query is known - is a small job. Advice is not: "what should I do about my slowest stock" needs real reasoning over several retrieved figures. A cheap fast model handles the first, a stronger one handles the second. This single decision moves unit economics more than everything else in the system combined.
Cache identical work. Three people in one shop asking for the same report on the same morning is one computation. The cache key is the metric, the resolved parameters, and a data-version token - not the raw question string, which varies pointlessly.
Size credits from measurements, not estimates. Which requires knowing what each request actually cost. Which brings us to the second half.
The failure mode ordinary software does not have
Here is the scenario that should worry anyone shipping an agent.
You change a sentence in a prompt to fix a formatting complaint. Everything still works. Nothing errors. But a class of questions that used to resolve to gross_margin_by_category now resolves to revenue_by_category, and for three weeks a set of customers get answers that are wrong in a way nobody notices.
There is no stack trace for that. No test failed, because in most codebases there was no test that could fail.
So write the test
Rai has a fixed suite of cases in the form when a user asks X, the correct metric is Y. It runs on every deploy.
CASES = [
("what did we sell last month", "revenue_by_period"),
("am I making money on power tools", "gross_margin_by_category"),
("ni nini haijauzwa kwa muda mrefu", "dead_stock"),
("which supplier is charging me more", "supplier_price_variance"),
("nani ananidai", "receivables_aging"),
("stock ya kaa gani inaisha", "days_of_stock"),
]
@pytest.mark.parametrize("question,expected", CASES)
async def test_metric_resolution(question, expected, agent):
assert (await agent.resolve_metric(question)).name == expected
Real phrasings, including the awkward ones, including Swahili and Sheng, because those are exactly the cases a prompt tweak quietly breaks. A change that re-routes a question now fails the build instead of reaching a shop.
It is a boring artefact and it is the highest-leverage thing in the repository. It converts the scariest class of agent bug - the silent behavioural regression - into an ordinary red build. Nothing else in the toolchain does that.
It resolves the metric rather than asserting on generated prose, deliberately. Asserting on output text produces a suite that breaks on every wording change and gets deleted within a quarter. Asserting on the routing decision is stable, and the routing decision is what actually determines correctness.
Traces, errors, staging
Every agent run is traced through Langfuse - prompts, tokens, latency, path taken. It does double duty: it is how you debug a strange answer, and it is the source of truth for what a request actually cost, which is what credit sizing depends on. Errors go to Sentry.
Both are cloud free tiers on purpose. There is a version of this where you self-host the observability stack on day one and spend a fortnight on it. That fortnight belongs in the metric catalog. Move to self-hosted if volume ever makes it worth it, and not before.
And prompt and catalog changes go to a staging brain before production, always. Those are precisely the changes that break things without failing anything, and they are the ones people are most tempted to push directly because they "are only text".
What I would tell anyone building one of these
The interesting engineering is not the agent loop. It is:
- knowing what each request costs, from real traces rather than estimates;
- routing cheap work to cheap models, and being disciplined about which is which;
- metering somewhere the customer cannot reach;
- and a regression suite that catches the day a prompt tweak silently changed what a question means.
None of that appears in a demo. All of it decides whether the thing is still running, and still correct, twelve months later.
Frequently asked questions
- Why an append-only ledger instead of a balance column?
- Because a balance column cannot answer a dispute. An append-only ledger makes the balance a sum over rows you can show the customer, makes double-charging a detectable bug rather than an argument, and makes a unique constraint on request id the entire idempotency story for retries.
- How much does two-tier model routing actually save?
- It depends on your traffic mix, but routine report generation typically dominates request volume while advice dominates cost per request. Moving the high-volume, low-difficulty path to a cheaper model changes the unit economics more than any other single decision in the system - which is why it is worth designing the routing boundary deliberately rather than by default.
- Why assert on metric resolution instead of the answer text?
- A suite that asserts on generated prose breaks on every wording change and gets deleted within a quarter. Asserting on which metric a question resolves to is stable across rewording and is the decision that actually determines whether the number is right.
- Is a cloud observability free tier really enough to start?
- For early volume, yes, and the alternative has a real cost: a fortnight spent self-hosting a tracing stack is a fortnight not spent on the metric catalog, which is where correctness actually comes from. Move to self-hosted when volume forces it, and treat that as a good problem to have.