mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-07 07:58:09 +00:00
1677 lines
70 KiB
Python
1677 lines
70 KiB
Python
"""
|
|
The Router's entry points and the typed views they return.
|
|
|
|
``litellm.Router`` exposes its endpoint methods (``acompletion``, ``aembedding``, ...) plus eight
|
|
entry points, and every other read or write that code outside the router needs goes through one
|
|
of them:
|
|
|
|
- ``registry()`` -> ``RouterRegistry``: the configured deployments, indexed by id, model group,
|
|
alias, wildcard pattern and team, and the plugins they configure.
|
|
- ``settings()`` -> ``RouterSettings``: one frozen snapshot of the configuration a caller may read.
|
|
- ``health()`` -> ``RouterHealth``: cooldowns, blocked deployments, health checks and usage.
|
|
- ``select(model, ...)``: pick a deployment without calling it.
|
|
- ``apply(ChangeSet)`` / ``aapply(ChangeSet)``: runtime changes (deployments, settings, plugins),
|
|
returning an ``ApplyResult`` with the new registry version.
|
|
- ``observe(...)``: bind the router's routing observers to request logging.
|
|
- ``register_plugin(plugin)``: a custom routing strategy.
|
|
- ``retire()``: release the router's global registrations.
|
|
|
|
The views read the router's state when they are called; they hold no copy of it. A Rust core
|
|
implements the same views (see litellm-rust/crates/router-core). Each query names the old Router
|
|
member it replaces in its docstring; ``DeprecatedRouterMembers`` keeps the old names working, typed
|
|
as they were.
|
|
"""
|
|
|
|
# pyright: reportPrivateUsage=false, reportUnknownMemberType=false, reportUnknownVariableType=false
|
|
# pyright: reportMissingTypeArgument=false, reportUnknownParameterType=false
|
|
# The views are part of the router: they read and write the Router's ``_core_*`` members by design
|
|
# (see the router-core boundary in litellm/router.py), the same way Router's own methods do. Each
|
|
# selector query (``deployments(source=...)``, ``names(kind=...)``, ...) is overloaded per selector
|
|
# value and returns exactly the type of the ``_core_*`` member that value reads, which is the type
|
|
# the old public member had, so moving a caller onto the entry point keeps its types. Some of those
|
|
# members are still untyped (bare ``dict`` / ``list``) or mutable (the live objects); the overloads
|
|
# repeat them unchanged, and typing the members themselves is the core's job.
|
|
|
|
import copy
|
|
import hashlib
|
|
import inspect
|
|
import json
|
|
import warnings
|
|
from collections.abc import Iterator, Mapping, Sequence
|
|
from collections.abc import Set as AbstractSet
|
|
from dataclasses import dataclass, field
|
|
from types import MappingProxyType
|
|
from typing import TYPE_CHECKING, Final, Literal, Protocol, TypeAlias, overload
|
|
|
|
from litellm.router_config import detached
|
|
|
|
if TYPE_CHECKING:
|
|
from functools import _lru_cache_wrapper
|
|
from typing import Any # noqa: TID251 # only to spell the cores' untyped (Unknown) containers and one Any type
|
|
|
|
from opentelemetry.trace import Span
|
|
|
|
from litellm.caching.caching import DualCache, RedisCache
|
|
from litellm.integrations.SlackAlerting.slack_alerting import SlackAlerting
|
|
from litellm.litellm_core_utils.pattern_match_deployments import PatternMatchRouter
|
|
from litellm.litellm_core_utils.ptu_shares import PTUTeamCeiling
|
|
from litellm.litellm_core_utils.streaming_handler import CustomStreamWrapper
|
|
from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler
|
|
from litellm.messages.dispatch import MessagesResult
|
|
from litellm.responses.streaming_iterator import BaseResponsesAPIStreamingIterator
|
|
from litellm.router import Router
|
|
from litellm.router_strategy.adaptive_router.adaptive_router import AdaptiveRouter
|
|
from litellm.router_strategy.auto_router.auto_router import AutoRouter
|
|
from litellm.router_strategy.budget_limiter import RouterBudgetLimiting
|
|
from litellm.router_strategy.complexity_router.complexity_router import ComplexityRouter
|
|
from litellm.router_strategy.quality_router.quality_router import QualityRouter
|
|
from litellm.router_utils.cooldown_cache import CooldownCache
|
|
from litellm.router_utils.fallback_event_handlers import AttemptedFallbackTargets as _AttemptedFallbackTargets
|
|
from litellm.router_utils.health_state_cache import DeploymentHealthCache
|
|
from litellm.router_utils.kubernetes_pod_discovery import KubernetesPodDiscovery
|
|
from litellm.types.decisions import DecisionsResponse, OpenAIDecisionResponse
|
|
from litellm.types.llms.openai import ResponsesAPIResponse
|
|
from litellm.types.router import (
|
|
AllowedFailsPolicy,
|
|
CustomRoutingStrategyBase,
|
|
Deployment,
|
|
DeploymentModelListingInfo,
|
|
DeploymentTypedDict,
|
|
FallbackAccessCheck,
|
|
FallbackBudgetCheck,
|
|
GuardrailTypedDict,
|
|
ModelGroupInfo,
|
|
PreRoutingHookResponse,
|
|
RouterGeneralSettings,
|
|
RouterModelGroupAliasItem,
|
|
RoutingGroup,
|
|
RoutingPlugin,
|
|
SearchToolTypedDict,
|
|
TaggedPreRoutingStrategy,
|
|
)
|
|
from litellm.types.utils import BudgetConfig, EmbeddingResponse, ModelResponse
|
|
from litellm.types.utils import ModelInfo as ModelMapInfo
|
|
|
|
# The cores' untyped containers (bare ``list`` / ``dict``, element types Unknown), spelled with Any:
|
|
# an overload set with one partially-unknown overload makes every call through it partially unknown.
|
|
UntypedList: TypeAlias = list[Any] # pyright: ignore[reportExplicitAny] # see above
|
|
UntypedDict: TypeAlias = dict[Any, Any] # pyright: ignore[reportExplicitAny] # see above
|
|
|
|
# Re-exported for litellm.types, which may not import router_utils.
|
|
AttemptedFallbackTargets: TypeAlias = _AttemptedFallbackTargets
|
|
|
|
# What registry().plugins(kind) returns, one member per kind.
|
|
RegistryPlugins: TypeAlias = (
|
|
dict[str, list[TaggedPreRoutingStrategy[AutoRouter]]]
|
|
| dict[str, list[TaggedPreRoutingStrategy[ComplexityRouter]]]
|
|
| dict[str, list[TaggedPreRoutingStrategy[QualityRouter]]]
|
|
| dict[str, list[TaggedPreRoutingStrategy[AdaptiveRouter]]]
|
|
| list[RoutingPlugin]
|
|
| RouterBudgetLimiting
|
|
| DualCache
|
|
| None
|
|
)
|
|
|
|
|
|
DeploymentSource: TypeAlias = Literal[
|
|
"group",
|
|
"all",
|
|
"config",
|
|
"named",
|
|
"alias",
|
|
"routing_groups",
|
|
"routed_group",
|
|
"wildcard",
|
|
"routable",
|
|
"first_in_group",
|
|
"for_request",
|
|
]
|
|
NameKind: TypeAlias = Literal[
|
|
"ids",
|
|
"groups",
|
|
"group_set",
|
|
"deployment_names",
|
|
"team_public",
|
|
"litellm_models",
|
|
"litellm_base_models",
|
|
"wildcard",
|
|
"route_candidates",
|
|
]
|
|
ResolveKind: TypeAlias = Literal[
|
|
"group",
|
|
"alias",
|
|
"served",
|
|
"from_id",
|
|
"team_model",
|
|
"recognized",
|
|
"upstream",
|
|
"group_of_id",
|
|
"wildcard_access_group",
|
|
"team_model_alternatives",
|
|
"routing_group_alternatives",
|
|
"search_tool_support",
|
|
]
|
|
GroupField: TypeAlias = Literal[
|
|
"info",
|
|
"cached_info",
|
|
"display_name",
|
|
"mode",
|
|
"service_tiers",
|
|
"max_input_tokens",
|
|
"token_limits",
|
|
"listing_info",
|
|
]
|
|
CredentialForm: TypeAlias = Literal["params", "with_provider", "deployment"]
|
|
IndexName: TypeAlias = Literal["model_name", "team_model"]
|
|
PluginKind: TypeAlias = Literal[
|
|
"auto",
|
|
"complexity",
|
|
"quality",
|
|
"adaptive",
|
|
"routing",
|
|
"budget_limiter",
|
|
"prompt_caching_pins",
|
|
]
|
|
SelectKind: TypeAlias = Literal["pick", "healthy", "pass_through"]
|
|
UsageKind: TypeAlias = Literal["current", "remaining"]
|
|
|
|
|
|
def deployment_usable_by_team(deployment: "Mapping[str, object] | Deployment", team_id: str | None) -> bool:
|
|
"""
|
|
Whether ``team_id`` may use ``deployment``: a team-scoped deployment (``model_info.team_id``
|
|
set) only by that team, any other deployment by everyone. Needs no router instance.
|
|
"""
|
|
from litellm.router_rust.surface.registry import HostRegistry
|
|
|
|
return HostRegistry._core_deployment_usable_by_team(deployment, team_id)
|
|
|
|
|
|
def deployment_id(model_group: str, litellm_params: Mapping[str, object]) -> str:
|
|
"""
|
|
The stable id of a deployment that has no ``model_info.id``: a sha256 over the model group and
|
|
every litellm_params key and value, in order. Replaces ``Router.generate_model_id``.
|
|
"""
|
|
parts: Final = [model_group]
|
|
for k, v in litellm_params.items():
|
|
for item in (k, v):
|
|
if isinstance(item, str):
|
|
parts.append(item)
|
|
elif isinstance(item, dict):
|
|
parts.append(json.dumps(item, default=_json_default_stable_id))
|
|
else:
|
|
parts.append(str(item))
|
|
return hashlib.sha256("".join(parts).encode()).hexdigest()
|
|
|
|
|
|
# The arguments ``Router.__init__`` accepts, in its signature's order. Every core reads this list
|
|
# (the Python core's ``get_valid_args`` reads its constructor's signature, and the core suite checks
|
|
# that the two agree).
|
|
ROUTER_INIT_ARGS: Final[tuple[str, ...]] = (
|
|
"model_list",
|
|
"assistants_config",
|
|
"search_tools",
|
|
"guardrail_list",
|
|
"redis_url",
|
|
"redis_host",
|
|
"redis_port",
|
|
"redis_password",
|
|
"redis_db",
|
|
"cache_responses",
|
|
"cache_kwargs",
|
|
"caching_groups",
|
|
"client_ttl",
|
|
"polling_interval",
|
|
"default_priority",
|
|
"num_retries",
|
|
"max_fallbacks",
|
|
"timeout",
|
|
"stream_timeout",
|
|
"default_litellm_params",
|
|
"default_max_parallel_requests",
|
|
"set_verbose",
|
|
"debug_level",
|
|
"default_fallbacks",
|
|
"fallbacks",
|
|
"context_window_fallbacks",
|
|
"content_policy_fallbacks",
|
|
"model_group_alias",
|
|
"enable_pre_call_checks",
|
|
"enable_tag_filtering",
|
|
"tag_filtering_match_any",
|
|
"tag_routing_prefix",
|
|
"plugins",
|
|
"retry_after",
|
|
"retry_policy",
|
|
"model_group_retry_policy",
|
|
"allowed_fails",
|
|
"allowed_fails_policy",
|
|
"cooldown_time",
|
|
"disable_cooldowns",
|
|
"fallback_on_unsupported_params",
|
|
"routing_strategy",
|
|
"optional_pre_call_checks",
|
|
"routing_strategy_args",
|
|
"routing_groups",
|
|
"provider_budget_config",
|
|
"alerting_config",
|
|
"router_general_settings",
|
|
"deployment_affinity_ttl_seconds",
|
|
"model_group_affinity_config",
|
|
"ignore_invalid_deployments",
|
|
"enable_health_check_routing",
|
|
"health_check_staleness_threshold",
|
|
"health_check_ignore_transient_errors",
|
|
"background_health_check_model_groups",
|
|
"enable_weighted_failover",
|
|
"enable_mid_stream_fallback_continuation",
|
|
"fallback_access_check",
|
|
"fallback_budget_check",
|
|
"auto_router_capability_limit",
|
|
"session_affinity_repin_on_failover",
|
|
)
|
|
|
|
|
|
def router_init_args() -> tuple[str, ...]:
|
|
"""The arguments ``Router.__init__`` accepts (was ``Router.get_valid_args``). Needs no router instance."""
|
|
return ROUTER_INIT_ARGS
|
|
|
|
|
|
def mark_advisor_orchestration_failure(exception: BaseException) -> None:
|
|
"""Tag an advisor orchestration failure, so the router does not cool down the deployment it picked."""
|
|
from litellm.router_utils.cooldown_handlers import mark_advisor_orchestration_failure as mark
|
|
|
|
mark(exception)
|
|
|
|
|
|
def fallback_chain(
|
|
fallbacks: Sequence[Mapping[str, list[str]]], # mutable-ok: the fallbacks config shape
|
|
model_group: str,
|
|
) -> tuple[list[str] | None, int | None]: # mutable-ok: get_fallback_model_group's type, unchanged
|
|
"""
|
|
The fallbacks configured for a model group, and the index of the generic ``*`` entry (was
|
|
``get_fallback_model_group``). Needs no router instance.
|
|
"""
|
|
from litellm.router_utils.fallback_event_handlers import get_fallback_model_group
|
|
|
|
return get_fallback_model_group(fallbacks=fallbacks, model_group=model_group)
|
|
|
|
|
|
def request_fallback_chain(
|
|
fallbacks: Sequence[Mapping[str, list[str]]], # mutable-ok: the fallbacks config shape
|
|
request_kwargs: Mapping[str, object],
|
|
model_group: str | None,
|
|
) -> tuple[list[str] | None, int | None]: # mutable-ok: get_fallback_model_group's type, unchanged
|
|
"""
|
|
``fallback_chain`` for a request: the group a pre-routing hook picked first, then the routed,
|
|
requested and original groups (was ``get_fallback_model_group_for_lookup_groups``).
|
|
"""
|
|
from litellm.router_utils.fallback_event_handlers import (
|
|
fallback_lookup_groups,
|
|
get_fallback_model_group_for_lookup_groups,
|
|
)
|
|
|
|
return get_fallback_model_group_for_lookup_groups(
|
|
fallbacks=fallbacks, lookup_groups=fallback_lookup_groups(kwargs=request_kwargs, model_group=model_group)
|
|
)
|
|
|
|
|
|
def fallbacks_disabled(request_kwargs: Mapping[str, object]) -> bool:
|
|
"""Whether the request opted out of fallbacks (was ``fallbacks_disabled_for_request``)."""
|
|
from litellm.router_utils.fallback_event_handlers import fallbacks_disabled_for_request
|
|
|
|
return fallbacks_disabled_for_request(kwargs=request_kwargs)
|
|
|
|
|
|
def references_provider_scoped_resource(request_kwargs: Mapping[str, object]) -> bool:
|
|
"""Whether the request names a file, batch or fine-tuning job that only its own provider knows."""
|
|
from litellm.router_utils.fallback_event_handlers import PROVIDER_SCOPED_RESOURCE_KEYS
|
|
|
|
return any(request_kwargs.get(key) for key in PROVIDER_SCOPED_RESOURCE_KEYS)
|
|
|
|
|
|
def deployment_metadata(request_kwargs: Mapping[str, object]) -> Mapping[str, object] | None:
|
|
"""The metadata bucket the router stamped the picked deployment into (was ``find_deployment_metadata``)."""
|
|
from litellm.router_utils.router_callbacks.track_deployment_metrics import find_deployment_metadata
|
|
|
|
return find_deployment_metadata(request_kwargs)
|
|
|
|
|
|
def _json_default_stable_id(obj: object) -> str:
|
|
from litellm.router_rust.steps.prerouting import _json_default_stable_id as stable_id
|
|
|
|
return stable_id(obj)
|
|
|
|
|
|
class RouterRegistry:
|
|
"""
|
|
Read-only queries over the configured deployments (``registry()``).
|
|
|
|
``version`` changes whenever the deployments change, so a caller can tell whether
|
|
what it read earlier is still current.
|
|
"""
|
|
|
|
__slots__ = ("_router",)
|
|
|
|
def __init__(self, router: "Router") -> None:
|
|
self._router = router
|
|
|
|
@property
|
|
def version(self) -> int:
|
|
return self._router._registry_version
|
|
|
|
# -- one deployment -------------------------------------------------------------------------
|
|
|
|
def deployment(self, id: str) -> "Deployment | None":
|
|
"""The deployment with this ``model_info.id`` (was ``get_deployment``)."""
|
|
return self._router._core_get_deployment(model_id=id)
|
|
|
|
def has_deployment(self, id: str) -> bool:
|
|
"""Whether a deployment has this id (was ``has_model_id``)."""
|
|
return self._router._core_has_model_id(id)
|
|
|
|
def deployment_dict(
|
|
self, id: str
|
|
) -> "UntypedDict | None": # a mutable list/dict, as before: Router._core_get_model_info's type, the live dict
|
|
"""The deployment as its model_list dict (was ``get_model_info``)."""
|
|
return self._router._core_get_model_info(id=id)
|
|
|
|
def deployment_model_info(self, id: str, model_name: str, *, cached: bool = False) -> "ModelMapInfo | None":
|
|
"""
|
|
Model info for a deployment, merged with the pricing map (was
|
|
``get_deployment_model_info``; ``cached=True`` was ``cached_deployment_model_info``).
|
|
"""
|
|
if cached:
|
|
return self._router._core_cached_deployment_model_info(id, model_name)
|
|
return self._router._core_get_deployment_model_info(model_id=id, model_name=model_name)
|
|
|
|
def deployment_model_info_cache(self) -> "_lru_cache_wrapper[ModelMapInfo | None]":
|
|
"""
|
|
The lru cache ``deployment_model_info(..., cached=True)`` reads, called as ``(id, model_name)``
|
|
and cleared with ``cache_clear()`` (was the ``cached_deployment_model_info`` attribute).
|
|
"""
|
|
return self._router._core_cached_deployment_model_info
|
|
|
|
def router_model_info(
|
|
self,
|
|
deployment: "dict[str, object] | Deployment | None", # mutable-ok: the core takes a dict
|
|
received_model_name: str,
|
|
id: str | None = None,
|
|
) -> "ModelMapInfo":
|
|
"""Model info used for routing decisions (was ``get_router_model_info``)."""
|
|
r: Final = self._router
|
|
# The core's overloads split (deployment, id) by which one is None; this forwards either form unchanged.
|
|
return r._core_get_router_model_info( # pyright: ignore[reportCallIssue] # see above
|
|
deployment=deployment, # pyright: ignore[reportArgumentType] # see above
|
|
received_model_name=received_model_name,
|
|
id=id, # pyright: ignore[reportArgumentType] # see above
|
|
)
|
|
|
|
def discovered_model_info(self, id: str | None) -> Mapping[str, int]:
|
|
"""Limits discovered from the provider for a deployment (was ``get_discovered_model_info``)."""
|
|
return self._router._core_get_discovered_model_info(id)
|
|
|
|
def zero_cost(self) -> Mapping[str, bool] | None:
|
|
"""The per-deployment zero-cost lookup the auth checks read (was ``_zero_cost_cache``)."""
|
|
return getattr(self._router, "_zero_cost_cache", None)
|
|
|
|
# -- many deployments -----------------------------------------------------------------------
|
|
|
|
@overload
|
|
def deployments(
|
|
self,
|
|
model_name: str | None = None,
|
|
*,
|
|
source: Literal["group"] = "group",
|
|
team_id: str | None = None,
|
|
model_alias: str | None = None,
|
|
request: Mapping[str, object] | None = None,
|
|
call_type: str | None = None,
|
|
) -> "list[DeploymentTypedDict] | None": ... # mutable-ok: Router._core_get_model_list's type, the live lists
|
|
|
|
@overload
|
|
def deployments(
|
|
self,
|
|
model_name: str | None = None,
|
|
*,
|
|
source: Literal["alias"],
|
|
team_id: str | None = None,
|
|
model_alias: str | None = None,
|
|
request: Mapping[str, object] | None = None,
|
|
call_type: str | None = None,
|
|
) -> "list[DeploymentTypedDict]": ... # mutable-ok: Router._core_get_model_list_from_model_alias's type
|
|
|
|
@overload
|
|
def deployments(
|
|
self,
|
|
model_name: str,
|
|
*,
|
|
source: Literal["all", "routed_group"],
|
|
team_id: str | None = None,
|
|
model_alias: str | None = None,
|
|
request: Mapping[str, object] | None = None,
|
|
call_type: str | None = None,
|
|
) -> "list[DeploymentTypedDict]": ... # mutable-ok: the cores' type (get_all_deployments, ...)
|
|
|
|
@overload
|
|
def deployments(
|
|
self,
|
|
model_name: str | None = None,
|
|
*,
|
|
source: Literal["config"],
|
|
team_id: str | None = None,
|
|
model_alias: str | None = None,
|
|
request: Mapping[str, object] | None = None,
|
|
call_type: str | None = None,
|
|
) -> Iterator[Mapping[str, object]]: ...
|
|
|
|
@overload
|
|
def deployments(
|
|
self,
|
|
model_name: str | None = None,
|
|
*,
|
|
source: Literal["routing_groups"],
|
|
team_id: str | None = None,
|
|
model_alias: str | None = None,
|
|
request: Mapping[str, object] | None = None,
|
|
call_type: str | None = None,
|
|
) -> "Sequence[DeploymentTypedDict]": ...
|
|
|
|
@overload
|
|
def deployments(
|
|
self,
|
|
model_name: str,
|
|
*,
|
|
source: Literal["for_request"],
|
|
team_id: str | None = None,
|
|
model_alias: str | None = None,
|
|
request: Mapping[str, object] | None = None,
|
|
call_type: str | None = None,
|
|
) -> "Sequence[DeploymentTypedDict]": ...
|
|
|
|
@overload
|
|
def deployments(
|
|
self,
|
|
model_name: str,
|
|
*,
|
|
source: Literal["named"],
|
|
team_id: str | None = None,
|
|
model_alias: str | None = None,
|
|
request: Mapping[str, object] | None = None,
|
|
call_type: str | None = None,
|
|
) -> "tuple[DeploymentTypedDict, ...] | None": ...
|
|
|
|
@overload
|
|
def deployments(
|
|
self,
|
|
model_name: str,
|
|
*,
|
|
source: Literal["routable"],
|
|
team_id: str | None = None,
|
|
model_alias: str | None = None,
|
|
request: Mapping[str, object] | None = None,
|
|
call_type: str | None = None,
|
|
) -> "tuple[DeploymentTypedDict, ...]": ...
|
|
|
|
@overload
|
|
def deployments(
|
|
self,
|
|
model_name: str,
|
|
*,
|
|
source: Literal["wildcard"],
|
|
team_id: str | None = None,
|
|
model_alias: str | None = None,
|
|
request: Mapping[str, object] | None = None,
|
|
call_type: str | None = None,
|
|
) -> Sequence[Mapping[str, object]]: ...
|
|
|
|
@overload
|
|
def deployments(
|
|
self,
|
|
model_name: str,
|
|
*,
|
|
source: Literal["first_in_group"],
|
|
team_id: str | None = None,
|
|
model_alias: str | None = None,
|
|
request: Mapping[str, object] | None = None,
|
|
call_type: str | None = None,
|
|
) -> "Deployment | None": ...
|
|
|
|
def deployments(
|
|
self,
|
|
model_name: str | None = None,
|
|
*,
|
|
source: DeploymentSource = "group",
|
|
team_id: str | None = None,
|
|
model_alias: str | None = None,
|
|
request: Mapping[str, object] | None = None,
|
|
call_type: str | None = None,
|
|
) -> "Sequence[DeploymentTypedDict] | Sequence[Mapping[str, object]] | Iterator[Mapping[str, object]] | Deployment | None":
|
|
"""
|
|
Deployments by name. ``source`` picks which index answers:
|
|
|
|
- ``group``: the group, its aliases, routing groups, then wildcards; every deployment when
|
|
``model_name`` is None (was ``get_model_list``)
|
|
- ``all``: every deployment of the group, with ``model_alias`` (was ``get_all_deployments``)
|
|
- ``config``: deployments that came from config rather than the DB (was ``config_deployments``)
|
|
- ``named``: the group index entry, or None (was ``deployments_named``)
|
|
- ``alias`` / ``routing_groups`` / ``routed_group``: (was ``get_model_list_from_model_alias``,
|
|
``get_model_list_from_routing_groups``, ``get_model_list_of_routed_group``)
|
|
- ``wildcard``: deployments a wildcard route serves (was ``wildcard_route_deployments``)
|
|
- ``routable``: deployments that pass the routing checks for ``request`` and, when given,
|
|
the endpoint ``call_type`` (was ``get_routable_deployments``)
|
|
- ``first_in_group``: the first deployment of the group (was ``get_deployment_by_model_group_name``)
|
|
- ``for_request``: the deployments a request may use (was ``deployments_for_request``)
|
|
"""
|
|
r: Final = self._router
|
|
if source == "group":
|
|
return r._core_get_model_list(model_name=model_name, team_id=team_id)
|
|
if source == "config":
|
|
return r._core_config_deployments()
|
|
if source == "alias":
|
|
return r._core_get_model_list_from_model_alias(model_name=model_name)
|
|
if source == "routing_groups":
|
|
return r._core_get_model_list_from_routing_groups(model_name=model_name)
|
|
if model_name is None:
|
|
raise TypeError(f"registry().deployments(source={source!r}) needs a model_name")
|
|
if source == "all":
|
|
return r._core_get_all_deployments(model_name=model_name, model_alias=model_alias, team_id=team_id)
|
|
if source == "named":
|
|
return r._core_deployments_named(model_name)
|
|
if source == "routed_group":
|
|
return r._core_get_model_list_of_routed_group(model_name, team_id)
|
|
if source == "wildcard":
|
|
return r._core_wildcard_route_deployments(model_name, team_id)
|
|
if source == "routable":
|
|
return r._core_get_routable_deployments(
|
|
model_name, MappingProxyType({}) if request is None else request, call_type
|
|
)
|
|
if source == "first_in_group":
|
|
return r._core_get_deployment_by_model_group_name(model_group_name=model_name)
|
|
if source == "for_request":
|
|
return r._core_deployments_for_request(model_name, request or {})
|
|
raise ValueError(f"unknown deployment source {source!r}")
|
|
|
|
def all(self) -> "UntypedList": # a mutable list/dict, as before: the model_list attribute's type, the live list
|
|
"""The live model list, in configured order (was the ``model_list`` attribute)."""
|
|
return self._router._core_model_list
|
|
|
|
def includes(
|
|
self,
|
|
deployment: "DeploymentTypedDict | dict[str, object]", # mutable-ok: the core takes a dict
|
|
*,
|
|
model_name: str | None = None,
|
|
team_id: str | None = None,
|
|
) -> bool:
|
|
"""
|
|
Whether a deployment serves ``model_name`` for ``team_id`` (was
|
|
``should_include_deployment``), or, without ``model_name``, whether the team may use it at
|
|
all (was ``deployment_usable_by_team``).
|
|
"""
|
|
if model_name is None:
|
|
return deployment_usable_by_team(deployment, team_id)
|
|
return self._router._core_should_include_deployment(model_name=model_name, model=deployment, team_id=team_id)
|
|
|
|
@overload
|
|
def index(self, name: Literal["model_name"]) -> dict[str, list[int]]: ... # mutable-ok: the live index
|
|
|
|
@overload
|
|
def index(self, name: Literal["team_model"]) -> dict[tuple[str, str], list[int]]: ... # mutable-ok: the live index
|
|
|
|
def index(self, name: IndexName) -> Mapping[str, Sequence[int]] | Mapping[tuple[str, str], Sequence[int]]:
|
|
"""
|
|
The raw name indexes: ``model_name`` maps a group name to model_list positions (was
|
|
``model_name_to_deployment_indices``), ``team_model`` maps (team, public name) keys (was
|
|
``team_model_to_deployment_indices``).
|
|
"""
|
|
if name == "model_name":
|
|
return self._router._core_model_name_to_deployment_indices
|
|
if name == "team_model":
|
|
return self._router._core_team_model_to_deployment_indices
|
|
raise ValueError(f"unknown index {name!r}")
|
|
|
|
# -- names ----------------------------------------------------------------------------------
|
|
|
|
@overload
|
|
def names(
|
|
self,
|
|
model_name: str | None = None,
|
|
*,
|
|
kind: Literal["groups", "ids"] = "groups",
|
|
team_id: str | None = None,
|
|
exclude_team_models: bool = False,
|
|
) -> list[str]: ... # mutable-ok: the cores' type (get_model_names, get_model_ids), a fresh list
|
|
|
|
@overload
|
|
def names(
|
|
self,
|
|
model_name: str | None = None,
|
|
*,
|
|
kind: Literal["group_set"],
|
|
team_id: str | None = None,
|
|
exclude_team_models: bool = False,
|
|
) -> set[str]: ... # mutable-ok: the model_names attribute's type, the live set
|
|
|
|
@overload
|
|
def names(
|
|
self,
|
|
model_name: str | None = None,
|
|
*,
|
|
kind: Literal["deployment_names"],
|
|
team_id: str | None = None,
|
|
exclude_team_models: bool = False,
|
|
) -> "UntypedList": ... # a mutable list/dict, as before: the deployment_names attribute's type, the live list
|
|
|
|
@overload
|
|
def names(
|
|
self,
|
|
model_name: str | None = None,
|
|
*,
|
|
kind: Literal["team_public"],
|
|
team_id: str | None = None,
|
|
exclude_team_models: bool = False,
|
|
) -> frozenset[str]: ...
|
|
|
|
@overload
|
|
def names(
|
|
self,
|
|
model_name: str,
|
|
*,
|
|
kind: Literal["wildcard", "route_candidates"],
|
|
team_id: str | None = None,
|
|
exclude_team_models: bool = False,
|
|
) -> frozenset[str]: ...
|
|
|
|
@overload
|
|
def names(
|
|
self,
|
|
model_name: str,
|
|
*,
|
|
kind: Literal["litellm_models", "litellm_base_models"],
|
|
team_id: str | None = None,
|
|
exclude_team_models: bool = False,
|
|
) -> tuple[str, ...]: ...
|
|
|
|
def names(
|
|
self,
|
|
model_name: str | None = None,
|
|
*,
|
|
kind: NameKind = "groups",
|
|
team_id: str | None = None,
|
|
exclude_team_models: bool = False,
|
|
) -> Sequence[str] | AbstractSet[str]:
|
|
"""
|
|
Names known to the router. ``kind``:
|
|
|
|
- ``groups``: every routable model name, with team public names for ``team_id``
|
|
(was ``get_model_names``)
|
|
- ``group_set``: the set of configured group names (was the ``model_names`` attribute)
|
|
- ``ids``: deployment ids, of one group or all (was ``get_model_ids``)
|
|
- ``deployment_names``: (was the ``deployment_names`` attribute)
|
|
- ``team_public``: team public model names (was ``team_public_model_names``), or only the
|
|
ones ``team_id`` serves its own deployments under (was ``team_public_model_names_of``)
|
|
- ``litellm_models``: upstream model names a group resolves to (was ``resolved_litellm_models``)
|
|
- ``litellm_base_models``: the same, with each deployment's ``base_model`` where it sets one
|
|
(was ``resolved_litellm_models(..., use_base_model=True)``)
|
|
- ``wildcard``: names a wildcard route serves (was ``wildcard_routed_models``)
|
|
- ``route_candidates``: deployment ids a request for the name may reach
|
|
(was ``get_candidate_model_ids_for_route``)
|
|
"""
|
|
r: Final = self._router
|
|
if kind == "groups":
|
|
return r._core_get_model_names(team_id=team_id)
|
|
if kind == "group_set":
|
|
return r._core_model_names
|
|
if kind == "ids":
|
|
return r._core_get_model_ids(model_name=model_name, exclude_team_models=exclude_team_models)
|
|
if kind == "deployment_names":
|
|
return r._core_deployment_names
|
|
if kind == "team_public":
|
|
return r._core_team_public_model_names if team_id is None else r.team_public_model_names_of(team_id)
|
|
if model_name is None:
|
|
raise TypeError(f"registry().names(kind={kind!r}) needs a model_name")
|
|
if kind == "litellm_models":
|
|
return r._core_resolved_litellm_models(model_name, team_id=team_id)
|
|
if kind == "litellm_base_models":
|
|
return r._core_resolved_litellm_models(model_name, team_id=team_id, use_base_model=True)
|
|
if kind == "wildcard":
|
|
return r._core_wildcard_routed_models(model_name, team_id)
|
|
if kind == "route_candidates":
|
|
return r._core_get_candidate_model_ids_for_route(model_name, team_id=team_id)
|
|
raise ValueError(f"unknown name kind {kind!r}")
|
|
|
|
# -- resolving a name -----------------------------------------------------------------------
|
|
|
|
@overload
|
|
def resolve(
|
|
self,
|
|
name: str,
|
|
*,
|
|
kind: Literal["group", "served"] = "group",
|
|
team_id: str | None = None,
|
|
custom_llm_provider: str | None = None,
|
|
served_model: str | None = None,
|
|
) -> str: ...
|
|
|
|
@overload
|
|
def resolve(
|
|
self,
|
|
name: str,
|
|
*,
|
|
kind: Literal["alias", "upstream"],
|
|
team_id: str | None = None,
|
|
custom_llm_provider: str | None = None,
|
|
served_model: str | None = None,
|
|
) -> str | None: ...
|
|
|
|
@overload
|
|
def resolve(
|
|
self,
|
|
name: str,
|
|
*,
|
|
kind: Literal["search_tool_support"],
|
|
team_id: str | None = None,
|
|
custom_llm_provider: str | None = None,
|
|
served_model: str | None = None,
|
|
) -> tuple[bool | None, ...]: ...
|
|
|
|
@overload
|
|
def resolve(
|
|
self,
|
|
name: str | None,
|
|
*,
|
|
kind: Literal["from_id", "team_model"],
|
|
team_id: str | None = None,
|
|
custom_llm_provider: str | None = None,
|
|
served_model: str | None = None,
|
|
) -> str | None: ...
|
|
|
|
@overload
|
|
def resolve(
|
|
self,
|
|
name: str,
|
|
*,
|
|
kind: Literal["recognized", "wildcard_access_group", "team_model_alternatives"],
|
|
team_id: str | None = None,
|
|
custom_llm_provider: str | None = None,
|
|
served_model: str | None = None,
|
|
) -> bool: ...
|
|
|
|
@overload
|
|
def resolve(
|
|
self,
|
|
name: str | None,
|
|
*,
|
|
kind: Literal["routing_group_alternatives"],
|
|
team_id: str | None = None,
|
|
custom_llm_provider: str | None = None,
|
|
served_model: str | None = None,
|
|
) -> bool: ...
|
|
|
|
@overload
|
|
def resolve(
|
|
self,
|
|
name: str,
|
|
*,
|
|
kind: Literal["group_of_id"],
|
|
team_id: str | None = None,
|
|
custom_llm_provider: str | None = None,
|
|
served_model: str | None = None,
|
|
) -> "UntypedList | None": ... # a mutable list/dict, as before: Router._core_get_model_group's type, unchanged
|
|
|
|
def resolve(
|
|
self,
|
|
name: str | None,
|
|
*,
|
|
kind: ResolveKind = "group",
|
|
team_id: str | None = None,
|
|
custom_llm_provider: str | None = None,
|
|
served_model: str | None = None,
|
|
) -> str | bool | Sequence[object] | None:
|
|
"""
|
|
Resolve a requested name. ``kind``:
|
|
|
|
- ``group``: the model group a name routes to (was ``routable_model_group``)
|
|
- ``alias``: the target of a model_group_alias, or None (was ``get_model_from_alias``)
|
|
- ``served``: the name to report for ``served_model`` (was ``resolve_served_model_name``)
|
|
- ``from_id``: the model name of a deployment id (was ``resolve_model_name_from_model_id``)
|
|
- ``team_model``: a team's public name to its group (was ``map_team_model``; ``team_id`` required)
|
|
- ``recognized``: whether the router knows the name (was ``is_recognized_model``)
|
|
- ``upstream``: the upstream model a name routes to (was ``get_routable_upstream_model``)
|
|
- ``group_of_id``: the group of a deployment id (was ``get_model_group``)
|
|
- ``wildcard_access_group``: whether an access group grants a wildcard route
|
|
(was ``is_model_access_group_for_wildcard_route``)
|
|
- ``team_model_alternatives``: whether a team deployment has siblings to fall back to
|
|
(was ``team_model_has_alternatives``)
|
|
- ``routing_group_alternatives``: (was ``routing_group_has_alternatives``)
|
|
- ``search_tool_support``: per routable deployment, whether it answers Codex's client
|
|
``tool_search`` (was ``get_search_tool_support``)
|
|
"""
|
|
r: Final = self._router
|
|
if kind == "from_id":
|
|
return r._core_resolve_model_name_from_model_id(name, custom_llm_provider=custom_llm_provider)
|
|
if kind == "team_model":
|
|
if team_id is None:
|
|
raise TypeError("registry().resolve(kind='team_model') needs a team_id")
|
|
return r._core_map_team_model(name, team_id, custom_llm_provider=custom_llm_provider)
|
|
if kind == "routing_group_alternatives":
|
|
return r._core_routing_group_has_alternatives(name)
|
|
if name is None:
|
|
raise TypeError(f"registry().resolve(kind={kind!r}) needs a name")
|
|
if kind == "group":
|
|
return r._core_routable_model_group(name)
|
|
if kind == "alias":
|
|
return r._core_get_model_from_alias(model=name)
|
|
if kind == "served":
|
|
# Without a served_model the name is reported as requested, as the core does for a non-match.
|
|
return name if served_model is None else r._core_resolve_served_model_name(name, served_model)
|
|
if kind == "recognized":
|
|
return r._core_is_recognized_model(name)
|
|
if kind == "upstream":
|
|
return r._core_get_routable_upstream_model(name, team_id=team_id)
|
|
if kind == "search_tool_support":
|
|
return r._core_get_search_tool_support(name, team_id=team_id)
|
|
if kind == "group_of_id":
|
|
return r._core_get_model_group(id=name)
|
|
if kind == "wildcard_access_group":
|
|
return r._core_is_model_access_group_for_wildcard_route(model_access_group=name)
|
|
if kind == "team_model_alternatives":
|
|
return r._core_team_model_has_alternatives(name)
|
|
raise ValueError(f"unknown resolve kind {kind!r}")
|
|
|
|
def aliases(
|
|
self,
|
|
) -> "dict[str, str | RouterModelGroupAliasItem]": # mutable-ok: the model_group_alias attribute, the live dict
|
|
"""model_group_alias as configured (was the ``model_group_alias`` attribute)."""
|
|
return self._router._core_model_group_alias
|
|
|
|
@overload
|
|
def patterns(self, *, team: Literal[False] = False) -> "PatternMatchRouter": ...
|
|
|
|
@overload
|
|
def patterns(self, *, team: Literal[True]) -> "dict[str, PatternMatchRouter]": ... # mutable-ok: the live dict
|
|
|
|
@overload
|
|
def patterns(self, *, team: bool) -> "PatternMatchRouter | dict[str, PatternMatchRouter]": ... # mutable-ok: same
|
|
|
|
def patterns(self, *, team: bool = False) -> "PatternMatchRouter | Mapping[str, PatternMatchRouter]":
|
|
"""The wildcard pattern router, or the per-team ones (was ``pattern_router`` / ``team_pattern_routers``)."""
|
|
return self._router._core_team_pattern_routers if team else self._router._core_pattern_router
|
|
|
|
# -- groups ---------------------------------------------------------------------------------
|
|
|
|
@overload
|
|
def group(
|
|
self,
|
|
name: str,
|
|
*,
|
|
field: Literal["info", "cached_info"] = "info",
|
|
include_hidden: bool = False,
|
|
team_id: str | None = None,
|
|
) -> "ModelGroupInfo | None": ...
|
|
|
|
@overload
|
|
def group(
|
|
self,
|
|
name: str,
|
|
*,
|
|
field: Literal["display_name", "mode"],
|
|
include_hidden: bool = False,
|
|
team_id: str | None = None,
|
|
) -> str | None: ...
|
|
|
|
@overload
|
|
def group(
|
|
self,
|
|
name: str,
|
|
*,
|
|
field: Literal["service_tiers"],
|
|
include_hidden: bool = False,
|
|
team_id: str | None = None,
|
|
) -> tuple[object, ...]: ...
|
|
|
|
@overload
|
|
def group(
|
|
self,
|
|
name: str,
|
|
*,
|
|
field: Literal["max_input_tokens"],
|
|
include_hidden: bool = False,
|
|
team_id: str | None = None,
|
|
) -> tuple[int | None, ...]: ...
|
|
|
|
@overload
|
|
def group(
|
|
self,
|
|
name: str,
|
|
*,
|
|
field: Literal["token_limits"],
|
|
include_hidden: bool = False,
|
|
team_id: str | None = None,
|
|
) -> tuple[int | None, int | None]: ...
|
|
|
|
@overload
|
|
def group(
|
|
self,
|
|
name: str,
|
|
*,
|
|
field: Literal["listing_info"],
|
|
include_hidden: bool = False,
|
|
team_id: str | None = None,
|
|
) -> "DeploymentModelListingInfo | None": ...
|
|
|
|
def group(
|
|
self,
|
|
name: str,
|
|
*,
|
|
field: GroupField = "info",
|
|
include_hidden: bool = False,
|
|
team_id: str | None = None,
|
|
) -> "ModelGroupInfo | DeploymentModelListingInfo | tuple[int | None, ...] | tuple[object, ...] | str | None":
|
|
"""
|
|
What the router knows about a model group. ``field``:
|
|
|
|
- ``info``: the group's merged model info (was ``get_model_group_info``)
|
|
- ``cached_info``: (was ``cached_model_group_info``)
|
|
- ``display_name`` / ``mode`` / ``token_limits`` / ``listing_info``: configured values
|
|
(were ``get_configured_display_name``, ``get_configured_mode``,
|
|
``get_configured_token_limits``, ``get_model_listing_info``)
|
|
- ``service_tiers``: (was ``get_configured_service_tiers``)
|
|
- ``max_input_tokens``: each routable deployment's own ``model_info.max_input_tokens``
|
|
"""
|
|
r: Final = self._router
|
|
if field == "info":
|
|
return r._core_get_model_group_info(name, include_hidden=include_hidden, team_id=team_id)
|
|
if field == "cached_info":
|
|
return r._core_cached_model_group_info(name)
|
|
if field == "display_name":
|
|
return r._core_get_configured_display_name(name)
|
|
if field == "mode":
|
|
return r._core_get_configured_mode(name)
|
|
if field == "service_tiers":
|
|
return r._core_get_configured_service_tiers(name, team_id=team_id)
|
|
if field == "max_input_tokens":
|
|
return r._core_get_configured_max_input_tokens(name, team_id=team_id)
|
|
if field == "token_limits":
|
|
return r._core_get_configured_token_limits(name)
|
|
if field == "listing_info":
|
|
return r._core_get_model_listing_info(name)
|
|
raise ValueError(f"unknown group field {field!r}")
|
|
|
|
def routing_groups(self) -> "dict[str, RoutingGroup]": # mutable-ok: the routing_groups attribute, the live dict
|
|
"""Routing groups by name (was the ``routing_groups`` attribute)."""
|
|
return self._router._core_routing_groups
|
|
|
|
def routing_group(self, name: str) -> "RoutingGroup | None":
|
|
"""The routing group callable as ``name``, or None (was ``get_routing_group``)."""
|
|
return self._router.get_routing_group(name)
|
|
|
|
def client(self, deployment: Mapping[str, object], *, client_type: str | None = None) -> object:
|
|
"""The SDK client the router cached for a deployment, or None (was ``_get_client``)."""
|
|
return self._router._get_client(deployment=deployment, kwargs={}, client_type=client_type)
|
|
|
|
def access_groups(
|
|
self,
|
|
model_name: str | None = None,
|
|
model_access_group: str | None = None,
|
|
team_id: str | None = None,
|
|
) -> dict[str, list[str]]: # mutable-ok: Router._core_get_model_access_groups's type, a fresh dict
|
|
"""Access group name to model names (was ``get_model_access_groups``)."""
|
|
return self._router._core_get_model_access_groups(
|
|
model_name=model_name, model_access_group=model_access_group, team_id=team_id
|
|
)
|
|
|
|
@overload
|
|
def credentials(
|
|
self, id: str, *, form: Literal["params"] = "params", team_id: str | None = None
|
|
) -> (
|
|
"UntypedDict | None"
|
|
): ... # a mutable list/dict, as before: Router._core_get_deployment_credentials's type, unchanged
|
|
|
|
@overload
|
|
def credentials(
|
|
self, id: str, *, form: Literal["with_provider"], team_id: str | None = None
|
|
) -> "dict[str, Any] | None": ... # pyright: ignore[reportExplicitAny] # the core's type, unchanged # mutable-ok: same
|
|
|
|
@overload
|
|
def credentials(
|
|
self, id: str, *, form: Literal["deployment"], team_id: str | None = None
|
|
) -> "Deployment | None": ...
|
|
|
|
def credentials(
|
|
self, id: str, *, form: CredentialForm = "params", team_id: str | None = None
|
|
) -> "Mapping[str, object] | Deployment | None":
|
|
"""
|
|
A deployment's credentials. ``form``: ``params`` (was ``get_deployment_credentials``),
|
|
``with_provider`` (was ``get_deployment_credentials_with_provider``), ``deployment``, the
|
|
deployment that owns the credential (was ``get_credential_deployment``).
|
|
"""
|
|
r: Final = self._router
|
|
if form == "params":
|
|
return r._core_get_deployment_credentials(model_id=id)
|
|
if form == "with_provider":
|
|
return r._core_get_deployment_credentials_with_provider(model_id=id, team_id=team_id)
|
|
if form == "deployment":
|
|
return r._core_get_credential_deployment(model_id=id, team_id=team_id)
|
|
raise ValueError(f"unknown credential form {form!r}")
|
|
|
|
@overload
|
|
def plugins(
|
|
self, kind: Literal["auto"]
|
|
) -> "dict[str, list[TaggedPreRoutingStrategy[AutoRouter]]]": ... # mutable-ok: the live dict
|
|
|
|
@overload
|
|
def plugins(
|
|
self, kind: Literal["complexity"]
|
|
) -> "dict[str, list[TaggedPreRoutingStrategy[ComplexityRouter]]]": ... # mutable-ok: the live dict
|
|
|
|
@overload
|
|
def plugins(
|
|
self, kind: Literal["quality"]
|
|
) -> "dict[str, list[TaggedPreRoutingStrategy[QualityRouter]]]": ... # mutable-ok: the live dict
|
|
|
|
@overload
|
|
def plugins(
|
|
self, kind: Literal["adaptive"]
|
|
) -> "dict[str, list[TaggedPreRoutingStrategy[AdaptiveRouter]]]": ... # mutable-ok: the live dict
|
|
|
|
@overload
|
|
def plugins(self, kind: Literal["routing"]) -> "list[RoutingPlugin]": ... # mutable-ok: the live list
|
|
|
|
@overload
|
|
def plugins(self, kind: Literal["budget_limiter"]) -> "RouterBudgetLimiting | None": ...
|
|
|
|
@overload
|
|
def plugins(self, kind: Literal["prompt_caching_pins"]) -> "DualCache": ...
|
|
|
|
def plugins(self, kind: PluginKind) -> "RegistryPlugins":
|
|
"""
|
|
Plugins configured by the deployments: ``auto``, ``complexity``, ``quality`` and ``adaptive``
|
|
sub-routers by deployment name, ``routing`` plugins, the deployment ``budget_limiter`` and
|
|
the ``prompt_caching_pins`` store (were ``auto_routers``, ``complexity_routers``,
|
|
``quality_routers``, ``adaptive_routers``, ``routing_plugins``,
|
|
``get_router_deployment_budget_limiter`` and ``prompt_caching_pin_cache``).
|
|
"""
|
|
r: Final = self._router
|
|
if kind == "auto":
|
|
return r._core_auto_routers
|
|
if kind == "complexity":
|
|
return r._core_complexity_routers
|
|
if kind == "quality":
|
|
return r._core_quality_routers
|
|
if kind == "adaptive":
|
|
return r._core_adaptive_routers
|
|
if kind == "routing":
|
|
return r._core_routing_plugins
|
|
if kind == "budget_limiter":
|
|
return r._core_get_router_deployment_budget_limiter()
|
|
if kind == "prompt_caching_pins":
|
|
return r._core_prompt_caching_pin_cache
|
|
raise ValueError(f"unknown plugin kind {kind!r}")
|
|
|
|
|
|
# Settings a caller may read. Assigning one from outside the router goes through apply(ChangeSet(assign=...)).
|
|
SETTING_FIELDS: Final[tuple[str, ...]] = (
|
|
"routing_strategy",
|
|
"fallbacks",
|
|
"context_window_fallbacks",
|
|
"content_policy_fallbacks",
|
|
"default_deployment",
|
|
"default_litellm_params",
|
|
"default_max_parallel_requests",
|
|
"cooldown_time",
|
|
"allowed_fails",
|
|
"allowed_fails_policy",
|
|
"disable_cooldowns",
|
|
"enable_pre_call_checks",
|
|
"enable_health_check_routing",
|
|
"health_check_ignore_transient_errors",
|
|
"background_health_check_model_groups",
|
|
"enable_tag_filtering",
|
|
"tag_filtering_match_any",
|
|
"tag_routing_prefix",
|
|
"router_general_settings",
|
|
"provider_budget_config",
|
|
"provider_default_deployment_ids",
|
|
"fallback_access_check",
|
|
"fallback_budget_check",
|
|
"kubernetes_pod_discovery",
|
|
"search_tools",
|
|
"guardrail_list",
|
|
"cache_responses",
|
|
)
|
|
|
|
# Runtime settings kept as plain router attributes rather than ``settings()`` fields. A write
|
|
# to one (``router.num_retries = 2``) is a changeset too: ``ChangeSet(assign={...})``.
|
|
PLAIN_SETTING_ATTRIBUTES: Final[frozenset[str]] = frozenset({"num_retries", "max_fallbacks", "retry_after", "timeout"})
|
|
# Former attributes whose value lives under another name, assignable through a changeset.
|
|
ASSIGNABLE_ALIASES: Final[Mapping[str, str]] = MappingProxyType({"model_group_alias": "_core_model_group_alias"})
|
|
|
|
_MISSING: Final = object()
|
|
# The settings fields ``RouterSettings`` copies, so an in-place edit of what a read returns never
|
|
# reaches the router (only ``apply()`` changes settings).
|
|
_COPIED_SETTING_FIELDS: Final = (
|
|
"fallbacks",
|
|
"context_window_fallbacks",
|
|
"content_policy_fallbacks",
|
|
"default_litellm_params",
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class RouterSettings:
|
|
"""
|
|
The router's configuration as one frozen snapshot (``settings()``). Fields hold the router's
|
|
own objects, except the fallback lists and ``default_litellm_params``, which are copies: settings
|
|
change only through ``apply()``, so editing one of those in place changes nothing on any core.
|
|
A field the router never set reads as ``None``.
|
|
|
|
Each field has the type of the Router attribute it reads (``_core_<field>``), the type the old
|
|
``router.<field>`` attribute had; the mutable ones are the router's live objects. ``of`` fills
|
|
every field. The ``None`` defaults exist for snapshots built directly (tests), which set only the
|
|
fields they need; the fields whose Router attribute cannot be ``None`` say so on their line.
|
|
"""
|
|
|
|
routing_strategy: str | None = None
|
|
fallbacks: "list | list[dict[str, list[str] | list | None]] | None" = None # mutable-ok: the router's live list
|
|
context_window_fallbacks: list | None = None # mutable-ok: the router's live list
|
|
content_policy_fallbacks: list | None = None # mutable-ok: the router's live list
|
|
default_deployment: object = None
|
|
default_litellm_params: dict = None # pyright: ignore[reportAssignmentType] # unset only when built directly (see above) # mutable-ok: the router's live dict
|
|
default_max_parallel_requests: int | None = None
|
|
cooldown_time: float = None # pyright: ignore[reportAssignmentType] # unset only when built directly (see above)
|
|
allowed_fails: int = None # pyright: ignore[reportAssignmentType] # unset only when built directly (see above)
|
|
allowed_fails_policy: "AllowedFailsPolicy | None" = None
|
|
disable_cooldowns: bool | None = None
|
|
enable_pre_call_checks: bool = None # pyright: ignore[reportAssignmentType] # unset only when built directly (see above)
|
|
enable_health_check_routing: bool = None # pyright: ignore[reportAssignmentType] # unset only when built directly (see above)
|
|
health_check_ignore_transient_errors: bool = None # pyright: ignore[reportAssignmentType] # unset only when built directly (see above)
|
|
background_health_check_model_groups: frozenset[str] | None = None
|
|
enable_tag_filtering: bool = None # pyright: ignore[reportAssignmentType] # unset only when built directly (see above)
|
|
tag_filtering_match_any: bool = None # pyright: ignore[reportAssignmentType] # unset only when built directly (see above)
|
|
tag_routing_prefix: str = None # pyright: ignore[reportAssignmentType] # unset only when built directly (see above)
|
|
router_general_settings: "RouterGeneralSettings" = None # pyright: ignore[reportAssignmentType] # unset only when built directly (see above)
|
|
provider_budget_config: "dict[str, BudgetConfig] | None" = None # mutable-ok: the router's live dict
|
|
provider_default_deployment_ids: list[str] = None # pyright: ignore[reportAssignmentType] # unset only when built directly (see above) # mutable-ok: the router's live list
|
|
fallback_access_check: "FallbackAccessCheck | None" = None
|
|
fallback_budget_check: "FallbackBudgetCheck | None" = None
|
|
kubernetes_pod_discovery: "KubernetesPodDiscovery" = None # pyright: ignore[reportAssignmentType] # unset only when built directly (see above)
|
|
search_tools: "list[SearchToolTypedDict]" = None # pyright: ignore[reportAssignmentType] # unset only when built directly (see above) # mutable-ok: the router's live list
|
|
guardrail_list: "list[GuardrailTypedDict]" = None # pyright: ignore[reportAssignmentType] # unset only when built directly (see above) # mutable-ok: the router's live list
|
|
cache_responses: bool | None = None
|
|
_router: "Router | None" = field(default=None, repr=False, compare=False)
|
|
_unset: frozenset[str] = field(default=frozenset(), repr=False, compare=False)
|
|
|
|
def __post_init__(self) -> None:
|
|
for name in _COPIED_SETTING_FIELDS:
|
|
value: object = getattr(self, name) # pyright: ignore[reportAny] # a settings field
|
|
if isinstance(value, list | dict):
|
|
object.__setattr__(self, name, copy.deepcopy(value)) # pyright: ignore[reportUnknownArgumentType] # a settings list or dict
|
|
|
|
@classmethod
|
|
def of(cls, router: "Router") -> "RouterSettings":
|
|
read: Final = {name: getattr(router, "_core_" + name, _MISSING) for name in SETTING_FIELDS}
|
|
values: Final = {name: None if value is _MISSING else value for name, value in read.items()}
|
|
unset: Final = frozenset(name for name, value in read.items() if value is _MISSING)
|
|
# Each value is the Router's own ``_core_<field>`` attribute, typed as the field (or None when unset).
|
|
return cls(**values, _router=router, _unset=unset) # pyright: ignore[reportArgumentType] # see above
|
|
|
|
def _owner(self, member: str) -> "Router":
|
|
if self._router is None:
|
|
raise AttributeError(f"'NoneType' object has no attribute {member!r}")
|
|
return self._router
|
|
|
|
def is_set(self, name: str) -> bool:
|
|
"""Whether the router has this setting at all (some exist only in some configurations)."""
|
|
return name in SETTING_FIELDS and name not in self._unset
|
|
|
|
def as_dict(self) -> Mapping[str, object]:
|
|
"""The runtime-updatable settings and routing groups as plain values (was ``get_settings``)."""
|
|
return self._owner("_core_get_settings")._core_get_settings()
|
|
|
|
def allowed_fails_for(self, exception: Exception) -> int | None:
|
|
"""Allowed fails for this exception under allowed_fails_policy (was ``get_allowed_fails_from_policy``)."""
|
|
return self._owner("_core_get_allowed_fails_from_policy")._core_get_allowed_fails_from_policy(
|
|
exception=exception
|
|
)
|
|
|
|
def guardrail(self, name: str) -> "GuardrailTypedDict":
|
|
"""The guardrail configured under this name (was ``get_available_guardrail``)."""
|
|
return self._owner("_core_get_available_guardrail")._core_get_available_guardrail(name)
|
|
|
|
|
|
class RouterHealth:
|
|
"""Cooldowns, blocked deployments, health checks and usage (``health()``)."""
|
|
|
|
__slots__ = ("_router",)
|
|
|
|
def __init__(self, router: "Router") -> None:
|
|
self._router = router
|
|
|
|
@property
|
|
def store(self) -> "DualCache":
|
|
"""
|
|
The cache that holds routing state (cooldowns, fail counts, usage counters, latency):
|
|
in-memory plus the router's Redis when configured. Read and write it with the router's own
|
|
key names. Replaces the ``cache`` attribute.
|
|
"""
|
|
return self._router._core_cache
|
|
|
|
@property
|
|
def cooldowns(self) -> "CooldownCache":
|
|
"""The cooldown store (was ``cooldown_cache``)."""
|
|
return self._router._core_cooldown_cache
|
|
|
|
@property
|
|
def health_checks(self) -> "DeploymentHealthCache":
|
|
"""Background health check results (was ``health_state_cache``)."""
|
|
return self._router._core_health_state_cache
|
|
|
|
@property
|
|
def latency(
|
|
self,
|
|
) -> "UntypedDict": # a mutable list/dict, as before: the deployment_latency_map attribute's type, the live dict
|
|
"""Latency samples by deployment (was ``deployment_latency_map``)."""
|
|
return self._router._core_deployment_latency_map
|
|
|
|
@staticmethod
|
|
def is_blocked(deployment: "Deployment") -> bool:
|
|
"""Whether an admin paused this deployment (was ``is_deployment_blocked``)."""
|
|
from litellm.router_rust.surface.health_routing import is_blocked as deployment_is_blocked
|
|
|
|
return deployment_is_blocked(deployment)
|
|
|
|
@staticmethod
|
|
def all_blocked(deployments: "Sequence[DeploymentTypedDict]") -> bool:
|
|
"""Whether every deployment in the list is paused (was ``are_all_deployments_blocked``)."""
|
|
from litellm.router_rust.surface.health_routing import all_blocked as deployments_all_blocked
|
|
|
|
return deployments_all_blocked(deployments)
|
|
|
|
def fully_blocked_model_names(self) -> set[str]: # mutable-ok: the core's type, a fresh set
|
|
"""Groups whose every deployment is paused (was ``get_fully_blocked_model_names``)."""
|
|
return self._router._core_get_fully_blocked_model_names()
|
|
|
|
async def fully_unhealthy_model_names(self) -> set[str]: # mutable-ok: the core's type, a fresh set
|
|
"""Groups whose every deployment failed its health check (was ``async_get_fully_unhealthy_model_names``)."""
|
|
return await self._router._core_async_get_fully_unhealthy_model_names()
|
|
|
|
@overload
|
|
async def usage(
|
|
self, model_group: str, *, kind: Literal["current"] = "current"
|
|
) -> tuple[int | None, int | None]: ...
|
|
|
|
@overload
|
|
async def usage(
|
|
self, model_group: str, *, kind: Literal["remaining"]
|
|
) -> dict[str, int]: ... # mutable-ok: Router._core_get_remaining_model_group_usage's type, a fresh dict
|
|
|
|
async def usage(
|
|
self, model_group: str, *, kind: UsageKind = "current"
|
|
) -> tuple[int | None, int | None] | Mapping[str, int]:
|
|
"""
|
|
tpm/rpm usage of a group this minute (``current``, was ``get_model_group_usage``) or what is
|
|
left of its limits (``remaining``, was ``get_remaining_model_group_usage``).
|
|
"""
|
|
if kind == "current":
|
|
return await self._router._core_get_model_group_usage(model_group=model_group)
|
|
if kind == "remaining":
|
|
return await self._router._core_get_remaining_model_group_usage(model_group=model_group)
|
|
raise ValueError(f"unknown usage kind {kind!r}")
|
|
|
|
def team_ptu_ceiling(self, team_id: str, model_group: str) -> "PTUTeamCeiling | None":
|
|
"""A team's PTU share ceiling on a group (was ``team_ptu_ceiling``)."""
|
|
return self._router._core_team_ptu_ceiling(team_id, model_group)
|
|
|
|
def split_priority(self, model: str, kwargs: Mapping[str, object]) -> tuple[int | None, Mapping[str, object]]:
|
|
"""The request priority and the kwargs without it (was ``split_request_priority``)."""
|
|
return self._router._core_split_request_priority(model, kwargs)
|
|
|
|
def failures_this_minute(self, deployment_id: str) -> int:
|
|
"""Failures counted against a deployment this minute (was ``get_deployment_failures_for_current_minute``)."""
|
|
from litellm.router_utils.router_callbacks.track_deployment_metrics import (
|
|
get_deployment_failures_for_current_minute,
|
|
)
|
|
|
|
return get_deployment_failures_for_current_minute(self._router._core_ctx, deployment_id)
|
|
|
|
def record_failure(self, deployment_id: str, exception: Exception, exception_status: str | int) -> bool:
|
|
"""
|
|
Count a failure against a deployment this minute and cool it down once it is over its allowed
|
|
fails (was ``increment_deployment_failures_for_current_minute`` then ``set_cooldown_deployments``).
|
|
Returns whether the deployment was cooled down.
|
|
"""
|
|
from litellm.router_utils.cooldown_handlers import set_cooldown_deployments
|
|
from litellm.router_utils.router_callbacks.track_deployment_metrics import (
|
|
increment_deployment_failures_for_current_minute,
|
|
)
|
|
|
|
ctx: Final = self._router._core_ctx
|
|
increment_deployment_failures_for_current_minute(ctx, deployment_id)
|
|
return set_cooldown_deployments(
|
|
ctx,
|
|
original_exception=exception,
|
|
exception_status=exception_status,
|
|
deployment=deployment_id,
|
|
time_to_cooldown=self._router._core_cooldown_time,
|
|
)
|
|
|
|
def priority_header_fields(
|
|
self, model: str, kwargs: Mapping[str, object], header: str | None
|
|
) -> Mapping[str, int | str]:
|
|
"""The request fields a priority header sets (was ``priority_header_update``)."""
|
|
return self._router.priority_header_update(model=model, kwargs=kwargs, header=header)
|
|
|
|
|
|
@dataclass
|
|
class ChangeSet:
|
|
"""
|
|
Runtime changes for ``apply``. Applied in this order: ``assign``, ``settings``, ``upsert``,
|
|
``delete``, ``redis_cache``.
|
|
|
|
- ``assign``: replace settings fields as they are (what ``router.<field> = value`` did)
|
|
- ``settings``: validated runtime settings (what ``update_settings(**settings)`` did)
|
|
- ``upsert``: add or replace deployments (``upsert_deployment``)
|
|
- ``delete``: remove deployments by id (``delete_deployment``)
|
|
- ``redis_cache``: point the routing state at a new Redis cache (``update_redis_cache``)
|
|
- ``refresh_model_info``: re-read discovered model info (``arefresh_model_info``; ``aapply`` only)
|
|
"""
|
|
|
|
assign: Mapping[str, object] = field(default_factory=dict)
|
|
settings: Mapping[str, object] = field(default_factory=dict)
|
|
upsert: Sequence["Deployment"] = ()
|
|
delete: Sequence[str] = ()
|
|
redis_cache: "RedisCache | None" = None
|
|
refresh_model_info: bool = False
|
|
refresh_client: "AsyncHTTPHandler | None" = None
|
|
|
|
|
|
@dataclass
|
|
class ApplyResult:
|
|
"""What ``apply`` did: the registry version after it, and per upsert/delete the old deployment."""
|
|
|
|
version: int
|
|
upserted: "Sequence[Deployment | None]" = field(default_factory=list)
|
|
deleted: "Sequence[Deployment | None]" = field(default_factory=list)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class RoutingObservers:
|
|
"""The router's routing observers (``observe()``)."""
|
|
|
|
slack_alerting_logger: "SlackAlerting | None" = None
|
|
|
|
|
|
def apply_changes(router: "Router", changes: ChangeSet) -> ApplyResult:
|
|
if changes.refresh_model_info:
|
|
raise TypeError("ChangeSet(refresh_model_info=True) needs aapply()")
|
|
unknown: Final = sorted(set(changes.assign) - {*SETTING_FIELDS, *PLAIN_SETTING_ATTRIBUTES, *ASSIGNABLE_ALIASES})
|
|
if unknown:
|
|
raise ValueError(f"not assignable router settings: {unknown}")
|
|
for name, value in changes.assign.items():
|
|
setattr(
|
|
router,
|
|
name if name in PLAIN_SETTING_ATTRIBUTES else ASSIGNABLE_ALIASES.get(name, "_core_" + name),
|
|
detached(value),
|
|
)
|
|
if changes.settings:
|
|
router._core_update_settings(**{name: detached(value) for name, value in changes.settings.items()})
|
|
upserted: Final = [router._core_upsert_deployment(deployment=d) for d in changes.upsert]
|
|
deleted: Final = [router._core_delete_deployment(id=i) for i in changes.delete]
|
|
if changes.redis_cache is not None:
|
|
router._core_update_redis_cache(cache=changes.redis_cache)
|
|
return ApplyResult(version=router._registry_version, upserted=upserted, deleted=deleted)
|
|
|
|
|
|
async def aapply_changes(router: "Router", changes: ChangeSet) -> ApplyResult:
|
|
refresh: Final = changes.refresh_model_info
|
|
result: Final = apply_changes(
|
|
router,
|
|
ChangeSet(
|
|
assign=changes.assign,
|
|
settings=changes.settings,
|
|
upsert=changes.upsert,
|
|
delete=changes.delete,
|
|
redis_cache=changes.redis_cache,
|
|
),
|
|
)
|
|
if refresh:
|
|
await router._core_arefresh_model_info(client=changes.refresh_client)
|
|
return result
|
|
|
|
|
|
def _takes_legacy_llm_router(check: object) -> bool:
|
|
"""Whether a fallback check still declares ``llm_router`` instead of ``ctx`` (the pre-RoutingContext shape)."""
|
|
try:
|
|
params: Final = inspect.signature(check).parameters # pyright: ignore[reportArgumentType] # any callable
|
|
except (TypeError, ValueError):
|
|
return False
|
|
return "ctx" not in params and "llm_router" in params
|
|
|
|
|
|
async def _run_fallback_check(
|
|
check: "FallbackAccessCheck | FallbackBudgetCheck",
|
|
ctx: "RoutingContext",
|
|
*,
|
|
model: str,
|
|
request_kwargs: Mapping[str, object],
|
|
) -> bool:
|
|
if _takes_legacy_llm_router(check):
|
|
warnings.warn(
|
|
f"{type(check).__name__} takes llm_router=; fallback checks now receive ctx: RoutingContext. "
|
|
"Passing llm_router is deprecated and will be removed",
|
|
DeprecationWarning,
|
|
stacklevel=2,
|
|
)
|
|
legacy: Final = check # pyright: ignore[reportAny] # the old protocol shape, checked above
|
|
return await legacy(model=model, request_kwargs=request_kwargs, llm_router=ctx._router) # pyright: ignore[reportCallIssue, reportPrivateUsage] # see above
|
|
return await check(model=model, request_kwargs=request_kwargs, ctx=ctx)
|
|
|
|
|
|
class RegistryReader(Protocol):
|
|
"""Anything that answers ``registry()``: a Router or a RoutingContext. What proxy auth helpers read."""
|
|
|
|
def registry(self) -> "RouterRegistry": ...
|
|
|
|
|
|
class RoutingContext:
|
|
"""
|
|
What a routing plugin (strategy, pre-call check, sub-router) may use of the router. Plugins get
|
|
one instead of the Router itself.
|
|
"""
|
|
|
|
__slots__ = ("_router",)
|
|
|
|
def __init__(self, router: "Router") -> None:
|
|
self._router = router
|
|
|
|
def registry(self) -> RouterRegistry:
|
|
return self._router.registry()
|
|
|
|
def deployment_params(self, request: Mapping[str, object], deployment: "Deployment | None") -> Mapping[str, object]:
|
|
return self._router._params_sent_to_deployment(request, deployment)
|
|
|
|
def default_litellm_params(self) -> Mapping[str, object]:
|
|
"""The router's ``default_litellm_params`` that are set (a None value is no default)."""
|
|
configured: Final = self.settings().default_litellm_params
|
|
return {key: value for key, value in (configured or {}).items() if value is not None}
|
|
|
|
def settings(self) -> RouterSettings:
|
|
return self._router.settings()
|
|
|
|
def health(self) -> RouterHealth:
|
|
return self._router.health()
|
|
|
|
def observers(self) -> RoutingObservers:
|
|
return self._router.observe()
|
|
|
|
def belongs_to(self, router: "Router") -> bool:
|
|
return self._router is router or router.__dict__.get("_core") is self._router
|
|
|
|
@property
|
|
def explicit_allowed_fails(self) -> int | None:
|
|
"""``allowed_fails`` as the caller passed it, ``None`` when it came from the global default."""
|
|
return self._router.explicit_allowed_fails
|
|
|
|
async def select(
|
|
self,
|
|
model: str,
|
|
request_kwargs: dict[str, object] | None = None, # mutable-ok: the selection cores take a dict
|
|
*,
|
|
kind: SelectKind = "pick",
|
|
messages: list[dict[str, str]] | None = None, # mutable-ok: the selection cores take a list
|
|
input: str | list[object] | None = None, # mutable-ok: the selection cores take a list
|
|
specific_deployment: bool | None = False,
|
|
parent_otel_span: "Span | None" = None,
|
|
health_check_probe: bool = False,
|
|
) -> object:
|
|
return await self._router.select( # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType, reportCallIssue, reportArgumentType] # the selection cores return untyped deployment dicts
|
|
model,
|
|
request_kwargs,
|
|
kind=kind, # pyright: ignore[reportArgumentType] # forwarded as given, the overloads narrow it
|
|
messages=messages,
|
|
input=input,
|
|
specific_deployment=specific_deployment,
|
|
parent_otel_span=parent_otel_span,
|
|
health_check_probe=health_check_probe,
|
|
)
|
|
|
|
async def with_fallbacks(
|
|
self,
|
|
*args: object,
|
|
**kwargs: object, # kwargs-ok: the router's fallback loop takes the request's own keyword payload
|
|
) -> object:
|
|
"""Run a call through the router's retry and fallback loop (was ``async_function_with_fallbacks``)."""
|
|
return await self._router.async_function_with_fallbacks(*args, **kwargs)
|
|
|
|
async def acompletion(
|
|
self,
|
|
**kwargs: object, # kwargs-ok: forwarded to the router endpoint as given
|
|
) -> "ModelResponse | CustomStreamWrapper":
|
|
return await self._router.acompletion(**kwargs) # pyright: ignore[reportCallIssue, reportArgumentType] # forwarded as given
|
|
|
|
async def aresponses(
|
|
self,
|
|
**kwargs: object, # kwargs-ok: forwarded to the router endpoint as given
|
|
) -> "ResponsesAPIResponse | BaseResponsesAPIStreamingIterator":
|
|
return await self._router.aresponses(**kwargs) # pyright: ignore[reportReturnType, reportArgumentType] # factory-built endpoint, returns what litellm.aresponses returns
|
|
|
|
async def aembedding(
|
|
self,
|
|
**kwargs: object, # kwargs-ok: forwarded to the router endpoint as given
|
|
) -> "EmbeddingResponse":
|
|
return await self._router.aembedding(**kwargs) # pyright: ignore[reportCallIssue, reportArgumentType] # forwarded as given
|
|
|
|
def embedding(
|
|
self,
|
|
**kwargs: object, # kwargs-ok: forwarded to the router endpoint as given
|
|
) -> "EmbeddingResponse":
|
|
return self._router.embedding(**kwargs) # pyright: ignore[reportCallIssue, reportArgumentType] # forwarded as given
|
|
|
|
async def adecisions(
|
|
self,
|
|
**kwargs: object, # kwargs-ok: forwarded to the router endpoint as given
|
|
) -> "OpenAIDecisionResponse | DecisionsResponse":
|
|
return await self._router.adecisions(**kwargs) # pyright: ignore[reportReturnType, reportArgumentType] # factory-built endpoint, returns what litellm.adecisions returns
|
|
|
|
async def aanthropic_messages(
|
|
self,
|
|
**kwargs: object, # kwargs-ok: forwarded to the router endpoint as given
|
|
) -> "MessagesResult":
|
|
return await self._router.aanthropic_messages(**kwargs) # pyright: ignore[reportReturnType, reportArgumentType] # factory-built endpoint, returns what litellm.anthropic_messages returns
|
|
|
|
def apply(self, changes: ChangeSet) -> ApplyResult:
|
|
return self._router.apply(changes)
|
|
|
|
def register_plugin(self, plugin: "CustomRoutingStrategyBase") -> None:
|
|
self._router.register_plugin(plugin)
|
|
|
|
async def fallback_access_allowed(self, *, model: str, request_kwargs: Mapping[str, object]) -> bool:
|
|
check: Final = self._router.settings().fallback_access_check
|
|
return check is None or await _run_fallback_check(check, self, model=model, request_kwargs=request_kwargs)
|
|
|
|
async def fallback_budget_allowed(self, *, model: str, request_kwargs: Mapping[str, object]) -> bool:
|
|
check: Final = self._router.settings().fallback_budget_check
|
|
return check is None or await _run_fallback_check(check, self, model=model, request_kwargs=request_kwargs)
|
|
|
|
def pre_call_checks(
|
|
self,
|
|
model: str,
|
|
healthy_deployments: list[object], # mutable-ok: Router._pre_call_checks takes and filters a list
|
|
messages: list[dict[str, str]] | None = None, # mutable-ok: Router._pre_call_checks takes a list
|
|
input: str | list[object] | None = None, # mutable-ok: Router._pre_call_checks takes a list
|
|
request_kwargs: dict[str, object] | None = None, # mutable-ok: Router._pre_call_checks takes a dict
|
|
input_token_count: int | None = None,
|
|
skip_inline_token_count: bool = False,
|
|
request_deployments: "Sequence[DeploymentTypedDict] | None" = None,
|
|
) -> Sequence[object]:
|
|
return self._router._pre_call_checks(
|
|
model=model,
|
|
healthy_deployments=healthy_deployments,
|
|
messages=messages,
|
|
input=input,
|
|
request_kwargs=request_kwargs,
|
|
input_token_count=input_token_count,
|
|
skip_inline_token_count=skip_inline_token_count,
|
|
request_deployments=request_deployments,
|
|
)
|
|
|
|
def count_pre_call_check_tokens(
|
|
self,
|
|
messages: Sequence[Mapping[str, object]] | None,
|
|
input: str | list[object] | None, # mutable-ok: Router._count_pre_call_check_tokens takes a list
|
|
request_kwargs: Mapping[str, object] | None = None,
|
|
) -> int:
|
|
return self._router._count_pre_call_check_tokens(messages, input, request_kwargs)
|
|
|
|
def update_kwargs_before_fallbacks(
|
|
self,
|
|
model: str,
|
|
kwargs: dict[str, object], # mutable-ok: the core updates the request kwargs in place
|
|
metadata_variable_name: str | None = "metadata",
|
|
) -> None:
|
|
return self._router._update_kwargs_before_fallbacks(model, kwargs, metadata_variable_name)
|
|
|
|
def arm_read_prefetch(
|
|
self,
|
|
model: str,
|
|
request_kwargs: dict[str, object] | None = None, # mutable-ok: the core takes a dict
|
|
) -> None:
|
|
"""(was ``arm_routing_read_prefetch``)"""
|
|
return self._router._core_arm_routing_read_prefetch(model, request_kwargs)
|
|
|
|
async def pre_routing_hook(
|
|
self,
|
|
model: str,
|
|
request_kwargs: dict[str, object], # mutable-ok: Router._core_async_pre_routing_hook takes a dict
|
|
messages: list[dict[str, object]] | None = None, # mutable-ok: the core takes a list
|
|
input: str | list[object] | None = None, # mutable-ok: Router._core_async_pre_routing_hook takes a list
|
|
specific_deployment: bool | None = False,
|
|
) -> "PreRoutingHookResponse | None":
|
|
"""(was ``async_pre_routing_hook``)"""
|
|
return await self._router._core_async_pre_routing_hook(
|
|
model=model,
|
|
request_kwargs=request_kwargs,
|
|
messages=messages,
|
|
input=input,
|
|
specific_deployment=specific_deployment,
|
|
)
|
|
|
|
def log_retry(
|
|
self,
|
|
kwargs: dict[str, object], # mutable-ok: Router._core_log_retry records the retry into the request kwargs
|
|
e: Exception,
|
|
) -> dict: # mutable-ok: the fallback loop edits it; Router._core_log_retry's type
|
|
return self._router._core_log_retry(kwargs=kwargs, e=e)
|
|
|
|
|
|
__all__ = [
|
|
"SETTING_FIELDS",
|
|
"ApplyResult",
|
|
"ChangeSet",
|
|
"RouterHealth",
|
|
"RouterRegistry",
|
|
"RouterSettings",
|
|
"RoutingContext",
|
|
"RoutingObservers",
|
|
"deployment_id",
|
|
]
|