Cloudflare Workers AI
Speech-to-text on Workers AI: Deepgram Flux with turn detection, Deepgram Nova-3 streaming, and Whisper, over the AI binding or the REST API.
Overview
Workers AI hosts speech-to-text models on Cloudflare's network. @pipedog/cloudflare has a service
for each kind:
WorkersAIFluxSTTService: Deepgram Flux (@cf/deepgram/flux), conversational STT that also detects the end of the user's turn. It replaces VAD and Smart Turn.WorkersAINova3STTService: Deepgram Nova-3 (@cf/deepgram/nova-3) over a WebSocket, with interim results. Use it with a VAD, as with Deepgram.WorkersAIWhisperSTTService: Whisper (@cf/openai/whisper-large-v3-turbo,@cf/openai/whisper,@cf/openai/whisper-tiny-en), one request per speech segment.
This package is pipedog's own: Pipecat has no Workers AI services. The Flux and Nova-3 services are the Deepgram services with a different way to open the socket, since Workers AI carries Deepgram's WebSocket protocol, so they behave and emit frames the same way.
Source
packages/cloudflare/src/flux_stt.ts, stt.ts and whisper.ts.
Workers AI models
Inputs, outputs and pricing of each model.
Voice agent fully on Cloudflare
Flux, a Workers AI LLM and Aura in a Durable Object.
Installation
bun add @pipedog/cloudflareimport { WorkersAIFluxSTTService } from "@pipedog/cloudflare/flux_stt";
import { WorkersAINova3STTService } from "@pipedog/cloudflare/stt";
import { WorkersAIWhisperSTTService } from "@pipedog/cloudflare/whisper";Prerequisites
Every Workers AI service reaches the models one of two ways:
- The
AIbinding, inside a Worker or Durable Object. Add"ai": { "binding": "AI" }towrangler.jsoncand passai: env.AI. No token is needed. The realtime models open their socket withenv.AI.run(model, input, { websocket: true }). - The REST API, anywhere: pass
accountIdandapiToken(a Cloudflare API token with the Workers AI permission). Requests go tohttps://api.cloudflare.com/client/v4/accounts/{accountId}/ai/run/{model}, and the realtime models openwss://on the same URL, with the session parameters in the query string. The examples read them fromCLOUDFLARE_ACCOUNT_IDandCLOUDFLARE_API_TOKEN.
// Anywhere else: an account ID and an API token with Workers AI access.
const fluxRest = new WorkersAIFluxSTTService({
accountId: process.env.CLOUDFLARE_ACCOUNT_ID ?? "",
apiToken: process.env.CLOUDFLARE_API_TOKEN ?? "",
});
To share one connection setup between services, build a WorkersAIClient and pass it as client:
import { WorkersAIClient } from "@pipedog/cloudflare/client";
// One client shared by several services.
const client = new WorkersAIClient({ ai: env.AI });
const fluxShared = new WorkersAIFluxSTTService({ client });
WorkersAIFluxSTTService
Configuration
Prop
Type
Settings
WorkersAIFluxSTTService.Settings is Deepgram Flux's settings class; see
Deepgram Flux settings. The ones Workers AI
documents:
| Setting | Type | Default | Description |
|---|---|---|---|
model | string | "@cf/deepgram/flux" | The Workers AI model ID. |
eotThreshold | number | null | null (Flux: 0.7) | End-of-turn confidence that ends a turn, 0.5 to 0.9. |
eagerEotThreshold | number | null | null | Enables EagerEndOfTurn and TurnResumed, 0.3 to 0.9. |
eotTimeoutMs | number | null | null (Flux: 5000) | Ends a turn this long after speech, whatever the confidence. |
keyterm | string[] | [] | Terms to boost. |
Usage
import { WorkersAIFluxSTTService } from "@pipedog/cloudflare/flux_stt";
// Inside a Worker or Durable Object: the AI binding, no token.
const flux = new WorkersAIFluxSTTService({
ai: env.AI,
settings: new WorkersAIFluxSTTService.Settings({
eotThreshold: 0.75,
keyterm: ["pipedog", "Cloudflare"],
}),
});Flux owns turn detection: use it with no VAD analyzer, and the user aggregator follows its turn
events, as with DeepgramFluxSTTService (see Turn detection).
Change settings mid-call with an STTUpdateSettingsFrame:
// Queue it on the worker, or push it from a processor upstream of the service.
const update = new STTUpdateSettingsFrame({
delta: new WorkersAIFluxSTTService.Settings({ eotThreshold: 0.85 }),
});Event handlers
The same as DeepgramFluxSTTService: on_start_of_turn, on_end_of_turn,
on_eager_end_of_turn, on_turn_resumed, on_update, plus on_connected, on_disconnected and
on_connection_error.
Notes
- The session parameters are the model's inputs (
encoding,sample_rate,eot_threshold,eager_eot_threshold,eot_timeout_ms,keyterm,mip_opt_out,tag). The model is the URL path, not amodelparameter. - Workers AI's turn messages carry an
eventand notype; they are read as Deepgram'sTurnInfo. Workers AI doesn't document Deepgram'sConnectedmessage, so an open socket counts as confirmed. - Audio is 16-bit PCM (
linear16) at the pipeline's input rate.
WorkersAINova3STTService
Configuration
Prop
Type
Settings
WorkersAINova3STTService.Settings is Deepgram's settings class (see
DeepgramSTTService settings), with model defaulting to
"@cf/deepgram/nova-3".
Usage
import { WorkersAINova3STTService } from "@pipedog/cloudflare/stt";
const nova = new WorkersAINova3STTService({
ai: env.AI,
settings: new WorkersAINova3STTService.Settings({ language: Language.EN, smartFormat: true }),
});
Notes
- Every Deepgram parameter but the model is sent as a session parameter. Workers AI documents
interim_results,endpointing,vad_events,utterance_end_ms,smart_format,punctuate,keyterm,languageand more on the model page.
WorkersAIWhisperSTTService
Configuration
Prop
Type
Settings
| Setting | Type | Default | Description |
|---|---|---|---|
model | string | "@cf/openai/whisper-large-v3-turbo" | WorkersAIWhisperModel.LARGE_V3_TURBO, .WHISPER or .TINY_EN. |
language | Language | null | null | The audio's language, sent as its ISO 639-1 code; null detects it. Turbo only. |
prompt | string | null | null | Context for the model (initial_prompt). Turbo only. |
vadFilter | boolean | null | null | Run Whisper's own VAD first (vad_filter). Turbo only. |
Usage
import { WorkersAIWhisperModel, WorkersAIWhisperSTTService } from "@pipedog/cloudflare/whisper";
const whisper = new WorkersAIWhisperSTTService({
ai: env.AI,
settings: new WorkersAIWhisperSTTService.Settings({
model: WorkersAIWhisperModel.LARGE_V3_TURBO,
language: Language.EN,
prompt: "A conversation about Cloudflare Workers.",
}),
});Notes
- A segmented service: it needs a VAD analyzer. The audio between the VAD's start and stop is sent as a WAV file, base64 for turbo and an array of bytes for the original models.
Runtime notes
| Runtime | Works | What it needs |
|---|---|---|
| Bun | ✅ yes | Nothing. |
| Node.js | ✅ yes | Nothing. |
| Deno | ✅ yes | Nothing. |
| Cloudflare Workers | ✅ yes | Nothing. |
Generated from pipecat.runtimes and pipecat.compat in packages/cloudflare/package.json: ✅ means the compat smoke test passes on that runtime.
- No dependencies beyond pipedog's own packages. HTTP goes through
fetchand WebSockets through the corewebsocketConnect(REST) or the binding's socket (Workers). - Tested against a local fake of Workers AI (both paths), not against Workers AI itself. A live test
(
packages/cloudflare/test/live_workers_ai.test.ts) runs whenCLOUDFLARE_ACCOUNT_IDandCLOUDFLARE_API_TOKENare set.
pipedog is an unofficial community TypeScript port of Pipecat. It is not affiliated with or endorsed by Daily or the Pipecat project.
Portions adapted from the Pipecat documentation, Copyright (c) 2024–2025 Daily, BSD-2-Clause (see apps/docs/LICENSE-pipecat-docs).