fix(router): evict stale global pattern_router entries on upsert/delete (#39664)

* fix(router): evict stale global pattern_router entries on upsert/delete

upsert_deployment and delete_deployment cleaned team_pattern_routers but left
the outgoing deployment in the global pattern_router, so wildcard requests kept
round-robining onto the stale entry after a PATCH /model/{id}/update.

Fixes #29064

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(router): dedupe test_get_configured_mode_reads_deployment_model_info name

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(router): restore global pattern_router eviction dropped by previous commit

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
devin-ai-integration[bot] 2026-09-03 17:31:05 -07:00 committed by GitHub
parent b7d1e89667
commit 5dd3fdbc3d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 35 additions and 0 deletions

View file

@ -9546,6 +9546,7 @@ class Router:
public_model_name for _, public_model_name in self.team_model_to_deployment_indices
)
self.pattern_router.remove_deployment(model_id)
for team_id in list(self.team_pattern_routers.keys()):
team_pattern_router = self.team_pattern_routers[team_id]
team_pattern_router.remove_deployment(model_id)

View file

@ -5101,6 +5101,40 @@ def test_team_wildcard_credentials_not_usable_after_delete_deployment():
)
def test_global_wildcard_pattern_router_evicts_stale_entry_on_upsert_and_delete():
"""
Regression for #29064: upsert_deployment removed the old deployment from
model_list but left it in the global pattern_router, so wildcard requests
round-robined between the stale and the corrected deployment.
"""
from litellm.types.router import Deployment, LiteLLM_Params
router = litellm.Router(
model_list=[
{
"model_name": "openai/*",
"litellm_params": {"model": "openai/openai/*", "api_key": "sk-old"},
"model_info": {"id": "global-wildcard"},
}
]
)
router.upsert_deployment(
Deployment(
model_name="openai/*",
litellm_params=LiteLLM_Params(model="openai/*", api_key="sk-new"),
model_info={"id": "global-wildcard"},
)
)
matches = router.pattern_router.route("openai/gpt-5.2")
assert matches is not None
assert [m["litellm_params"]["api_key"] for m in matches] == ["sk-new"]
router.delete_deployment(id="global-wildcard")
assert router.pattern_router.patterns == {}
def test_pattern_match_router_remove_deployment():
"""
remove_deployment must drop only the deployment with the given model id and