fix(memory): guard provider embedding dimensions to 1536 schema (#563)

- Reject incompatible explicit provider embedding dimensions on create/update
- Ignore previously saved incompatible dimensions at runtime, fall back to 1536
- Share RequiredMemoryEmbeddingDimensions constant from store package
- Remove dimensions input from provider UI (always 1536 per pgvector schema)
- Add HTTP, runtime, and validation unit tests

Closes #548
Supersedes #410
This commit is contained in:
Kai (Tam Nhu) Tran authored and GitHub committed 2026-03-30 21:36:09 +07:00
1 parent 53932cf532
commit 65d0d62acf
14 files changed
+312 -47

No files matched your search

+5 -4
View File
@@ -123,11 +123,12 @@ func buildEmbeddingProvider(
apiBase = memCfg.EmbeddingAPIBase
}
// Dimension truncation: provider settings override, default to 1536 to match pgvector schema.
// Dimension truncation: default to RequiredMemoryEmbeddingDimensions to match pgvector schema.
// Models that natively output 1536 ignore the parameter; models with larger native dims get truncated.
dims := 1536
if es != nil && es.Dimensions > 0 {
dims = es.Dimensions
dims := store.RequiredMemoryEmbeddingDimensions
if es != nil && es.Dimensions > 0 && es.Dimensions != store.RequiredMemoryEmbeddingDimensions {
slog.Warn("ignoring incompatible provider embedding dimensions for memory schema",
"provider", dbp.Name, "requested", es.Dimensions, "required", store.RequiredMemoryEmbeddingDimensions)
}
// Try registry first for the actual API key / base (handles runtime-registered providers)
+60
View File
@@ -0,0 +1,60 @@
package cmd
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/nextlevelbuilder/goclaw/internal/store"
)
func captureEmbeddingRequest(t *testing.T, es *store.EmbeddingSettings) map[string]any {
t.Helper()
var requestBody map[string]any
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := json.NewDecoder(r.Body).Decode(&requestBody); err != nil {
t.Fatalf("Decode() error = %v", err)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"data":[{"embedding":[0.1,0.2]}]}`))
}))
defer server.Close()
provider := &store.LLMProviderData{
Name: "embedding-provider",
ProviderType: store.ProviderOpenAICompat,
APIKey: "test-key",
APIBase: server.URL,
Enabled: true,
}
ep := buildEmbeddingProvider(provider, es, nil, nil)
if ep == nil {
t.Fatal("buildEmbeddingProvider() = nil, want provider")
}
if _, err := ep.Embed(context.Background(), []string{"hello"}); err != nil {
t.Fatalf("Embed() error = %v", err)
}
return requestBody
}
func TestBuildEmbeddingProviderDefaultsTo1536Dimensions(t *testing.T) {
requestBody := captureEmbeddingRequest(t, nil)
if got := requestBody["dimensions"]; got != float64(1536) {
t.Fatalf("dimensions = %v, want 1536", got)
}
}
func TestBuildEmbeddingProviderIgnoresIncompatibleStoredDimensions(t *testing.T) {
requestBody := captureEmbeddingRequest(t, &store.EmbeddingSettings{
Enabled: true,
Model: "voyage-4-nano",
Dimensions: 2048,
})
if got := requestBody["dimensions"]; got != float64(1536) {
t.Fatalf("dimensions = %v, want fallback 1536", got)
}
}