Skip to content

fix: clarify ForceEndTurn deployment behavior - #788

Merged
GregHolmes merged 7 commits into
mainfrom
gh/fix-force-end-turn-followups
Oct 20, 2026
Merged

GregHolmes merged 7 commits into
mainfrom
gh/fix-force-end-turn-followups

Conversation

@GregHolmes

@GregHolmes GregHolmes commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • State that ForceEndTurn is available on hosted deployments, including EU and AU, while self-hosted availability follows the deployed release.
  • Report FORCE_END_TURN_UNSUPPORTED immediately in the Voice Agent example, and raise its errors (rejection and timeouts) after the connection closes.
  • Restore the socket-client grouping in the generated-patch ledger.
  • Update the StrictInt expressivity patch's unfreeze condition in .fernignore and AGENTS.md.
  • Add tests/custom/test_voice_agent_force_end_turn_example.py covering the late-rejection wait and the leave-context-before-raising path.

Validation

  • python3 -m compileall on both changed examples
  • git diff --check
  • ruff check in a Python 3.12 container
  • pytest tests/custom/test_voice_agent_force_end_turn_example.py (2 passed)
  • mypy examples/32-voice-agent-force-end-turn.py

@github-actions

github-actions Bot commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Code Coverage

Package Line Rate Branch Rate Complexity Health
src.deepgram 97% 94% 0 ✔
src.deepgram.agent 100% 100% 0 ✔
src.deepgram.agent.v1 98% 100% 0 ✔
src.deepgram.agent.v1.settings 100% 100% 0 ✔
src.deepgram.agent.v1.settings.think 100% 100% 0 ✔
src.deepgram.agent.v1.settings.think.models 97% 100% 0 ✔
src.deepgram.auth 100% 100% 0 ✔
src.deepgram.auth.v1 100% 100% 0 ✔
src.deepgram.auth.v1.tokens 97% 100% 0 ✔
src.deepgram.core 88% 81% 0 ➖
src.deepgram.errors 100% 100% 0 ✔
src.deepgram.helpers 98% 92% 0 ✔
src.deepgram.listen 100% 100% 0 ✔
src.deepgram.listen.v1 98% 93% 0 ✔
src.deepgram.listen.v1.media 97% 100% 0 ✔
src.deepgram.listen.v2 98% 93% 0 ✔
src.deepgram.manage 100% 100% 0 ✔
src.deepgram.manage.v1 100% 100% 0 ✔
src.deepgram.manage.v1.models 96% 100% 0 ✔
src.deepgram.manage.v1.projects 97% 100% 0 ✔
src.deepgram.manage.v1.projects.billing 100% 100% 0 ✔
src.deepgram.manage.v1.projects.billing.balances 96% 100% 0 ✔
src.deepgram.manage.v1.projects.billing.breakdown 97% 100% 0 ✔
src.deepgram.manage.v1.projects.billing.fields 97% 100% 0 ✔
src.deepgram.manage.v1.projects.billing.purchases 97% 100% 0 ✔
src.deepgram.manage.v1.projects.keys 96% 100% 0 ✔
src.deepgram.manage.v1.projects.members 97% 100% 0 ✔
src.deepgram.manage.v1.projects.members.invites 96% 100% 0 ✔
src.deepgram.manage.v1.projects.members.scopes 96% 100% 0 ✔
src.deepgram.manage.v1.projects.models 96% 100% 0 ✔
src.deepgram.manage.v1.projects.usage 98% 100% 0 ✔
src.deepgram.manage.v1.projects.usage.breakdown 97% 100% 0 ✔
src.deepgram.manage.v1.projects.usage.fields 97% 100% 0 ✔
src.deepgram.read 100% 100% 0 ✔
src.deepgram.read.v1 100% 100% 0 ✔
src.deepgram.read.v1.text 98% 100% 0 ✔
src.deepgram.self_hosted 100% 100% 0 ✔
src.deepgram.self_hosted.v1 100% 100% 0 ✔
src.deepgram.self_hosted.v1.distribution_credentials 96% 100% 0 ✔
src.deepgram.speak 100% 100% 0 ✔
src.deepgram.speak.v1 98% 97% 0 ✔
src.deepgram.speak.v1.audio 91% 80% 0 ✔
src.deepgram.speak.v2 98% 93% 0 ✔
src.deepgram.speak.v2.audio 100% 100% 0 ✔
src.deepgram.voice_agent 100% 100% 0 ✔
src.deepgram.voice_agent.configurations 95% 100% 0 ✔
src.deepgram.voice_agent.variables 95% 100% 0 ✔
Summary 95% (6556 / 6873) 91% (1438 / 1574) 0 ✔

Scope: hand-maintained SDK logic. Fern-generated data models (types/, requests/), package __init__.py files, version.py, and the unused core/http_sse/ scaffolding are excluded — see .coveragerc. Unscoped whole-package coverage is ~70%.

@dg-coreylweathers dg-coreylweathers left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What this PR does
Changes the ForceEndTurn examples and the conversational-STT skill file to say the control is available on Deepgram-hosted deployments including EU and AU (self-hosted depends on the release), makes the Voice Agent example fail fast on FORCE_END_TURN_UNSUPPORTED, and edits the manual-patch ledger.

