From e54df5f4f79ddda675a828ce1b0f06f1a7eeeee1 Mon Sep 17 00:00:00 2001 From: Vuong Hoang Date: Mon, 27 Apr 2026 16:04:39 -0700 Subject: [PATCH] chatterbox: switch health probe from /health (doesn't exist) to /api/model-info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit devnen/Chatterbox-TTS-Server doesn't expose /health — neither in code nor OpenAPI. The deploy hung on the playbook's `Wait for /health to respond` loop indefinitely (each curl -> 404, retry forever) even though the container was up and the model loaded clean to CUDA at 22:52:21 (~42s after start). /api/model-info returns `{"loaded":true,...}` only after the model finishes loading, so it doubles as liveness + readiness. Updated: * compose.yaml healthcheck — grep for `"loaded":true` from /api/model-info. * playbook wait step — same probe instead of /health. * verify /health → verify /api/model-info reports loaded. * verify /v1/audio/voices — switched from greping for `voice|alloy|echo` literals to parsing JSON and asserting the actual response shape: `{"status":"ok","voices":[...]}` (devnen's shape — note this is NOT the OpenAI list-format vibevoice uses). --- playbooks/deploy-chatterbox.yaml | 22 ++++++++++++++++------ stacks/chatterbox/compose.yaml | 7 +++++-- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/playbooks/deploy-chatterbox.yaml b/playbooks/deploy-chatterbox.yaml index d3f2d51..4bbbe72 100644 --- a/playbooks/deploy-chatterbox.yaml +++ b/playbooks/deploy-chatterbox.yaml @@ -70,22 +70,32 @@ steps: - name: docker compose up -d shell: cd {{ compose_dir }} && docker compose up -d - - name: Wait for /health to respond (allow ~15 min for model download + warmup) + - name: Wait for /api/model-info to report loaded:true (allow ~15 min for model download + warmup) + # The devnen wrapper has no /health endpoint. /api/model-info returns + # `{"loaded":true,...}` only after the model finishes loading, so it + # doubles as a liveness + ready probe. shell: | for i in $(seq 1 180); do - curl -sf -o /dev/null --max-time 3 http://localhost:{{ host_port }}/health && exit 0 + curl -sf --max-time 3 http://localhost:{{ host_port }}/api/model-info \ + | grep -q '"loaded":true' && exit 0 sleep 5 done exit 1 changed_when: "false" verify: - - name: /health returns 200 - shell: curl -sf -o /dev/null http://localhost:{{ host_port }}/health + - name: /api/model-info reports loaded:true + shell: curl -sf http://localhost:{{ host_port }}/api/model-info | grep -q '"loaded":true' changed_when: "false" - - name: /v1/audio/voices returns valid JSON - shell: curl -sf http://localhost:{{ host_port }}/v1/audio/voices | grep -q 'voice\|alloy\|echo' + - name: /v1/audio/voices returns the wrapper's status:ok + voices list + # Devnen's response shape is `{"status":"ok","voices":["Abigail.wav",...]}` + # — voices is an array of files in the predefined-voices dir. Assert + # both fields rather than greping for any one voice name (the seed + # set could change with upstream). + shell: | + curl -sf http://localhost:{{ host_port }}/v1/audio/voices \ + | python3 -c "import json,sys; d=json.load(sys.stdin); assert d.get('status')=='ok' and isinstance(d.get('voices'), list)" changed_when: "false" - name: Container is running diff --git a/stacks/chatterbox/compose.yaml b/stacks/chatterbox/compose.yaml index 3527eb3..be101f4 100644 --- a/stacks/chatterbox/compose.yaml +++ b/stacks/chatterbox/compose.yaml @@ -49,8 +49,11 @@ services: # is to use the in-image config + env var overrides. # - ${CHATTERBOX_CONFIG}:/app/config.yaml:ro healthcheck: - # Devnen wrapper exposes /health; the OpenAPI/docs path is /docs. - test: ["CMD-SHELL", "curl -fsS -o /dev/null http://localhost:8004/health || exit 1"] + # The devnen wrapper does NOT expose /health (no such route — use + # /docs or /openapi.json to enumerate). /api/model-info returns + # `{"loaded":true,...}` only after the model finishes loading, + # so it doubles as a liveness + ready probe. + test: ["CMD-SHELL", "curl -fsS http://localhost:8004/api/model-info | grep -q '\"loaded\":true' || exit 1"] interval: 30s timeout: 10s retries: 3