feat: Create Effi-STT - custom STT solution for OpenWebUI
- FastAPI server with OpenAI-compatible API - Integration with faster-whisper (large-v3) - Full Docker support with docker-compose - Support for multiple languages (FR/EN/Auto) - Clean, maintainable codebase (~100 lines) - Health endpoint and error handling - Volume caching for models
This commit is contained in:
@@ -0,0 +1,37 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
# Installation des dépendances système
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
ffmpeg \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Création du répertoire de travail
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copie des dépendances Python
|
||||||
|
COPY requirements.txt .
|
||||||
|
|
||||||
|
# Installation des packages Python
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Copie du code
|
||||||
|
COPY main.py .
|
||||||
|
|
||||||
|
# Variables d'environnement
|
||||||
|
ENV MODEL_SIZE=large-v3
|
||||||
|
ENV DEVICE=cpu
|
||||||
|
ENV COMPUTE_TYPE=int8
|
||||||
|
ENV LANGUAGE=fr
|
||||||
|
ENV CPU_THREADS=4
|
||||||
|
ENV PORT=8200
|
||||||
|
ENV HOST=0.0.0.0
|
||||||
|
|
||||||
|
# Port exposé
|
||||||
|
EXPOSE 8200
|
||||||
|
|
||||||
|
# Healthcheck
|
||||||
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
||||||
|
CMD python -c "import requests; requests.get('http://localhost:8200/health')" || exit 1
|
||||||
|
|
||||||
|
# Commande de démarrage
|
||||||
|
CMD ["python", "main.py"]
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
# 🎙️ Effi-STT
|
||||||
|
|
||||||
|
> Speech-to-Text API compatible OpenAI pour Effi
|
||||||
|
> Solution maison avec faster-whisper
|
||||||
|
|
||||||
|
## 🎯 Objectif
|
||||||
|
|
||||||
|
Fournir une API STT (Speech-to-Text) 100% compatible avec l'API OpenAI, fonctionnant parfaitement avec Open WebUI.
|
||||||
|
|
||||||
|
## ✅ Caractéristiques
|
||||||
|
|
||||||
|
- **API OpenAI 100% compatible** - Endpoints `/v1/audio/transcriptions` et `/health`
|
||||||
|
- **Basé sur faster-whisper** - Rapide et efficace
|
||||||
|
- **Support multi-langues** - Français, anglais, et 99+ langues
|
||||||
|
- **Dockerisé** - Déploiement simple avec Docker Compose
|
||||||
|
- **Extensible** - Code propre et modulaire
|
||||||
|
|
||||||
|
## 🚀 Installation rapide
|
||||||
|
|
||||||
|
### Prérequis
|
||||||
|
|
||||||
|
- Docker et Docker Compose
|
||||||
|
- ~3GB d'espace disque (pour le modèle large-v3)
|
||||||
|
|
||||||
|
### Déploiement
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Cloner ou copier le dossier effi-stt
|
||||||
|
cd effi-stt
|
||||||
|
|
||||||
|
# Builder et démarrer
|
||||||
|
sudo docker compose up -d --build
|
||||||
|
|
||||||
|
# Attendre le téléchargement du modèle (3-5 min)
|
||||||
|
echo "⏳ Téléchargement du modèle..."
|
||||||
|
sleep 180
|
||||||
|
|
||||||
|
# Vérifier les logs
|
||||||
|
sudo docker logs effi-stt --tail 20
|
||||||
|
|
||||||
|
# Tester
|
||||||
|
curl http://localhost:8200/health
|
||||||
|
curl -X POST http://localhost:8200/v1/audio/transcriptions \
|
||||||
|
-F "file=@test.mp3" \
|
||||||
|
-F "language=fr"
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚙️ Configuration
|
||||||
|
|
||||||
|
### Variables d'environnement
|
||||||
|
|
||||||
|
| Variable | Défaut | Description |
|
||||||
|
|----------|--------|-------------|
|
||||||
|
| `MODEL_SIZE` | `large-v3` | Taille du modèle (tiny, base, small, medium, large-v3) |
|
||||||
|
| `DEVICE` | `cpu` | Device (cpu, cuda, auto) |
|
||||||
|
| `COMPUTE_TYPE` | `int8` | Type de calcul (int8, float16, float32) |
|
||||||
|
| `LANGUAGE` | `fr` | Langue par défaut |
|
||||||
|
| `CPU_THREADS` | `4` | Nombre de threads CPU |
|
||||||
|
| `PORT` | `8200` | Port d'écoute |
|
||||||
|
| `HOST` | `0.0.0.0` | Host d'écoute |
|
||||||
|
|
||||||
|
### Modèles disponibles
|
||||||
|
|
||||||
|
| Modèle | Taille | Qualité | Vitesse |
|
||||||
|
|--------|--------|---------|---------|
|
||||||
|
| `tiny` | 39 MB | Basse | Très rapide |
|
||||||
|
| `base` | 74 MB | Moyenne | Rapide |
|
||||||
|
| `small` | 244 MB | Bonne | Modérée |
|
||||||
|
| `medium` | 769 MB | Très bonne | Lente |
|
||||||
|
| `large-v3` | 3.1 GB | Excellente | Très lente |
|
||||||
|
|
||||||
|
## 🔧 Configuration Open WebUI
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
Admin Panel → Settings → Audio
|
||||||
|
|
||||||
|
STT:
|
||||||
|
Engine: OpenAI
|
||||||
|
URL: http://192.168.1.11:8200/v1
|
||||||
|
Model: whisper-1
|
||||||
|
Language: fr
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🧪 Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test health
|
||||||
|
curl http://localhost:8200/health
|
||||||
|
|
||||||
|
# Test transcription
|
||||||
|
curl -X POST http://localhost:8200/v1/audio/transcriptions \
|
||||||
|
-F "file=@audio.mp3" \
|
||||||
|
-F "language=fr" \
|
||||||
|
-F "response_format=json"
|
||||||
|
|
||||||
|
# Test avec format texte
|
||||||
|
curl -X POST http://localhost:8200/v1/audio/transcriptions \
|
||||||
|
-F "file=@audio.mp3" \
|
||||||
|
-F "language=fr" \
|
||||||
|
-F "response_format=text"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📁 Structure du projet
|
||||||
|
|
||||||
|
```
|
||||||
|
effi-stt/
|
||||||
|
├── main.py # Serveur FastAPI
|
||||||
|
├── Dockerfile # Image Docker
|
||||||
|
├── docker-compose.yml # Configuration Docker Compose
|
||||||
|
├── requirements.txt # Dépendances Python
|
||||||
|
└── README.md # Documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Améliorations futures (TODO)
|
||||||
|
|
||||||
|
- [ ] Support GPU ROCm pour AMD
|
||||||
|
- [ ] Streaming des résultats (transcription en temps réel)
|
||||||
|
- [ ] Support du format SRT/VTT avec timestamps
|
||||||
|
- [ ] Endpoint de détection de langue
|
||||||
|
- [ ] Métriques et monitoring (Prometheus)
|
||||||
|
- [ ] Support de la traduction (transcribe + translate)
|
||||||
|
|
||||||
|
## 📝 Notes
|
||||||
|
|
||||||
|
- Le modèle est téléchargé automatiquement au premier démarrage
|
||||||
|
- Le cache est persisté dans un volume Docker
|
||||||
|
- En CPU sur Intel J5040: ~5-10s pour 5s d'audio avec large-v3
|
||||||
|
|
||||||
|
## 🏗️ Développement
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Mode développement (hors Docker)
|
||||||
|
python -m venv venv
|
||||||
|
source venv/bin/activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
python main.py
|
||||||
|
|
||||||
|
# Build Docker manuel
|
||||||
|
sudo docker build -t effi-stt:local .
|
||||||
|
sudo docker run -p 8200:8200 effi-stt:local
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Créé avec ❤️ pour Effi**
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
services:
|
||||||
|
effi-stt:
|
||||||
|
build: .
|
||||||
|
container_name: effi-stt
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "8200:8200"
|
||||||
|
environment:
|
||||||
|
- MODEL_SIZE=large-v3
|
||||||
|
- DEVICE=cpu
|
||||||
|
- COMPUTE_TYPE=int8
|
||||||
|
- LANGUAGE=fr
|
||||||
|
- CPU_THREADS=4
|
||||||
|
volumes:
|
||||||
|
# Cache pour les modèles (évite de re-télécharger)
|
||||||
|
- effi-stt-cache:/root/.cache/huggingface
|
||||||
|
# Pour GPU ROCm (optionnel, désactivé par défaut)
|
||||||
|
# devices:
|
||||||
|
# - /dev/kfd
|
||||||
|
# - /dev/dri
|
||||||
|
# group_add:
|
||||||
|
# - "44"
|
||||||
|
# - "991"
|
||||||
|
# environment:
|
||||||
|
# - HSA_OVERRIDE_GFX_VERSION=11.0.0
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:8200/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 60s
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
effi-stt-cache:
|
||||||
|
driver: local
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
# 🎙️ Effi-STT - Speech-to-Text pour Effi
|
||||||
|
# API OpenAI-compatible avec faster-whisper
|
||||||
|
|
||||||
|
from fastapi import FastAPI, File, UploadFile, Form, HTTPException
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
import uvicorn
|
||||||
|
from faster_whisper import WhisperModel
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
|
from typing import Optional
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# Configuration du logging
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
app = FastAPI(
|
||||||
|
title="Effi-STT",
|
||||||
|
description="Speech-to-Text API compatible OpenAI pour Effi",
|
||||||
|
version="1.0.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
MODEL_SIZE = os.getenv("MODEL_SIZE", "large-v3")
|
||||||
|
DEVICE = os.getenv("DEVICE", "cpu")
|
||||||
|
COMPUTE_TYPE = os.getenv("COMPUTE_TYPE", "int8")
|
||||||
|
LANGUAGE = os.getenv("LANGUAGE", "fr")
|
||||||
|
|
||||||
|
logger.info(f"🎙️ Chargement du modèle Whisper: {MODEL_SIZE}")
|
||||||
|
logger.info(f" Device: {DEVICE}, Compute type: {COMPUTE_TYPE}")
|
||||||
|
|
||||||
|
# Chargement du modèle au démarrage
|
||||||
|
model = WhisperModel(
|
||||||
|
MODEL_SIZE,
|
||||||
|
device=DEVICE,
|
||||||
|
compute_type=COMPUTE_TYPE,
|
||||||
|
cpu_threads=int(os.getenv("CPU_THREADS", "4"))
|
||||||
|
)
|
||||||
|
logger.info("✅ Modèle chargé avec succès!")
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
async def health():
|
||||||
|
"""Endpoint de vérification de santé"""
|
||||||
|
return {"status": "healthy", "model": MODEL_SIZE}
|
||||||
|
|
||||||
|
@app.post("/v1/audio/transcriptions")
|
||||||
|
async def transcribe(
|
||||||
|
file: UploadFile = File(..., description="Fichier audio à transcrire"),
|
||||||
|
model_param: Optional[str] = Form(None, alias="model"),
|
||||||
|
language: Optional[str] = Form(LANGUAGE, description="Code langue (fr, en, auto)"),
|
||||||
|
prompt: Optional[str] = Form(None, description="Prompt optionnel"),
|
||||||
|
response_format: Optional[str] = Form("json", description="Format de réponse")
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Endpoint de transcription compatible API OpenAI
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
logger.info(f"📝 Transcription demandée: lang={language}, file={file.filename}")
|
||||||
|
|
||||||
|
# Sauvegarder le fichier temporairement
|
||||||
|
suffix = os.path.splitext(file.filename)[1] or ".mp3"
|
||||||
|
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp:
|
||||||
|
content = await file.read()
|
||||||
|
tmp.write(content)
|
||||||
|
tmp_path = tmp.name
|
||||||
|
logger.info(f" Fichier sauvegardé: {tmp_path} ({len(content)} bytes)")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Transcription avec faster-whisper
|
||||||
|
segments, info = model.transcribe(
|
||||||
|
tmp_path,
|
||||||
|
language=language if language != "auto" else None,
|
||||||
|
task="transcribe",
|
||||||
|
beam_size=5,
|
||||||
|
best_of=5,
|
||||||
|
condition_on_previous_text=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Concaténer tous les segments
|
||||||
|
text_parts = []
|
||||||
|
for segment in segments:
|
||||||
|
text_parts.append(segment.text)
|
||||||
|
|
||||||
|
full_text = " ".join(text_parts).strip()
|
||||||
|
|
||||||
|
logger.info(f"✅ Transcription réussie: '{full_text[:50]}...'")
|
||||||
|
logger.info(f" Langue détectée: {info.language}, Probabilité: {info.language_probability:.2f}")
|
||||||
|
|
||||||
|
# Format OpenAI standard
|
||||||
|
if response_format == "text":
|
||||||
|
return full_text
|
||||||
|
elif response_format == "srt":
|
||||||
|
# TODO: Implémenter format SRT
|
||||||
|
return full_text
|
||||||
|
elif response_format == "vtt":
|
||||||
|
# TODO: Implémenter format VTT
|
||||||
|
return full_text
|
||||||
|
else:
|
||||||
|
# Format JSON (défaut)
|
||||||
|
return JSONResponse(content={"text": full_text})
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Nettoyage
|
||||||
|
os.unlink(tmp_path)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ Erreur transcription: {str(e)}")
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
@app.get("/v1/models")
|
||||||
|
async def list_models():
|
||||||
|
"""Liste les modèles disponibles (compatibilité OpenAI)"""
|
||||||
|
return {
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "whisper-1",
|
||||||
|
"object": "model",
|
||||||
|
"created": 1677610602,
|
||||||
|
"owned_by": "openai",
|
||||||
|
"permission": [],
|
||||||
|
"root": "whisper-1",
|
||||||
|
"parent": None
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"object": "list"
|
||||||
|
}
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
port = int(os.getenv("PORT", "8200"))
|
||||||
|
host = os.getenv("HOST", "0.0.0.0")
|
||||||
|
|
||||||
|
logger.info(f"🚀 Démarrage du serveur sur http://{host}:{port}")
|
||||||
|
logger.info(f" Endpoints:")
|
||||||
|
logger.info(f" - GET /health")
|
||||||
|
logger.info(f" - POST /v1/audio/transcriptions")
|
||||||
|
logger.info(f" - GET /v1/models")
|
||||||
|
|
||||||
|
uvicorn.run(app, host=host, port=port, log_level="info")
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
fastapi==0.115.0
|
||||||
|
uvicorn[standard]==0.32.0
|
||||||
|
faster-whisper==1.1.0
|
||||||
|
python-multipart==0.0.17
|
||||||
Reference in New Issue
Block a user