تبدیل گفتار به متن — Speech-to-text

آشا رونویسی گفتار به متن (STT) را از طریق endpoint اختصاصیِ /v1/audio/transcriptions پشتیبانی می‌کند. صوتی base64 بفرستید و پاسخ JSON با متن رونویسی‌شده و آمار استفاده بگیرید.

کشف مدل‌ها

مدل‌های STT را از چند راه می‌توانید پیدا کنید:

از طریق API

همهٔ مدل‌ها را با endpoint سازگار با OpenAI یعنی GET /v1/models فهرست کنید. مدل‌های رونویسی صوتی (مثل خانوادهٔ Whisper) با حالتِ audio->text در architecture.modality قابل شناسایی‌اند:

cURL GET /v1/models
curl "https://app.asha-ai.ir/v1/models"

در صفحهٔ مدل‌ها

در فهرست مدل‌ها با فیلتر «صوتی» (Audio) مدل‌های صوتی — از جمله رونویسی — را ببینید. این فیلتر سمتِ کلاینت و بر اساس نوع مدل در داشبورد اعمال می‌شود.

استفاده از API

درخواست POST به /v1/audio/transcriptions با بدنهٔ JSON شامل صوتی base64 بفرستید. پاسخ JSON است با متن رونویسی‌شده و آمار استفادهٔ اختیاری.

مثال ساده

import base64
import json
import os

import requests

with open("audio.wav", "rb") as f:
    base64_audio = base64.b64encode(f.read()).decode("utf-8")

response = requests.post(
    url="https://app.asha-ai.ir/v1/audio/transcriptions",
    headers={
        "Authorization": f"Bearer {os.environ['ASHA_API_KEY']}",
        "Content-Type": "application/json",
    },
    data=json.dumps({
        "model": "~openai/whisper-1",
        "input_audio": {
            "data": base64_audio,
            "format": "wav",
        },
    }),
)

result = response.json()
print(result["text"])
package main

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	raw, err := os.ReadFile("audio.wav")
	if err != nil {
		panic(err)
	}

	payload := map[string]any{
		"model": "~openai/whisper-1",
		"input_audio": map[string]any{
			"data":   base64.StdEncoding.EncodeToString(raw),
			"format": "wav",
		},
	}
	body, _ := json.Marshal(payload)

	req, _ := http.NewRequest("POST", "https://app.asha-ai.ir/v1/audio/transcriptions", bytes.NewReader(body))
	req.Header.Set("Authorization", "Bearer "+os.Getenv("ASHA_API_KEY"))
	req.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	out, _ := io.ReadAll(resp.Body)
	var result map[string]any
	_ = json.Unmarshal(out, &result)
	fmt.Println(result["text"])
}
import fs from "fs";

const audioBuffer = await fs.promises.readFile("audio.wav");
const base64Audio = audioBuffer.toString("base64");

const response = await fetch("https://app.asha-ai.ir/v1/audio/transcriptions", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ASHA_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "~openai/whisper-1",
    input_audio: {
      data: base64Audio,
      format: "wav",
    },
  }),
});

const result = await response.json();
console.log(result.text);
import fs from "fs/promises";

const audioBuffer = await fs.readFile("audio.wav");
const base64Audio = audioBuffer.toString("base64");

const response = await fetch("https://app.asha-ai.ir/v1/audio/transcriptions", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ASHA_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "~openai/whisper-1",
    input_audio: {
      data: base64Audio,
      format: "wav",
    },
  }),
});

const result = await response.json();
console.log(result.text);
# Base64-encode your audio file
AUDIO_BASE64=$(base64 < audio.wav | tr -d 'n')

curl https://app.asha-ai.ir/v1/audio/transcriptions 
  -H "Content-Type: application/json" 
  -H "Authorization: Bearer $ASHA_API_KEY" 
  -d '{
    "model": "~openai/whisper-1",
    "input_audio": {
      "data": "'"$AUDIO_BASE64"'",
      "format": "wav"
    }
  }'

پارامترهای درخواست

پارامترنوعالزامیشرح
modelstringبلهمدل STT (مثل ~openai/whisper-1)
input_audioobjectبلهدادهٔ صوتی برای رونویسی
input_audio.datastringبلهدادهٔ صوتی با کدگذاری base64 (بایت خام، نه Data URI)
input_audio.formatstringبلهفرمت صوتی (مثل wav، mp3، flac، m4a، ogg، webm، aac)
languagestringخیرکد زبان ISO-639-1 (مثل "en"، "ja")؛ اگر حذف شود خودکار تشخیص داده می‌شود
temperaturenumberخیردمای نمونه‌گیری بین ۰ تا ۱؛ مقادیر پایین‌تر نتیجهٔ قطعی‌تری می‌دهند
providerobjectخیرپیکربندی عبور پارامترهای مختص ارائه‌دهنده

درخواست‌های Multipart سازگار با OpenAI

