From c28979b495f5d52fd8f8cf13ae0db54f725a8b96 Mon Sep 17 00:00:00 2001 From: slhaf Date: Fri, 11 Apr 2025 21:50:11 +0800 Subject: [PATCH] =?UTF-8?q?feat(memory):=20=E5=AE=9E=E7=8E=B0=E8=AE=B0?= =?UTF-8?q?=E5=BF=86=E5=88=87=E7=89=87=E6=8C=81=E4=B9=85=E5=8C=96=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=AE=B0=E5=BF=86=E5=AD=98=E5=82=A8=E7=BB=93?= =?UTF-8?q?=E6=9E=84-=20=E6=96=B0=E5=A2=9E=20ChatClient=20=E7=B1=BB?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E4=B8=8E=E5=A4=A7=E6=A8=A1=E5=9E=8B=E7=9A=84?= =?UTF-8?q?=E4=BA=A4=E4=BA=92=20-=20=E6=B7=BB=E5=8A=A0=E4=BA=86chat?= =?UTF-8?q?=E5=8C=85=EF=BC=8C=E7=94=A8=E4=BA=8E=E5=90=8E=E7=BB=AD=E5=A4=A7?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E5=AF=B9=E6=8E=A5=20-=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=20MemoryGraph=20=E7=B1=BB=EF=BC=8C=E5=A2=9E=E5=8A=A0=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E5=AF=B9=E8=AF=9D=E7=BC=93=E5=AD=98=E5=92=8C=E5=BD=93?= =?UTF-8?q?=E5=89=8D=E5=AF=B9=E8=AF=9D=E5=8E=8B=E7=BC=A9=E4=B8=8A=E4=B8=8B?= =?UTF-8?q?=E6=96=87=20-=20=E4=BF=AE=E6=94=B9=20MemoryNode=20=E7=B1=BB?= =?UTF-8?q?=EF=BC=8C=E5=AE=9E=E7=8E=B0=E8=AE=B0=E5=BF=86=E5=88=87=E7=89=87?= =?UTF-8?q?=E7=9A=84=E5=BA=8F=E5=88=97=E5=8C=96=E5=92=8C=E5=8F=8D=E5=BA=8F?= =?UTF-8?q?=E5=88=97=E5=8C=96=20-=20=E6=9B=B4=E6=96=B0=20MemorySlice=20?= =?UTF-8?q?=E7=B1=BB=EF=BC=8C=E5=A2=9E=E5=8A=A0=E5=A4=9A=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E5=AD=97=E6=AE=B5=E5=92=8C=E6=96=B9=E6=B3=95?= =?UTF-8?q?=EF=BC=8C=E5=B0=86=E5=88=87=E7=89=87=E5=86=85=E5=AE=B9=E4=BB=8E?= =?UTF-8?q?SliceData=E7=A7=BB=E5=8A=A8=E8=87=B3MemorySlice=20-=20=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E6=9C=AA=E4=BD=BF=E7=94=A8=E7=9A=84=20SliceData=20?= =?UTF-8?q?=E7=B1=BB=20-=20=E6=B7=BB=E5=8A=A0=E6=97=A5=E5=BF=97=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=E5=92=8C=E5=BC=82=E5=B8=B8=E5=A4=84=E7=90=86=EF=BC=8C?= =?UTF-8?q?=E6=96=B0=E7=9A=84=E5=BC=82=E5=B8=B8=E7=B1=BBNullSliceListExcep?= =?UTF-8?q?tion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 15 +++ src/main/java/work/slhaf/chat/ChatClient.java | 70 +++++++++++ .../work/slhaf/chat/constant/Constant.java | 22 ++++ .../java/work/slhaf/chat/pojo/ChatBody.java | 25 ++++ .../work/slhaf/chat/pojo/ChatResponse.java | 16 +++ .../java/work/slhaf/chat/pojo/Message.java | 14 +++ .../slhaf/chat/pojo/PrimaryChatResponse.java | 111 ++++++++++++++++++ .../java/work/slhaf/memory/MemoryGraph.java | 41 ++++++- .../slhaf/memory/content/MemorySlice.java | 55 ++++++--- .../work/slhaf/memory/content/SliceData.java | 10 -- .../exception/NullSliceListException.java | 7 ++ .../work/slhaf/memory/node/MemoryNode.java | 49 +++++++- src/test/java/memory/InsertTest.java | 14 +-- src/test/java/memory/SearchTest.java | 9 +- 14 files changed, 413 insertions(+), 45 deletions(-) create mode 100644 src/main/java/work/slhaf/chat/ChatClient.java create mode 100644 src/main/java/work/slhaf/chat/constant/Constant.java create mode 100644 src/main/java/work/slhaf/chat/pojo/ChatBody.java create mode 100644 src/main/java/work/slhaf/chat/pojo/ChatResponse.java create mode 100644 src/main/java/work/slhaf/chat/pojo/Message.java create mode 100644 src/main/java/work/slhaf/chat/pojo/PrimaryChatResponse.java delete mode 100644 src/main/java/work/slhaf/memory/content/SliceData.java create mode 100644 src/main/java/work/slhaf/memory/exception/NullSliceListException.java diff --git a/pom.xml b/pom.xml index 9bb4116b..32b2b406 100644 --- a/pom.xml +++ b/pom.xml @@ -49,6 +49,21 @@ RELEASE test + + org.slf4j + slf4j-api + 2.0.17 + + + ch.qos.logback + logback-classic + 1.5.17 + + + cn.hutool + hutool-all + 5.8.36 + \ No newline at end of file diff --git a/src/main/java/work/slhaf/chat/ChatClient.java b/src/main/java/work/slhaf/chat/ChatClient.java new file mode 100644 index 00000000..7c52924a --- /dev/null +++ b/src/main/java/work/slhaf/chat/ChatClient.java @@ -0,0 +1,70 @@ +package work.slhaf.chat; + +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.json.JSONUtil; +import lombok.Data; +import lombok.NoArgsConstructor; +import work.slhaf.chat.constant.Constant; +import work.slhaf.chat.pojo.ChatBody; +import work.slhaf.chat.pojo.ChatResponse; +import work.slhaf.chat.pojo.Message; +import work.slhaf.chat.pojo.PrimaryChatResponse; + +import java.util.List; + +@Data +@NoArgsConstructor +public class ChatClient { + private String clientId; + + private String url; + private String apikey; + private String model; + + private int top_p; + private int temperature; + private int max_tokens; + + public ChatClient(String url, String apikey, String model) { + this.url = url; + this.apikey = apikey; + this.model = model; + } + + public ChatResponse runChat(List messages) { + HttpRequest request = HttpRequest.post(url); + request.header("Content-Type", "application/json"); + request.header("Authorization", "Bearer " + apikey); + + ChatBody body; + if (top_p > 0) { + body = ChatBody.builder() + .model(model) + .messages(messages) + .top_p(top_p) + .temperature(temperature) + .max_tokens(max_tokens) + .build(); + } else { + body = ChatBody.builder() + .model(model) + .messages(messages) + .build(); + } + + HttpResponse response = request.body(JSONUtil.toJsonStr(body)).execute(); + ChatResponse finalResponse; + + PrimaryChatResponse primaryChatResponse = JSONUtil.toBean(response.body(), PrimaryChatResponse.class); + finalResponse = ChatResponse.builder() + .type(Constant.Response.SUCCESS) + .message(primaryChatResponse.getChoices().get(0).getMessage().getContent()) + .usageBean(primaryChatResponse.getUsage()) + .build(); + + response.close(); + return finalResponse; + } + +} diff --git a/src/main/java/work/slhaf/chat/constant/Constant.java b/src/main/java/work/slhaf/chat/constant/Constant.java new file mode 100644 index 00000000..ee18e9fe --- /dev/null +++ b/src/main/java/work/slhaf/chat/constant/Constant.java @@ -0,0 +1,22 @@ +package work.slhaf.chat.constant; + +public class Constant { + + public static class Character { + public static final String USER = "user"; + public static final String SYSTEM = "system"; + public static final String ASSISTANT = "assistant"; + } + + public static class Model { + public static final String DEEP_SEEK_CHAT = "deepseek-chat"; + public static final String GLM_4_FLASH = "glm-4_flash"; + public static final String GLM_4_PLUS = "glm-4_plus"; + public static final String GLM_4_0520 = "glm-4_0520"; + } + + public static class Response { + public static final String SUCCESS = "success"; + public static final String ERROR = "error"; + } +} diff --git a/src/main/java/work/slhaf/chat/pojo/ChatBody.java b/src/main/java/work/slhaf/chat/pojo/ChatBody.java new file mode 100644 index 00000000..bab09a6f --- /dev/null +++ b/src/main/java/work/slhaf/chat/pojo/ChatBody.java @@ -0,0 +1,25 @@ +package work.slhaf.chat.pojo; + +import lombok.*; + +import java.util.List; + +@Builder +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ChatBody { + @NonNull + private String model; + @NonNull + private List messages; + @Builder.Default + private int temperature = 1; + @Builder.Default + private int top_p = 1; + private boolean stream; + @Builder.Default + private int max_tokens = 1024; + private int presence_penalty; + private int frequency_penalty; +} diff --git a/src/main/java/work/slhaf/chat/pojo/ChatResponse.java b/src/main/java/work/slhaf/chat/pojo/ChatResponse.java new file mode 100644 index 00000000..cbb7854a --- /dev/null +++ b/src/main/java/work/slhaf/chat/pojo/ChatResponse.java @@ -0,0 +1,16 @@ +package work.slhaf.chat.pojo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ChatResponse { + private String type; + private String message; + private PrimaryChatResponse.UsageBean usageBean; +} diff --git a/src/main/java/work/slhaf/chat/pojo/Message.java b/src/main/java/work/slhaf/chat/pojo/Message.java new file mode 100644 index 00000000..dcc1008a --- /dev/null +++ b/src/main/java/work/slhaf/chat/pojo/Message.java @@ -0,0 +1,14 @@ +package work.slhaf.chat.pojo; + +import lombok.*; + +@Builder +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Message { + @NonNull + private String role; + @NonNull + private String content; +} diff --git a/src/main/java/work/slhaf/chat/pojo/PrimaryChatResponse.java b/src/main/java/work/slhaf/chat/pojo/PrimaryChatResponse.java new file mode 100644 index 00000000..a28f1fc9 --- /dev/null +++ b/src/main/java/work/slhaf/chat/pojo/PrimaryChatResponse.java @@ -0,0 +1,111 @@ +package work.slhaf.chat.pojo; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class PrimaryChatResponse { + + /** + * id + */ + private String id; + /** + * object + */ + private String object; + /** + * created + */ + private int created; + /** + * model + */ + private String model; + /** + * choices + */ + private List choices; + /** + * usage + */ + private UsageBean usage; + /** + * system_fingerprint + */ + private String system_fingerprint; + + @Setter + @Getter + public static class UsageBean { + /** + * prompt_tokens + */ + private int prompt_tokens; + /** + * completion_tokens + */ + private int completion_tokens; + /** + * total_tokens + */ + private int total_tokens; + /** + * prompt_cache_hit_tokens + */ + private int prompt_cache_hit_tokens; + /** + * prompt_cache_miss_tokens + */ + private int prompt_cache_miss_tokens; + + @Override + public String toString() { + return "UsageBean{" + + "prompt_tokens=" + prompt_tokens + + ", completion_tokens=" + completion_tokens + + ", total_tokens=" + total_tokens + + ", prompt_cache_hit_tokens=" + prompt_cache_hit_tokens + + ", prompt_cache_miss_tokens=" + prompt_cache_miss_tokens + + '}'; + } + } + + @Setter + @Getter + public static class ChoicesBean { + /** + * index + */ + private int index; + /** + * message + */ + private MessageBean message; + /** + * logprobs + */ + private Object logprobs; + /** + * finish_reason + */ + private String finish_reason; + + @Setter + @Getter + public static class MessageBean { + /** + * role + */ + private String role; + /** + * content + */ + private String content; + + } + } +} diff --git a/src/main/java/work/slhaf/memory/MemoryGraph.java b/src/main/java/work/slhaf/memory/MemoryGraph.java index 696bd51e..8a066169 100644 --- a/src/main/java/work/slhaf/memory/MemoryGraph.java +++ b/src/main/java/work/slhaf/memory/MemoryGraph.java @@ -1,6 +1,7 @@ package work.slhaf.memory; import lombok.Data; +import lombok.extern.slf4j.Slf4j; import work.slhaf.memory.content.MemorySlice; import work.slhaf.memory.exception.UnExistedTopicException; import work.slhaf.memory.node.MemoryNode; @@ -15,6 +16,7 @@ import java.time.LocalDateTime; import java.util.*; @Data +@Slf4j public class MemoryGraph implements Serializable { @Serial @@ -43,14 +45,25 @@ public class MemoryGraph implements Serializable { /** * 近两日的对话总结缓存, 用于为大模型提供必要的记忆补充, hashmap以切片的存储时间为键,总结为值 * 该部分作为'主LLM'system prompt常驻 + * 该部分作为近两日的整体对话缓存, 不区分用户 */ private HashMap dialogMap; + /** + * 近两日的区分用户的对话总结缓存,在prompt结构上比dialogMap层级深一层, dialogMap更具近两日整体对话的摘要性质 + */ + private HashMap> userDialogMap; + + /** + * 当前对话的活动性总结, 拥有比dialogMap更丰富的全文细节, 作为当前对话token超限时的必要上下文压缩存储 + */ + private List currentCompressedSessionContext; + /** * 存储确定性记忆, 如'用户爱好'等确定性信息 * 该部分作为'主LLM'system prompt常驻 */ - private HashMap> staticMemory; + private HashMap> staticMemory; public MemoryGraph(String id) { this.id = id; @@ -98,7 +111,7 @@ public class MemoryGraph implements Serializable { try (ObjectInputStream ois = new ObjectInputStream( new FileInputStream(filePath.toFile()))) { MemoryGraph graph = (MemoryGraph) ois.readObject(); - System.out.println("MemoryGraph 已从文件加载: " + filePath); + log.info("MemoryGraph 已从文件加载: " + filePath); return graph; } } @@ -115,7 +128,7 @@ public class MemoryGraph implements Serializable { } } - public void insertMemory(List topicPath, MemorySlice slice) { + public void insertMemory(List topicPath, MemorySlice slice) throws IOException, ClassNotFoundException { topicPath = new ArrayList<>(topicPath); //查看是否存在根主题节点 String rootTopic = topicPath.getFirst(); @@ -165,12 +178,14 @@ public class MemoryGraph implements Serializable { updateDateIndex(now, slice); updateDialogMap(slice); + node.saveMemorySliceList(); } private void updateDialogMap(MemorySlice slice) { - String summary = slice.getSliceData().getSummary(); + String summary = slice.getSummary(); LocalDateTime now = LocalDateTime.now(); - //移除两天前的上下文补充(切片总结) + //更新dialogMap + //移除两天前的上下文缓存(切片总结) List keysToRemove = new ArrayList<>(); dialogMap.forEach((k, v) -> { if (now.minusDays(2).isAfter(k)){ @@ -180,7 +195,21 @@ public class MemoryGraph implements Serializable { for (LocalDateTime dateTime : keysToRemove) { dialogMap.remove(dateTime); } + keysToRemove.clear(); + //放入新缓存 dialogMap.put(now,summary); + //更新userDialogMap + //移除两天前上下文缓存(切片总结) + userDialogMap.forEach((k,v) -> { + if (now.minusDays(2).isAfter(k)){ + keysToRemove.add(k); + } + }); + for (LocalDateTime dateTime : keysToRemove) { + userDialogMap.remove(dateTime); + } + //放入新缓存 + userDialogMap.get(now).put(slice.getStartUser(),slice.getSummary()); } private void updateDateIndex(LocalDate now, MemorySlice slice) { @@ -211,7 +240,7 @@ public class MemoryGraph implements Serializable { } - public List selectMemoryByPath(List topicPath) { + public List selectMemoryByPath(List topicPath) throws IOException, ClassNotFoundException { List targetSliceList = new ArrayList<>(); topicPath = new ArrayList<>(topicPath); String targetTopic = topicPath.getLast(); diff --git a/src/main/java/work/slhaf/memory/content/MemorySlice.java b/src/main/java/work/slhaf/memory/content/MemorySlice.java index a5952754..6c73901d 100644 --- a/src/main/java/work/slhaf/memory/content/MemorySlice.java +++ b/src/main/java/work/slhaf/memory/content/MemorySlice.java @@ -1,21 +1,56 @@ package work.slhaf.memory.content; import lombok.Data; +import work.slhaf.chat.pojo.Message; import java.io.Serializable; import java.util.List; @Data public class MemorySlice implements Serializable, Comparable { - //关联的完整对话的id + + /** + * 关联的完整对话的id + */ private String memoryId; - //该切片在关联的完整对话中的顺序, 由时间戳确定 + + /** + * 该切片在关联的完整对话中的顺序, 由时间戳确定 + */ private Long timestamp; - private String slicePath; + + /** + * 格式为"<日期>.slice", 如2025-04-11.slice + */ + private String summary; + + private List chatMessages; + + /** + * 关联的其他主题, 即"邻近节点(联系)" + */ private List> relatedTopics; - //关联完整对话中的前序切片, 排序为键,完整路径为值 - private MemorySlice sliceBefore; - private MemorySlice sliceAfter; + + /** + * 关联完整对话中的前序切片, 排序为键,完整路径为值 + */ + private MemorySlice sliceBefore, sliceAfter; + + /** + * 多用户设定 + * 发起该切片对话的用户 + */ + private String startUser; + + /** + * 该切片涉及到的用户 + */ + private List involvedUsers; + + /** + * 是否仅供发起用户作为记忆参考 + */ + private boolean isPrivate; @Override public int compareTo(MemorySlice memorySlice) { @@ -27,12 +62,4 @@ public class MemorySlice implements Serializable, Comparable { return 0; } - public SliceData getSliceData(){ - //todo: 待实现获取逻辑 - return new SliceData(); - } - - public void saveSlice(SliceData sliceData){ - //todo: 待实现存储逻辑, 该逻辑内将设置`slicePath` - } } diff --git a/src/main/java/work/slhaf/memory/content/SliceData.java b/src/main/java/work/slhaf/memory/content/SliceData.java deleted file mode 100644 index 57db6349..00000000 --- a/src/main/java/work/slhaf/memory/content/SliceData.java +++ /dev/null @@ -1,10 +0,0 @@ -package work.slhaf.memory.content; - -import com.alibaba.fastjson2.JSONArray; -import lombok.Data; - -@Data -public class SliceData { - private String summary; - private JSONArray content; -} diff --git a/src/main/java/work/slhaf/memory/exception/NullSliceListException.java b/src/main/java/work/slhaf/memory/exception/NullSliceListException.java new file mode 100644 index 00000000..1d813bec --- /dev/null +++ b/src/main/java/work/slhaf/memory/exception/NullSliceListException.java @@ -0,0 +1,7 @@ +package work.slhaf.memory.exception; + +public class NullSliceListException extends RuntimeException { + public NullSliceListException(String message) { + super(message); + } +} diff --git a/src/main/java/work/slhaf/memory/node/MemoryNode.java b/src/main/java/work/slhaf/memory/node/MemoryNode.java index 3580f208..b13297c3 100644 --- a/src/main/java/work/slhaf/memory/node/MemoryNode.java +++ b/src/main/java/work/slhaf/memory/node/MemoryNode.java @@ -1,17 +1,29 @@ package work.slhaf.memory.node; import lombok.Data; +import lombok.extern.slf4j.Slf4j; import work.slhaf.memory.content.MemorySlice; +import work.slhaf.memory.exception.NullSliceListException; -import java.io.Serializable; +import java.io.*; import java.time.LocalDate; +import java.util.ArrayList; import java.util.List; @Data +@Slf4j public class MemoryNode implements Serializable, Comparable { - //记忆节点所属日期 + + private static String SLICE_DATA_DIR = "./data/slice/"; + + /** + * 记忆节点所属日期, 以日期为文件名在硬盘存储记忆数据(如 2025-04-11.slice) + */ private LocalDate localDate; - //该日期对应的全部记忆切片 + + /** + * 该日期对应的全部记忆切片 + */ private List memorySliceList; @Override @@ -23,4 +35,35 @@ public class MemoryNode implements Serializable, Comparable { } return 0; } + + public List getMemorySliceList() throws IOException, ClassNotFoundException { + //检查是否存在对应文件 + File file = new File(SLICE_DATA_DIR+this.getLocalDate()+".slice"); + if (file.exists()){ + this.memorySliceList = deserialize(file); + }else { + this.memorySliceList = new ArrayList<>(); + } + return this.memorySliceList; + } + + public void saveMemorySliceList() throws IOException { + if (memorySliceList == null){ + throw new NullSliceListException("memorySliceList为NULL! 检查实现逻辑!"); + } + File file = new File(SLICE_DATA_DIR+this.getLocalDate()+".slice"); + try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file))){ + oos.writeObject(this.memorySliceList); + } + //取消切片挂载, 释放内存 + this.memorySliceList = null; + } + + private List deserialize(File file) throws IOException, ClassNotFoundException { + try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) { + List sliceList = (List) ois.readObject(); + log.info("读取记忆切片成功"); + return sliceList; + } + } } diff --git a/src/test/java/memory/InsertTest.java b/src/test/java/memory/InsertTest.java index 31dae867..299496fe 100644 --- a/src/test/java/memory/InsertTest.java +++ b/src/test/java/memory/InsertTest.java @@ -7,8 +7,8 @@ import work.slhaf.memory.content.MemorySlice; import work.slhaf.memory.node.MemoryNode; import work.slhaf.memory.node.TopicNode; +import java.io.IOException; import java.time.LocalDate; -import java.time.LocalDateTime; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedList; @@ -28,7 +28,7 @@ public class InsertTest { } @Test - public void testInsertMemory_NewRootTopic() { + public void testInsertMemory_NewRootTopic() throws IOException, ClassNotFoundException { // 准备测试数据 List topicPath = new LinkedList<>(Arrays.asList("Programming", "Java", "Collections")); MemorySlice slice = createTestMemorySlice("slice1"); @@ -54,7 +54,7 @@ public class InsertTest { } @Test - public void testInsertMemory_ExistingTopicPath() { + public void testInsertMemory_ExistingTopicPath() throws IOException, ClassNotFoundException { // 准备初始数据 List topicPath1 = new LinkedList<>(Arrays.asList("Programming", "Java", "Collections")); MemorySlice slice1 = createTestMemorySlice("slice1"); @@ -75,7 +75,7 @@ public class InsertTest { } @Test - public void testInsertMemory_DifferentDays() { + public void testInsertMemory_DifferentDays() throws IOException, ClassNotFoundException { // 准备测试数据 List topicPath = new LinkedList<>(Arrays.asList("Math", "Algebra")); MemorySlice slice1 = createTestMemorySlice("slice1"); @@ -101,7 +101,7 @@ public class InsertTest { } @Test - public void testInsertMemory_PartialExistingPath() { + public void testInsertMemory_PartialExistingPath() throws IOException, ClassNotFoundException { // 准备初始数据 - 创建部分路径 List topicPath1 = new LinkedList<>(Arrays.asList("Science", "Physics")); MemorySlice slice1 = createTestMemorySlice("slice1"); @@ -129,11 +129,10 @@ public class InsertTest { } @Test - public void testSerializationConsistency() { + public void testSerializationConsistency() throws IOException, ClassNotFoundException { // 构造 MemorySlice MemorySlice slice = new MemorySlice(); slice.setMemoryId("001"); - slice.setSlicePath("/demo/path"); List topicPath = Arrays.asList("生活", "学习", "Java"); @@ -160,7 +159,6 @@ public class InsertTest { // 校验:MemorySlice 内容一致 MemorySlice deserializedSlice = javaNode.getMemoryNodes().get(0).getMemorySliceList().get(0); assertEquals("001", deserializedSlice.getMemoryId()); - assertEquals("/demo/path", deserializedSlice.getSlicePath()); } } diff --git a/src/test/java/memory/SearchTest.java b/src/test/java/memory/SearchTest.java index 6f33222b..9932a84c 100644 --- a/src/test/java/memory/SearchTest.java +++ b/src/test/java/memory/SearchTest.java @@ -8,6 +8,7 @@ import work.slhaf.memory.exception.UnExistedTopicException; import work.slhaf.memory.node.MemoryNode; import work.slhaf.memory.node.TopicNode; +import java.io.IOException; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; @@ -21,7 +22,7 @@ class SearchTest { // 初始化测试环境,模拟插入基础数据 @BeforeEach - void setUp() { + void setUp() throws IOException, ClassNotFoundException { memoryGraph = new MemoryGraph("testGraph"); // 构建基础主题路径:根主题 -> 编程 -> Java @@ -42,7 +43,7 @@ class SearchTest { // 场景1:查询存在的完整主题路径(含相关主题) @Test - void selectMemory_shouldReturnTargetAndRelatedAndParentMemories() { + void selectMemory_shouldReturnTargetAndRelatedAndParentMemories() throws IOException, ClassNotFoundException { // 准备相关主题数据:根主题 -> 算法 -> 排序 List sortPath = new ArrayList<>(); sortPath.add("算法"); @@ -81,7 +82,7 @@ class SearchTest { // 场景3:无相关主题时仅返回目标节点和父节点记忆 @Test - void selectMemory_withoutRelatedTopics_shouldReturnTargetAndParent() { + void selectMemory_withoutRelatedTopics_shouldReturnTargetAndParent() throws IOException, ClassNotFoundException { // 插入父级记忆:根主题 -> 编程 List parentPath = new ArrayList<>(); parentPath.add("编程"); @@ -102,7 +103,7 @@ class SearchTest { // 场景4:验证日期排序,应优先取最新日期的邻近记忆 @Test - void selectMemory_shouldGetLatestRelatedMemory() { + void selectMemory_shouldGetLatestRelatedMemory() throws IOException, ClassNotFoundException { // 准备相关主题路径:根主题 -> 数据库 List dbPath = new ArrayList<>(); dbPath.add("数据库");