From 6f643b525f029b64c81e8edda93235669b96b274 Mon Sep 17 00:00:00 2001 From: slhaf Date: Sat, 12 Apr 2025 15:26:13 +0800 Subject: [PATCH] =?UTF-8?q?feat(memory):=20=E5=A2=9E=E5=8A=A0=E8=AE=B0?= =?UTF-8?q?=E5=BF=86=E7=BC=93=E5=AD=98=E5=8A=9F=E8=83=BD=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=95=B0=E6=8D=AE=E7=BB=93=E6=9E=84-=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20memorySliceCache=20=E5=92=8C=20memoryNodeCacheCount?= =?UTF-8?q?er=20=E7=94=A8=E4=BA=8E=E7=BC=93=E5=AD=98=E8=AE=B0=E5=BF=86?= =?UTF-8?q?=E5=88=87=E7=89=87=20-=20=E4=BC=98=E5=8C=96=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E7=BB=93=E6=9E=84=EF=BC=8C=E4=BD=BF=E7=94=A8=20ConcurrentHashM?= =?UTF-8?q?ap=20=E5=92=8C=20CopyOnWriteArrayList=20=E6=9B=BF=E4=BB=A3=20Ha?= =?UTF-8?q?shMap=20=E5=92=8C=20ArrayList=20-=20=E4=B8=BA=20MemoryNode=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=94=AF=E4=B8=80=E6=A0=87=E8=AF=86=20memory?= =?UTF-8?q?NodeId=EF=BC=8C=E5=8F=AF=E4=BD=9C=E4=B8=BA=E8=AE=B0=E5=BF=86?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E6=96=87=E4=BB=B6=E5=90=8D=20-=20=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=20selectMemoryByPath=20=E6=96=B9=E6=B3=95=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=BC=93=E5=AD=98=E9=80=BB=E8=BE=91=20-=20?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=20updateDialogMap=20=E6=96=B9=E6=B3=95?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=E7=94=A8=E6=88=B7=E5=AF=B9=E8=AF=9D?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E6=9B=B4=E6=96=B0=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/work/slhaf/memory/MemoryGraph.java | 94 ++++++++++++++++--- .../work/slhaf/memory/node/MemoryNode.java | 12 ++- .../work/slhaf/memory/node/TopicNode.java | 6 +- 3 files changed, 92 insertions(+), 20 deletions(-) diff --git a/src/main/java/work/slhaf/memory/MemoryGraph.java b/src/main/java/work/slhaf/memory/MemoryGraph.java index f26409ee..8b26a7f0 100644 --- a/src/main/java/work/slhaf/memory/MemoryGraph.java +++ b/src/main/java/work/slhaf/memory/MemoryGraph.java @@ -16,6 +16,8 @@ import java.nio.file.Paths; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; @EqualsAndHashCode(callSuper = true) @Data @@ -38,13 +40,13 @@ public class MemoryGraph extends PersistableObject { * 用于存储已存在的主题列表,便于记忆查找, 使用根主题名称作为键, 子主题名称集合为值 * 该部分在'主题提取LLM'的system prompt中常驻 */ - private HashMap> existedTopics; + private HashMap /*子主题列表*/> existedTopics; /** * 记忆节点的日期索引, 同一日期内按照对话id区分 * 同时作为临时的同一对话切片容器, 用于为同一对话内的不同切片提供更新上下文的场所 */ - private HashMap>> dateIndex; + private HashMap>> dateIndex; /** * 近两日的对话总结缓存, 用于为大模型提供必要的记忆补充, hashmap以切片的存储时间为键,总结为值 @@ -56,7 +58,7 @@ public class MemoryGraph extends PersistableObject { /** * 近两日的区分用户的对话总结缓存,在prompt结构上比dialogMap层级深一层, dialogMap更具近两日整体对话的摘要性质 */ - private HashMap> userDialogMap; + private ConcurrentHashMap> userDialogMap; /** * 当前对话的活动性总结, 拥有比dialogMap更丰富的全文细节, 作为当前对话token超限时的必要上下文压缩存储 @@ -67,7 +69,23 @@ public class MemoryGraph extends PersistableObject { * 存储确定性记忆, 如'用户爱好'等确定性信息 * 该部分作为'主LLM'system prompt常驻 */ - private HashMap> staticMemory; + private HashMap> staticMemory; + + /** + * memorySliceCache计数器,每日清空 + */ + private ConcurrentHashMap /*触发查询的主题列表*/, Integer> memoryNodeCacheCounter; + + /** + * 记忆切片缓存,每日清空 + * 用于记录作为终点节点调用次数最多的记忆节点的切片数据 + */ + private ConcurrentHashMap /*主题路径*/, List /*切片列表*/> memorySliceCache; + + /** + * 缓存日期 + */ + private LocalDate cacheDate; public MemoryGraph(String id) { this.id = id; @@ -75,6 +93,8 @@ public class MemoryGraph extends PersistableObject { this.existedTopics = new HashMap<>(); this.dateIndex = new HashMap<>(); this.staticMemory = new HashMap<>(); + this.memoryNodeCacheCounter = new ConcurrentHashMap<>(); + this.memorySliceCache = new ConcurrentHashMap<>(); } public static MemoryGraph initialize(String id) { @@ -133,14 +153,18 @@ public class MemoryGraph extends PersistableObject { } public void insertMemory(List topicPath, MemorySlice slice) throws IOException, ClassNotFoundException { + //每日刷新缓存 + checkCacheDate(); + //如果topicPath在memorySliceCache中存在对应缓存,由于进行的插入操作,则需要移除该缓存,但不清除相关计数 + memorySliceCache.remove(topicPath); topicPath = new ArrayList<>(topicPath); //查看是否存在根主题节点 String rootTopic = topicPath.getFirst(); topicPath.removeFirst(); if (!topicNodes.containsKey(rootTopic)) { TopicNode rootNode = new TopicNode(); - rootNode.setMemoryNodes(new ArrayList<>()); - rootNode.setTopicNodes(new HashMap<>()); + rootNode.setMemoryNodes(new CopyOnWriteArrayList<>()); + rootNode.setTopicNodes(new ConcurrentHashMap<>()); topicNodes.put(rootTopic, rootNode); existedTopics.put(rootTopic, new LinkedHashSet<>()); } @@ -154,9 +178,9 @@ public class MemoryGraph extends PersistableObject { TopicNode newNode = new TopicNode(); lastTopicNode.getTopicNodes().put(topic, newNode); lastTopicNode = newNode; - List nodeList = new ArrayList<>(); + CopyOnWriteArrayList nodeList = new CopyOnWriteArrayList<>(); lastTopicNode.setMemoryNodes(nodeList); - lastTopicNode.setTopicNodes(new HashMap<>()); + lastTopicNode.setTopicNodes(new ConcurrentHashMap<>()); existedTopicNodes.add(topic); } } @@ -174,6 +198,7 @@ public class MemoryGraph extends PersistableObject { if (!hasSlice) { node = new MemoryNode(); node.setLocalDate(now); + node.setMemoryNodeId(UUID.randomUUID().toString()); node.setMemorySliceList(new ArrayList<>()); lastTopicNode.getMemoryNodes().add(node); lastTopicNode.getMemoryNodes().sort(null); @@ -188,11 +213,12 @@ public class MemoryGraph extends PersistableObject { private void updateDialogMap(MemorySlice slice) { String summary = slice.getSummary(); LocalDateTime now = LocalDateTime.now(); - //更新dialogMap + + //更新dialogMap ------------------------- //移除两天前的上下文缓存(切片总结) List keysToRemove = new ArrayList<>(); dialogMap.forEach((k, v) -> { - if (now.minusDays(2).isAfter(k)){ + if (now.minusDays(2).isAfter(k)) { keysToRemove.add(k); } }); @@ -201,11 +227,13 @@ public class MemoryGraph extends PersistableObject { } keysToRemove.clear(); //放入新缓存 - dialogMap.put(now,summary); + dialogMap.put(now, summary); + //--------------------------------------- + //更新userDialogMap //移除两天前上下文缓存(切片总结) - userDialogMap.forEach((k,v) -> { - if (now.minusDays(2).isAfter(k)){ + userDialogMap.forEach((k, v) -> { + if (now.minusDays(2).isAfter(k)) { keysToRemove.add(k); } }); @@ -213,7 +241,10 @@ public class MemoryGraph extends PersistableObject { userDialogMap.remove(dateTime); } //放入新缓存 - userDialogMap.get(now).put(slice.getStartUser(),slice.getSummary()); + userDialogMap + .computeIfAbsent(now, k -> new ConcurrentHashMap<>()) + .merge(slice.getStartUser(), slice.getSummary(), (oldVal, newVal) -> oldVal + " " + newVal); + } private void updateDateIndex(LocalDate now, MemorySlice slice) { @@ -245,6 +276,14 @@ public class MemoryGraph extends PersistableObject { } public List selectMemoryByPath(List topicPath) throws IOException, ClassNotFoundException { + //每日刷新缓存 + checkCacheDate(); + //检测缓存并更新计数, 查看是否需要放入缓存 + updateCacheCounter(topicPath); + //查看是否存在缓存,如果存在,则直接返回 + if (memorySliceCache.containsKey(topicPath)) { + return memorySliceCache.get(topicPath); + } List targetSliceList = new ArrayList<>(); topicPath = new ArrayList<>(topicPath); String targetTopic = topicPath.getLast(); @@ -277,10 +316,35 @@ public class MemoryGraph extends PersistableObject { if (!targetParentMemoryNodes.isEmpty()) { targetSliceList.addAll(targetParentMemoryNodes.getFirst().getMemorySliceList()); } + //放入缓存 + updateCache(topicPath, targetSliceList); return targetSliceList; } - public HashMap> selectMemoryByDate(LocalDate date){ + private void updateCache(List topicPath, List targetSliceList) { + Integer tempCount = memoryNodeCacheCounter.get(topicPath); + if (tempCount >= 5) { + memorySliceCache.put(topicPath, targetSliceList); + } + } + + private void updateCacheCounter(List topicPath) { + if (memoryNodeCacheCounter.containsKey(topicPath)) { + Integer tempCount = memoryNodeCacheCounter.get(topicPath); + memoryNodeCacheCounter.put(topicPath, ++tempCount); + } else { + memoryNodeCacheCounter.put(topicPath, 1); + } + } + + private void checkCacheDate() { + if (cacheDate.isBefore(LocalDate.now())) { + memorySliceCache.clear(); + memoryNodeCacheCounter.clear(); + } + } + + public HashMap> selectMemoryByDate(LocalDate date) { return dateIndex.get(date); } diff --git a/src/main/java/work/slhaf/memory/node/MemoryNode.java b/src/main/java/work/slhaf/memory/node/MemoryNode.java index 8090fa2e..742b4ab8 100644 --- a/src/main/java/work/slhaf/memory/node/MemoryNode.java +++ b/src/main/java/work/slhaf/memory/node/MemoryNode.java @@ -23,7 +23,12 @@ public class MemoryNode extends PersistableObject implements Comparable getMemorySliceList() throws IOException, ClassNotFoundException { //检查是否存在对应文件 - File file = new File(SLICE_DATA_DIR+this.getLocalDate()+".slice"); + File file = new File(SLICE_DATA_DIR+this.getMemoryNodeId()+".slice"); if (file.exists()){ this.memorySliceList = deserialize(file); }else { + //逻辑正常的话,这部分应该不会出现,除非在insertMemory中进行save操作之前出现异常,中断了方法,但程序却没有结束 this.memorySliceList = new ArrayList<>(); } return this.memorySliceList; @@ -57,7 +63,7 @@ public class MemoryNode extends PersistableObject implements Comparable topicNodes; - private List memoryNodes; + private ConcurrentHashMap topicNodes; + private CopyOnWriteArrayList memoryNodes; }