Research report · July 2026
A binary classifier that decides, per query, whether an e-commerce search should return the ordinary product grid (SIMPLE — “men’s shirts”) or additionally trigger an LLM-generated shopping overview (COMPLEX — “shirts for a beach wedding in Hawaii in summer”). Built and evaluated on 6,636 ensemble-labeled queries from five public datasets, with leave-one-dataset-out generalization tests, a targeted commerce-complex challenge slice, and an explicit latency/cost frontier.
▶ Try the live demo — the exact int8 export of the recommended model, served from this site. Code on GitHub.
Recommendation. Ship the fine-tuned DistilBERT trained with commerce-complex enrichment, operated at threshold τ = 0.25. It is within noise of the best LLM-free accuracy on the IID test set, doubles recall on the commerce-complex slice the feature actually targets, and costs ~3 ms per query — 200× faster than putting an LLM in the routing hot path (649 ms p50, ~$0.02 per 1k queries).
Search overviews are expensive (LLM latency + tokens) and, on straightforward queries, worse than useless — they push the product grid below the fold. The router’s job is to spend that cost only where synthesis adds value. We use a binary taxonomy:
The full annotation rubric (with tie-breakers, e.g. attribute-stacking stays SIMPLE;
bare superlatives are COMPLEX; canonical occasion-categories like “wedding dress” stay
SIMPLE) is reproduced below and shipped as src/rubric.py.
Label SIMPLE when a conventional product-search backend fully serves the intent:
the query names a product, brand, model/SKU, or category, possibly with directly
filterable attributes (size, color, material, gender, price cap). The user knows
what they want; showing a ranked product grid is the complete answer.
Label COMPLEX when an LLM overview genuinely helps: satisfying the query requires
inference, synthesis, or explanation that a product grid alone does not provide.
Signals (any one suffices): occasion/context translation · advice/recommendation
intent incl. superlatives · multi-constraint trade-offs or persona fit ·
comparisons · compatibility/fitment · question-form or how-to ·
gift discovery · problem/symptom queries.
Tie-breakers: attribute-stacking alone stays SIMPLE no matter how many attributes;
a bare superlative ("best coffee maker") is COMPLEX; a price cap alone stays SIMPLE,
price + use case is COMPLEX; canonical occasion-categories ("wedding dress",
"running shoes") stay SIMPLE; nonsense/navigational fragments default to SIMPLE.
Five public query datasets, four of them commerce product-search logs and one general-web set (MS MARCO) kept deliberately as a domain-shift probe. From each we sampled, cleaned (length 3–150 chars, ≥90% ASCII, cross-dataset dedupe), and pooled 6,661 unique queries.
| Dataset | Source | Unique queries | Sampled | % COMPLEX |
|---|---|---|---|---|
| Amazon ESCI (US) | amazon-science/esci-data (Apache-2.0) | 97,345 | 2,500 | 4.7 |
| Home Depot | Kaggle relevance task (HF mirror) | 11,795 | 2,000 | 1.2 |
| MS MARCO (dev) | Microsoft passage ranking | 101,093 | 1,500 | 96.5 |
| WANDS (Wayfair) | wayfair/WANDS (MIT) | 480 | 480 | 3.2 |
| TREC Product Search ’23 | trec-product-search (HF) | 186 | 186 | 3.2 |
Figure 1 — Labels correlate strongly with dataset. Commerce logs are 95–99% SIMPLE; MS MARCO’s question-form queries are 96.5% COMPLEX. This imbalance drives both the generalization findings (§6) and the enrichment experiment (§7).
No public dataset carries this label, so ground truth was produced by an ensemble of Gemini 2.5 Flash annotators: two independent prompt variants — A: rubric-verbatim, label-only; B: a re-framed “router” task with per-query rationale — each labeling all 6,661 queries at temperature 0, plus an adjudicator variant with a larger thinking budget that ruled only on A/B disagreements.
| Model | Description | Params / cost profile |
|---|---|---|
| length_thresh | ≥6 tokens → COMPLEX (honesty baseline) | 1 constant |
| heuristic | hand-written rules: question words, superlatives, “vs”, gift, pronouns, “for” + occasion/persona lexicons | ~90 keywords, 1 µs |
| feats_gbm | 18 engineered lexical features → HistGradientBoosting | small tree ensemble |
| tfidf_logreg | word 1-2gram + char 3-5gram TF-IDF → logistic regression (C tuned on val) | sparse linear, 0.3 ms |
| minilm_logreg | frozen all-MiniLM-L6-v2 embeddings → logistic regression | 22M frozen + linear |
| distilbert_ft | DistilBERT fine-tuned end-to-end (3 epochs, 32 s on M-series) | 66M, ~3 ms |
| gemini_fewshot | Gemini 2.5 Flash, 10-example few-shot prompt (no rubric) | API call, 649 ms |
Splits: 70/10/20 train/val/test, stratified by label × dataset, fixed seed; all models share the identical test set (n = 1,328).
Figure 2 — Accuracy at argmax threshold, 2,000-resample bootstrap CIs. The hatched Gemini bar shares a model family with the labeling ensemble (§9); ★ marks the recommended model. All learned models beat the heuristic by wide, non-overlapping margins.
| Model | Acc | Macro-F1 | Complex P | Complex R | ROC-AUC | p50 lat. | $/1k queries |
|---|---|---|---|---|---|---|---|
| gemini_fewshot ⚠ | .971 | .962 | .917 | .969 | — | 649 ms | $0.02 |
| distilbert_ft | .961 | .945 | .950 | .884 | .985 | 3.3 ms | ~0 |
| distilbert_ft +enrich ★ | .957 | .942 | .907 | .916 | .984 | 3.3 ms | ~0 |
| tfidf_logreg +enrich | .953 | .932 | .954 | .844 | .976 | 0.3 ms | ~0 |
| tfidf_logreg | .949 | .927 | .947 | .834 | .974 | 0.3 ms | ~0 |
| minilm_logreg | .938 | .912 | .907 | .825 | .966 | 3.2 ms | ~0 |
| feats_gbm | .907 | .862 | .902 | .691 | .895 | 9.7 ms | ~0 |
| heuristic | .887 | .834 | .829 | .669 | — | 1 µs | ~0 |
| length ≥ 6 | .791 | .704 | .575 | .516 | — | ~0 | ~0 |
Three observations. First, query complexity is largely a surface-lexical property: a 0.3 ms sparse linear model recovers ~95% accuracy, and even a length threshold gets 79%. Second, fine-tuning buys its margin mostly on complex recall (.916 vs .844 for TF-IDF at comparable precision) — exactly the metric the product cares about. Third, the LLM’s +1 pt over DistilBERT costs 200× the latency; in a hot path serving every search, that trade is unjustifiable.
Each trainable model was retrained five times with one dataset fully held out. Accuracy stays high on held-out commerce datasets, but macro-F1 reveals the cost: recall of the rare COMPLEX class collapses on unseen domains.
Figure 3 — Macro-F1 with each dataset held out of training. Trained without MS MARCO, every learned model routes essentially all of its question-form queries to SIMPLE (3.5% accuracy on a 97%-COMPLEX set), while the keyword heuristic — which hard-codes question words — reaches 0.51 F1 / 73% accuracy.
The headline finding of this study: learned complexity signals do not transfer across phrasing domains. A model that has never seen question-form informational queries does not discover that “how much does X cost” is COMPLEX — no matter the architecture. Training-data diversity matters more than model capacity; the cheap insurance is a rule-OR-model union (route COMPLEX if either fires), which preserves the heuristic’s domain-independent floor.
Only ~160 of the pool’s COMPLEX labels come from commerce logs — yet “shirts for a beach wedding”-style queries are the whole point of the feature. Two interventions target this gap:
| Challenge slice (n=1,000) | Complex recall | False-positive rate |
|---|---|---|
| distilbert_ft (τ=.5) | .292 | .018 |
| tfidf_logreg (τ=.5) | .400 | .027 |
| distilbert_ft +enrich (τ=.5) | .462 | .034 |
| distilbert_ft +enrich (τ=.25) ★ | .600 | .063 |
| heuristic | .585 | .179 |
Figure 4 — Threshold sweep for the enriched DistilBERT. Moving from argmax (τ=.5) to τ=.25 lifts commerce-complex recall .462→.600 while IID accuracy slips only .957→.950 and the false-positive rate stays at 6.3%. The model is systematically under-confident on occasion-translation queries (they cluster at p≈0.1–0.6), so a lower threshold recovers them cheaply.
Net effect of both interventions: commerce-complex recall .292 → .600 (+31 pts) for −1.1 pt IID accuracy. The heuristic still catches slightly fewer of these than the shipped configuration while triggering falsely 3× as often.
The recommended model makes 66 errors on the 1,328-query test set. Reading all of them, three clusters emerge:
distilbert_ft_enriched at τ = 0.25, in-process
(~3 ms p50, no extra infra). Where 3 ms is too much, tfidf_logreg
at 0.3 ms keeps 95.3% accuracy but gives up complex recall.All code is public at github.com/verma7/commerce-query-router
(data and model weights gitignored, pipeline fully scripted): src/rubric.py (taxonomy), src/label.py +
aggregate_labels.py (ensemble labeling, resumable),
train.py / train_transformer.py / train_enriched.py
(models, --lodo), gemini_classify.py (LLM baseline),
evaluate.py (bootstrap metrics), predict.py (shipping Router),
demo/server.py (interactive demo at localhost:8642).
Raw-dataset sources and licenses: data/raw/MANIFEST.json. Splits, seeds, and all
reported numbers (results/*.json) are committed. Total external cost of the
study: ≈ $2 in Gemini 2.5 Flash calls.
Report generated 2026-07-23. Datasets: Amazon ESCI (Apache-2.0), Home Depot (Kaggle mirror), MS MARCO (non-commercial research terms), WANDS (MIT), TREC Product Search 2023. Labels are LLM-ensemble judgments, not human gold.