fix(complexity_router): use max aggregation for semantic keyword route scoring

SemanticRouter defaults to mean aggregation across a route's utterances. Since
each tier's route holds one utterance per configured keyword, a real semantic
match on one keyword was averaged together with the tier's other, unrelated
keywords and dragged below match_threshold — e.g. a MEDIUM tier with keywords
[beep, boop, new york] never fired for a genuine "new york" paraphrase, because
mean(sim_to_beep, sim_to_boop, sim_to_new_york) landed well under the threshold
even though sim_to_new_york alone cleared it. Pass aggregation="max" so a tier
matches if the query is close enough to any one of its keywords, not the
average of all of them.

Verified against live Voyage embeddings: raw cosine similarity for "new york"
vs a paraphrase was 0.54 (above a 0.5 threshold), but the route scored 0.28
under mean aggregation and never matched; max aggregation fixes it.

Adds a regression test with a tier holding one matching and two unrelated
keywords, asserting the tier still fires; fails without aggregation="max".
This commit is contained in:
Abhimanyu Kapur 2026-07-11 11:14:59 -07:00
parent 02137b2d12
commit eb0c966255
2 changed files with 35 additions and 0 deletions

View file

@ -485,6 +485,7 @@ class ComplexityRouter(CustomLogger):
score_threshold=self.config.match_threshold,
),
auto_sync="local",
aggregation="max",
)
self._semantic_routelayer = routelayer
return routelayer

View file

@ -1546,6 +1546,40 @@ class TestSemanticKeywordTierRules:
assert result.model == "o1-preview" # REASONING via semantic match
assert fake_router.async_embedding_calls, "expected an embedding call for the prompt"
@pytest.mark.asyncio
async def test_tier_matches_on_best_utterance_not_diluted_by_others(self, basic_config):
"""A tier with several keywords must match if the query is close to ANY of them,
not the average across all of them. A tier's route holds one utterance per keyword;
mean aggregation (the semantic_router library default) scores the query against the
*average* similarity across every utterance in the route, so a real match on one
keyword gets dragged below threshold by the tier's other, unrelated keywords.
"""
fake_router = FakeEmbeddingRouter()
config = {
**basic_config,
"keyword_tier_rules": [
{"keywords": ["kubernetes deployment", "thanks", "goodbye"], "tier": "REASONING"},
],
"semantic_keyword_matching": True,
"embedding_model": "fake-embed",
"match_threshold": 0.5,
}
router = ComplexityRouter(
model_name="test-router",
litellm_router_instance=fake_router,
complexity_router_config=config,
)
# Only "kubernetes deployment" is close to this query (cos 1.0); "thanks" and
# "goodbye" are orthogonal (cos 0.0). Mean over the three would be ~0.33, below the
# 0.5 threshold; the best (max) utterance alone clears it.
result = await router.async_pre_routing_hook(
model="test-model",
request_kwargs={},
messages=[{"role": "user", "content": "help me roll out my k8s cluster today"}],
)
assert result is not None
assert result.model == "o1-preview" # REASONING via best-utterance semantic match
@pytest.mark.asyncio
async def test_semantic_embedding_call_carries_caller_metadata(self, basic_config):
"""The query embedding call must carry the caller's metadata/litellm_metadata