guard()
Wrap one provider with a fallback chain, a rate limit and a spend cap. It returns the same type it wrapped, so it drops into your session unchanged.
attach() watches. guard() acts. It wraps a single native provider, a LiveKit plugin or a Pipecat service, and returns a drop-in of the same type.
llm = voicegateway.guard(
openai.LLM(model="gpt-4o-mini"),
fallback=[openai.LLM(model="gpt-4o")], # tried in order when the primary errors
rate_limit="60/min", # or "5/s"
budget="$5.00/day", # or "$100/month"
project="my-agent", # whose spend the budget reads
)Guard only the providers that need it. Every argument is optional.
What each control does
| Control | Behavior | When it trips |
|---|---|---|
fallback | Runs each fallback in order until one succeeds. The row attach() writes carries status="fallback" and fallback_from=<primary>. | Every provider failed: the last error is raised |
rate_limit | A token bucket parsed from "60/min" or "5/s" | The bucket is empty: RateLimitExceeded |
budget | Reads the window's spend from the rows attach() already wrote | Spend is at or over the cap: BudgetExceededError |
The budget closes the loop: enforcement reads the same cost data the meter records. That is also why guard() writes no metrics of its own. With attach() on the session, nothing is counted twice.
Fallback covers failures before the first chunk
LLM and TTS stream. An error before the primary yields its first chunk moves on to the next fallback. Once a chunk has arrived, the request is committed to that provider and a later failure surfaces instead of being patched over. STT is a single request and response, so it is all or nothing.
Size a fallback chain for "the primary is down", not "the primary died mid-sentence".
What it does not do
- Manage keys. You pass providers you already configured.
- Meter. Cost and latency come from
attach(). - Cross frameworks. A LiveKit guard takes LiveKit fallbacks; a Pipecat guard takes Pipecat fallbacks.
It raises ImportError when the provider's framework extra is missing, and ValueError for a rate_limit or budget it cannot parse.