Compare commits

...
Author SHA1 Message Date
8d90e3dff1 fix: reorder HeuristicRouter rules so low-complexity check precedes math check (#671)
* fix: reorder HeuristicRouter rules so low-complexity check precedes math check

Trivial arithmetic like "what is 2+2?" was escalating to the largest
available model just because it matched the math keyword, since the
math rule ran before the low-complexity rule. Math queries above the
low-complexity threshold still escalate correctly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix: make low-complexity math routing effective

---------

Co-authored-by: Ari <ari.silva@paipe.co>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Elliot Slusky <elliot@slusky.com>
2026-08-14 18:18:33 -07:00
3 changed files with 33 additions and 17 deletions
+13 -7
View File
@@ -93,11 +93,16 @@ class HeuristicRouter(RouterPolicy):
Rules (applied in order):
1. Code detected → prefer model with "code"/"coder" in name
2. Math detected → prefer larger model
3. Low complexity (score < 0.20) → prefer smaller/faster model
2. Low complexity (score <= 0.20) → prefer smaller/faster model
3. Math detected → prefer larger model
4. High complexity (score >= 0.55 OR reasoning keywords) → prefer larger model
5. High urgency (>0.8) → override to smaller model
6. Default fallback → default_model → fallback_model → first available
Low complexity is checked before the math check so that simple arithmetic
("calculate 2+2") routes to the smallest model instead of always escalating
on the "math" keyword; math problems above the low-complexity threshold
still escalate to the larger model.
"""
def __init__(
@@ -134,14 +139,15 @@ class HeuristicRouter(RouterPolicy):
# Fall through to larger model for code
return _largest_model(available) or available[0]
# Rule 2: Math detected → prefer larger model
# Rule 2: Low complexity → prefer smaller model (checked before the math
# rule so simple arithmetic doesn't escalate to the largest model)
if context.complexity_score <= 0.20:
return _smallest_model(available) or available[0]
# Rule 3: Math detected → prefer larger model
if context.has_math:
return _largest_model(available) or available[0]
# Rule 3: Low complexity → prefer smaller model
if context.complexity_score < 0.20:
return _smallest_model(available) or available[0]
# Rule 4: High complexity or reasoning → prefer larger model
if context.complexity_score >= 0.55 or context.has_reasoning:
return _largest_model(available) or available[0]
+17 -5
View File
@@ -88,13 +88,25 @@ class TestHeuristicRouter:
router = HeuristicRouter(
available_models=["small", "large", "coder"],
)
ctx = RoutingContext(
query="solve x",
query_length=7,
has_math=True,
)
ctx = build_routing_context("solve the integral of x^2 dx")
assert ctx.has_math is True
assert ctx.complexity_score > 0.20
assert router.select_model(ctx) == "large"
def test_low_complexity_math_prefers_small(self) -> None:
"""Regression test: a trivial math query ("calculate 2+2") must not
escalate to the largest model just because it contains a math
keyword — the low-complexity rule takes priority over the math rule.
"""
_register_models()
router = HeuristicRouter(
available_models=["small", "large", "coder"],
)
ctx = build_routing_context("calculate 2+2")
assert ctx.has_math is True
assert ctx.complexity_score == 0.20
assert router.select_model(ctx) == "small"
def test_high_complexity_prefers_large(self) -> None:
_register_models()
router = HeuristicRouter(
+3 -5
View File
@@ -77,11 +77,9 @@ class TestRouterWithNewModels:
router = HeuristicRouter(
available_models=NEW_LOCAL_MODELS,
)
ctx = RoutingContext(
query="solve the integral of x^2 dx",
query_length=29,
has_math=True,
)
ctx = build_routing_context("solve the integral of x^2 dx")
assert ctx.has_math is True
assert ctx.complexity_score > 0.20
selected = router.select_model(ctx)
assert selected == "gpt-oss:120b"