Remplace la voicebank générée par Kokoro (timbre anglais sur français phonémisé -> accent que Qwen3 clonait) par 41 vraies voix FR issues de CML-TTS (livres audio studio) : 1 narrateur dédié, 18F/14M nommées, 4F/4M anonymes réservées. - scripts/import_voices.py : import multi-shards parquet, 1 clip/locuteur (le plus propre via levenshtein), genre estimé par F0 (YIN, anti-octave), filtre débit de parole (ref_text aligné sur l'audio). - VoiceEntry.anonymous + assign_voices : les figurants « anonyme (...) » tirent dans un pool réservé, jamais mélangé avec les voix nommées ; narrateur dédié (fr_narrator remplace fr_f_siwis). - dedup._anon_attrs : genre/âge déduits du nom anonyme (bon genre de voix). - tts/qwen3.py : garde-fou anti-dérive (rejette/réessaie les sorties en boucle ou coupées en estimant la durée plausible du chunk). Limite connue : Qwen3 ne sait pas synthétiser les fragments d'1-2 mots (incises, titres) -> trous ; à traiter (repli Kokoro ou fusion des incises). Inclut aussi du travail en cours antérieur (refacto backend LLM pluggable mlx/lmstudio, benchmark, ajustements frontend/API). Claude-Session: https://claude.ai/code/session_01XSVvcy1mfb4k1xDgib9vVU
44 lines
1.6 KiB
Python
44 lines
1.6 KiB
Python
"""Abstraction des moteurs LLM (backend pluggable).
|
|
|
|
Calque du pattern TTS (`tts/base.py`) : un backend ne fait *qu'une* chose,
|
|
transformer une liste de messages (role/content) en texte brut. Toute la logique
|
|
agnostique (calcul des parametres depuis les Settings, retrait de la pensee,
|
|
extraction JSON tolerante, retries) vit dans la facade `client.LLM`, jamais
|
|
dupliquee par backend.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from abc import ABC, abstractmethod
|
|
from typing import Callable, Optional
|
|
|
|
|
|
class LLMBackend(ABC):
|
|
"""Interface commune a tous les moteurs LLM."""
|
|
|
|
name: str = "base"
|
|
|
|
def __init__(self, model_ref: str):
|
|
# Reference du modele, interpretee par le backend : id mlx-community
|
|
# (mlx) ou nom du modele charge dans LM Studio (lmstudio, vide -> actif).
|
|
self.model_ref = model_ref
|
|
|
|
@abstractmethod
|
|
def complete(
|
|
self,
|
|
messages: list[dict],
|
|
*,
|
|
max_tokens: int,
|
|
temperature: float,
|
|
reasoning: bool,
|
|
token_sink: Optional[Callable[[str], None]] = None,
|
|
) -> str:
|
|
"""Genere et renvoie le texte BRUT (chaine de pensee incluse).
|
|
|
|
- `messages` : liste {role, content} (system optionnel + user).
|
|
- `reasoning` : si vrai, le modele peut emettre une chaine de pensee ;
|
|
le backend peut s'arreter des que le JSON post-pensee est complet. La
|
|
facade retire la pensee en aval (`_strip_reasoning`).
|
|
- `token_sink` : si fourni, appele avec chaque morceau de texte au fil de
|
|
la generation (streaming pour `inkflow benchmark --stream`).
|
|
"""
|