mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-12 01:36:54 +02:00
fix(voix): refuser les lectures Gemini qui sautent des mots
Le contrôle lettre à lettre acceptait une lecture dès 85 %, alors qu'une lecture complète atteint ~97 % (petites erreurs de Whisper) : une lecture à 86 % (mots sautés) passait sans nouvelle génération. Seuil porté à 92 %. Les mots non entendus sont nommés dans les journaux et l'avertissement, pour savoir ce qui manquait. Le modèle Whisper, déjà dans l'image, est chargé sans interroger Hugging Face (HF_HUB_OFFLINE). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Ze4bs7tpF1KGWUk6ZZSZ4
This commit is contained in:
3 files changed
+34
-2
No files matched your search
@@ -30,6 +30,8 @@ ENV WHISPER_MODEL=${WHISPER_MODEL} \
|
||||
HOME=/tmp \
|
||||
XDG_CACHE_HOME=/tmp/.cache
|
||||
RUN python -c "from faster_whisper import WhisperModel; WhisperModel('${WHISPER_MODEL}', device='cpu', compute_type='int8')"
|
||||
# Modèle déjà présent : plus aucune requête vers Hugging Face au démarrage
|
||||
ENV HF_HUB_OFFLINE=1
|
||||
|
||||
COPY main.py .
|
||||
COPY app ./app
|
||||
|
||||
@@ -9,7 +9,7 @@ pas rogner une syllabe.
|
||||
|
||||
import asyncio
|
||||
import difflib
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
@@ -17,16 +17,21 @@ import numpy as np
|
||||
from . import proc
|
||||
from .align import Word, normalize
|
||||
|
||||
MIN_COVERAGE = 0.92
|
||||
|
||||
|
||||
@dataclass
|
||||
class ReadingCheck:
|
||||
coverage: float # part des mots attendus effectivement entendus
|
||||
extra: float # mots entendus en trop (consigne lue, ajouts), rapportés au texte
|
||||
ending_ok: bool # la fin du texte est bien lue
|
||||
missing: list[str] = field(default_factory=list) # mots attendus non entendus (diagnostic)
|
||||
|
||||
@property
|
||||
def acceptable(self) -> bool:
|
||||
return self.coverage >= 0.85 and self.extra <= 0.25 and self.ending_ok
|
||||
# Comparaison lettre à lettre : une lecture complète atteint ~97 %
|
||||
# (petites erreurs de Whisper) ; sous 92 %, des mots ont été sautés.
|
||||
return self.coverage >= MIN_COVERAGE and self.extra <= 0.25 and self.ending_ok
|
||||
|
||||
def describe(self) -> str:
|
||||
parts = [f"{self.coverage:.0%} des mots lus"]
|
||||
@@ -34,6 +39,8 @@ class ReadingCheck:
|
||||
parts.append("fin tronquée")
|
||||
if self.extra > 0.25:
|
||||
parts.append("mots ajoutés")
|
||||
if self.missing and self.coverage < MIN_COVERAGE:
|
||||
parts.append("non entendus : " + " ".join(self.missing[:12]))
|
||||
return ", ".join(parts)
|
||||
|
||||
|
||||
@@ -59,9 +66,21 @@ def check_reading(expected_text: str, spoken: list[Word]) -> ReadingCheck:
|
||||
coverage=matched / len(expected),
|
||||
extra=max(0, len(heard) - matched) / len(expected),
|
||||
ending_ok=tail_matched >= min(4, len(expected) - tail_start),
|
||||
missing=_missing_words(expected_text, spoken),
|
||||
)
|
||||
|
||||
|
||||
def _missing_words(expected_text: str, spoken: list[Word]) -> list[str]:
|
||||
"""Mots attendus sans équivalent proche dans ce qui a été entendu."""
|
||||
heard = {normalize(w.text) for w in spoken}
|
||||
missing = []
|
||||
for word in expected_text.split():
|
||||
token = normalize(word)
|
||||
if token and not any(difflib.SequenceMatcher(a=token, b=h).ratio() >= 0.7 for h in heard):
|
||||
missing.append(word.strip(".,!?;:…"))
|
||||
return missing
|
||||
|
||||
|
||||
# Enveloppe sonore : niveau de la voix toutes les 10 ms
|
||||
FRAME = 0.01
|
||||
# Est « audible » ce qui dépasse le niveau de crête moins 38 dB : un seuil
|
||||
|
||||
@@ -60,3 +60,14 @@ def test_small_recognition_errors_are_not_missing_words():
|
||||
heard = _heard("Découvrez nos plans de saison à petit prix. On vous attend ce week -end.")
|
||||
check = check_reading(expected, heard)
|
||||
assert check.acceptable, check
|
||||
|
||||
|
||||
def test_skipped_words_are_detected_and_named():
|
||||
expected = (
|
||||
"Pour Halloween découvrez nos costumes nos bonbons et toute notre décoration effrayante en magasin."
|
||||
)
|
||||
heard = _heard("Pour Halloween découvrez nos costumes et toute notre décoration en magasin.")
|
||||
check = check_reading(expected, heard)
|
||||
assert not check.acceptable
|
||||
assert "bonbons" in check.missing and "effrayante" in check.missing
|
||||
assert "non entendus" in check.describe()
|
||||
Reference in new issue
Block a user