From c32240e14b038aede6f522281305a66413bfdfa0 Mon Sep 17 00:00:00 2001 From: Garegin Harutyunyan Date: Mon, 23 Mar 2026 17:44:48 +0400 Subject: [PATCH] Fixed review comments. --- changelog/4022.changed.md | 1 + .../07p-interruptible-krisp-viva.py | 6 +++--- src/pipecat/audio/vad/krisp_viva_vad.py | 4 ++-- tests/test_krisp_viva_vad.py | 17 +++++++++-------- 4 files changed, 15 insertions(+), 13 deletions(-) create mode 100644 changelog/4022.changed.md diff --git a/changelog/4022.changed.md b/changelog/4022.changed.md new file mode 100644 index 000000000..7669f0810 --- /dev/null +++ b/changelog/4022.changed.md @@ -0,0 +1 @@ +- Added `KrispVivaVadAnalyzer` for Voice Activity Detection using the Krisp VIVA SDK (requires `krisp_audio`). diff --git a/examples/foundational/07p-interruptible-krisp-viva.py b/examples/foundational/07p-interruptible-krisp-viva.py index ca11ef90e..b8fe82d08 100644 --- a/examples/foundational/07p-interruptible-krisp-viva.py +++ b/examples/foundational/07p-interruptible-krisp-viva.py @@ -60,19 +60,19 @@ transport_params = { "daily": lambda: DailyParams( audio_in_enabled=True, audio_out_enabled=True, - vad_analyzer=SileroVADAnalyzer(params=VADParams(stop_secs=0.2)), + vad_analyzer=SileroVADAnalyzer(params=VADParams(stop_secs=0.2)), # or KrispVivaVadAnalyzer audio_in_filter=KrispVivaFilter(), ), "twilio": lambda: FastAPIWebsocketParams( audio_in_enabled=True, audio_out_enabled=True, - vad_analyzer=SileroVADAnalyzer(params=VADParams(stop_secs=0.2)), + vad_analyzer=SileroVADAnalyzer(params=VADParams(stop_secs=0.2)), # or KrispVivaVadAnalyzer audio_in_filter=KrispVivaFilter(), ), "webrtc": lambda: TransportParams( audio_in_enabled=True, audio_out_enabled=True, - vad_analyzer=SileroVADAnalyzer(params=VADParams(stop_secs=0.2)), + vad_analyzer=SileroVADAnalyzer(params=VADParams(stop_secs=0.2)), # or KrispVivaVadAnalyzer audio_in_filter=KrispVivaFilter(), ), } diff --git a/src/pipecat/audio/vad/krisp_viva_vad.py b/src/pipecat/audio/vad/krisp_viva_vad.py index 701a00752..2bcc13ab2 100644 --- a/src/pipecat/audio/vad/krisp_viva_vad.py +++ b/src/pipecat/audio/vad/krisp_viva_vad.py @@ -207,8 +207,8 @@ class KrispVivaVadAnalyzer(VADAnalyzer): logger.error(f"Error analyzing audio with Krisp VIVA VAD: {e}", exc_info=True) return 0.0 - def __del__(self): - """Cleanup when the analyzer is destroyed.""" + async def cleanup(self): + """Cleanup analyzer resources.""" try: self._session = None KrispVivaSDKManager.release() diff --git a/tests/test_krisp_viva_vad.py b/tests/test_krisp_viva_vad.py index 64ff6ac92..12b06bc70 100644 --- a/tests/test_krisp_viva_vad.py +++ b/tests/test_krisp_viva_vad.py @@ -6,6 +6,7 @@ """Unit tests for KrispVivaVadAnalyzer.""" +import asyncio import os import sys import tempfile @@ -132,7 +133,7 @@ class TestKrispVivaVadAnalyzer(unittest.TestCase): self.assertIn("Model path", str(context.exception)) # acquire() is not called because exception is raised before it self.mock_sdk_manager.acquire.assert_not_called() - # release() is called in exception handler and also in __del__ when object is destroyed + # release() is called in the initialization exception handler self.assertGreaterEqual(self.mock_sdk_manager.release.call_count, 1) def test_initialization_with_invalid_extension(self): @@ -148,7 +149,7 @@ class TestKrispVivaVadAnalyzer(unittest.TestCase): self.assertIn(".kef extension", str(context.exception)) # acquire() is not called because exception is raised before it self.mock_sdk_manager.acquire.assert_not_called() - # release() is called in exception handler and also in __del__ when object is destroyed + # release() is called in the initialization exception handler self.assertGreaterEqual(self.mock_sdk_manager.release.call_count, 1) finally: os.unlink(tmp_path) @@ -160,7 +161,7 @@ class TestKrispVivaVadAnalyzer(unittest.TestCase): # acquire() is not called because exception is raised before it self.mock_sdk_manager.acquire.assert_not_called() - # release() is called in exception handler and also in __del__ when object is destroyed + # release() is called in the initialization exception handler self.assertGreaterEqual(self.mock_sdk_manager.release.call_count, 1) def test_initialization_with_custom_frame_duration(self): @@ -348,19 +349,19 @@ class TestKrispVivaVadAnalyzer(unittest.TestCase): self.assertGreaterEqual(confidence, 0.0) self.assertLessEqual(confidence, 1.0) - def test_cleanup_on_destruction(self): - """Test that cleanup happens when analyzer is destroyed.""" + def test_cleanup(self): + """Test that cleanup releases resources explicitly.""" analyzer = KrispVivaVadAnalyzer(model_path=self.model_path) analyzer.set_sample_rate(16000) # Reset mock to track calls self.mock_sdk_manager.release.reset_mock() - # Delete analyzer (triggers __del__) - del analyzer + asyncio.run(analyzer.cleanup()) - # Verify SDK was released + # Verify SDK was released and session was cleared self.mock_sdk_manager.release.assert_called_once() + self.assertIsNone(analyzer._session) def test_initialization_with_vad_params(self): """Test analyzer initialization with VAD parameters."""