Skip to content

Commit 23b5efe

Browse files
committed
docs(learnings/voice-providers): document Vapi-native voices + SSML rejection on provider:vapi
The voice catalog (Clara, Elliot, Nico, Emma, Neil, Sagar, Kai, Godfrey, Naina, Sid, Layla, Gustavo) wraps Cartesia / ElevenLabs / other backends behind a single 'provider: vapi' alias. Verified empirically that voice.enableSsmlParsing returns HTTP 400 'should not exist' on this provider — the field is only valid on provider: 11labs voices. Adds: new column in the quick-lookup table, a dedicated Vapi-native voices section with the field cheat-sheet, an explanation of when SSML break tags do/don't render based on the wrapped backend, and a four-step smoke-test protocol for confirming SSML behavior on Vapi-aliased voices. Worth noting for future readers: Vapi's voice-formatting pipeline preserves <break>, <spell>, and <<...>> through angle-bracket stripping, so even when the wrapped backend ignores SSML, tags don't leak as literal spoken text — worst case is silent no-op.
1 parent 097a9ae commit 23b5efe

1 file changed

Lines changed: 51 additions & 9 deletions

File tree

docs/learnings/voice-providers.md

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ The `voice` block on an assistant or `membersOverrides.voice` on a squad is **pr
88

99
## Quick lookup
1010

11-
| Field | 11labs | Cartesia (sonic-3) | OpenAI / Azure / Rime / LMNT / Minimax / Neuphonic / SmallestAI |
12-
|-------|--------|---------------------|------------------------------------------------------------------|
13-
| Speech rate | `voice.speed` (0.7–1.2) | `voice.generationConfig.speed` (0.6–1.5) | `voice.speed` |
14-
| Stability / consistency | `voice.stability` (0.0–1.0) | — (not exposed) ||
15-
| Voice similarity | `voice.similarityBoost` (0.0–1.0) |||
16-
| SSML parsing | `voice.enableSsmlParsing: true` | (parsed natively, no flag) | varies — see provider docs |
17-
| Pronunciation dictionary | `voice.pronunciationDictionaryLocators[]` (array of `{pronunciationDictionaryId, versionId}`) | `voice.pronunciationDictId` (single string id; not in Vapi docs but accepted as a Cartesia passthrough) ||
18-
| Volume control || `voice.generationConfig.volume` (0.5–2.0) ||
19-
| Emotion / accent (experimental) || `voice.experimentalControls.emotion`, `voice.experimentalControls.speed` (-1 to 1, older API) ||
11+
| Field | 11labs | Cartesia (sonic-3) | Vapi-native (Clara, Elliot, Nico, etc.) | OpenAI / Azure / Rime / LMNT / Minimax / Neuphonic / SmallestAI |
12+
|-------|--------|---------------------|-------|------------------------------------------------------------------|
13+
| Speech rate | `voice.speed` (0.7–1.2) | `voice.generationConfig.speed` (0.6–1.5) | `voice.speed` | `voice.speed` |
14+
| Stability / consistency | `voice.stability` (0.0–1.0) | — (not exposed) |||
15+
| Voice similarity | `voice.similarityBoost` (0.0–1.0) ||||
16+
| SSML parsing | `voice.enableSsmlParsing: true` | (parsed natively, no flag) | **`enableSsmlParsing` REJECTED** — see notes below | varies — see provider docs |
17+
| Pronunciation dictionary | `voice.pronunciationDictionaryLocators[]` (array of `{pronunciationDictionaryId, versionId}`) | `voice.pronunciationDictId` (single string id; not in Vapi docs but accepted as a Cartesia passthrough) |(treat as unsupported until confirmed) ||
18+
| Volume control || `voice.generationConfig.volume` (0.5–2.0) |||
19+
| Emotion / accent (experimental) || `voice.experimentalControls.emotion`, `voice.experimentalControls.speed` (-1 to 1, older API) |||
2020

2121
---
2222

@@ -70,6 +70,48 @@ voice:
7070

7171
---
7272

