Hướng dẫn tích hợp API

Kokoro Vietnamese cho TTS, PhoWhisper-base cho STT. Các ví dụ HTTP dùng địa chỉ của dịch vụ đang mở.

Endpoint

Phương thức và đường dẫnKết quả
GET /health200: status, version, queue_depth, worker_busy, free_disk_mb.
GET /v1/models200: tts/stt, trạng thái và voices thật. Chọn voice.id từ danh mục.
GET /v1/runtime200: snapshot CPU/RAM, effective_threads, heavy_task_concurrency, queue_limit.
POST /v1/tts/jobsJSON → 202: job_id, reused, job.
POST /v1/stt/jobsMultipart trường file → 202: job_id, reused, job.
GET /v1/jobs/{id}200: job cùng tiến độ; TTS có chunks/audio_url, STT có text khi hoàn tất.
POST /v1/jobs/{id}/cancel200: job_id, status. Hủy đang chạy có thể chờ lượt suy luận hiện tại.
GET /v1/jobs/{id}/chunks/{index}200: audio/wav. Index bắt đầu từ 0; chưa sẵn sàng hoặc hết hạn trả 404.
GET /v1/jobs/{id}/audio200: một WAV ghép đầy đủ khi completed; chưa sẵn sàng/hết hạn trả 404.
WS /v1/stt/streamNhận ready, gửi PCM16 LE mono 16kHz, nhận partial/final/end.

Tạo giọng đọc

Đầu vào JSON: text bắt buộc; model mặc định kokoro-vi, voice mặc định diem_trinh, speed mặc định 1.0 (0.25–4.0). Văn bản dài được chia tự động. Các URL trả về là đường dẫn tương đối trên cùng dịch vụ.

curl -X POST "http://127.0.0.1:6800/v1/tts/jobs" \
  -H "Content-Type: application/json" \
  -d '{"text":"Xin chào. Đại hội XIV.","model":"kokoro-vi","voice":"diem_trinh","speed":1.0}'
{"job_id":"job_...","reused":false,"job":{"id":"job_...","type":"tts","status":"queued","progress":{"done":0,"total":1,"percent":0,"stage":"queued","unit":"chunks","current_chunk":null,"elapsed_seconds":0},"cancel_requested":false,"normalization_version":"vi-2"}}

Đối với tên riêng, thêm pronunciation_overrides, ví dụ {"OpenAI":"ô pần ây ai"}. Tối đa 32 mục, mỗi cụm 80 ký tự, cách đọc 200 ký tự; literal, phân biệt hoa/thường, ưu tiên cụm dài nhất. Chuẩn hóa hỗ trợ số/ngày/giờ/đơn vị và số La Mã theo ngữ cảnh; không bảo đảm phát âm mọi tên riêng.

Theo dõi và lấy kết quả

Poll mỗi khoảng một giây. Khi completed, đọc text STT hoặc tải audio_url TTS. Dừng poll ở failed/cancelled/interrupted và hiển thị error nếu có. Completed không có nghĩa model nhận đúng mọi từ.

{"done":1,"total":3,"percent":33.3,"stage":"synthesizing","unit":"chunks","current_chunk":2,"elapsed_seconds":12.5}

Đây là giá trị job.progress. Phần trăm TTS dựa trên số đoạn hoàn thành, giữ tối đa 99 trước khi ghép xong; completed là 100. STT trả percent=null trong lúc xử lý, nên dùng thanh chờ. Stage có thể là queued, waiting_for_slot, loading_model, synthesizing, merging, transcribing, cancelling hoặc trạng thái cuối.

Header tùy chọn Idempotency-Key: cùng endpoint + cùng giá trị + cùng payload trả lại job cũ; payload khác trả 409. Dùng UUID riêng mỗi tác vụ, giữ UUID khi retry; các ứng dụng dùng chung namespace. Không dùng một giá trị cố định cho mọi yêu cầu.

Python: TTS và tải một WAV đầy đủ

import time
import requests

BASE = "http://127.0.0.1:6800"

def wait_job(job_id):
    deadline = time.monotonic() + 600
    while time.monotonic() < deadline:
        r = requests.get(f"{BASE}/v1/jobs/{job_id}", timeout=10)
        r.raise_for_status()
        job = r.json()
        if job["status"] == "completed":
            return job
        if job["status"] in {"failed", "cancelled", "interrupted"}:
            raise RuntimeError(job)
        time.sleep(1)
    raise TimeoutError(job_id)

