From ad3c8c483293e7602a017431e404eb24358c5f10 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Wed, 15 Apr 2026 20:05:28 +0200 Subject: [PATCH] fix(agents): handle embedding model dim changes on collection upload (#9365) Bumps LocalAGI to pick up the LocalRecall postgres backend fix that resizes the pgvector column when the configured embedding model returns vectors of a different dimensionality than the existing collection. Switching the agent pool's embedding model now triggers a transparent re-embed at startup instead of failing every subsequent upload with 'expected N dimensions, not M' (SQLSTATE 22000). Also surfaces a 409 with an actionable message in UploadToCollectionEndpoint as a safety net for the rare cases the upstream migration path doesn't cover (e.g. a model swapped at runtime), instead of the previous opaque 500. --- .../endpoints/localai/agent_collections.go | 21 +++++++++++++++++++ go.mod | 8 +++---- go.sum | 12 +++++------ 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/core/http/endpoints/localai/agent_collections.go b/core/http/endpoints/localai/agent_collections.go index dd4bd2370..17997bcfe 100644 --- a/core/http/endpoints/localai/agent_collections.go +++ b/core/http/endpoints/localai/agent_collections.go @@ -85,12 +85,33 @@ func UploadToCollectionEndpoint(app *application.Application) echo.HandlerFunc { if strings.Contains(err.Error(), "not found") { return c.JSON(http.StatusNotFound, map[string]string{"error": err.Error()}) } + if isEmbeddingDimensionMismatch(err) { + return c.JSON(http.StatusConflict, map[string]string{ + "error": "embedding model dimensionality changed and the collection could not be migrated automatically; restart LocalAI to trigger re-embedding, or reset the collection", + }) + } return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) } return c.JSON(http.StatusOK, map[string]string{"status": "ok", "filename": file.Filename, "key": key}) } } +// isEmbeddingDimensionMismatch detects the pgvector-side error that bubbles up +// from LocalRecall when the configured embedding model returns vectors of a +// different dimensionality than the collection's vector column. LocalRecall +// migrates the column on startup; this guard only fires for edge cases the +// migration path doesn't cover (e.g. a model swapped at runtime), so we +// surface a 409 with an actionable message instead of an opaque 500. +func isEmbeddingDimensionMismatch(err error) bool { + if err == nil { + return false + } + msg := err.Error() + return strings.Contains(msg, "SQLSTATE 22000") && + strings.Contains(msg, "expected ") && + strings.Contains(msg, " dimensions, not ") +} + func ListCollectionEntriesEndpoint(app *application.Application) echo.HandlerFunc { return func(c echo.Context) error { svc := app.AgentPoolService() diff --git a/go.mod b/go.mod index 628615106..fa2dabe3c 100644 --- a/go.mod +++ b/go.mod @@ -153,7 +153,7 @@ require ( github.com/go-telegram/bot v1.17.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gocolly/colly v1.2.0 // indirect - github.com/gofiber/fiber/v2 v2.52.9 // indirect + github.com/gofiber/fiber/v2 v2.52.11 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b // indirect github.com/google/go-github/v69 v69.2.0 // indirect @@ -168,8 +168,8 @@ require ( github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/mschoch/smat v0.2.0 // indirect - github.com/mudler/LocalAGI v0.0.0-20260325172845-736ccbb95ec8 - github.com/mudler/localrecall v0.5.9-0.20260321005011-810084e9369b // indirect + github.com/mudler/LocalAGI v0.0.0-20260415165142-3369136c7380 + github.com/mudler/localrecall v0.5.9-0.20260415164846-8ad831f840fc // indirect github.com/mudler/skillserver v0.0.6 github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/oxffaa/gopher-parse-sitemap v0.0.0-20191021113419-005d2eb1def4 // indirect @@ -318,7 +318,7 @@ require ( github.com/docker/cli v29.4.0+incompatible // indirect github.com/docker/docker v28.5.2+incompatible github.com/docker/docker-credential-helpers v0.9.3 // indirect - github.com/docker/go-connections v0.6.0 + github.com/docker/go-connections v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/flynn/noise v1.1.0 // indirect diff --git a/go.sum b/go.sum index b89302a70..138100daa 100644 --- a/go.sum +++ b/go.sum @@ -378,8 +378,8 @@ github.com/gocolly/colly v1.2.0/go.mod h1:Hof5T3ZswNVsOHYmba1u03W65HDWgpV5HifSuu github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw= -github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= +github.com/gofiber/fiber/v2 v2.52.11 h1:5f4yzKLcBcF8ha1GQTWB+mpblWz3Vz6nSAbTL31HkWs= +github.com/gofiber/fiber/v2 v2.52.11/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -705,8 +705,8 @@ github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= -github.com/mudler/LocalAGI v0.0.0-20260325172845-736ccbb95ec8 h1:wfF/rwxGvoqUbCzbOUUfx0ME7DUMmZa2hM2kjXVUxhI= -github.com/mudler/LocalAGI v0.0.0-20260325172845-736ccbb95ec8/go.mod h1:6uCPaPPXuA0zS/RaUk39qk57WgRuwYWrEXALqJF+a4U= +github.com/mudler/LocalAGI v0.0.0-20260415165142-3369136c7380 h1:gSS535c1MO3IRSUIWJT1xzZjT4lZBsqtHpptXvrEsmw= +github.com/mudler/LocalAGI v0.0.0-20260415165142-3369136c7380/go.mod h1:rD7G70wl+5zlpvNF13iZBpAuat8LsiJFn678z3Kxleo= github.com/mudler/cogito v0.9.5-0.20260315222927-63abdec7189b h1:A74T2Lauvg61KodYqsjTYDY05kPLcW+efVZjd23dghU= github.com/mudler/cogito v0.9.5-0.20260315222927-63abdec7189b/go.mod h1:6sfja3lcu2nWRzEc0wwqGNu/eCG3EWgij+8s7xyUeQ4= github.com/mudler/edgevpn v0.31.1 h1:7qegiDWd0kAg6ljhNHxqvp8hbo/6BbzSdbb7/2WZfiY= @@ -715,8 +715,8 @@ github.com/mudler/go-piper v0.0.0-20241023091659-2494246fd9fc h1:RxwneJl1VgvikiX github.com/mudler/go-piper v0.0.0-20241023091659-2494246fd9fc/go.mod h1:O7SwdSWMilAWhBZMK9N9Y/oBDyMMzshE3ju8Xkexwig= github.com/mudler/go-processmanager v0.1.0 h1:fcSKgF9U/a1Z7KofAFeZnke5YseadCI5GqL9oT0LS3E= github.com/mudler/go-processmanager v0.1.0/go.mod h1:h6kmHUZeafr+k5hRYpGLMzJFH4hItHffgpRo2QIkP+o= -github.com/mudler/localrecall v0.5.9-0.20260321005011-810084e9369b h1:XeAnOEOOSKMfS5XNGpRTltQgjKCinho0V4uAhrgxN7Q= -github.com/mudler/localrecall v0.5.9-0.20260321005011-810084e9369b/go.mod h1:xuPtgL9zUyiQLmspYzO3kaboYrGbWmwi8BQPt1aCAcs= +github.com/mudler/localrecall v0.5.9-0.20260415164846-8ad831f840fc h1:p1ucQ2rbU4mhG2Xl1Emg5Q6QCYCjI+fvMF9KTek/+sY= +github.com/mudler/localrecall v0.5.9-0.20260415164846-8ad831f840fc/go.mod h1:xuPtgL9zUyiQLmspYzO3kaboYrGbWmwi8BQPt1aCAcs= github.com/mudler/memory v0.0.0-20260406210934-424c1ecf2cf8 h1:Ry8RiWy8fZ6Ff4E7dPmjRsBrnHOnPeOOj2LhCgyjQu0= github.com/mudler/memory v0.0.0-20260406210934-424c1ecf2cf8/go.mod h1:EA8Ashhd56o32qN7ouPKFSRUs/Z+LrRCF4v6R2Oarm8= github.com/mudler/skillserver v0.0.6 h1:ixz6wUekLdTmbnpAavCkTydDF6UdXAG3ncYufSPK9G0=