Gate recall on the margin over the runner-up, not just the score
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
This commit is contained in:
@@ -277,6 +277,14 @@ type VoiceConfig struct {
|
||||
// default if unset.
|
||||
QueryMinScore float64 `json:"query_min_score,omitempty"`
|
||||
|
||||
// QueryMinMargin — the second half of the recall gate: the top hit must
|
||||
// beat the runner-up by more than this. The absolute score above cannot do
|
||||
// the job on its own, because the e5 embedder puts every cosine in one
|
||||
// narrow high band, so a made-up question scores as high as a real one.
|
||||
// The margin asks whether one note is clearly the best instead.
|
||||
// Negative ⇒ off. 0 ⇒ the default below.
|
||||
QueryMinMargin float64 `json:"query_min_margin,omitempty"`
|
||||
|
||||
// Persona — optional prompt prefix that tunes maven's character. Prepended
|
||||
// to every LLM system prompt (nudge phrasing, note queries, general
|
||||
// knowledge). Empty string ⇒ current hardcoded persona (feminine-gendered
|
||||
@@ -404,7 +412,12 @@ const (
|
||||
DefaultAutotuneInterval = 10 * time.Minute
|
||||
DefaultRouterThreshold = 0.55
|
||||
DefaultQueryMinScore = 0.55
|
||||
DefaultToolTimeout = 30 * time.Second
|
||||
// Read off the margin sweep in internal/memory/recalleval on the e5
|
||||
// embedder: 0.008 answers 68% of real questions (down from 72%) and cuts
|
||||
// false recall from 5/5 to 1/5. Every larger delta costs real recall
|
||||
// without removing that last one until 0.020, which drops recall to 44%.
|
||||
DefaultQueryMinMargin = 0.008
|
||||
DefaultToolTimeout = 30 * time.Second
|
||||
|
||||
DefaultFactEnrichmentInterval = 30 * time.Second
|
||||
)
|
||||
@@ -486,6 +499,14 @@ func (c *Config) applyDefaults() {
|
||||
if c.Voice.QueryMinScore <= 0 {
|
||||
c.Voice.QueryMinScore = DefaultQueryMinScore
|
||||
}
|
||||
// Unset ⇒ default. Negative is how you turn the margin off on purpose,
|
||||
// so it is clamped to 0 rather than replaced by the default.
|
||||
switch {
|
||||
case c.Voice.QueryMinMargin == 0:
|
||||
c.Voice.QueryMinMargin = DefaultQueryMinMargin
|
||||
case c.Voice.QueryMinMargin < 0:
|
||||
c.Voice.QueryMinMargin = 0
|
||||
}
|
||||
if c.Voice.ToolTimeout <= 0 {
|
||||
c.Voice.ToolTimeout = Duration(DefaultToolTimeout)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user