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}, // 默认半衰期为 30 天:60 天前 = 2 个半衰期 = 2^-2。 {name: "two half-lives halves twice", updatedAt: now.Add(-60 * 24 * time.Hour), decayWeight: 1.0, want: 0.25}, // 较低的基础 decay_weight 会整体压低曲线。 {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}, // 未来时间戳不得把得分抬到基础权重以上。 {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() // 一条新鲜但 decay_weight 近乎零的条目(design:用户可削弱某记忆)。 addItem(t, s, MemoryItem{ PersonaID: "persona.a", Content: "低权重但新鲜", UpdatedAt: now, DecayWeight: 0.1, Scope: ScopePersona, Kind: KindWorldEvent, }) // 稍有历史、但 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) // query 为空时无任何命中,因此排序完全由衰减因子 // (decay_weight × 时间衰减)驱动:低权重的新条目必须 // 排在满权重、稍旧的条目之后。 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) } }