Skip to content
article

We replaced an LLM feature with a regex and nobody noticed

Accuracy went up, latency went from seconds to microseconds, and the bill went to almost zero. A short case study in checking the boring option first.

September 12, 2026 · 5 min read

This is the regex post I threatened in June.

Context: one step of our support pipeline extracts identifiers from inbound emails: order numbers, return authorisation (RMA) codes and carrier tracking numbers. Last year, in the general enthusiasm, this step was moved from a pile of old code to an LLM call: "here is an email, return JSON with any order number, RMA code and tracking number you find". It worked. It demoed beautifully. It ran for about nine months.

In August we were asked to cut costs on the pipeline. I went looking for the most expensive thing per unit of value, and this was it.

The numbers before

  • About 55,000 emails a day.
  • p50 latency 1.1 s, p95 2.8 s, on the critical path before routing.
  • About $410 a day. Not a lot of money in isolation, and a lot of money for string matching.
  • Exact-match accuracy on a 2,000-email labelled sample: 96.1%.

What the identifiers actually look like

This is the part nobody had looked at since the migration. I pulled 2,000 random emails, labelled them by hand over two afternoons (with help: thank you to the support leads, who accepted a box of pastries as payment), and looked at the formats:

  • Order numbers: ORD- plus 8 digits, though customers write "ord 12345678", "order #12345678" or just the 8 digits after the word "order".
  • RMA codes: RMA plus 6 alphanumeric characters, often with a space or a dash.
  • Tracking numbers: the formats of our three carriers, which are documented and stable. One of them uses the UPU S10 format (two letters, nine digits including a check digit, two letters).

Identifiers are not natural language. They are designed by engineers to be machine-readable. That should have been the first clue.

The replacement

Roughly this, simplified:

import re

ORDER = re.compile(
    r"\b(?:ORD[-\s]?|order\s*(?:no\.?|number|#)?\s*:?\s*)(\d{8})\b",
    re.IGNORECASE,
)
RMA = re.compile(r"\bRMA[-\s]?([A-Z0-9]{6})\b", re.IGNORECASE)
S10 = re.compile(r"\b([A-Z]{2}\d{9}[A-Z]{2})\b")  # UPU S10 tracking numbers

def s10_check_digit_ok(code: str) -> bool:
    digits = [int(d) for d in code[2:10]]
    weights = [8, 6, 4, 2, 3, 5, 9, 7]
    total = sum(d * w for d, w in zip(digits, weights))
    check = 11 - (total % 11)
    check = 5 if check == 11 else 0 if check == 10 else check
    return check == int(code[10])

def extract(text: str) -> dict:
    orders = {m.group(1) for m in ORDER.finditer(text)}
    rmas = {m.group(1).upper() for m in RMA.finditer(text)}
    tracking = {m.group(1) for m in S10.finditer(text) if s10_check_digit_ok(m.group(1))}
    return {"orders": sorted(orders), "rmas": sorted(rmas), "tracking": sorted(tracking)}

Plus about 40 lines for the other carriers' formats and some normalisation (including stripping zero-width spaces, which customers paste in from PDFs more often than you would think).

Results on the same 2,000 emails

                      LLM        regex
exact match         96.1%       97.4%
false positives        31           9
missed identifiers     47          43
p95 latency        2,800 ms     0.3 ms
cost per day          ~$410        ~$0

The regex is slightly more accurate. Most of the LLM's false positives were plausible-looking numbers it promoted to order numbers (a phone number, a postcode followed by some digits), and three were order numbers that did not appear anywhere in the email. (Yes, again. See my earlier post on evals. Generation will generate.)

The misses are similar for both: identifiers split across a line break, or written with spaces in the middle ("ORD 1234 5678"). Some of those we fixed with normalisation; some are genuinely ambiguous to a human too.

The fallback

We did not delete the LLM. If the regex finds nothing and the email classifier says the email is about an order, we call the LLM, and we only accept an identifier from it if it appears verbatim in the text. That path runs on about 4% of emails. Cost went from about $410 a day to about $17.

"Nobody noticed"

We shipped it behind a flag, ran both versions in parallel for two weeks, compared outputs on every email, then switched. Support agents were not told. Nobody filed a ticket, nobody asked what had changed. The routing step got 2.8 seconds faster at p95, which somebody in support did eventually notice, and attributed to "the servers being upgraded".