endpoint همچنین درخواست‌های multipart/form-data را به سبک OpenAI می‌پذیرد؛ پس کلاینت‌هایی که برای /v1/audio/transcriptions OpenAI ساخته شده‌اند (از جمله SDKهای رسمی) فقط با عوض‌کردن Base URL به https://app.asha-ai.ir/v1 کار می‌کنند:

Python (OpenAI SDK) transcribe.py
import os

from openai import OpenAI

client = OpenAI(
    base_url="https://app.asha-ai.ir/v1",
    api_key=os.environ["ASHA_API_KEY"],
)

with open("audio.wav", "rb") as f:
    result = client.audio.transcriptions.create(
        model="~openai/whisper-large-v3",
        file=f,
    )

print(result.text)
cURL (multipart) transcribe.sh
curl https://app.asha-ai.ir/v1/audio/transcriptions 
  -H "Authorization: Bearer $ASHA_API_KEY" 
  -F file="@audio.wav" 
  -F model="~openai/whisper-large-v3"

فیلدهای file، model، language، temperature، response_format و timestamp_granularities پشتیبانی می‌شوند. prompt پذیرفته می‌شود اما نادیده گرفته می‌شود. response_format می‌تواند json (پیش‌فرض) یا verbose_json باشد؛ دومی task، language، duration و برچسب‌های زمانی سطح قطعه را اضافه می‌کند؛ verbose_json فقط روی ارائه‌دهنده‌های سازگار با OpenAI در دسترس است و بقیه آن را با 400 رد می‌کنند. text، srt و vtt با خطای 400 رد می‌شوند. با verbose_json، timestamp_granularities[]=word را هم بدهید تا برچسب‌های زمانی سطح کلمه در آرایهٔ words بیایند (segment پیش‌فرض ارائه‌دهنده است). همین response_format و timestamp_granularities در مسیر JSON با base64 هم کار می‌کنند.

آپلود multipart به ۲۵ MB محدود است، همان سقفی که OpenAI اعمال می‌کند. برای فرمت‌های فشرده این یعنی حدود ۲۶ دقیقه MP3 با ۱۲۸ kbps، ۵۲ دقیقه با ۶۴ kbps یا بیش از ۲ ساعت ویسِ Opus با ۲۴ kbps. WAV فشرده‌نشده خیلی زودتر سقف را پر می‌کند (حدود ۱۳ دقیقه مونو با ۱۶ کیلوهرتز)؛ برای ضبط‌های طولانی mp3 یا opus را ترجیح بدهید. فایل‌های بزرگ‌تر را با base64 و JSON از طریق input_audio بفرستید که offload جریانی را پشتیبانی می‌کند. ضبط‌هایی که به بیش از حدود یک دقیقه پردازش نیاز دارند را به‌هرحال بشکنید، چون ارائه‌دهنده‌های پاییندست بعد از ۶۰ ثانیه در هر درخواست timeout می‌شوند.

گزینه‌های مختص ارائه‌دهنده

با پارامتر provider گزینه‌های مختص ارائه‌دهنده را بفرستید؛ با نام ارائه‌دهنده کلید می‌خورند و فقط گزینه‌های ارائه‌دهندهٔ جورشده منتقل می‌شوند:

JSON request
{
  "model": "~openai/whisper-large-v3",
  "input_audio": {
    "data": "UklGRiQA...",
    "format": "wav"
  },
  "provider": {
    "options": {
      "groq": {
        "prompt": "Expected vocabulary: OpenRouter, API, transcription"
      }
    }
  }
}

قالب پاسخ

endpoint STT پاسخ JSON با متن رونویسی‌شده برمی‌گرداند:

JSON response
{
  "text": "Hello, this is a test of speech-to-text transcription.",
  "usage": {
    "seconds": 9.2,
    "total_tokens": 113,
    "input_tokens": 83,
    "output_tokens": 30,
    "cost": 8
  }
}

فیلدهای پاسخ

فیلدنوعشرح
textstringمتن رونویسی‌شده
usage.secondsnumberمدت صوتِ ورودی بر حسب ثانیه
usage.total_tokensnumberمجموع توکن‌های استفاده‌شده (ورودی + خروجی)
usage.input_tokensnumberتعداد توکن‌های ورودیِ صورت‌حساب‌شده
usage.output_tokensnumberتعداد توکن‌های خروجی تولیدشده
usage.costnumberهزینهٔ کل درخواست به تومان

فرمت‌های صوتی پشتیبانی‌شده

فرمت‌های صوتی پشتیبانی‌شده بسته به ارائه‌دهنده متفاوت‌اند. فرمت‌های رایج:

فرمتMIME Typeشرح
wavaudio/wavصوت بدون فشرده‌سازی، بالاترین کیفیت
mp3audio/mpegصوت فشرده، سازگاری گسترده
flacaudio/flacصوت فشردهٔ بدون اتلاف
m4aaudio/mp4صوت MPEG-4
oggaudio/oggصوت Ogg Vorbis
webmaudio/webmصوت WebM؛ رایج در ضبط‌های مرورگر
aacaudio/aacکدگذاری پیشرفتهٔ صوتی

