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) } }