11831c6ace
The e5 embedder puts every cosine in one narrow band (0.79-0.89), so the absolute query_min_score gate cannot tell a real hit from a made-up question: any value under the band answers everything, any value above it answers nothing. False recall was 5/5. New gate asks whether one note is clearly the best instead: top1 - top2 > delta. New query_min_margin config knob, default 0.008, read off the sweep in the recall harness. The absolute floor stays as a second check. On the recall fixture with e5: answered 72% -> 68%, false recall 5/5 -> 1/5. Vikunja #359 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CGeSZxh1DCtRxmFVSYVGvJ
59 lines
1.7 KiB
Go
59 lines
1.7 KiB
Go
package main
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/kami/maven/internal/memory"
|
|
)
|
|
|
|
func TestBestRecall(t *testing.T) {
|
|
const min = 0.55
|
|
const margin = 0.008
|
|
|
|
t.Run("empty results", func(t *testing.T) {
|
|
if _, ok := bestRecall(nil, min, margin); ok {
|
|
t.Error("empty results returned ok")
|
|
}
|
|
})
|
|
|
|
t.Run("top below threshold", func(t *testing.T) {
|
|
res := []memory.Result{{Score: 0.4, Meta: map[string]string{"text": "выпил воды"}}}
|
|
if _, ok := bestRecall(res, min, margin); ok {
|
|
t.Error("below-threshold hit returned ok")
|
|
}
|
|
})
|
|
|
|
t.Run("hit without text meta", func(t *testing.T) {
|
|
res := []memory.Result{{Score: 0.9, Meta: map[string]string{"type": "fact"}}}
|
|
if _, ok := bestRecall(res, min, margin); ok {
|
|
t.Error("textless hit returned ok")
|
|
}
|
|
})
|
|
|
|
t.Run("clearing hit returns its text", func(t *testing.T) {
|
|
res := []memory.Result{
|
|
{Score: 0.82, Meta: map[string]string{"text": "выпил воды в три часа", "type": "fact"}},
|
|
{Score: 0.60, Meta: map[string]string{"text": "другое"}},
|
|
}
|
|
got, ok := bestRecall(res, min, margin)
|
|
if !ok {
|
|
t.Fatal("clearing hit not returned")
|
|
}
|
|
if got != "выпил воды в три часа" {
|
|
t.Errorf("wrong text: %q", got)
|
|
}
|
|
})
|
|
|
|
// The runner-up is almost as close, so the embedder cannot tell the two
|
|
// notes apart. Silence beats reading back a coin flip.
|
|
t.Run("runner-up too close", func(t *testing.T) {
|
|
res := []memory.Result{
|
|
{Score: 0.860, Meta: map[string]string{"text": "выпил воды в три часа"}},
|
|
{Score: 0.858, Meta: map[string]string{"text": "другое"}},
|
|
}
|
|
if _, ok := bestRecall(res, min, margin); ok {
|
|
t.Error("thin-margin hit returned ok")
|
|
}
|
|
})
|
|
}
|