v0.4.0
The waterfall now learns. A vendor that reports no budget is parked instead of
being re-dialled on every turn for as long as its cap lasts.
Out-of-budget vendors are parked (#80)
LiteLLM decides cooldowns by HTTP status and cools only 429/401/408/404.
Budget exhaustion is none of those -- Anthropic answers a usage cap with 400,
OpenRouter answers no-credit with 402 -- so a vendor that is dead for weeks
was dialled first on every single message, failed, and only then did the chain
fall through. Fallback worked; nothing learned. Filed upstream as
BerriAI/litellm#37592.
Now a 4xx whose message names a spend problem drops that vendor from the chain
and rebuilds the Router without it:
waterfall: openrouter is out of budget; parked for 3600s
waterfall: rebuilding, chain is now anthropic -> gemini -> requesty
The subtle part, and the reason the first attempt at this was wrong: parking
hangs off litellm's failure callback, not off an except. When a fallback
covers the failure the Router returns that answer and the primary's exception
never propagates, so an except only ever sees the case where the whole chain is
dead -- which is the one case parking cannot help. Verified against live vendors:
OpenRouter genuinely at 402 with gemini answering returns 'ok', raises nothing,
and OpenRouter is parked anyway.
Details worth knowing:
- Status alone does not park. A 400 is also "your request was malformed", and
parking a vendor for an hour over one bad prompt would be worse than the
problem. The message has to name money too. - A stated recovery date wins over the window; Anthropic returns
You will regain access on YYYY-MM-DD. A date that fails to parse falls back
to the window rather than being trusted -- a mis-parse could park a vendor for
a year. - The vendor is identified by its exact deployment string, which litellm
passes aslitellm_params.metadata.deployment. Not by the model name in the
callback: litellm strips the provider prefix there, so two vendors reached
through different routers both reportopenai/gpt-4oand the wrong one gets
parked. An ambiguous match parks nothing, because removing a working rung is
the worse error. - Parks survive restarts. The watchdog restarts this process routinely; an
in-memory park would be re-learned every few minutes. - A vendor returns on its own, with no timer. Expired entries are pruned
whenever the chain is read, so the first turn after the window rebuilds with
that vendor back in place. - A fully parked chain still tries everything. Refusing to answer is worse
than one wasted call, and every park is ultimately a guess about someone
else's billing.
Rate limits cool on the first 429 (#51)
allowed_fails=0, so a rate-limited deployment is cooled immediately rather than
after litellm's default fail threshold. A Slack agent's traffic is bursty and
low-volume: by the time a threshold is reached the burst is over, and every
failure in it was a wasted round-trip.
The rest of #51 stays deferred, and num_retries 1 -> 2 is explicitly declined --
an extra retry would double the latency of exactly the problem #80 removes.
Also
announce and the new parking both need state that outlives a restart, so the
state-file handling moved into a shared state module. No behaviour change to
announcements.
Upgrading
git pull
.venv/bin/pip install -r requirements.txt
.venv/bin/python selfcheck.py
deploy/service.sh restart
- New optional config key:
budget_park_sec(default 3600) -- how long to
skip a vendor that reported no budget when it does not say when it will be
back.0disables parking entirely. Nothing to change if the default suits. shmobster-state.jsongains aparked_vendorskey alongside the announced
version. It is created and maintained automatically; delete the key to
un-park everything immediately.- Upgrading from v0.2.0 or earlier: v0.3.0 requires voitta-yolt v1.0.0+, see
those notes.