From 65d0d62acf151cf7cfa77b8f38870c0aecb4be1e Mon Sep 17 00:00:00 2001 From: "Kai (Tam Nhu) Tran" <61256810+kaitranntt@users.noreply.github.com> Date: Mon, 30 Mar 2026 10:36:09 -0400 Subject: [PATCH] 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 --- cmd/gateway_agents.go | 9 +- cmd/gateway_agents_test.go | 60 ++++++++ internal/bootstrap/seed_store_test.go | 7 +- .../methods/agents_create_owner_test.go | 7 +- .../http/provider_embedding_validation.go | 27 ++++ .../provider_embedding_validation_test.go | 48 ++++++ internal/http/providers.go | 10 +- internal/http/providers_test.go | 137 ++++++++++++++++++ internal/store/provider_store.go | 4 + .../tools/context_file_interceptor_test.go | 7 +- ui/web/src/i18n/locales/en/providers.json | 3 +- ui/web/src/i18n/locales/vi/providers.json | 3 +- ui/web/src/i18n/locales/zh/providers.json | 3 +- .../provider-detail/provider-overview.tsx | 34 +---- 14 files changed, 312 insertions(+), 47 deletions(-) create mode 100644 cmd/gateway_agents_test.go create mode 100644 internal/http/provider_embedding_validation.go create mode 100644 internal/http/provider_embedding_validation_test.go diff --git a/cmd/gateway_agents.go b/cmd/gateway_agents.go index 51032a7c..7ec00a86 100644 --- a/cmd/gateway_agents.go +++ b/cmd/gateway_agents.go @@ -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) diff --git a/cmd/gateway_agents_test.go b/cmd/gateway_agents_test.go new file mode 100644 index 00000000..1d84313f --- /dev/null +++ b/cmd/gateway_agents_test.go @@ -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) + } +} diff --git a/internal/bootstrap/seed_store_test.go b/internal/bootstrap/seed_store_test.go index b9cd817f..be179c9d 100644 --- a/internal/bootstrap/seed_store_test.go +++ b/internal/bootstrap/seed_store_test.go @@ -57,6 +57,9 @@ func (s *seedStubStore) ListUserContextFilesByName(_ context.Context, _ uuid.UUI func (s *seedStubStore) DeleteUserContextFile(_ context.Context, _ uuid.UUID, _, _ string) error { return nil } +func (s *seedStubStore) MigrateUserDataOnMerge(_ context.Context, _ []string, _ string) error { + return nil +} // Remaining interface methods — not exercised. func (s *seedStubStore) Create(_ context.Context, _ *store.AgentData) error { return nil } @@ -111,10 +114,6 @@ func (s *seedStubStore) EnsureUserProfile(_ context.Context, _ uuid.UUID, _ stri func (s *seedStubStore) PropagateContextFile(_ context.Context, _ uuid.UUID, _ string) (int, error) { return 0, nil } -func (s *seedStubStore) MigrateUserDataOnMerge(_ context.Context, _ []string, _ string) error { - return nil -} - // ---- Tests ---- // TestSeedUserFiles_PredefinedAgent_UsesAgentLevelUserMD is the primary regression test. diff --git a/internal/gateway/methods/agents_create_owner_test.go b/internal/gateway/methods/agents_create_owner_test.go index c72aaa93..aa9b313f 100644 --- a/internal/gateway/methods/agents_create_owner_test.go +++ b/internal/gateway/methods/agents_create_owner_test.go @@ -83,6 +83,9 @@ func (s *createCaptureStore) ListUserContextFilesByName(_ context.Context, _ uui func (s *createCaptureStore) DeleteUserContextFile(_ context.Context, _ uuid.UUID, _, _ string) error { return nil } +func (s *createCaptureStore) MigrateUserDataOnMerge(_ context.Context, _ []string, _ string) error { + return nil +} func (s *createCaptureStore) GetUserOverride(_ context.Context, _ uuid.UUID, _ string) (*store.UserAgentOverrideData, error) { return nil, nil } @@ -104,10 +107,6 @@ func (s *createCaptureStore) EnsureUserProfile(_ context.Context, _ uuid.UUID, _ func (s *createCaptureStore) PropagateContextFile(_ context.Context, _ uuid.UUID, _ string) (int, error) { return 0, nil } -func (s *createCaptureStore) MigrateUserDataOnMerge(_ context.Context, _ []string, _ string) error { - return nil -} - // ---- helpers ---- // minimalConfig returns a config sufficient for handleCreate (provider + model defaults only). diff --git a/internal/http/provider_embedding_validation.go b/internal/http/provider_embedding_validation.go new file mode 100644 index 00000000..d5fb7fa2 --- /dev/null +++ b/internal/http/provider_embedding_validation.go @@ -0,0 +1,27 @@ +package http + +import ( + "fmt" + + "github.com/nextlevelbuilder/goclaw/internal/store" +) + +// Provider-level embedding settings are used by the memory system, whose +// PostgreSQL schema currently stores fixed vector(1536) embeddings. +func validateProviderEmbeddingSettings(p *store.LLMProviderData) error { + es := store.ParseEmbeddingSettings(p.Settings) + if es == nil || !es.Enabled { + return nil + } + if es.Dimensions < 0 { + return fmt.Errorf("embedding.dimensions must be a positive integer or omitted") + } + if es.Dimensions > 0 && es.Dimensions != store.RequiredMemoryEmbeddingDimensions { + return fmt.Errorf( + "embedding.dimensions must be %d or omitted because GoClaw memory stores vector(%d)", + store.RequiredMemoryEmbeddingDimensions, + store.RequiredMemoryEmbeddingDimensions, + ) + } + return nil +} diff --git a/internal/http/provider_embedding_validation_test.go b/internal/http/provider_embedding_validation_test.go new file mode 100644 index 00000000..488f5bcd --- /dev/null +++ b/internal/http/provider_embedding_validation_test.go @@ -0,0 +1,48 @@ +package http + +import ( + "encoding/json" + "testing" + + "github.com/nextlevelbuilder/goclaw/internal/store" +) + +func embeddingProvider(dims int) *store.LLMProviderData { + settings := map[string]any{ + "embedding": map[string]any{ + "enabled": true, + "model": "test-model", + "dimensions": dims, + }, + } + raw, _ := json.Marshal(settings) + return &store.LLMProviderData{Settings: raw} +} + +func TestValidateProviderEmbeddingSettings(t *testing.T) { + tests := []struct { + name string + p *store.LLMProviderData + wantErr bool + }{ + {"nil settings", &store.LLMProviderData{}, false}, + {"embedding disabled", &store.LLMProviderData{ + Settings: json.RawMessage(`{"embedding":{"enabled":false}}`), + }, false}, + {"dimensions omitted (0)", &store.LLMProviderData{ + Settings: json.RawMessage(`{"embedding":{"enabled":true,"model":"m"}}`), + }, false}, + {"dimensions 1536", embeddingProvider(1536), false}, + {"dimensions 2048", embeddingProvider(2048), true}, + {"dimensions 1024", embeddingProvider(1024), true}, + {"dimensions negative", embeddingProvider(-1), true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateProviderEmbeddingSettings(tt.p) + if (err != nil) != tt.wantErr { + t.Fatalf("validateProviderEmbeddingSettings() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/http/providers.go b/internal/http/providers.go index b7fe187a..6c3ad58b 100644 --- a/internal/http/providers.go +++ b/internal/http/providers.go @@ -33,7 +33,7 @@ type ProvidersHandler struct { cliMu sync.Mutex // serializes Claude CLI provider create to prevent duplicates msgBus *bus.MessageBus sysConfigStore store.SystemConfigStore - tracingStore store.TracingStore // optional: for provider-scoped pool activity + tracingStore store.TracingStore // optional: for provider-scoped pool activity agents store.AgentCRUDStore // optional: for provider pool activity agent lookup } @@ -311,6 +311,10 @@ func (h *ProvidersHandler) handleCreateProvider(w http.ResponseWriter, r *http.R writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()}) return } + if err := validateProviderEmbeddingSettings(&p); err != nil { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidRequest, err.Error())}) + return + } if err := validateProviderURL(p.APIBase); err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()}) @@ -448,6 +452,10 @@ func (h *ProvidersHandler) handleUpdateProvider(w http.ResponseWriter, r *http.R writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()}) return } + if err := validateProviderEmbeddingSettings(&candidate); err != nil { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidRequest, err.Error())}) + return + } // Track old name before update for registry cleanup var oldName string diff --git a/internal/http/providers_test.go b/internal/http/providers_test.go index b949e95e..66736d64 100644 --- a/internal/http/providers_test.go +++ b/internal/http/providers_test.go @@ -1,11 +1,17 @@ package http import ( + "bytes" + "context" "encoding/json" + "net/http" + "net/http/httptest" + "strings" "testing" "github.com/google/uuid" + "github.com/nextlevelbuilder/goclaw/internal/crypto" "github.com/nextlevelbuilder/goclaw/internal/providers" "github.com/nextlevelbuilder/goclaw/internal/store" ) @@ -50,3 +56,134 @@ func TestProvidersHandlerRegisterInMemoryAppliesCodexPoolDefaults(t *testing.T) t.Fatalf("ExtraProviderNames = %#v, want [\"codex-work\"]", defaults.ExtraProviderNames) } } + +func setupProvidersAdminToken(t *testing.T) string { + t.Helper() + token := "system-admin-key" + setupTestCache(t, map[string]*store.APIKeyData{ + crypto.HashAPIKey(token): { + ID: uuid.New(), + Scopes: []string{"operator.admin"}, + }, + }) + return token +} + +func TestProvidersHandlerCreateRejectsIncompatibleEmbeddingDimensions(t *testing.T) { + token := setupProvidersAdminToken(t) + providerStore := newMockProviderStore() + handler := NewProvidersHandler(providerStore, newMockSecretsStore(), nil, "") + mux := http.NewServeMux() + handler.RegisterRoutes(mux) + + body := `{ + "name": "voyage", + "provider_type": "openai_compat", + "api_base": "https://api.voyageai.com/v1", + "enabled": true, + "settings": { + "embedding": { + "enabled": true, + "model": "voyage-4-nano", + "dimensions": 2048 + } + } + }` + + req := httptest.NewRequest(http.MethodPost, "/v1/providers", bytes.NewBufferString(body)) + req.Header.Set("Authorization", "Bearer "+token) + w := httptest.NewRecorder() + mux.ServeHTTP(w, req) + + if w.Code != http.StatusBadRequest { + t.Fatalf("status code = %d, want %d", w.Code, http.StatusBadRequest) + } + if !strings.Contains(w.Body.String(), "1536") { + t.Fatalf("response body = %q, want mention of 1536", w.Body.String()) + } + if len(providerStore.providers) != 0 { + t.Fatalf("provider store mutated on invalid create: %#v", providerStore.providers) + } +} + +func TestProvidersHandlerCreateAllows1536EmbeddingDimensions(t *testing.T) { + token := setupProvidersAdminToken(t) + providerStore := newMockProviderStore() + handler := NewProvidersHandler(providerStore, newMockSecretsStore(), nil, "") + mux := http.NewServeMux() + handler.RegisterRoutes(mux) + + body := `{ + "name": "gemini-emb", + "provider_type": "gemini_native", + "api_base": "https://generativelanguage.googleapis.com/v1beta/openai", + "api_key": "token", + "enabled": true, + "settings": { + "embedding": { + "enabled": true, + "model": "gemini-embedding-001", + "dimensions": 1536 + } + } + }` + + req := httptest.NewRequest(http.MethodPost, "/v1/providers", bytes.NewBufferString(body)) + req.Header.Set("Authorization", "Bearer "+token) + w := httptest.NewRecorder() + mux.ServeHTTP(w, req) + + if w.Code != http.StatusCreated { + t.Fatalf("status code = %d, want %d, body=%s", w.Code, http.StatusCreated, w.Body.String()) + } + if len(providerStore.providers) != 1 { + t.Fatalf("provider count = %d, want 1", len(providerStore.providers)) + } +} + +func TestProvidersHandlerUpdateRejectsIncompatibleEmbeddingDimensions(t *testing.T) { + token := setupProvidersAdminToken(t) + providerStore := newMockProviderStore() + provider := &store.LLMProviderData{ + BaseModel: store.BaseModel{ID: uuid.New()}, + Name: "voyage", + ProviderType: store.ProviderOpenAICompat, + APIBase: "https://api.voyageai.com/v1", + Enabled: true, + Settings: json.RawMessage(`{"embedding":{"enabled":true,"model":"voyage-4-nano","dimensions":1536}}`), + } + if err := providerStore.CreateProvider(context.Background(), provider); err != nil { + t.Fatalf("CreateProvider() error = %v", err) + } + + handler := NewProvidersHandler(providerStore, newMockSecretsStore(), nil, "") + mux := http.NewServeMux() + handler.RegisterRoutes(mux) + + body := `{ + "settings": { + "embedding": { + "enabled": true, + "model": "voyage-4-nano", + "dimensions": 2048 + } + } + }` + + req := httptest.NewRequest(http.MethodPut, "/v1/providers/"+provider.ID.String(), bytes.NewBufferString(body)) + req.Header.Set("Authorization", "Bearer "+token) + w := httptest.NewRecorder() + mux.ServeHTTP(w, req) + + if w.Code != http.StatusBadRequest { + t.Fatalf("status code = %d, want %d", w.Code, http.StatusBadRequest) + } + current, err := providerStore.GetProvider(context.Background(), provider.ID) + if err != nil { + t.Fatalf("GetProvider() error = %v", err) + } + es := store.ParseEmbeddingSettings(current.Settings) + if es == nil || es.Dimensions != 1536 { + t.Fatalf("embedding dimensions = %+v, want 1536 preserved", es) + } +} diff --git a/internal/store/provider_store.go b/internal/store/provider_store.go index d4d7a6c9..ee5cd8a0 100644 --- a/internal/store/provider_store.go +++ b/internal/store/provider_store.go @@ -78,6 +78,10 @@ type LLMProviderData struct { Settings json.RawMessage `json:"settings,omitempty"` } +// RequiredMemoryEmbeddingDimensions is the fixed vector size used by the pgvector memory schema. +// All memory embeddings must match this dimensionality until the schema supports variable sizes. +const RequiredMemoryEmbeddingDimensions = 1536 + // EmbeddingSettings holds embedding-specific configuration stored in provider settings JSONB. type EmbeddingSettings struct { Enabled bool `json:"enabled"` diff --git a/internal/tools/context_file_interceptor_test.go b/internal/tools/context_file_interceptor_test.go index 0c1bb65a..bf2bb5a8 100644 --- a/internal/tools/context_file_interceptor_test.go +++ b/internal/tools/context_file_interceptor_test.go @@ -43,6 +43,9 @@ func (s *stubAgentStore) DeleteUserContextFile(_ context.Context, _ uuid.UUID, _ func (s *stubAgentStore) ListUserContextFilesByName(_ context.Context, _ uuid.UUID, _ string) ([]store.UserContextFileData, error) { return nil, nil } +func (s *stubAgentStore) MigrateUserDataOnMerge(_ context.Context, _ []string, _ string) error { + return nil +} // Remaining interface methods — not exercised in these tests. func (s *stubAgentStore) Create(_ context.Context, _ *store.AgentData) error { return nil } @@ -93,10 +96,6 @@ func (s *stubAgentStore) EnsureUserProfile(_ context.Context, _ uuid.UUID, _ str func (s *stubAgentStore) PropagateContextFile(_ context.Context, _ uuid.UUID, _ string) (int, error) { return 0, nil } -func (s *stubAgentStore) MigrateUserDataOnMerge(_ context.Context, _ []string, _ string) error { - return nil -} - // ---- Tests ---- // TestInterceptor_CacheHit verifies that a second read does NOT call GetAgentContextFiles again. diff --git a/ui/web/src/i18n/locales/en/providers.json b/ui/web/src/i18n/locales/en/providers.json index d0a41766..680e20f5 100644 --- a/ui/web/src/i18n/locales/en/providers.json +++ b/ui/web/src/i18n/locales/en/providers.json @@ -237,7 +237,8 @@ "enableDesc": "Allow this provider to generate text embeddings for memory, skills, and knowledge graph", "model": "Embedding Model", "dimensions": "Output Dimensions", - "dimensionsHint": "Truncate output to N dimensions (e.g. 1536). Required for models with native dims > 1536 like gemini-embedding-001 (3072d).", + "dimensionsHint": "GoClaw memory currently stores embeddings in vector(1536). Leave this empty when the model already outputs 1536, or set it to exactly 1536 when the provider supports truncation from larger native dimensions.", + "dimensionsInvalid": "Only 1536 is currently supported by GoClaw memory.", "apiBase": "Embedding API Base", "apiBasePlaceholder": "(same as provider)", "apiBaseHint": "Override if embedding endpoint differs from the provider's main API base", diff --git a/ui/web/src/i18n/locales/vi/providers.json b/ui/web/src/i18n/locales/vi/providers.json index b12dfeb7..2716d2aa 100644 --- a/ui/web/src/i18n/locales/vi/providers.json +++ b/ui/web/src/i18n/locales/vi/providers.json @@ -269,7 +269,8 @@ "enableDesc": "Cho phép provider này tạo embedding cho bộ nhớ, kỹ năng và đồ thị tri thức", "model": "Model Embedding", "dimensions": "Số chiều đầu ra", - "dimensionsHint": "Cắt ngắn đầu ra về N chiều (VD: 1536). Bắt buộc với model có chiều gốc > 1536 như gemini-embedding-001 (3072d).", + "dimensionsHint": "Bộ nhớ GoClaw hiện lưu embedding với vector(1536). Để trống nếu model đã xuất 1536 chiều, hoặc đặt đúng 1536 nếu provider hỗ trợ cắt từ chiều gốc lớn hơn.", + "dimensionsInvalid": "Hiện tại bộ nhớ GoClaw chỉ hỗ trợ đúng 1536 chiều.", "apiBase": "API Base Embedding", "apiBasePlaceholder": "(giống provider)", "apiBaseHint": "Ghi đè nếu endpoint embedding khác với API base chính của provider", diff --git a/ui/web/src/i18n/locales/zh/providers.json b/ui/web/src/i18n/locales/zh/providers.json index 7a78f417..c0a219cc 100644 --- a/ui/web/src/i18n/locales/zh/providers.json +++ b/ui/web/src/i18n/locales/zh/providers.json @@ -287,7 +287,8 @@ "enableDesc": "允许此Provider为记忆、技能和知识图谱生成文本嵌入", "model": "Embedding模型", "dimensions": "输出维度", - "dimensionsHint": "将输出截断为N维(如1536)。对于原生维度 > 1536的模型(如gemini-embedding-001 3072d)必须设置。", + "dimensionsHint": "GoClaw memory 当前以 vector(1536) 存储 embedding。若模型本身输出 1536 维则留空;若 provider 支持从更高原生维度截断,则只能设置为 1536。", + "dimensionsInvalid": "GoClaw memory 当前只支持精确的 1536 维。", "apiBase": "Embedding API地址", "apiBasePlaceholder": "(与Provider相同)", "apiBaseHint": "如果Embedding端点与Provider的主API地址不同,可在此覆盖", diff --git a/ui/web/src/pages/providers/provider-detail/provider-overview.tsx b/ui/web/src/pages/providers/provider-detail/provider-overview.tsx index 36e371bc..616faabe 100644 --- a/ui/web/src/pages/providers/provider-detail/provider-overview.tsx +++ b/ui/web/src/pages/providers/provider-detail/provider-overview.tsx @@ -96,7 +96,6 @@ function providerFormSignature(input: { embEnabled: boolean; embModel: string; embApiBase: string; - embDimensions: string; routing: ChatGPTOAuthRoutingConfig; isOAuth: boolean; }): string { @@ -107,7 +106,6 @@ function providerFormSignature(input: { embEnabled: input.embEnabled, embModel: input.embModel, embApiBase: input.embApiBase, - embDimensions: input.embDimensions, routing: input.isOAuth ? routingSignature(input.routing) : "", }); } @@ -178,7 +176,6 @@ export function ProviderOverview({ provider, onUpdate }: ProviderOverviewProps) const [embEnabled, setEmbEnabled] = useState(initEmb?.enabled ?? false); const [embModel, setEmbModel] = useState(initEmb?.model ?? ""); const [embApiBase, setEmbApiBase] = useState(initEmb?.api_base ?? ""); - const [embDimensions, setEmbDimensions] = useState(initEmb?.dimensions ? String(initEmb.dimensions) : ""); const syncedProviderIDRef = useRef(provider.id); const savedFormSignature = useMemo( @@ -192,14 +189,13 @@ export function ProviderOverview({ provider, onUpdate }: ProviderOverviewProps) embEnabled: initEmb?.enabled ?? false, embModel: initEmb?.model ?? "", embApiBase: initEmb?.api_base ?? "", - embDimensions: initEmb?.dimensions ? String(initEmb.dimensions) : "", routing: { strategy: initialRouting?.strategy ?? "primary_first", extra_provider_names: initialRouting?.extraProviderNames ?? [], }, isOAuth, }), - [initEmb?.api_base, initEmb?.dimensions, initEmb?.enabled, initEmb?.model, initialRouting?.extraProviderNames, initialRouting?.strategy, isOAuth, provider.api_key, provider.display_name, provider.enabled, showApiKey], + [initEmb?.api_base, initEmb?.enabled, initEmb?.model, initialRouting?.extraProviderNames, initialRouting?.strategy, isOAuth, provider.api_key, provider.display_name, provider.enabled, showApiKey], ); const savedFormSignatureRef = useRef(savedFormSignature); const draftFormSignature = useMemo( @@ -213,11 +209,10 @@ export function ProviderOverview({ provider, onUpdate }: ProviderOverviewProps) embEnabled, embModel, embApiBase, - embDimensions, routing: poolRouting, isOAuth, }), - [apiKey, displayName, embApiBase, embDimensions, embEnabled, embModel, enabled, isOAuth, poolRouting, provider.api_key, showApiKey], + [apiKey, displayName, embApiBase, embEnabled, embModel, enabled, isOAuth, poolRouting, provider.api_key, showApiKey], ); useEffect(() => { @@ -228,7 +223,6 @@ export function ProviderOverview({ provider, onUpdate }: ProviderOverviewProps) setEmbEnabled(es?.enabled ?? false); setEmbModel(es?.model ?? ""); setEmbApiBase(es?.api_base ?? ""); - setEmbDimensions(es?.dimensions ? String(es.dimensions) : ""); setPoolRouting({ strategy: routing?.strategy ?? "primary_first", extra_provider_names: routing?.extraProviderNames ?? [], @@ -322,7 +316,7 @@ export function ProviderOverview({ provider, onUpdate }: ProviderOverviewProps) } = useProviderCodexPoolActivity(provider.id, 8, isPoolOwner); const { verifyEmbedding, embVerifying, embResult, resetEmb } = useProviderVerify(); - useEffect(() => { resetEmb(); }, [embModel, embDimensions, resetEmb]); + useEffect(() => { resetEmb(); }, [embModel, resetEmb]); const [saving, setSaving] = useState(false); @@ -332,8 +326,6 @@ export function ProviderOverview({ provider, onUpdate }: ProviderOverviewProps) const nextDisplayName = displayName.trim(); const nextEmbModel = embModel.trim(); const nextEmbAPIBase = embApiBase.trim(); - const parsedDims = embDimensions ? parseInt(embDimensions, 10) : 0; - const nextEmbDimensions = parsedDims > 0 ? String(parsedDims) : ""; const submittedAPIKey = showApiKey && apiKey && apiKey !== "***" ? apiKey : ""; const data: ProviderInput = { name: provider.name, @@ -354,7 +346,6 @@ export function ProviderOverview({ provider, onUpdate }: ProviderOverviewProps) enabled: true, model: nextEmbModel || undefined, api_base: nextEmbAPIBase || undefined, - dimensions: parsedDims > 0 ? parsedDims : undefined, } : { enabled: false }, }; @@ -371,7 +362,6 @@ export function ProviderOverview({ provider, onUpdate }: ProviderOverviewProps) setDisplayName(nextDisplayName); setEmbModel(nextEmbModel); setEmbApiBase(nextEmbAPIBase); - setEmbDimensions(nextEmbDimensions); if (submittedAPIKey) { setApiKey("***"); } @@ -388,8 +378,7 @@ export function ProviderOverview({ provider, onUpdate }: ProviderOverviewProps) }; const handleVerifyEmbedding = () => { - const parsedDims = embDimensions ? parseInt(embDimensions, 10) : 0; - verifyEmbedding(provider.id, embModel.trim() || undefined, parsedDims > 0 ? parsedDims : undefined); + verifyEmbedding(provider.id, embModel.trim() || undefined, undefined); }; const displayNameDirty = displayName !== (provider.display_name || ""); @@ -400,8 +389,7 @@ export function ProviderOverview({ provider, onUpdate }: ProviderOverviewProps) const embeddingDirty = embEnabled !== (initEmb?.enabled ?? false) || embModel !== (initEmb?.model ?? "") - || embApiBase !== (initEmb?.api_base ?? "") - || embDimensions !== (initEmb?.dimensions ? String(initEmb.dimensions) : ""); + || embApiBase !== (initEmb?.api_base ?? ""); const routingDirty = isOAuth && routingSignature(poolRouting) !== routingSignature({ strategy: initialRouting?.strategy ?? "primary_first", extra_provider_names: initialRouting?.extraProviderNames ?? [], @@ -534,16 +522,8 @@ export function ProviderOverview({ provider, onUpdate }: ProviderOverviewProps)
1536
{t("embedding.dimensionsHint")}