跳到主要内容

用 CloudBase 云函数代理 Deepgram 语音转文字

一句话定义:在 CloudBase 云函数里用 @deepgram/sdk 调 Deepgram 的 nova-3 模型,从云存储里拉一段音频做 STT,拿到带 punctuation/diarization/timestamp 的转写结果,直接写进数据库。

预计耗时:30 分钟 | 难度:进阶

适用场景

  • 适用:有会议录音/语音笔记/客服通话要批量转文字,自己不想维护 Whisper 推理机器
  • 适用:需要"谁在什么时间说了什么"——Deepgram 的 diarize + utterances 直接给说话人编号 + 句级时间戳,自建 Whisper 拼这套要写一堆后处理
  • 适用:中英混说也能跑——nova-3 是 Deepgram 当前 SOTA,中英文都覆盖
  • 不适用:实时低延迟场景(直播字幕/语音助手),那种走 WebSocket 流式,本文是批量接口(prerecorded);流式做法本文末尾会给最小代码,但部署在云函数里不划算,推荐走 Web 云函数或直连客户端
  • 不适用:转写量极大(每天上万小时),那时候 Deepgram 单价 × 量级会很疼,值得对比一下自建 Whisper Large v3 的 GPU 成本

环境要求

依赖版本
Node.js(云函数运行时)≥ 18
@cloudbase/node-sdklatest
@deepgram/sdk^4.x(本文按 v4 API 写)
云函数类型普通事件函数即可;批量转写不需要长连接
公网出口云函数默认能访问公网,Deepgram API 走 api.deepgram.com,境内可达

需要准备:

  • Deepgram 账号 + 一个 API key(注册即送 200 美元额度,够练手)
  • 一个 CloudBase 环境,里面有云存储 + 数据库
  • 一个测试用音频文件,中文/英文都行,建议先用 30 秒以内的小样本

第一步:申请 Deepgram key + 在 CloudBase 配置环境变量

  1. 登录 Deepgram Console,进入 API Keys,点 Create a New API Key,Scope 选 Member(只需要调用 transcription)
  2. 复制生成的 key,只显示一次,丢了只能重新建
  3. 打开 CloudBase 控制台 → 你的环境 → 云函数,先不创建函数,记下环境 ID(后面 tcb fn deploy -e 要用)
  4. 后面创建完函数,在「函数配置 → 环境变量」里加:
    • DEEPGRAM_API_KEY:刚才那串
    • TCB_ENV:你的环境 ID(代码里 tcb.init({ env }) 会用)

为什么 key 不直接写代码里?除了不能进 git 这条铁律,云函数的环境变量改完不需要重新打包,改 key/换环境只要在控制台改一下重启实例,比改代码快。

第二步:把音频上传到云存储

最简单的两条路:

A. 控制台手动上传:进入 CloudBase 控制台 → 云存储 → 上传文件,选一个 .mp3.wav,记下生成的 fileID,形如 cloud://your-env.xxx/uploads/audio.mp3

B. 前端走 SDK 上传:已经有上传链路的话,参考 add-file-upload-wechat-miniprogram,小程序/Web 端用 app.uploadFile 上传后把 fileID 写进数据库,云函数从数据库取 fileID 即可。

Deepgram 支持的格式很广:mp3/wav/m4a/flac/ogg/webm/mp4 等,基本上 ffmpeg 能解的都能传。单文件 batch 上限约 2GB,真有大文件再考虑分片。

第三步:写云函数(下载音频 → 调 Deepgram → 存数据库)

新建一个函数目录:

mkdir transcribe-audio && cd transcribe-audio
npm init -y
npm install --save @cloudbase/node-sdk @deepgram/sdk

index.js:

const tcb = require("@cloudbase/node-sdk");
const { createClient } = require("@deepgram/sdk");

const app = tcb.init({ env: process.env.TCB_ENV });
const db = app.database();
const deepgram = createClient(process.env.DEEPGRAM_API_KEY);

// event: { fileID: "cloud://...", language?: "zh-CN" | "en", recordId?: string }
exports.main = async (event) => {
const { fileID, language = "zh-CN", recordId } = event;
if (!fileID) {
return { ok: false, error: "missing_fileID" };
}

// 1. 从云存储下载音频到 buffer
let audioBuffer;
try {
const downloadResult = await app.downloadFile({ fileID });
audioBuffer = downloadResult.fileContent; // Buffer
} catch (err) {
console.error("download failed", err);
return { ok: false, error: "download_failed", message: err.message };
}

// 2. 调 Deepgram 批量转写
let response;
try {
response = await deepgram.listen.v1.media.transcribeFile(audioBuffer, {
model: "nova-3",
smart_format: true, // 自动加标点 + 数字格式化
punctuate: true,
diarize: true, // 说话人分离,结果在 utterances[i].speaker
utterances: true, // 句级切分 + 时间戳
language, // "zh-CN" / "en" / "auto" 等
});
} catch (err) {
// Deepgram SDK 抛 DeepgramError,带 statusCode/body
console.error("deepgram failed", err);
return {
ok: false,
error: "deepgram_failed",
statusCode: err.statusCode,
message: err.message,
};
}

const transcript =
response?.result?.results?.channels?.[0]?.alternatives?.[0]?.transcript || "";
const utterances = response?.result?.results?.utterances || [];

// 3. 写数据库;有 recordId 就更新,否则新建
const payload = {
fileID,
language,
transcript,
utterances, // [{ start, end, speaker, transcript, confidence }]
durationSec: response?.result?.metadata?.duration,
model: "nova-3",
updatedAt: new Date(),
};

if (recordId) {
await db.collection("transcripts").doc(recordId).update(payload);
return { ok: true, recordId, transcript };
} else {
const { id } = await db.collection("transcripts").add({
...payload,
createdAt: new Date(),
});
return { ok: true, recordId: id, transcript };
}
};

几个容易踩的点:

  • app.downloadFile 返回 { fileContent: Buffer };直接把 buffer 喂给 transcribeFile 即可,不需要落本地磁盘。云函数 /tmp 也能写,但走内存更快
  • 中文音频必须传 language: "zh-CN",不传会按英文识别,出来一堆音译乱码(nova-3 默认 en)
  • smart_format: true 已经包含 punctuate,但显式写一份不冲突,后期换模型万一行为变了也清楚
  • 真要"谁在说"的颗粒度,只看 transcript 不够,得用 utterances 数组,里面每条带 speaker(整数,从 0 开始) + start/end(秒)
  • response.result 这一层是 SDK v4 的封装,直接拿 response.results 会拿到 undefined,这是 v3 → v4 升级最常见的坑

package.jsondependencies 应当类似:

{
"name": "transcribe-audio",
"main": "index.js",
"dependencies": {
"@cloudbase/node-sdk": "^3.0.0",
"@deepgram/sdk": "^4.0.0"
}
}

第四步:部署 + 调用

部署:

tcb login
tcb fn deploy transcribe-audio -e your-env-id

部署完到控制台:

  1. 「函数配置 → 环境变量」:加 DEEPGRAM_API_KEYTCB_ENV
  2. 「函数配置 → 执行超时时间」:默认 3 秒太短,改到 60 秒(批量转写 1 分钟音频的端到端耗时大约 5-10 秒,留余量给下载 + 数据库写入)
  3. 「函数配置 → 内存」:512MB 够用;音频 buffer 占内存,如果文件经常超过 100MB,直接拉到 1024MB

本地用 tcb 调一次:

tcb fn invoke transcribe-audio -e your-env-id \
--params '{"fileID":"cloud://your-env.xxx/uploads/audio.mp3","language":"zh-CN"}'

预期返回:

{
"ok": true,
"recordId": "xxxx",
"transcript": "大家好,这是一段测试音频..."
}

运行验证

  1. 准备一段 30 秒以内、内容已知的中文音频(自己念一段绕口令最直观),mp3 即可
  2. 上传到云存储,记下 fileID
  3. 用上面的 tcb fn invoke 调一次,核对返回里 transcript 字段是不是和你念的一致
  4. 进数据库 transcripts 集合,核对:
    • transcript 全文文本
    • utterances 是数组,每条有 start/end/speaker/transcript
    • durationSec 大致等于音频时长(±1 秒)
  5. 多人对话音频再跑一次,确认 utterances[*].speaker 至少出现 0 和 1 两个不同的整数

如果想前端实时看到结果,在数据库前端做一次 add-realtime-notifications-database-watch,watch 这条 record 即可,函数写完数据库前端瞬间能拿到。

常见错误

错误信息原因修复
401 UnauthorizedDEEPGRAM_API_KEY 没配/写错控制台环境变量里重新粘一遍,确认没多空格;改完重启实例(随便重新部署一次或在控制台手动重启)
Deepgram 返回 Audio decode failed上传的不是音频,或者格式损坏ffprobe 先验下文件;webm 浏览器录音常见编码问题,转一道 ffmpeg -i in.webm out.mp3
中文音频转出全英文乱码(类似 "Da jia hao")没传 language: "zh-CN",模型默认 en 按音译做了显式传 language: "zh-CN";混合中英文可以试 language: "auto",但准确率不如指定语言
函数 3 秒超时,日志显示 deepgram 调用刚发出云函数默认超时 3 秒,1 分钟音频转写要 5-10 秒控制台 → 函数配置 → 超时时间,调到 60 秒以上;长音频( >5 分钟)调到 300 秒并加大内存
Cannot read property 'channels' of undefinedSDK v3 → v4 写法变了,response.results 变成 response.result.results按本文 response?.result?.results?.channels?.[0] 取;或者升级前看一遍 Deepgram SDK CHANGELOG
音频文件几十 MB,函数报 Request body too large事件型云函数请求体上限较小,直接把 buffer 当 event 传过来撑爆了始终走 fileID 让函数自己去云存储下载,不要把音频字节塞进 event
转出来的 transcript 准确率明显低音频码率过低 / 背景噪音 / 多人重叠录音建议 ≥ 16kHz 单声道;混叠场景考虑前置降噪,Deepgram 也有 redact/filler_words 等参数可以调

完整错误码可在 Deepgram 文档里查,云函数自身的错误码看 https://docs.cloudbase.net/error-code/

流式实时转写(可选)

需要边说边出字(直播字幕/语音助手),把 transcribeFile 换成 WebSocket 连接。普通事件函数不能持有长连接,要用 Web 云函数(HTTP 触发) 或者干脆让客户端直连 Deepgram(用临时 token):

const connection = await deepgram.listen.v1.connect({
model: "nova-3",
interim_results: true, // 中间结果,边说边出
punctuate: true,
language: "zh-CN",
});

connection.on("open", () => {
// 客户端开始往 connection 推 PCM/Opus 音频帧
});

connection.on("message", (data) => {
if (data.type === "Results") {
const partial = data.channel.alternatives[0].transcript;
// 推给前端
}
});

connection.connect();

Deepgram 流式延迟亚 300 毫秒,但部署形态、鉴权方式都和批量不一样,有需要单独再起一篇,本文不展开。

相关文档