diff --git a/config/defaults.json b/config/defaults.json
index 8a411ba..adbf006 100644
--- a/config/defaults.json
+++ b/config/defaults.json
@@ -15,7 +15,7 @@
"dictation": false,
"profanity_filter": false,
"diarize": false,
- "diarize_version": "",
+ "diarize_model": "",
"detect_entities": false,
"multichannel": false,
"utterances": false,
diff --git a/static/app.js b/static/app.js
index 6044708..6246112 100644
--- a/static/app.js
+++ b/static/app.js
@@ -305,7 +305,7 @@ function appData() {
dictation: false,
profanity_filter: false,
diarize: false,
- diarize_version: '',
+ diarize_model: '',
detect_entities: false,
multichannel: false,
utterances: false,
@@ -1089,7 +1089,7 @@ function appData() {
dictation: false,
profanity_filter: false,
diarize: false,
- diarize_version: '',
+ diarize_model: '',
detect_entities: false,
multichannel: false,
utterances: false,
diff --git a/stt/options.py b/stt/options.py
index 005a1c7..491edc4 100644
--- a/stt/options.py
+++ b/stt/options.py
@@ -173,6 +173,22 @@ def clean_params(params: dict, mode: Mode) -> dict:
continue
_put(result, wire, v)
+ # `diarize_model` is mutually exclusive with the deprecated `diarize` and
+ # `diarize_version`. Deepgram does not pick a winner, it rejects outright:
+ #
+ # 400 diarize_model cannot be used together with diarize or
+ # diarize_version. (INVALID_QUERY_PARAMETER)
+ #
+ # `diarize_model` wins here because it is the current parameter and the only
+ # way to reach the v2 diarizer: the boolean `diarize` ALWAYS routes to v1,
+ # and `diarize_version` is legacy. The UI already makes the two controls
+ # exclusive; this covers the `extra` escape hatch and any saved config from
+ # before the field was renamed, so an old preset degrades to the modern
+ # parameter instead of a hard 400.
+ if "diarize_model" in result:
+ for legacy in ("diarize", "diarize_version"):
+ result.pop(legacy, None)
+
return result
diff --git a/templates/index.html b/templates/index.html
index 625f3cf..5cf6b91 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -389,6 +389,19 @@
letter-spacing: 0.06em;
}
+ .field-hint {
+ font-size: 11px;
+ line-height: 1.45;
+ color: #8b93a7;
+ margin-top: 5px;
+ }
+ .field-hint code {
+ background: #1a2234;
+ padding: 1px 4px;
+ border-radius: 3px;
+ color: #a5b4fc;
+ }
+
.field-input {
background: #1a1f2e;
border: 1px solid #2d3748;
@@ -1319,9 +1332,14 @@
+
-
-
+
+
@@ -1340,17 +1358,34 @@
-
-
-
-
Diarize Version
-
+
+
+
Diarizer (diarize_model)
+
+
+ v2 is batch only. Deepgram rejects the WebSocket handshake for
+ diarize_model=v2 on a stream; use latest there.
-
+
+ Sends diarize_model and omits diarize.
+ The deprecated diarize checkbox always selects v1.
+
+
diff --git a/tests/test_options.py b/tests/test_options.py
index cde024f..f07abf9 100644
--- a/tests/test_options.py
+++ b/tests/test_options.py
@@ -178,7 +178,11 @@ def test_sdk_accepts_every_serialized_param_name():
# lost. Regression guard for the keyterms crash class. filler_words is NOT in
# this list: Deepgram's docs mark it stream-unavailable, so it is stripped in
# streaming mode by design — see test_stream_unavailable_params_are_stripped.
- for name in ("no_delay", "word_confidence", "diarize_version", "entity_prompt"):
+ # `diarize_version` is deliberately absent from this list: the UI now models
+ # the current `diarize_model`, which deepgram-sdk 7 names as a real kwarg, so
+ # it rides via_kwargs rather than the passthrough. See
+ # test_diarize_model_is_a_named_sdk_kwarg_not_passthrough.
+ for name in ("no_delay", "word_confidence", "entity_prompt", "alternatives"):
assert name in via_query, f"{name} is not being forwarded"
@@ -283,3 +287,46 @@ def test_alias_cannot_smuggle_a_denied_wire_name():
options.PARAM_ALIASES = original
assert "callback" in DENIED_PARAMS
+
+
+def test_diarize_model_wins_over_the_deprecated_diarize_params():
+ """Deepgram rejects the combination outright rather than picking a winner:
+
+ 400 diarize_model cannot be used together with diarize or
+ diarize_version. (INVALID_QUERY_PARAMETER)
+
+ Verified live. diarize_model is kept because it is the current parameter and
+ the only route to the v2 diarizer; the boolean `diarize` always selects v1.
+ """
+ from stt.options import clean_params, Mode
+ out = clean_params(
+ {"model": "nova-3", "diarize": True, "extra": {"diarize_model": "v2"}},
+ Mode.BATCH,
+ )
+ assert out["diarize_model"] == "v2"
+ assert "diarize" not in out
+ assert "diarize_version" not in out
+
+ out = clean_params(
+ {"model": "nova-3", "extra": {"diarize_model": "latest", "diarize_version": "latest"}},
+ Mode.BATCH,
+ )
+ assert out["diarize_model"] == "latest"
+ assert "diarize_version" not in out
+
+
+def test_deprecated_diarize_still_works_alone():
+ """Legacy customer configs send the boolean, and replicating those is a job
+ this tool has. It must keep working when diarize_model is absent."""
+ from stt.options import clean_params, Mode
+ out = clean_params({"model": "nova-3", "diarize": True}, Mode.BATCH)
+ assert out["diarize"] is True
+ assert "diarize_model" not in out
+
+
+def test_diarize_model_is_a_named_sdk_kwarg_not_passthrough():
+ """deepgram-sdk 7 added diarize_model to connect(); it should ride as a real
+ kwarg rather than through additional_query_parameters."""
+ import app
+ built = app._params_to_sdk_kwargs({"model": "nova-3", "extra": {"diarize_model": "latest"}})
+ assert built.get("diarize_model") == "latest"