beamai_memory_forgetting_test.erl
1 %%%------------------------------------------------------------------- 2 %%% @doc beamai_memory_forgetting 模块单元测试 3 %%% 4 %%% 测试覆盖: 5 %%% - 基于重要性的遗忘策略 6 %%% - LRU 遗忘策略 7 %%% - 时间衰减遗忘策略 8 %%% - FIFO 遗忘策略 9 %%% - 混合遗忘策略 10 %%% - 时间衰减计算 11 %%% - 访问跟踪 12 %%% 13 %%% @end 14 %%%------------------------------------------------------------------- 15 -module(beamai_memory_forgetting_test). 16 17 -include_lib("eunit/include/eunit.hrl"). 18 -include_lib("beamai_memory/include/beamai_episodic_memory.hrl"). 19 20 %%==================================================================== 21 %%% 测试函数 22 %%==================================================================== 23 24 %%-------------------------------------------------------------------- 25 %% @doc 测试基于重要性的遗忘策略 26 %%-------------------------------------------------------------------- 27 importance_forgetting_test() -> 28 Now = erlang:system_time(millisecond), 29 30 %% 创建测试记忆:不同重要性 31 Memories = [ 32 #{id => <<"1">>, importance => 0.3, created_at => Now}, 33 #{id => <<"2">>, importance => 0.9, created_at => Now}, 34 #{id => <<"3">>, importance => 0.6, created_at => Now}, 35 #{id => <<"4">>, importance => 0.2, created_at => Now} 36 ], 37 38 %% 应用遗忘策略,保留前 2 个 39 Filtered = beamai_memory_forgetting:forget(Memories, 2, importance), 40 41 %% 验证:应该保留 importance=0.9 和 0.6 的记忆 42 ?assertEqual(2, length(Filtered)), 43 Ids = [maps:get(id, M) || M <- Filtered], 44 ?assert(lists:member(<<"2">>, Ids)), %% importance=0.9 45 ?assert(lists:member(<<"3">>, Ids)), %% importance=0.6 46 ?assertNot(lists:member(<<"1">>, Ids)), %% importance=0.3 47 ?assertNot(lists:member(<<"4">>, Ids)), %% importance=0.2 48 49 ok. 50 51 %%-------------------------------------------------------------------- 52 %% @doc 测试 LRU 遗忘策略 53 %%-------------------------------------------------------------------- 54 lru_forgetting_test() -> 55 Now = erlang:system_time(millisecond), 56 OneHourAgo = Now - (60 * 60 * 1000), 57 TwoHoursAgo = Now - (2 * 60 * 60 * 1000), 58 59 %% 创建测试记忆:不同访问时间 60 Memories = [ 61 #{id => <<"1">>, last_accessed_at => TwoHoursAgo, created_at => Now}, 62 #{id => <<"2">>, last_accessed_at => Now, created_at => Now}, 63 #{id => <<"3">>, last_accessed_at => OneHourAgo, created_at => Now} 64 ], 65 66 %% 应用 LRU 策略,保留前 2 个 67 Filtered = beamai_memory_forgetting:forget(Memories, 2, lru), 68 69 %% 验证:应该保留最近访问的记忆 70 ?assertEqual(2, length(Filtered)), 71 Ids = [maps:get(id, M) || M <- Filtered], 72 ?assert(lists:member(<<"2">>, Ids)), %% 最近访问 73 ?assert(lists:member(<<"3">>, Ids)), %% 1小时前访问 74 ?assertNot(lists:member(<<"1">>, Ids)), %% 2小时前访问 75 76 ok. 77 78 %%-------------------------------------------------------------------- 79 %% @doc 测试时间衰减遗忘策略 80 %%-------------------------------------------------------------------- 81 time_decay_forgetting_test() -> 82 Now = erlang:system_time(millisecond), 83 OneDayAgo = Now - (24 * 60 * 60 * 1000), 84 TwoDaysAgo = Now - (2 * 24 * 60 * 60 * 1000), 85 86 %% 创建测试记忆:不同创建时间 87 Memories = [ 88 #{id => <<"1">>, created_at => TwoDaysAgo}, 89 #{id => <<"2">>, created_at => Now}, 90 #{id => <<"3">>, created_at => OneDayAgo} 91 ], 92 93 %% 应用时间衰减策略,保留前 2 个 94 Filtered = beamai_memory_forgetting:forget(Memories, 2, time_decay), 95 96 %% 验证:应该保留最新的记忆 97 ?assertEqual(2, length(Filtered)), 98 Ids = [maps:get(id, M) || M <- Filtered], 99 ?assert(lists:member(<<"2">>, Ids)), %% 最新 100 ?assert(lists:member(<<"3">>, Ids)), %% 1天前 101 ?assertNot(lists:member(<<"1">>, Ids)), %% 2天前 102 103 ok. 104 105 %%-------------------------------------------------------------------- 106 %% @doc 测试 FIFO 遗忘策略 107 %%-------------------------------------------------------------------- 108 fifo_forgetting_test() -> 109 Now = erlang:system_time(millisecond), 110 111 %% 创建测试记忆:不同创建时间 112 Memories = [ 113 #{id => <<"1">>, created_at => Now - 3000}, 114 #{id => <<"2">>, created_at => Now - 2000}, 115 #{id => <<"3">>, created_at => Now - 1000}, 116 #{id => <<"4">>, created_at => Now} 117 ], 118 119 %% 应用 FIFO 策略,保留最后 2 个 120 Filtered = beamai_memory_forgetting:forget(Memories, 2, fifo), 121 122 %% 验证:应该保留最后加入的记忆 123 ?assertEqual(2, length(Filtered)), 124 Ids = [maps:get(id, M) || M <- Filtered], 125 ?assert(lists:member(<<"3">>, Ids)), 126 ?assert(lists:member(<<"4">>, Ids)), 127 ?assertNot(lists:member(<<"1">>, Ids)), 128 ?assertNot(lists:member(<<"2">>, Ids)), 129 130 ok. 131 132 %%-------------------------------------------------------------------- 133 %% @doc 测试混合遗忘策略 134 %%-------------------------------------------------------------------- 135 hybrid_forgetting_test() -> 136 Now = erlang:system_time(millisecond), 137 138 %% 创建测试记忆:综合考虑重要性和时间 139 Memories = [ 140 #{id => <<"1">>, importance => 0.9, created_at => Now - (2 * 60 * 60 * 1000), last_accessed_at => Now - (1 * 60 * 60 * 1000)}, 141 #{id => <<"2">>, importance => 0.5, created_at => Now, last_accessed_at => Now}, 142 #{id => <<"3">>, importance => 0.7, created_at => Now, last_accessed_at => Now}, 143 #{id => <<"4">>, importance => 0.2, created_at => Now, last_accessed_at => Now} 144 ], 145 146 %% 应用混合策略,保留前 2 个 147 Filtered = beamai_memory_forgetting:forget(Memories, 2, hybrid), 148 149 %% 验证:应该保留综合评分最高的记忆 150 ?assertEqual(2, length(Filtered)), 151 Ids = [maps:get(id, M) || M <- Filtered], 152 %% M3 (0.7 + 新) 和 M1 (0.9,虽然稍旧但重要性高) 应该被保留 153 %% 计算评分: 154 %% M1: 0.6*0.9 + 0.3*0.5 + 0.1*0.92 = 0.54 + 0.15 + 0.092 = 0.782 155 %% M3: 0.6*0.7 + 0.3*1 + 0.1*1 = 0.42 + 0.3 + 0.1 = 0.82 156 %% M2: 0.6*0.5 + 0.3*1 + 0.1*1 = 0.3 + 0.3 + 0.1 = 0.7 157 ?assert(lists:member(<<"3">>, Ids)), %% 最高综合评分 158 ?assert(lists:member(<<"1">>, Ids)), %% 次高综合评分 159 160 ok. 161 162 %%-------------------------------------------------------------------- 163 %% @doc 测试时间衰减计算 164 %%-------------------------------------------------------------------- 165 decay_importance_test() -> 166 %% 测试 map 类型记忆 167 Memory1 = #{importance => 0.9, content => <<"test">>}, 168 Decayed1 = beamai_memory_forgetting:decay_importance(Memory1, 10), 169 NewImp1 = maps:get(importance, Decayed1), 170 ?assert(NewImp1 < 0.9), %% 应该衰减 171 ?assert(NewImp1 > 0.0), %% 但不会完全消失 172 173 %% 测试 episode 记录 174 Episode = #episode{importance = 0.8}, 175 DecayedEpisode = beamai_memory_forgetting:decay_importance(Episode, 10), 176 ?assert(DecayedEpisode#episode.importance < 0.8), 177 178 %% 测试 event 记录 179 Event = #event{importance = 0.7}, 180 DecayedEvent = beamai_memory_forgetting:decay_importance(Event, 10), 181 ?assert(DecayedEvent#event.importance < 0.7), 182 183 %% 测试批量衰减 184 Memories = [ 185 #{importance => 0.9}, 186 #{importance => 0.5} 187 ], 188 DecayedMemories = beamai_memory_forgetting:batch_decay_importance(Memories, 10), 189 ?assertEqual(2, length(DecayedMemories)), 190 [Imp1, Imp2] = [maps:get(importance, M) || M <- DecayedMemories], 191 ?assert(Imp1 < 0.9), 192 ?assert(Imp2 < 0.5), 193 194 ok. 195 196 %%-------------------------------------------------------------------- 197 %% @doc 测试访问跟踪 198 %%-------------------------------------------------------------------- 199 access_tracking_test() -> 200 MemoryId = <<"test_memory">>, 201 202 %% 测试1:首次访问 203 ok = beamai_memory_forgetting:record_access(MemoryId), 204 Info1 = beamai_memory_forgetting:get_access_info(MemoryId), 205 ?assertMatch(#{access_count := 1}, Info1), 206 ?assert(maps:get(last_accessed_at, Info1) > 0), 207 208 %% 测试2:多次访问 209 timer:sleep(10), %% 确保时间有差异 210 ok = beamai_memory_forgetting:record_access(MemoryId), 211 Info2 = beamai_memory_forgetting:get_access_info(MemoryId), 212 ?assertEqual(2, maps:get(access_count, Info2)), 213 214 %% 测试3:自定义时间戳 215 CustomTime = 1234567890, 216 ok = beamai_memory_forgetting:record_access(MemoryId, CustomTime), 217 Info3 = beamai_memory_forgetting:get_access_info(MemoryId), 218 ?assertEqual(CustomTime, maps:get(last_accessed_at, Info3)), 219 220 %% 测试4:不存在的记忆 221 ?assertEqual(undefined, beamai_memory_forgetting:get_access_info(<<"non_existent">>)), 222 223 ok. 224 225 %%-------------------------------------------------------------------- 226 %% @doc 测试未超过容量限制时不删除 227 %%-------------------------------------------------------------------- 228 no_forget_when_under_limit_test() -> 229 Memories = [ 230 #{id => <<"1">>, importance => 0.3}, 231 #{id => <<"2">>, importance => 0.5} 232 ], 233 234 %% 容量为 5,当前只有 2 个记忆 235 Filtered = beamai_memory_forgetting:forget(Memories, 5, importance), 236 237 %% 应该保留所有记忆 238 ?assertEqual(2, length(Filtered)), 239 ?assertEqual([<<"1">>, <<"2">>], [maps:get(id, M) || M <- Filtered]), 240 241 ok. 242 243 %%-------------------------------------------------------------------- 244 %% @doc 测试批量遗忘 245 %%-------------------------------------------------------------------- 246 batch_forget_test() -> 247 Now = erlang:system_time(millisecond), 248 249 %% 创建多个记忆组 250 Groups = #{ 251 <<"prefs">> => [ 252 #{id => <<"p1">>, importance => 0.3, created_at => Now}, 253 #{id => <<"p2">>, importance => 0.7, created_at => Now}, 254 #{id => <<"p3">>, importance => 0.5, created_at => Now} 255 ], 256 <<"history">> => [ 257 #{id => <<"h1">>, importance => 0.2, created_at => Now}, 258 #{id => <<"h2">>, importance => 0.8, created_at => Now}, 259 #{id => <<"h3">>, importance => 0.4, created_at => Now}, 260 #{id => <<"h4">>, importance => 0.6, created_at => Now} 261 ] 262 }, 263 264 %% 每个组应用不同容量限制 265 MaxCounts = #{<<"prefs">> => 2, <<"history">> => 2}, 266 267 FilteredGroups = beamai_memory_forgetting:batch_forget( 268 Groups, 269 MaxCounts, 270 importance, 271 #{} 272 ), 273 274 %% 验证 prefs 组 275 Prefs = maps:get(<<"prefs">>, FilteredGroups), 276 ?assertEqual(2, length(Prefs)), 277 PrefsIds = [maps:get(id, M) || M <- Prefs], 278 ?assert(lists:member(<<"p2">>, PrefsIds)), %% 0.7 279 ?assert(lists:member(<<"p3">>, PrefsIds)), %% 0.5 280 281 %% 验证 history 组 282 History = maps:get(<<"history">>, FilteredGroups), 283 ?assertEqual(2, length(History)), 284 HistoryIds = [maps:get(id, M) || M <- History], 285 ?assert(lists:member(<<"h2">>, HistoryIds)), %% 0.8 286 ?assert(lists:member(<<"h4">>, HistoryIds)), %% 0.6 287 288 ok. 289 290 %%-------------------------------------------------------------------- 291 %% @doc 测试 episode 和 event 记录的遗忘 292 %%-------------------------------------------------------------------- 293 record_forgetting_test() -> 294 Now = erlang:system_time(millisecond), 295 296 %% 创建 episode 记录 297 Episodes = [ 298 #episode{id = <<"ep1">>, importance = 0.3, created_at = Now}, 299 #episode{id = <<"ep2">>, importance = 0.8, created_at = Now}, 300 #episode{id = <<"ep3">>, importance = 0.5, created_at = Now} 301 ], 302 303 FilteredEpisodes = beamai_memory_forgetting:forget(Episodes, 2, importance), 304 ?assertEqual(2, length(FilteredEpisodes)), 305 ?assertEqual([<<"ep2">>, <<"ep3">>], [E#episode.id || E <- FilteredEpisodes]), 306 307 %% 创建 event 记录 308 Events = [ 309 #event{id = <<"ev1">>, importance = 0.2, timestamp = Now}, 310 #event{id = <<"ev2">>, importance = 0.9, timestamp = Now}, 311 #event{id = <<"ev3">>, importance = 0.6, timestamp = Now} 312 ], 313 314 FilteredEvents = beamai_memory_forgetting:forget(Events, 2, importance), 315 ?assertEqual(2, length(FilteredEvents)), 316 ?assertEqual([<<"ev2">>, <<"ev3">>], [E#event.id || E <- FilteredEvents]), 317 318 ok. 319 320 %%-------------------------------------------------------------------- 321 %% @doc 测试边界条件 322 %%-------------------------------------------------------------------- 323 edge_cases_test() -> 324 %% 测试空列表 325 ?assertEqual([], beamai_memory_forgetting:forget([], 5, importance)), 326 327 %% 测试单个记忆 328 Single = [#{id => <<"1">>, importance => 0.5}], 329 ?assertEqual(1, length(beamai_memory_forgetting:forget(Single, 5, importance))), 330 331 %% 测试 MaxCount = 0 332 Multiple = [ 333 #{id => <<"1">>, importance => 0.3}, 334 #{id => <<"2">>, importance => 0.7} 335 ], 336 ?assertEqual(0, length(beamai_memory_forgetting:forget(Multiple, 0, importance))), 337 338 %% 测试 MaxCount = 1 339 Filtered = beamai_memory_forgetting:forget(Multiple, 1, importance), 340 ?assertEqual(1, length(Filtered)), 341 ?assertEqual(<<"2">>, maps:get(id, hd(Filtered))), 342 343 ok.