Point Your OpenAI Code at Your Own Voice Stack
JarbasAl
OVOS Contributor
Point Your OpenAI Code at Your Own Voice Stack
A lot of software already knows how to talk to one company's cloud: openai.audio.speech.create(...) for text-to-speech, openai.audio.transcriptions.create(...) for speech-to-text, openai.chat.completions.create(...) for chat. That code works, until you remember that every word of audio and every document you send it leaves your machine.
The OVOS speech servers speak that same API. Point your existing OpenAI-SDK code at your own server instead of the cloud: run the server on your own machine, and nothing has to leave it. Change the base_url, keep the rest of your code.
This is part of the Third-Party Server Compatibility work: OVOS servers expose drop-in OpenAI-compatible HTTP endpoints, and for TTS, MaryTTS-compatible ones too.
What Shipped
Each server acts as a compatible front-end to local OVOS plugins. Your app talks to what looks like a cloud API. underneath, it is a local Piper voice, a local Whisper model, or your own persona running on hardware you control.
| OVOS Server | Compatible API Surface | PR |
|---|---|---|
ovos-tts-server |
OpenAI audio.speech |
#88 |
ovos-tts-server |
MaryTTS /process, /voices, /locales |
#94 |
ovos-stt-http-server (repository ovos-stt-server) |
OpenAI audio.transcriptions (and audio.translations) |
#77 |
ovos-persona-server |
OpenAI chat.completions |
#14 |
ovos-persona-server |
OpenAI embeddings, files, vector stores |
#11 |
ovos-openai-plugin |
RAG memory built on the persona-server vector-store API | #54 |
The ovos-tts-server OpenAI route mounts under /openai, so audio.speech lands at /openai/v1/audio/speech. The MaryTTS router is available both under /marytts and at the bare /process path, because real MaryTTS clients, including Home Assistant's marytts integration and ovos-tts-plugin-marytts, hardcode the root path and cannot be pointed at a sub-prefix.
The servers also picked up a set of vendor backends behind the same client-facing API.
ovos-stt-http-server added routers for these vendors:
- Deepgram (#54)
- Google (#55)
- AssemblyAI (#56)
- Speechmatics (#57)
- Azure (#58)
- AWS Transcribe (#60)
- IBM Watson (#61)
- Wit.ai (#62)
- Vosk (#63)
- whisper.cpp (#64)
- and Kaldi (#69)
ovos-tts-server added an ElevenLabs-compatible router next to OpenAI and MaryTTS. The audio itself is still rendered by whatever OVOS TTS plugin you load (Piper and others). Point the same client code at a cloud vendor or a local one. The endpoint shape does not change.
How to Try It
Install and start the two speech servers:
pip install "ovos-tts-server[audio]" ovos-tts-plugin-piper
ovos-tts-server --engine ovos-tts-plugin-piper
# listens on http://localhost:9666 by default
# the OpenAI router is in the prerelease line of the STT server, so ask for it
pip install --pre ovos-stt-http-server ovos-stt-plugin-fasterwhisper
ovos-stt-server --engine ovos-stt-plugin-fasterwhisper
# listens on http://localhost:8080 by default
The TTS routes on ovos-tts-server 1.13.4 with the Piper plugin, from its OpenAPI document: /openai/v1/audio/speech, /process, /voices, /locales, the same three under /marytts, and an /elevenlabs set beside them. The MaryTTS root route answers a plain GET with a WAV:
$ curl -o hello.wav 'http://localhost:9666/process?INPUT_TEXT=hello&INPUT_TYPE=TEXT&OUTPUT_TYPE=AUDIO&AUDIO=WAVE_FILE&LOCALE=en_US'
$ file hello.wav
hello.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 16000 Hz
From here it is ordinary openai SDK code. The only thing that changed is base_url.
from openai import OpenAI
# Point the SDK at your local OVOS speech servers.
# The API key is ignored by the local servers.
tts = OpenAI(base_url="http://localhost:9666/openai/v1", api_key="local")
stt = OpenAI(base_url="http://localhost:8080/openai/v1", api_key="local")
# --- Text to speech (served by a local Piper voice) ---
speech = tts.audio.speech.create(
model="tts-1",
voice="alloy",
input="Hello from my own private voice stack.",
)
speech.stream_to_file("hello.wav")
# the TTS route answers with MP3 audio; hello.wav came back as 8864 bytes of MPEG layer III
# --- Speech to text (served by a local Whisper model) ---
with open("hello.wav", "rb") as audio:
result = stt.audio.transcriptions.create(
model="whisper-1",
file=audio,
)
print(result.text)
For the reasoning layer, ovos-persona-server speaks chat.completions, so agent frameworks that expect OpenAI can drive a fully local persona:
pip install --pre ovos-persona-server
ovos-persona-server --persona /path/to/my-persona.json
# listens on http://localhost:8337 by default
llm = OpenAI(base_url="http://localhost:8337/openai/v1", api_key="local")
reply = llm.chat.completions.create(
model="ovos-persona",
messages=[{"role": "user", "content": "What's the weather like?"}],
)
print(reply.choices[0].message.content)
On 0.17.5a2, with a persona named demo whose chat engine points at an OpenAI-compatible endpoint, the raw route answers like this (asked to reply with exactly three words):
{"id": "chatcmpl-r7Tqj3yYmgTjPvOD8bciNBYdTAm1", "object": "chat.completion", "model": "demo",
"choices": [{"index": 0, "finish_reason": "stop",
"message": {"role": "assistant", "content": "Understood. Will comply."}}],
"usage": {"prompt_tokens": 5, "completion_tokens": 3, "total_tokens": 8}}
GET /openai/v1/models lists the loaded personas as models, and /openai/v1/files/ and /openai/v1/vector_stores/ answer 200 on the same server.
The persona server is not OpenAI-only. It mounts parallel routers for Ollama, Cohere, Anthropic, Gemini, AWS Bedrock, and HuggingFace TGI, each under its own prefix (/ollama/api/..., /cohere/v1/..., /anthropic/v1/..., and so on). One process can answer clients written against several different vendor SDKs at once, all backed by the same local persona.
Retrieval Without the Cloud
The same persona server also implements the OpenAI files and vector-store endpoints (/openai/v1/files, /openai/v1/vector_stores), which is what makes local RAG possible. Upload documents, let the server chunk and embed them with an OVOS embeddings plugin, and search them, all on your own box.
ovos-openai-plugin ships PersonaServerRAGMemory to close the loop. It is an OVOS persona memory plugin: before each turn it searches a persona-server vector store and injects the retrieved chunks into the conversation, then hands off to the persona's normal chat backend to write the answer. RAG composes with any chat engine instead of owning the round-trip. Everything is configurable from the persona's JSON block. You set how many results to retrieve, a minimum score to drop weak hits, whether to query on the latest utterance or fold in prior turns, and where the context lands (system prompt, developer message, or tool call):
{
"name": "kb-assistant",
"solvers": ["ovos-chat-openai-plugin"],
"memory_module": "ovos-openai-rag-memory-plugin",
"ovos-openai-rag-memory-plugin": {
"api_url": "http://localhost:8337/openai/v1",
"vector_store_id": "vs_...",
"retrieval": {"max_num_results": 5, "query_mode": "history"},
"inject_mode": "system"
}
}
A private knowledge base, a local chat model, and OpenAI-shaped clients in front of both. As long as you keep the vendor routers pointed at nothing and the server bound to your own network, no document has to leave it.
Why This Matters
-
Privacy by default, not by force. If you use the local Piper, Whisper, or persona backends, audio, transcripts, and documents stay on your machine. The endpoint your app talks to is
localhost. Switch a router to Deepgram, Azure, or another cloud vendor and that vendor sees the audio or text again, same as through any other client. The servers do not hide that trade-off. They let you make it per request instead of per app. -
Offline, when you choose the local backends. No internet round-trip, no rate limits, no surprise outages for the parts you keep local.
-
No lock-in. The API surface is a shared shape, not a leash. Point the same client at OVOS, a cloud vendor, and back again by changing a URL.
-
Drop-in migration. Existing OpenAI-SDK apps adopt your private stack with a one-line diff, not a rewrite.
This work is part of the OpenVoiceOS From Beta to Breakthrough milestone, funded through the NGI0 Commons Fund, a fund established by NLnet with financial support from the European Commission's Next Generation Internet programme, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 101135429. Additional funding is made available by the Swiss State Secretariat for Education, Research and Innovation (SERI).
Help Us Build Voice for Everyone
OpenVoiceOS is more than software, it's a mission. If you believe voice assistants should be open, inclusive, and user-controlled, here's how you can help:
- 💸 Donate: Help us fund development, infrastructure, and legal protection.
- 📣 Contribute Open Data: Share voice samples and transcriptions under open licenses.
- 🌍 Translate: Help make OVOS accessible in every language.
We're not building this for profit. We're building it for people. With your support, we can keep voice tech transparent, private, and community-owned.