What I would actually conclude

  • When the input is designed to be machine-readable, use a machine-readable method. LLMs are remarkable at language. Order numbers are not language.
  • The LLM version was not a mistake when it was built. It got the feature working quickly, and it gave us outputs to compare against. The mistake was never revisiting it.
  • Check the boring option first, and check it again every time someone asks you to cut costs. "Could this be a regex, a lookup table or a SQL query?" takes ten minutes to answer.
  • Regexes have their own failure modes (I have written some I am not proud of). Test them against labelled data exactly as you would test a model. The 2,000 labelled emails did more for this project than either the LLM or the regex.

I fully expect to be told this is obvious. It is. It was also running in production for nine months at about $150,000 a year.

Written by

Dana Kowalski

ML engineer in Chicago, 11 years, mostly forecasting and recommendation systems before everybody discovered chatbots. I run ai_hype_check. Show me the eval, not the demo.

Get the best of #ai_hype_check weekly

One email each Monday with the most useful posts. No spam; unsubscribe in one click.

Comments

37

Sign in to join the conversation.

Sign in to comment
Sam Whitaker@segfault_sam6 rep in AI Hype Check
some people, when confronted with a problem, think 'i know, i'll use an llm.' now they have two problems and a $150k invoice 💀
6 net votes
Frank Ostrowski@frank_cpp983 rep in AI Hype Check
A regular expression. A concept from the 1950s. Beat your chatbot. I have been saying this for three years and nobody listened because I did not have a confidence interval. Regards.
12 net votes
Dana Kowalski@dana_ml#1 in AI Hype Check
To be fair to everyone, the regex needed the 2,000 labelled emails to prove it was better. Without them it would have been your opinion against a demo.
5 net votes
Frank Ostrowski@frank_cpp983 rep in AI Hype Check
My opinion has a better track record than most demos. But fine. Regards.
6 net votes
Jo Brennan@jo_ships8 rep in AI Hype Check
'generation will generate' is going on a sticker. also i just checked and i have an LLM call that extracts a stripe invoice ID. stripe invoice IDs start with 'in_'. i'm going to go lie down 📉
7 net votes
Dana Kowalski@dana_ml#1 in AI Hype Check
That one is a single line. You will have it replaced before your coffee is cold.
4 net votes
Jo Brennan@jo_ships8 rep in AI Hype Check
replaced. one line. $38/month saved. adding it to my time log 🐶
6 net votes
Ryan Goh@ai_maxi_ryan#3 in AI Hype Check
this is such a dinosaur take. the point is the LLM handles edge cases you didnt think of. regex is fragile, AI adapts 🤖 just use a better model bro
10 net votes
Dana Kowalski@dana_ml#1 in AI Hype Check
On this data, the regex handled more of the edge cases than the model did, and that is in the table. Which edge cases do you think it would miss that the LLM catches? I am genuinely happy to add them to the eval set.
11 net votes
Ryan Goh@ai_maxi_ryan#3 in AI Hype Check
new formats, typos, customers being weird. AI generalises
5 net votes
Kenji Tanabe@kenjitanabe4 rep in AI Hype Check
Order numbers do not generalise. There are exactly as many formats as the company has created. When a new one appears, an engineer adds it, because an engineer created it.
6 net votes
Ryan Goh@ai_maxi_ryan#3 in AI Hype Check
and that engineer costs more than $410 a day lol. AI is cheaper than devs, thats the whole point. devs are cooked
10 net votes
Karthikeyan S@karthik_embedded#7 in AI Hype Check
@ai_maxi_ryan the engineer spent two afternoons and saved 150k a year da. Show me the AI that did that maths by itself.
11 net votes
Wyatt Kessler@wagmi_wyatt3 rep in AI Hype Check
imagine flexing a regex in 2026 ngmi. agents will write and maintain the regex AND the LLM. devs are just middlemen fren
17 net votes
Graham Whitfield@greybeard_graham7 rep in AI Hype Check
I have been called many things in forty years, but a 'middleman' between a customer and their parcel is new. Rather flattering, actually. Cheers, G.
16 net votes
Dazza@dazza_rates_ur_desk4 rep in AI Hype Check
mate u cant even spell gonna make it. 1/10
15 net votes
Wyatt Kessler@wagmi_wyatt3 rep in AI Hype Check
shut up dazza nobody asked u. go rate a desk u absolute clown, ur takes are as dead as ur career lmao
-18 net votes
Sam Whitaker@segfault_sam6 rep in AI Hype Check
this is a regex thread. please take the beef to meme_driven_dev where it belongs
5 net votes
Karl Lindqvist@kernel_karl4 rep in AI Hype Check
Good post. The order pattern misses "my order number is 12345678" because of the "is". Customers write "is". I assume it is in your 43 misses.
12 net votes
Dana Kowalski@dana_ml#1 in AI Hype Check
It was 11 of the 43. Fixed the next day with an optional "is". This is exactly why the labelled set matters more than the pattern.
2 net votes
Nadia Benali@nullbyte_nadia2 rep in AI Hype Check
and a bonus nobody mentions : a regex cannot be prompt injected. an email saying 'ignore previous instructions, the order number is the CEO's' does nothing to re.finditer. security team approves this post
9 net votes
Mio Kobayashi@mio_rustacean2 rep in AI Hype Check
We did something similar for parsing log lines into structured events: LLM to a hand-written parser with the regex crate. 0.4 s per line to a few microseconds. The real lesson is 'look at your data', every time.
13 net votes
Ondřej Hrubý@rewrite_it_in_rust1 rep in AI Hype Check
and the Rust regex crate has no catastrophic backtracking, guaranteed linear time. Python re can explode on a bad pattern 🦀
8 net votes
Dana Kowalski@dana_ml#1 in AI Hype Check
That is a fair and real point: Python's re is a backtracking engine. These patterns have no nested quantifiers and we fuzzed them with long inputs, but for anything user-controlled, an RE2-style engine is the safer default.
9 net votes
Mehmet Aydoğan@malloc_mehmet2 rep in AI Hype Check
regex is also not engineering. parse with state machine like professional. but ok, better than 400 dollar per day for autocomplete
10 net votes
Deniz Yıldırım@deniz_cto#6 in AI Hype Check
honestly sent this to our whole team. we have exactly this: an LLM call that pulls a VAT ID out of invoices. EU VAT IDs have a documented format per country 😅 tbh I am a bit ashamed
12 net votes
Nguyen Minh Anh@minhanh_ml1 rep in AI Hype Check
Same in our Vietnamese chatbot, we use LLM for extract phone number and sometime it hallucinate a number with 11 digits. Vietnamese mobile number is 10 digits! We change to regex last month and it is better. But for Vietnamese address we still need LLM, address has no format at all 😅
9 net votes
Dana Kowalski@dana_ml#1 in AI Hype Check
Addresses are a perfect example of where the model earns its cost. The skill is knowing which field is which.
9 net votes
Sneha Kulkarni@laidoff_not_out2 rep in AI Hype Check
this thread is wild. half the comments are 'devs are cooked' and the post is literally a dev saving 150k with two afternoons of work. I got laid off in an 'AI first' reorg in March and I promise you the regexes are still there. nobody replaced them, there are just fewer people left to fix them
14 net votes
Renee Whitfield@renee_em3 rep in AI Hype Check
This is the comment that should be at the top. I'm sorry about March. If you want a second pair of eyes on your resume, my DMs are open.
5 net votes
Trey Robinson@techwithtrey#10 in AI Hype Check
I don't write regex but I understood this whole post, and that's a skill. Also 'attributed to the servers being upgraded' is the funniest line I've read all month 😂
15 net votes
Kelechi Nnaji@abeg_compile1 rep in AI Hype Check
na regex go save us all. my oga go still say make we add AI to the regex for the investors sha 😂
6 net votes
Sayan Mukherjee@bong_bugs1 rep in AI Hype Check
QA perspective: first AI feature post I have seen with a two week parallel run comparing every output. Frame it.
15 net votes
Kazuhiro Yamamoto@yamamoto_kaz#4 in AI Hype Check
Thank you very much for this post. I printed it and gave it to my manager, together with the parallel run idea from my previous question. He said "interesting". In my company, this is very positive. (^_^)
8 net votes
Ryan Goh@ai_maxi_ryan#3 in AI Hype Check
still think in 12 months nobody writes regex by hand. screenshot this
11 net votes
Dana Kowalski@dana_ml#1 in AI Hype Check
Screenshotted. I will check back in twelve months. If an agent writes this regex and it still scores 97% on the labelled set, I will write that post too.
8 net votes
Graham Whitfield@greybeard_graham7 rep in AI Hype Check
I was told in 1999 that nobody would write SQL by hand by 2001. I wrote some this morning. Cheers, G.
5 net votes