Cloudflare Workers AI Voice Agents: A Builder's Guide
Learn how to wire Cloudflare Workers AI voice agent LLMs into a real-time pipeline, with streaming, tool calls, and cost math for production use.
Most voice agent tutorials assume you'll call OpenAI or Anthropic for the LLM turn and treat that as a fixed cost of doing business. That's a reasonable default, but it's not the only option, and for a chunk of voice traffic it isn't even the cheapest or fastest one. Cloudflare now ships a first-party path for running the LLM, the speech-to-text, and the text-to-speech inside the same edge network that terminates the call, and it's worth understanding before you wire up a third API key.
This is a build guide for developers who already have (or are about to build) a voice pipeline and want to know exactly where Workers AI fits, what it costs in practice, and where it falls short of a frontier hosted model. We'll wire up the @cloudflare/voice package, stream a Workers AI model into text-to-speech, add function calling for appointment booking, and cover the production gotchas that don't show up in the quickstart.
Why put the LLM on Workers AI instead of calling out to OpenAI or Anthropic
A typical voice agent built on Vapi or a custom WebSocket server makes three network hops per turn: audio to a speech-to-text provider, transcript to an LLM provider, and response text to a text-to-speech provider. Each hop is a round trip to wherever that vendor's nearest region happens to be, and none of them are guaranteed to be close to the caller or to each other.
The latency argument
Cloudflare's pitch is that if STT, LLM, and TTS all run as Workers AI bindings inside the same Worker or Durable Object, the network hop disappears — the model calls become internal requests within Cloudflare's data center rather than calls to a third-party API. Vapi's own orchestration layer targets a 10-second ceiling on first response, and ElevenLabs markets 300ms streaming time-to-first-byte on its TTS; keeping every hop in one network is how you get meaningfully under those numbers instead of just meeting them.
The cost argument
Workers AI bills in Neurons at $0.011 per 1,000 Neurons, with 10,000 Neurons a day free on every account. Llama 3.3 70B (fp8-fast) runs at roughly 26,668 Neurons per million input tokens and 204,805 Neurons per million output tokens — call it $0.29 in and $2.25 out per million tokens. A typical 200-token voice turn (transcript in, spoken response out) costs a fraction of a cent, and you're not paying a second vendor's margin on top of the same tokens.
The honest tradeoff
None of this means Workers AI models match GPT-5 or Claude Opus on reasoning quality. Llama 3.3 70B and the open-weight models Cloudflare hosts are good at short, scripted voice turns — intent classification, slot filling, scheduling logic — and noticeably weaker on multi-step reasoning or nuanced objection handling. Treat this as a latency and cost optimization for the 80% of turns that are simple, not a wholesale replacement for a frontier model on every call.
The @cloudflare/voice building blocks
Cloudflare's @cloudflare/voice package (currently in beta) gives you a server-side mixin called withVoice that wraps the Workers Agents SDK's Agent class — itself backed by a Durable Object, so each caller gets a stateful, single-threaded object that persists the conversation in SQLite and survives reconnects. Audio streams in and out over plain WebSocket frames; there's no SFU or meeting infrastructure required, which is a real simplification if you've previously stood up WebRTC infrastructure by hand.
Speech-to-text and text-to-speech, built in
Two Workers AI-backed STT classes ship out of the box: WorkersAIFluxSTT (wrapping @cf/deepgram/flux, recommended for full voice agents because it handles continuous turn detection) and WorkersAINova3STT (wrapping @cf/deepgram/nova-3, recommended for transcription-only use). TTS ships as WorkersAITTS, defaulting to @cf/deepgram/aura-1. All three require nothing beyond the Workers AI binding already on your account — no separate Deepgram API key.
Wiring the binding and the agent class
The binding itself is a couple of lines in your Wrangler configuration, then the agent class declares its transcriber, TTS, and turn handler:
{
"name": "voice-agent-worker",
"main": "src/index.ts",
"compatibility_date": "2026-06-01",
"ai": {
"binding": "AI"
},
"durable_objects": {
"bindings": [
{ "name": "VOICE_AGENT", "class_name": "AppointmentVoiceAgent" }
]
},
"limits": {
"cpu_ms": 50000
}
}
import { Agent } from "agents";
import { withVoice, WorkersAIFluxSTT, WorkersAITTS } from "@cloudflare/voice";
const VoiceAgent = withVoice(Agent);
export class AppointmentVoiceAgent extends VoiceAgent<Env> {
transcriber = new WorkersAIFluxSTT(this.env.AI, {
eotThreshold: 0.8,
keyterms: ["reschedule", "cancel", "Bend", "Central Oregon"],
});
tts = new WorkersAITTS(this.env.AI, {
model: "@cf/deepgram/aura-1",
speaker: "asteria",
});
async onTurn(transcript: string) {
const stream = await this.env.AI.run(
"@cf/meta/llama-3.3-70b-instruct-fp8-fast",
{
messages: [
{
role: "system",
content:
"You are a scheduling assistant for a home services company. Keep replies under two sentences.",
},
...this.getConversationHistory(),
{ role: "user", content: transcript },
],
stream: true,
},
);
return stream;
}
}
When onTurn returns a stream, @cloudflare/voice chunks the output into sentences and starts synthesis on each sentence as soon as it's complete — the caller starts hearing the first clause while the model is still generating the rest of the reply. That sentence-level pipelining is most of the perceived latency win, more so than any single model's raw speed.
Choosing an LLM model for a voice latency budget
Not every Workers AI model belongs in a voice turn. Llama 3.3 70B instruct (fp8-fast) is the reasonable default: strong enough for scheduling and FAQ-style dialogue, priced around $0.29/$2.25 per million tokens in/out. If your prompts are simple and you want to shave another 100-200ms off generation, Llama 3.1 8B instruct (fp8-fast) runs at roughly $0.045/$0.38 per million tokens and is noticeably faster to first token, at the cost of weaker instruction-following on longer system prompts. gpt-oss-20b sits in between on both price and quality.
Avoid the larger dense reasoning models — qwq-32b, deepseek-r1-distill-qwen-32b — for anything in the live voice path. They're priced and built for reasoning traces, not sub-second conversational turns, and their higher output-token cost ($1.00 and $4.88 per million output tokens respectively) compounds fast if the model rambles before answering.
Adding function calling for appointment booking
A voice agent that can only talk isn't useful; it needs to write to a calendar or a CRM. Workers AI's chat models support OpenAI-style tool definitions, so the same env.AI.run call from onTurn can pass a tools array and check for a tool_calls response before falling back to plain text:
const tools = [
{
type: "function",
function: {
name: "book_appointment",
description: "Book a service appointment on the calendar",
parameters: {
type: "object",
properties: {
date: { type: "string", description: "ISO 8601 date" },
time: { type: "string", description: "24-hour local time, HH:MM" },
service: { type: "string" },
},
required: ["date", "time", "service"],
},
},
},
];
async function runTurn(env: Env, messages: any[]) {
const result = await env.AI.run(
"@cf/meta/llama-3.3-70b-instruct-fp8-fast",
{ messages, tools },
);
const toolCall = result.tool_calls?.[0];
if (toolCall?.name === "book_appointment") {
const booking = await createCalendarEvent(env, toolCall.arguments);
return `Booked for ${booking.date} at ${booking.time}. Anything else?`;
}
return result.response;
}
Note that this non-streaming env.AI.run call waits for the full response before you know whether it's a tool call, which costs you the sentence-level TTS pipelining described earlier. In practice, most production builds run a fast intent check first (a cheap classification pass or even a lightweight regex on the transcript) and only fall through to the full tool-calling round trip when it looks like a booking or cancellation request.
Controlling cost with AI Gateway spend limits and fallback routing
Neuron pricing is predictable per call but not per caller — a chatty caller or a scripted bot spamming your inbound number can run up a bill fast. Routing the AI binding through AI Gateway gives you spend limits scoped by model, provider, or custom metadata (caller ID, tenant, campaign), and — more usefully for a voice agent that occasionally needs real reasoning — dynamic routing with a fallback model:
{
"route": "voice-agent-primary",
"primary": { "provider": "workers-ai", "model": "@cf/meta/llama-3.3-70b-instruct-fp8-fast" },
"fallback": { "provider": "anthropic", "model": "claude-opus-4-8" },
"spendLimit": { "amount": 50, "window": "1d", "scope": "gateway" }
}
With this in place, routine turns stay on the cheap edge model, and once you set a rule that escalates on low-confidence transcripts or a caller-requested "let me talk to someone who actually understands," the same binding can hand off to Anthropic's Claude without you touching the agent code — the routing lives in the gateway.
Production gotchas
A few things the quickstart docs don't emphasize enough:
CPU time is not wall-clock time, but it still bites. Workers on the paid plan default to a 30-second CPU time limit per request (configurable up to 5 minutes), and waiting on the model response doesn't count against it — but tokenizing, parsing tool-call JSON, and any local post-processing you do on each streamed chunk does. A voice agent that does heavy client-side formatting on every token can hit exceededCpu errors under load even though the model itself responded fine.
Open-weight model quality varies turn to turn more than a frontier model does. Llama 3.3 70B will occasionally ignore a system prompt constraint that Claude or GPT-5 would reliably respect — usually on edge-case phrasing you didn't test. Build an eval set of real call transcripts before launch, not just happy-path scripts.
WebSocket message size caps changed recently. Cloudflare raised the Workers WebSocket message limit from 1 MiB to 32 MiB in late 2025, which matters if you're buffering raw audio frames rather than streaming them — older guides and sample code may still assume the 1 MiB ceiling and chunk more aggressively than you now need to.
Subrequest limits apply per invocation. Paid accounts get 10,000 subrequests per invocation, which sounds unlimited until a single turn fans out to a transcription call, an LLM call, a tool-calling round trip to your CRM, and a TTS call — each retry on any of those counts against the same budget. Log subrequest counts in staging before you assume headroom.
Free tier Neuron limits will stall a demo, not just a launch. The 10,000 Neurons/day free allocation sounds generous until you realize a single Llama 3.3 70B turn with a long system prompt and conversation history can burn several thousand Neurons; a demo day with a handful of test calls can exhaust it before lunch.
When NOT to build this yourself
Running the LLM on Workers AI makes sense when your voice agent handles a narrow, well-scoped set of turns — scheduling, FAQ, intake — and you're comfortable owning a Durable Object, an eval pipeline, and the prompt maintenance that comes with an open-weight model. It does not make sense in a few common situations.
If the calls require genuine judgment — insurance claims triage, legal intake nuance, a caller who's upset and needs de-escalation — the reasoning gap between Llama 3.3 70B and a frontier model is the difference between a resolved call and an escalated complaint. Route those to Claude or GPT-5 through the AI Gateway fallback shown above, or don't build the edge-LLM path at all for that use case.
If you don't already have a reason to be inside the Cloudflare stack — no existing Workers deployment, no Durable Objects experience on the team — the setup cost of learning Durable Object lifecycle, WebSocket hibernation, and Workers AI's OpenAI-compatible-but-not-identical tool-calling format will likely cost you more engineering time than the savings are worth for a single agent. A hosted platform like Vapi in front of any LLM provider gets you to production faster, even if the per-minute cost is higher. See our Vapi-on-Cloudflare-Workers setup guide for that middle path — Vapi's orchestration with Workers as the backend logic layer, without owning the raw audio pipeline.
And if you'd rather skip the build entirely, WildRun AI deploys production voice agents on this exact architecture for home services, dental, legal, and real estate teams in Bend and across Central Oregon, tuned and evaluated against real call data before they go live. Book a demo if you want the pipeline built and maintained rather than debugged at 11pm.
Architecture
Caller (PSTN/SIP or browser mic)
|
v
Cloudflare edge (WebSocket, no SFU required)
|
v
Durable Object: AppointmentVoiceAgent (@cloudflare/voice)
|-- WorkersAIFluxSTT ---> transcript
|-- onTurn(transcript)
| |
| v
| AI Gateway (spend limits, dynamic routing)
| |
| +--> Workers AI: llama-3.3-70b-instruct-fp8-fast (default)
| +--> fallback: Anthropic Claude (low-confidence / escalation)
| |
| v
| tool_calls? --> booking API / CRM write
|
|-- WorkersAITTS <--- streamed sentence chunks
|
v
Caller hears response (sentence-level pipelining)
Frequently asked questions
Can I use Claude or GPT-5 instead of a Workers AI model for the LLM turn?
Yes. Route the AI binding through Cloudflare AI Gateway and set up dynamic routing with a fallback model, or call the Anthropic or OpenAI provider directly through the gateway's unified REST API. Many production builds keep Workers AI as the default for routine turns and fall back to a frontier model for low-confidence or escalated calls.
How much does it cost to run the LLM on Workers AI for a voice agent?
Workers AI bills in Neurons at $0.011 per 1,000, with 10,000 Neurons free per day. Llama 3.3 70B instruct (fp8-fast) runs about $0.29 per million input tokens and $2.25 per million output tokens, so a typical 200-token voice turn costs a small fraction of a cent.
Does Workers AI support function calling for booking appointments?
Yes, Workers AI chat models accept OpenAI-style tool definitions in the same env.AI.run call used for the conversational turn. Check the response for tool_calls before falling back to plain text, and note that tool-calling requests are non-streaming, so they skip the sentence-level TTS pipelining a plain text turn gets.
Do I need Cloudflare Realtime or WebRTC infrastructure to build this?
No. The @cloudflare/voice package streams audio over plain WebSocket frames and runs on a Durable Object, so there is no SFU or meeting infrastructure to stand up. Cloudflare Realtime is a separate, additional option for WebRTC-based call transport, not a requirement.
What's the latency difference between Workers AI and a hosted frontier model?
Keeping STT, LLM, and TTS inside the same Cloudflare network removes the cross-vendor network hop that a typical OpenAI or Anthropic-backed pipeline pays on every turn. The tradeoff is that Llama 3.3 70B and other open-weight models on Workers AI are less consistent on complex reasoning than a frontier model.
When should I use a hosted platform like Vapi instead of building this myself?
If your team has no existing Cloudflare Workers or Durable Objects experience, the setup cost of learning the voice pipeline will likely outweigh the latency and cost savings for a single agent. A hosted orchestration platform gets you to production faster even at a higher per-minute cost.