What I checked

  • Is the availability claim true? Yes: the repo's own tests/manual/listen/v2/force_end_turn/main.py passed all four steps with trigger: manual against api.deepgram.com, api.eu.deepgram.com, and api.au.deepgram.com.
  • Is FORCE_END_TURN_UNSUPPORTED real? Yes, it is the Voice Agent warning for a non-Flux listen provider (deepgram-docs schemas.agent.v1.yml:220), and both Warning and Error carry .code/.description.
  • Gates in Docker: mypy clean, ruff clean on changed files, pytest 1034 passed.

Blocking

  • The AGENTS.md/.fernignore hunk for listen_v2eot_threshold.py and requests/deepgram_listen_provider_v2.py is stale: main's AGENTS.md:85 already says Fern emits that description natively as of the 2026-09-23 regen and the files were unfrozen, so main's .fernignore no longer lists them. The branch is also CONFLICTING. Fix: rebase onto main and drop that hunk entirely; keep only the StrictInt unfreeze-condition change.

Should-fix

  • "Self-hosted availability depends on the deployed release" (examples/16:24-25, SKILL.md:94) omits the switch. deepgram-docs says it needs listen_v2_force_end_turn = true under [features] on the 2026-09-15 release or later. Say that.
  • tests/manual/listen/v2/force_end_turn/main.py:24-27,174 and async.py:8,115 still say "gated per deployment" / "not enabled on this deployment"; align them with the new sentence.

Nit: examples/32:94-104, the trailing rejected-check can raise after agent_finished is set; fine if intended, worth a comment.

@GregHolmes
GregHolmes force-pushed the gh/fix-force-end-turn-followups branch from a46bb3b to 3836a89 Compare October 6, 2026 11:27

@dg-coreylweathers dg-coreylweathers left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes for two should-fixes and three small cleanups. Everything the PR claims checked out live; these are about the text and the example around it.

What this PR does: ForceEndTurn is a message that lets an app end a speaker's turn right away instead of waiting for Flux to detect it. The examples and manual tests used to call it "gated per deployment." They now say it works on Deepgram-hosted US, EU and AU, and that self-hosted customers need the 2026-09-15 release plus listen_v2 = true and listen_v2_force_end_turn = true under [features]. The Voice Agent example now stops at once on FORCE_END_TURN_UNSUPPORTED instead of timing out after 15 seconds.

What I checked:

  • Hosted availability: I ran tests/manual/listen/v2/force_end_turn/async.py today against api.deepgram.com, api.eu.deepgram.com and api.au.deepgram.com. All three returned EndOfTurn with trigger="manual".
  • Self-hosted claim: release-260915 shipped on 2026-09-15 with Flux Forced End-of-Turn. The flag names match what the self-hosted team gave on 9/4.
  • Voice Agent example: it completes against prod with Flux. With a Nova-3 listener it raises FORCE_END_TURN_UNSUPPORTED after 2 seconds.
  • Tests and lint: the new test passes, ruff is clean, and CI is green on 3.10–3.13.

Please fix:

  1. reference.md:5417 still says "Requires server-side enablement; deployments without the feature reject the message and close the connection," so a hosted developer reads it as "this may not work for me." Replace it with:
    - Available on Deepgram-hosted deployments (including EU and AU). Self-hosted deployments need the 2026-09-15 release or later with the ForceEndTurn feature enabled; unsupported deployments reply UNPARSABLE_CLIENT_MESSAGE and close the connection
  2. Delete the "Link the frozen EOT description patch to deepgram-docs #1202" bullet from the PR description. Nothing in the diff makes that change.
  3. examples/16-transcription-force-end-turn.py:59: the docstring still says "Because ForceEndTurn is gated … point it at a deployment that has the feature." Suggested: "Accepts DEEPGRAM_BASE_URL=wss:// so it can target a regional or self-hosted deployment. Defaults to production."
  4. examples/32-voice-agent-force-end-turn.py:125: raising inside the with block also prints "Connection error: sent 1011 (internal error)" after the real error. Record the failure, leave the with block, then raise.
  5. examples/32-voice-agent-force-end-turn.py:96-97: the new lines add two mypy attr-defined errors because message is typed object. Typing the callback parameter as typing.Any clears them.

Open question (not blocking): the 9/4 thread in #engineering-self-hosted-releases that names the listen_v2_force_end_turn flag also planned to remove it. If a later self-hosted release drops the flag, "2026-09-15 or later with listen_v2_force_end_turn = true" will be wrong for newer releases. Do you know whether that removal has shipped?

@GregHolmes
GregHolmes force-pushed the gh/fix-force-end-turn-followups branch from dfec20b to 715f194 Compare October 16, 2026 09:40
@GregHolmes

Copy link
Copy Markdown
Contributor Author

Addressed the lifecycle coverage feedback in 5798467.

  • Added a main()-level regression that simulates AgentAudioDone followed by FORCE_END_TURN_UNSUPPORTED.
  • Confirms the agent context exits normally before the example raises its user-facing error.
  • Re-ran focused tests (44 passed), Ruff, mypy for the Voice Agent example, compilation, and diff checks.

