Skip to content

fix(stt): provider robustness — device config, CUDA fallbacks, container normalization (SILK/m4a), lazy-install recovery - #73510

Merged
teknium1 merged 23 commits into
mainfrom
fix/voice-stt-provider-robustness
Jul 28, 2026
Merged

fix(stt): provider robustness — device config, CUDA fallbacks, container normalization (SILK/m4a), lazy-install recovery#73510
teknium1 merged 23 commits into
mainfrom
fix/voice-stt-provider-robustness

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

fix(stt): consolidated STT provider correctness & robustness wave (13 fixes)

Consolidated salvage branch for the Cluster-F STT provider backlog. All fixes live in
tools/transcription_tools.py (+ tools/voice_mode.py, tools/lazy_deps.py, one
gateway/run.py import guard) with per-contributor commits and original authorship
preserved via cherry-pick. Every premise re-verified against current main
(f228e145ba, post-#73067) before picking. 389 tests pass across the touched test
files; the CUBLAS regression test was sabotage-verified (fails when the marker is
removed).

What's included (per-commit)

Fix Source PR Author Notes
Honor stt.local.device / stt.local.compute_type from config #9249 @Tranquil-Flow Main hardcoded auto/auto; CUDA→CPU fallback retained. Closes #8319
CUBLAS_STATUS_NOT_SUPPORTED added to _CUDA_LIB_ERROR_MARKERS #17559 @liuhao1024 Blackwell/RTX-5090 CPU fallback. Closes #17526. Post-merge: close #18626 (heavier ctranslate2-pin alternative)
Apple Silicon / Rosetta local STT crash guard #28624 @AnthonyFrancis Force CPU int8 + KMP_DUPLICATE_LIB_OK on Darwin arm64/translated. Numpy-pin hunk dropped (stale). Closes #28622
Scope 25MB upload cap to remote providers only #65823 @ypwcharles Local whisper no longer rejects big files; cap enforced pre-upload for cloud providers
Raw-import fallback when lazy_deps.ensure fails (PYTHONPATH installs) #67314 @tusharui STT + TTS. Closes #53259. Post-merge: close dupes #53289, #53489
Graceful handling when transcription module is missing #9066 @RichardHojunJang gateway/run.py no longer crashes message handling on ImportError
Named error for configured-but-unregistered plugin providers #55168 @ooiuuii error_type: provider_not_registered instead of generic "No STT provider". Closes #55167
Strip Qwen3-ASR language …<asr_text> envelope #65534 (3 commits) @LauraGPT, co-credit @malaiwah (#8773, earliest report/fix) Anchored regex, no-op for other providers. Post-merge: close #8773 as dupe with credit
check_voice_requirements() recognizes all STT providers #41657 (3 commits) @zehuaw1 mistral/xai/elevenlabs/command/plugin providers no longer show misleading "MISSING"
WARN-level logging when faster-whisper lazy install fails #46127 @damiankluk transcription_tools half only — Docker/CI files and gateway DM hunks dropped (superseded by main's neutral-marker design)
.silk (WeChat/QQ) voice-note preprocessing #11593 @dso2ng Central decode-to-WAV inside transcribe_audio — any platform caching .silk gets STT free. Reworked: pilk==0.2.4 is now a lazy dep (stt.silk) instead of a voice-extra pin. Fixes inbound half of #32196
Transcode-to-m4a retry when OpenAI STT rejects the container #68732 @carljborg On format-related BadRequestError, ffmpeg → 16 kHz mono m4a, retry once. Composes with the language-hint kwargs on main. Fixes #68719
Fresh: TOCTOU lock around local model load + keyless local base_url ours; co-credit @nnnet (#25193 idea) Double-checked threading.Lock (#24767); empty stt.openai.api_key accepted for loopback/RFC-1918/.local base URLs (no more sham api_key: not-needed). Post-merge: close #25193 (dirty branch, reimplemented)

Design notes

Testing

  • pytest -o addopts="" -q over 11 touched test files: 389 passed.
  • Sabotage check: removing CUBLAS_STATUS_NOT_SUPPORTED from the marker tuple fails
    test_cublas_status_not_supported_retries_on_cpu; restoring it passes.
  • New tests: local-model lock concurrency (single load across 4 threads), keyless
    local base_url resolution (loopback/private/public matrix), plus all tests carried
    by the salvaged commits.
  • Contributor email mappings added under contributors/emails/ for all
    non-auto-resolving authors.

Post-merge actions

Infographic

fix(stt): provider robustness — device config, CUDA fallbacks, container normalization (SILK/m4a), lazy-install recovery

Tranquil-Flow and others added 22 commits July 28, 2026 09:24
The local STT transcription function hardcoded device="auto" and
compute_type="auto" when instantiating WhisperModel, ignoring the
user's stt.local.device and stt.local.compute_type config values.

Closes #8319
- Add Blackwell-specific cuBLAS error marker to _CUDA_LIB_ERROR_MARKERS
- Allows CPU fallback on RTX 5090 (sm_120) when faster-whisper
  reports CUBLAS_STATUS_NOT_SUPPORTED instead of loading successfully
- Add regression test for CUBLAS_STATUS_NOT_SUPPORTED path

Closes #17526
Force CPU (int8) for faster-whisper on Apple Silicon / Rosetta, where
ctranslate2's device=auto path can hard-abort in native code. Salvaged
from PR #28624 without the numpy pin change (main already moved on).

(cherry picked from commit 7edf2d5, pyproject.toml hunk dropped)
Replace 
aise ImportError(str(e)) with pass in the except Exception
handler of _import_edge_tts(), _import_elevenlabs(), and
_import_mistral_client() so packages installed via PYTHONPATH or Docker
layered filesystems still work when lazy_deps.ensure() raises.

Also fix the Mistral STT path in transcription_tools.py which only
caught ImportError, not FeatureUnavailable.

Adds 6 regression tests using sys.modules fixtures (no
builtins.__import__ patching).
Add isolated test where ensure('stt.mistral') raises FeatureUnavailable
but the raw mistralai.client.Mistral import succeeds, verifying the
transcription_tools.py fallthrough path introduced in the same PR.
Normalize the structured <asr_text> marker after extracting text from string, SDK object, and dictionary transcription responses. Preserve the current provider-aware STT configuration architecture.

Refreshes #8773 on current main.

Co-authored-by: angelos <angelos@oikos.lan.home.malaiwah.com>

Assisted-by: Codex:gpt-5.6
The /voice status command only checked for 'local', 'groq', and 'openai'
providers. Any other valid provider (local_command, mistral, xai,
elevenlabs, or custom command providers) fell through to the generic
MISSING message — even when transcription worked perfectly.

- Import _has_any_command_stt_provider (already defined, never imported)
- Add elif branches for local_command, mistral, xai, elevenlabs
- Add generic catch-all via _has_any_command_stt_provider() for
  arbitrary custom command providers
PR review feedback:
- Replace _has_any_command_stt_provider() with selected-provider
  check via _resolve_command_stt_provider_config()
- Add _check_plugin_stt_provider() for plugin-registered backends
- Add tests: selected command, unrelated command (should NOT pass),
  and plugin provider path
Log lazy-install failures at WARNING instead of DEBUG, with actionable
guidance about venv write-permission issues (the most common cause of
silent STT failures).

Salvaged from PR #46127 (transcription_tools half only — the gateway DM
hunks are superseded by main's neutral-marker enrichment design, and the
Docker/CI files were unrelated scope).

(cherry picked from commit d3e07bd, reduced)
Decode WeChat/QQ SILK v3 voice notes to WAV inside transcribe_audio so
any platform that caches a .silk file gets STT for free (same central-
normalization philosophy as the outbound container repair). pilk is
lazy-installed on first use (stt.silk in tools/lazy_deps.py) instead of
being added to the voice extra.

Fixes the inbound half of #32196.

(cherry picked from commit e5db793; reworked to compose with the
provider-scoped upload size cap and to lazy-dep pilk)
… audio container

Newer OpenAI transcription models (gpt-4o-transcribe, gpt-4o-mini-transcribe)
reject some containers the legacy whisper-1 endpoint accepted -- notably the
Ogg/Opus voice notes messaging platforms deliver -- returning a 400
'corrupted or unsupported' error, so voice-note transcription fails for users
on those models even though SUPPORTED_FORMATS still advertises .ogg/.aac/.flac.

Wrap the OpenAI upload: on a format-related BadRequestError, transcode the
source to a compact 16 kHz mono AAC .m4a via ffmpeg and retry once. This is
model-agnostic (no per-model format table to maintain) and adds no cost for
formats the endpoint already accepts.

Fixes #68719
…e STT

Two small fresh fixes on top of the salvage wave:

- Wrap the check-then-load of the module-global faster-whisper model in a
  double-checked threading.Lock so concurrent voice messages can't both
  download/load the model (#24767).
- Treat an empty stt.openai.api_key as no-auth when stt.openai.base_url
  points at a loopback/RFC-1918/.local host, so local OpenAI-compatible
  STT servers (faster-whisper-server, speaches, vLLM whisper) work
  without a sham api_key value. Reimplements the idea from PR #25193 —
  credit @nnnet.

Co-authored-by: nnnet <nnnet@users.noreply.github.com>
…registration errors

Follow-ups for the salvaged wave: the auto-detect legacy-error test now
stubs the split validators, the unknown-command-provider test expects
the new provider_not_registered error, and _transcribe_local tolerates
a null stt.local config section again.
_transcribe_openai now imports BadRequestError for the container-retry
path; the managed-gateway fake module needs to provide it.
@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on fa5bf44

ℹ️ Info

Desktop E2E visual evidence · View test artifacts · View job

1 visual diff.

inline evidence upload failed.

Failed to upload diff-665a0833239e-onboarding-overlay-diff.png with gh image (exit code 1): Error uploading /home/runner/work/_temp/e2e-evidence/diff-665a0833239e-onboarding-overlay-diff.png: step 0 (get upload token): uploadToken not found on repo page — do you have write access to NousResearch/hermes-agent? (or, if NousResearch enforces SAML SSO, authorize at https://github.com/orgs/NousResearch/sso)

@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have tool/tts Text-to-speech and transcription comp/tools Tool registry, model_tools, toolsets comp/gateway Gateway runner, session dispatch, delivery labels Jul 28, 2026
@teknium1
teknium1 merged commit c0c5dac into main Jul 28, 2026
43 checks passed
@teknium1
teknium1 deleted the fix/voice-stt-provider-robustness branch July 28, 2026 18:53
teknium1 added a commit that referenced this pull request Jul 28, 2026
…ntry point

The rebase onto #73510's prepare/dispatch split left the guard inside
_transcribe_prepared_audio, where source validation ran first and a
blocked .env surfaced a format error instead of the read-block message.
Guard now fires before any validation/preprocessing.
teknium1 added a commit that referenced this pull request Jul 28, 2026
…ntry point

The rebase onto #73510's prepare/dispatch split left the guard inside
_transcribe_prepared_audio, where source validation ran first and a
blocked .env surfaced a format error instead of the read-block message.
Guard now fires before any validation/preprocessing.
teknium1 added a commit that referenced this pull request Jul 29, 2026
…ntry point

The rebase onto #73510's prepare/dispatch split left the guard inside
_transcribe_prepared_audio, where source validation ran first and a
blocked .env surfaced a format error instead of the read-block message.
Guard now fires before any validation/preprocessing.
teknium1 added a commit that referenced this pull request Jul 29, 2026
…ntry point

The rebase onto #73510's prepare/dispatch split left the guard inside
_transcribe_prepared_audio, where source validation ran first and a
blocked .env surfaced a format error instead of the read-block message.
Guard now fires before any validation/preprocessing.
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…ntry point

The rebase onto NousResearch#73510's prepare/dispatch split left the guard inside
_transcribe_prepared_audio, where source validation ran first and a
blocked .env surfaced a format error instead of the read-block message.
Guard now fires before any validation/preprocessing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment