699 字
3 分钟
语音识别与降级链路

语音识别与降级链路#

本篇聚焦项目里的语音识别模块。它的核心并不只是“调用一个 ASR 接口”,而是如何在同一条链路里处理 Data URI、同步主方案、异步降级方案,并保证失败时不让整个聊天流程崩掉。


函数签名#

def transcribe_audio_to_text(audio_path: str) -> str:
"""
将本地音频文件转为文字。
失败时自动降级,保证程序不崩溃。
"""

完整执行流程#


第一步:Base64 编码#

# 为什么要 Base64 编码?
# 音频是二进制数据,HTTP JSON 只能传文本
# Base64 把二进制转为 ASCII 字符串(体积增大约 33%)
ext = Path(audio_path).suffix.lower().lstrip('.') or 'wav'
# 部分后缀到 MIME 子类型的映射
fmt_map = {'m4a': 'mp4', 'ogg': 'ogg', 'flac': 'flac', 'mp3': 'mp3'}
audio_fmt = fmt_map.get(ext, 'wav')
mime_type = f"audio/{audio_fmt}" # 例如 "audio/wav"
with open(audio_path, "rb") as f:
audio_b64 = base64.b64encode(f.read()).decode('utf-8')
# Data URI 格式:data:<MIME>;base64,<数据>
# 这样就能把文件「内嵌」进 JSON,不依赖文件服务器
data_uri = f"data:{mime_type};base64,{audio_b64}"
Data URI 长什么样?
data:audio/wav;base64,UklGRj6yAQBXQVZFZm10IBAAAA...(很长的字符串)

方案一:qwen3-asr-flash#

resp = httpx.post(
DASHSCOPE_MULTIMODAL_URL, # 原生多模态接口,不是 compatible-mode!
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
},
json={
"model": "qwen3-asr-flash",
"input": {
"messages": [
# system 消息必须存在,但 ASR 模型不支持自定义 prompt,留空
{"role": "system", "content": [{"text": ""}]},
# audio 字段直接放 Data URI 字符串(不是字典!)
{"role": "user", "content": [{"audio": data_uri}]},
]
},
"parameters": {
"asr_options": {
# enable_itn=False:保留口语数字,不转阿拉伯数字
# "三点一四" 不会变成 "3.14"
"enable_itn": False
}
},
},
timeout=30,
)
# 原生接口的返回结构(与 OpenAI 兼容接口不同!)
result = resp.json()
text = result["output"]["choices"][0]["message"]["content"][0]["text"].strip()
三大坑:为什么之前一直报错?
错误尝试报错正确做法
compatible-mode 接口发 base64InvalidParameter改用原生多模态接口
"type": "audio_url" + audio_url.urlInput should be 'text','image','audio'...改为 "audio": data_uri
"audio": {"data": "...", "format": "wav"}audio: Input should be a valid stringaudio 字段直接是字符串,不是字典

方案二:sensevoice-v1(降级)#

sensevoice 使用**异步「提交-轮询」**模式:

# ── 第一步:提交任务 ──────────────────────────────────
submit_resp = httpx.post(
DASHSCOPE_ASR_SUBMIT_URL,
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
"X-DashScope-Async": "enable", # ← 必须!否则走同步接口会 404
},
json={
"model": "sensevoice-v1",
"input": {"file_url": data_uri}, # sensevoice 也支持 Data URI
"parameters": {},
},
)
task_id = submit_resp.json()["output"]["task_id"]
# ── 第二步:轮询结果 ──────────────────────────────────
for attempt in range(POLL_MAX_RETRIES): # 最多等 40 秒
time.sleep(POLL_INTERVAL_SECONDS)
poll_resp = httpx.get(
DASHSCOPE_TASK_QUERY_URL.format(task_id=task_id),
headers={"Authorization": f"Bearer {api_key}"},
)
status = poll_resp.json()["output"]["task_status"]
if status == "SUCCEEDED":
text = poll_resp.json()["output"]["results"][0]["transcription"]
return text
elif status in ("FAILED", "CANCELED"):
break # 失败,退出轮询
# PENDING / RUNNING:继续等待

API 接口对比#

维度qwen3-asr-flash(主)sensevoice-v1(降级)
接口类型同步,立即返回异步,需轮询
延迟秒级秒级~十秒
Base64 支持
URL 路径/aigc/multimodal-generation/generation/audio/asr/transcription
请求头特殊要求X-DashScope-Async: enable
结果路径output.choices[0].message.content[0].textoutput.results[0].transcription

相关笔记