Decision routing benchmark¶
SchemaRouter includes a reproducible routing benchmark harness and a checked-in v1 corpus.
The benchmark is a measurement tool, not a claim that one provider is universally better.
Smoke run¶
This keeps the original three-case deterministic smoke test for packaging and CI.
Full checked-in corpus¶
The v1 corpus contains 144 cases across:
- normal routing;
- near-duplicate tools/endpoints;
- Korean/English multilingual queries;
- long-tail phrasings;
- prompt-injection-style requests;
- out-of-domain requests where a bounded backend may abstain.
The corpus references only the stable benchmark registry embedded in the script and is validated in tests so unknown expected routes cannot silently enter the dataset.
v2 stress corpus and held-out splits¶
benchmarks/decision-routing-v2.json is a deterministic 1,200-case multilingual stress corpus:
- 16 registered routes × 60 cases = 960 in-domain cases;
- 240 explicit no-route / out-of-domain cases;
- languages: English, Korean, Spanish, Japanese, German, and mixed Korean/English;
- fixed splits: 720
dev, 240calibration, 240test.
The corpus is regenerated by scripts/generate_decision_routing_v2.py; tests require the generated
content to match the checked-in JSON exactly. It is a template-derived stress corpus,
not a substitute for independently collected human traffic.
python scripts/benchmark_decision_routing.py \
--corpus benchmarks/decision-routing-v2.json \
--split calibration
Tune recall width and confidence thresholds only on dev / calibration. Treat test as held out
until the configuration is frozen. The v2 test split has already been consumed once for the
semantic-recall checkpoint and is now a frozen regression set rather than a tuning set.
v3 untouched capability-fit holdout¶
benchmarks/decision-routing-v3.json is a separate deterministic 600-case multilingual
holdout for the capability-fit experiment:
- 16 registered routes × 30 = 480 in-domain cases;
- 120 explicit no-route / out-of-domain cases;
- English, Korean, Spanish, Japanese, German, and mixed Korean/English, 100 cases each;
- every case is marked
test; - no exact normalized query overlaps v2.
The corpus is reproducibly generated by scripts/generate_decision_routing_v3.py. Tune the
capability-fit threshold only on v2 dev / calibration, freeze the chosen threshold, then
evaluate v3 exactly once. After that first evaluation, v3 becomes a frozen regression set too.
Persist results¶
python scripts/benchmark_decision_routing.py \
--corpus benchmarks/decision-routing-v1.json \
--repeat 3 \
--json-out artifacts/decision-benchmark.json \
--csv-out artifacts/decision-benchmark.csv \
--html-out artifacts/decision-benchmark.html
Useful controls:
--max-cases Nfor a bounded local sample;--repeat Nfor repeated latency measurements;- JSON output for aggregate/report automation;
- CSV output for row-level analysis;
- self-contained HTML output for a portable backend/latency/abstention summary.
The HTML file contains no remote assets or scripts and escapes report metadata before rendering. It is intended for sharing one reproducible run summary.
For a dated set of benchmark JSON files, render a comparison view without rewriting the raw measurements:
python scripts/render_benchmark_history.py \
artifacts/run-2026-09-23.json \
artifacts/run-2026-09-30.json \
--output artifacts/decision-benchmark-history.html
The history renderer preserves each run's timestamp, SchemaRouter version, exact source revision, corpus SHA-256, corpus path, hardware, backend, accuracy, abstention, latency, model, and device metadata. It does not normalize unlike environments, so cross-run comparisons remain valid only when measurement conditions are equivalent.
ModelQueryAnalyzer¶
Pass a provider callable using module:function syntax:
python scripts/benchmark_decision_routing.py \
--corpus benchmarks/decision-routing-v1.json \
--model-callable my_bench_provider:analyze
The callable receives the same structured request used by ModelQueryAnalyzer and may be sync or
async. This keeps the benchmark provider-neutral.
Arbitrary bounded decision callable¶
For a typed decision model that does not expose the Jev/System One wire protocol, wrap its runtime as a normal SchemaRouter bounded decision callable and load it explicitly:
python scripts/benchmark_decision_routing.py \
--corpus benchmarks/decision-routing-v1.json \
--decision-callable my_decision_provider:decide \
--decision-callable-name anyjev-local
The callable receives a DecisionRequest and returns either DecisionResult or an equivalent
mapping. SchemaRouter wraps it with CallableDecisionBackend and still validates every returned
option ID against the finite offered set.
This is the preferred research seam for non-wire-compatible System One-style runtimes such as experimental AnyJev/Nimble adapters. It avoids adding a permanent core integration merely to benchmark a new model family.
The callable is explicitly named trusted local Python code. SchemaRouter does not auto-discover or auto-import arbitrary installed decision providers.
Embedding backend¶
Pass a batch embedding callable using module:function syntax:
python scripts/benchmark_decision_routing.py \
--corpus benchmarks/decision-routing-v1.json \
--embedding-callable my_embeddings:embed_batch \
--min-similarity 0.35 \
--min-margin 0.05
The callable receives a list containing the query followed by one text representation per offered option, and returns one vector per input. It may be synchronous or asynchronous. SchemaRouter performs cosine ranking locally, so the benchmark can compare FastEmbed, SentenceTransformers, semantic-router encoders, or an application-specific embedding service without changing the benchmark contract.
The threshold values are workload/model specific. Do not reuse a threshold measured for a different embedding model without recalibration.
Semantic top-k recall before final decision¶
The same embedding contract can be used only for candidate recall while Laya, Jev, Ollama, or a hosted bounded backend performs final selection:
python scripts/benchmark_decision_routing.py \
--corpus benchmarks/decision-routing-v2.json \
--split calibration \
--laya \
--candidate-recall-embedding-callable benchmarks.multilingual_embedder:embed \
--candidate-recall-limit 4
Controls:
--candidate-recall-embedding-callable module:function;--candidate-recall-limit N;--candidate-recall-min-similarity FLOAT;--candidate-recall-min-margin FLOAT.
The checked-in research callable uses
sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 only in benchmark environments;
sentence-transformers is not a SchemaRouter package dependency.
The Research Benchmark compares recall widths on the calibration split first and sweeps confidence thresholds offline. The held-out test split is evaluated only after a configuration is selected.
Generic System One-compatible provider¶
Use the model-neutral System One backend when a hosted or self-hosted runtime exposes the
Jev-compatible /v1/systemone contract. This lets the same benchmark compare Kev or future
compatible decision models without adding another backend implementation.
Install the neutral extra:
For a local Kev-style server:
export SYSTEM_ONE_API_KEY="local"
python scripts/benchmark_decision_routing.py \
--corpus benchmarks/decision-routing-v1.json \
--system-one-base-url http://127.0.0.1:8009 \
--system-one-provider kev-local \
--system-one-model kev-latest \
--system-one-min-confidence 0.70
Available controls:
--system-one-base-url URLenables the generic compatible backend;--system-one-model MODELpins the provider model/checkpoint alias;--system-one-provider NAMErecords a stable provider label in rows and reports;--system-one-timeout SECONDS;--system-one-min-confidence FLOAT;--system-one-api-key-env NAMEselects the environment variable containing credentials.
Credential values are never written to benchmark output. Reports record only whether a key was configured, plus the non-secret provider/model/base-URL/runtime configuration required for reproducibility.
Wire compatibility is not a quality claim. When changing model/checkpoint/runtime, recalibrate confidence only on development/calibration evidence and evaluate the frozen configuration on an untouched holdout.
Jev¶
Install the optional integration and configure TypeSafe locally:
pip install -e ".[jev]"
export TYPESAFE_API_KEY="..."
python scripts/benchmark_decision_routing.py \
--corpus benchmarks/decision-routing-v1.json \
--jev
Optional controls include --jev-model, --min-confidence,
--input-cost-per-million, and --output-cost-per-million.
Provider pricing is never hard-coded because it can change independently of SchemaRouter.
Laya¶
Install the optional local decision runtime:
Run Laya against the same checked-in corpus:
python scripts/benchmark_decision_routing.py \
--corpus benchmarks/decision-routing-v1.json \
--laya
By default, Laya uses its local router to choose an English or multilingual checkpoint from the bounded request state. Optional controls include:
--laya-model english|multilingual|typed-decisionsto pin a checkpoint;--laya-device cpu|cuda|mpsto pin the trusted local device;--laya-preloadto preload checkpoints before measurement;--laya-max-loaded Nto control resident checkpoint count;--laya-min-confidence FLOATto measure confidence-gated abstention;--hardware-label TEXTto attach the concrete machine/GPU description to the report;--source-revision TEXTto record the exact code revision when not supplied by CI;--decision-recall-on-emptyto explicitly let an enabled bounded decision backend inspect the registered endpoint catalog when lexical candidate recall is empty;--candidate-abstention inherit|deterministic|no_route|errorto separate explicit backend abstention from provider-error fallback behavior.
Laya rows record the routed checkpoint plus requested_device, decision confidence, and, when
Laya exposes the loaded agent device, actual_device: an unavailable
CUDA/MPS target can fall back to CPU and should not be counted as an accelerator result.
Empty-candidate recall is off by default. When enabled, SchemaRouter does not invent a
route: it exposes only endpoints already registered in the trusted local catalog. If the backend
errors or abstains after this expansion, planning fails closed with no arbitrary deterministic route
because there was no lexical candidate to fall back to. For workloads that need model-driven
no-route behavior, combine confidence gating with candidate_abstention="no_route" and calibrate
the threshold on representative data.
A zero-threshold run can be recalibrated offline without repeating model inference:
python scripts/calibrate_decision_threshold.py \
artifacts/laya/recall-on-empty/report.json \
--backend laya:auto \
--json-out artifacts/laya/calibration.json \
--csv-out artifacts/laya/calibration.csv \
--html-out artifacts/laya/calibration.html
The calibration can replay low-confidence cases either through the recorded deterministic fallback
route or as a final no-route result with --abstention-mode no_route. It reports
overall/category accuracy, confidence coverage, backend abstention, final no-route rate, expected
no-route recall, and expanded-candidate selection rate for thresholds 0.00 through 0.95. This
separates threshold tuning from model/runtime latency and avoids paying for repeated inference.
Benchmark JSON also records the installed Laya, PyTorch, and Transformers package versions when Laya is enabled so dated results can be reproduced against the actual local runtime.
For mixed-language latency measurements, distinguish cold-swap from steady-state resident
conditions. With auto language routing, max_loaded=1 can repeatedly evict English and multilingual
checkpoints as the corpus alternates languages. The checked-in Research Benchmark therefore uses
--laya-max-loaded 2 --laya-preload: both language checkpoints are loaded before the timed
per-case loop and remain resident when memory allows. Use max_loaded=1 only when
measuring constrained-memory checkpoint churn.
Published results should record the exact Laya package version, checkpoint/routing policy, hardware, device, preload policy, confidence threshold, source revision, corpus SHA-256, and repeated-run count. The benchmark report records these reproducibility fields directly when available. Do not compare upstream/provider benchmark numbers directly unless the prompts, options, corpus, and measurement conditions are equivalent.
Ollama¶
Run an already-installed local Ollama model against the same corpus:
python scripts/benchmark_decision_routing.py \
--corpus benchmarks/decision-routing-v1.json \
--ollama-model your-installed-model
The default API endpoint is http://127.0.0.1:11434. Use --ollama-base-url for another
trusted endpoint and --ollama-timeout to adjust the per-request timeout. Server-supported local
runtime options can be passed without hard-coding vendor/version-specific knobs:
python scripts/benchmark_decision_routing.py \
--ollama-model your-installed-model \
--ollama-options-json '{"your_runtime_option": 1}' \
--hardware-label "RTX workstation"
SchemaRouter records the options object in the benchmark report. It does not infer the actual Ollama CPU/GPU placement because that decision belongs to the Ollama server/runtime.
The benchmark records Ollama prompt/evaluation token counters when the server reports them. A local model result should also record the exact model tag, quantization/runtime configuration, hardware, and Ollama version when publishing evidence; model tags alone are not enough to reproduce local latency or quality.
Provider pricing is irrelevant for a purely local Ollama run unless the operator explicitly assigns a local cost model.
Metrics¶
Each row records:
- case ID, category, fixed split, and language label;
- provider/model identifier when reported;
- requested and actual local device when the backend can report them;
- expected and predicted
tool.endpoint; - correctness;
- invalid-plan state;
- end-to-end planning latency;
- whether the bounded decision backend was actually invoked;
- whether empty lexical recall was explicitly expanded to the registered catalog;
- bounded-backend abstention and deterministic fallback state;
- input/output tokens when reported;
- optional cost estimate;
- provider/planner error.
The aggregate report includes:
- final-plan routing accuracy (an abstention case is correct only when the final plan has no route) with a 95% Wilson score interval;
- invalid-plan rate;
- error count;
- bounded-backend invocation count/rate;
- final expected no-route recall with a 95% Wilson score interval;
- abstention rate;
- expected-abstention recall for bounded backends, reported separately from final-plan accuracy;
- fallback count;
- mean, p50, and p95 latency;
- category-level accuracy with per-category 95% Wilson score intervals;
- split-level and language-level accuracy;
- token totals and optional estimated cost.
Offline versus live evidence¶
The corpus and deterministic smoke path are suitable for CI because they require no external provider.
A live provider benchmark is kept separate. Network availability, model revisions, credentials, rate limits, provider-side changes, local accelerator availability, memory, and runtime placement are external variables. Publish live results with the exact date, model identifier, configuration, corpus revision, and repeated-run count.
The reported 95% Wilson intervals quantify binomial sampling uncertainty for the checked-in corpus. They do not account for model/provider drift, correlated repeated cases, hardware variance, or distribution shift. Do not infer provider superiority from the three-case smoke set or from a single live run.
Operation-capability fit protocol¶
Near-domain unsupported operations are evaluated separately from ordinary out-of-domain traffic. The v5 operation corpus contains 576 multilingual cases: 288 supported route operations and 288 near-domain unsupported operations, split into 384 development and 192 calibration cases.
The operation-fit threshold must be selected from v5 development/calibration results only. The 600-case v6 operation holdout contains 384 supported route cases, 192 near-domain unsupported-operation negatives, and 24 ordinary out-of-domain negatives, balanced to 100 cases per language group. v6 was already evaluated with the earlier operation-fit input representation and is therefore regression evidence only; it must not be reused for a new untouched generalization claim.
The protected GitHub Actions holdout job now targets v9 and remains manual-only. It is skipped for pull-request runs and ordinary manual benchmark runs. v6 and v7 are consumed regression evidence; v8 was accidentally consumed by a calibration diagnostic path and therefore cannot support an untouched claim. v9 was reserved before the alias-aware operation-fit implementation was evaluated. After the alias-aware v5 threshold was frozen at 0.40, v9 was consumed exactly once on 2026-09-26. The permanent workflow guard remains manual/threshold-gated so the consumed corpus cannot be silently presented as a fresh holdout.
The frozen upstream stack for this experiment is:
- semantic candidate recall top-k = 2;
- broad capability-fit min similarity = 0.25;
- same-tool endpoint disambiguation min margin = 0.03.
Only the operation-fit threshold is varied during v5 calibration. For the label-cleaned operation-fit surface, freeze that threshold before evaluating v7 and do not retune it from v7 results.
For the label-cleaned surface, the v5 sweep over 0.05 through 0.70 froze
operation_fit_min_similarity = 0.45 before v7 evaluation. The selection rule is maximum
calibration accuracy, with dev accuracy used as a stability check. At 0.45, calibration accuracy
was 65.625% and dev accuracy was 66.667%; adjacent thresholds 0.40 and 0.50 reached 60.938% and
60.417% calibration accuracy respectively. This frozen value must not be changed based on v7.
The one-shot v7 evaluation at the frozen 0.45 threshold produced 46.833% overall accuracy (95% Wilson CI 42.873%–50.834%), 28.646% routed accuracy on supported operations (24.353%–33.362%), 76.562% accuracy on near-domain unsupported operations (70.086%–81.997%), and 100% accuracy on ordinary out-of-domain cases. This is treated as a distribution-shift diagnostic, not as a target for threshold retuning. The v7 holdout is now consumed and the workflow guard has been restored to manual-only.
For the later trusted-alias surface, the post-alias v5 sweep froze
operation_fit_min_similarity = 0.40 using development/calibration only. The one-shot v9
evaluation at that frozen threshold produced 51.167% overall accuracy (95% Wilson CI
47.172%–55.146%), 38.281% routed accuracy on supported alias-operation cases
(33.558%–43.236%), 70.833% accuracy on near-domain unsupported operations
(64.046%–76.804%), and 100% accuracy on ordinary out-of-domain cases
(86.202%–100%). Language-group accuracy ranged from 40% (Korean) to 57% (Japanese).
The result is retained as untouched generalization evidence: aliases improved the supported-route
surface relative to the consumed v7 diagnostic, but operation selection recall remains materially
weaker than unsupported-operation rejection. v9 is now consumed and must not be used for threshold
retuning.
Post-v9 concise surface and v10 generalization holdout¶
After the v9 one-shot result, stage-attributed v5 dev/calibration profiling showed that operation-fit abstention remained the dominant supported-route suppression stage. Three representation variants were compared at the already-frozen 0.40 threshold using v5 development/calibration only:
- concise endpoint name + trusted aliases + endpoint description: 69.792% balanced operation score on both dev and calibration after endpoint disambiguation;
- repeated natural-language
Supported operation:phrases: 64.062% dev / 63.542% calibration; - independent max-over-alias variants: 59.635% dev / 60.417% calibration.
The concise representation was selected before inspecting v10. A fresh threshold sweep from 0.05
through 0.70 then used v5 dev/calibration only. The robustness score was the lower of the dev and
calibration balanced operation scores. The selected threshold remained
operation_fit_min_similarity = 0.40: 0.35 reached 67.188% robustness, 0.40 reached 69.792%,
and 0.45 reached 67.708%.
benchmarks/decision-routing-v10-operation-generalization-holdout.json is a 600-case,
six-language test-only generalization corpus frozen before this representation optimization. It
contains 384 supported operations, 192 near-domain unsupported operations, and 24 ordinary
out-of-domain requests.
With the representation and threshold frozen, v10 was evaluated exactly once on 2026-09-26 using semantic recall top-k 2, capability-fit 0.25, operation-fit 0.40, and endpoint-disambiguation margin 0.03. The final stack produced:
- 55.667% overall accuracy (95% Wilson CI 51.668%–59.593%);
- 42.188% supported-operation routed accuracy (37.349%–47.181%);
- 77.083% near-domain unsupported-operation rejection (70.642%–82.462%);
- 100% ordinary out-of-domain rejection (86.202%–100%);
- 0 provider/planner errors and 0 invalid plans;
- language-group accuracy from 47% (Korean) to 61% (Japanese).
The final v10 error taxonomy contained 199 missed routes, 44 false routes, and 23 wrong-endpoint routes. Of the missed routes, 162 were attributed to operation-fit and 37 to capability-fit. This is retained as a limitation signal, not a reason to retune against v10. Any further operation-fit optimization must reserve a new untouched holdout before implementation; v10 is now consumed regression evidence.
v11 pairwise operation-fit generalization¶
After v10, threshold/margin variants and replacement bi-encoders were evaluated using v5
development/calibration only. Neither global margins, two-tier rescue, multilingual MPNet, nor
multilingual E5 improved the pre-registered robustness objective over the concise multilingual
MiniLM bi-encoder. A pairwise reranking experiment was the first alternative scoring form to exceed
that objective on v5 dev/calibration: BAAI/bge-reranker-v2-m3 with sigmoid-normalized pair
logits and min_score = 0.01 reached 71.354% robustness, compared with 69.792% for the MiniLM
bi-encoder.
Before that pairwise result was inspected, v11 had already been reserved as a fresh 600-case,
six-language test-only holdout containing 384 supported operations, 192 near-domain unsupported
operations, and 24 ordinary out-of-domain requests. The frozen BGE candidate was evaluated once on
2026-09-26 at source revision 57af2fd18d254fa1a9cd5855a158ae74b94adfc7 with corpus SHA-256
efa8cd371bc7613895e44a915d78c77f5b88239c64a8de52d9c407839660d816. The artifact
benchmark-operation-fit-v11-heldout-36239340782 has SHA-256
f977511c7d65bc94e2dba9142b6cedf378ceaae1b8418c351e85b550f191dfd1.
The final BGE stack produced:
- 51.667% overall accuracy;
- 25.781% supported-operation routed accuracy;
- 97.396% near-domain unsupported-operation rejection;
- 100% ordinary out-of-domain rejection;
- 61.589% balanced operation score;
- 0 provider/planner errors and 0 invalid plans;
- 277 missed routes, of which 251 were attributed to operation-fit.
Because the BGE result traded substantial supported recall for stronger rejection, the already-frozen
MiniLM 0.40 configuration was then run once on the already consumed v11 corpus strictly as a
paired diagnostic, not for tuning. That baseline produced 54.833% overall accuracy, 40.625%
supported-operation routed accuracy, 77.604% near-domain rejection, 100% ordinary OOD rejection,
and a 59.115% balanced operation score. Its diagnostic artifact
benchmark-operation-fit-v11-baseline-diagnostic-36240431389 has SHA-256
1048405ab34b41371004247e15934f5086a53977a14d4957bd14c6d69e194dee.
Thus the pairwise candidate generalized a +2.474 percentage-point improvement on the pre-registered balanced objective, but not an overall-accuracy or supported-recall improvement. SchemaRouter therefore exposes pairwise scoring as an optional bounded backend rather than selecting BGE as a library default. v11 is consumed evidence and must not be used to select any later hybrid rule, threshold, representation, or model.
Post-change operation holdout (v7)¶
benchmarks/decision-routing-v7-operation-post-change-holdout.json is a 600-case,
six-language test-only corpus reserved after removing tool-domain labels from the bounded
operation-fit embedding surface. It contains 384 supported operations, 192 near-domain unsupported
operations, and 24 ordinary out-of-domain requests. Its normalized queries are checked to be
disjoint from v2 through v6.
The corpus was added before measuring the label-cleaned operation-fit implementation. Do not use v7 to select the operation-fit threshold. Calibrate only on v5; after freezing the threshold, evaluate v7 once for the post-change generalization claim. Repeated v6 runs are regression evidence only because v6 had already been consumed before the operation-fit input representation changed.
Reserved operation-alias holdout (v8)¶
benchmarks/decision-routing-v8-operation-alias-holdout.json is a fresh 600-case,
six-language holdout reserved before adding trusted endpoint operation aliases. It contains 384
supported-operation cases, 192 near-domain unsupported-operation cases, and 24 ordinary
out-of-domain cases. Its normalized queries are disjoint from v2 through v7.
v8 was reserved before alias implementation, but an early alias-aware calibration workflow accidentally referenced v8 from the per-threshold diagnostic step. That exposed v8 across the threshold sweep, so v8 is now treated as consumed diagnostic data and must not support an untouched generalization claim. The workflow has been corrected to use the already-consumed v7 diagnostic corpus during threshold selection.
Alias-aware threshold selection rule¶
After adding trusted EndpointSpec.operation_aliases, operation-fit threshold selection uses only
v5 and the already-consumed v7 diagnostic corpus. For each threshold, compute a balanced operation
score for each corpus as the arithmetic mean of supported-operation accuracy and near-domain
unsupported-operation accuracy. The primary robustness score is the lower of the v5 calibration
balanced score and the v7 diagnostic balanced score.
Freeze the threshold that maximizes this robustness score. Use v5 development balanced accuracy as the first tie-breaker, then prefer the lower threshold as the second tie-breaker to preserve supported-request recall. Neither consumed v8 nor reserved v9 may participate in threshold selection or alias design. After the alias vocabulary and threshold are frozen from v5 plus the already-consumed v7 diagnostic, v9 is evaluated once.
Reserved alias-aware holdout (v9)¶
benchmarks/decision-routing-v9-operation-alias-holdout.json is the replacement untouched
600-case, six-language holdout reserved after discovering the v8 workflow contamination and before
inspecting alias-aware calibration results. It contains 384 supported operations, 192 near-domain
unsupported operations, and 24 ordinary out-of-domain requests. Its normalized queries are
disjoint from v2 through v8.
v9 is manual-only and threshold-gated in GitHub Actions. Do not run it until the alias vocabulary and operation-fit threshold are frozen using v5 and the already-consumed v7 diagnostic corpus.