野生流动开发者文档
语音识别调用(ASR)

Whisper 与 VibeVoice 单引擎识别

选择单个 ASR 引擎,复用相同上传、任务轮询和下载接口。

三个入口已完成生产调用、结果下载与实际计费核验;使用现有 API Key 即可调用。

选择模型

模型 ID运行引擎结果每音频分钟单任务预授权上限
wildflow/whisper-asr-v1faster-whisper large-v3文本与段级时间戳;不提供说话人分离¥0.02¥2.40
wildflow/vibevoice-asr-v1VibeVoice-ASR段级正文与时间戳,保留实际输出的说话人标签;不执行 Whisper¥0.04¥4.80

需要同时保留两份结果时,继续使用 双引擎识别,价格为 ¥0.05 / 音频分钟。 单引擎入口不会顺带运行另一个引擎。三个入口共享执行池,不是每个入口各有 8 路独立容量。

快速调用

使用已有普通 API Key,不需要重新注册或申请专用 Key。主动限制 Key 模型范围时,应包含所选模型。 按音频准备和上传输入完成前两步, 取得 INPUT_ARTIFACT_ID。每条输入仍为一个 FLAC 文件,最多 120 分钟、256 MiB。

下面以 Whisper 为例;切换 VibeVoice 只改 WILDFLOW_ASR_MODEL,接口路径和请求字段不变。 切换模型是新任务,必须使用新的幂等键;仅重试原任务时保留原键和请求正文。

export WILDFLOW_ASR_MODEL="wildflow/whisper-asr-v1"
export WILDFLOW_IDEMPOTENCY_KEY="$(python3 -c 'import uuid; print(uuid.uuid4())')"

python3 - "$WILDFLOW_ASR_MODEL" "$INPUT_ARTIFACT_ID" > asr-request.json <<'PY'
import json
import sys
print(json.dumps({
    "model": sys.argv[1],
    "input_artifact_ids": [sys.argv[2]],
    "parameters": {"language": "zh"}
}, ensure_ascii=False))
PY

curl --fail-with-body --silent --show-error \
  -X POST https://api.wildflow.cn/v1/jobs \
  -H "Authorization: Bearer $WILDFLOW_API_KEY" \
  -H "Idempotency-Key: $WILDFLOW_IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  --data-binary @asr-request.json --output operation.json

export RESULT_FILE="single-asr-result.json"
export RESULT_MEDIA_TYPE="application/json"

下面的命令使用 Python 3,读取上一步保存的 operation.json,等待任务完成后下载结果。等待超过两小时会停止;再次执行本段会继续查询原任务,不会重新提交。

python3 - <<'PY'
import hashlib
import json
import os
import time
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime
from pathlib import Path
from urllib.error import HTTPError
from urllib.parse import quote
from urllib.request import Request, urlopen

base = "https://api.wildflow.cn"
headers = {"Authorization": "Bearer " + os.environ["WILDFLOW_API_KEY"]}
operation = json.loads(Path("operation.json").read_text())
operation_id = operation["id"]
status_url = base + "/v1/jobs/" + quote(operation_id, safe="")
output = Path(os.environ["RESULT_FILE"])
expected_media_type = os.environ["RESULT_MEDIA_TYPE"]
deadline = time.monotonic() + 7200

def pause(value):
    try:
        seconds = float(value)
    except (TypeError, ValueError):
        try:
            seconds = (parsedate_to_datetime(value) - datetime.now(timezone.utc)).total_seconds()
        except (TypeError, ValueError, OverflowError):
            seconds = 3
    seconds = max(1, seconds)
    if time.monotonic() + seconds >= deadline:
        raise SystemExit("等待超时。保留 operation.json,稍后重新执行本段继续查询。")
    time.sleep(seconds)

