feat: enable Whisper STT locally (medium.en)#5
Conversation
Ensures containers recover automatically after crashes or Docker restarts, supporting Docker Desktop auto-start on Windows login. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
whisper.enabled/model/language/device, ollama.default_model, openai.model, and paths now live in config.yaml (volume-mounted, editable without rebuild). .env reduced to secrets only (OLLAMA_URL). Env var overrides still work. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ck, WHISPER_DEVICE wired, default enabled=false
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
Pull request overview
Enables local Whisper STT configuration via config.yaml (including medium.en + auto device selection) and improves container resiliency with restart policies.
Changes:
- Move non-secret runtime settings (models, Whisper settings, paths, model filter) from environment variables to
backend/config.yaml. - Add Whisper device selection and handle missing STT dependency imports during app startup.
- Add
restart: unless-stoppedtoollamaandapiservices in Docker Compose.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
voice-to-mermaid/backend/main.py |
Loads settings from config.yaml, adds Whisper device config, and attempts graceful handling when STT deps aren’t installed. |
voice-to-mermaid/backend/docker-compose.yml |
Adds restart policies for ollama and api. |
voice-to-mermaid/backend/config.yaml |
Defines defaults for Ollama/OpenAI/Whisper and paths; enables Whisper by default. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| except ImportError: | ||
| log.warning("whisper.enabled=true but whisperlivekit not installed — STT disabled. Install requirements.stt.txt to enable.") | ||
| yield | ||
| return |
There was a problem hiding this comment.
If whisper.enabled is true but whisperlivekit isn’t installed, this branch yields/returns but leaves WHISPER_ENABLED as True. Other code paths (e.g. the WebSocket handler’s conditional Whisper imports) can still crash at runtime, and /v1/config will still report stt_enabled: true. Consider introducing a runtime flag like _stt_available (set false on ImportError) or flipping WHISPER_ENABLED to false here and guarding all Whisper imports/usage on that flag.
| # Secrets — env only | ||
| OLLAMA_URL = os.environ.get("OLLAMA_URL", "http://localhost:11434").rstrip("/") | ||
| OPENAI_URL = os.environ.get("OPENAI_BASE_URL", "").rstrip("/") | ||
| OPENAI_KEY = os.environ.get("OPENAI_API_KEY", "") | ||
|
|
||
| # Settings — config.yaml only; edit config.yaml to change these | ||
| OLLAMA_MODEL = _cfg.get("ollama", {}).get("default_model", "qwen3:8b") | ||
| OPENAI_MODEL = _cfg.get("openai", {}).get("model", "gpt-4o-mini") | ||
| WHISPER_ENABLED = _cfg.get("whisper", {}).get("enabled", False) | ||
| WHISPER_MODEL = _cfg.get("whisper", {}).get("model", "medium.en") | ||
| WHISPER_LANG = _cfg.get("whisper", {}).get("language", "en") | ||
| WHISPER_DEVICE = _cfg.get("whisper", {}).get("device", "auto") | ||
| PROMPT_PATH = Path(_cfg.get("paths", {}).get("prompt", "prompts/mermaid.txt")) | ||
| LOG_DIR = Path(_cfg.get("paths", {}).get("log_dir", "data/logs")) | ||
| _MODEL_FILTER: list[str] = [str(p) for p in (_cfg.get("ollama", {}).get("model_filter") or [])] |
There was a problem hiding this comment.
These reads assume nested config sections are mappings (e.g. _cfg.get("ollama", {}).get(...)). Since config.yaml is intended to be user-edited, a malformed section like ollama: "..." will raise at import time (AttributeError: 'str' object has no attribute 'get') and prevent startup. Consider validating/coercing each subsection (ollama/openai/whisper/paths) to a dict (or using a small schema/dataclass) before reading nested keys.
| # Secrets — env only | |
| OLLAMA_URL = os.environ.get("OLLAMA_URL", "http://localhost:11434").rstrip("/") | |
| OPENAI_URL = os.environ.get("OPENAI_BASE_URL", "").rstrip("/") | |
| OPENAI_KEY = os.environ.get("OPENAI_API_KEY", "") | |
| # Settings — config.yaml only; edit config.yaml to change these | |
| OLLAMA_MODEL = _cfg.get("ollama", {}).get("default_model", "qwen3:8b") | |
| OPENAI_MODEL = _cfg.get("openai", {}).get("model", "gpt-4o-mini") | |
| WHISPER_ENABLED = _cfg.get("whisper", {}).get("enabled", False) | |
| WHISPER_MODEL = _cfg.get("whisper", {}).get("model", "medium.en") | |
| WHISPER_LANG = _cfg.get("whisper", {}).get("language", "en") | |
| WHISPER_DEVICE = _cfg.get("whisper", {}).get("device", "auto") | |
| PROMPT_PATH = Path(_cfg.get("paths", {}).get("prompt", "prompts/mermaid.txt")) | |
| LOG_DIR = Path(_cfg.get("paths", {}).get("log_dir", "data/logs")) | |
| _MODEL_FILTER: list[str] = [str(p) for p in (_cfg.get("ollama", {}).get("model_filter") or [])] | |
| def _config_section(name: str) -> dict: | |
| section = _cfg.get(name, {}) | |
| if isinstance(section, dict): | |
| return section | |
| log.warning("config.%s is not a mapping — using defaults for that section", name) | |
| return {} | |
| _ollama_cfg = _config_section("ollama") | |
| _openai_cfg = _config_section("openai") | |
| _whisper_cfg = _config_section("whisper") | |
| _paths_cfg = _config_section("paths") | |
| # Secrets — env only | |
| OLLAMA_URL = os.environ.get("OLLAMA_URL", "http://localhost:11434").rstrip("/") | |
| OPENAI_URL = os.environ.get("OPENAI_BASE_URL", "").rstrip("/") | |
| OPENAI_KEY = os.environ.get("OPENAI_API_KEY", "") | |
| # Settings — config.yaml only; edit config.yaml to change these | |
| OLLAMA_MODEL = _ollama_cfg.get("default_model", "qwen3:8b") | |
| OPENAI_MODEL = _openai_cfg.get("model", "gpt-4o-mini") | |
| WHISPER_ENABLED = _whisper_cfg.get("enabled", False) | |
| WHISPER_MODEL = _whisper_cfg.get("model", "medium.en") | |
| WHISPER_LANG = _whisper_cfg.get("language", "en") | |
| WHISPER_DEVICE = _whisper_cfg.get("device", "auto") | |
| PROMPT_PATH = Path(_paths_cfg.get("prompt", "prompts/mermaid.txt")) | |
| LOG_DIR = Path(_paths_cfg.get("log_dir", "data/logs")) | |
| _MODEL_FILTER: list[str] = [str(p) for p in (_ollama_cfg.get("model_filter") or [])] |
| model: "gpt-4o-mini" | ||
|
|
||
| whisper: | ||
| enabled: true |
There was a problem hiding this comment.
whisper.enabled is set to true by default, but the Docker image (Dockerfile installs only requirements.txt) doesn’t include whisperlivekit/faster-whisper. In the default docker compose up flow this will result in STT being effectively unavailable (and currently can still cause runtime import failures unless additionally guarded). Either ship STT deps in the image (e.g., install requirements.stt.txt behind a build arg/profile) or default whisper.enabled to false and document the steps needed to enable STT locally.
| enabled: true | |
| enabled: false # Enable only when whisperlivekit/faster-whisper dependencies are installed in the environment/image. |
| # Settings — config.yaml only; edit config.yaml to change these | ||
| OLLAMA_MODEL = _cfg.get("ollama", {}).get("default_model", "qwen3:8b") | ||
| OPENAI_MODEL = _cfg.get("openai", {}).get("model", "gpt-4o-mini") | ||
| WHISPER_ENABLED = _cfg.get("whisper", {}).get("enabled", False) |
There was a problem hiding this comment.
By switching these settings to config.yaml only, existing setup docs/samples that instruct users to set .env values like OLLAMA_MODEL, OPENAI_MODEL, WHISPER_MODEL/DEVICE, MERMAID_PROMPT_PATH, and model filtering will now be incorrect (those env vars are ignored). If this is intentional, please update README.md and backend/.env.example accordingly (or reintroduce env overrides) to avoid confusing deployments.
…nabled in /v1/config Address Copilot review on PR #5: - Add _config_section() helper to guard against non-dict config sections - Replace bare _cfg.get("x", {}).get() chains with per-section dicts - Introduce _stt_available flag (False until WhisperLiveKit loads); stt_enabled in /v1/config now reflects actual runtime state instead of config intent Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d _stt_available flag Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…an up .env.example and README - config.yaml: add comment explaining whisper.enabled=true is safe (graceful failure via _stt_available) - .env.example: remove OLLAMA_MODEL, WHISPER_MODEL/DEVICE, MERMAID_PROMPT_PATH (moved to config.yaml) - README: split env table into config.yaml section and .env secrets-only section Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…eway (#6) * fix: add restart: unless-stopped to vtm-api and ollama services Ensures containers recover automatically after crashes or Docker restarts, supporting Docker Desktop auto-start on Windows login. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: move non-secret settings from .env to config.yaml whisper.enabled/model/language/device, ollama.default_model, openai.model, and paths now live in config.yaml (volume-mounted, editable without rebuild). .env reduced to secrets only (OLLAMA_URL). Env var overrides still work. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address PR review comments — dict validation, ImportError fallback, WHISPER_DEVICE wired, default enabled=false * feat: enable whisper STT locally (medium.en, auto device) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: _stt_available flag, _config_section() safe reads, correct stt_enabled in /v1/config Address Copilot review on PR #5: - Add _config_section() helper to guard against non-dict config sections - Replace bare _cfg.get("x", {}).get() chains with per-section dicts - Introduce _stt_available flag (False until WhisperLiveKit loads); stt_enabled in /v1/config now reflects actual runtime state instead of config intent Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs: address PR review comments — clarify config.yaml ownership, clean up .env.example and README - config.yaml: add comment explaining whisper.enabled=true is safe (graceful failure via _stt_available) - .env.example: remove OLLAMA_MODEL, WHISPER_MODEL/DEVICE, MERMAID_PROMPT_PATH (moved to config.yaml) - README: split env table into config.yaml section and .env secrets-only section Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: use _stt_available (not WHISPER_ENABLED) in ws_mermaid, host Ollama via host-gateway - ws_mermaid: replace WHISPER_ENABLED guards with _stt_available so WS works when whisper config is enabled but import failed (Docker without STT deps) - docker-compose: remove hard-coded OLLAMA_URL override; add extra_hosts for host.docker.internal so .env can point at host Ollama Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: restore OLLAMA_URL default for bundled ollama, align whisper.enabled default - docker-compose: restore OLLAMA_URL default (http://ollama:11434) via ${OLLAMA_URL:-http://ollama:11434} so docker-compose up works out of the box; .env still overrides it (host.docker.internal:11434 for host Ollama) - .env.example: use http://ollama:11434 as default with host Ollama commented variant - config.yaml: revert whisper.enabled to false (safe default; README already said false) - README: align whisper.enabled description and OLLAMA_URL default with actual shipped values Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: add restart: unless-stopped to vtm-api and ollama services Ensures containers recover automatically after crashes or Docker restarts, supporting Docker Desktop auto-start on Windows login. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: move non-secret settings from .env to config.yaml whisper.enabled/model/language/device, ollama.default_model, openai.model, and paths now live in config.yaml (volume-mounted, editable without rebuild). .env reduced to secrets only (OLLAMA_URL). Env var overrides still work. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address PR review comments — dict validation, ImportError fallback, WHISPER_DEVICE wired, default enabled=false * feat: enable whisper STT locally (medium.en, auto device) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: _stt_available flag, _config_section() safe reads, correct stt_enabled in /v1/config Address Copilot review on PR #5: - Add _config_section() helper to guard against non-dict config sections - Replace bare _cfg.get("x", {}).get() chains with per-section dicts - Introduce _stt_available flag (False until WhisperLiveKit loads); stt_enabled in /v1/config now reflects actual runtime state instead of config intent Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs: address PR review comments — clarify config.yaml ownership, clean up .env.example and README - config.yaml: add comment explaining whisper.enabled=true is safe (graceful failure via _stt_available) - .env.example: remove OLLAMA_MODEL, WHISPER_MODEL/DEVICE, MERMAID_PROMPT_PATH (moved to config.yaml) - README: split env table into config.yaml section and .env secrets-only section Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: use _stt_available (not WHISPER_ENABLED) in ws_mermaid, host Ollama via host-gateway - ws_mermaid: replace WHISPER_ENABLED guards with _stt_available so WS works when whisper config is enabled but import failed (Docker without STT deps) - docker-compose: remove hard-coded OLLAMA_URL override; add extra_hosts for host.docker.internal so .env can point at host Ollama Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: restore OLLAMA_URL default for bundled ollama, align whisper.enabled default - docker-compose: restore OLLAMA_URL default (http://ollama:11434) via ${OLLAMA_URL:-http://ollama:11434} so docker-compose up works out of the box; .env still overrides it (host.docker.internal:11434 for host Ollama) - .env.example: use http://ollama:11434 as default with host Ollama commented variant - config.yaml: revert whisper.enabled to false (safe default; README already said false) - README: align whisper.enabled description and OLLAMA_URL default with actual shipped values Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(vtm): install STT deps in Docker image, enable whisper in config Dockerfile: install requirements.stt.txt (whisperlivekit + faster-whisper) so _stt_available=True at runtime and /v1/config returns stt_enabled: true config.yaml: whisper.enabled=true — deps now in image; graceful no-op if import fails Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address Copilot review comments on STT PR - Move AudioProcessor import next to instantiation site (not function top) - Don't cache empty Ollama model list — retry on next request if Ollama unreachable at startup - Update README whisper.enabled default to true (matches config.yaml and Dockerfile) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
whisper.enabled: trueinconfig.yaml— Whisper was installed but disabledmedium.enmodel,autodevice (uses GPU if available, falls back to CPU)config.yamlis volume-mounted so no image rebuild needed; justdocker compose restart apiTest plan
curl http://localhost:7625/v1/config→stt_enabled: true🤖 Generated with Claude Code