fix(litellm): skip cost-map fetch offline, widen cold-start grace period - #353
Merged
Conversation
Signed-off-by: Zachary Estrada <zachary.estrada@ll.mit.edu>
Collaborator
|
Sorry for the delay, I was away traveling! I did an e2e smoke test: looks good to me, maybe add a unit test for rendering the compose with the def test_offline_renders_local_cost_map_env(monkeypatch, tmp_path):
_patch_renderer(monkeypatch)
monkeypatch.setattr(renderer, "prepare_llm_context", lambda *_a, **_k: {
"mode": "internal",
"litellm_env_secret_files": {"OPENAI_API_KEY": str(tmp_path / "sec")},
"litellm_config_path": str(tmp_path / "litellm-config.yaml"),
"key_gen_request_path": str(tmp_path / "key_gen_request.yaml"),
"secret_files": {}, "api_keys": {},
})
crs_compose = _make_crs_compose(tmp_path, [_make_crs(tmp_path, "crs-libfuzzer")])
crs_compose.offline = True
rendered, _ = _render(crs_compose, _make_target(tmp_path, has_repo=False), tmp_path)
svc = yaml.safe_load(rendered)["services"]["oss-crs-litellm"]
assert svc["environment"] == ["LITELLM_LOCAL_MODEL_COST_MAP=True"]
assert svc["healthcheck"]["start_period"] == "90s" |
Signed-off-by: Zachary Estrada <zachary.estrada@ll.mit.edu>
azchin
force-pushed
the
fix/litellm-offline-boot
branch
from
August 14, 2026 04:28
f80942f to
f47c817
Compare
…s into fix/litellm-offline-boot
Kusari Analysis Results:❌ Kusari Inspector did not receive results for this commit in time, so the scan was not completed. Pushing a new commit will start a fresh scan. Please email support@kusari.dev if this persists. |
|
Kusari PR Analysis rerun based on - 36a3009 performed at: 2026-08-17T14:39:12Z - link to updated analysis |
Signed-off-by: Zachary Estrada <zachary.estrada@ll.mit.edu>
zestrada
force-pushed
the
fix/litellm-offline-boot
branch
from
August 17, 2026 17:33
36a3009 to
25e0252
Compare
Contributor
Author
|
Updated PR to include tests |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
We had issues running OSS-CRS due to non-determinism in LiteLLM startup and timeouts. This PR makes changes to the internal LiteLLM proxy's startup.
1. LiteLLM reaches out to github.com on every
oss-crs run.GetModelCostMapfetchesmodel_prices_and_context_window.jsonat startup, falling back to the copy bundled in the package when that fails. Under--offlinethis setsLITELLM_LOCAL_MODEL_COST_MAP=Trueto skip the fetch. Note this removes a startup dependency; it is not what caused those timeouts (see Validation).Only under
--offline, where the fetch cannot succeed and litellm falls back to the bundled map regardless — so nothing is given up. Online behavior is unchanged and live pricing is preserved. Making it unconditional would also be defensible (see User Impact) and is a one-line change if maintainers prefer it; this PR takes the narrower option.2. The healthcheck window is too small for a cold start. First boot applies ~140 Prisma migrations from
litellm-proxy-extrasto an empty Postgres; the composeentrypointoverride only replaces the ddtrace wrapper indocker/prod_entrypoint.sh, so those still run. The old window wasstart_period: 15s+10 × 5s= 65s (:112-117), and measured cold start is right around that time (see Validation). When it expires,oss-crs-litellm-key-gendoesn't start and the whole OSS-CRS run aborts, because every CRS module gates oncondition: service_healthy(:86-88).User Impact
No CLI, API, or config surface changes, and nothing to migrate. Online runs are unaffected Under
--offline, pricing comes from the map bundled in the pinned image, which is what litellm already fell back to when the fetch failed. So there is no pricing change in either mode.If maintainers prefer this unconditional
Dropping the
{% if offline %}gate would make every internal-mode run use the bundled map. That was my original change, but moved to the gated version to keep online runs unchanged.Against: online, fetching should be more accurate than a pinned map, especially if the OSS-CRS instance is not updated frequently. Pricing is used for
max_budgeton the CRS's LiteLLM virtual key (litellm-key-gen/main.py:49), so the map governs a cap on spending. The most dangerous case is under-pricing: an unpriced model contributes0.0, the budget never trips, and spend is unbounded.For: the accuracy gap on an up-to-date repo is measurable and small. Diffing the map bundled in the pinned image (
litellm-databasev1.94.0 /litellm_proxy_extras0.4.79.post2, 2963 entries) against current upstream (2988) across all 73 models indefaults/litellm/default-models.yaml:input_cost_per_token/output_cost_per_token)Across the full map rather than just our 73, the pinned image is missing 25 upstream entries and disagrees on 10 prices.
claude-opus-5and its bedrock/vertex/azure variants are absent from the bundled map. Alitellm-config.yamlnaming it would get no price and never tripllm_budget.gpt-5.6-luna/gpt-5.6-terraare priced 5× high in the bundled map (1e-06vs2e-07input).How stale does it get? The current pin is 8 days old, and Dependabot has bumped
LITELLM_IMAGEroughly every 2-3 weeks (7 bumps since late April), so the window is ~3 weeks worst case. Over those 8 days upstream repriced 0 of the 73 models shipped in OSS-CRS; the only 2 repricings anywhere in the map were the two above, both released after the pin.Also worth considering: the fetch's freshness is not guaranteed. It fails open - any DNS hiccup silently falls back to this same bundled map with only a log warning — so two runs of the same digest can already be priced from different sources with no way to tell which from the spend report.
Either way, a CI check that every model in a shipped config resolves to a nonzero cost in the pinned image would make this class of problem visible rather than silent could be something to consider (but not sure how updated/reliable those reference configs are for "models that are being used).
Release Note / Changelog
CHANGELOG.md([Unreleased]) for user-facing changesNo deprecation or breaking behavior. No public signatures change; the edits are a Jinja template, one context key in
renderer.py, and one test fixture. Happy to add an entry if maintainers read it the other way.Validation
Timing measured on an offline host from the litellm service logs (postgres
ready to accept connections→ first/health/liveliness200):LITELLM_LOCAL_MODEL_COST_MAPstart_periodCold start is dominated by litellm import plus the Prisma migrations, which run on every boot because the DB is fresh each run - not by the cost-map fetch, which fails fast offline (the warning is logged ~5.6s after pg-ready; the remaining ~30s is between that warning and
Running prisma migrate deploy). Removing the fetch saves only ~3-4s.So the
start_periodbump is doing the real work here. The old 65s budget expired ~5s before litellm became healthy, and even the faster passing run came within ~3s of the cliff on an unloaded box. 90s (→ 90+50 = 140s) is what makes this robust rather than lucky.Note the numbers above were collected on an older image carrying fewer Prisma migrations; so cold start on
maintoday should be somewhat slower than measured, not faster.Reproduced independently on a second (faster) host against the pinned digest, postgres pinned, fresh volume per run, on a
--internalDocker network with no egress:Failed to fetch remote model cost map … Temporary failure in name resolution. Falling back to local backup.Confirms both halves: the fetch fails fast offline (worth <1s here, ~3-4s on the slower host, never ~30s), and the env var does cleanly suppress it.
Other checks:
uv run verify— all five stages PASS:ruff check,ruff format,pyright, unit tests (591 passed, 29 deselected), libCRS unit tests (9 passed).Rendered the template with an internal-mode
llm_contextin three states and parsed each withyaml.safe_load. All valid YAML,start_period: 90s/retries: 10in every case, and the secret-derived$$(cat /run/secrets/litellm_env_*)exports incommandrender byte-identically throughout — this service had noenvironment:key before, so there was nothing to collide with:environmentoffline=True['LITELLM_LOCAL_MODEL_COST_MAP=True']offline=FalseCost-map comparison reproducible with:
diffed against
raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.jsonfor everylitellm_params.modelindefault-models.yaml, trying both theprovider/modeland bare-name lookup forms litellm uses.Migration count from
ls -d .../litellm_proxy_extras/migrations/2* | wc -lin the pinned image.Checklist