Skip to content

Deepgram

contrib ยท v0.1.0

Deepgram behind the voice contracts: live speech-to-text (Nova) and streaming text-to-speech (Aura). Install both, or one part with --only stt or --only tts. The API key stays on the server.

Install copit add @chanx-kit/deepgram
Import from deepgram
Requires kits audio-stream-in, audio-stream-out
Parts stt (transcriber.py), tts (synthesizer.py): all by default, or copit add @chanx-kit/deepgram --only <part>
Contract transcriber@1
UI kits transcriber
Contract synthesizer@1
UI kits player
Python packages chanx>=2.10.2,<3, websockets>=13
Keywords audio, speech-to-text, text-to-speech, deepgram, provider
Source kits/deepgram

Deepgram: live speech-to-text (Nova) and streaming text-to-speech (Aura), each behind its contract, so any transcriber or player UI works with it and switching provider is one class.

copit add @chanx-kit/deepgram                 # both parts
copit add @chanx-kit/deepgram --only stt      # speech-to-text only
copit add @chanx-kit/deepgram --only tts      # text-to-speech only
Part Module Contract Needs
stt transcriber.py transcriber@1 audio-stream-in
tts synthesizer.py synthesizer@1 audio-stream-out

Leaving a part out also leaves out the base kit only it needs. Import from the part's module (from .ws_kits.deepgram.transcriber import ...), since the other may not be there. Picking parts needs copit 0.9; older copit installs both.

Set DEEPGRAM_API_KEY in the server's environment; the browser never sees it.

Speech to text

Live speech-to-text with Deepgram behind the transcriber contract, so any transcriber UI works with it and you can switch providers by changing one class.

from .ws_kits.audio_stream_in import TranscriptTopic
from .ws_kits.deepgram.transcriber import DeepgramTranscriberTopic

class VoiceTranscriberTopic(DeepgramTranscriberTopic):
    model = "nova-3"
    options = {"smart_format": "true"}

    async def authorize(self, **params: str) -> bool:
        return await user_owns_session(self.scope, params["session"])

class VoiceConsumer(AsyncJsonWebsocketConsumer):
    topics = [VoiceTranscriberTopic, TranscriptTopic]

Set DEEPGRAM_API_KEY in the server's environment. The browser never sees it: it streams to your chanx server, which holds the Deepgram connection. Without a key the topic reports provider_unavailable on subscribe rather than failing at import.

How it maps

Deepgram transcriber@1
interim Results transcript_partial
Results with is_final transcript_final
speech_final, UtteranceEnd utterance_end
SpeechStarted (vad_events) speech_started
KeepAlive while no audio arrives (kept open during pauses)
Finalize then CloseStream on audio_end last finals, then the stream closes

Audio goes up as binary frames, which is Deepgram's native format: base64 is only on the browser-to-chanx leg.

The adapter speaks Deepgram's WebSocket protocol with websockets directly, no SDK. Its tests run against a local server replaying the documented protocol, plus the shared transcriber contract suite.

Verified against the live service (2026-09-27, scripts/live_check.py deepgram, and the sandbox in a browser): speech-started events, partials, finals and utterance ends, with a two-sentence line split into two turns at the pause.

Text to speech

Streaming text-to-speech with Deepgram Aura behind the synthesizer contract.

from .ws_kits.deepgram.synthesizer import DeepgramSynthesizerTopic

class ReplyVoiceTopic(DeepgramSynthesizerTopic):
    default_voice = "aura-2-thalia-en"   # the voice is the model

Set DEEPGRAM_API_KEY on the server; the same key serves the Deepgram transcriber. Each utterance opens the speak WebSocket with encoding=linear16 at the output rate, sends Speak and Flush, streams the binary audio until Flushed, then Closes.

Verified against the live service (2026-09-27): Aura speech for a two-sentence line, transcribed back by the Deepgram transcriber.