Why an LLM at All
TF-IDF plus logistic regression. I scored it on the same gold as the LLM.
The classifier writeup measures upgrades on top of a language model: a sharper prompt, retrieval, a bigger model for the hard rows. It does not answer whether the task needs an LLM.
So I built the boring baseline. It puts TF-IDF features into logistic regression, one classifier per axis. I trained it on 300 real snippets that the Opus judge graded, which is the scaled set and not v1’s synthetic one. I scored it once against the same 54 hand-labeled rows. Standard, not tuned.
One snippet, two arms, one human gold set. The table below is the score; the fork is why the comparison is fair.
| Against human gold, n=54 | Classical baseline | The LLM |
|---|---|---|
| Category accuracy | 72.2% | 92.6% |
| Domain accuracy | 66.7% | 92.6% |
| Cost per article | $0.00 | one API call |
| All 54 rows, wall-clock | 4.8 ms | network-bound, seconds |
The LLM wins by 20 points on category and 26 on domain. Both arms ran on identical rows, so a test can measure the gap. A paired exact McNemar returns p=0.013 on category and p=0.0005 on domain. I also counted the disagreements. The baseline alone was wrong 14 times to the LLM’s 3 on category, and 15 to 1 on domain (ADR-017, full report in baseline_eval.txt).
Where the baseline broke
The misses do not spread evenly. The baseline collapses toward whichever class is lexically loud.
- five of six
policystories came back asoperations - six of eleven
landrows came back asair
baseline_eval.txt record the bake-off, and the paragraph
above links both. The strip shows two boundaries, not the whole
confusion matrix. It does not show why these two boundaries are hard.
The two sentences below carry that reason. Those are the labels that depend on what kind of claim an article makes, not which words it contains. A treaty and a deployment share a vocabulary; only one of them is policy.
What that means for the spend: the LLM buys the boundaries that need reading comprehension.
Two handicaps
The training labels came from the Opus judge, not from people. So the
baseline learned to imitate a grader that itself disagrees with humans
about 5–6% of the time. Then I tested it against those humans.
Separately, industry had exactly one
training row, which makes that label structurally unlearnable. The
baseline scored 0.000 on it, as it had to.
Why they stay: both cut against the baseline. The same class of unfairness inflated the BM25 lift until I rebuilt the baseline. A margin this size survives the caveats.
Run the loser
This is the same fit, and it runs in your tab. I exported the model as data, about 208 KB over the wire, with no server and no key. It is the arm that lost the bake-off. Here you watch the wrong class light up. That is faster than a read of the confusion matrix.
A row from the gold set, or your own sentence
Pick an example, or type a sentence.
Baseline, live in this tab
category
—
operational_domain
—
region: not covered. The shipped classifier assigns a third axis. The baseline never had region labels to train on, so there is nothing to show here.
Recorded, not run
Strip 03The model is vendored from
the
classifier repo at commit 7b61315 — the export
and its inference module travel as a pair, and the repo’s parity
gate asserts this JavaScript reproduces scikit-learn’s own
decision function to 1e-6. The gold and LLM columns are read out of
committed artifacts
(data/gold/gold.csv,
evals/gold_predictions_v3.csv).
No API is called from this page.