Files
Cyrene_v2/internal/runtime/memory/decay_test.go
T

80 lines
2.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package memory
import (
"context"
"math"
"testing"
"time"
)
func TestRetriever_DecayFactor_久未使用降权(t *testing.T) {
t.Parallel()
r := NewRetriever(newTestStore(t))
now := time.Now().UTC()
tests := []struct {
name string
updatedAt time.Time
decayWeight float64
want float64
}{
{name: "fresh keeps full weight", updatedAt: now, decayWeight: 1.0, want: 1.0},
// Default half-life is 30 days: 60 days ago = 2 half-lives = 2^-2.
{name: "two half-lives halves twice", updatedAt: now.Add(-60 * 24 * time.Hour), decayWeight: 1.0, want: 0.25},
// Lower base decay_weight scales the whole curve down.
{name: "low decay_weight scales down", updatedAt: now, decayWeight: 0.5, want: 0.5},
{name: "low weight plus age compounds", updatedAt: now.Add(-60 * 24 * time.Hour), decayWeight: 0.5, want: 0.125},
// A future timestamp must not boost above the base weight.
{name: "future timestamp clamps to zero", updatedAt: now.Add(time.Hour), decayWeight: 1.0, want: 1.0},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := r.decayFactor(MemoryItem{
DecayWeight: tt.decayWeight,
UpdatedAt: tt.updatedAt,
}, now)
if math.Abs(got-tt.want) > 1e-9 {
t.Fatalf("decayFactor = %v, want %v", got, tt.want)
}
})
}
}
func TestRetriever_Retrieve_DecayWeight_降权(t *testing.T) {
t.Parallel()
ctx := context.Background()
s := newTestStore(t)
now := time.Now().UTC()
// A fresh item but with a near-zero decay_weight (design: 用户可削弱某记忆).
addItem(t, s, MemoryItem{
PersonaID: "persona.a", Content: "低权重但新鲜", UpdatedAt: now,
DecayWeight: 0.1, Scope: ScopePersona, Kind: KindWorldEvent,
})
// A slightly older item with a full decay_weight.
addItem(t, s, MemoryItem{
PersonaID: "persona.a", Content: "权重高稍旧", UpdatedAt: now.Add(-time.Hour),
DecayWeight: 1.0, Scope: ScopePersona, Kind: KindWorldEvent,
})
r := NewRetriever(s)
// A blank query matches nothing, so ordering is driven by the decay factor
// (decay_weight × time decay) alone: the low-weight fresh item must fall
// below the full-weight slightly-older item.
results, err := r.Retrieve(ctx, "persona.a", "", 10)
if err != nil {
t.Fatalf("Retrieve: %v", err)
}
if len(results) != 2 {
t.Fatalf("got %d results, want 2", len(results))
}
if got := results[0].Item.Content; got != "权重高稍旧" {
t.Fatalf("top result = %q, want the full-decay-weight item", got)
}
if got := results[1].Item.Content; got != "低权重但新鲜" {
t.Fatalf("second result = %q, want the low-decay-weight item", got)
}
}