قیمت‌گذاری

مدل‌های STT بسته به ارائه‌دهنده راهبرد قیمتی متفاوتی دارند:

  • بر اساس مدت (مثل OpenAI Whisper): به‌ازای هر ثانیه صوت ورودی
  • بر اساس توکن (مثل مدل‌های جدیدتر OpenAI): به‌ازای هر توکن ورودی/خروجی، شبیه مدل‌های متنی

هزینهٔ هر مدل را در فهرست مدل‌ها یا از طریق Models API ببینید. فیلد usage.cost در پاسخ، هزینهٔ واقعی هر درخواست را نشان می‌دهد.

تفاوت با ورودی صوتی

آشا دو راه برای پردازش صوت دارد:

  1. تبدیل گفتار به متن (همین صفحه): endpoint اختصاصیِ /v1/audio/transcriptions بهینه‌شده برای رونویسی؛ JSON ساخت‌یافته با متن و دادهٔ مصرف برمی‌گرداند. برای تبدیل صوت به متن مناسب است.
  2. ورودی صوتی از طریق Chat Completions (مستندات Audio): صوت را به‌صورت بخشی از درخواست /v1/chat/completions با نوع محتوای input_audio بفرستید. مدل صوت را کنار متن پردازش و به‌صورت گفتگو پاسخ می‌دهد؛ برای تحلیل صوت، پرس‌وجو دربارهٔ محتوای صوتی یا ترکیب صوت با دیگر حالت‌ها مناسب است.

بهترین روش‌ها

  • فرمت را مشخص کنید: همیشه input_audio.format را بدهید؛ تشخیص خودکار همیشه دقیق نیست.
  • مدل را آگاهانه انتخاب کنید: برای دقت بالا ~openai/whisper-large-v3 و برای نیاز به سرعتِ بیشتر ~openai/whisper-1؛ پایداری را با آزمایش روی داده‌های خودتان بسنجید.
  • زبان را در صورت معلوم‌بودن بفرستید: کوتاه‌تر، دقیق‌تر و کم‌خطاتر است.
  • خودرو را قطع کنید: درخواست‌های بزرگ را بعد از ۶۰ ثانیه timeout کنید؛ ارائه‌دهنده‌های پاییندست به همین ترتیب رفتار می‌کنند.
  • دادهٔ base64 را تمیز بفرستید: فقط بایت‌های خام کدگذاری‌شده، بدون پیشوند Data URI.
  • برای خروجی ساخت‌یافته از response_format=verbose_json و timestamp_granularities[]=word استفاده کنید.

عیب‌یابی

پیام خطاشرح و راه‌حل
400 Bad Request بدنهٔ نامعتبر؛ فرمت فیلدها و ویژگی‌های اختیاریِ پشتیبانی‌نشده را بررسی کنید.
413 Content Too Large فایل از ۲۵ MB رد شده؛ از فرمت کم‌حجم‌تر مثل mp3 استفاده کنید یا صوت را تکه‌تکه کنید.
429 Rate Limit محدودیت نرخ؛ با backoff و retry مواجه کنید.
400 Unsupported response_format فرمت پاسخ خواسته‌شده توسط ارائه‌دهندهٔ جورشده پشتیبانی نمی‌شود.

سؤالات متداول

چرا خطای 413 می‌گیرم؟

برای multipart سقف ۲۵ MB اعمال می‌شود. از فرمت کم‌حجم‌تر استفاده کنید یا صوت را کوتاه‌تر کنید؛ برای ضبط‌های طولانی‌تر مسیر base64 با input_audio را امتحان کنید.

منبع قیمت‌گذاری چیست؟

قیمت یا بر اساس مدت صوت (به‌ازای ثانیه) یا بر اساس توکن‌هاست و در Models API درج می‌شود. هزینهٔ هر درخواست دقیقاً در usage.cost به تومان ثبت می‌شود.

آیا prompt پشتیبانی می‌شود؟

در multipart پذیرفته می‌شود اما نادیده گرفته می‌شود. اگر ارائه‌دهندهٔ شما گزینهٔ واژگان جداگانه‌ای دارد، از طریق provider آن را بفرستید.

چرا کل درخواست یک دقیقه طول می‌کشد؟

می‌تواند به ۶۰ ثانیه timeout در پردازش ارائه‌دهنده برسد. ضبط‌های طولانی را به قطعات کوچک‌تر بشکنید و از timeout خودِ کلاینت مطمئن شوید.

کدام مدل‌ها به json خروجی می‌دهند؟

فقط مدل‌هایی که json یا verbose_json را در response_format خود اعلام کرده‌اند؛ برای بررسی، صفحهٔ مدل یا Models API را ببینید.

آیا خروجی مثل پاسخ Chat است؟

خیر. این endpoint فقط رونویسی انجام می‌دهد؛ برای گفتگو یا تحلیل صوت از Audio در Chat Completions استفاده کنید.