73+
## Vapi-native voices (Clara, Elliot, Nico, Emma, Neil, Sagar, Kai, Godfrey, Naina, Sid, Layla, Gustavo)
74+
75+
Vapi's first-party voice catalog wraps various TTS backends (Cartesia, ElevenLabs, others) behind a single `provider: vapi` alias. Which backend each named voice resolves to is **not publicly documented** — you can sometimes confirm by force-pulling an assistant that uses one (the pull engine occasionally resolves `vapi/<voiceId>` to the canonical provider name on the way back) or by inspecting the per-voice characteristics page in the Vapi voice docs.
76+
77+
```yaml
78+
voice:
79+
provider: vapi
80+
voiceId: <voiceName> # one of the Vapi catalog: Clara, Elliot, Nico, Emma, Neil, Sagar, Kai, Godfrey, Naina, Sid, Layla, Gustavo (grows over time)
81+
speed: 1.05 # top-level, 0.7–1.2 range observed
82+
chunkPlan:
83+
formatPlan:
84+
numberToDigitsCutoff: 10000 # numbers below this are spoken naturally; above are spelled digit-by-digit
85+
fallbackPlan:
86+
voices:
87+
- provider: 11labs
88+
model: eleven_turbo_v2_5
89+
voiceId: <fallback-voice-id>
90+
```
91+
92+
**Forbidden at top level for Vapi-native voices (will 400):**
93+
94+
- `voice.enableSsmlParsing` — API returns `400 Bad Request` with `"voice.property enableSsmlParsing should not exist"`. The field is **only valid on `provider: 11labs` voices**. Vapi-native voices do not expose an explicit SSML-parsing opt-in flag, even if the underlying backend (e.g. Cartesia sonic-3) parses SSML natively.
95+
- `voice.stability`, `voice.similarityBoost` — those are 11labs fields and will 400 here too.
96+
- `voice.generationConfig.*` — Cartesia path. Even if a Vapi-native voice wraps Cartesia internally, the `generationConfig` block is not exposed through the `provider: vapi` alias.
97+
98+
**Does SSML actually work without the flag?** Depends on the wrapped backend:
99+
100+
- If the Vapi voice wraps **Cartesia sonic-3**, SSML break tags work natively — Cartesia parses SSML from the text stream without an opt-in. Pause durations should approximate the declared values.
101+
- If the Vapi voice wraps **11labs Turbo v2.5**, SSML break tags would require `enableSsmlParsing: true` — but the flag is rejected on the `provider: vapi` alias, so SSML effectively does not render through this path. To use SSML on an 11labs-backed voice, declare it explicitly as `provider: 11labs` with the matching `voiceId`.
102+
- **Vapi's voice-formatting pipeline preserves `<break>`, `<spell>`, and `<<...>>` patterns through angle-bracket stripping** (the `removeAngleBracketContent` step has these as hardcoded exceptions — see `assistants/voice-formatting-plan` in the Vapi docs). So even when the wrapped backend doesn't render the tags, they don't leak as literal text either. Worst-case behavior is silent no-op, NOT audible regression like "the assistant says 'break time 350 milliseconds' out loud."
103+
104+
**Smoke-test protocol** when adding SSML pacing to a `provider: vapi` voice (or migrating from `provider: 11labs` to `provider: vapi`):
105+
106+
1. Push the new voice config to a non-prod sandbox assistant.
107+
2. Place one call that elicits a 3+ item enumeration (e.g. "what features does X have?").
108+
3. Listen for whether `<break time="..."/>` tags between list items render as audible pauses or get silently absorbed.
109+
4. If absorbed and pauses are needed for naturalness, either (a) switch the primary voice to `provider: 11labs` explicitly (with `enableSsmlParsing: true`), (b) use Cartesia natively, or (c) rely on commas + sentence punctuation as the prosody mechanism (Cartesia sonic-3 renders commas as ~150–200ms pauses, which covers most sentence-internal pacing without SSML).
110+
111+
**Cross-reference:** the comma-as-micro-pause convention works on most TTS providers and is the recommended fallback for sentence-internal pacing when SSML support is unreliable. The SSML-only-for-lists convention works on Cartesia-wrapped Vapi voices but should be smoke-tested before relying on it for a production fleet.
112+
113+
---
114+
73115
## OpenAI / Azure / Rime / LMNT / Minimax / Neuphonic / SmallestAI
74116

75117
```yaml

0 commit comments

Comments
 (0)