ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于 Cloudflare Agents 与 Telnyx WebRTC 构建电话语音 Agent:从浏览器桥接到 PSTN 通话的完整实践

2026/9/18 15:30:49 拓冰建站 浏览量
基于 Cloudflare Agents 与 Telnyx WebRTC 构建电话语音 Agent:从浏览器桥接到 PSTN 通话的完整实践 基于 Cloudflare Agents 与 Telnyx WebRTC 构建电话语音 Agent从浏览器桥接到 PSTN 通话的完整实践【免费下载链接】agentsBuild and deploy AI Agents on Cloudflare项目地址: https://gitcode.com/GitHub_Trending/agents1/agents导读本文围绕仓库中 Telnyx Phone Voice Agent 示例展开讲解如何用浏览器端的 Telnyx WebRTC 桥把真实 PSTN 电话通话路由进一个 Cloudflare Agent通话音频经 Telnyx STT 转写 → Workers AI 生成回复 → Telnyx TTS 合成语音并注回电话。读完本文你将掌握该示例的完整流程、本地运行与云端部署方法、JWT 凭据发放端点的安全配置以及 STT/TTS 底层实现的源码级原理。一、整体架构一通电话如何流入流出 Cloudflare Agent该示例的端到端数据流可以概括为电话 → 浏览器中的 Telnyx WebRTC 桥 → Cloudflare Agent → Telnyx STT → Workers AI → Telnyx TTS → 音频注回电话关键在于浏览器在此扮演的是控制面板 WebRTC 桥双重角色音频来自电话通话、最终也回到电话通话完全不经过浏览器麦克风或扬声器这一点在 src/client.tsx 的界面说明文案中也有明确标注。因此通话期间必须保持浏览器标签页打开关闭标签页即关闭了实时 WebRTC 桥。从部署视角看整个体系由三块组成Worker 侧语音 AgentMyVoiceAgent见 src/server.ts通过withVoice(Agent)封装挂载TelnyxSTT转写器与TelnyxTTS合成器并在onTurn中调用 Workers AI 完成推理Worker 侧 JWT 端点/api/telnyx-token负责给浏览器签发短时效的 Telnyx WebRTC 凭据全程不透出 Telnyx API Key浏览器侧桥接客户端TelnyxPhoneClientWebSocketVoiceTransportcreateTelnyxVoiceConfig三件套把电话音频接进 Agent 的 WebSocket。二、前置条件与配置2.1 需要准备的 Telnyx 资源资源用途Telnyx 账号所有 API 调用与计费的基础Telnyx API Key服务端调用 STT/TTS/凭据 API 的密钥Telnyx SIP Credential ConnectionWebRTC 登录凭据的归属连接绑定在该 SIP Connection 上的电话号码接听入站电话2.2 环境变量本地开发时复制环境变量模板npm install cp examples/telnyx-voice-agent/.env.example examples/telnyx-voice-agent/.env编辑.env设置两项TELNYX_API_KEY... TELNYX_CREDENTIAL_CONNECTION_ID...部署到生产 Worker 时不要把密钥写进代码或公开配置而是用 Wrangler 存为 secretcd examples/telnyx-voice-agent wrangler secret put TELNYX_API_KEY wrangler secret put TELNYX_CREDENTIAL_CONNECTION_ID关于这两个变量voice-providers/telnyx/README.md 的官方说明是TELNYX_API_KEY必填TELNYX_CREDENTIAL_CONNECTION_ID仅在启用电话功能telephony时必填它被TelnyxJWTEndpoint用来创建 WebRTC 登录令牌。2.3 依赖说明该示例的package.json见 examples/telnyx-voice-agent/package.json核心依赖包括agentsAgent 运行时、cloudflare/voice-telnyxTelnyx 语音 Provider 包、workers-ai-providerWorkers AI 模型接入、ai流式文本生成 SDK前端使用 React 与cloudflare/kumoUI 组件库构建侧由 Vite Wrangler 驱动。三、本地运行把浏览器变成电话桥3.1 启动命令npm run start -w cloudflare/agents-telnyx-voice-agent打开本地 URL 后点击Connect phone bridge。浏览器依次执行从/api/telnyx-token获取短时效 Telnyx WebRTC 令牌向 Cloudflare Agent 打开一条 WebSocket 语音通道等待入站电话。此时拨打绑定到 Telnyx SIP Connection 的电话号码入站电话会被自动接听autoAnswer: true并路由进 AI Agent。界面上的sip:{username}sip.telnyx.com见 src/client.tsx是浏览器实际注册到 Telnyx 的 SIP 用户名。3.2 浏览器端的桥接代码src/client.tsx 中connectBridge的核心逻辑如下const telnyx await createTelnyxVoiceConfig({ jwtEndpoint: /api/telnyx-token, autoAnswer: true, debug: false }); const phoneClient new TelnyxPhoneClient({ transport: new WebSocketVoiceTransport({ agent: my-voice-agent, name: sessionId }), bridge: telnyx.bridge }); phoneClient.connect();几个值得注意的细节name: sessionId会话 ID 来自localStorage中持久化的 UUID见 src/client.tsx保证同一浏览器后续刷新仍能关联同一 Agent 会话phoneClient.startCall()在 Worker 连接建立之后才调用connectionchange事件内用于向 Telnyx 注册并等待入站电话若失败会重置状态并给出错误提示见 src/client.tsx事件订阅statuschange、transcriptchange、interimtranscript、metricschange、audiolevelchange、mutechange、error等事件驱动整个控制面板的 UI 状态见 src/client.tsx。3.3 通话中的控制能力状态显示VoiceStatus四态——idle等待、listening聆听电话、thinking思考、speaking向电话讲话见 src/client.tsx静音/取消静音toggleMute()直接作用于TelnyxPhoneClient通话实时转写transcript最终结果与interimTranscript中间结果双通道展示延迟指标界面展示 LLM、TTS、首帧音频与总耗时first_audio_ms等见 src/client.tsx文本补发连接状态下可向同一个 Agent 发送文本轮次sendText便于调试对话逻辑见 src/client.tsx。四、生产部署与安全要点4.1 部署命令npm run deploy -w cloudflare/agents-telnyx-voice-agent该命令实际执行vite build wrangler deploy见 examples/telnyx-voice-agent/package.json。wrangler.jsonc中配置了 AI binding、MyVoiceAgent的 Durable Object 与 SQLite 迁移以及 SPA 资源托管并指定/agents/*与/api/telnyx-token优先由 Worker 处理见 examples/telnyx-voice-agent/wrangler.jsonc。4.2 为什么必须关闭匿名令牌示例代码中/api/telnyx-token明确写了allowUnauthenticated: true并伴随一条仅打印一次的控制台警告见 src/server.ts[telnyx-voice-agent] /api/telnyx-token is using allowUnauthenticated: true for the local demo. Add an authorize() callback before deploying this endpoint publicly.原因在于这个端点会让任意访客在你的 Telnyx 账号下铸造 WebRTC 凭据并挂机消费通话资源。该行为有意仅用于本地演示——公开部署前必须替换为authorize()回调。4.3 生产级 JWT 端点配置从源码层面看TelnyxJWTEndpoint的默认策略是默认拒绝匿名请求见 voice-providers/telnyx/src/server/jwt-endpoint.ts配置了authorize则校验回调结果403 拒绝未配置时若allowUnauthenticated不为 true直接返回 401。生产环境的推荐写法const endpoint new TelnyxJWTEndpoint({ apiKey: env.TELNYX_API_KEY, credentialConnectionId: env.TELNYX_CREDENTIAL_CONNECTION_ID, allowedOrigins: [https://your-app.example], authorize: async (request) { // 校验应用会话、签名令牌或其他认证状态 return Boolean(request.headers.get(Authorization)); } }); return endpoint.handleRequest(request);其余配置项说明见 voice-providers/telnyx/src/server/jwt-endpoint.tsbaseUrlTelnyx API 基础地址默认https://api.telnyx.com/v2allowedOriginsCORS 允许的来源列表默认不输出任何 CORS 头需使用精确来源如https://example.comauthorize创建/删除凭据前的请求鉴权回调可同步或异步返回布尔值allowUnauthenticated匿名创建凭据的显式开关默认false仅限本地演示。端点支持的 HTTP 语义见 voice-providers/telnyx/src/server/jwt-endpoint.tsPOST→ 创建 telephony credential 并签发 JWT返回{ token, credentialId, sipUsername }DELETE→ 按{ credentialId }吊销凭据用于会话结束后清理服务端资源OPTIONS→ CORS 预检。五、Worker 侧语音 Agent 实现5.1 核心类与关键配置src/server.ts 中的MyVoiceAgent是整套系统的中枢const VoiceAgent withVoice(Agent); export class MyVoiceAgent extends VoiceAgentEnv { transcriber new TelnyxSTT({ apiKey: this.env.TELNYX_API_KEY, language: en, interimResults: true }); tts new TelnyxTTS({ apiKey: this.env.TELNYX_API_KEY, voice: Telnyx.NaturalHD.astra }); async onTurn(transcript: string, context: VoiceTurnContext) { const workersAi createWorkersAI({ binding: this.env.AI }); const result streamText({ model: workersAi(cf/zai-org/glm-4.7-flash, { sessionAffinity: this.sessionAffinity }), instructions: SYSTEM_PROMPT, messages: [ ...context.messages.map((message) ({ role: message.role as user | assistant, content: message.content })), { role: user as const, content: transcript } ] }); return result.stream; } }要点拆解withVoice(Agent)把 Agent 扩展为语音 Agenttranscriber与tts两个实例属性分别实现agents/voice的Transcriber与TTSProvider接口onTurn是每次用户说完一句话后的回调把完整对话历史 本次转写文本交给 Workers AI 的cf/zai-org/glm-4.7-flash模型sessionAffinity保持会话连续性返回流式文本result.stream交给语音管线合成系统提示词要求回复“简洁、口语化、适合实时通话”见 src/server.ts这是电话场景的实用工程细节。5.2 fetch 路由Worker 入口先匹配/api/telnyx-token其余请求走routeAgentRequest见 src/server.ts。注意routeAgentRequest的返回值还可能为 null此时回退返回 404。六、源码级原理Telnyx STT 与 TTS 的实现细节cloudflare/voice-telnyx源码位于 voice-providers/telnyx是本示例的语音基础设施理解它的实现有助于排查通话质量问题。6.1 STTTelnyxSTTTelnyxSTT实现了agents/voice的Transcriber接口通过 Telnyx WebSocket 转写 API 实时接收音频。核心配置见 voice-providers/telnyx/src/providers/stt.ts配置项默认值说明engineTelnyx转写引擎可切为Deepgramlanguageen转写语言代码inputFormatwav音频输入格式Telnyx API 不接受裸 PCMtranscriptionModel无使用 Deepgram 引擎时的模型如nova-3interimResultstrue是否启用中间结果sttWsUrlwss://api.telnyx.com/v2/speech-to-text/transcriptionSTT WebSocket 地址实现上的三个关键工程点WAV 头拼接Cloudflare 语音管线喂进来的是 16 kHz 单声道裸 PCM16而 Telnyx STT 要求容器格式。默认inputFormat: wav时会话在发送任何数据前先发送一个 44 字节 WAV 头含 RIFF/WAVE、fmt、data三个 chunkdata size 字段填0x7fffffff表示长度未知见 voice-providers/telnyx/src/providers/stt.ts 与 voice-providers/telnyx/src/providers/stt.tsWorkers fetch-upgrade WebSocketSTT 使用 Cloudflare Workers 的 fetch-upgrade 模式建连认证放在请求头的Authorization: Bearer apiKey里浏览器/Node 原生 WebSocket API 无法做到因此该 Provider 面向 Workers 运行时见 voice-providers/telnyx/src/providers/stt.ts缓冲与先注册监听连接建立前先缓存 PCM chunkaccept()之前先注册 message/error/close 监听器避免帧在accept()与addEventListener()之间丢失见 voice-providers/telnyx/src/providers/stt.ts。消息解析按is_final区分最终转写onUtterance与中间结果onInterim见 voice-providers/telnyx/src/providers/stt.ts。6.2 TTSTelnyxTTSTelnyxTTS同时实现TTSProvider与StreamingTTSProvider支持两种后端见 voice-providers/telnyx/src/providers/tts.ts配置项默认值说明voiceTelnyx.NaturalHD.astra音色标识如Telnyx.NaturalHD.luna、Telnyx.Ultra.idbackendrestrest任意环境可用websocket仅 Workers 运行时ttsWsUrlwss://api.telnyx.com/v2/text-to-speech/speech流式后端地址两种后端的取舍backend: rest默认每个句子一次POST /v2/text-to-speech/speech返回完整 MP3 音频缓冲兼容所有音色包括 Ultra 系列任意环境可用backend: websocket以 chunk 形式流式返回合成音频首帧延迟更低但由于认证走请求头必须依赖 Cloudflare Workers 的 fetch-upgrade WebSocket 模式不支持 Telnyx Ultra 音色。流式后端的协议实现见 voice-providers/telnyx/src/providers/tts.ts值得注意Telnyx TTS 要求连续发送三帧且不等待服务端 ACK——先是{ text: }初始化再发{ text }内容最后发{ text: }停止帧触发合成服务端收到停止帧后才开始流式返回音频。返回帧中只消费text null且携带音频数据的纯音频帧跳过text 原文的 blob 帧见 voice-providers/telnyx/src/providers/tts.ts。6.3 音频格式约定电话通话音频以PCM16进入 AgentTelnyxPhoneClient上行播放方向TelnyxPhoneClient会把 PCM16 响应直接路由给电话桥MP3 等格式则在浏览器侧先解码再播放见 voice-providers/telnyx/README.md若使用更底层的TelnyxPhoneTransport需自行保证 Agent 输出 PCM16因为该传输层不做非 PCM 格式解码TTS 默认输出 MP3恰好匹配 Cloudflare 语音管线默认的audioFormat: mp3。七、包结构、路由与模块边界cloudflare/voice-telnyx采用子路径导入包根路径是服务端安全的不引入浏览器 WebRTC 代码只有需要浏览器侧 PSTN 桥时才引入/browserimport { TelnyxSTT } from cloudflare/voice-telnyx/stt; import { TelnyxTTS } from cloudflare/voice-telnyx/tts; import { TelnyxJWTEndpoint } from cloudflare/voice-telnyx; import { TelnyxCallBridge } from cloudflare/voice-telnyx/browser;对应的源码目录结构voice-providers/telnyx/srcproviders/放 STT/TTS 实现server/jwt-endpoint.ts放凭据端点transport/phone-transport.ts与phone-client.ts放浏览器侧桥接audio/utils.ts处理音频工具且配套了完整的 vitest 测试集voice-providers/telnyx/tests。八、从 JWT 端点到浏览器桥的完整调用链把整条链路串起来看用户点击Connect phone bridge浏览器POST /api/telnyx-tokenTelnyxJWTEndpoint.handleRequest依次调用 Telnyx API 两步POST /v2/telephony_credentials创建凭据拿到sipUsername再POST /v2/telephony_credentials/:id/token生成短时效 JWT若令牌生成失败端点会自动吊销刚创建的凭据以避免资源泄漏见 voice-providers/telnyx/src/server/jwt-endpoint.tscreateTelnyxVoiceConfig用 JWT 登录 Telnyx WebRTC返回bridge与sipUsernameWebSocketVoiceTransport打开到my-voice-agent的语音 WebSocket电话打进 Telnyx 号码 → 入站自动应答 → 电话音频经 WebRTC 桥流入 AgentAgent 内TelnyxSTT实时转写 →onTurn调用 Workers AI 生成文本 →TelnyxTTS合成语音 → 音频流经 WebSocket 回到浏览器桥 → 注回电话线路。九、实践清单与注意事项本地调试用allowUnauthenticated: true没问题但服务端只打印一次警告上线前务必换成authorize()allowedOrigins通话期间不要关闭浏览器标签页标签页就是电话桥本身生产环境两个密钥必须用wrangler secret put注入不要提交到代码库若切换 Deepgram 转写引擎记得通过transcriptionModel指定模型如nova-3追求更低首帧延迟且运行在 Workers 上时可将 TTSbackend切为websocket但注意其对 Ultra 音色的限制。十、相关示例与延伸examples/voice-agent——浏览器麦克风/扬声器语音 Agent与本例对比可直观理解“浏览器作桥”与“浏览器作终端”两种模式的差异examples/voice-input——可复用的语音输入组件voice-providers/telnyx/README.md——cloudflare/voice-telnyx的完整 API 文档与环境变量表语音 Agent 基座能力见 agents/voice 与 docs/agents/voice.md 相关说明agents/voice包内的withVoice、VoiceTurnContext、Transcriber、TTSProvider等接口定义。【免费下载链接】agentsBuild and deploy AI Agents on Cloudflare项目地址: https://gitcode.com/GitHub_Trending/agents1/agents创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考