Files
colgora ba1813c583 Voicebank : vraies voix françaises (CML-TTS) + pool anonyme + garde-fou Qwen3
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
2026-06-21 21:32:31 +02:00

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`).
"""