用 Cloudflare Whisper 為 Chatbot 添加 $0 成本多語言語音識別
如果你正在構建一個 Chatbot,想要添加語音輸入,又不想為 Google Speech-to-Text、AWS Transcribe 或 OpenAI Whisper API 付費,那麼你已經擁有了一切所需——Cloudflare 免費套餐裡就有。
這篇文章將詳細介紹我如何把 Cloudflare Workers AI Whisper large-v3-turbo 整合到我的作品集網站 Chatbot 中,以完全零成本支援 99 種語言的語音識別。同時,我也會分享遇到的兩個生產 Bug,以及精確的修復方法。
為什麼選擇 Cloudflare Workers AI?
Cloudflare 透過 Workers AI 提供語音識別能力,運行在 Cloudflare 全球邊緣網路上。免費套餐包含:
- Workers AI 推理:免費,額度參見官方限制文件,足夠個人專案和小型應用使用
- 零冷啟動:運行在邊緣節點,距離使用者最近
- 無需管理 API 金鑰:透過
env.AI直接綁定到 Worker - 99 種語言支援:包括中文、日文、韓文、法文、西班牙文、阿拉伯文等
最大的優勢:你不需要託管模型、管理 GPU 實例,也不用處理第三方 API 的速率限制。開箱即用。
Whisper Base vs Whisper Large v3 Turbo:深度對比
Cloudflare Workers AI 提供兩個 Whisper 模型。選對模型至關重要。
@cf/openai/whisper(基礎版)
這是 Whisper base 原版模型(約 3900 萬參數)。
優點:
- 推理速度快——適合對延遲敏感的場景
- 每次請求資源消耗低
- 在 Cloudflare 上經過充分驗證,非常穩定
缺點:
- 詞錯率(WER)較高,在非英語語言上表現一般
- 對口音、專業詞彙、混合語言輸入識別較弱
- audio 參數格式:
number[](將 Uint8Array 展開成普通 JS 陣列)
適合場景: 高並發、速度優先的應用,或純英語使用場景。
@cf/openai/whisper-large-v3-turbo(Turbo 版)
這是 Whisper large-v3 的精簡蒸餾版(約 8.09 億參數),在保留大部分精度的同時,推理速度比 large-v3 快約 2 倍。
優點:
- 與基礎版相比詞錯率降低約 50%——中日韓等語言效果顯著提升
- 多語言精度優秀,對各種口音表現良好
- 支援可選參數:
language、task(transcribe/translate)、vad_filter、beam_size - 返回豐富輸出:
text、word_count、帶時間碼的segments(WebVTT 格式)
缺點:
- 比基礎版重——單次請求延遲略高
- audio 參數格式:Base64 字串(不是數字陣列——這導致了我的第一個 Bug)
- 無法穩定解碼
webm/opus容器(這導致了我的第二個 Bug)
適合場景: 多語言 Chatbot、語音助理、無障礙工具、對精度要求高的轉錄流水線。
對比總結
| | @cf/openai/whisper | @cf/openai/whisper-large-v3-turbo |
|---|---|---|
| 參數量 | 約 3900 萬 | 約 8.09 億 |
| 支援語言 | 99 種 | 99 種 |
| 識別精度 | 良好 | 優秀 |
| 推理速度 | 最快 | 快(比 large-v3 快約 2×) |
| audio 參數格式 | number[] | Base64 字串 |
| 支援容器格式 | webm、wav、mp3 | wav、mp3(webm 不穩定) |
| 額外輸出欄位 | 僅 text | text、word_count、segments |
| Cloudflare 費用 | 免費套餐 | 免費套餐 |
架構設計
完整的資料處理流程:
瀏覽器麥克風
↓
MediaRecorder(錄製 webm/opus——瀏覽器預設格式)
↓ 【客戶端轉碼】
AudioContext.decodeAudioData() → 解碼 webm/opus
↓
encodeWAV() → 16-bit PCM WAV(單聲道,原始取樣率)
↓ HTTP POST multipart/form-data
/api/transcribe(Next.js Edge Route,運行在 Cloudflare Pages)
↓
getRequestContext().env.AI(Cloudflare AI 綁定)
↓
btoa() → 分塊 Base64 編碼(每批 8192 位元組)
↓
env.AI.run('@cf/openai/whisper-large-v3-turbo', { audio: base64 })
↓
{ text: "識別結果" } → 回填 Chatbot 輸入框
第一步:在 Wrangler 中綁定 AI
在 wrangler.jsonc(或 wrangler.toml)中添加 AI 綁定:
{
"ai": {
"binding": "AI"
}
}
在 env.d.ts 中宣告類型:
interface CloudflareEnv {
AI: any;
}
執行 npx wrangler types 可從即時綁定產生精準類型定義。
第二步:伺服器端轉錄路由
建立 src/app/api/transcribe/route.ts。兩個關鍵要求:
export const runtime = 'edge'——必須宣告,否則getRequestContext()在 Cloudflare Pages 上無法正常運作- 將音訊 Base64 編碼——turbo 模型要求此格式;Edge Runtime 沒有 Node.js 的
Buffer
import { NextRequest, NextResponse } from 'next/server';
import { getRequestContext } from '@cloudflare/next-on-pages';
export const runtime = 'edge';
export async function POST(req: NextRequest) {
try {
const formData = await req.formData();
const file = formData.get('file') as File;
if (!file) return NextResponse.json({ error: 'No audio file provided' }, { status: 400 });
const { env } = getRequestContext();
if (!env.AI) return NextResponse.json({ error: 'Cloudflare AI binding is missing' }, { status: 500 });
const arrayBuffer = await file.arrayBuffer();
const audioBytes = new Uint8Array(arrayBuffer);
// Edge Runtime 沒有 Buffer——分塊 Base64 編碼,避免大陣列堆疊溢位
let binary = '';
for (let i = 0; i < audioBytes.length; i += 8192) {
binary += String.fromCharCode(...audioBytes.subarray(i, i + 8192));
}
const audioBase64 = btoa(binary);
const response = await env.AI.run('@cf/openai/whisper-large-v3-turbo', { audio: audioBase64 });
if (!response || !response.text) throw new Error('No transcription returned from AI model');
return NextResponse.json({ text: response.text });
} catch (err: unknown) {
console.error('Transcription API Error:', err);
return NextResponse.json({ error: err instanceof Error ? err.message : String(err) }, { status: 500 });
}
}
我在生產中遇到的兩個 Bug
Bug 1:錯誤碼 5006 — 類型不匹配
從 @cf/openai/whisper 切換到 whisper-large-v3-turbo 後,API 返回錯誤 5006。
根本原因: 我沿用了舊格式 audio: [...audioBytes](數字陣列),這是基礎模型的格式。turbo 模型要求的是 Base64 字串。兩者的輸入 schema 完全不同,但文件沒有明確說明。
解決方案: 在伺服器端用 btoa() 分塊轉換,將 Uint8Array 編碼為 Base64 字串後傳入。
Bug 2:錯誤碼 3030 — 音訊檔案解碼失敗
修復 Base64 格式問題後,又遇到錯誤 3030。
根本原因: 瀏覽器以 webm/opus 格式錄製。雖然 Base64 編碼已正確,但 turbo 模型無法解碼 webm/opus 容器。Cloudflare Whisper turbo 只穩定支援 WAV 和 MP3。
解決方案: 在瀏覽器端用 AudioContext.decodeAudioData() 解碼 webm/opus,再用自訂 encodeWAV() 重新編碼為 16-bit PCM WAV 上傳。WAV 是 Whisper 最通用的輸入格式。
注意事項與技巧
-
export const runtime = 'edge'是必須的。 沒有它,getRequestContext()在 Cloudflare Pages 上不會返回任何內容。 -
Edge Runtime 沒有
Buffer。 Base64 編碼不能用Buffer.from(bytes).toString('base64'),要用btoa()分塊處理,避免大檔案時呼叫堆疊溢位。 -
turbo 和 base 模型的
audio欄位類型不同。 文件中沒有明確說明這一差異——我是透過報錯和實驗發現的。 -
必須關閉
AudioContext。 瀏覽器對同時存在的AudioContext實例數量有上限,解碼後必須await audioCtx.close()。 -
防範空錄音。 如果使用者誤觸麥克風即觸即鬆,
decodeAudioData會對空 blob 拋出例外。檢查blob.size < 1000並提前返回。 -
Base64 會增加約 33% 體積。 10 秒語音錄音轉 Base64 後約 215KB,遠在 Cloudflare 限制內。
-
iOS 需要使用者手勢才能建立
AudioContext。 使用onPointerDown(而非計時器或useEffect)滿足此要求。
最終效果
實現這套流程後,我的 Chatbot 支援:
- 長按說話:按住麥克風按鈕說話,放手後開始識別
- 點擊切換:點一下開始錄音,說完再點一下停止識別
- 99 種語言:中文、日文、韓文、英文等識別效果優秀
- 零成本:完全在 Cloudflare 免費套餐內
- 無第三方 API 金鑰:一切都在 Cloudflare 邊緣運行
完整實現已上線 elvisli.ca。

