mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-13 23:11:40 +00:00
fix(router): resolve route candidate ids through the router's own resolver (#40491)
get_candidate_model_ids_for_route (added in #40280 for the encrypted-content affinity check) reconstructed the candidate pool by unioning the model_name and team indexes with pattern_router.route. That diverged from how the router actually resolves a route: it took a union instead of the first matching path, and pattern_router.route only matches the literal name, so a provider-qualified pattern (matched by get_deployments_by_pattern, which retries the {provider}/{model} form) was missed and the default deployment was ignored. For an affinity follow-up on a wildcard or team-public route, that mismatch could strip encrypted reasoning on a same-group cooldown, or return a 503 on a real cross-path switch. Delegate the non-model_name case to _try_early_resolve_deployments_for_model_not_in_names, the same resolver _common_checks_available_deployment uses, so candidate membership follows the router's real precedence. With include_team_models left off it stays read-only and does not raise. Behavior for concrete model groups and routing groups is unchanged. Claude-Session: https://claude.ai/code/session_01KAumQbhzk6jdWWHFLA8Jar Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
b7dad8b44e
commit
2000642592
2 changed files with 25 additions and 17 deletions
|
|
@ -11170,28 +11170,31 @@ class Router:
|
|||
|
||||
def get_candidate_model_ids_for_route(self, model: str, team_id: str | None = None) -> frozenset[str]:
|
||||
"""
|
||||
Deployment ids that could serve ``model`` for ``team_id``, unioned across the paths
|
||||
the router resolves a route through: ``model_group_alias``, a routing group, the
|
||||
``model_name`` and team indexes, and wildcard pattern routes. Read-only and
|
||||
side-effect-free, unlike ``_common_checks_available_deployment`` which also applies
|
||||
fallbacks and can raise. Lets a pre-call check tell a genuine cross-group route from
|
||||
same-group unavailability without re-deriving that precedence at the call site, and
|
||||
without leaking deployment ids into request kwargs bound for the provider.
|
||||
Deployment ids that could serve ``model`` for ``team_id``, following the same
|
||||
precedence ``_common_checks_available_deployment`` uses to build a candidate pool:
|
||||
``model_group_alias``, then a routing group, then the first matching early-resolve
|
||||
path for a name that is not a ``model_name`` (team route, wildcard pattern via
|
||||
``get_deployments_by_pattern``, team pattern router, default deployment), then the
|
||||
``model_name`` and team indexes. Delegating to the router's own resolvers keeps this
|
||||
aligned with how a route actually resolves rather than re-deriving it, and unlike
|
||||
``_common_checks_available_deployment`` it is read-only: it does not apply request
|
||||
fallbacks and (with ``include_team_models`` left off) does not raise. Lets a pre-call
|
||||
check tell a genuine cross-group route from same-group unavailability without leaking
|
||||
deployment ids into request kwargs bound for the provider.
|
||||
"""
|
||||
resolved: Final = self._get_model_from_alias(model=model) or model
|
||||
routing_group_members: Final = self._get_routing_group_deployments(model=resolved, team_id=team_id)
|
||||
if routing_group_members is not None:
|
||||
return self._deployment_ids(routing_group_members)
|
||||
if resolved in self.model_names:
|
||||
return self._deployment_ids(self._get_all_deployments(model_name=resolved, team_id=team_id))
|
||||
team_router: Final = self.team_pattern_routers.get(team_id) if team_id is not None else None
|
||||
return self._deployment_ids(
|
||||
(
|
||||
*self._get_all_deployments(model_name=resolved, team_id=team_id),
|
||||
*(self.pattern_router.route(resolved) or ()),
|
||||
*((team_router.route(resolved) or ()) if team_router is not None else ()),
|
||||
)
|
||||
early: Final = self._try_early_resolve_deployments_for_model_not_in_names(
|
||||
model=resolved, request_team_id=team_id
|
||||
)
|
||||
if early is not None:
|
||||
early_deployments: Final = early[1]
|
||||
return self._deployment_ids(
|
||||
(early_deployments,) if isinstance(early_deployments, Mapping) else early_deployments
|
||||
)
|
||||
return self._deployment_ids(self._get_all_deployments(model_name=resolved, team_id=team_id))
|
||||
|
||||
@staticmethod
|
||||
def _deployment_ids(deployments: Sequence[Mapping[str, object]]) -> frozenset[str]:
|
||||
|
|
|
|||
|
|
@ -15023,7 +15023,9 @@ def test_get_candidate_model_ids_for_route_covers_model_name_and_pattern():
|
|||
pre-call check can tell a genuine cross-group route from same-group unavailability.
|
||||
A concrete model group returns its member ids; a wildcard/pattern deployment is
|
||||
included for a concrete model it matches, which the bare model_name index misses.
|
||||
Regression guard for the LIT-7195 tier-change discriminator's team/pattern gaps.
|
||||
The unprefixed-name case must resolve through get_deployments_by_pattern (which retries
|
||||
the provider-qualified form), not a bare pattern_router.route that only sees the literal
|
||||
name. Regression guard for the LIT-7195 tier-change discriminator's team/pattern gaps.
|
||||
"""
|
||||
router = Router(
|
||||
model_list=[
|
||||
|
|
@ -15047,6 +15049,9 @@ def test_get_candidate_model_ids_for_route_covers_model_name_and_pattern():
|
|||
|
||||
assert router.get_candidate_model_ids_for_route(model="grp") == frozenset({"dep-a", "dep-b"})
|
||||
assert "dep-wild" in router.get_candidate_model_ids_for_route(model="openai/gpt-4o-some-new-model")
|
||||
# unprefixed name whose provider resolves to openai: only get_deployments_by_pattern's
|
||||
# provider-qualified retry matches "openai/*"; a bare route() on the literal name misses it
|
||||
assert "dep-wild" in router.get_candidate_model_ids_for_route(model="gpt-5")
|
||||
|
||||
|
||||
def test_deployment_ids_stringifies_ids_and_skips_entries_without_a_model_info_id():
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue