Cloudflare Agents 语音包演进全解析:从 @cloudflare/voice 迁移到 agents/voice 的实战指南
2026/9/18 3:48:34 网站建设 项目流程

Cloudflare Agents 语音包演进全解析:从 @cloudflare/voice 迁移到 agents/voice 的实战指南

【免费下载链接】agentsBuild and deploy AI Agents on Cloudflare项目地址: https://gitcode.com/GitHub_Trending/agents1/agents

@cloudflare/voice是 Cloudflare Agents 仓库(当前工作目录GitHub_Trending/agents1/agents)中随agentsSDK 一起发布的实时语音能力包,历经 0.0.2 到 0.5.0 的持续迭代,最终在 0.5.0 被正式标记为弃用(deprecated),语音能力整体并入主包agents,以agents/voice子路径对外提供。本文以 packages/voice/CHANGELOG.md 为骨架,逐版本梳理这一演进过程:从 0.1.0 的连续 STT 架构重构,到采样率、播放调度、转写生命周期、每轮时序指标等一系列工程细节的打磨,并给出可直接落地的迁移步骤。读完本文,你将掌握@cloudflare/voiceagents/voice之间的完整映射关系、新版连续 STT 会话模型的工作原理,以及VoiceClient/ React hooks 的关键配置项与调试手段。

一、包定位:一个"兼容层"在 monorepo 中的位置

在仓库中,@cloudflare/voice对应 packages/voice 目录,其package.json明确将自身描述为 "Compatibility package for the Agents SDK voice exports"(agentsSDK 语音导出的兼容包)。它的源码结构非常薄——四个入口文件全部是对主包的一行式再导出(re-export):

文件内容
src/voice.tsexport * from "agents/voice"
src/voice-client.tsexport * from "agents/voice/client"
src/voice-react.tsxexport * from "agents/voice/react"
src/errors.tsexport * from "agents/voice/errors"

从源码结构看,兼容包不包含任何语音逻辑实现,真正的实现在 packages/agents 中。查看其package.jsonexports字段(packages/agents/package.json)可以看到主包暴露了远比兼容包更完整的语音入口:

  • agents/voiceagents/voice/clientagents/voice/reactagents/voice/errors
  • 以及兼容包尚未覆盖的agents/voice/typesagents/voice/workers-aiagents/voice/sfuagents/voice/text
  • 甚至包括通道侧入口agents/channels/voice

这意味着agents主包才是语音能力的前沿阵地,@cloudflare/voice只是面向存量用户的一层"保鲜包装"。当前 packages/voice/package.json 的peerDependencies声明agents: ">=0.23.0 <2.0.0",这也解释了为什么语音包的每次重要改动几乎都会伴随agents最低版本要求的上调。

二、0.5.0:正式弃用与四条导入路径的迁移

0.5.0 是 CHANGELOG 中最新也是最重要的一个版本:@cloudflare/voice被弃用,所有能力并入agents。CHANGELOG 明确承诺:"All existing entry points remain compatible re-export wrappers"——所有既有入口仍作为兼容再导出包装保留。

对应的迁移映射表(同样记录在 packages/voice/README.md 与 docs/voice/index.md):

旧导入新导入
@cloudflare/voiceagents/voice
@cloudflare/voice/clientagents/voice/client
@cloudflare/voice/reactagents/voice/react
@cloudflare/voice/errorsagents/voice/errors

新项目只需安装一个依赖:

npm install agents

然后按新路径导入:

import { withVoice, WorkersAIFluxSTT, WorkersAITTS } from "agents/voice"; import { VoiceClient } from "agents/voice/client"; import { useVoiceAgent } from "agents/voice/react";

官方文档(docs/agents/voice.md)强调:导出的名称、Voice 线上协议(wire protocol)与 SQLite 表名均未改变,兼容包在整个 Agents 1.x 生命周期内都会持续维护。也就是说,迁移在绝大多数场景下只是"换 import 路径"级别的改动,运行时与类型身份完全一致。这一设计让存量用户可以低风险升级,同时让新特性只在主包中演进。

三、0.1.0:架构级重构——转向"每通话连续 STT 会话"

在 0.1.0 之前,语音管线依赖客户端发送start_of_speech/end_of_speech事件来驱动 STT 分段处理。0.1.0 做了一次破坏性 API 变更,彻底改变了语音包的架构模型,其影响贯穿后续所有版本:

新模型:转写器会话(transcriber session)在start_call时创建,并存活于整个通话周期;由模型自身负责轮次检测(turn detection),客户端不再需要为 STT 发送起止语音事件。

这一模型与 docs/agents/voice.md 中 "Continuous STT" 一节的描述完全对应:"The transcriber session is created atstart_calland lives for the entire call. All audio is fed continuously — the model handles speech boundary detection (turn detection)." 客户端收到的transcript_interim消息实时携带部分转写结果,start_of_speech/end_of_speech仅用于客户端 UI 状态(说话指示、音量电平)。

3.1 新增 API

  • transcriber属性:取代了sttstreamingSttvad三个旧属性,成为唯一的 STT 配置入口;
  • createTranscriber(connection)钩子:支持运行时切换模型(例如在 Flux 与 Nova 3 之间做下拉切换);
  • WorkersAIFluxSTT:基于 Workers AI 的按通话 Flux 会话,推荐用于withVoice(完整语音代理);
  • WorkersAINova3STT:按通话 Nova 3 流式会话,推荐用于withVoiceInput(纯语音输入/听写);
  • query选项(VoiceClientOptions:向 WebSocket URL 追加查询参数(例如用于模型选择);
  • 行为收紧start_call时若未配置转写器则直接抛错;重复的start_call在已处于通话中时被静默忽略。

3.2 移除与变更

  • 移除stt(批式 STT)、streamingStt(按话语流式)、vad(服务端 VAD);
  • 移除WorkersAISTTWorkersAIVADpcmToWav工具;
  • 移除prerollMsvadThresholdvadPushbackSecondsvadRetryMsminAudioBytes等调参选项;
  • 移除VoiceInputAgentOptions类型与beforeTranscribe钩子(音频现在连续喂入,而非分批处理);
  • 管线指标中移除vad_msstt_ms
  • 不再支持 HibernationwithVoicewithVoiceInput现在要求继承自Agent(Durable Object),而非 partyserver 的Server

3.3 防驱逐机制:keepAlive

新模型下会话存活整个通话周期,为避免通话期间 Durable Object 被驱逐,语音代理使用keepAlive机制。docs/agents/voice.md 的 "Conversation History" 一节印证了这一点:"Voice agents usekeepAliveto prevent eviction during active calls." 这也解释了后续 0.3.1 版本为何专门修复"连接拆除时keepAlive的 alarm 写入仍在飞行途中"引发的未处理拒绝(详见下文第六节)。

四、持续打磨:采样率、播放调度与设备路由

0.1.0 之后,CHANGELOG 记录了大量针对"听感"与"播放正确性"的工程修复,这些细节直接影响语音产品的真实体验。

4.1 采样率支持:sampleRate选项(0.3.4)

旧版本中,原始的pcm16音频载荷被假定为固定的 16kHz。0.3.4 起新增sampleRate选项(默认16000),由服务端在audio_config消息中声明,VoiceClient读取后(通过新的sampleRategetter 暴露)以该采样率构造AudioBuffer用于播放。这样,原生采样率非 16kHz 的提供商(如 24kHz 的 Gemini TTS)能以正确速度播放;服务端省略该字段时回退到 16kHz。配置方式:

const VoiceAgent = withVoice(Agent, { sampleRate: 24000, // 与所选 TTS 提供商的原生采样率对齐 audioFormat: "mp3", // 发给客户端的音频格式,默认 "mp3" historyLimit: 20, // 加载进上下文的最近消息数,默认 20 maxMessageCount: 1000 // SQLite 中保存的最大消息数,默认 1000 });

4.2 消除块边界爆音:基于播放游标的无缝调度(0.3.2)

0.3.2 修复了一个可闻的缺陷:VoiceClient原先逐块播放响应——每个块在currentTime启动、等待ended事件后再调度下一块,导致每个块的接缝处都会出现几毫秒静默(事件循环延迟加上下一块准备耗时),在听感上表现为"每个块一下"的周期性咔哒声。修复方式是把各块在音频时钟上背靠背调度:

start(Math.max(currentTime, cursor)) // cursor 为持续推进的播放游标

因为块现在可以提前排队调度,客户端必须追踪所有已排程的音频源,并在打断/结束通话时全部停止(此前只需停止当前活动源);同时,播放计数会持续到最后一个排程块结束,保证排程尾部期间的 barge-in(打断)检测依然有效。

4.3 修复跨轮次的慢放:播放桥的按轮次重建(0.3.3)

0.3.3 修复了一个隐蔽问题:当一轮播放结束后存在空闲间隔,下一轮开头的声音会以错误速率播放(听感为"慢动作",随后逐渐恢复正常)。根因是VoiceClient通过MediaStreamAudioDestinationNode → HTMLAudioElement桥播放音频,复用那个已空闲的元素会让新的一轮从错误的速率点继续。修复策略是:当桥完全排空且空闲超过一个短阈值后,拆除并重建播放桥,确保每一轮都通过新创建的元素播放;由于轮内各块会在播放游标上持续排程至少一个源,重建绝不会发生在轮次中途。

4.4 输出设备选择:outputDeviceIdsetOutputDevice()(0.3.0)

0.3.0 为VoiceClient增加outputDeviceId选项和setOutputDevice()方法,用于在浏览器支持 sink 选择时,将助手语音路由到指定的音频输出设备。React 侧的使用方式(详见 docs/agents/voice.md 的 "Output Device Selection"):

const [outputDeviceId, setOutputDeviceId] = useState("default"); const voice = useVoiceAgent({ agent: "MyAgent", outputDeviceId });

实践要点:deviceId来自navigator.mediaDevices.enumerateDevices()kind === "audiooutput"的条目;"default"undefined使用系统默认输出;不支持 sink 选择的浏览器继续走默认输出,并在请求非默认设备时设置outputDeviceError;设备标签在用户授予麦克风权限前可能为空,因此展示扬声器选择器时应等startCall()之后刷新设备列表。另外,setOutputDevice()可以在不重连通话的前提下切换播放设备,非常适合通话中的扬声器切换场景。

五、转写生命周期、诊断与每轮时序指标

0.4.0 是一份内容密集的版本,聚焦"语音生命周期准确性、诊断能力与每轮时序可见性",其中大部分能力已经在当前 docs/agents/voice.md 中有完整的 API 呈现。

5.1 生命周期清理

  • 清除过期的 interim 转写:在通话开始、结束、断开、关闭或启动失败时,清除残留的临时转写文本,避免把上一轮的半截语音带入新状态;
  • speaking事件语义收紧:仅在发送首个服务端音频块时才发出speaking
  • 转写器就绪(transcriber readiness):0.3.4 引入——语音代理会等待流式 STT 启动完成后再进入listening状态或运行 call-start 钩子。自定义转写器会话若异步建立上游流式连接,可实现waitUntilReady(): Promise<void>:就绪时 resolve,启动失败时 reject(通话回到idle)。若close()在就绪等待期间放弃了启动,需要 settle 该 promise,避免服务端启动工作无限等待;同步就绪的提供商可省略此方法;
  • 错误上报:转写器启动与运行期失败通过onFatalError、结构化客户端错误以及可靠的通话清理来报告;
  • 结构化诊断与日志:新增无内容的浏览器诊断与结构化 Worker 错误日志,且不会读取任意的提供商响应体(避免破坏响应流)。

5.2 结果分类与VoiceTurnMetrics

0.4.0 保留了模型的完成原因(finish reason),可区分五类完成结果:no-output(无输出)、output-limit(输出达上限)、content-filtered(内容被过滤)与 model-error(模型错误),再加上正常的完成。在此基础上,通过VoiceClient与 React hooks 暴露稳定的、带类型的每轮时序汇总,覆盖:

  • 语音时序:speechStartToFirstInterimMsspeechStartToFinalMs
  • 轮次与模型时序:afterTranscribeMsmodelToFirstTextMsexposedReasoningMs(模型流暴露的推理时长)、modelStreamConsumptionMsfinalInputToFirstAudioMsturnTotalMs
  • TTS 时序:ttsToFirstAudioMsttsWallMs、以及累积的重叠 TTS 工作量ttsWorkMs

关于指标语义,docs/agents/voice.md 的 "Pipeline metrics" 一节给出了关键约束:VoiceTurnMetrics对每个被分配的语音或文本轮次都会发射一次(包括中止、跳过、空输出、模型错误、TTS 错误等结局);turnIdsourceoutcome是用于关联与解释的维度而非测量值;未触达的时序字段会被省略而非置零;所有时长使用 Worker 时钟,彼此重叠、不可相加。withVoiceInput只发射其可测得的语音、afterTranscribe与总时长,模型与 TTS 时序保持缺省。终端结局(terminal outcome)包括:completedno_outputoutput_limitcontent_filteredmodel_errortts_errorabortedskippederror

消费方式:

client.addEventListener("turnmetrics", (turnMetrics) => { console.log(turnMetrics.turnId, turnMetrics.outcome); }); client.turnMetrics; // VoiceTurnMetrics | null,最近一次终端汇总

React 侧同理,useVoiceAgent()useVoiceInput()都通过turnMetrics暴露最近一次终端汇总。0.4.0 同时声明:保持既有四字段指标线上形状兼容(wire shape),同时让"无音频"与"流式 TTS"的计量口径一致。

六、错误处理、文本流与提示构造语义

6.1 防泄漏的 teardown 处理(0.3.1)

0.3.1 修复了连接拆除时"fire-and-forget"语音生命周期处理器泄漏未处理拒绝的问题。withVoiceInput混入(mixin)在同步的onMessage处理器中派发start_callend_callinterrupt与转写发射事件,且不等待它们完成;若客户端中途断开(例如keepAlive()的 alarm 写入仍在飞行中),可能浮现一条可重试的 "Network connection lost." 拒绝。修复后,这些后台任务经由一个 teardown 感知的辅助函数运行:吞掉预期的连接拆除错误,并把意外错误记入日志。

6.2 支持 AI SDKfullStream(0.3.3)

0.3.3 起,语音轮次支持 AI SDK 的fullStream响应,并会在使用textStream时给出警告。docs/agents/voice.md 中的推荐做法正是返回result.fullStream

async onTurn(transcript: string, context: VoiceTurnContext) { const workersai = createWorkersAI({ binding: this.env.AI }); const result = streamText({ model: workersai("@cf/moonshotai/kimi-k2.7-code"), system: "You are a helpful voice assistant. Keep responses concise.", messages: [ ...context.messages.map(m => ({ role: m.role as "user" | "assistant", content: m.content })), { role: "user", content: transcript } ], abortSignal: context.signal }); return result.fullStream; }

0.2.0 还专门修复过withVoice对 AI SDKtextStream响应的文本流处理,使onTurn()直接返回streamText(...).textStream时也能正常产出 TTS 音频。

6.3 工具调用间的文本间距(0.3.6)

0.3.6 修复了被工具调用分隔的流式文本段之间的空格问题:Think messenger 投递与 Voice 现在共用来自agents/chat的同一套"边界感知文本拼接"逻辑。这带来两个迁移要求:

  • @cloudflare/think/messengers导入textDeltaFromStreamChunk()的存量用户,需改用TextStreamCallback并传入完整的结构化流事件;
  • 安装@cloudflare/think@0.16.0@cloudflare/voice@0.3.6时需同步升级到agents@0.21.0(两者都要求agents >= 0.20.2);
  • 若此前依赖"工具调用两侧文本被无空格拼接",需要更新精确文本断言。

6.4VoiceTurnContext.messages语义(0.3.6)

0.3.6 明确了VoiceTurnContext.messages的定义:它是当前转写之前的已完成历史(针对语音轮次与文本轮次均如此),从而避免按文档方式构造提示时出现重复的用户消息。对既有onTurn()实现的迁移指导:

  • 若你直接把context.messages作为完整 LLM 输入,请只追加一次transcript
  • 若你原本就在context.messages之外另附transcript,无需任何改动;
  • onTurn()内部直接调用getConversationHistory()仍会包含当前转写。

这与 docs/agents/voice.md 的说明一致:"The pipeline persists the current transcript before invoking the hook, so a directgetConversationHistory()call insideonTurn()includes it." 同时context还提供connection(WebSocket 连接)与signal(在打断或断开时中止)。

七、提供商生态:内置与第三方流式 STT

7.1 Workers AI 内置提供商(免 API Key)

类型默认模型推荐场景
WorkersAIFluxSTT连续 STT@cf/deepgram/fluxwithVoice
WorkersAINova3STT连续 STT@cf/deepgram/nova-3withVoiceInput
WorkersAITTSTTS@cf/deepgram/aura-1两者通用
import { WorkersAIFluxSTT, WorkersAINova3STT, WorkersAITTS } from "agents/voice"; transcriber = new WorkersAIFluxSTT(this.env.AI, { eotThreshold: 0.8, // 结束轮次(end-of-turn)阈值 keyterms: ["Cloudflare", "Workers"] // 领域关键词,提升识别率 }); tts = new WorkersAITTS(this.env.AI, { model: "@cf/deepgram/aura-1", speaker: "asteria" });

相关修复记录:0.2.0 修复了 Workers AI STT 会话在 Flux 与 Nova 3 上的边界情况——Flux 现在从轮次生命周期事件中保留最近的非空轮次转写,使得携带空transcriptEndOfTurn事件仍能发出完整话语,且 Flux 的StartOfTurn驱动服务端 barge-in(模型检测到用户说话即中止正在进行的 LLM/TTS 播放);Nova 3 则防御性地在读取前规范化已定稿段状态,避免异常关闭路径下陈旧 teardown 消息抛错。0.3.6 还修复了仅把首个keyterms词条传给 Workers AI Flux 与 Nova-3 STT 的问题,现在会传递完整数组。

7.2 第三方提供商

CHANGELOG 0.3.5 为语音管线新增了AssemblyAI 与 ElevenLabs 流式 STT 提供商。当前生态(对应仓库voice-providers目录下的独立包,如 voice-providers/assemblyai、voice-providers/deepgram、voice-providers/elevenlabs、voice-providers/telnyx):

能力
@cloudflare/voice-assemblyaiAssemblyAISTT连续 STT(Universal 3.5 Pro Realtime)
@cloudflare/voice-deepgramDeepgramSTT连续 STT
@cloudflare/voice-elevenlabsElevenLabsSTTElevenLabsTTS连续 STT 与高质量 TTS
@cloudflare/voice-telnyxTelnyxSTTTelnyxTTS连续 STT、TTS 与电话传输
@cloudflare/voice-twilioTwilio 适配器电话(通话)接入
import { AssemblyAISTT } from "@cloudflare/voice-assemblyai"; export class MyAgent extends VoiceAgent<Env> { transcriber = new AssemblyAISTT({ apiKey: this.env.ASSEMBLYAI_API_KEY }); tts = new WorkersAITTS(this.env.AI); }

AssemblyAI 有一个值得注意的工程细节:每轮代理回复后,管线会自动把说出的文本回喂给 AssemblyAI 作为会话上下文(agent_context),从而提升对"yes"、"7pm"这类短应答的识别准确率。0.4.0 同时更新了随附的语音提供商,使生命周期失败能够向上传播、错误日志保持一致。

7.3 电话接入(Twilio)的音频格式注意点

使用 Twilio 适配器时有一个关键约束(见 docs/agents/voice.md 的 "Telephony (Twilio)"):WorkersAITTS返回 MP3,而 Workers 运行时无法将 MP3 解码为 PCM,因此电话场景必须使用输出原始 PCM 的 TTS 提供商(例如 ElevenLabs 搭配outputFormat: "pcm_16000")。

八、React hooks 的演进:enabled选项与调参语义

8.1enabled选项(0.2.0)

useVoiceAgent新增enabled选项,让 React 应用可以延迟创建与连接VoiceClient,直到异步前置条件(例如按用户生成的 capability token)就绪

const voice = useVoiceAgent({ agent: "MyAgent", enabled: isReady // false 时不创建/不连接 VoiceClient });

处于禁用态时,hook 不创建也不连接VoiceClient,返回空闲/断开状态,且startCall()sendText()sendJSON()等动作回调均为安全的 no-op。当enabled翻转为true时,hook 以当前选项连接;首次启用被视为初始连接,因此onReconnect只在后续连接身份变化时触发。

8.2 调参选项与重连语义

选项类型默认值说明
silenceThresholdnumber0.04RMS 低于此值视为静音
silenceDurationMsnumber500触发end_of_speech的静音时长(ms)
interruptThresholdnumber0.05播放期间检测到说话所需的 RMS
interruptChunksnumber2连续高 RMS 块达到该数量即触发打断

注意:修改调参选项会触发客户端重连(连接 key 包含这些选项)。useVoiceInput是面向听写/语音转文字的轻量 hook,把各轮话语累积成一个字符串,并暴露turnMetrics(最新终端 STT 汇总)与clear()等方法。

九、依赖治理与发布工程细节

CHANGELOG 中有一组容易被忽略但工程价值很高的记录,集中在peerDependencies的治理上:

  • 0.0.5:把通配符*的 peer 依赖替换为真实版本区间——agents>=0.9.0 <1.0.0partysocket^1.0.0
  • 0.1.1 / 0.1.2:发布脚本曾把宽区间覆盖成过紧的^0.x.y,导致安装警告。0.1.2 修正了发布时的 peer 依赖范围,0.1.1 又把updateInternalDependencies"patch"改为"minor",防止未来发布时再次覆盖区间;
  • 0.1.3:把agents的 peer 下限从>=0.9.0收紧到>=0.11.7(与 monorepo 实际测试集对齐),上限<1.0.0不变。其可见效果是:用新版@cloudflare/voice配旧版agents<0.11.7)会出现 peer 警告——这正是设计意图:低于 0.11.7 的agents不再被测过;
  • 0.0.4:修复 TypeScript 6 声明产出。TS6 强制 TS4094,禁止在导出的匿名类类型中使用#private成员;通过为withVoicewithVoiceInput混入函数增加显式返回类型接口(VoiceAgentMixinMembersVoiceInputMixinMembers),生成的.d.ts只暴露公共 API 表面。

结合 packages/voice/package.json,当前要求的agents >=0.23.0 <2.0.0是该治理链条的最新形态;react作为可选的 peer 依赖(peerDependenciesMeta.react.optional: true),保证非 React 环境也能使用VoiceClient。该包以dist/docs/README.md为发布内容,通过 nx 构建目标把 docs/voice 文档与 scripts/copy-package-docs.ts 一并纳入产物。

十、迁移总览与落地建议

结合 docs/voice/index.md(面向迁移的官方指引)与 docs/agents/voice.md(最新完整参考),把存量项目迁移到agents/voice的建议步骤总结如下:

  1. 升级依赖npm install agents,并把@cloudflare/voice替换为agents(确保版本满足 CHANGELOG 中对应能力要求的agents下限,如 0.3.6 要求>=0.20.2);
  2. 批量替换导入路径:按第二节表格完成@cloudflare/voice/client/react/errors四处替换;
  3. 核对onTurn()提示构造:确认context.messages只追加一次transcript(对应 0.3.6 语义);如果依赖textDeltaFromStreamChunk(),改为TextStreamCallback并传入完整结构化流事件;
  4. 利用新增能力:接入sampleRate对齐 TTS 原生采样率、outputDeviceId支持多扬声器、enabled控制连接时机、turnMetrics观察每轮时序并区分no_output/output_limit/content_filtered/model_error等结局;
  5. 注意电话场景约束:Twilio 适配器必须使用输出 PCM 的 TTS 提供商。

整体演进逻辑可以概括为:语音管线从"客户端驱动的 VAD 分段"走向"服务端连续 STT + 模型轮次检测",随之带来更低的端到端延迟(连续喂入、提前排队调度)、更强的持久性(按通话会话 +keepAlive防驱逐)与更高的可观测性(结构化诊断 + 每轮时序指标)。而 0.5.0 把这一切收拢进agents主包,只是让"能力在哪里"这个问题变得更简单:从今往后,语音能力的演进都以agents/voice为准,@cloudflare/voice仅作为向后兼容的别名继续存在。

【免费下载链接】agentsBuild and deploy AI Agents on Cloudflare项目地址: https://gitcode.com/GitHub_Trending/agents1/agents

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询