@@ -15,8 +15,10 @@ process.
1515
1616Works with [ CrewAI] ( #crewai ) · [ LiteLLM] ( #litellm ) · [ LangChain] ( #langchain ) ·
1717[ LangGraph] ( #langgraph ) · [ OpenAI] ( #openai ) · [ Anthropic] ( #anthropic ) ·
18- [ Gemini] ( #google-gemini ) · [ Vercel AI SDK] ( #vercel-ai-sdk ) — or any stack, via
19- plain ` check() ` / ` record() ` .
18+ [ Gemini] ( #google-gemini ) · [ Vercel AI SDK] ( #vercel-ai-sdk ) — and voice pipelines
19+ via [ Pipecat] ( #pipecat-voice ) · [ LiveKit] ( #livekit-voice ) — or any stack, via
20+ plain ` check() ` / ` record() ` . See the [ adapter matrix] ( #adapter-matrix ) for what
21+ ships in Python vs TypeScript.
2022The hard-stop is contract-based: gate each call through the guard — adapters do
2123it for LLM calls; for paid tools, [ ` reserve_tool() ` / ` settle_tool() ` ] ( #tool-spend-under-the-same-ceiling )
2224block * before* the call runs (` record_tool() ` alone meters a call after the
@@ -552,6 +554,125 @@ The middleware `check()`s before each call (throwing `BudgetExceeded` to halt th
552554run) and ` record() ` s priced usage after — same semantics as the Python guard. See
553555[ ` js/README.md ` ] ( js/README.md ) .
554556
557+ ## Voice adapters (STT → LLM → TTS)
558+
559+ A voice pipeline has no single call site to wrap: the LLM sits inside a running
560+ session and turns fire continuously for the life of a call. So instead of a
561+ function wrapper, these adapters enforce ** per turn** — reserve before the LLM
562+ call (so a turn is blocked * before* its TTS/audio spend piles on top of a call
563+ that would already cross the ceiling), settle on the real usage the pipeline
564+ reports, and release a turn that ends without ever reporting usage (an
565+ interrupted turn) so the reservation never leaks against the ceiling. The token
566+ cost map is LLM-only, so STT/TTS spend is metered only when you supply per-unit
567+ prices. Both voice adapters are ** Python-only** today.
568+
569+ ### Pipecat (voice)
570+
571+ ``` bash
572+ pip install floe-guard[pipecat]
573+ ```
574+
575+ Drop a ` FloeBudgetGuardProcessor ` into the pipeline directly after the LLM
576+ service. It reserves on each turn's ` LLMFullResponseStartFrame ` and settles from
577+ the ` LLMUsageMetricsData ` Pipecat emits — so the pipeline's ` PipelineTask ` must
578+ be created with ` enable_metrics=True, enable_usage_metrics=True ` .
579+
580+ ``` python
581+ from pipecat.pipeline.pipeline import Pipeline
582+ from pipecat.pipeline.task import PipelineTask, PipelineParams
583+ from floe_guard import BudgetGuard
584+ from floe_guard.integrations.pipecat import FloeBudgetGuardProcessor
585+
586+ guard = BudgetGuard(limit_usd = 1.00 )
587+
588+ pipeline = Pipeline([
589+ transport.input(),
590+ stt,
591+ context_aggregator.user(),
592+ llm,
593+ FloeBudgetGuardProcessor(guard, model = " gpt-4o" ), # meters AND hard-stops
594+ tts,
595+ transport.output(),
596+ context_aggregator.assistant(),
597+ ])
598+ task = PipelineTask(
599+ pipeline,
600+ params = PipelineParams(enable_metrics = True , enable_usage_metrics = True ),
601+ )
602+ ```
603+
604+ By default a blocked turn pushes a fatal ` ErrorFrame ` that terminates the
605+ pipeline — the hard-stop every other adapter gives you. Pass an
606+ ` on_budget_exceeded ` async callback to speak a graceful "wrapping up" line first
607+ instead of cutting the call dead.
608+
609+ ### LiveKit (voice)
610+
611+ ``` bash
612+ pip install floe-guard[livekit]
613+ ```
614+
615+ ` LiveKitBudgetGuard.attach(session, agent) ` wires the reserve-before /
616+ settle-after contract onto a LiveKit ` AgentSession ` : it reserves in the agent's
617+ ` llm_node ` and settles on the session's ` metrics_collected ` ` LLMMetrics ` .
618+ LiveKit's ` LLMMetrics ` doesn't report the served model, so cost settles against
619+ the ` model ` you pass here.
620+
621+ ``` python
622+ from livekit.agents import AgentSession
623+ from floe_guard import BudgetGuard, ManualPrice
624+ from floe_guard.integrations.livekit import LiveKitBudgetGuard
625+
626+ guard = BudgetGuard(
627+ limit_usd = 1.00 ,
628+ price_overrides = {" gemini-2.0-flash" : ManualPrice(0.30e-6 , 2.50e-6 )},
629+ )
630+ budget = LiveKitBudgetGuard(guard, model = " gemini-2.0-flash" )
631+
632+ session = AgentSession(... )
633+ budget.attach(session, agent) # wire reserve / settle / release
634+ await session.start(agent = agent, room = ctx.room)
635+ ```
636+
637+ Pass ` stt_usd_per_second ` / ` tts_usd_per_1k_chars ` to also meter STT/TTS spend
638+ (often a voice agent's larger bill) via ` record_tool ` , and an ` on_budget_exceeded `
639+ async callback to speak a wrap-up line before a turn ends. See
640+ [ ` examples/voice_turn_budget.py ` ] ( examples/voice_turn_budget.py ) .
641+
642+ ## Adapter matrix
643+
644+ | Adapter | Python | TypeScript |
645+ | ---| ---| ---|
646+ | OpenAI | ✅ | via [ Vercel AI SDK] ( #vercel-ai-sdk ) |
647+ | Anthropic | ✅ | via [ Vercel AI SDK] ( #vercel-ai-sdk ) |
648+ | Google Gemini | ✅ | via [ Vercel AI SDK] ( #vercel-ai-sdk ) |
649+ | LangChain | ✅ | — |
650+ | LangGraph | ✅ | — |
651+ | CrewAI | ✅ | — |
652+ | LiteLLM | ✅ | — |
653+ | Vercel AI SDK | — | ✅ |
654+ | ** Pipecat** (voice) | ✅ | — |
655+ | ** LiveKit** (voice) | ✅ | — |
656+
657+ The TypeScript package is a single Vercel AI SDK middleware that guards any model
658+ the AI SDK wraps (OpenAI, Anthropic, Gemini, …) — it is not a per-framework
659+ adapter set like the Python package.
660+
661+ ### TypeScript voice parity
662+
663+ ** There is no TypeScript voice adapter today.** The JS package
664+ ([ ` js/ ` ] ( js/ ) ) ships one surface — the Vercel AI SDK middleware — and it is
665+ ** text-only** : it guards a wrapped ` LanguageModel ` , not a running STT → LLM → TTS
666+ session. A voice/telephony builder on a Node stack should either drive the LLM
667+ turn through the Vercel AI middleware and meter STT/TTS spend by hand via
668+ ` recordTool ` , or run the LLM leg through the ** hosted [ Floe proxy] ( #upgrade-to-hosted-floe ) **
669+ (un-bypassable, cross-vendor) — or use the Python Pipecat / LiveKit adapters
670+ above, which enforce the whole turn. A native TS Pipecat/LiveKit adapter is ** not
671+ yet available** ; there is no committed roadmap for one.
672+
673+ For wiring floe-guard into an existing voice pipeline, see the Floe docs:
674+ ** [ Add Floe to your existing pipeline] ( https://floe-labs.gitbook.io/docs/getting-started/integrate-existing-pipeline ) ** .
675+
555676## Honest about what this is
556677
557678floe-guard is a ** local, estimate-based** guardrail. It prices tokens from a
0 commit comments