Routing overhaul: measured eval harness, hijack-proof corpus, top-3 pooling - #35
Merged
Merged
Conversation
…ooling
Routing accuracy measured on a new held-out benchmark of 353 labeled
queries (scripts/routing_benchmark.json):
semantic router: 76.8% -> 98.9%
keyword (?lite=1): 45.9% -> 97.5%
What changed:
- scripts/eval_routing.py: offline eval that replicates the browser
pipeline exactly (same quantized ONNX model, same scoring); reports
per-engine accuracy and names the corpus phrase that won each
misroute. Run after any corpus edit.
- scripts/search_phrases.json: full corpus rewrite. Removed all
single-word "attractor" phrases ('buy', 'music', 'guide' each caused
multiple hijacks), sharpened intent boundaries (brave = synthesized
answers / creative / live sentiment, ddg = simple facts + navigation,
wirecutter = new-product buying advice, ebay = used/vintage/parts),
expanded keyword rules so lite mode covers every engine (ebay was 9%,
hn 6%, youtube 13%).
- index.html scoreAll(): max-pooling -> mean of top-3 neighbors, so a
single stray phrase can't hijack a route (+1.7pts measured). Also:
drop undispatchable routes when config load fails but the model
loads (crash fix), remove '!gh' placeholder (no such bang), bump
EMBEDDINGS_VERSION to 15.
- scripts/validate_config.py: enforce corpus rules — no single-word
phrases outside the ddg route, no phrase in two routes, benchmark
queries must stay held-out of the corpus.
- tests: the '?q=how+to+make+pizza routes non-DDG' assertion conflicted
with recipes correctly being simple-DDG lookups; switched to a
video-shaped how-to with a wide non-DDG margin.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Routing quality is now measured, not guessed — and dramatically better. A new offline eval harness scores both routers against a held-out benchmark of 353 labeled real-world queries:
?lite=1/ low-memory)No destinations added or removed; same engines, same bangs.
What changed
scripts/eval_routing.py(new): replicates the browser routing pipeline exactly (same quantized ONNX MiniLM, same scoring math) and reports per-engine accuracy plus the exact corpus phrase that "won" each misroute — corpus debugging is now mechanical.scripts/routing_benchmark.json(new) holds the labeled queries; the validator enforces it stays held-out of the training corpus.scripts/search_phrases.json): removed every single-word "attractor" phrase (buy,music,guide,gif,twitter… each one hijacked unrelated queries under nearest-neighbor pooling), sharpened intent boundaries — Brave = synthesized answers / creative / live sentiment, DDG = simple facts + navigation, Wirecutter = new-product buying advice, eBay = used/vintage/parts — and expanded keyword rules so lite mode covers every engine (eBay was at 9%, HN 6%, YouTube 13%; all now 93–100%).index.html):scoreAll()now uses the mean of each route's top-3 cosine similarities instead of pure max-pooling, so one stray phrase can't hijack a route (+1.7pts measured, structurally more robust).search-config.jsonfails to load but the model loads (semantic result could name an engine the UI can't render);!ghplaceholder advertised a bang that doesn't exist;MIN_QUERY_LEN_FOR_MODELcomment/code mismatch.scripts/validate_config.py): rejects single-word phrases outside theddgroute, duplicate phrases across routes, and benchmark leakage into the corpus.?q=how+to+make+pizzaasserted a non-DDG routing, but a simple recipe lookup should go to DDG under the routing philosophy; swapped to a video-shaped how-to with a wide non-DDG margin.Test plan
python3 scripts/eval_routing.py— 98.9% / 97.5% on the held-out benchmark, 4 remaining misses all sub-0.05-margin ambiguous casesnpm run lint(ESLint + Ruff + config validator)🤖 Generated with Claude Code