mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
fix(rust_bridge): let LITELLM_RUST win over litellm.rust() for optional tiers
Parse the switch with pydantic TypeAdapter(bool) so 1/true/yes/on and 0/false/no/off all work, and treat an unparseable value as unset instead of off. PYTHON_ONLY and RUST_REQUIRED still ignore both switches Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
358d767c9e
commit
f9d423827f
4 changed files with 73 additions and 37 deletions
|
|
@ -4,10 +4,11 @@ import os
|
|||
from enum import Enum, auto
|
||||
from typing import Final
|
||||
|
||||
from pydantic import TypeAdapter, ValidationError
|
||||
from typing_extensions import assert_never
|
||||
|
||||
_TRUE_ENV_VALUES: Final = frozenset({"1", "true", "yes", "on"})
|
||||
_GLOBAL_ENV_NAME: Final = "LITELLM_RUST"
|
||||
_ENV_BOOL: Final = TypeAdapter(bool)
|
||||
|
||||
|
||||
class Rollout(Enum):
|
||||
|
|
@ -34,7 +35,10 @@ _CONFIGURATION: Final = _RustConfiguration()
|
|||
def _parse_env_bool(value: str | None) -> bool | None:
|
||||
if value is None:
|
||||
return None
|
||||
return value.strip().lower() in _TRUE_ENV_VALUES
|
||||
try:
|
||||
return _ENV_BOOL.validate_python(value.strip())
|
||||
except ValidationError:
|
||||
return None
|
||||
|
||||
|
||||
def decide(
|
||||
|
|
@ -50,10 +54,10 @@ def decide(
|
|||
return Decision.RUST_REQUIRED
|
||||
case Rollout.RUST_OPT_IN | Rollout.RUST_OPT_OUT:
|
||||
switch: Final = (
|
||||
process_override
|
||||
if process_override is not None
|
||||
else environment_override
|
||||
environment_override
|
||||
if environment_override is not None
|
||||
else process_override
|
||||
if process_override is not None
|
||||
else rollout is Rollout.RUST_OPT_OUT
|
||||
)
|
||||
return Decision.RUST_WITH_FALLBACK if switch else Decision.PYTHON
|
||||
|
|
@ -80,6 +84,7 @@ def reset_rust_configuration() -> None:
|
|||
def rust(enabled: bool | None) -> None:
|
||||
"""Set the process override for optional Rust paths.
|
||||
|
||||
``PYTHON_ONLY`` and ``RUST_REQUIRED`` routes in the catalog ignore this switch.
|
||||
``PYTHON_ONLY`` and ``RUST_REQUIRED`` routes in the catalog ignore this switch,
|
||||
and an explicit ``LITELLM_RUST`` environment value wins over it.
|
||||
"""
|
||||
_CONFIGURATION.override = enabled
|
||||
|
|
|
|||
|
|
@ -33,12 +33,14 @@ Decision: Final = configuration.Decision
|
|||
(Rollout.RUST_REQUIRED, False, False, Decision.RUST_REQUIRED),
|
||||
(Rollout.RUST_OPT_IN, None, None, Decision.PYTHON),
|
||||
(Rollout.RUST_OPT_IN, None, True, Decision.RUST_WITH_FALLBACK),
|
||||
(Rollout.RUST_OPT_IN, True, False, Decision.RUST_WITH_FALLBACK),
|
||||
(Rollout.RUST_OPT_IN, False, True, Decision.PYTHON),
|
||||
(Rollout.RUST_OPT_IN, True, None, Decision.RUST_WITH_FALLBACK),
|
||||
(Rollout.RUST_OPT_IN, True, False, Decision.PYTHON),
|
||||
(Rollout.RUST_OPT_IN, False, True, Decision.RUST_WITH_FALLBACK),
|
||||
(Rollout.RUST_OPT_OUT, None, None, Decision.RUST_WITH_FALLBACK),
|
||||
(Rollout.RUST_OPT_OUT, None, False, Decision.PYTHON),
|
||||
(Rollout.RUST_OPT_OUT, False, True, Decision.PYTHON),
|
||||
(Rollout.RUST_OPT_OUT, True, False, Decision.RUST_WITH_FALLBACK),
|
||||
(Rollout.RUST_OPT_OUT, False, None, Decision.PYTHON),
|
||||
(Rollout.RUST_OPT_OUT, False, True, Decision.RUST_WITH_FALLBACK),
|
||||
(Rollout.RUST_OPT_OUT, True, False, Decision.PYTHON),
|
||||
),
|
||||
)
|
||||
def test_decide_precedence(
|
||||
|
|
@ -68,50 +70,53 @@ def test_opt_out_route_configuration(
|
|||
|
||||
expected: Final = (
|
||||
Decision.RUST_WITH_FALLBACK
|
||||
if process is True or (process is None and environment not in frozenset({"0", "off"}))
|
||||
if environment == "1" or (environment is None and process is not False)
|
||||
else Decision.PYTHON
|
||||
)
|
||||
assert configuration.decision(Rollout.RUST_OPT_OUT) is expected
|
||||
|
||||
|
||||
def test_process_override_wins_over_environment(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("LITELLM_RUST", "0")
|
||||
@pytest.mark.parametrize(
|
||||
("environment", "process", "expected"),
|
||||
(
|
||||
*((value, True, False) for value in ("0", "false", "False", "no", "off", "f", "n", " 0 ")),
|
||||
*((value, False, True) for value in ("1", "true", "TRUE", "yes", "on", "t", "y", " 1 ")),
|
||||
),
|
||||
)
|
||||
def test_environment_wins_over_process_override(
|
||||
monkeypatch: pytest.MonkeyPatch, environment: str, process: bool, expected: bool
|
||||
) -> None:
|
||||
monkeypatch.setenv("LITELLM_RUST", environment)
|
||||
configuration.rust(process)
|
||||
|
||||
assert configuration.rust_enabled() is expected
|
||||
|
||||
|
||||
def test_process_override_applies_when_environment_is_unset() -> None:
|
||||
configuration.rust(True)
|
||||
|
||||
assert configuration.rust_enabled() is True
|
||||
|
||||
|
||||
def test_global_environment_accepts_explicit_false(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("LITELLM_RUST", "off")
|
||||
|
||||
assert configuration.rust_enabled() is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", ("", " ", "sometimes", "2"))
|
||||
def test_invalid_environment_value_disables_rust(monkeypatch: pytest.MonkeyPatch, value: str) -> None:
|
||||
def test_invalid_environment_value_is_ignored(monkeypatch: pytest.MonkeyPatch, value: str) -> None:
|
||||
monkeypatch.setenv("LITELLM_RUST", value)
|
||||
|
||||
assert configuration.rust_enabled() is False
|
||||
|
||||
|
||||
def test_process_override_and_reset_apply_to_existing_threads(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("LITELLM_RUST", "1")
|
||||
|
||||
with ThreadPoolExecutor(max_workers=1) as executor:
|
||||
assert executor.submit(configuration.rust_enabled).result() is True
|
||||
configuration.rust(False)
|
||||
assert executor.submit(configuration.rust_enabled).result() is False
|
||||
configuration.reset_rust_configuration()
|
||||
assert executor.submit(configuration.rust_enabled).result() is True
|
||||
|
||||
|
||||
def test_explicit_override_precedes_invalid_environment(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("LITELLM_RUST", "sometimes")
|
||||
|
||||
assert configuration.decision(Rollout.RUST_OPT_OUT) is Decision.RUST_WITH_FALLBACK
|
||||
configuration.rust(True)
|
||||
assert configuration.rust_enabled() is True
|
||||
|
||||
|
||||
def test_process_override_and_reset_apply_to_existing_threads() -> None:
|
||||
with ThreadPoolExecutor(max_workers=1) as executor:
|
||||
assert executor.submit(configuration.rust_enabled).result() is False
|
||||
configuration.rust(True)
|
||||
assert executor.submit(configuration.rust_enabled).result() is True
|
||||
configuration.reset_rust_configuration()
|
||||
assert executor.submit(configuration.rust_enabled).result() is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("value", "expected"), (("1", "True"), ("0", "False")))
|
||||
def test_environment_controls_startup(value: str, expected: str) -> None:
|
||||
environment: Final = {**os.environ, "LITELLM_RUST": value}
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ def test_public_missing_required_argument_error_does_not_depend_on_native_select
|
|||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("asynchronous", [False, True])
|
||||
@pytest.mark.parametrize("enabled", [False, None])
|
||||
@pytest.mark.parametrize("enabled", [False, True, None])
|
||||
async def test_environment_opt_out_never_loads_native(
|
||||
monkeypatch: pytest.MonkeyPatch, asynchronous: bool, enabled: bool | None
|
||||
) -> None:
|
||||
|
|
|
|||
|
|
@ -114,6 +114,32 @@ def test_environment_switch_enables_opt_in_route(monkeypatch: pytest.MonkeyPatch
|
|||
assert calls.calls == (RUST,)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("rollout", "environment", "switch", "expected"),
|
||||
(
|
||||
(Rollout.RUST_OPT_IN, "0", True, (PYTHON,)),
|
||||
(Rollout.RUST_OPT_OUT, "0", True, (PYTHON,)),
|
||||
(Rollout.RUST_OPT_IN, "1", False, (RUST,)),
|
||||
(Rollout.RUST_OPT_OUT, "1", False, (RUST,)),
|
||||
(Rollout.RUST_REQUIRED, "0", False, (RUST,)),
|
||||
(Rollout.PYTHON_ONLY, "1", True, (PYTHON,)),
|
||||
),
|
||||
)
|
||||
def test_environment_switch_wins_over_process_switch(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
rollout: Rollout,
|
||||
environment: str,
|
||||
switch: bool,
|
||||
expected: tuple[str, ...],
|
||||
) -> None:
|
||||
calls: Final = recorder()
|
||||
monkeypatch.setenv("LITELLM_RUST", environment)
|
||||
configuration.rust(switch)
|
||||
|
||||
assert run(rollout, calls) == expected[-1]
|
||||
assert calls.calls == expected
|
||||
|
||||
|
||||
def test_context_outside_rule_stays_on_python() -> None:
|
||||
calls: Final = recorder()
|
||||
configuration.rust(True)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue