feat(config): add model alias field and self-validation

Add ModelConfig.Alias (yaml: alias), IsAlias(), and an alias
short-circuit at the top of Validate() that rejects self-reference and
forbids setting backend/parameters.model on a pure-redirect alias.

Assisted-by: Claude:claude-opus-4-8 [Claude Code]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
Ettore Di Giacinto
2026-06-20 09:30:44 +00:00
parent 518381278e
commit 411d01a704
2 changed files with 55 additions and 0 deletions

View File

@@ -37,6 +37,12 @@ type ModelConfig struct {
schema.PredictionOptions `yaml:"parameters,omitempty" json:"parameters,omitempty"`
Name string `yaml:"name,omitempty" json:"name,omitempty"`
// Alias, when set, makes this config a pure redirect: every request for
// Name is served by the model named here. All other fields are ignored.
// The target must be an existing, non-alias model (enforced at load and
// at create/swap time). See docs/content for Model Aliases.
Alias string `yaml:"alias,omitempty" json:"alias,omitempty"`
F16 *bool `yaml:"f16,omitempty" json:"f16,omitempty"`
Threads *int `yaml:"threads,omitempty" json:"threads,omitempty"`
Debug *bool `yaml:"debug,omitempty" json:"debug,omitempty"`
@@ -391,6 +397,10 @@ func (c *ModelConfig) HasRouter() bool {
return len(c.Router.Candidates) > 0
}
// IsAlias reports whether this config is a pure redirect to another model.
// Value receiver so it is callable on non-addressable config values too.
func (c ModelConfig) IsAlias() bool { return c.Alias != "" }
// @Description PII filtering configuration. PII redaction is per-model so
// that local models don't pay the latency or behaviour change of regex
// scanning, while cloud-bound traffic (cloud-proxy backend) can default to
@@ -1243,6 +1253,22 @@ func (cfg *ModelConfig) SetDefaults(opts ...ConfigLoaderOption) {
}
func (c *ModelConfig) Validate() (bool, error) {
// An alias is a pure redirect: validate only its own shape here. Target
// existence and the no-chain rule need the full config set, so the loader
// (load-time) and the create/swap endpoints enforce those.
if c.IsAlias() {
if c.Name == "" {
return false, fmt.Errorf("alias config requires a name")
}
if c.Alias == c.Name {
return false, fmt.Errorf("alias %q cannot point to itself", c.Name)
}
if c.Backend != "" || c.Model != "" {
return false, fmt.Errorf("alias config %q must not set backend or parameters.model: an alias is a pure redirect", c.Name)
}
return true, nil
}
downloadedFileNames := []string{}
for _, f := range c.DownloadFiles {
downloadedFileNames = append(downloadedFileNames, f.Filename)

View File

@@ -787,3 +787,32 @@ var _ = Describe("pattern detector config", func() {
Expect(err).To(MatchError(ContainSubstring("pattern \"EMAILish\"")))
})
})
var _ = Describe("ModelConfig alias", func() {
It("reports IsAlias when alias is set", func() {
c := ModelConfig{Name: "gpt-4", Alias: "my-llama-3"}
Expect(c.IsAlias()).To(BeTrue())
Expect(ModelConfig{Name: "real"}.IsAlias()).To(BeFalse())
})
It("validates a minimal alias config", func() {
c := ModelConfig{Name: "gpt-4", Alias: "my-llama-3"}
ok, err := c.Validate()
Expect(err).ToNot(HaveOccurred())
Expect(ok).To(BeTrue())
})
It("rejects an alias pointing to itself", func() {
c := ModelConfig{Name: "loop", Alias: "loop"}
ok, err := c.Validate()
Expect(ok).To(BeFalse())
Expect(err).To(MatchError(ContainSubstring("itself")))
})
It("rejects an alias that also sets a backend", func() {
c := ModelConfig{Name: "gpt-4", Alias: "my-llama-3", Backend: "llama-cpp"}
ok, err := c.Validate()
Expect(ok).To(BeFalse())
Expect(err).To(MatchError(ContainSubstring("pure redirect")))
})
})