From 36d800ebd1aaf2ef15bc7271ff54c56e246ab2a2 Mon Sep 17 00:00:00 2001 From: localai-org-maint-bot Date: Mon, 21 Sep 2026 10:51:17 +0200 Subject: [PATCH] fix(api): report an alias's target in /v1/models/capabilities (#12183) An alias config is a pure redirect with no backend of its own, so the capabilities listing described it from its stub: no capabilities, no modalities, and the default 4096 context_size. Clients that size their context budget from this endpoint (nib, for one) then compacted every turn against a model that really serves 100k. Resolve the alias and report the target's capabilities, modalities and context_size under the alias's id. A dangling or chained alias now reports no enrichment instead of defaults no model runs with. Co-authored-by: Ettore Di Giacinto Co-authored-by: Claude Opus 5 (1M context) --- .../endpoints/openai/list_capabilities.go | 21 +++++++++- .../openai/list_capabilities_test.go | 40 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/core/http/endpoints/openai/list_capabilities.go b/core/http/endpoints/openai/list_capabilities.go index d947c2ade..2ef063fc7 100644 --- a/core/http/endpoints/openai/list_capabilities.go +++ b/core/http/endpoints/openai/list_capabilities.go @@ -35,7 +35,7 @@ func ListModelCapabilitiesEndpoint(bcl *config.ModelConfigLoader, ml *model.Mode dataModels := []schema.ModelCapabilities{} for _, m := range modelNames { entry := schema.ModelCapabilities{ID: m, Object: "model"} - if cfg, ok := bcl.GetModelConfig(m); ok { + if cfg, ok := modelConfigFor(bcl, m); ok { entry.Capabilities = cfg.Capabilities() entry.ThreeDOperations = cfg.ThreeDOperations() entry.InputModalities = cfg.InputModalities() @@ -53,3 +53,22 @@ func ListModelCapabilitiesEndpoint(bcl *config.ModelConfigLoader, ml *model.Mode }) } } + +// modelConfigFor returns the config that describes what a listed model can +// do. An alias is a pure redirect whose own config carries no backend, so its +// capabilities, modalities and context_size would all be empty defaults; +// report the target's instead, since that is the model a request for the +// alias reaches. A dangling or chained alias returns false: the endpoint then +// reports the entry without enrichment rather than advertise defaults no +// model runs with. +func modelConfigFor(bcl *config.ModelConfigLoader, name string) (config.ModelConfig, bool) { + cfg, ok := bcl.GetModelConfig(name) + if !ok { + return config.ModelConfig{}, false + } + resolved, _, err := bcl.ResolveAlias(&cfg) + if err != nil { + return config.ModelConfig{}, false + } + return *resolved, true +} diff --git a/core/http/endpoints/openai/list_capabilities_test.go b/core/http/endpoints/openai/list_capabilities_test.go index d384da1ad..b093697ec 100644 --- a/core/http/endpoints/openai/list_capabilities_test.go +++ b/core/http/endpoints/openai/list_capabilities_test.go @@ -142,4 +142,44 @@ parameters: Expect(entry).NotTo(BeNil()) Expect(entry.ContextSize).To(Equal(backend.DefaultContextSize)) }) + It("reports an alias with its target's capabilities and context_size", func() { + writeConfig("real-llm", ` +name: real-llm +backend: llama-cpp +context_size: 100000 +known_usecases: + - FLAG_CHAT + - FLAG_VISION +template: + chat: "{{ .Input }}" +parameters: + model: model.gguf +`) + writeConfig("friendly", ` +name: friendly +alias: real-llm +`) + resp := call() + target := entryFor(resp, "real-llm") + Expect(target).NotTo(BeNil()) + + entry := entryFor(resp, "friendly") + Expect(entry).NotTo(BeNil()) + Expect(entry.ID).To(Equal("friendly")) + Expect(entry.ContextSize).To(Equal(100000)) + Expect(entry.Capabilities).To(Equal(target.Capabilities)) + Expect(entry.InputModalities).To(Equal(target.InputModalities)) + Expect(entry.OutputModalities).To(Equal(target.OutputModalities)) + }) + + It("reports no context_size for an alias whose target does not exist", func() { + writeConfig("dangling", ` +name: dangling +alias: missing-model +`) + entry := entryFor(call(), "dangling") + Expect(entry).NotTo(BeNil()) + Expect(entry.ContextSize).To(BeZero()) + Expect(entry.Capabilities).To(BeEmpty()) + }) })