Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/cpp/audio-cpp/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# recipe is a make target (not a prepare.sh) so 'make purge && make' is a clean
# rebuild and so the bump bot can see the pin.

AUDIO_CPP_VERSION?=9d6e7b39236e0151ad28a70fab0d538b84ce8718
AUDIO_CPP_VERSION?=502b5b74bd26e9b4aed267d1776ecf131cae7215
AUDIO_CPP_REPO?=https://github.com/0xShug0/audio.cpp

CURRENT_MAKEFILE_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
Expand Down
41 changes: 27 additions & 14 deletions backend/cpp/audio-cpp/loaded_model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,24 @@ namespace audiocpp_backend {
// --------------------------------------------------------------------------
// Enum coupling
//
// audiocpp_backend::Task mirrors engine::runtime::VoiceTaskKind positionally so
// audiocpp_backend::Task mirrors the VoiceTaskKind values LocalAI can expose so
// capability_routing can stay stdlib-only and testable without an audio.cpp
// checkout. Nothing about that mirroring is enforced by the type system, and a
// drift is silent in the worst possible way: every unit still compiles, every
// test still passes, and the backend runs a different task than the one the
// caller asked for.
// checkout. audio.cpp can also expose engine-only task kinds for which LocalAI
// has no RPC; those are deliberately filtered in to_capabilities below.
// Nothing about the mirrored subset is enforced by the type system, and a drift
// is silent in the worst possible way: every unit still compiles, every test
// still passes, and the backend runs a different task than the one the caller
// asked for.
//
// Two mechanisms pin it, and both are needed because they catch different edits:
//
// 1. The assertions below pin every enumerator's value on both sides. An
// insertion or a reorder anywhere before the last member shifts the values
// after it and fails the build here.
// 2. An enumerator APPENDED after the last one shifts nothing, so no value
// assertion can see it. What sees it is the switch in from_engine_task,
// which covers the engine enum with no `default:` label. CMakeLists.txt
// compiles this file with -Werror=switch so that omission is an error
// 2. An enumerator APPENDED after the mirrored subset shifts nothing, so no
// mirror assertion can see it. The final engine assertion and the switch
// in from_engine_task cover the full upstream enum. CMakeLists.txt compiles
// this file with -Werror=switch so an unclassified addition is an error
// rather than a warning nobody reads.
//
// Neither mechanism catches a pure RENAME of an upstream enumerator, but that
Expand Down Expand Up @@ -67,12 +69,14 @@ static_assert(kEngine(engine::runtime::VoiceTaskKind::SpeechToSpeech) == 8, "Voi
static_assert(kEngine(engine::runtime::VoiceTaskKind::Alignment) == 9, "VoiceTaskKind drifted");
static_assert(kEngine(engine::runtime::VoiceTaskKind::VoiceDesign) == 10, "VoiceTaskKind drifted");
static_assert(kEngine(engine::runtime::VoiceTaskKind::SpeakerRecognition) == 11, "VoiceTaskKind drifted");
// The last member. Pinning it pins the member count too, as long as the
// enumerators stay contiguous and unassigned, which upstream's declaration is.
static_assert(kEngine(engine::runtime::VoiceTaskKind::Svc) == 12,
static_assert(kEngine(engine::runtime::VoiceTaskKind::Svc) == 12, "VoiceTaskKind drifted");
// The last upstream member. Pinning it pins the member count too, as long as
// the enumerators stay contiguous and unassigned, which upstream's declaration
// is. Midi is not mirrored because LocalAI has no MIDI RPC.
static_assert(kEngine(engine::runtime::VoiceTaskKind::Midi) == 13,
"engine::runtime::VoiceTaskKind gained, lost or reordered a member. "
"audiocpp_backend::Task mirrors it positionally: update capability_routing.h, "
"to_engine_task and from_engine_task together, then move this pin.");
"Classify the new task as a LocalAI capability or an engine-only task, "
"then update the conversions and move this pin.");

static_assert(kMirror(Task::Vad) == 0, "Task drifted from VoiceTaskKind");
static_assert(kMirror(Task::Asr) == 1, "Task drifted from VoiceTaskKind");
Expand Down Expand Up @@ -263,6 +267,8 @@ Task from_engine_task(engine::runtime::VoiceTaskKind kind) {
case K::VoiceDesign: return Task::VoiceDesign;
case K::SpeakerRecognition: return Task::SpeakerRecognition;
case K::Svc: return Task::Svc;
case K::Midi:
throw CapabilityError("audio-cpp: LocalAI has no RPC for the MIDI task");
}
return Task::Vad;
}
Expand Down Expand Up @@ -291,6 +297,13 @@ Capabilities to_capabilities(const std::string &family,
caps.family = family;
caps.tasks.reserve(set.supported_tasks.size());
for (const auto &supported : set.supported_tasks) {
// MuScriptor exposes Midi in addition to Asr. LocalAI has no MIDI RPC,
// so advertising it as any mirrored Task would make capability routing
// claim a surface the backend cannot serve. Keep the ASR capability and
// omit only the engine-specific one.
if (supported.task == engine::runtime::VoiceTaskKind::Midi) {
continue;
}
TaskCapability capability;
capability.task = from_engine_task(supported.task);
capability.modes.reserve(supported.modes.size());
Expand Down
Loading