Merge pull request #48294 from BerriAI/diag-catalog-1.106

chore(release): backport #46711 to stable/1.106.x and cut 1.106.4
This commit is contained in:
yuneng-jiang 2026-10-24 23:01:34 -07:00 • committed by GitHub
commit a554fcfcf7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 19 additions and 54 deletions

View file

@ -1,4 +1,4 @@
"""Ordered rollout policy for routes and loggers.
"""Ordered rollout policy for routes.
The first matching rule wins; unmatched contexts stay on Python. Native
admission separately decides whether the selected implementation can execute.
@ -39,34 +39,17 @@ class RouteRule:
providers: frozenset[str] | None = None
models: frozenset[str] | None = None
def matches(self, context: Context) -> bool:
def matches(self, context: RouteContext) -> bool:
return (
isinstance(context, RouteContext)
and context.route is self.route
context.route is self.route
and (self.providers is None or context.provider in self.providers)
and (self.models is None or context.model in self.models)
)
@dataclass(frozen=True, slots=True)
class LoggerContext:
pass
@dataclass(frozen=True, slots=True)
class LoggerRule:
rollout: Rollout
def matches(self, context: Context) -> bool:
return isinstance(context, LoggerContext)
Context: TypeAlias = RouteContext | LoggerContext
Rule: TypeAlias = RouteRule | LoggerRule
Rules: TypeAlias = tuple[Rule, ...]
Rules: TypeAlias = tuple[RouteRule, ...]
RULES: Final[Rules] = (
LoggerRule(Rollout.RUST_OPT_IN),
RouteRule(Route.CHAT_COMPLETIONS, Rollout.PYTHON_ONLY),
RouteRule(Route.EMBEDDINGS, Rollout.PYTHON_ONLY),
RouteRule(Route.OCR, Rollout.RUST_REQUIRED),
@ -79,10 +62,10 @@ RULES: Final[Rules] = (
)
def rollout(context: Context, rules: Rules | None = None) -> Rollout:
def rollout(context: RouteContext, rules: Rules | None = None) -> Rollout:
selected_rules: Final = RULES if rules is None else rules
return next((rule.rollout for rule in selected_rules if rule.matches(context)), Rollout.PYTHON_ONLY)
def decision(context: Context, rules: Rules | None = None) -> Decision:
def decision(context: RouteContext, rules: Rules | None = None) -> Decision:
return _decision(rollout(context, rules))

View file

@ -44,11 +44,9 @@ def _construct(factory: NativeDiagnosticFactory, minimum_custom_key_length: int)
def run(native: Callable[[NativeDiagnosticProcessor], ResultT], python: Callable[[], ResultT]) -> ResultT:
from litellm.constants import MINIMUM_CUSTOM_KEY_LENGTH
from litellm.rust_bridge.catalog import LoggerContext, decision
from litellm.rust_bridge.configuration import Decision
from litellm.rust_bridge.configuration import rust_enabled
selected: Final = decision(LoggerContext())
if selected is Decision.PYTHON:
if not rust_enabled():
return python()
factory: Final = PROCESSOR.load()
if factory is None:

View file

@ -6,7 +6,7 @@ from typing import Final, Generic, TypeAlias, TypeVar
from litellm.rust_bridge import catalog, runtime
from litellm.rust_bridge.bindings import NativeBinding
from litellm.rust_bridge.catalog import Route, RouteContext, RouteRule, Rules
from litellm.rust_bridge.catalog import Route, RouteContext, Rules
from litellm.rust_bridge.configuration import Decision
from litellm.rust_bridge.configuration import decision as rollout_decision
@ -35,7 +35,7 @@ class PublicDispatch(Generic[RequestT]):
def _requires_projection(self, rules: Rules) -> bool:
for rule in rules:
if not isinstance(rule, RouteRule) or rule.route is not self.route:
if rule.route is not self.route:
continue
if rule.providers is not None or rule.models is not None:
if rollout_decision(rule.rollout) is not Decision.PYTHON:

View file

@ -1,6 +1,6 @@
[project]
name = "litellm"
version = "1.106.3"
version = "1.106.4"
description = "Library to easily interface with LLM API providers"
readme = "README.md"
requires-python = ">=3.10, <3.15"
@ -379,7 +379,7 @@ litellm-enterprise = { workspace = true }
members = ["enterprise", "litellm-proxy-extras"]
[tool.commitizen]
version = "1.106.3"
version = "1.106.4"
version_files = [
"pyproject.toml:^version",
]

View file

@ -7,9 +7,6 @@ import pytest
from litellm.rust_bridge import catalog, configuration
from litellm.rust_bridge.catalog import (
Context,
LoggerContext,
LoggerRule,
Route,
RouteContext,
RouteRule,
@ -63,13 +60,6 @@ def test_missing_rule_stays_on_python_even_when_rust_is_enabled(monkeypatch: pyt
assert catalog.decision(RouteContext(route), rules=()) is Decision.PYTHON
def test_logger_rollout_obeys_the_global_switch() -> None:
assert catalog.rollout(LoggerContext()) is Rollout.RUST_OPT_IN
assert catalog.decision(LoggerContext()) is Decision.PYTHON
configuration.rust(True)
assert catalog.decision(LoggerContext()) is Decision.RUST_WITH_FALLBACK
@pytest.mark.parametrize(
("context", "expected"),
(
@ -115,12 +105,10 @@ def test_ocr_has_no_python_path_to_opt_out_to(
(RouteContext(Route.OCR, provider="local"), Decision.RUST_REQUIRED),
(RouteContext(Route.OCR, provider="other"), Decision.PYTHON),
(RouteContext(Route.MESSAGES, provider="local"), Decision.PYTHON),
(LoggerContext(), Decision.RUST_REQUIRED),
),
)
def test_mixed_rules_select_only_the_matching_domain(context: Context, expected: Decision) -> None:
def test_rules_select_only_the_matching_route(context: RouteContext, expected: Decision) -> None:
rules: Final[Rules] = (
LoggerRule(Rollout.RUST_REQUIRED),
RouteRule(Route.OCR, Rollout.RUST_REQUIRED, providers=frozenset({"local"})),
RouteRule(Route.OCR, Rollout.PYTHON_ONLY),
)
@ -128,7 +116,6 @@ def test_mixed_rules_select_only_the_matching_domain(context: Context, expected:
assert catalog.decision(context, rules) is expected
@pytest.mark.parametrize("context", (RouteContext(Route.OCR), LoggerContext()))
@pytest.mark.parametrize(
("rollout", "process", "environment", "expected"),
(
@ -142,9 +129,8 @@ def test_mixed_rules_select_only_the_matching_domain(context: Context, expected:
(Rollout.RUST_OPT_OUT, True, "0", Decision.PYTHON),
),
)
def test_all_domains_share_rollout_switches_and_first_match(
def test_rollout_switches_and_first_match(
monkeypatch: pytest.MonkeyPatch,
context: Context,
rollout: Rollout,
process: bool | None,
environment: str | None,
@ -155,19 +141,17 @@ def test_all_domains_share_rollout_switches_and_first_match(
monkeypatch.setenv("LITELLM_RUST", environment)
rules: Final[Rules] = (
RouteRule(Route.OCR, rollout),
LoggerRule(rollout),
RouteRule(Route.OCR, Rollout.RUST_REQUIRED),
)
assert catalog.decision(context, rules) is expected
assert catalog.decision(context, ()) is Decision.PYTHON
assert catalog.decision(RouteContext(Route.OCR), rules) is expected
assert catalog.decision(RouteContext(Route.OCR), ()) is Decision.PYTHON
@pytest.mark.parametrize("context", (RouteContext(Route.OCR),))
def test_empty_constraints_match_nothing(context: Context) -> None:
def test_empty_constraints_match_nothing() -> None:
rules: Final[Rules] = (
RouteRule(Route.OCR, Rollout.RUST_REQUIRED, providers=frozenset()),
RouteRule(Route.OCR, Rollout.RUST_REQUIRED, models=frozenset()),
)
assert catalog.decision(context, rules) is Decision.PYTHON
assert catalog.decision(RouteContext(Route.OCR), rules) is Decision.PYTHON

2
uv.lock generated
View file

@ -4540,7 +4540,7 @@ wheels = [
[[package]]
name = "litellm"
version = "1.106.3"
version = "1.106.4"
source = { editable = "." }
dependencies = [
{ name = "aiohttp" },