feat(regen): listen v2 force-end-turn and listen v1 diarize metadata - #768
Open
GregHolmes wants to merge 8 commits into
Open
feat(regen): listen v2 force-end-turn and listen v1 diarize metadata#768GregHolmes wants to merge 8 commits into
GregHolmes wants to merge 8 commits into
Conversation
GregHolmes
requested review from
deepgram-kiley and
dg-coreylweathers
as code owners
August 18, 2026 12:45
Contributor
|
The ForceEndTurn gate has opened on staging, so the feature is now verifiable end-to-end rather than only through a mocked websocket. Add tests/manual/listen/v2/force_end_turn/ (sync + async, since the two socket clients have separate send_force_end_turn implementations). The sync script covers the whole end-of-turn control matrix via TurnInfo .trigger: ForceEndTurn yields trigger="manual", eot_threshold=0.7 yields "model", and eot_threshold=1.0 yields "timeout" rather than "model". That last case corrects a misreading. eot_threshold=1.0 does suppress Flux's confidence-based detection, but eot_timeout_ms is a separate mechanism that still ends the turn, so counting EndOfTurn events makes 1.0 look like it changes nothing. Only trigger distinguishes them, and it was not emitted when this was last measured. Full manual control needs eot_threshold=1.0 together with a long eot_timeout_ms; with both, no EndOfTurn arrives until ForceEndTurn is sent. Add examples/16-transcription-force-end-turn.py demonstrating that combination. Both the scripts and the example report the gate and exit cleanly where the feature is not enabled, rather than dying on the connection teardown the rejection causes. Document the gate and the destructive failure mode on both send_force_end_turn docstrings.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fern SDK regeneration for 2026-08-18 (fern-python-sdk
5.27.1, CLI5.95.1).Status: reconciled & verified.
mypy src/clean (900 files) ·mypy tests/typecheckclean ·ruff check src/deepgramclean · 992 passed / 1 skipped · coverage 94.69% (≥ 90% gate). No breaking changes vs 7.7.0 — 0 removed public exports across every package, all renames aliased with identical values.New in this regen
speakerandspeaker_confidenceon the words item (pre-recorded only;speaker_confidenceis not returned for streaming), plusListenV1ResponseMetadataDiarizeInfo/ListenV1ResultsMetadataDiarizeInfo(model_uuid,arch) identifying which diarizer ran. Verified end-to-end against production:diarize=truereturnsdiarize_info{model_uuid, arch: "v1"}and every word carries both fields; both are absent when diarization is off.eot_thresholdnow accepts up to1.0(previously documented as0.5–0.9). Boundary-verified against production:0.49and1.01are rejected with HTTP 400,0.5and1.0are accepted.send_force_end_turn()+ListenV2ForceEndTurn(a no-payload control message), plus a newListenV2TurnInfo.triggerfield (model|manual|timeout, open enum) identifying what ended a turn. Verified end-to-end on a deployment that has the feature; still gated on production, where sending the message returnsUNPARSABLE_CLIENT_MESSAGE— "The ForceEndTurn message is not enabled on this deployment." — and closes the websocket. See Force-end-turn and the end-of-turn controls.*ThinkProvider*/*SpeakProvider*prefixes and renamed the agent conversation-history subtypes (see Backward compatibility). New canonical names:AnthropicModel,CartesiaModelId,CartesiaVoice,DeepgramModel,GoogleModel,GoogleVersion,GroqReasoningMode,ConversationHistoryMessageRole,FunctionCallHistoryMessageFunctionCallsItem.SpeakV2SpeedValue— a dedicatedfloattype for the mid-streamSpeakV2Configure.speedfield.Force-end-turn and the end-of-turn controls
Updated 2026-08-20. This supersedes the earlier finding that force-end-turn was rejected everywhere. The gate has since opened on staging, and the feature is now verified end-to-end through the SDK.
send_force_end_turn(), continuous speechEndOfTurnwithtrigger="manual",end_of_turn_confidence=0.0085, connection stays open and the turn index advanceseot_threshold=0.7EndOfTurnwithtrigger="model"eot_threshold=1.0EndOfTurnwithtrigger="timeout"— not"model"eot_threshold=1.0+eot_timeout_ms=60000EndOfTurnat allsend_force_end_turn()EndOfTurnwithtrigger="manual"UNPARSABLE_CLIENT_MESSAGE, socket closedThe
eot_threshold=1.0row corrects an earlier reading. Counting events,0.7and1.0both produce exactly oneEndOfTurn, which looks like1.0suppresses nothing.triggershows that is a misattribution: at1.0Flux's confidence-based detection really is suppressed, and what closed the turn waseot_timeout_ms(default 5000), a separate mechanism. Nothing buttriggerdistinguishes those two cases — andtriggerwas not being emitted when this was last measured, which is why the distinction was invisible.So the docstring's advice to "set to 1.0 to fully suppress natural end-of-turn detection and drive turn endings yourself" is incomplete rather than wrong: it needs
eot_timeout_msraised as well. The last two rows show the working combination. Worth reporting upstream as a spec-doc fix — the SDK type is generated and unfrozen, so patching the docstring here would mean freezing a file for a comment; better fixed at the source.No SDK change was needed —
send_force_end_turn()serialized correctly andtriggerparsed all three values unmodified.Caveat: one API key per environment, so this shows the gate is open for that staging project; whether enablement is per-project, per-deployment, or global still needs the feature-flag config.
Backward compatibility (all preserved — non-breaking)
AnthropicThinkProviderModel→AnthropicModel,CartesiaSpeakProviderModelId→CartesiaModelId,CartesiaSpeakProviderVoice→CartesiaVoice,DeepgramSpeakProviderModel→DeepgramModel,GoogleThinkProviderModel→GoogleModel,GoogleThinkProviderVersion→GoogleVersion,GroqThinkProviderReasoningMode→GroqReasoningMode.AgentV1HistoryContentRole,AgentV1HistoryFunctionCallsFunctionCallsItem(+Params), and the double-Context...ContentRole/...FunctionCallsFunctionCallsItem(+Params) are recreated as hand-written aliases to the new canonical types, so every old import path still resolves.Context...MessagesItemContentRole/...FunctionCallsFunctionCallsItemshims whose underlying generated modules were deleted now re-export the new canonical types.send_force_end_turn()takes an optional message (no-payload control), matching its siblings; listen/v2send_configurestill accepts a raw dict alongside the typed model; callableAgentV1SettingsAgent(...), legacymessages=/.messages, andlanguage_hint=all continue to work.SpeakV2Speed retype — patched (spec bug, raised with Fern)
The generator retyped
SpeakV2Speed(thespeak.v2.connect(speed=...)param) fromfloattoUnion[Literal["0.85"…"1.15"], Any]— a string-literal enum. That contradicts the actual API contract and is inconsistent with the newSpeakV2SpeedValue = floaton the Configure message. Verified against production (wss://api.deepgram.com,flux-alexis-en):speed: 1.05(number) →ConfigureSuccess {applied.speed: 1.05}speed: 2.0→ConfigureFailure SPEED_OUT_OF_RANGE(value echoed back as the number2.0)speed: "1.05"(string) →Error MESSAGE-0000 "The message could not be parsed."So the string form the generated type implies is rejected at the wire. Patched
SpeakV2Speedback tofloat(frozen in.fernignore, guarded by a regression test). Raised upstream with Fern; unfreeze and drop the patch once the spec/generator emits a number.Maintenance
Patches re-applied (generator still needs them): socket-client broad
except+ optional no-payload control params; agent_sanitize_numeric_types;query_encoderbool coercion;ApiError/ParsingErrorcredential redaction; agent-settings andlanguage_hint→language_hintsback-compat validators;AgentV1UpdateListenListenlegacy-provider coercion; the legacy alias re-exports merged into 10__init__.py; both frozen wire tests.New frozen files this cycle: 14 back-compat alias modules for the renames, plus
types/speak_v2speed.py(thefloatpatch)..fernignoreand the AGENTS.md freeze ledger updated to match.Added test coverage:
send_force_end_turn(sync + async), listen/v2send_configuretyped-model + async paths, the provider/agent-history rename aliases, and theSpeakV2Speed-is-floatregression guard. Plus live coverage intests/manual/listen/v2/force_end_turn/(sync + async, since the two socket clients have separate implementations) andexamples/16-transcription-force-end-turn.py. All three report the gate and exit cleanly where the feature is not enabled, rather than dying on the connection teardown the rejection causes.Verification
mypy+ruffclean, 992 passed / 1 skipped, coverage 94.69%.tests/manualscripts pass against the live API, plus the two newforce_end_turnscripts — all four checks pass against a deployment with the feature, and both report SKIP against production.27-transcription-live-sagemaker.py, which needs the optionaldeepgram_sagemakerpackage (not a regression). New example 16 verified against both a feature-enabled deployment and production.Version bump
The branch carries a patch bump in
pyproject.toml, but this is afeat. That's expected — the generator patch-bumps on every regen and release-please recomputes the correct minor at release time from the conventional-commit type. This only holds because the squash-merge message isfeat(regen): …, so the PR title matters here.