@dg-coreylweathers Could you re-review?

@dg-coreylweathers dg-coreylweathers left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. All nine items from my two earlier reviews are fixed in the code, and the example behaves as described against the live API.

What this PR does: ForceEndTurn lets an app end a speaker's turn right away. The examples, manual tests, reference.md and the skill file now say it works on Deepgram-hosted US, EU and AU, and that self-hosted needs the 2026-09-15 release plus listen_v2 = true and listen_v2_force_end_turn = true under [features]. The Voice Agent example now stops at once on FORCE_END_TURN_UNSUPPORTED.

What I checked:

  • Does the Voice Agent example stop on rejection? Yes. With a Nova-3 listener on prod (request 01a1461c-6cdb-7913-8741-8b7a4dd5b951) it printed the warning, raised RuntimeError with exit code 1, and printed no "Connection error: sent 1011" line. With Flux (01a1461c-7537-70e1-8673-a0b649211ddb) it completed with exit 0.
  • Does the new test catch the bug? Yes. Against main's version of the example both tests fail, one with "DID NOT RAISE RuntimeError" (a false success after AgentAudioDone). Against the version from my last review, the context-exit test fails.
  • Is the self-hosted flag still required? Yes, as of 2026-10-15. Linear API-75 plans to remove it; when that ships, these five sentences need an update.
  • mypy is clean on example 32, ruff is clean, and CI is green on 3.10–3.13.

Nits fixed in 1db8e8a: AGENTS.md:45 and example 16's comments now say "unsupported deployment" instead of "gated"; example 32 raises both timeouts after the connection closes, so a timeout no longer prints a "1011" connection error; ruff format applied. The example tests still pass (2 of 2), and ruff, ruff format and mypy are clean. The PR body now lists the StrictInt unfreeze change, the new test, and pytest/mypy.

Merge note: this conflicts with #789 in AGENTS.md (both edit the agent/v1/socket_client.py line). Keep #789's line, then this PR's StrictInt line below it.

GregHolmes and others added 7 commits October 19, 2026 13:48
Replace remaining "gated" wording with unsupported-deployment phrasing,
raise example 32 timeouts after the connection closes so they no longer
print a spurious 1011 connection error, and apply ruff format.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@dg-coreylweathers dg-coreylweathers left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approving. My earlier approval was dismissed by the rebase onto main; nothing new was added. Commits 2–7 are identical to what I approved, and commit 1 changed only where AGENTS.md and .fernignore collided with #789 and #803. Both of those PRs' lines are still there.

What I checked:

  • Does the new main break anything? No. #789 only adds send_raw(), which the Voice Agent example and its test never call, and #803 doesn't touch the Listen V2 code the examples use. In Docker, tests/custom gives 960 passed and 1 skipped, the new test passes 2 of 2, ruff and mypy are clean, and CI is green on 3.10–3.13.
  • Is "available on Deepgram-hosted deployments" true for India too? Yes. tests/manual/listen/v2/force_end_turn/async.py against wss://api.in.deepgram.com returned EndOfTurn with trigger='manual'.

Nits (non-blocking):

  • tests/manual/listen/v2/force_end_turn/async.py:93 still says "A gated deployment closes the socket". Suggested: "A deployment without ForceEndTurn closes the socket rather than ignoring the message,"
  • Optional: name India alongside EU and AU.

Follow-up: if Linear API-75 removes the listen_v2_force_end_turn flag in a self-hosted release, the self-hosted sentences in these files will need updating.

@dg-coreylweathers dg-coreylweathers left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving; details in the review above.

@GregHolmes
GregHolmes merged commit 1495ad1 into main Oct 20, 2026
10 checks passed
@GregHolmes
GregHolmes deleted the gh/fix-force-end-turn-followups branch October 20, 2026 09:00
GregHolmes added a commit that referenced this pull request Oct 20, 2026
🤖 I have created a release *beep* *boop*
---

##
[7.14.0](v7.13.0...v7.14.0)
(2026-10-20)

### Features

* **Voice Agent:** Send protocol-transparent control frames with sync or
async `connection.send_raw(...)`. Dictionary payloads are serialized
without model validation; pre-serialized JSON strings are sent
unchanged.
* **Listen V1:** Receive typed `ListenV1ConfigureSuccess` and
`ListenV1ConfigureFailure` responses after
`connection.send_configure(ListenV1Configure(...))`, including failure
codes for unsupported or over-limit keyterms.

### Bug Fixes

* **Voice Agent:** Clarify ForceEndTurn availability for hosted and
self-hosted deployments. The example now reports
`FORCE_END_TURN_UNSUPPORTED` and waits for the final outcome before
closing the connection.

## Validation

- Source PR CI passed across Python 3.10 through 3.13.
- #803: `poetry run pytest` passed with 1,075 tests and 4 skips; mypy
and Ruff passed.
- #789: targeted socket-client tests, mypy, and Ruff passed.
- #788: ForceEndTurn regression coverage passed.

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Greg Holmes <greg.holmes@deepgram.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants