Fix STT health check incorrectly marking Open WebUI as whisper.cpp
Port 8080 is Open WebUI — it passes /health + /v1/models checks but returns 405 on POST /v1/audio/transcriptions. Updated probe logic to: - treat 405 as 'endpoint missing, try next path' - treat non-JSON 500 as broken, JSON-500 with detail as 'audio too short' (ok) - use 500ms silence WAV instead of 1-frame (too tiny for alignment models) Changed whisper.cpp default from :8080 to :8085 to avoid clash with Open WebUI. Updated s-llms.html placeholder and code snippet accordingly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8f9060d025
commit
d16be50cc9
39
server.py
39
server.py
@ -48,7 +48,7 @@ _NVIDIA_CLONE_DEFAULT = os.environ.get("NVIDIA_TTS_CLONE_URL", "http://host.dock
|
|||||||
_NVIDIA_ZEROSHOT_DEFAULT = os.environ.get("NVIDIA_ZEROSHOT_TTS_URL", _NVIDIA_CLONE_DEFAULT)
|
_NVIDIA_ZEROSHOT_DEFAULT = os.environ.get("NVIDIA_ZEROSHOT_TTS_URL", _NVIDIA_CLONE_DEFAULT)
|
||||||
_NVIDIA_FLOW_DEFAULT = os.environ.get("NVIDIA_FLOW_TTS_URL", "http://host.docker.internal:8094")
|
_NVIDIA_FLOW_DEFAULT = os.environ.get("NVIDIA_FLOW_TTS_URL", "http://host.docker.internal:8094")
|
||||||
_FASTER_WHISPER_DEFAULT = os.environ.get("FASTER_WHISPER_URL", "http://host.docker.internal:8000")
|
_FASTER_WHISPER_DEFAULT = os.environ.get("FASTER_WHISPER_URL", "http://host.docker.internal:8000")
|
||||||
_WHISPER_CPP_DEFAULT = os.environ.get("WHISPER_CPP_URL", "http://host.docker.internal:8080")
|
_WHISPER_CPP_DEFAULT = os.environ.get("WHISPER_CPP_URL", "http://host.docker.internal:8085")
|
||||||
_GROQ_STT_ENDPOINT = "https://api.groq.com/openai/v1"
|
_GROQ_STT_ENDPOINT = "https://api.groq.com/openai/v1"
|
||||||
_KOKORO_DEFAULT = os.environ.get("KOKORO_URL", "http://host.docker.internal:8880/v1")
|
_KOKORO_DEFAULT = os.environ.get("KOKORO_URL", "http://host.docker.internal:8880/v1")
|
||||||
_VIBEVOICE_DEFAULT = os.environ.get("VIBEVOICE_URL", "http://192.168.178.8:8027")
|
_VIBEVOICE_DEFAULT = os.environ.get("VIBEVOICE_URL", "http://192.168.178.8:8027")
|
||||||
@ -1903,15 +1903,15 @@ def _stt_backend_api_key(settings: dict, backend: str) -> str:
|
|||||||
return settings.get("whisper_api_key", "").strip()
|
return settings.get("whisper_api_key", "").strip()
|
||||||
|
|
||||||
|
|
||||||
def _make_minimal_wav() -> bytes:
|
def _make_minimal_wav(duration_ms: int = 500) -> bytes:
|
||||||
"""44-byte WAV header with 1 frame of silence — smallest valid WAV."""
|
"""Minimal WAV: mono 16-bit 16kHz silence of given duration."""
|
||||||
import struct
|
sample_rate = 16000
|
||||||
num_frames = 1
|
num_frames = sample_rate * duration_ms // 1000
|
||||||
data = b"\x00\x00"
|
data = b"\x00\x00" * num_frames
|
||||||
header = struct.pack(
|
header = struct.pack(
|
||||||
"<4sI4s4sIHHIIHH4sI",
|
"<4sI4s4sIHHIIHH4sI",
|
||||||
b"RIFF", 36 + len(data), b"WAVE",
|
b"RIFF", 36 + len(data), b"WAVE",
|
||||||
b"fmt ", 16, 1, 1, 16000, 32000, 2, 16,
|
b"fmt ", 16, 1, 1, sample_rate, sample_rate * 2, 2, 16,
|
||||||
b"data", len(data),
|
b"data", len(data),
|
||||||
)
|
)
|
||||||
return header + data
|
return header + data
|
||||||
@ -1942,9 +1942,11 @@ def _stt_backend_health(url: str) -> tuple[bool, list[str]]:
|
|||||||
if not ok:
|
if not ok:
|
||||||
return False, models
|
return False, models
|
||||||
# Verify the transcription endpoint actually works (catches broken builds like
|
# Verify the transcription endpoint actually works (catches broken builds like
|
||||||
# CTranslate2 containers compiled without CUDA that pass /health but fail on load)
|
# CTranslate2 containers compiled without CUDA that pass /health but fail on load,
|
||||||
|
# and LLM servers that expose /v1/models but no transcription endpoint)
|
||||||
try:
|
try:
|
||||||
wav = _make_minimal_wav()
|
wav = _make_minimal_wav()
|
||||||
|
endpoint_found = False
|
||||||
for path in ("/v1/audio/transcriptions", "/transcribe"):
|
for path in ("/v1/audio/transcriptions", "/transcribe"):
|
||||||
try:
|
try:
|
||||||
r = requests.post(
|
r = requests.post(
|
||||||
@ -1953,14 +1955,29 @@ def _stt_backend_health(url: str) -> tuple[bool, list[str]]:
|
|||||||
data={"model": "whisper-1", "response_format": "text"},
|
data={"model": "whisper-1", "response_format": "text"},
|
||||||
timeout=8,
|
timeout=8,
|
||||||
)
|
)
|
||||||
|
if r.status_code in {404, 405}:
|
||||||
|
continue # try next path — endpoint missing or wrong method
|
||||||
if r.status_code == 500:
|
if r.status_code == 500:
|
||||||
ok = False # backend is broken (model load failed etc.)
|
# Distinguish backend crash from "couldn't process empty audio":
|
||||||
break # 200, 400, 404, 422 all mean the endpoint exists and model loaded
|
# a JSON detail that isn't the generic FastAPI text means the
|
||||||
|
# model loaded and responded — treat as ok (audio was just bad)
|
||||||
|
try:
|
||||||
|
detail = r.json().get("detail", "")
|
||||||
|
ok = bool(detail) and detail.lower() != "internal server error"
|
||||||
|
except Exception:
|
||||||
|
ok = False # non-JSON 500 → backend likely broken
|
||||||
|
else:
|
||||||
|
ok = True # 200, 400, 415, 422 → endpoint works
|
||||||
|
endpoint_found = True
|
||||||
|
break
|
||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
ok = False
|
ok = False
|
||||||
|
endpoint_found = True
|
||||||
break
|
break
|
||||||
except Exception:
|
except Exception:
|
||||||
pass # timeout or other — don't mark as broken, just skip
|
pass # timeout — don't penalise slow model loads
|
||||||
|
if not endpoint_found:
|
||||||
|
ok = False # all paths returned 404/405 — not an STT backend
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return ok, models
|
return ok, models
|
||||||
|
|||||||
@ -350,7 +350,7 @@ Check "Enable CORS" for browser access</pre>
|
|||||||
<p class="llm-local-desc">Minimal C++ Whisper with a built-in HTTP server. CPU or Metal/CUDA. Low memory, fast on consumer hardware.</p>
|
<p class="llm-local-desc">Minimal C++ Whisper with a built-in HTTP server. CPU or Metal/CUDA. Low memory, fast on consumer hardware.</p>
|
||||||
<div class="llm-local-url">
|
<div class="llm-local-url">
|
||||||
<span class="llm-local-url-label">URL</span>
|
<span class="llm-local-url-label">URL</span>
|
||||||
<input class="llm-local-url-inp" type="text" placeholder="http://localhost:8080" data-llm-local-key="whisper-cpp" data-llm-local-default="http://localhost:8080" spellcheck="false">
|
<input class="llm-local-url-inp" type="text" placeholder="http://localhost:8085" data-llm-local-key="whisper-cpp" data-llm-local-default="http://localhost:8085" spellcheck="false">
|
||||||
<button class="llm-local-ping" data-ping-key="whisper-cpp" title="Test connection">Connect</button>
|
<button class="llm-local-ping" data-ping-key="whisper-cpp" title="Test connection">Connect</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="llm-local-actions">
|
<div class="llm-local-actions">
|
||||||
@ -359,12 +359,12 @@ Check "Enable CORS" for browser access</pre>
|
|||||||
<div class="llm-local-snippet">
|
<div class="llm-local-snippet">
|
||||||
<div class="llm-snippet-bar">
|
<div class="llm-snippet-bar">
|
||||||
<span>Build & run</span>
|
<span>Build & run</span>
|
||||||
<button class="llm-copy-btn" data-copy="git clone https://github.com/ggml-org/whisper.cpp cd whisper.cpp && cmake -B build && cmake --build build -j ./build/bin/whisper-server -m models/ggml-large-v3.bin --port 8080">Copy</button>
|
<button class="llm-copy-btn" data-copy="git clone https://github.com/ggml-org/whisper.cpp cd whisper.cpp && cmake -B build && cmake --build build -j ./build/bin/whisper-server -m models/ggml-large-v3.bin --port 8085">Copy</button>
|
||||||
</div>
|
</div>
|
||||||
<pre>git clone https://github.com/ggml-org/whisper.cpp
|
<pre>git clone https://github.com/ggml-org/whisper.cpp
|
||||||
cd whisper.cpp && cmake -B build && cmake --build build -j
|
cd whisper.cpp && cmake -B build && cmake --build build -j
|
||||||
./build/bin/whisper-server \
|
./build/bin/whisper-server \
|
||||||
-m models/ggml-large-v3.bin --port 8080</pre>
|
-m models/ggml-large-v3.bin --port 8085</pre>
|
||||||
</div>
|
</div>
|
||||||
<a class="llm-local-link" href="https://github.com/ggml-org/whisper.cpp" target="_blank" rel="noopener">github.com/ggml-org/whisper.cpp <span class="mdi mdi-open-in-new link-icon"></span></a>
|
<a class="llm-local-link" href="https://github.com/ggml-org/whisper.cpp" target="_blank" rel="noopener">github.com/ggml-org/whisper.cpp <span class="mdi mdi-open-in-new link-icon"></span></a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user