1e47eaca5a
The embedder moved from paraphrase-multilingual-MiniLM-L12-v2 to multilingual-e5-small. Both are 384-dimensional, so nothing in the code noticed: cosine between an old stored vector and a new query vector is noise, and recall degrades silently. So the DB now records the embedder that wrote its vectors. One value for the whole DB (migration #11, a small `meta` key/value table) rather than a column on every vector row: the backfill re-embeds every note and fact in one pass, so a per-row marker would hold the same string in every row and cost a column on two tables for nothing. The identity comes from the embedder itself via a new optional ID() method ("multilingual-e5-small@384", model file name plus dimension), so pointing the config at another model changes the string without anyone editing a constant. mavend logs a loud WARNING at startup naming both the stored and the configured embedder when they differ. Detection only — recall behaviour is unchanged. TODO(#378) in store.CheckEmbedder marks where the backfill will hook in. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CGeSZxh1DCtRxmFVSYVGvJ
68 lines
1.7 KiB
Go
68 lines
1.7 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
)
|
|
|
|
// A fresh DB has no marker yet, so the current embedder is recorded and
|
|
// nothing is flagged.
|
|
func TestCheckEmbedderFreshDBRecords(t *testing.T) {
|
|
s := newTestStore(t)
|
|
ctx := context.Background()
|
|
|
|
stored, mismatch, err := s.CheckEmbedder(ctx, "multilingual-e5-small@384")
|
|
if err != nil {
|
|
t.Fatalf("CheckEmbedder: %v", err)
|
|
}
|
|
if mismatch {
|
|
t.Fatal("fresh DB reported a mismatch")
|
|
}
|
|
if stored != "multilingual-e5-small@384" {
|
|
t.Fatalf("stored = %q", stored)
|
|
}
|
|
got, err := s.Meta(ctx, metaKeyEmbedderID)
|
|
if err != nil {
|
|
t.Fatalf("Meta: %v", err)
|
|
}
|
|
if got != "multilingual-e5-small@384" {
|
|
t.Fatalf("marker not persisted, got %q", got)
|
|
}
|
|
}
|
|
|
|
// Both models are 384-dim, so this is the only thing that catches the swap.
|
|
func TestCheckEmbedderDifferentModelMismatch(t *testing.T) {
|
|
s := newTestStore(t)
|
|
ctx := context.Background()
|
|
|
|
if err := s.SetMeta(ctx, metaKeyEmbedderID, "paraphrase-multilingual-MiniLM-L12-v2@384"); err != nil {
|
|
t.Fatalf("SetMeta: %v", err)
|
|
}
|
|
stored, mismatch, err := s.CheckEmbedder(ctx, "multilingual-e5-small@384")
|
|
if err != nil {
|
|
t.Fatalf("CheckEmbedder: %v", err)
|
|
}
|
|
if !mismatch {
|
|
t.Fatal("different embedder not detected")
|
|
}
|
|
if stored != "paraphrase-multilingual-MiniLM-L12-v2@384" {
|
|
t.Fatalf("stored = %q", stored)
|
|
}
|
|
}
|
|
|
|
// The same embedder must never raise a false alarm, including on re-check.
|
|
func TestCheckEmbedderSameModelNoAlarm(t *testing.T) {
|
|
s := newTestStore(t)
|
|
ctx := context.Background()
|
|
|
|
for i := 0; i < 2; i++ {
|
|
_, mismatch, err := s.CheckEmbedder(ctx, "multilingual-e5-small@384")
|
|
if err != nil {
|
|
t.Fatalf("CheckEmbedder: %v", err)
|
|
}
|
|
if mismatch {
|
|
t.Fatalf("false alarm on pass %d", i)
|
|
}
|
|
}
|
|
}
|