From f3f1a1085c256f81f7557919c0713c3f92dc2007 Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Fri, 17 Jul 2026 10:53:30 +0100 Subject: [PATCH] feat(speak): default to Flux TTS (flux-alexis-en) instead of Aura 2 Change the default `dg speak` model from aura-2-asteria-en to flux-alexis-en, so a bare `dg speak` now uses Flux TTS (Speak v2, WebSocket streaming) out of the box. Routing is unchanged and still prefix-based, so Aura remains available via `-m aura-*`. Because the default now takes the v2 streaming path (raw linear16 wrapped in WAV), containerized formats like mp3 require an explicit `-m aura-*`. Updated --model/agent help and README examples to reflect Flux-by-default with Aura as opt-in, and added a test asserting no -m routes to Flux (v2) rather than Aura (v1). The core client speak_text() default is intentionally left as aura-2-asteria-en: it is the Aura-only v1 REST path; Flux uses speak_text_stream(). --- README.md | 35 +++++++++-------- .../src/deepctl_cmd_speak/command.py | 39 ++++++++++--------- .../tests/unit/test_speak_command.py | 32 +++++++++++++++ 3 files changed, 71 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 097f8e2..4e38ff8 100644 --- a/README.md +++ b/README.md @@ -75,8 +75,8 @@ dg login # Transcribe an audio file dg listen recording.wav -# Text-to-speech -dg speak "Hello from Deepgram" -o hello.mp3 +# Text-to-speech (Flux TTS by default) +dg speak "Hello from Deepgram" -o hello.wav # Live microphone transcription dg listen --mic @@ -132,8 +132,9 @@ dg read earnings.txt --sentiment --summarize --topics ### Text-to-speech ```bash -# Stream directly to a player -dg speak "Hello from Deepgram" | ffplay -nodisp -autoexit - +# Stream directly to a player (Flux TTS streams a WAV; -loglevel error hides +# ffmpeg's cosmetic end-of-stream notice) +dg speak "Hello from Deepgram" | ffplay -loglevel error -nodisp -autoexit - ``` ### Account & project management @@ -173,22 +174,22 @@ cat audio.raw | dg listen --encoding linear16 --sample-rate 16000 Convert text to natural speech. Supports file output and piping. -`aura-*` models use the Speak v1 batch REST API. `flux-*` (Flux TTS) models use -the Speak v2 WebSocket API and stream by default; their raw `linear16` output is -wrapped in a WAV container so it is directly playable. +By default `dg speak` uses Flux TTS (`flux-alexis-en`) — the Speak v2 WebSocket +API, which streams and emits raw audio; its `linear16` output is wrapped in a WAV +container so it is directly playable. Pass an `aura-*` model to use the Speak v1 +batch REST API instead, which supports containerized formats like MP3. ```bash -# Aura (v1, batch REST) -dg speak "Welcome to Deepgram" -o welcome.mp3 -dg speak --file script.txt -o output.mp3 -m aura-2-luna-en -echo "Hello" | dg speak -o greeting.mp3 -dg speak "Stream me" | ffplay -nodisp - # pipe to audio player - -# Flux TTS (v2, WebSocket streaming) -dg speak "Hello from Flux" -m flux-alexis-en -o hello.wav +# Flux TTS (v2, WebSocket streaming) — the default +dg speak "Hello from Flux" -o hello.wav # Piped audio is a streaming WAV; -loglevel error hides ffmpeg's cosmetic # end-of-stream notice (the audio is complete). -dg speak "Hello from Flux" -m flux-alexis-en | ffplay -loglevel error -nodisp -autoexit - +dg speak "Hello from Flux" | ffplay -loglevel error -nodisp -autoexit - + +# Aura (v1, batch REST) — opt in with -m aura-*; needed for MP3 output +dg speak "Welcome to Deepgram" -o welcome.mp3 -m aura-2-asteria-en +dg speak --file script.txt -o output.mp3 -m aura-2-luna-en +echo "Hello" | dg speak -o greeting.mp3 -m aura-2-asteria-en ``` ### Text Intelligence @@ -292,7 +293,7 @@ export DEEPGRAM_PROJECT_ID="your-project-id" # Non-interactive usage dg listen recording.wav -dg speak "Deploy complete" -o notification.mp3 +dg speak "Deploy complete" -o notification.mp3 -m aura-2-asteria-en dg keys --create --comment "ci-key" --scopes member dg keys --delete KEY_ID --yes dg read --file report.txt --summarize diff --git a/packages/deepctl-cmd-speak/src/deepctl_cmd_speak/command.py b/packages/deepctl-cmd-speak/src/deepctl_cmd_speak/command.py index 4c9dd83..e45eaa3 100644 --- a/packages/deepctl-cmd-speak/src/deepctl_cmd_speak/command.py +++ b/packages/deepctl-cmd-speak/src/deepctl_cmd_speak/command.py @@ -162,26 +162,29 @@ class SpeakCommand(BaseCommand): ci_friendly = True examples = [ + # Flux TTS is the default (flux-alexis-en) — WebSocket streaming, + # raw audio wrapped in WAV. Piped audio is a streaming WAV (unknown + # length up front), so pass `-loglevel error` to silence ffmpeg's + # cosmetic end-of-stream notice. 'dg speak "Hello world"', - 'dg speak "Hello world" -o hello.mp3', - "dg speak --file message.txt -o output.mp3", - 'echo "Hello" | dg speak -o hello.mp3', - 'dg speak "Hello" | ffplay -nodisp -', + 'dg speak "Hello world" -o hello.wav', + "dg speak --file message.txt -o output.wav", + 'dg speak "Hello" | ffplay -loglevel error -nodisp -autoexit -', + # Aura (Speak v1, batch REST) — opt in with -m aura-*; needed for + # containerized formats like mp3. + 'dg speak "Hello" -m aura-2-asteria-en -o hello.mp3', 'dg speak "Hello" -m aura-2-luna-en -o hello.wav --encoding linear16 --container wav', - # Flux TTS — WebSocket streaming (flux-* models), streaming by default. - # Piped audio is a streaming WAV (unknown length up front), so pass - # `-loglevel error` to silence ffmpeg's cosmetic end-of-stream notice. - 'dg speak "Hello from Flux" -m flux-alexis-en -o hello.wav', - 'dg speak "Hello from Flux" -m flux-alexis-en | ffplay -loglevel error -nodisp -autoexit -', + 'echo "Hello" | dg speak -o hello.mp3 -m aura-2-asteria-en', ] agent_help = ( "Convert text to speech using Deepgram's TTS API. " "Text can be provided as an argument, from a file, or piped via stdin. " "Audio is written to a file (--output) or stdout for piping. " - "aura-* models use Speak v1 (batch REST). flux-* (Flux TTS) models use " - "Speak v2, streaming over WebSocket by default and emitting raw audio; " - "linear16 output is wrapped in a WAV container so it is directly playable. " - "Supports model selection and audio format options." + "By default (flux-* models, e.g. flux-alexis-en) uses Flux TTS / Speak " + "v2, streaming over WebSocket and emitting raw audio; linear16 output is " + "wrapped in a WAV container so it is directly playable. Pass an aura-* " + "model to use Speak v1 (batch REST), which supports containerized " + "formats like mp3. Supports model selection and audio format options." ) def get_arguments(self) -> list[dict[str, Any]]: @@ -201,13 +204,13 @@ def get_arguments(self) -> list[dict[str, Any]]: { "names": ["--model", "-m"], "help": ( - "TTS model. aura-* = Speak v1 (REST batch; default " - "aura-2-asteria-en); flux-* = Flux TTS / Speak v2 (WebSocket " - "streaming, e.g. flux-alexis-en)." + "TTS model. flux-* = Flux TTS / Speak v2 (WebSocket " + "streaming; default flux-alexis-en); aura-* = Speak v1 " + "(REST batch, e.g. aura-2-asteria-en)." ), "type": str, "is_option": True, - "default": "aura-2-asteria-en", + "default": "flux-alexis-en", }, { "names": ["--encoding"], @@ -251,7 +254,7 @@ def handle( ) -> BaseResult | None: text = kwargs.get("text") output_path = kwargs.get("output") - model = kwargs.get("model") or "aura-2-asteria-en" + model = kwargs.get("model") or "flux-alexis-en" encoding = kwargs.get("encoding") container = kwargs.get("container") sample_rate = kwargs.get("sample_rate") diff --git a/packages/deepctl-cmd-speak/tests/unit/test_speak_command.py b/packages/deepctl-cmd-speak/tests/unit/test_speak_command.py index 29c0ce5..b84831b 100644 --- a/packages/deepctl-cmd-speak/tests/unit/test_speak_command.py +++ b/packages/deepctl-cmd-speak/tests/unit/test_speak_command.py @@ -264,6 +264,38 @@ def test_handle_write_to_file( assert output_file.read_bytes() == b"chunk1chunk2" + @patch("deepctl_cmd_speak.command.sys") + def test_handle_default_model_routes_to_flux( + self, mock_sys, command, mock_config, mock_auth_manager, mock_client, tmp_path + ): + """With no -m, the default is Flux (v2 streaming), not Aura (v1 REST).""" + mock_sys.stdin.isatty.return_value = True + mock_sys.stdout.isatty.return_value = True + + pcm = b"\x01\x00\x02\x00" # raw 16-bit PCM + mock_client.speak_text_stream.return_value = iter([pcm]) + + output_file = tmp_path / "hello.wav" + result = command.handle( + config=mock_config, + auth_manager=mock_auth_manager, + client=mock_client, + text="Hello world", + output=str(output_file), + model=None, + encoding=None, + container=None, + sample_rate=None, + file=None, + ) + + assert isinstance(result, SpeakResult) + assert result.model == "flux-alexis-en" + # Routed to streaming (v2), not batch REST (v1). + mock_client.speak_text_stream.assert_called_once() + mock_client.speak_text.assert_not_called() + assert output_file.read_bytes()[:4] == b"RIFF" + @patch("deepctl_cmd_speak.command.sys") def test_handle_flux_streams_and_wraps_wav( self,