Dify AI 语音助手完整实战:三步接通"按住说话"到"开口回答"
【免费下载链接】difyBuild Agentic workflows, RAG pipelines, with rich AI model and tool support on one collaborative workspace. Deploy on cloud, VPC, or self-hosted, so teams move from prototype to production without rebuilding the stack.项目地址: https://gitcode.com/GitHub_Trending/di/dify
你在手机里按住麦克风说"帮我查下明天杭州天气",几秒后助手开口回答——这个体验只依赖两个接口:语音转文字(STT,把语音变成文本)和文字转语音(TTS,把文本变成可播放的音频)。Dify 内置的 AI 语音助手能力把这两个接口直接挂在你的应用上,配好模型就能用。照这篇文章做,十分钟能跑通一条最小验证链路;做完后,你会拥有一个会"听、懂、说"的语音应用,而不只是一段孤立的代码。
能力一览:30 秒判断你的需求能不能落地
这一节解决"能不能做"的问题:读完你会拿到一份输入侧和输出侧的硬性限制清单,对得上再继续往下看。
| 方向 | 接口 | 输入 | 输出 | 硬限制 |
|---|---|---|---|---|
| STT 语音转文字 | POST /v1/apps/{app_id}/audio-to-text | 音频文件,字段名固定为file | JSON:{"text": "识别结果"} | 单文件 ≤ 30MB;格式限 mp3 / m4a / wav / amr / mpga |
| TTS 文字转语音 | POST /v1/apps/{app_id}/text-to-audio | JSON:{"text": "...", "voice": "可选"} | 音频流(默认 MP3 容器),非 JSON | 音色列表由模型提供商决定;不传voice时用该提供商的第一个音色 |
两点提醒:
- 两个接口都要在应用里显式开启对应功能开关,没开会直接返回"功能未启用"类错误,而不是静默失败。
- TTS 的返回体是原始音频字节,前端不能当 JSON 解析,要按 blob 处理。
最短上手路径:三分钟接通第一个语音接口
读完这一节,你会完成"配提供商 → 选模型 → 发首个真实请求"三步,并亲眼看到识别文本、亲耳听到合成音频。
第一步,配提供商。打开 Dify 控制台,进入"设置 → 模型供应商",添加一个同时支持 STT 和 TTS 的供应商(下文以 OpenAI 为例),填入 API Key。💡 两个方向都用同一把 Key 最省事,后面不用切配置。
第二步,选模型并开启功能。进入你的应用,打开"功能设置",分别开启"语音转文本"和"文本转语音",STT 选whisper-1,TTS 选tts-1,音色先按默认。
第三步,发首个真实请求。准备一段不超过 30MB 的测试音频,执行:
# 字段名必须叫 file,这是最常见的报错原因(NoAudioUploaded) curl -X POST "http://127.0.0.1:5001/v1/apps/{app_id}/audio-to-text" \ -H "Authorization: Bearer <你的应用api_key>" \ -F "file=@test.mp3" # 成功后应看到:{"text": "你刚才说的内容"}再验证 TTS:
curl -X POST "http://127.0.0.1:5001/v1/apps/{app_id}/text-to-audio" \ -H "Authorization: Bearer <api_key>" \ -H "Content-Type: application/json" \ -d '{"text": "你好,我是语音助手"}' -o reply.mp3 # 成功后应听到:reply.mp3 可以被播放器正常播放两个请求都通了,最短链路就成立了。后端实现主要在api/services/audio_service.py,排查行为时可以对照。
声音怎么进来:STT 配置要点与上传示例
这一节解决"音频进来之后会经过什么校验"。读完后你应能独立处理格式、大小、鉴权三类 4xx 报错。
配置要点(对应源码中的校验顺序):
- 功能开关必须打开,否则 400 类"未启用"错误;
- MIME 类型必须在
audio/{mp3|m4a|wav|amr|mpga}白名单内,不在则返回"不支持的音频类型"; - 体积超过 30MB 直接拒绝,超长录音请在客户端先分段或压缩。
提供商怎么选,看语言场景和计费方式:
| 提供商 | 语言支持 | 中文效果 | 成本特点 |
|---|---|---|---|
| OpenAI Whisper | 多语言 | 稳定,长句偶有错字 | 按音频分钟计费,短对话划算 |
| Azure Speech | 多语言 | 好,可定制语言区域 | 按字符计费,长文本便宜 |
| Google STT | 多语言 | 中上 | 按分钟计费,有免费额度 |
| 阿里云 | 中文优先 | 中文场景最省心的选择 | 按量计费,国内网络延迟低 |
判断标准很简单:中文为主选国内提供商,多语言混用选 Whisper,其余按免费额度和延迟实测定。
前端上传最小示例(≤15 行):
// 字段名必须是 file,且要带真实 MIME,白名单按 MIME 校验 const form = new FormData(); form.append("file", blob, "recording.mp3"); const res = await fetch( `https://<你的域名>/v1/apps/${appId}/audio-to-text`, { method: "POST", headers: { Authorization: `Bearer ${apiKey}` }, body: form, } ); const { text } = await res.json(); // 识别结果就在 text 字段声音怎么出去:TTS 配置要点与音色策略
这一节解决"用什么声音说"。读完你会拿到一张场景到音色的映射表,以及一段可直接播放的流式处理代码。
配置要点:voice参数可传可不传——不传时,Dify 会取该 TTS 模型返回的音色列表里的第一个(见transcript_tts中voices[0]的逻辑)。上线前最好显式指定,避免供应商调整默认值后声音"偷偷变了"。可用哪些音色由模型决定,以控制台里"文本转语音"下拉框为准,不要凭记忆写死。
音色选择策略表:
| 场景 | 推荐音色 | 理由 |
|---|---|---|
| 客服问答、高频使用 | alloy或nova | 中性偏友好,长时间听不累 |
| 故事、品牌讲解 | fable | 女声,叙述感强 |
| 正式播报、通知 | onyx或echo | 低频偏多,显得稳重 |
| 创意内容、轻松语气 | shimmer | 变化多,适合短内容 |
一句话:高频对话用中性音色,低频内容用个性音色。
流式播放最小示例(≤15 行):
async function speak(text, voice = "nova") { const res = await fetch( `https://<你的域名>/v1/apps/${appId}/text-to-audio`, { method: "POST", headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ text, voice }), } ); // 响应体是音频字节,不是 JSON,直接当 blob 播放 const url = URL.createObjectURL(await res.blob()); new Audio(url).play(); }端到端实战:音频 → 文本 → LLM → 音频
这一节把前四节串起来。读完后你会清楚每一步的数据形态变化,并拥有一个后端可运行的完整闭环。
数据形态变化就三次:音频字节 → 纯文本 → 纯文本 → 音频字节。中间两步(文本到文本)走普通对话接口,和纯文字聊天完全一样,语音只是首尾两端的"翻译层"。
后端最小可运行闭环(Python,≤15 行):
import httpx def voice_chat(base: str, app_id: str, key: str, audio: bytes) -> bytes: # 坑点1:字段名必须是 "file";坑点2:TTS 响应是音频字节不是 JSON h = {"Authorization": f"Bearer {key}"} t = httpx.post(f"{base}/v1/apps/{app_id}/audio-to-text", headers=h, files={"file": ("r.mp3", audio, "audio/mpeg")}) text = t.json()["text"] a = httpx.post(f"{base}/v1/apps/{app_id}/chat-messages", headers=h, json={"inputs": {}, "query": text, "response_mode": "blocking", "user": "demo"}) answer = a.json()["answer"] r = httpx.post(f"{base}/v1/apps/{app_id}/text-to-audio", headers=h, json={"text": answer}) return r.content # 直接返回可播放的音频前端同理:录音拿 blob → 调audio-to-text取text→ 调对话接口取answer→ 调text-to-audio播放。三处 URL 都在api/controllers/service_api/app/audio.py和 Web 侧api/controllers/web/audio.py有对应实现,接口行为变化时以源码为准。
上线前必查的三个坑
坑一:识别不准。现象:文本有错字,或句首句尾被吞。原因:客户端把静音段一起录了,或格式是模型不擅长的高压缩音频。解法:前端剪掉首尾静音,统一转 mp3/m4a 再上传;中文场景换国内优化的 STT 模型复测同一段音频再下结论。
坑二:音色机械。现象:回复像导航播报,数字和英文特别生硬。原因:LLM 原样输出代码、符号、连续数字,TTS 直接照读。解法:TTS 前加一步文本清洗(拆长句、数字转口语、去 markdown 符号):
def for_speech(answer: str) -> str: return re.sub(r"[*_#`\n]+", " ", answer) # 先去掉符号再进 TTS坑三:首字延迟高。现象:说完到开口回答超过 5 秒。原因:STT、LLM、TTS 三段全串行,且 TTS 要等 LLM 完整回答才合成。解法:分别测三段耗时定位最慢段;短问答用blocking模式,长回答按句分段调用 TTS、音频端排队播放,让第一句话先出声。⏱ 目标是首句出声压在 2~3 秒内。
结尾:上线检查清单
- STT 侧:格式白名单(mp3/m4a/wav/amr/mpga)与 30MB 上限在前端提前拦截,别等后端 413/415 才报错
- TTS 侧:显式指定
voice,不要依赖供应商默认值 - 全链路压一次"说完→首句出声"的延迟,确认低于用户能忍的 3 秒
- 两个接口的 401(Key 失效)和功能未启用错误都有用户可读的提示
- 网络失败有重试:TTS 请求建议失败后重试 1 次,音频丢了对话就断了
AI 语音助手的语音能力,说到底就是两个音频接口加一次 LLM 调用——链路接通之后,剩下要打磨的全是延迟和音色的细节,而它们都可以逐段量化。
【免费下载链接】difyBuild Agentic workflows, RAG pipelines, with rich AI model and tool support on one collaborative workspace. Deploy on cloud, VPC, or self-hosted, so teams move from prototype to production without rebuilding the stack.项目地址: https://gitcode.com/GitHub_Trending/di/dify
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考