mirror of
https://github.com/mudler/LocalAI.git
synced 2026-07-30 18:09:05 -04:00
backend(audio-cpp): use upstream's 'spk' task name and pin the preference order
The SpeakerRecognition short name was 'spkrec', which audio.cpp neither prints nor parses; a name copied out of audio.cpp was rejected and a pinned 'spkrec' would not survive the engine boundary. Emit 'spk', keep 'spkrec' as an input-only alias, and correct the known-tasks lists. Three assertions were vacuous because their fixtures advertised a single task, so reversing a preference order or dropping the RPC name and the attempted pairs from the capability error all passed. Give them fixtures that can tell the orderings apart. Assisted-by: Claude:claude-opus-5 [Claude Code] Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
committed by
localai-org-maint-bot
parent
bc5dd88de7
commit
9b893da80f
@@ -10,8 +10,11 @@ struct NamedTask {
|
||||
const char *name;
|
||||
};
|
||||
|
||||
// Short names match audiocpp_cli's --task values (docs/usage.md). Two kinds
|
||||
// have no CLI name upstream; they get stable names here so pinning still works.
|
||||
// Short names are exactly the strings audio.cpp prints and parses in
|
||||
// framework/runtime/session.cpp, so a name pinned here survives conversion at
|
||||
// the engine boundary and a name copied out of audio.cpp is accepted here. All
|
||||
// thirteen have an upstream name; only "spk" is absent from the --task table in
|
||||
// docs/usage.md.
|
||||
const NamedTask kTaskNames[] = {
|
||||
{Task::Vad, "vad"},
|
||||
{Task::Asr, "asr"},
|
||||
@@ -24,10 +27,20 @@ const NamedTask kTaskNames[] = {
|
||||
{Task::SpeechToSpeech, "s2s"},
|
||||
{Task::Alignment, "align"},
|
||||
{Task::VoiceDesign, "vdes"},
|
||||
{Task::SpeakerRecognition, "spkrec"},
|
||||
{Task::SpeakerRecognition, "spk"},
|
||||
{Task::Svc, "svc"},
|
||||
};
|
||||
|
||||
// Accepted on input but never emitted. "spkrec" was this backend's own earlier
|
||||
// name for the kind; upstream only ever knew "spk".
|
||||
const NamedTask kTaskAliases[] = {
|
||||
{Task::SpeakerRecognition, "spkrec"},
|
||||
};
|
||||
|
||||
// First match wins, which is safe because a Capabilities value holds at most one
|
||||
// entry per task: it mirrors upstream runtime::TaskCapability (model.h), which
|
||||
// pairs one kind with a modes vector, and no loader's supported_tasks list
|
||||
// repeats a kind.
|
||||
bool family_supports(const Capabilities &caps, Task task, Mode mode) {
|
||||
for (const auto &capability : caps.tasks) {
|
||||
if (capability.task != task) {
|
||||
@@ -85,6 +98,11 @@ std::vector<Task> task_candidates(Rpc rpc, const RequestShape &shape) {
|
||||
case Rpc::SoundGeneration:
|
||||
return {Task::AudioGeneration};
|
||||
case Rpc::AudioTransform:
|
||||
// Svc is listed for completeness but is unreachable by auto-routing, by
|
||||
// design: the only families advertising it (seed_vc, vevo2) also
|
||||
// advertise VoiceConversion, which always wins, and no request signal
|
||||
// means "this input is singing". Singing voice conversion therefore
|
||||
// requires an explicit task:svc pin.
|
||||
return {Task::SourceSeparation, Task::VoiceConversion, Task::Svc,
|
||||
Task::SpeechToSpeech};
|
||||
}
|
||||
@@ -153,6 +171,12 @@ bool parse_task_name(const std::string &value, Task &out) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (const auto &entry : kTaskAliases) {
|
||||
if (value == entry.name) {
|
||||
out = entry.task;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -184,7 +208,7 @@ Route resolve_route(Rpc rpc, const RequestShape &shape,
|
||||
if (!parse_task_name(shape.pinned_task, pinned)) {
|
||||
route.error = "audio-cpp: unknown task option '" + shape.pinned_task +
|
||||
"'. Known tasks: gen, tts, clon, vc, svc, s2s, asr, "
|
||||
"align, vad, diar, sep, vdes, spkrec";
|
||||
"align, vad, diar, sep, vdes, spk";
|
||||
return route;
|
||||
}
|
||||
// A pinned task is honoured exactly. Silently rerouting would make the
|
||||
|
||||
@@ -75,8 +75,9 @@ struct Route {
|
||||
|
||||
Route resolve_route(Rpc rpc, const RequestShape &shape, const Capabilities &caps);
|
||||
|
||||
// Canonical audio.cpp CLI short names: gen, tts, clon, vc, svc, s2s, asr,
|
||||
// align, vad, diar, sep, vdes, spkrec.
|
||||
// Canonical audio.cpp short names: gen, tts, clon, vc, svc, s2s, asr, align,
|
||||
// vad, diar, sep, vdes, spk. parse_task_name additionally accepts "spkrec" as
|
||||
// a legacy alias; task_name only ever emits "spk".
|
||||
const char *task_name(Task task);
|
||||
const char *mode_name(Mode mode);
|
||||
const char *rpc_name(Rpc rpc);
|
||||
|
||||
@@ -119,6 +119,10 @@ static void test_tts_stream_requires_streaming() {
|
||||
"error names the family");
|
||||
check(bad.error.find("tts/offline") != std::string::npos,
|
||||
"error lists what the family does support");
|
||||
check(bad.error.find("TTSStream") != std::string::npos,
|
||||
"error names the RPC that was refused");
|
||||
check(bad.error.find("tts/streaming") != std::string::npos,
|
||||
"error lists the (task, mode) pairs that were tried");
|
||||
}
|
||||
|
||||
static void test_transcription_stream_falls_back_to_offline() {
|
||||
@@ -178,7 +182,16 @@ static void test_prompt_does_not_hijack_asr() {
|
||||
shape.has_prompt_text = true;
|
||||
const auto r = resolve_route(Rpc::AudioTranscription, shape, nemotron());
|
||||
check(r.ok, "ASR with a prompt routes");
|
||||
check(r.task == Task::Asr, "Asr is preferred over Alignment");
|
||||
|
||||
// nemotron advertises Asr alone, so the assertion has to be made against a
|
||||
// family that advertises both: otherwise "Asr is preferred" only restates
|
||||
// that Asr is the only option, and reversing the preference order passes.
|
||||
Capabilities both{"asr_with_aligner",
|
||||
{{Task::Asr, {Mode::Offline}},
|
||||
{Task::Alignment, {Mode::Offline}}}};
|
||||
const auto pref = resolve_route(Rpc::AudioTranscription, shape, both);
|
||||
check(pref.ok, "a family offering both routes");
|
||||
check(pref.task == Task::Asr, "Asr is preferred over Alignment");
|
||||
}
|
||||
|
||||
static void test_audio_transform_prefers_separation() {
|
||||
@@ -186,6 +199,16 @@ static void test_audio_transform_prefers_separation() {
|
||||
resolve_route(Rpc::AudioTransform, RequestShape{}, htdemucs());
|
||||
check(sep.ok && sep.task == Task::SourceSeparation, "separation routes");
|
||||
|
||||
// htdemucs advertises separation alone, so the check above cannot fail on
|
||||
// ordering. This family advertises both, which is what pins the preference.
|
||||
Capabilities sep_and_vc{"sep_and_vc",
|
||||
{{Task::SourceSeparation, {Mode::Offline}},
|
||||
{Task::VoiceConversion, {Mode::Offline}}}};
|
||||
const auto pref =
|
||||
resolve_route(Rpc::AudioTransform, RequestShape{}, sep_and_vc);
|
||||
check(pref.ok && pref.task == Task::SourceSeparation,
|
||||
"separation is preferred over voice conversion");
|
||||
|
||||
Capabilities miocodec{"miocodec",
|
||||
{{Task::VoiceConversion, {Mode::Offline}},
|
||||
{Task::SpeechToSpeech, {Mode::Offline}}}};
|
||||
@@ -250,6 +273,23 @@ static void test_names_round_trip() {
|
||||
}
|
||||
check(std::string(mode_name(Mode::Offline)) == "offline", "offline name");
|
||||
check(std::string(mode_name(Mode::Streaming)) == "streaming", "streaming name");
|
||||
|
||||
// The emitted name must be the one audio.cpp itself prints and parses
|
||||
// (framework/runtime/session.cpp), because `task:` is user-facing: a name
|
||||
// copied out of audio.cpp has to be accepted here, and a name pinned here
|
||||
// has to survive conversion at the engine boundary.
|
||||
check(std::string(task_name(Task::SpeakerRecognition)) == "spk",
|
||||
"speaker recognition emits upstream's name 'spk'");
|
||||
|
||||
Task pinned = Task::Vad;
|
||||
check(parse_task_name("spk", pinned) && pinned == Task::SpeakerRecognition,
|
||||
"'spk' parses to SpeakerRecognition");
|
||||
|
||||
// Accepted as a legacy alias so configs written against the earlier name
|
||||
// keep working, but never emitted.
|
||||
Task alias = Task::Vad;
|
||||
check(parse_task_name("spkrec", alias) && alias == Task::SpeakerRecognition,
|
||||
"'spkrec' is still accepted as an alias");
|
||||
}
|
||||
|
||||
static void test_describe_capabilities() {
|
||||
|
||||
Reference in New Issue
Block a user