r = requests.post(BASE + "/v1/tts/jobs", json={
    "text": "Xin chào.", "voice": "diem_trinh"
}, timeout=30)
r.raise_for_status()
job = wait_job(r.json()["job_id"])
r = requests.get(BASE + job["audio_url"], timeout=60)
r.raise_for_status()
with open("out.wav", "wb") as f:
    f.write(r.content)

Nhận dạng file âm thanh

Multipart trường file, tối đa 25 MiB và 300 giây. Nhận WAV/MP3/M4A/WebM cùng các định dạng PyAV giải mã được. Không tự đặt Content-Type multipart; thư viện cần tự tạo boundary.

curl -X POST "http://127.0.0.1:6800/v1/stt/jobs" -F "file=@recording.wav"

Python, dùng hàm wait_job ở trên:

with open("recording.wav", "rb") as f:
    r = requests.post(BASE + "/v1/stt/jobs", files={"file": f}, timeout=60)
r.raise_for_status()
job = wait_job(r.json()["job_id"])
print(job["text"])

Vùng có lời nói được giải mã riêng, tối đa 20 giây mỗi lượt. VAD giảm văn bản tự sinh trên im lặng; tiếng nhạc, giọng chồng lấn hoặc môi trường ồn vẫn có thể gây nhận sai. Không tự thêm từ theo văn bản dự đoán.

STT trực tiếp qua WebSocket

  1. Mở WS/WSS cùng dịch vụ, đợi {"type":"ready","sample_rate":16000,"format":"pcm16le"}.
  2. Gửi từng frame PCM16 little-endian, mono, 16kHz; tối đa 65536 bytes/frame, số byte chẵn. Không gửi header WAV hay blob MediaRecorder WebM.
  3. Nhận partial (tạm thời) và final (kết quả cuối). Mỗi text là toàn bộ nội dung hiện tại; thay nội dung hiển thị, không nối mọi partial lại.
  4. Gửi {"type":"stop"} rồi tiếp tục đợi final và end trước khi đóng. Nếu final.complete=false hoặc có error, không coi đó là phiên thành công đầy đủ.
const url = new URL("/v1/stt/stream", location.origin);
url.protocol = location.protocol === "https:" ? "wss:" : "ws:";
const ws = new WebSocket(url);
let ready = false;
ws.onmessage = ({data}) => {
  const message = JSON.parse(data);
  if (message.type === "ready") ready = true;
  if (message.type === "partial" || message.type === "final") {
    console.log(message.text);
  }
  if (message.type === "error") console.error(message.code, message.message);
  if (message.type === "end") { ready = false; ws.close(); }
};
function sendPCM16(arrayBuffer) {
  if (ready && ws.readyState === WebSocket.OPEN) ws.send(arrayBuffer);
}
function stop() {
  if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({type:"stop"}));
}

Ví dụ JavaScript chạy trên cùng origin dịch vụ. Demo đã xử lý thu micro và resample; HTTPS cần WSS. Giới hạn phiên 300 giây, idle không có audio 30 giây; không gửi audio nhanh hơn realtime quá 15 giây. Khi slot đang bận, server trả error/service_busy rồi đóng phiên. Kiểm tra origin bổ sung tại Settings.allowed_origins nếu dùng giao diện ở domain khác.

Giới hạn và xử lý lỗi

Một tác vụ suy luận nặng tại một thời điểm, hàng đợi tối đa 5 job. Phiên live giữ slot suốt phiên. TTS ≤50000 ký tự, JSON ≤1 MiB, kết quả giữ 24 giờ. Chờ có khoảng nghỉ khi retry queue đầy.

{"error":{"code":"queue_full","message":"Job queue is full, retry later","request_id":"..."}}
CodeHTTP / nơi trảXử lý
invalid_request400/422Kiểm tra dữ liệu và trường bắt buộc.
not_found404Kiểm tra job ID, tiến độ và TTL audio.
conflict409Idempotency-Key đã dùng cho payload khác.
payload_too_large413Giảm kích thước đầu vào.
unprocessable_media422Kiểm tra định dạng, thời lượng file.
queue_full429Đợi rồi retry cùng tác vụ.
service_busy / model_not_ready503 hoặc WS errorĐợi slot hoặc kiểm tra model trên máy chủ.
processing_errorerror trong job/WSGhi job ID và thông tin lỗi để kiểm tra.

Mặc định tối đa 2 luồng; sửa inference_threads trong app/config.py để cho phép 4/8, rồi khởi động lại. Dịch vụ tự giới hạn thêm theo CPU/quota đã cấp; cấu hình tài nguyên được đọc một lần lúc startup.