feat: experimental real-time streaming transcription (fixes #320) - #387
feat: experimental real-time streaming transcription (fixes #320)#387jatinkrmalik wants to merge 9 commits into
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #387 +/- ##
==========================================
+ Coverage 79.91% 80.64% +0.73%
==========================================
Files 30 31 +1
Lines 4615 4955 +340
Branches 699 770 +71
==========================================
+ Hits 3688 3996 +308
- Misses 779 799 +20
- Partials 148 160 +12 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
- Add real_time_streaming config option (default: true) - Add streaming_chunk_duration config option (default: 3.0s) - Modify recognition loop to process audio chunks at regular intervals - Keep existing silence-based chunking when streaming is disabled - Stream audio every 3 seconds instead of waiting for silence pauses This enables true real-time transcription as users speak, rather than waiting for silence periods to process entire utterances.
Implement streaming transcription as an experimental feature gated behind an 'experimental_streaming' config toggle (off by default). - Add TranscriptBuffer with LA-2 dedup for overlapping segment deduplication - Add streaming engine integration for Vosk (PartialResult API) and Whisper (sliding window with transcript buffer) - Add streaming config to settings dialog with toggle and chunk duration spin - Register streaming callback in tray indicator for live text injection - Add StreamingCallbackProtocol and extend SpeechRecognitionManagerProtocol - Add 23 unit tests covering buffer logic, config defaults, and integration - Update existing test_main.py for new constructor kwargs
…- fix flaky streaming config tests by using robust temp config paths\n- prevent duplicate text injection by removing tray-level final injection\n- make TranscriptBuffer.flush emit deltas only; avoid re-emitting committed prefixes\n- align streaming Whisper inference with non-streaming settings and model locking\n- avoid Vosk overlap replay and add partial/final duplicate suppression\n- fix settings dialog read path for VAD/silence values from speech_recognition section\n- document streaming as experimental/WIP in README and docs\n\nUltraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)\n\nCo-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
test_recognition_manager.py had stacked patches on os.makedirs with incorrect LIFO tearDown ordering, leaving os.makedirs as a MagicMock for subsequent test modules. Fix tearDown ordering and harden TestStreamingConfigIntegration to restore real stdlib functions before use.
Cover streaming-specific code paths in recognition_manager.py: - Callback registration (add/remove/multiple) - _emit_text with voice commands, exceptions, edge cases - _enqueue_streaming_segment (overlap, queue full, final flag) - _process_streaming_segment routing (vosk/whisper/whisper_cpp) - _process_streaming_vosk (partial/final, dedup, JSON errors) - _process_streaming_whisper (transcribe, auto-language, cpp delegate) - _perform_recognition streaming segment handling - Init streaming attributes and state reset - TranscriptBuffer edge cases and TrayIndicator callback - CommonTypes protocol coverage Improves patch coverage from ~30% to ~50% for recognition_manager.py, transcript_buffer.py to 98%, common_types.py to 71%. Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
34a639f to
cb6b6bd
Compare
|
A note from local testing/investigation: The streaming behavior is much more straightforward for Vosk than for Whisper/whisper.cpp. Vosk has a native incremental recognizer API: we can feed audio continuously via Whisper is different. It is fundamentally a windowed transcription model, not a true streaming decoder. For the experimental mode here we are repeatedly transcribing short overlapping chunks and then trying to decide what text is stable enough to inject. That creates a few practical problems:
So my current read is: keeping this as an opt-in experimental mode is the right shape. Vosk streaming can be treated as closer to "native streaming". Whisper/whisper.cpp streaming should be framed as a best-effort low-latency chunking mode, where users may need to tune chunk duration upward for better punctuation and flow. |
Summary
Implements real-time streaming transcription as an experimental feature (off by default) that shows text as you speak, rather than waiting until you stop recording.
Fixes #320
Related discussions: #382, #94
Changes
Phase 1: Config & Engine Plumbing
config_manager.py: Addedexperimental_streaming,streaming_chunk_duration_ms,streaming_overlap_msto DEFAULT_CONFIG (all off/default)common_types.py: AddedStreamingCallbackProtocoland extendedSpeechRecognitionManagerProtocolwith streaming methodsmain.py: Passes streaming config from settings toSpeechRecognitionManagerconstructorPhase 2: TranscriptBuffer (LA-2 Dedup)
transcript_buffer.py: New file implementing Local Agreement (LA-2) policy for word-level dedup across overlapping audio segments. Prevents flickering and duplicate text injection.Phase 3: Streaming Engine
recognition_manager.py:PartialResult()APITranscriptBufferwith sliding windowadd_streaming_callback/remove_streaming_callbackfor external consumersreconfigureaccepts streaming params with validationPhase 4: UI & Integration
settings_dialog.py: Added streaming toggle and chunk duration spin in Recognition Settingstray_indicator.py: Registers streaming callback for live text injectionTests
test_streaming.py: 23 unit tests covering TranscriptBuffer logic (basic, flush, reset, overlap, case-insensitive), config defaults, and config manager round-trip integrationtest_main.py: Updated for new constructor kwargsArchitecture
Testing
black --line-length 100flake8clean on all changed filesHow to Test
Notes
experimental_streamingconfig keyPartialResult()— fast and well-testedTranscriptBufferLA-2 policy ensures words are only committed when seen in consecutive passes, preventing duplicates from overlapping audio segments