while time.monotonic() < deadline:
    try:
        with urlopen(Request(status_url, headers=headers), timeout=60) as response:
            status = json.load(response)
            retry_after = response.headers.get("Retry-After", "3")
    except HTTPError as error:
        if error.code in (429, 503):
            pause(error.headers.get("Retry-After", "3"))
            continue
        raise SystemExit(f"查询失败:HTTP {error.code}。保留原任务,检查错误后继续查询。")

    state = status["state"]
    print("任务状态:", state, flush=True)
    if state == "succeeded":
        artifacts = status.get("artifacts", [])
        if not artifacts:
            raise SystemExit("任务成功但缺少结果文件,请保留任务 ID 并联系支持。")
        artifact = artifacts[0]
        if artifact["media_type"] != expected_media_type:
            raise SystemExit("结果格式与预期不同,请检查任务和模型。")
        download_url = base + "/v1/artifacts/" + quote(artifact["id"], safe="") + "/content"
        temporary = output.with_name(output.name + ".part")
        digest = hashlib.sha256()
        size = 0
        with urlopen(Request(download_url, headers=headers), timeout=60) as response:
            with temporary.open("wb") as file:
                while chunk := response.read(1024 * 1024):
                    file.write(chunk)
                    digest.update(chunk)
                    size += len(chunk)
        if size != artifact["size_bytes"] or digest.hexdigest() != artifact["sha256"]:
            raise SystemExit("文件校验失败,保留原任务并重新执行本段下载。")
        temporary.replace(output)
        print("已保存:", output)
        break
    if state not in ("queued", "running", "submitting"):
        raise SystemExit(f"任务已停止:{state}。请保留任务 ID;不要换幂等键重复提交。")
    pause(retry_after)
else:
    raise SystemExit("等待超时。保留 operation.json,稍后继续查询原任务。")
PY

网络中断时可重新执行本段。failed、cancelled、recovery_required 或未知状态会停止下载;需要帮助时查看任务恢复说明。

参数与结果

  • language:默认 zh;Whisper 使用该语言,VibeVoice 根据音频识别语言,不保证此参数可强制其语言。
  • context、hotwords:兼容接收,但当前 HTTP 执行路径未传递给引擎,不承诺热词增强效果。
  • source_offset_seconds:仅记录在 timeline 中,消费方应自行叠加到片段时间,不会改写结果中的时间值。
  • 不接收客户端 asr_engine 参数;请使用 model 选择产品。

结果保留与双引擎相同的 schema_version、source_artifact_id、timeline 和 engines 外层结构, 但 engines 中只出现所选引擎:

所选模型读取正文额外信息
Whisperengines.word_timestamps.segments[].text当前提供 start / end 段级时间戳
VibeVoiceengines.segment_text.segments[].text段级时间戳及模型输出的说话人标签

输出 model_id 和 model_revision 标识实际引擎。Whisper 固定为 Systran/faster-whisper-large-v3 (edaa852ec7e145841d8ffdb056a99866b5f0a478);VibeVoice 固定为 microsoft/VibeVoice-ASR (d0c9efdb8d614685062c04425d91e01b6f37d944)。不承诺两种引擎文字一致。

word_timestamps 是保留的历史角色名;当前生产响应未提供 segments[].words,不能据此依赖逐词时间戳。 VibeVoice 的 segments[].speaker 仅在模型输出说话人标签时存在,标签不等于真实身份识别。

费用与批量任务

按实际音频毫秒计量,不把不足一分钟的音频按整分钟收费。成功结算后退回预授权差额; 失败退款和未知结果处理沿用任务恢复规则。 例如 180 小时分别为 Whisper ¥216、VibeVoice ¥432、双引擎 ¥540(不含重建新任务产生的新费用)。 分段推理不会对同一任务重复收取音频时长费用。

三个入口共用现有队列与执行容量,多余请求排队。新任务排队与执行期限为 14 天,不是完成时延保证。 本次以两段音频分别调用三个入口,六个任务全部成功、下载和幂等重放通过;部分任务排队或自动重试。 这不是独占机器吞吐基准,不把双引擎测试成绩当成单引擎性能承诺。批量提交前确保余额足够覆盖所有待处理任务的预授权。

ASR 可能漏字、错字或产生时间戳偏差,专有名词、数字和说话人标注须人工复核;不提供实时流式识别。 更多计费规则见价格与计费。