refactor: rewrite STT service to use qwen3_asr_toolkit Python API

- Replace Whisper subprocess calls with direct qwen3_asr_toolkit API
- Remove subprocess dependencies, use pure Python API
- Update to use DASHSCOPE_API_KEY instead of WHISPER_MODEL
- Cleaner code without CLI calls
- Better Ukrainian language recognition quality
This commit is contained in:
Apple
2025-11-15 12:55:21 -08:00
parent 65e33add81
commit e0cb3ddbdb
5 changed files with 115 additions and 146 deletions

View File

@@ -172,7 +172,7 @@ services:
timeout: 10s
retries: 3
# STT Service (Speech-to-Text using Whisper)
# STT Service (Speech-to-Text using Qwen3 ASR Toolkit)
stt-service:
build:
context: ./services/stt-service
@@ -181,8 +181,7 @@ services:
ports:
- "9000:9000"
environment:
- WHISPER_MODEL=${WHISPER_MODEL:-base}
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
- DASHSCOPE_API_KEY=${DASHSCOPE_API_KEY:-}
volumes:
- ./logs:/app/logs
networks:

View File

@@ -2,7 +2,8 @@ FROM python:3.11-slim
WORKDIR /app
# Встановлюємо системні залежності (ffmpeg для конвертації аудіо)
# Встановлюємо системні залежності
# qwen3_asr_toolkit може потребувати ffmpeg для обробки деяких форматів
RUN apt-get update && apt-get install -y \
ffmpeg \
&& rm -rf /var/lib/apt/lists/*

View File

@@ -1,16 +1,14 @@
# STT Service (Speech-to-Text)
Сервіс для розпізнавання мови з аудіо файлів за допомогою Whisper.
Сервіс для розпізнавання мови з аудіо файлів за допомогою Qwen3 ASR Toolkit.
## Можливості
- Розпізнавання мови з голосових повідомлень (Telegram voice, audio, video_note)
- Підтримка форматів: ogg, mp3, wav, m4a, webm
- Автоматична конвертація в WAV 16kHz mono через ffmpeg
- Підтримка кількох Whisper-реалізацій:
- `faster-whisper` (рекомендовано, локально)
- `whisper` CLI (fallback)
- OpenAI Whisper API (якщо є API key)
- Підтримка форматів: ogg, mp3, wav, m4a, webm, flac
- Автоматична обробка та конвертація аудіо (всередині qwen3_asr_toolkit)
- Чистий Python API без subprocess/CLI викликів
- Висока якість розпізнавання української мови
## Запуск
@@ -60,17 +58,15 @@ Health check endpoint.
### Environment Variables
- `WHISPER_MODEL`: модель Whisper (`base`, `small`, `medium`, `large`) - за замовчуванням `base`
- `OPENAI_API_KEY`: API ключ OpenAI (опційно, для використання OpenAI Whisper API)
- `DASHSCOPE_API_KEY`: **Обов'язково** - API ключ DashScope для доступу до Qwen3 ASR API
- Отримати ключ: https://dashscope.console.aliyun.com/
- Встановити: `export DASHSCOPE_API_KEY="your-api-key"`
### Моделі Whisper
### Отримання API ключа DashScope
- `base`: найшвидша, менша точність (~74M параметрів)
- `small`: баланс швидкості та якості (~244M)
- `medium`: краща якість (~769M)
- `large`: найкраща якість (~1550M)
Для української мови рекомендую `small` або `medium`.
1. Зареєструйтеся на https://dashscope.console.aliyun.com/
2. Створіть API ключ в розділі "API Keys"
3. Встановіть змінну середовища `DASHSCOPE_API_KEY`
## Інтеграція з Gateway
@@ -84,21 +80,13 @@ Gateway автоматично використовує STT-сервіс для
## Встановлення залежностей
### faster-whisper (рекомендовано)
### qwen3-asr-toolkit
```bash
pip install faster-whisper
pip install qwen3-asr-toolkit
```
Моделі завантажуються автоматично при першому використанні.
### whisper CLI (fallback)
```bash
pip install openai-whisper
```
### ffmpeg (обов'язково)
### ffmpeg (може знадобитися для деяких форматів)
```bash
# Ubuntu/Debian
@@ -113,19 +101,27 @@ brew install ffmpeg
## Troubleshooting
### Помилка: "No Whisper implementation available"
### Помилка: "qwen3_asr_toolkit not available"
Встановіть одну з реалізацій:
- `pip install faster-whisper` (рекомендовано)
- або `pip install openai-whisper`
- або встановіть `OPENAI_API_KEY`
Встановіть бібліотеку:
```bash
pip install qwen3-asr-toolkit
```
### Помилка: "DASHSCOPE_API_KEY not configured"
Встановіть змінну середовища:
```bash
export DASHSCOPE_API_KEY="your-api-key"
```
Або додайте в `docker-compose.yml`:
```yaml
environment:
- DASHSCOPE_API_KEY=${DASHSCOPE_API_KEY}
```
### Помилка: "ffmpeg not found"
Встановіть ffmpeg (див. вище).
### Повільна обробка
- Використовуйте меншу модель (`base` замість `medium`)
- Або використовуйте GPU (додайте `device="cuda"` в коді)
Встановіть ffmpeg (див. вище). Більшість форматів обробляються без ffmpeg, але деякі можуть його потребувати.

View File

@@ -1,11 +1,10 @@
"""
STT Service (Speech-to-Text) для DAGI Router
Використовує Whisper для розпізнавання голосу
Використовує qwen3_asr_toolkit для розпізнавання голосу
"""
import os
import uuid
import subprocess
import logging
from pathlib import Path
from typing import Optional
@@ -19,8 +18,8 @@ logger = logging.getLogger(__name__)
app = FastAPI(
title="STT Service",
description="Speech-to-Text service using Whisper",
version="1.0.0"
description="Speech-to-Text service using Qwen3 ASR Toolkit",
version="2.0.0"
)
app.add_middleware(
@@ -32,10 +31,19 @@ app.add_middleware(
)
# Configuration
WHISPER_MODEL = os.getenv("WHISPER_MODEL", "base") # base, small, medium
DASHSCOPE_API_KEY = os.getenv("DASHSCOPE_API_KEY")
TEMP_DIR = Path("/tmp/stt")
TEMP_DIR.mkdir(exist_ok=True)
# Initialize Qwen3 ASR Toolkit
try:
from qwen3_asr_toolkit import transcribe_audio
ASR_AVAILABLE = True
logger.info("qwen3_asr_toolkit loaded successfully")
except ImportError:
ASR_AVAILABLE = False
logger.warning("qwen3_asr_toolkit not available, install with: pip install qwen3-asr-toolkit")
class STTResponse(BaseModel):
text: str
@@ -43,119 +51,78 @@ class STTResponse(BaseModel):
duration: Optional[float] = None
def convert_audio_to_wav(input_path: str, output_path: str) -> bool:
"""Конвертувати аудіо в WAV 16kHz mono"""
try:
cmd = [
"ffmpeg", "-y", "-i", input_path,
"-ar", "16000", # Sample rate
"-ac", "1", # Mono
"-f", "wav",
output_path
]
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=30
)
if result.returncode != 0:
logger.error(f"ffmpeg error: {result.stderr}")
return False
return True
except Exception as e:
logger.error(f"Audio conversion failed: {e}")
return False
def transcribe_with_whisper(audio_path: str) -> tuple[str, Optional[str], Optional[float]]:
def transcribe_with_qwen(audio_path: str) -> tuple[str, Optional[str], Optional[float]]:
"""
Розпізнати мову з аудіо файлу
Розпізнати мову з аудіо файлу через qwen3_asr_toolkit
Повертає (text, language, duration)
"""
if not ASR_AVAILABLE:
raise ImportError("qwen3_asr_toolkit not installed. Install with: pip install qwen3-asr-toolkit")
if not DASHSCOPE_API_KEY:
raise ValueError("DASHSCOPE_API_KEY environment variable not set")
try:
# Варіант 1: faster-whisper (рекомендовано)
try:
from faster_whisper import WhisperModel
model = WhisperModel(WHISPER_MODEL, device="cpu", compute_type="int8")
segments, info = model.transcribe(audio_path, language="uk", beam_size=5)
text_parts = []
for segment in segments:
text_parts.append(segment.text)
text = " ".join(text_parts).strip()
language = info.language
duration = sum(segment.end - segment.start for segment in segments)
return text, language, duration
except ImportError:
logger.warning("faster-whisper not installed, trying whisper CLI")
# qwen3_asr_toolkit автоматично обробляє різні формати аудіо
# та виконує необхідні конвертації
transcript = transcribe_audio(audio_path)
# Варіант 2: whisper CLI (fallback)
try:
cmd = ["whisper", audio_path, "--model", WHISPER_MODEL, "--language", "uk", "--output_format", "txt"]
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=60
)
if result.returncode == 0:
# Whisper CLI створює .txt файл з тим самим ім'ям
txt_path = audio_path.replace(".wav", ".txt")
if Path(txt_path).exists():
text = Path(txt_path).read_text(encoding="utf-8").strip()
return text, "uk", None
except FileNotFoundError:
logger.warning("whisper CLI not found")
# transcribe_audio повертає текст
# Можна також отримати додаткову інформацію, якщо API підтримує
text = transcript.strip() if isinstance(transcript, str) else str(transcript).strip()
# Варіант 3: OpenAI Whisper API (якщо є API key)
openai_api_key = os.getenv("OPENAI_API_KEY")
if openai_api_key:
try:
import openai
client = openai.OpenAI(api_key=openai_api_key)
with open(audio_path, "rb") as audio_file:
transcript = client.audio.transcriptions.create(
model="whisper-1",
file=audio_file,
language="uk"
)
return transcript.text, transcript.language, None
except Exception as e:
logger.warning(f"OpenAI Whisper API failed: {e}")
# Для української мови встановлюємо language="uk"
# qwen3_asr_toolkit може автоматично визначати мову
language = "uk" # Можна змінити на автоматичне визначення
raise Exception("No Whisper implementation available")
# Duration можна отримати з аудіо файлу, якщо потрібно
# Поки що повертаємо None
duration = None
return text, language, duration
except Exception as e:
logger.error(f"Transcription failed: {e}")
logger.error(f"Qwen3 ASR transcription failed: {e}", exc_info=True)
raise
@app.post("/stt", response_model=STTResponse)
async def stt(file: UploadFile = File(...)):
"""
Розпізнати мову з аудіо файлу
Розпізнати мову з аудіо файлу через qwen3_asr_toolkit
Підтримує формати: ogg, mp3, wav, m4a, webm
Підтримує формати: ogg, mp3, wav, m4a, webm, flac
qwen3_asr_toolkit автоматично обробляє конвертацію
"""
if not ASR_AVAILABLE:
raise HTTPException(
status_code=503,
detail="qwen3_asr_toolkit not available. Install with: pip install qwen3-asr-toolkit"
)
if not DASHSCOPE_API_KEY:
raise HTTPException(
status_code=500,
detail="DASHSCOPE_API_KEY not configured"
)
tmp_id = str(uuid.uuid4())
tmp_input = TEMP_DIR / f"{tmp_id}_input.{file.filename.split('.')[-1] if '.' in file.filename else 'ogg'}"
tmp_wav = TEMP_DIR / f"{tmp_id}.wav"
# Визначаємо розширення файлу
file_ext = "ogg"
if file.filename and "." in file.filename:
file_ext = file.filename.split(".")[-1].lower()
tmp_input = TEMP_DIR / f"{tmp_id}.{file_ext}"
try:
# Зберігаємо вхідний файл
content = await file.read()
tmp_input.write_bytes(content)
logger.info(f"Received audio file: {file.filename}, size: {len(content)} bytes")
logger.info(f"Received audio file: {file.filename}, size: {len(content)} bytes, format: {file_ext}")
# Конвертуємо в WAV 16kHz
if not convert_audio_to_wav(str(tmp_input), str(tmp_wav)):
raise HTTPException(status_code=400, detail="Audio conversion failed")
# Розпізнаємо мову
text, language, duration = transcribe_with_whisper(str(tmp_wav))
# qwen3_asr_toolkit автоматично обробляє різні формати
# та виконує необхідні конвертації всередині
text, language, duration = transcribe_with_qwen(str(tmp_input))
logger.info(f"Transcribed: {text[:100]}... (lang: {language})")
@@ -167,26 +134,33 @@ async def stt(file: UploadFile = File(...)):
except HTTPException:
raise
except ValueError as e:
logger.error(f"STT configuration error: {e}")
raise HTTPException(status_code=500, detail=str(e))
except ImportError as e:
logger.error(f"STT import error: {e}")
raise HTTPException(status_code=503, detail=str(e))
except Exception as e:
logger.error(f"STT error: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=f"STT failed: {str(e)}")
finally:
# Очищаємо тимчасові файли
for path in [tmp_input, tmp_wav]:
if path.exists():
try:
path.unlink()
except:
pass
if tmp_input.exists():
try:
tmp_input.unlink()
except Exception as e:
logger.warning(f"Failed to delete temp file {tmp_input}: {e}")
@app.get("/health")
async def health():
"""Health check"""
return {
"status": "ok",
"status": "ok" if ASR_AVAILABLE else "degraded",
"service": "stt-service",
"model": WHISPER_MODEL
"engine": "qwen3_asr_toolkit",
"asr_available": ASR_AVAILABLE,
"api_key_configured": DASHSCOPE_API_KEY is not None
}

View File

@@ -1,6 +1,5 @@
fastapi==0.104.1
uvicorn[standard]==0.24.0
python-multipart==0.0.6
faster-whisper==1.0.0
openai>=1.0.0
qwen3-asr-toolkit>=1.0.0