server: compatible with OpenAI API, add model alias

This commit is contained in:
karl
2026-07-17 21:13:57 +02:00
committed by Pascal
parent 19bbf94dcc
commit 8d556c9806
5 changed files with 25 additions and 18 deletions
+3 -2
View File
@@ -132,9 +132,10 @@ them by name :
```
./build/tts-server \
--model models/qwen-talker-1.7b-base-Q8_0.gguf \
--codec models/qwen-tokenizer-12hz-Q8_0.gguf --port 8080
--codec models/qwen-tokenizer-12hz-Q8_0.gguf \
--alias qwen3-tts-base --port 8080
curl -X POST localhost:8080/v1/voices -H "Content-Type: application/json" \
curl -X POST localhost:8080/v1/audio/voices -H "Content-Type: application/json" \
-d "{\"name\":\"freeman\",\"ref_text\":\"$(cat ref.txt)\",
\"spk_b64\":\"$(base64 -w0 ref.spk)\",\"rvq_b64\":\"$(base64 -w0 ref.rvq)\"}"
+5 -4
View File
@@ -617,6 +617,7 @@ Required:
--codec <gguf> Codec GGUF (qwen-tokenizer-*.gguf)
Optional:
--alias <name> Report this model id instead of the GGUF file name
--host <ip> Listen address (default: 127.0.0.1)
--port <n> Listen port (default: 8080)
--lang <n> Language label (default: auto)
@@ -636,14 +637,14 @@ POST /v1/audio/speech OAI text-to-speech; response_format "pcm"
fields keep the engine defaults, temperature
0 selects greedy decoding, the subtalker
mirrors the talker knobs
GET /v1/models single loaded model
GET /v1/voices model speakers plus registered cloned voices
POST /v1/voices register a cloned voice: {name, ref_text,
GET /v1/models single loaded model, using --alias when set
GET /v1/audio/voices model speakers plus registered cloned voices
POST /v1/audio/voices register a cloned voice: {name, ref_text,
wav_b64} extracts server side through
qt_extract_voice_ref, {name, ref_text,
spk_b64, rvq_b64} takes the pre-extracted
latents verbatim
DELETE /v1/voices/{name} drop a registered voice
DELETE /v1/audio/voices/{name} drop a registered voice
GET /health liveness probe
```
+1
View File
@@ -4,4 +4,5 @@
./build/tts-server \
--model models/qwen-talker-1.7b-base-Q8_0.gguf \
--codec models/qwen-tokenizer-12hz-Q8_0.gguf \
--alias qwen3-tts-base \
--host 127.0.0.1 --port 8080 --lang auto
+10 -10
View File
@@ -10,12 +10,12 @@
// Endpoints:
// POST /v1/audio/speech OAI text-to-speech
// GET /v1/models single loaded model
// GET /v1/voices model speakers plus registered cloned voices
// POST /v1/voices register a cloned voice: {name, ref_text,
// GET /v1/audio/voices model speakers plus registered cloned voices
// POST /v1/audio/voices register a cloned voice: {name, ref_text,
// wav_b64} extracts server side, {name,
// ref_text, spk_b64, rvq_b64} takes
// pre-extracted latents verbatim
// DELETE /v1/voices/{name} drop a registered voice
// DELETE /v1/audio/voices/{name} drop a registered voice
// GET /health liveness probe
//
// Audio out: response_format "pcm" streams s16le 24 kHz mono chunked as it
@@ -54,7 +54,7 @@ struct tts_request {
float repetition_penalty; // strictly positive
};
// One voice registration parsed from the POST /v1/voices JSON body.
// One voice registration parsed from the POST /v1/audio/voices JSON body.
// Exactly one payload form is present: wav holds decoded base64 WAV
// bytes for server side extraction, or spk plus rvq hold the raw
// contents of pre-extracted .spk and .rvq files. ref_text carries the
@@ -75,7 +75,7 @@ using tts_sink = std::function<bool(const float * samples, int n_samples)>;
// Adapter implemented by each project tool.
struct tts_backend {
std::string model_id; // reported by GET /v1/models
std::vector<std::string> voices; // reported by GET /v1/voices, may be empty
std::vector<std::string> voices; // reported by GET /v1/audio/voices, may be empty
// Run synthesis. When the request streams, the adapter routes the ABI
// on_chunk to sink ; otherwise it pushes the whole buffer once. Returns
// the ABI status (0 on success), and fills err with the ABI message on
@@ -84,7 +84,7 @@ struct tts_backend {
// Voice registry hooks, all optional: a null hook answers 501 on the
// matching route. register_voice stores or replaces a cloned voice,
// remove_voice drops one (false when absent), registered_voices lists
// the current names for GET /v1/voices alongside the model speakers.
// the current names for GET /v1/audio/voices alongside the model speakers.
std::function<bool(const tts_voice_upload & up, std::string & err)> register_voice;
std::function<bool(const std::string & name)> remove_voice;
std::function<std::vector<std::string>()> registered_voices;
@@ -334,7 +334,7 @@ static bool tts_b64_decode(const std::string & in, std::string & out) {
return true;
}
// Parse the POST /v1/voices body: name plus either wav_b64 or the
// Parse the POST /v1/audio/voices body: name plus either wav_b64 or the
// spk_b64 / rvq_b64 pair, ref_text optional (enables ICL clone mode).
static bool tts_parse_voice_upload(const std::string & body, tts_voice_upload & up, std::string & err) {
yyjson_doc * doc = yyjson_read(body.c_str(), body.size(), 0);
@@ -517,11 +517,11 @@ static int tts_server_run(const tts_backend & be, const server_config & cfg) {
[&be](const httplib::Request & req, httplib::Response & res) { tts_handle_speech(be, req, res); });
svr.Get("/v1/models",
[&be](const httplib::Request & req, httplib::Response & res) { tts_handle_models(be, req, res); });
svr.Get("/v1/voices",
svr.Get("/v1/audio/voices",
[&be](const httplib::Request & req, httplib::Response & res) { tts_handle_voices(be, req, res); });
svr.Post("/v1/voices",
svr.Post("/v1/audio/voices",
[&be](const httplib::Request & req, httplib::Response & res) { tts_handle_voice_register(be, req, res); });
svr.Delete(R"(/v1/voices/(.+))",
svr.Delete(R"(/v1/audio/voices/(.+))",
[&be](const httplib::Request & req, httplib::Response & res) { tts_handle_voice_delete(be, req, res); });
svr.Get("/health", tts_handle_health);
+6 -2
View File
@@ -42,6 +42,7 @@ static void print_usage(const char * prog) {
" --model <gguf> Talker LM GGUF (qwen-talker-*.gguf)\n"
" --codec <gguf> Codec GGUF (qwen-tokenizer-*.gguf)\n\n"
"Optional:\n"
" --alias <name> Report this model id instead of the GGUF file name\n"
" --host <ip> Listen address (default: 127.0.0.1)\n"
" --port <n> Listen port (default: 8080)\n"
" --lang <name> Language label (default: auto)\n"
@@ -60,6 +61,7 @@ static std::string basename_of(const char * path) {
int main(int argc, char ** argv) {
const char * talker_path = NULL;
const char * codec_path = NULL;
std::string model_alias;
std::string lang = "auto";
server_config cfg;
bool use_fa = true;
@@ -71,6 +73,8 @@ int main(int argc, char ** argv) {
talker_path = argv[++i];
} else if (!std::strcmp(arg, "--codec") && i + 1 < argc) {
codec_path = argv[++i];
} else if (!std::strcmp(arg, "--alias") && i + 1 < argc) {
model_alias = argv[++i];
} else if (!std::strcmp(arg, "--host") && i + 1 < argc) {
cfg.host = argv[++i];
} else if (!std::strcmp(arg, "--port") && i + 1 < argc) {
@@ -110,13 +114,13 @@ int main(int argc, char ** argv) {
}
tts_backend be;
be.model_id = basename_of(talker_path);
be.model_id = model_alias.empty() ? basename_of(talker_path) : model_alias;
int n = qt_n_speakers(q);
for (int i = 0; i < n; i++) {
be.voices.push_back(qt_speaker_name(q, i));
}
// Voice registry: POST /v1/voices stores a cloned voice either from a
// Voice registry: POST /v1/audio/voices stores a cloned voice either from a
// WAV (server side extraction through qt_extract_voice_ref) or from
// pre-extracted .spk / .rvq payloads. Re-registering a name replaces
// the previous entry.