Embed a question and a stored note differently (Vikunja #371)
Note recall is asymmetric: a short question goes in, a longer note comes out. Adds EmbedQuery/EmbedPassage helpers and the e5 prefixes, and points the note/fact write path at the passage side and the query path at the query side. Reviewers: the three call sites in voice.go. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CGeSZxh1DCtRxmFVSYVGvJ
This commit is contained in:
@@ -37,3 +37,60 @@ func TestHashEmbedderCyrillic(t *testing.T) {
|
||||
t.Fatalf("cosine(shared)=%.3f not > cosine(disjoint)=%.3f", cosine(a, b), cosine(a, c))
|
||||
}
|
||||
}
|
||||
|
||||
// recordingEmbedder â an asymmetric embedder that only remembers which side
|
||||
// was asked for. Enough to pin the dispatch; real vectors need the model.
|
||||
type recordingEmbedder struct{ calls []string }
|
||||
|
||||
func (r *recordingEmbedder) Dim() int { return 2 }
|
||||
func (r *recordingEmbedder) Close() error { return nil }
|
||||
|
||||
func (r *recordingEmbedder) Embed(_ context.Context, _ string) ([]float32, error) {
|
||||
r.calls = append(r.calls, "embed")
|
||||
return []float32{1, 0}, nil
|
||||
}
|
||||
|
||||
func (r *recordingEmbedder) EmbedQuery(_ context.Context, _ string) ([]float32, error) {
|
||||
r.calls = append(r.calls, "query")
|
||||
return []float32{1, 0}, nil
|
||||
}
|
||||
|
||||
func (r *recordingEmbedder) EmbedPassage(_ context.Context, _ string) ([]float32, error) {
|
||||
r.calls = append(r.calls, "passage")
|
||||
return []float32{0, 1}, nil
|
||||
}
|
||||
|
||||
// TestEmbedQueryAndPassageSplit â a question and a stored note must not take
|
||||
// the same path. If both ended up on the same call the asymmetric model buys
|
||||
// nothing, which is the whole reason for the swap.
|
||||
func TestEmbedQueryAndPassageSplit(t *testing.T) {
|
||||
rec := &recordingEmbedder{}
|
||||
if _, err := EmbedQuery(context.Background(), rec, "где логи?"); err != nil {
|
||||
t.Fatalf("EmbedQuery: %v", err)
|
||||
}
|
||||
if _, err := EmbedPassage(context.Background(), rec, "логи в /var/log"); err != nil {
|
||||
t.Fatalf("EmbedPassage: %v", err)
|
||||
}
|
||||
if len(rec.calls) != 2 || rec.calls[0] != "query" || rec.calls[1] != "passage" {
|
||||
t.Errorf("calls %v, want [query passage]", rec.calls)
|
||||
}
|
||||
}
|
||||
|
||||
// TestEmbedFallsBackToPlainEmbed â HashEmbedder has no sides, so both helpers
|
||||
// must still work and give the same vector.
|
||||
func TestEmbedFallsBackToPlainEmbed(t *testing.T) {
|
||||
h := NewHashEmbedder(64)
|
||||
q, err := EmbedQuery(context.Background(), h, "text")
|
||||
if err != nil {
|
||||
t.Fatalf("EmbedQuery: %v", err)
|
||||
}
|
||||
p, err := EmbedPassage(context.Background(), h, "text")
|
||||
if err != nil {
|
||||
t.Fatalf("EmbedPassage: %v", err)
|
||||
}
|
||||
for i := range q {
|
||||
if q[i] != p[i] {
|
||||
t.Fatalf("hash embedder gave two different vectors for the same text")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user