feat: integra VAD ao controle de turnos da conversa
This commit is contained in:
93
tests/test_vad_session_composition.py
Normal file
93
tests/test_vad_session_composition.py
Normal file
@@ -0,0 +1,93 @@
|
||||
"""Composition selects VAD input and routes application events to the channel."""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
|
||||
from voice_assistant.agent.mock import MockAgentAdapter
|
||||
from voice_assistant.application.events import TurnOpened, TurnInterrupted
|
||||
from voice_assistant.audio_gateway.app import main
|
||||
from voice_assistant.config.loader import load_config
|
||||
from voice_assistant.tts.mock import MockTTSAdapter
|
||||
from voice_assistant.vad.adapter import VADAdapter
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def anyio_backend():
|
||||
return 'asyncio'
|
||||
|
||||
|
||||
class VAD(VADAdapter):
|
||||
async def process(self, audio):
|
||||
return ()
|
||||
|
||||
async def flush(self):
|
||||
return ()
|
||||
|
||||
async def reset(self):
|
||||
pass
|
||||
|
||||
async def aclose(self):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.parametrize('server_vad', [False, True])
|
||||
async def test_composition_selects_vad_and_accepts_environment_policy(monkeypatch, server_vad):
|
||||
config = load_config('development', config_dir=Path(__file__).resolve().parents[1] / 'config')
|
||||
assert config.voice_input == {
|
||||
'pre_roll_ms': 200, 'history_ms': 2000, 'max_input_chunk_bytes': 65536,
|
||||
}
|
||||
monkeypatch.setattr(main, 'app_config', config)
|
||||
monkeypatch.setattr(main, 'create_agent_adapter', lambda **kwargs: MockAgentAdapter())
|
||||
monkeypatch.setattr(main, 'create_tts_adapter', lambda **kwargs: MockTTSAdapter())
|
||||
detector = VAD()
|
||||
calls = []
|
||||
|
||||
def factory(**kwargs):
|
||||
calls.append(kwargs)
|
||||
return detector
|
||||
|
||||
monkeypatch.setattr(main, 'create_vad_adapter', factory)
|
||||
session = await main.create_voice_session(AsyncMock(), None, server_vad=server_vad)
|
||||
try:
|
||||
if server_vad:
|
||||
assert len(calls) == 1
|
||||
assert calls[0]['config'] is config
|
||||
await session.receive_audio(bytes(512))
|
||||
else:
|
||||
assert calls == []
|
||||
with pytest.raises(Exception, match='requires VAD'):
|
||||
await session.receive_audio(bytes(512))
|
||||
finally:
|
||||
await session.aclose()
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_bad_policy_releases_all_created_providers(monkeypatch):
|
||||
config = main.app_config.model_copy(update={'voice_input': {'history_ms': 0}})
|
||||
agent, tts, vad = MockAgentAdapter(), MockTTSAdapter(), VAD()
|
||||
for provider in [agent, tts, vad]:
|
||||
provider.aclose = AsyncMock()
|
||||
monkeypatch.setattr(main, 'app_config', config)
|
||||
monkeypatch.setattr(main, 'create_agent_adapter', lambda **kwargs: agent)
|
||||
monkeypatch.setattr(main, 'create_tts_adapter', lambda **kwargs: tts)
|
||||
monkeypatch.setattr(main, 'create_vad_adapter', lambda **kwargs: vad)
|
||||
with pytest.raises(ValueError, match='history_ms'):
|
||||
await main.create_voice_session(AsyncMock(), None, server_vad=True)
|
||||
for provider in [agent, tts, vad]:
|
||||
provider.aclose.assert_awaited_once()
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_automatic_turn_events_map_to_existing_protocol():
|
||||
socket = AsyncMock()
|
||||
await main.send_voice_event(socket, TurnOpened('session', 2))
|
||||
await main.send_voice_event(socket, TurnInterrupted('session', 1))
|
||||
events = [json.loads(call.args[0]) for call in socket.send_text.await_args_list]
|
||||
assert events == [
|
||||
{'type': 'turn.started', 'session_id': 'session', 'turn_id': 2},
|
||||
{'type': 'interrupted', 'session_id': 'session', 'turn_id': 1},
|
||||
]
|
||||
Reference in New Issue
Block a user