第一次调 Qwen-Audio-3.1 卡住,多数不是模型侧的问题,而是送进去的音频参数和请求字段没对齐:采样率、声道数、编码格式和接口真正接受的输入形式对不上,返回解析又写得含糊。稳妥的做法是先挑一条几秒钟、人声清晰的短音频,用命令行读出它的真实参数,转成单声道和统一采样率,构造一次最小请求,把请求参数和返回内容一起写进日志,再对同一段音频跑两次确认结果是否稳定。
先把一条短音频跑通,再去调效果。适用场景是第一次接入音频模型、或换了音频来源后报错;操作动作是 ffprobe 读参数、ffmpeg 转单声道与采样率、发一次最小请求、记录请求与返回字段;验证方式是转换前后参数对照、两次调用结果对比;边界是官方接口的字段名和取值必须以官方文档为准,未确认前只写占位骨架,不要写死。
用命令行读出这条音频的真实参数
在写调用代码之前,先确认这条音频到底长什么样。凭印象判断“应该就是 16k 单声道”是最常见的误判来源。ffprobe 是比较通用的选择,可以直接拿到采样率、声道数、时长和编码格式。
ffprobe -v error -show_format -show_streams sample.wav输出里需要重点看几个字段:audio 流下的 codec_name 是编码格式(如 pcm_s16le、mp3、aac),sample_rate 是采样率,channels 是声道数,duration 是时长;format 段的 format_name 是容器格式,bit_rate 是码率。这几个值决定了你后面要不要转格式、以及请求里该填什么。
如果命令直接报错,报错位置通常能帮你定位问题:找不到文件会在打开输入阶段提示路径不存在;容器能打开但音频流读不出来,会在解析流信息时提示 invalid data 或找不到 codec。这两种情况不要急着改代码,先把文件本身换成一条能正常播放的音频再试。
把音频转成单声道并按需统一采样率
格式转换建议放在本地做,而不是指望接口帮你兜底。转换的意义是:转换之后每次送测都用同一套参数,链路里少一个变量,出问题时容易判断是数据问题还是调用问题。
ffmpeg -i sample.mp3 -ac 1 -ar 16000 -c:a pcm_s16le sample_16k_mono.wav这里的 -ac 1 是单声道,-ar 16000 是采样率,-c:a pcm_s16le 是输出编码。采样率具体取多少、输出用 wav 还是别的容器,需要结合官方文档和你要做的是语音识别还是音频理解来确认,不要照搬。转换完再读一次参数做前后对照:
ffprobe -v error -show_entries stream=codec_name,sample_rate,channels -of default=nw=1 sample_16k_mono.wav对照时要确认三个值都变了:声道数变成 1,采样率等于你设定的值,编码格式是你指定的那个。如果声道数还是 2,多半是输入音频本身是多轨或有异常声道布局,需要单独确认。
用一条短音频发一次最小请求
先把链路跑通,排除长音频和复杂格式的干扰。请求构造可以拆成三段:读取音频、组装并发送请求、解析返回。字段名以官方文档为准,下面只是通用骨架,未确认的地方用占位标注。
import base64, requests, json
# 1. 读取音频(base64 或本地路径,取决于接口要求)
with open("sample_16k_mono.wav", "rb") as f:
audio_b64 = base64.b64encode(f.read()).decode()
# 2. 发送请求(字段名以官方文档为准,以下为占位)
payload = {
"model": "<按官方文档填写模型名>",
"input": {
"audio": audio_b64, # 或 audio_url / file_id,二选一
"format": "wav", # 占位,确认是否必填
"sample_rate": 16000 # 占位,确认是否必填
},
"parameters": {"<按文档填写>": "<值>"}
}
headers = {"Authorization": "Bearer <API_KEY>", "Content-Type": "application/json"}
resp = requests.post("<请求地址>", headers=headers, json=payload, timeout=60)
# 3. 解析返回
print(resp.status_code)
print(resp.text[:500])第一次先把 resp.text 原样打出来,不要急着写字段提取逻辑。返回结构没确认之前,任何 resp.json()["output"]["text"] 这类取值都可能因为字段名不同而抛异常,反而掩盖真正的服务端错误。
把请求参数和返回内容一起写进日志
日志的作用是在出错时一眼分清是发送侧的问题还是解析侧的问题。建议至少记录:文件名、音频时长、请求时间、返回状态码、返回文本长度。这几项足以覆盖大多数第一次接入的失败场景。
{
"file": "sample_16k_mono.wav",
"duration_sec": 3.2,
"sample_rate": 16000,
"channels": 1,
"request_time": "2026-01-01T10:00:00+08:00",
"http_status": 200,
"resp_text_len": 128,
"resp_head": "{\"output\":..."
}读这条日志时的判断顺序是:状态码非 2xx,问题在发送侧,优先看请求体里的音频编码和字段名;状态码 200 但 resp_text_len 为 0 或异常小,问题在参数或音频内容本身;状态码 200 且长度正常但取值报错,问题在解析侧,回头核对返回的真实字段结构。
对同一段音频重复跑两次并对比结果
同样的输入跑两次,是为了区分偶发失败和稳定失败。对比方式很直接:把两次日志里的状态码、返回文本长度、以及返回文本本身做比对。文本比较可以先比长度,再比内容是否完全一致;如果返回里带有非确定性的表述,就退一步比关键字段是否一致。
run1: status=200 text_len=128 text_sha1=<第一次哈希>
run2: status=200 text_len=131 text_sha1=<第二次哈希>
diff: 状态一致;文本长度差 3;内容哈希不同两次结果一致,说明这条链路是稳的,可以拿它当基线,再去换更长的音频或更多样的格式。两次结果都不一样,先别改模型或参数,回头检查音频是不是每次读取时被改写了、请求里有没有随机字段。如果是同一状态码下的稳定失败(比如两次都返回同样的错误信息),下一步动作是拿这条错误信息去核对请求字段和音频参数,而不是反复重试。