mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-07 02:59:05 +00:00
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:
parent
02137b2d12
commit
eb0c966255
2 changed files with 35 additions and 0 deletions
|
|
@ -485,6 +485,7 @@ class ComplexityRouter(CustomLogger):
|
|||
score_threshold=self.config.match_threshold,
|
||||
),
|
||||
auto_sync="local",
|
||||
aggregation="max",
|
||||
)
|
||||
self._semantic_routelayer = routelayer
|
||||
return routelayer
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue