guard() is VoiceGateway’s control seam. It wraps a native provider (a
LiveKit plugin or a Pipecat service) and returns a drop-in replacement of the
same type that adds three controls: fallback, rate limiting, and spend caps.
guard() writes no metrics; attach() is the sole
meter, so guard(provider) plus attach(session) never double-counts. The
two seams never call each other: they coordinate only through ContextVars and
shared spend/limit state in the framework-neutral core. Use attach() on
every session; reach for guard() on the specific providers where you want
fallback or limits.
Signature
ImportError when the provider’s framework extra is not installed,
and ValueError when rate_limit / budget can’t be parsed or the
provider’s framework isn’t recognized (see Frameworks and extras).
- fallback: on a primary-provider error, each fallback runs in order
until one succeeds.
attach()stampsfallback_from=<primary>andstatus="fallback"on the row for the provider that actually produced the result. If every provider fails, the last error is re-raised. - rate_limit: a token bucket parsed from the DSL (
"60/min","5/s"). An empty bucket raisesRateLimitExceeded. - budget: a spend cap parsed from the DSL (
"$5.00/day","$100/month"). guard reads the window’s accumulated spend from the core and raisesBudgetExceededErrorwhen it is at or over the cap. This closes the measure-then-enforce loop: enforcement reads the cost dataattach()already wrote.
Fallback only covers pre-output failures
A real constraint on both frameworks, for any streaming modality (LLM, TTS): guard opens the primary provider and starts consuming its stream. An error before the first chunk moves to the next fallback; once the primary has yielded a chunk, the request is committed to it and a later failure surfaces rather than getting silently patched over. STT is a single call-and-response on both frameworks, so it’s naturally all-or-nothing. Size a fallback chain for “primary is down” rather than “primary died mid-stream.”Wiring
- LiveKit
- Pipecat
guard() returns a subclass of the LiveKit base (livekit.agents.{llm,stt,tts})
so the result slots into an AgentSession unchanged:What guard() does not do
It does not manage provider keys (you pass already-configured, bring-your- own-key providers), does not meter (cost and latency come fromattach()),
and does not abstract across frameworks: a LiveKit guard takes LiveKit
fallbacks, a Pipecat guard takes Pipecat fallbacks.
See also
- attach(): the passive meter guard composes with.
- Frameworks and extras: the framework-neutral core and the errors you get for a missing extra or an unrecognized provider.