Pin remaining unbounded deps, drop stale JS model-list fallback, add smoke test

pyannote.audio was still `>=3.1` — the same unbounded-constraint pattern
that silently broke qwen3 (transformers>=4.45.0 resolving to a version
without qwen3_asr support on a later rebuild). Confirmed it had already
drifted once this session (4.0.4 -> 4.0.7). Pinned to the exact version
the current diarization code (token=, .speaker_diarization) is verified
against.

asr.js's config-fetch failure path fabricated a hardcoded backend/model
list that could silently drift from the server's real one (this is
exactly how the wrong Qwen3-ASR-2B/8B model IDs stuck around). Replaced it
with a visible error instead of a second source of truth.

Added scripts/smoke_test.sh: hits every backend's /transcribe or
/synthesize directly with a synthetic clip. Every regression found this
session (wrong model IDs, pyannote API drift, cuDNN path conflict) would
have shown up here immediately instead of waiting for a user to hit it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
du5t
2026-07-23 23:29:30 +09:00
parent 9472387d1b
commit 89f8643426
4 changed files with 59 additions and 12 deletions

View File

@@ -7,16 +7,13 @@ async function loadConfig() {
const r = await fetch('/asr/config'); const r = await fetch('/asr/config');
SERVER_CONFIG = await r.json(); SERVER_CONFIG = await r.json();
} catch (e) { } catch (e) {
SERVER_CONFIG = { // No hardcoded fallback list here on purpose: the server's /asr/config is
default_backend: 'faster-whisper', // the single source of truth for backend/model names. A duplicated guess
default_model: 'large-v3', // here would silently drift out of sync with it (this has already bitten
default_language: 'ko', // us once — see qwen3 model IDs). Surface the failure instead.
backends: { SERVER_CONFIG = null;
'faster-whisper': { models: ['tiny','base','small','medium','large-v3','large-v2','turbo'] }, const el = document.getElementById('file-status');
'qwen3': { models: ['Qwen/Qwen3-ASR-0.6B-hf','Qwen/Qwen3-ASR-1.7B-hf'] }, if (el) el.textContent = '설정을 불러오지 못했습니다. 페이지를 새로고침해주세요.';
'vibevoice': { models: ['microsoft/VibeVoice-ASR'] },
},
};
} }
initAsrUI(); initAsrUI();
} }

View File

@@ -2,5 +2,5 @@ fastapi==0.115.0
uvicorn[standard]==0.30.6 uvicorn[standard]==0.30.6
python-multipart==0.0.9 python-multipart==0.0.9
faster-whisper==1.1.1 faster-whisper==1.1.1
pyannote.audio>=3.1 pyannote.audio==4.0.7
numpy numpy

View File

@@ -2,7 +2,7 @@
fastapi==0.115.0 fastapi==0.115.0
uvicorn[standard]==0.30.6 uvicorn[standard]==0.30.6
python-multipart==0.0.9 python-multipart==0.0.9
transformers>=4.45.0 transformers==5.14.1
accelerate>=0.30.0 accelerate>=0.30.0
librosa>=0.10.0 librosa>=0.10.0
soundfile>=0.12.0 soundfile>=0.12.0

50
scripts/smoke_test.sh Executable file
View File

@@ -0,0 +1,50 @@
#!/usr/bin/env bash
# Minimal post-deploy smoke test. Hits every ASR/TTS worker's own endpoint
# directly (bypassing the gateway's OIDC auth) with a synthetic test clip, to
# catch startup/dependency/API-compat regressions before a user has to find
# them (qwen3's wrong model IDs, pyannote's use_auth_token/token rename, and
# the cuDNN LD_LIBRARY_PATH conflict all would have shown up here as an
# immediate FAIL instead of a silent break discovered later).
#
# Run this after every `bash build.sh` + redeploy.
set -uo pipefail
CONTAINER="${SPEECH_CONTAINER:-speech}"
TMP_WAV="/tmp/smoke_test_tone.wav"
FAIL=0
pass() { printf ' OK %s\n' "$1"; }
fail() { printf 'FAIL %s: %s\n' "$1" "$2"; FAIL=1; }
echo "=== generating synthetic test clip ==="
podman exec "$CONTAINER" ffmpeg -y -f lavfi -i "sine=frequency=440:duration=3" -ar 16000 -ac 1 "$TMP_WAV" >/dev/null 2>&1
echo "=== faster-whisper (8001) ==="
resp=$(podman exec "$CONTAINER" curl -s -o /dev/null -w "%{http_code}" -X POST http://127.0.0.1:8001/transcribe \
-F "file=@${TMP_WAV};type=audio/wav" -F "model=tiny" -F "language=ko")
[ "$resp" = "200" ] && pass "faster-whisper" || fail "faster-whisper" "HTTP $resp"
echo "=== qwen3 (8004) ==="
resp=$(podman exec "$CONTAINER" curl -s -o /dev/null -w "%{http_code}" -X POST http://127.0.0.1:8004/transcribe \
-F "file=@${TMP_WAV};type=audio/wav" -F "model=Qwen/Qwen3-ASR-0.6B-hf" -F "language=ko")
[ "$resp" = "200" ] && pass "qwen3" || fail "qwen3" "HTTP $resp"
echo "=== vibevoice (8006) ==="
resp=$(podman exec "$CONTAINER" curl -s -o /dev/null -w "%{http_code}" -X POST http://127.0.0.1:8006/transcribe \
-F "file=@${TMP_WAV};type=audio/wav" -F "model=microsoft/VibeVoice-ASR")
[ "$resp" = "200" ] && pass "vibevoice" || fail "vibevoice" "HTTP $resp"
echo "=== xtts (8005) ==="
resp=$(podman exec "$CONTAINER" curl -s -o /dev/null -w "%{http_code}" -X POST http://127.0.0.1:8005/synthesize \
-F "text=스모크 테스트입니다." -F "language=ko" -F "speaker_wav=${TMP_WAV}")
[ "$resp" = "200" ] && pass "xtts" || fail "xtts" "HTTP $resp"
podman exec "$CONTAINER" rm -f "$TMP_WAV"
echo
if [ "$FAIL" = "0" ]; then
echo "all backends OK"
else
echo "one or more backends FAILED — check: podman logs $CONTAINER"
fi
exit "$FAIL"