waken-voice
Voice Source and Output for
Waken — "nginx for AI agents." Unlike
waken-claude/waken-gemini/waken-copilot, this package doesn't wrap an
AI backend at all: it wraps a channel. VoiceSource turns an audio file
into an Event; VoiceOutput turns a Response back into audio. Both talk
to a pluggable backend — OpenAI (Whisper + TTS) by default, or swap in Groq
(Whisper-backed transcription) and/or gTTS (free, no-API-key speech
synthesis).
What this is NOT: a live microphone listener
Continuous microphone capture with voice-activity-detection to segment
utterances is a real, hard, hardware/OS-dependent problem — and not one an
autonomously-built package can honestly claim to have solved without a real
microphone and a human speaking into it to verify against. So VoiceSource
doesn't try. Instead it watches a directory for audio files appearing on
disk, exactly like the built-in FilesystemSource watches for any file —
dropped there by a phone system, a Telegram/Slack voice-message webhook, a
push-to-talk recorder, or anything else. That's a small, honest, fully
testable scope that composes cleanly with whatever actually captures audio,
which is the same "small, composable pieces" philosophy Waken itself is
built on.
Install
pip install "waken-voice[openai]" # default: Whisper + TTS via OpenAI
pip install "waken-voice[groq]" # transcription via Groq instead
pip install "waken-voice[gtts]" # synthesis via gTTS instead (free, no key)
Each provider is an extra, not a hard dependency — install only the ones
you use. openai, groq, and gtts can all be combined in one install
(e.g. pip install "waken-voice[groq,gtts]" for a fully OpenAI-free setup).
- OpenAI: set
OPENAI_API_KEYin the environment, or passapi_key=...toOpenAIWhisperTranscriber/OpenAITTSSynthesizer. - Groq: set
GROQ_API_KEY, or passapi_key=...toGroqWhisperTranscriber. - gTTS: no API key — it calls Google Translate's public (undocumented) TTS endpoint directly.
Usage
Default (OpenAI for both directions — no transcriber/synthesizer
arguments needed):
from waken import Runtime
from waken_openai import OpenAIAdapter
from waken_voice import VoiceSource, VoiceOutput
runtime = Runtime()
runtime.target("openai", OpenAIAdapter())
runtime.source("voice-in", VoiceSource(watch="./voice-inbox", target="openai"))
runtime.output("voice-in", VoiceOutput(output_dir="./voice-outbox"))
runtime.run()
Groq for transcription, gTTS for synthesis:
from waken import Runtime
from waken_openai import OpenAIAdapter
from waken_voice import VoiceSource, VoiceOutput, GroqWhisperTranscriber, GTTSSynthesizer
runtime = Runtime()
runtime.target("openai", OpenAIAdapter())
runtime.source(
"voice-in",
VoiceSource(
watch="./voice-inbox",
target="openai",
transcriber=GroqWhisperTranscriber(), # reads GROQ_API_KEY
),
)
runtime.output(
"voice-in",
VoiceOutput(output_dir="./voice-outbox", synthesizer=GTTSSynthesizer(lang="en")),
)
runtime.run()
Drop a .wav/.mp3/.m4a/.ogg file into ./voice-inbox; VoiceSource
transcribes it and dispatches an Event(payload={"prompt": <transcript>})
to the "openai" target. runtime.output(name, ...) registers an Output
under a name resolved by event.source (or an explicit event.output) — so
registering VoiceOutput under "voice-in", the same name as the source
above, means a Response to a voice-originated Event gets spoken back
by default; see docs/api-spec.md
§3
and §9
in the main waken repo for the full delivery-resolution rule.
Providers
VoiceSource takes a Transcriber (anything with an async transcribe(path) -> str method); VoiceOutput takes a Synthesizer (anything with an async
synthesize(text) -> bytes method). Both are plain Protocols, not base
classes — write your own by matching the method signature, same
name-keyed-object philosophy as runtime.target("claude", ClaudeAdapter())
in core Waken.
| Transcriber (STT) | Synthesizer (TTS) | |
|---|---|---|
| OpenAI | OpenAIWhisperTranscriber(model="whisper-1", **client_kwargs) |
OpenAITTSSynthesizer(voice="alloy", model="tts-1", **client_kwargs) |
| Groq | GroqWhisperTranscriber(model="whisper-large-v3", **client_kwargs) |
— Groq has no TTS endpoint |
| gTTS | — Google Translate's TTS endpoint doesn't do STT | GTTSSynthesizer(lang="en", **gtts_kwargs) |
**client_kwargs forwards to the provider's own async client
(openai.AsyncOpenAI(...) / groq.AsyncGroq(...)); **gtts_kwargs forwards
to gtts.gTTS(...) (e.g. slow=True). Each class imports its SDK lazily on
construction, so you only need the extra actually installed for whichever
class you use.
gTTS is worth knowing the shape of before relying on it: it's a thin
wrapper around Google Translate's public but undocumented TTS endpoint, not
an official API with an uptime or rate-limit contract, and it offers a
fixed accent-per-language voice rather than named-voice selection like
OpenAI's alloy/nova/... It's genuinely free and requires no signup,
which is the whole appeal — just don't reach for it if you need guaranteed
availability or voice control.
VoiceSource
VoiceSource(
watch, # directory to poll for new audio files
target, # name of the registered Target to dispatch to
interval=1.0, # poll interval, seconds
source_name="voice",
transcriber=None, # a Transcriber; defaults to OpenAIWhisperTranscriber()
)
Polls watch every interval seconds for new files, same mechanism as the
built-in FilesystemSource (see
waken/plugins/sources/filesystem.py) —
no OS-level file-watching dependency, files present before start() are the
baseline and never fire. Only .wav, .mp3, .m4a, and .ogg files are
transcribed; anything else is ignored silently, since handing a non-audio
file to a transcriber is a guaranteed, pointless error rather than a
useful attempt.
VoiceOutput
VoiceOutput(
output_dir, # directory to write synthesized audio into
synthesizer=None, # a Synthesizer; defaults to OpenAITTSSynthesizer()
play=True, # attempt local playback after writing the file
)
Writes {event.event_id}.mp3 under output_dir. A Response with no
text delivers nothing.
Playback (play=True, the default) is best-effort and untested against
real audio hardware or OS combinations. _play shells out to a platform
player (afplay on macOS, paplay on Linux, an unverified fallback
elsewhere) via asyncio.create_subprocess_exec, after the file is already
safely written to disk — a missing or failing player is logged, not raised.
This is the one piece of the package that genuinely can't be verified by an
agent with no speakers to listen to; treat it as a starting point for your
own environment, not a guarantee. Set play=False and hook your own
playback (or upload/streaming path) onto the written file if you need
something verified for your setup. (Same honest-about-limits spirit as
waken-copilot's
README
regarding its own unverified pieces.)
Development
git clone https://github.com/WakenHQ/waken-voice
cd waken-voice
pip install -e ".[dev]"
pytest
dev pulls in openai, groq, and gtts together so the full suite can
run. Tests mock each provider's SDK client (openai.AsyncOpenAI,
groq.AsyncGroq, gtts.gTTS) and VoiceOutput._play entirely — no real
network access, API key, or audio hardware is required to run them.
License
Metadata
Release files for waken-voice 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| waken_voice-0.2.0.tar.gz | 12.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| waken_voice-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 24.1 kB
Release files / waken_voice-0.2.0.tar.gz
| Download URL | waken_voice-0.2.0.tar.gz |
|---|---|
| Size | 12.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6aa257bd497a2195bd9c4d1d68f520fefc6735ad06ca6352eddc7f5358ed1e29
|
|
BLAKE2b-256 checksum How to use checksums |
d8fc4cd3bfd17d486b0a4d7c960e366388c8051a08575cdb89f7e03340e2fe0b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 5, 2026.
Transparency logRelease files / waken_voice-0.2.0-py3-none-any.whl
| Download URL | waken_voice-0.2.0-py3-none-any.whl |
|---|---|
| Size | 12.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
96585e761596405855f7789dc2bcf1df24ee51522bf7a41a804782023289e39f
|
|
BLAKE2b-256 checksum How to use checksums |
a4e5da9037301117bc797a8928ca022102207680a4054b402e6f1822bb3da7e3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 5, 2026.
Transparency log