maazaowski
HomeBlogProjectsAbout

Get new posts in your inbox

Software, AI agents, and what I learn building them.

© 2026 maazaowski. All rights reserved.

GuestbookLinkedInGitHubEmail
AI EngineeringAILLM EvalsPrompt EngineeringSoftware EngineeringCustomer Support

Prompts are code. Start testing them like code.

Your prompt is the most important function in your app. Why is it the only one with no tests?

June 29, 2026·Updated June 29, 2026·7 min read

...

A few weeks into building a system that turns messy supplier invoices into clean, structured data, I caught myself doing something I'd never tolerate anywhere else in the codebase.

I changed a prompt, eyeballed two examples, decided it looked better, and shipped it.

No test. No diff. No proof. Just vibes.

That's the quiet secret of a lot of LLM products right now. The prompt is the single most important piece of logic in the whole system, the function that decides whether the output is right or wrong, and it's the one piece we change by feel and ship on faith.


The Reframe

Here's the shift that fixed it for me: a prompt is a function.

It takes inputs (a document, a schema, some context) and returns an output: structured data. We've known how to keep functions honest for fifty years. You write down the right answer, you run the function, you check. Prompts don't get an exemption just because they're written in English.

So we built the boring thing. Regression tests for prompts.


What the Harness Actually Looks Like

Three parts, none of them clever.

A library of labeled cases. Real invoices, each paired with a hand-verified correct answer. Not copied from a previous model run. Not accepted because it looked reasonable. A committed answer key, confirmed by reading the source document and checking every number.

test_cases/
  invoices/
    acme-telecom-001.json
    acme-telecom-002.json
    fast-logistics-001.json
    global-freight-001.json
    edge-cases/
      multi-currency-001.json
      partial-credit-note-001.json

Each case looks like this:

json
{
  "input": "acme-telecom-002.pdf",
  "expected": {
    "vendor": "Acme Telecom",
    "total": 4823.50,
    "tax_rate": 0.17,
    "line_items": [
      { "description": "Monthly Service Fee", "amount": 3500.00 },
      { "description": "Support Add-on", "amount": 823.50 },
      { "description": "Equipment Rental", "amount": 500.00 }
    ],
    "currency": "USD"
  }
}

A harness that scores the output. For every case: did each field come back correct, did the model invent data that isn't in the source, do the line items sum to the stated total, and how much did this cost to run?

Run-versus-run comparison. When I change a prompt, I can see across the whole suite exactly which cases got better and which got worse.


The First Run Was Humbling

The first time I ran it, the result was humbling and clarifying in equal measure.

A prompt improvement I was proud of had quietly fixed three cases and broken five.

I would have shipped it. I'd have sworn it was better. The harness showed me the receipts.

Here's what that comparison looks like in practice:

txt
Baseline:
  Row accuracy:           87%
  Number reconciliation:  79%
  Invented fields:        12
  Avg cost per invoice:   $0.004
  Avg latency:            1.9s
 
Candidate:
  Row accuracy:           93%
  Number reconciliation:  91%
  Invented fields:        4
  Avg cost per invoice:   $0.005
  Avg latency:            2.3s

Before this, prompt review sounded like: "I think this version is better."

After this, it sounds like: "Row accuracy is up six points, reconciliation is up twelve, invented fields dropped by two thirds. Latency is up 400ms and cost is up slightly. Is that trade-off worth it for this supplier tier?"

That's a real conversation. The vibes version never got there.


Prompts Rot Exactly Like Code

A tweak that helps the document in front of you regresses the ten you're not looking at.

Without a test suite, you don't have a prompt. You have a rumor.

It's invisible until it isn't. The output can look fine and still be wrong in ways that only surface in production. A total off by a rounding error. A line item the model invented. A tax rate pulled from the wrong column. Everything looks clean until someone pays the wrong amount.


Two Things I Didn't Expect

The hard part isn't the harness. It's the answer key.

Building trustworthy answer keys, reading real invoices and reconciling every number by hand, was days of unglamorous work. It was also, by a wide margin, the most valuable thing we did.

You can't measure a model without a trusted answer to measure against. And that answer doesn't build itself. It means reading documents. Labeling cases. Agreeing on what counts as a correct extraction when the source format is ambiguous. Creating examples for the edge cases your team keeps forgetting.

If you take one thing from this: invest in the answer key before you invest in anything clever.

You can grade a lot without a human, or even a second model.

Two of our most useful checks are completely deterministic.

Source checking. If a value the model returned doesn't literally appear in the source document, flag it as a possible invented value. No secondary model, no subjectivity. Just: does this string appear in the source?

Reconciliation. The extracted line items have to sum to the total the document itself states. If they don't, something went wrong. This check catches extraction errors, rounding mismatches, and missed line items, and it costs nothing to run.

python
def check_reconciliation(extracted, tolerance=0.01):
    extracted_sum = sum(item["amount"] for item in extracted["line_items"])
    stated_total = extracted["total"]
    delta = abs(extracted_sum - stated_total)
    return {
        "passed": delta <= tolerance,
        "delta": delta,
        "extracted_sum": extracted_sum,
        "stated_total": stated_total
    }

Simple checks beat clever ones. Start with the deterministic ones before you reach for anything that requires a second API call.


The Mental Model

This is the loop now:

The important part is the loop. You're not trying to get the prompt right in one shot. You're building a system where every change becomes measurable.


The Payoff Isn't Only Quality. It's Speed with a Seatbelt.

I can now try an aggressive prompt rewrite, run the suite, and either commit it with evidence or throw it away in minutes. Instead of deploying it and finding out in production which invoices it silently mangled.

The test suite also becomes a record your team can actually rely on.

Every unusual invoice becomes a future regression case. Every production failure becomes a test. The suite holds the cases your team would otherwise forget:

  • The invoice where the line item total matches but the tax is embedded rather than separate
  • The supplier who uses a comma as a decimal separator
  • The credit note that looks like a regular invoice until you check the sign on the total
  • The PDF where the table spans two pages and the model only sees the first A prompt alone can't reliably hold all of that. A prompt plus a regression suite can.

The One Habit Worth Stealing

If you're building anything where the output actually matters (money, contracts, health, compliance), here's where to start:

Version your prompts. Test them against labeled cases. Refuse to ship a change you can't prove is an improvement.

The feedback loop is the advantage. Not the prompt.

Treat your prompts like code. Not for rigor's sake, but because they are code. They're just wearing a sentence as a disguise.


Have you built a test harness that changed how your team ships AI features? Or are you still shipping by vibes and hoping for the best? Drop a comment. I'd like to hear how others are testing this in the real world.

Share:

Subscribe to the newsletter

New posts on software, AI agents, and what I learn building them. Straight to your inbox, no spam.

Discussion

Loading comments...

Related Posts

AI Engineering·July 14, 2026·7 min read

Why three of my clients replaced their SaaS tools this year

Three separate clients asked me to replace their SaaS tools this year. Same story each time. The math has shifted, and most product companies don't have an answer for it.

SaaSAI
Read→
AI Engineering·June 18, 2026·7 min read

AI is a compiler, not a runtime

Everyone wants to put a model in the loop. After a while building real systems, I think most people are putting it in the wrong place.

aisoftware-engineering
Read→

On this page

  • The Reframe
  • What the Harness Actually Looks Like
  • The First Run Was Humbling
  • Prompts Rot Exactly Like Code
  • Two Things I Didn't Expect
  • The hard part isn't the harness. It's the answer key.
  • You can grade a lot without a human, or even a second model.
  • The Mental Model
  • The Payoff Isn't Only Quality. It's Speed with a Seatbelt.
  • The One Habit Worth Stealing