mirror of
https://github.com/mudler/LocalAI.git
synced 2026-06-20 14:49:09 -04:00
feat(config): resolve and validate model alias targets in the loader
Assisted-by: Claude:opus-4-8 [Claude Code] Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
@@ -294,6 +294,44 @@ func (bcl *ModelConfigLoader) UpdateModelConfig(m string, updater func(*ModelCon
|
||||
}
|
||||
}
|
||||
|
||||
// ResolveAlias follows a one-hop alias to its target config. Returns
|
||||
// (resolved, wasAlias, err). Non-alias configs return (cfg, false, nil)
|
||||
// unchanged. Strict: the target must exist and must not itself be an alias
|
||||
// (chains are rejected). The returned config is a copy of the target.
|
||||
func (bcl *ModelConfigLoader) ResolveAlias(cfg *ModelConfig) (*ModelConfig, bool, error) {
|
||||
if cfg == nil || !cfg.IsAlias() {
|
||||
return cfg, false, nil
|
||||
}
|
||||
target, exists := bcl.GetModelConfig(cfg.Alias)
|
||||
if !exists {
|
||||
return nil, true, fmt.Errorf("alias %q points to unknown model %q", cfg.Name, cfg.Alias)
|
||||
}
|
||||
if target.IsAlias() {
|
||||
return nil, true, fmt.Errorf("alias %q points to another alias %q (chains are not allowed)", cfg.Name, cfg.Alias)
|
||||
}
|
||||
return &target, true, nil
|
||||
}
|
||||
|
||||
// ValidateAliasTarget checks an alias config's target at create/swap time:
|
||||
// the target must exist, must not be an alias, and must not be disabled.
|
||||
// Returns nil for non-alias configs.
|
||||
func (bcl *ModelConfigLoader) ValidateAliasTarget(cfg *ModelConfig) error {
|
||||
if cfg == nil || !cfg.IsAlias() {
|
||||
return nil
|
||||
}
|
||||
target, exists := bcl.GetModelConfig(cfg.Alias)
|
||||
if !exists {
|
||||
return fmt.Errorf("alias target %q does not exist", cfg.Alias)
|
||||
}
|
||||
if target.IsAlias() {
|
||||
return fmt.Errorf("alias target %q is itself an alias (chains are not allowed)", cfg.Alias)
|
||||
}
|
||||
if target.IsDisabled() {
|
||||
return fmt.Errorf("alias target %q is disabled", cfg.Alias)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Preload prepare models if they are not local but url or huggingface repositories
|
||||
func (bcl *ModelConfigLoader) Preload(modelPath string) error {
|
||||
bcl.Lock()
|
||||
@@ -475,5 +513,21 @@ func (bcl *ModelConfigLoader) LoadModelConfigsFromPath(path string, opts ...Conf
|
||||
}
|
||||
}
|
||||
|
||||
// Surface aliases whose targets are missing or themselves aliases. These
|
||||
// resolve to a clear request-time error; warning here gives operators
|
||||
// visibility without failing startup.
|
||||
for name, c := range bcl.configs {
|
||||
if !c.IsAlias() {
|
||||
continue
|
||||
}
|
||||
target, ok := bcl.configs[c.Alias]
|
||||
switch {
|
||||
case !ok:
|
||||
xlog.Warn("alias points to unknown model", "alias", name, "target", c.Alias)
|
||||
case target.IsAlias():
|
||||
xlog.Warn("alias points to another alias (chains are not allowed)", "alias", name, "target", c.Alias)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -61,3 +61,51 @@ var _ = Describe("ModelConfigLoader.GetModelsConflictingWith", func() {
|
||||
Expect(bcl.GetModelsConflictingWith("a")).To(ConsistOf("b"))
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("ModelConfigLoader alias resolution", func() {
|
||||
var loader *ModelConfigLoader
|
||||
|
||||
BeforeEach(func() {
|
||||
loader = NewModelConfigLoader("")
|
||||
loader.configs["real"] = ModelConfig{Name: "real", Backend: "llama-cpp"}
|
||||
loader.configs["gpt-4"] = ModelConfig{Name: "gpt-4", Alias: "real"}
|
||||
loader.configs["chain"] = ModelConfig{Name: "chain", Alias: "gpt-4"}
|
||||
loader.configs["dangling"] = ModelConfig{Name: "dangling", Alias: "nope"}
|
||||
})
|
||||
|
||||
It("returns non-alias configs unchanged", func() {
|
||||
cfg := loader.configs["real"]
|
||||
got, was, err := loader.ResolveAlias(&cfg)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(was).To(BeFalse())
|
||||
Expect(got.Name).To(Equal("real"))
|
||||
})
|
||||
|
||||
It("resolves an alias to its target", func() {
|
||||
cfg := loader.configs["gpt-4"]
|
||||
got, was, err := loader.ResolveAlias(&cfg)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(was).To(BeTrue())
|
||||
Expect(got.Name).To(Equal("real"))
|
||||
})
|
||||
|
||||
It("rejects an alias chain", func() {
|
||||
cfg := loader.configs["chain"]
|
||||
_, was, err := loader.ResolveAlias(&cfg)
|
||||
Expect(was).To(BeTrue())
|
||||
Expect(err).To(MatchError(ContainSubstring("chains are not allowed")))
|
||||
})
|
||||
|
||||
It("rejects a dangling alias", func() {
|
||||
cfg := loader.configs["dangling"]
|
||||
_, _, err := loader.ResolveAlias(&cfg)
|
||||
Expect(err).To(MatchError(ContainSubstring("unknown model")))
|
||||
})
|
||||
|
||||
It("ValidateAliasTarget passes for a real target and fails for a chain", func() {
|
||||
good := loader.configs["gpt-4"]
|
||||
Expect(loader.ValidateAliasTarget(&good)).ToNot(HaveOccurred())
|
||||
bad := loader.configs["chain"]
|
||||
Expect(loader.ValidateAliasTarget(&bad)).To(MatchError(ContainSubstring("itself an alias")))
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user