correction agent

This commit is contained in:
Denis Defolie
2026-10-06 18:16:06 +02:00
parent 3051d6abbe
commit ec88de1b3c
15 changed files with 207 additions and 47 deletions
+23
View File
@@ -105,6 +105,29 @@ def test_someone_else_cannot_confirm(policy: AccessPolicy) -> None:
assert resolution.answer.startswith("Seul Denis")
def test_short_yes_is_accepted_when_the_requester_stays_the_closest_voice(policy: AccessPolicy) -> None:
"""Essai réel du 2026-10-06 : « OUI » de Denis à 0,35, sous le seuil d'identification."""
policy.ask("esp32-01", DOOR_OPEN, "Denis")
resolution = policy.resolve("esp32-01", "OUI", None, closest="Denis", score=0.35)
assert resolution is not None and resolution.command == DOOR_OPEN
@pytest.mark.parametrize(
("speaker", "closest", "score"),
[
(None, "Denis", 0.12), # trop loin de Denis : n'importe qui
(None, "Jessica", 0.40), # plus proche de quelqu'un d'autre
(None, None, None), # pas d'empreinte du tout
("Jessica", "Jessica", 0.8), # reconnue comme quelqu'un d'autre
],
)
def test_short_yes_from_someone_else_is_refused(policy: AccessPolicy, speaker: str | None,
closest: str | None, score: float | None) -> None:
policy.ask("esp32-01", DOOR_OPEN, "Denis")
resolution = policy.resolve("esp32-01", "oui", speaker, closest=closest, score=score)
assert resolution is not None and resolution.command is None
def test_confirmation_expires(policy: AccessPolicy, clock: Clock) -> None:
policy.ask("esp32-01", DOOR_OPEN, "Denis")
clock.now += 16
+4
View File
@@ -57,6 +57,10 @@ def with_mac(mac: str = MAC) -> MachinesSettings:
("Éteins le P.C. du bureau.", "shutdown"),
("Tu peux redémarrer l'ordinateur du bureau ?", "reboot"),
("Verrouille le PC du bureau", "lock"),
# Mots anglais, tels que Whisper les a écrits à l'essai réel du 2026-10-06.
("Locke le PC du bureau", "lock"),
("Reboot le PC du bureau", "reboot"),
("Shut down le PC du bureau", "shutdown"),
("Allume le PC du bureau", "wake"),
("Le PC du bureau", None),
],
+32 -1
View File
@@ -131,7 +131,7 @@ def session(duration_s: float = 1.5, peak_dbfs: float = -12.0) -> SessionResult:
def pipeline(stt: FakeStt, tts: FakeTts, player: FakePlayer, vad: FakeVad | None = None,
diarizer: FakeDiarizer | None = None, identifier: FakeIdentifier | None = None,
diarizer: FakeDiarizer | None = None, identifier: FakeIdentifier | ShortYesIdentifier | None = None,
publisher: FakePublisher | None = None, assistant: EchoAssistant | None = None,
controller: FakeController | None = None, policy: AccessPolicy | None = None) -> VoicePipeline:
ticks = iter([10.0, 11.2]) # début, puis réponse prête 1,2 s plus tard
@@ -324,3 +324,34 @@ def test_sensitive_command_waits_for_yes() -> None:
controller=controller, policy=policy).handle(session())
assert second is not None and second.command == door
assert [call[1] for call in controller.calls] == [door]
class ShortYesIdentifier:
"""Un « oui » de 0,7 s : sous le seuil, mais Denis reste le profil le plus proche (ou non)."""
def __init__(self, closest: str, score: float) -> None:
self._verdict = Identification(name=None, score=score, closest=closest)
def identify(self, embedding: np.ndarray) -> Identification:
return self._verdict
@pytest.mark.parametrize(("closest", "score", "executed"), [("Denis", 0.35, True), ("Jessica", 0.35, False),
("Denis", 0.10, False)])
def test_short_yes_confirms_only_from_the_closest_voice(closest: str, score: float, executed: bool) -> None:
"""Essai réel du 2026-10-06 : « OUI » de Denis à 0,35 refusé comme « voix inconnue »."""
events: list[str] = []
controller = FakeController(events)
policy = AccessPolicy(ACCESS, HOME)
door = DeviceCommand("open", "door", "salon")
pipeline(FakeStt("ouvre la porte", words=DENIS_WORDS), FakeTts(), FakePlayer(events),
diarizer=FakeDiarizer(), identifier=FakeIdentifier(), assistant=EchoAssistant(door),
controller=controller, policy=policy).handle(session())
second = pipeline(FakeStt("oui", words=(Word(" oui", 0.0, 0.1),)), FakeTts(), FakePlayer(events),
diarizer=FakeDiarizer(), identifier=ShortYesIdentifier(closest, score),
assistant=EchoAssistant(), controller=controller, policy=policy).handle(session())
assert second is not None
assert [call[1] for call in controller.calls] == ([door] if executed else [])
assert second.answer == ("J'ouvre la porte du salon." if executed else "Seul Denis peut confirmer. J'annule.")