• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

0plus1 / sottovoce / #4

10 Dec 2025 01:17PM UTC coverage: 72.956% (+0.4%) from 72.589%
#4

push

coveralls-python

0plus1
Add speech synthesis

89 of 133 new or added lines in 7 files covered. (66.92%)

6 existing lines in 2 files now uncovered.

232 of 318 relevant lines covered (72.96%)

0.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

37.93
/main.py
1
from __future__ import annotations
1✔
2

3
import sys
1✔
4
from typing import Optional, Protocol, runtime_checkable
1✔
5
from pathlib import Path
1✔
6

7
from src.filteredWarnings import suppress_noisy_warnings
1✔
8
# Apply warning filters before importing libraries that emit noisy warnings.
9
suppress_noisy_warnings()
1✔
10
from RealtimeSTT import AudioToTextRecorder  # pyright: ignore[reportMissingTypeStubs]  # noqa: E402
1✔
11

12
from src.config import Settings, get_settings  # noqa: E402
1✔
13
from src.llm_client import LLMClient, LLMConfig # noqa: E402
1✔
14
from src.session_logger import SessionLogger # noqa: E402
1✔
15
from src.tts_engine import TtsEngine # noqa: E402
1✔
16

17
@runtime_checkable
1✔
18
class RecorderProtocol(Protocol):
1✔
19
    """Minimal protocol we rely on from RealtimeSTT.AudioToTextRecorder."""
20

21
    def text(self, on_transcription_finished: Optional[object] = None) -> Optional[str]: ...
22

23
    def __enter__(self) -> "RecorderProtocol": ...
24

25
    def __exit__(
26
        self,
27
        exc_type: Optional[type[BaseException]],
28
        exc_value: Optional[BaseException],
29
        traceback: Optional[object],
30
    ) -> None: ...
31

32

33
def build_recorder(settings: Settings) -> RecorderProtocol:
1✔
34
    """Create a recorder configured from Settings."""
35
    recorder: RecorderProtocol = AudioToTextRecorder(
1✔
36
        model=settings.rtstt_model,
37
        compute_type=settings.rtstt_compute_type,
38
        language=settings.rtstt_language,
39
        use_microphone=settings.rtstt_use_microphone,
40
        no_log_file=True
41
    )
42
    return recorder
1✔
43

44

45
def build_llm_client(settings: Settings) -> LLMClient:
1✔
46
    """Instantiate the LLM client configured for LM Studio (OpenAI-compatible)."""
47
    cfg = LLMConfig(
1✔
48
        endpoint=settings.llm_endpoint,
49
        model=settings.llm_model,
50
        timeout=settings.llm_timeout,
51
    )
52
    return LLMClient(cfg)
1✔
53

54

55
def transcribe_loop(recorder: RecorderProtocol, llm_client: LLMClient, logger: SessionLogger, tts_engine: TtsEngine) -> None:
1✔
56
    """Sequential listen -> transcribe -> LLM -> TTS -> log loop."""
57
    print("Initialising. Press Ctrl+C to quit.")
×
58
    print(f"Session log: {logger.path()}")
×
59
    while True:
×
60
        user_text = recorder.text()
×
61
        if not user_text:
×
62
            continue
×
63
        print(f"[YOU] {user_text}")
×
64
        print("[SYSTEM] Processing response...")
×
65
        try:
×
66
            llm_response = llm_client.complete(user_text)
×
67
        except Exception as exc:
×
68
            print(f"[SYSTEM] LLM call failed: {exc}", file=sys.stderr)
×
69
            continue
×
70
        print(f"[ASSISTANT] {llm_response}")
×
71
        logger.append_turn(user_text, llm_response)
×
NEW
72
        if tts_engine.enabled:
×
NEW
73
            try:
×
NEW
74
                print("[SYSTEM] TTS speaking...")
×
NEW
75
                audio_path = logger.path().with_suffix(".wav")
×
NEW
76
                tts_engine.synthesize(llm_response, audio_path)
×
NEW
77
            except Exception as exc:
×
NEW
78
                print(f"[SYSTEM] TTS failed: {exc}", file=sys.stderr)
×
79

80

81
def main() -> None:
1✔
NEW
82
    suppress_noisy_warnings()
×
UNCOV
83
    settings = get_settings()
×
UNCOV
84
    try:
×
85
        llm_client = build_llm_client(settings)
×
NEW
86
        prompt_path = Path("PROMPT.md")
×
NEW
87
        if prompt_path.exists():
×
NEW
88
            llm_client.load_system_prompt(prompt_path)
×
89
        logger = SessionLogger(directory=settings.session_logs_dir)
×
NEW
90
        tts_engine = TtsEngine(settings)
×
91
        with build_recorder(settings) as recorder:
×
NEW
92
            transcribe_loop(recorder, llm_client, logger, tts_engine)
×
93
    except KeyboardInterrupt:
×
94
        print("\nExiting.")
×
95
    except Exception as exc:  # pragma: no cover - runtime path
96
        print(f"Fatal error: {exc}", file=sys.stderr)
97
        raise
98

99

100
if __name__ == "__main__":
1✔
101
    main()
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc