package memory import ( "context" "math" "testing" "time" ) // 向量编解码往返:float32 打包进 bytea 再取回不失真。 func TestVecRoundTrip(t *testing.T) { v := []float32{0.1, -0.5, 1.0, 0, 0.333} got := decodeVec(encodeVec(v)) if len(got) != len(v) { t.Fatalf("长度不符: %d vs %d", len(got), len(v)) } for i := range v { if got[i] != v[i] { t.Errorf("第%d个失真: %v vs %v", i, got[i], v[i]) } } // 脏数据(长度非 4 倍数)返回 nil,不 panic。 if decodeVec([]byte{1, 2, 3}) != nil { t.Error("非法字节应返回 nil") } if decodeVec(nil) != nil { t.Error("空返回 nil") } } // cosine01:同向=1,正交=0.5→截到实际(正交余弦0→0),反向截到 0。 func TestCosine01(t *testing.T) { a := []float32{1, 0, 0} if c := cosine01(a, a); math.Abs(c-1) > 1e-6 { t.Errorf("同向应为 1,得 %v", c) } if c := cosine01(a, []float32{0, 1, 0}); c != 0 { t.Errorf("正交余弦 0,得 %v", c) } if c := cosine01(a, []float32{-1, 0, 0}); c != 0 { t.Errorf("反向应截到 0(不奖励相反语义),得 %v", c) } if c := cosine01(a, []float32{0, 0, 0}); c != 0 { t.Errorf("零向量应为 0,得 %v", c) } } // 三项打分模式(rel 非 nil):相关性高的排到前面,即使重要度/最近性略低。 func TestRankProfiles_RelevanceMode(t *testing.T) { now := time.Now() rows := []Profile{ {BaseModel: BaseModel{ID: "a"}, Key: "无关但重要", Importance: 10, LastSeenAt: now}, {BaseModel: BaseModel{ID: "b"}, Key: "高度相关", Importance: 3, LastSeenAt: now}, } rel := map[string]float64{"a": 0.05, "b": 0.95} // b 语义强相关 // 两项模式(rel=nil):a(重要度10)应在前。 if got := rankProfiles(rows, now, 0, nil); got[0].ID != "a" { t.Errorf("两项模式重要度高者应在前,得 %s", got[0].ID) } // 三项模式:b 相关性 0.95 主导,应翻到前面。 if got := rankProfiles(rows, now, 0, rel); got[0].ID != "b" { t.Errorf("三项模式相关性高者应在前,得 %s", got[0].ID) } } // relevance 优雅降级:无 embedder / query 空 → 返回 nil(不报错、不影响召回)。 func TestRelevance_GracefulFallback(t *testing.T) { s := &Store{} // 无 embedder if s.relevance(context.Background(), []Profile{{Key: "k"}}, "问题") != nil { t.Error("无 embedder 应返回 nil") } s.emb = stubEmbedder{} if s.relevance(context.Background(), []Profile{{Key: "k"}}, " ") != nil { t.Error("query 空白应返回 nil(不白算嵌入)") } } // stubEmbedder 返回固定维度向量,供降级测试(不打真网络)。 type stubEmbedder struct{} func (stubEmbedder) Embed(_ context.Context, _ []string) ([][]float32, error) { return [][]float32{{1, 0}}, nil }