diff --git a/core/http/endpoints/localai/aliases.go b/core/http/endpoints/localai/aliases.go new file mode 100644 index 000000000..923e22c63 --- /dev/null +++ b/core/http/endpoints/localai/aliases.go @@ -0,0 +1,33 @@ +package localai + +import ( + "net/http" + + "github.com/labstack/echo/v4" + "github.com/mudler/LocalAI/core/config" +) + +// AliasInfo is one alias -> target pair. +type AliasInfo struct { + Name string `json:"name"` + Target string `json:"target"` +} + +// ListAliasesEndpoint returns every configured model alias and its target. +// +// @Summary List model aliases +// @Tags models +// @Success 200 {array} AliasInfo +// @Router /api/aliases [get] +func ListAliasesEndpoint(cl *config.ModelConfigLoader) echo.HandlerFunc { + return func(c echo.Context) error { + // Non-nil so an empty result marshals as [] rather than null. + out := []AliasInfo{} + for _, cfg := range cl.GetAllModelsConfigs() { + if cfg.IsAlias() { + out = append(out, AliasInfo{Name: cfg.Name, Target: cfg.Alias}) + } + } + return c.JSON(http.StatusOK, out) + } +} diff --git a/core/http/endpoints/localai/aliases_test.go b/core/http/endpoints/localai/aliases_test.go new file mode 100644 index 000000000..47d3c5328 --- /dev/null +++ b/core/http/endpoints/localai/aliases_test.go @@ -0,0 +1,57 @@ +package localai_test + +import ( + "net/http" + "net/http/httptest" + "os" + "path/filepath" + + "github.com/labstack/echo/v4" + "github.com/mudler/LocalAI/core/config" + . "github.com/mudler/LocalAI/core/http/endpoints/localai" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("ListAliasesEndpoint", func() { + var tempDir string + + BeforeEach(func() { + var err error + tempDir, err = os.MkdirTemp("", "localai-aliases-test") + Expect(err).ToNot(HaveOccurred()) + }) + AfterEach(func() { + os.RemoveAll(tempDir) + }) + + It("returns only alias configs as name/target pairs", func() { + // Seed one real model and one alias pointing at it. + Expect(os.WriteFile( + filepath.Join(tempDir, "real.yaml"), + []byte("name: real\nbackend: llama-cpp\nmodel: foo\n"), + 0644, + )).To(Succeed()) + Expect(os.WriteFile( + filepath.Join(tempDir, "gpt-4.yaml"), + []byte("name: gpt-4\nalias: real\n"), + 0644, + )).To(Succeed()) + + loader := config.NewModelConfigLoader(tempDir) + Expect(loader.LoadModelConfigsFromPath(tempDir)).To(Succeed()) + + app := echo.New() + app.GET("/api/aliases", ListAliasesEndpoint(loader)) + + req := httptest.NewRequest("GET", "/api/aliases", nil) + rec := httptest.NewRecorder() + app.ServeHTTP(rec, req) + + Expect(rec.Code).To(Equal(http.StatusOK)) + Expect(rec.Body.String()).To(ContainSubstring(`"name":"gpt-4"`)) + Expect(rec.Body.String()).To(ContainSubstring(`"target":"real"`)) + // The real model must not appear as an alias entry. + Expect(rec.Body.String()).ToNot(ContainSubstring(`"name":"real"`)) + }) +}) diff --git a/core/http/routes/localai.go b/core/http/routes/localai.go index a66801556..2026296ef 100644 --- a/core/http/routes/localai.go +++ b/core/http/routes/localai.go @@ -80,6 +80,9 @@ func RegisterLocalAIRoutes(router *echo.Echo, // Custom model edit endpoint router.POST("/models/edit/:name", localai.EditModelEndpoint(cl, ml, appConfig), adminMiddleware) + // List model aliases endpoint + router.GET("/api/aliases", localai.ListAliasesEndpoint(cl), adminMiddleware) + // Toggle model enable/disable endpoint router.PUT("/models/toggle-state/:name/:action", localai.ToggleStateModelEndpoint(cl, ml, appConfig), adminMiddleware)