docs(frank-tts): the harness sends a voice only in voice_id
The section this PR adds claimed speech.voice was the forwarded voice key. It is not: the openai-speech transport builds {model, input, response_format, voice?} and sets voice only when the CALL passed voice_id, tts.localVoice is the local Kokoro backend key, and speech.voice is the TUI speak-aloud voice whose enum is Kokoro-only. A restarted session proved it the honest way - the tts device arrived at 7772 with no voice key and got "unknown voice None; available voices: narrator1, narrator2, narrator3" - so the fresh-process success earlier that day was the run that was told to pass voice_id, not evidence of a setting.
The law now reads: voice comes from voice_id alone, so a voice-less call is the harness default shape (the endpoint grew DEFAULT_VOICE in its own PR to answer it), and speech.voice must stay a Kokoro name - a pack name there breaks the vocalizer and does nothing for synthesis. config.yml reverted to am_fenrir on the operator box, and the worklog entry that recorded narrator1 as wiring carries the correction.
This commit is contained in:
@@ -154,17 +154,24 @@ The operator harness reaches this endpoint through
|
||||
`baseUrl: http://192.168.0.143:7772/v1`, `auth: none`, and the runner api
|
||||
`openai-speech` on the **model** row (naming a runner api at *provider* level
|
||||
fails the whole file's validation and disables every custom provider, which is
|
||||
how the fleet's chat endpoints disappear) — plus `config.yml`:
|
||||
`modelRoles.speech: frank-tts (dankclaw)/frank-tts` and `speech.voice:
|
||||
narrator1`. Three laws, read out of the harness bundle and measured the day of
|
||||
the flip:
|
||||
how the fleet's chat endpoints disappear) — plus `config.yml`
|
||||
`modelRoles.speech: frank-tts (dankclaw)/frank-tts`. Four laws, read out of the
|
||||
harness bundle and measured the day of the flip:
|
||||
|
||||
- **The forwarded voice key is `speech.voice`, not `tts.localVoice`.** The
|
||||
latter belongs to the local Kokoro backend and still carries `bm_george`; it
|
||||
is what answered when the role pointed at `local/kokoro`. `speech.voice`
|
||||
becomes the request's `voice`, and this endpoint treats an unknown name as an
|
||||
error rather than a default — so a Kokoro-style revert has to move **both**
|
||||
keys together.
|
||||
- **The only voice the harness can send is the `voice_id` argument of the call.**
|
||||
The transport builds `{model, input, response_format, voice?}` and adds `voice`
|
||||
**only** when the caller passed `voice_id`; no setting feeds it.
|
||||
`tts.localVoice` is the *local Kokoro backend's* voice (it is what answered
|
||||
`voice=kokoro/bm_george, backend=local` in the control test), and
|
||||
`speech.voice` is the TUI's **speak-aloud** voice — its enum is Kokoro's list,
|
||||
and it never reaches an HTTP request. So a harness call arrives voice-less by
|
||||
default, which is exactly why the endpoint grew `DEFAULT_VOICE`: the declared
|
||||
pack answers a voice-less request and the journal marks that line `(default)`,
|
||||
while an **unknown** name still 400s with the list that exists.
|
||||
- **`speech.voice` must stay a Kokoro name.** Setting it to a pack name does not
|
||||
select a pack; it hands the vocalizer a value outside its enum. (The first
|
||||
draft of this section said the opposite and was reverted the same day, after a
|
||||
fresh session's call came back `unknown voice None`.)
|
||||
- **There is no silent fallback.** An explicitly configured `modelRoles.speech`
|
||||
yields exactly one candidate, so a dead 7772 fails the speech device with
|
||||
*"No available speech model matches the speech role"* instead of quietly
|
||||
|
||||
@@ -2706,9 +2706,10 @@ every restart since the cutover.
|
||||
actually selects a voice. Engine-side ledger for the harness request:
|
||||
`TTFA 67.5 ms`, `Total 2158.8 ms`, `audio 3.76 s, RTF 0.574`.
|
||||
- **The harness `speech` role moved off Kokoro**: `config.yml`
|
||||
`modelRoles.speech: frank-tts (dankclaw)/frank-tts` and `speech.voice:
|
||||
narrator1`. `tts.localVoice` stays `bm_george` — that key belongs to the local
|
||||
backend, which is what answered in the control test.
|
||||
`modelRoles.speech: frank-tts (dankclaw)/frank-tts`. No voice key moves with it.
|
||||
The bundle is unambiguous: the speech device forwards the call's `voice_id` and
|
||||
nothing else, `tts.localVoice` belongs to the local backend (it is what answered
|
||||
in the control test), and `speech.voice` is the TUI's speak-aloud enum.
|
||||
- Two harness laws learned by breaking it first (both now on [[Frank TTS]]): an
|
||||
explicit speech role has **no fallback candidate**, so a dead endpoint errors
|
||||
instead of degrading; and `models.yml` loads at process start while
|
||||
@@ -2722,3 +2723,14 @@ every restart since the cutover.
|
||||
voice leg works; open-notebook itself is what is missing), the engine page's
|
||||
"Deploy order — nothing below has been run" is now a **Deploy record**, and the
|
||||
homepage card lost `wip: down` so the admin panel probes 7772 for real.
|
||||
- Corrected the same evening, by breaking it in a restarted session: a harness
|
||||
call that names no voice reaches 7772 with **no `voice` key at all** — `400
|
||||
unknown voice None; available voices: narrator1, narrator2, narrator3` —
|
||||
because no setting feeds one. The earlier `Saved 180524 bytes
|
||||
(voice=narrator1)` succeeded only because that run was *told* to pass
|
||||
`voice_id`. The endpoint therefore grew `DEFAULT_VOICE` (its own PR): a
|
||||
voice-less request speaks the declared pack and the journal line carries
|
||||
`(default)`, while an unknown name still 400s with the list. Reverting the role
|
||||
to Kokoro is one key, not two, and `speech.voice: narrator1` — briefly recorded
|
||||
above as wiring — went back to `am_fenrir`: that key is the vocalizer's Kokoro
|
||||
enum, so a pack name there breaks speak-aloud and does nothing for synthesis.
|
||||
|
||||
Reference in New Issue
Block a user