mirror of
https://github.com/mudler/LocalAI.git
synced 2026-07-08 23:37:43 -04:00
Realtime sessions previously lazy-loaded each pipeline sub-model (VAD,
transcription, LLM, TTS) on first use, so every cold session paid a
per-request model-load stall and load errors only surfaced mid-stream.
Warm the whole pipeline eagerly and blockingly at session start
(including the voice-gate speaker-recognition model, which an enforced
gate blocks each utterance on; compaction's summary_model stays lazy
since it only runs off the response path):
- Add backend.PreloadModel / PreloadModelByName as the single load path
for every modality (no transcription special-case; backend-omitted
configs are deprecated).
- The realtime session blocks on Model.Warmup and returns a
model_load_error to the client if any stage fails to load;
updateSession warms in the background. Opt out per pipeline with
pipeline.disable_warmup, exposed as a UI toggle via the
config-metadata registry.
Add a LocalAI-native POST /backend/load (and /v1/backend/load) that
pre-loads a model -- expanding realtime pipelines into their sub-models
-- as the inverse of /backend/shutdown. There is one preload engine
(backend.PreloadStages): the realtime Warmup methods, /backend/load and
the --load-to-memory startup flag all use it, so --load-to-memory now
also expands pipeline models and records load-failure traces. Pipeline
sub-model alias resolution is likewise shared
(ModelConfigLoader.LoadResolvedModelConfig). Surface the endpoint
everywhere an admin manages models:
- MCP admin tool load_model (httpapi + inproc clients, safety/catalog
prompts, catalog/dispatch tests).
- "Load into memory" action in the React models UI.
- Swagger regenerated; docs moved to the general backend-monitor page
since it is not realtime-specific.
Fix a Traces UI crash ("json: unsupported value: -Inf"): audio-snippet
RMS/peak now floor at a finite dBFS, and backend-trace data is sanitized
to drop non-finite floats before marshaling. The sanitizer is
copy-on-write -- it runs on every RecordBackendTrace, so containers are
only re-allocated on the paths that actually changed.
Migrate core/http/openresponses_test.go onto the prebuilt mock-backend
the rest of the http suite already uses -- it was the last spec still
pointing at a real HuggingFace model, so it 404'd wherever no vision
backend was built -- and fix its item_reference specs to send the
spec's "id" field instead of "item_id", which the handler never
accepted.
Assisted-by: Claude:claude-opus-4-8 Claude Code
Signed-off-by: Richard Palethorpe <io@richiejp.com>
72 lines
2.3 KiB
Go
72 lines
2.3 KiB
Go
package inproc
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
|
|
"github.com/mudler/LocalAI/core/config"
|
|
"github.com/mudler/LocalAI/pkg/model"
|
|
"github.com/mudler/LocalAI/pkg/system"
|
|
)
|
|
|
|
var _ = Describe("inproc.Client LoadModel", func() {
|
|
var (
|
|
ctx context.Context
|
|
tempDir string
|
|
cl *config.ModelConfigLoader
|
|
ml *model.ModelLoader
|
|
c *Client
|
|
seedModel func(name, body string)
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
ctx = context.Background()
|
|
tempDir = GinkgoT().TempDir()
|
|
systemState, err := system.GetSystemState(system.WithModelPath(tempDir))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
appConfig := config.NewApplicationConfig(config.WithSystemState(systemState))
|
|
cl = config.NewModelConfigLoader(tempDir)
|
|
ml = model.NewModelLoader(systemState) // no backends installed
|
|
c = New(appConfig, systemState, cl, ml, nil)
|
|
|
|
seedModel = func(name, body string) {
|
|
Expect(os.WriteFile(filepath.Join(tempDir, name+".yaml"), []byte(body), 0o644)).To(Succeed())
|
|
Expect(cl.LoadModelConfigsFromPath(tempDir)).To(Succeed())
|
|
}
|
|
})
|
|
|
|
It("errors when the model loader is unavailable", func() {
|
|
noLoader := New(c.AppConfig, c.SystemState, cl, nil, nil)
|
|
_, err := noLoader.LoadModel(ctx, "anything")
|
|
Expect(err).To(MatchError(ContainSubstring("model loader not available")))
|
|
})
|
|
|
|
It("loads a regular model through the model loader", func() {
|
|
seedModel("solo", "name: solo\n")
|
|
// No backend is installed in the test env, so the load itself fails — but
|
|
// the call must exercise the single-model path and surface that error
|
|
// rather than panicking or silently succeeding.
|
|
loaded, err := c.LoadModel(ctx, "solo")
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(loaded).To(BeEmpty())
|
|
})
|
|
|
|
It("expands a pipeline model into its sub-models", func() {
|
|
seedModel("voicebot", "name: voicebot\npipeline:\n vad: vad-m\n llm: llm-m\n")
|
|
seedModel("vad-m", "name: vad-m\n")
|
|
seedModel("llm-m", "name: llm-m\n")
|
|
|
|
loaded, err := c.LoadModel(ctx, "voicebot")
|
|
// Sub-models can't load without backends, so the joined error names them
|
|
// — proving the pipeline stub was expanded rather than loaded directly.
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(err.Error()).To(ContainSubstring("vad-m"))
|
|
Expect(err.Error()).ToNot(ContainSubstring("voicebot"))
|
|
Expect(loaded).To(BeEmpty())
|
|
})
|
|
})
|