diff --git a/src/main/java/work/slhaf/Main.java b/src/main/java/work/slhaf/Main.java index ae0564cd..209c383f 100644 --- a/src/main/java/work/slhaf/Main.java +++ b/src/main/java/work/slhaf/Main.java @@ -6,7 +6,7 @@ import work.slhaf.agent.core.interaction.data.InteractionInputData; import java.io.IOException; public class Main { - public static void main(String[] args) throws IOException, ClassNotFoundException { + public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException { Agent agent = Agent.initialize(); InteractionInputData inputData = new InteractionInputData(); diff --git a/src/main/java/work/slhaf/agent/Agent.java b/src/main/java/work/slhaf/agent/Agent.java index 556f7ef6..dbf1ddda 100644 --- a/src/main/java/work/slhaf/agent/Agent.java +++ b/src/main/java/work/slhaf/agent/Agent.java @@ -38,7 +38,7 @@ public class Agent implements TaskCallback { * 接收用户输入,包装为标准输入数据类 * @param inputData */ - public void receiveUserInput(InteractionInputData inputData) throws IOException, ClassNotFoundException { + public void receiveUserInput(InteractionInputData inputData) throws IOException, ClassNotFoundException, InterruptedException { inputData.setLocalDateTime(LocalDateTime.now()); interactionHub.call(inputData); } diff --git a/src/main/java/work/slhaf/agent/common/chat/pojo/ChatBody.java b/src/main/java/work/slhaf/agent/common/chat/pojo/ChatBody.java index f94bf136..bcea8b4d 100644 --- a/src/main/java/work/slhaf/agent/common/chat/pojo/ChatBody.java +++ b/src/main/java/work/slhaf/agent/common/chat/pojo/ChatBody.java @@ -1,8 +1,10 @@ package work.slhaf.agent.common.chat.pojo; +import com.alibaba.fastjson2.JSONObject; import lombok.*; import java.util.List; +import java.util.Map; @Builder @Data diff --git a/src/main/java/work/slhaf/agent/common/config/Config.java b/src/main/java/work/slhaf/agent/common/config/Config.java index 40f381a4..8cbc682b 100644 --- a/src/main/java/work/slhaf/agent/common/config/Config.java +++ b/src/main/java/work/slhaf/agent/common/config/Config.java @@ -4,7 +4,7 @@ import cn.hutool.json.JSONUtil; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FileUtils; -import work.slhaf.agent.core.model.CoreModel; +import work.slhaf.agent.core.module.CoreModel; import work.slhaf.agent.modules.memory.MemorySelectExtractor; import work.slhaf.agent.modules.memory.MemorySelector; import work.slhaf.agent.modules.memory.MemoryUpdater; diff --git a/src/main/java/work/slhaf/agent/common/model/ModelConstant.java b/src/main/java/work/slhaf/agent/common/model/ModelConstant.java index 8c2cc7ca..a6260632 100644 --- a/src/main/java/work/slhaf/agent/common/model/ModelConstant.java +++ b/src/main/java/work/slhaf/agent/common/model/ModelConstant.java @@ -4,18 +4,103 @@ public class ModelConstant { public static final String CORE_MODEL_PROMPT = """ """; public static final String SLICE_EVALUATOR_PROMPT = """ + 记忆切片选择器提示词(最终版) + + 功能说明 + 你需要根据用户输入的JSON数据,分析其中的`text`(当前输入内容)、`history`(对话历史)和`memory_slices`(可用记忆切片),选出相关记忆切片。当text内容与history明显不相关时,应以text为主要判断依据。 + + 输入字段说明 + • `text`: 用户当前输入的文本内容(首要分析对象) + • `history`: 用户与助手的对话历史记录(辅助参考) + • `memory_slices`: 可用的记忆切片列表,每个切片包含: + - `summary`: 切片内容摘要 + - `id`: 切片唯一标识(时间戳) + - `date`: 切片所属日期 + + 核心判断逻辑 + 1. 主题连续性检测: + IF 满足以下任一条件: + • text包含明显的新主题关键词(如"另外问下"、"突然想到"等转折词) + • text与history最后3轮对话的语义相关性<30% + • history为空 + THEN 进入「独立分析模式」: + • 仅基于text内容匹配memory_slices + • 忽略history上下文 + + 2. 常规模式: + ELSE: + • 综合text和history最近2-3轮内容进行联合判断 + + 输出规则 + { + "results": [id1, id2...] + } + + 完整示例 + 示例1(独立分析模式): + 输入:{ + "text": "突然想到,之前讨论的量子计算进展现在怎么样了?", + "history": [/* 10轮关于新冠疫苗的讨论 */], + "memory_slices": [ + {"summary": "量子计算机近期突破:IBM发布433量子位处理器", "id": 1672537000}, + {"summary": "新冠疫苗加强针接种指南", "id": 1672623400} + ] + } + 输出:{ + "results": [1672537000] + } + + 示例2(强相关延续): + 输入:{ + "text": "React 18的新特性具体有哪些?", + "history": [ + {"role": "user", "content": "现在前端框架怎么选?"}, + {"role": "assistant", "content": "建议考虑React、Vue..."} + ], + "memory_slices": [ + {"summary": "React 18更新详解:并发渲染、自动批处理等", "id": 1672709800}, + {"summary": "Vue3组合式API教程", "id": 1672796200} + ] + } + 输出:{ + "results": [1672709800] + } + + 示例3(模糊关联): + 输入:{ + "text": "这个方案的设计思路", + "history": [/* 5轮关于A项目的技术方案讨论 */], + "memory_slices": [ + {"summary": "A项目架构设计V3.2", "id": 1672882600}, + {"summary": "B项目风险评估报告", "id": 1672969000} + ] + } + 输出:{ + "results": [1672882600] + } + + 最终注意事项 + 1. 匹配优先级: + 独立分析模式:text关键词 > 语义相似度 > 日期 + 常规模式:上下文关联度 > text关键词 > 历史延续性 + + 2. 结果排序规则: + • 匹配度高的在前 + • 同等匹配度时,时间近的在前 + • 完全匹配优先于部分匹配 + 3. 直接输出JSON字符串 """; public static final String TOPIC_EXTRACTOR_PROMPT = """ MemorySelectExtractor 提示词 功能说明 你需要根据用户输入的JSON数据,分析其`text`和`history`字段内容,判断是否需要通过主题路径或日期进行记忆查询,并返回标准化格式的JSON响应。 - 注意:你只需要返回对应的JSON文本 + 注意:你只需要直接输出对应的JSON字符串 输入字段说明 • `text`: 用户当前输入的文本内容 - • `topic_tree`: 当前可用的主题树结构(多层级结构,需返回从根节点到目标节点的完整路径) + • `topic_tree`: 当前可用的主题树结构(多层级结构,需返回从根节点([root])到目标节点的完整路径) • `date`: 当前对话发生的日期(用于时间推理) @@ -25,7 +110,7 @@ public class ModelConstant { 输出规则 1. 基本响应格式: { - "recall": boolean, + "recall": boolean, //不存在匹配项则为false, 存在则为true "matches": [ // 匹配项列表 ] @@ -73,7 +158,7 @@ public class ModelConstant { 输入:{ "text": "关于NodeJS的并发处理,还有哪些要注意的", "topic_tree": " - 编程 + 编程[root] ├── JavaScript │ ├── NodeJS │ │ ├── 并发处理 @@ -96,7 +181,7 @@ public class ModelConstant { 输入:{ "text": "现在我想了解Express中间件的原理", "topic_tree": " - 编程 + 编程[root] ├── JavaScript │ ├── NodeJS │ │ ├── 并发处理 @@ -121,7 +206,7 @@ public class ModelConstant { 输入:{ "text": "2024-04-15讨论的Python内容和现在的Express需求", "topic_tree": " - 编程 + 编程[root] ├── JavaScript │ ├── NodeJS │ │ ├── 并发处理 @@ -147,7 +232,7 @@ public class ModelConstant { 输入:{ "text": "上周说的那个JavaScript特性", "topic_tree": " - 编程 + 编程[root] ├── JavaScript │ ├── NodeJS │ │ ├── 并发处理 diff --git a/src/main/java/work/slhaf/agent/core/InteractionHub.java b/src/main/java/work/slhaf/agent/core/InteractionHub.java index cd47a5cc..05561785 100644 --- a/src/main/java/work/slhaf/agent/core/InteractionHub.java +++ b/src/main/java/work/slhaf/agent/core/InteractionHub.java @@ -8,7 +8,7 @@ import work.slhaf.agent.core.interaction.TaskCallback; import work.slhaf.agent.core.interaction.data.InteractionContext; import work.slhaf.agent.core.interaction.data.InteractionInputData; import work.slhaf.agent.core.memory.MemoryManager; -import work.slhaf.agent.core.model.CoreModel; +import work.slhaf.agent.core.module.CoreModel; import work.slhaf.agent.modules.preprocess.PreprocessExecutor; import work.slhaf.agent.modules.task.TaskScheduler; @@ -35,7 +35,7 @@ public class InteractionHub { return interactionHub; } - public void call(InteractionInputData inputData) throws IOException, ClassNotFoundException { + public void call(InteractionInputData inputData) throws IOException, ClassNotFoundException, InterruptedException { //预处理 InteractionContext interactionContext = PreprocessExecutor.getInstance().execute(inputData); //加载模块 diff --git a/src/main/java/work/slhaf/agent/core/interaction/InteractionModule.java b/src/main/java/work/slhaf/agent/core/interaction/InteractionModule.java index db2de53b..21e633ad 100644 --- a/src/main/java/work/slhaf/agent/core/interaction/InteractionModule.java +++ b/src/main/java/work/slhaf/agent/core/interaction/InteractionModule.java @@ -5,5 +5,5 @@ import work.slhaf.agent.core.interaction.data.InteractionContext; import java.io.IOException; public interface InteractionModule { - void execute(InteractionContext context) throws IOException, ClassNotFoundException; + void execute(InteractionContext context) throws IOException, ClassNotFoundException, InterruptedException; } diff --git a/src/main/java/work/slhaf/agent/core/memory/MemoryGraph.java b/src/main/java/work/slhaf/agent/core/memory/MemoryGraph.java index 4ad2da02..5e37d387 100644 --- a/src/main/java/work/slhaf/agent/core/memory/MemoryGraph.java +++ b/src/main/java/work/slhaf/agent/core/memory/MemoryGraph.java @@ -102,6 +102,11 @@ public class MemoryGraph extends PersistableObject { */ private List users; + /** + * 已被选中的切片时间戳集合,需要及时清理 + */ + private Set selectedSlices; + public MemoryGraph(String id) { this.id = id; this.topicNodes = new HashMap<>(); @@ -111,6 +116,7 @@ public class MemoryGraph extends PersistableObject { this.memoryNodeCacheCounter = new ConcurrentHashMap<>(); this.memorySliceCache = new ConcurrentHashMap<>(); this.modelPrompt = new HashMap<>(); + this.selectedSlices = new HashSet<>(); } public static MemoryGraph getInstance(String id) throws IOException, ClassNotFoundException { @@ -188,7 +194,7 @@ public class MemoryGraph extends PersistableObject { node = new MemoryNode(); node.setLocalDate(now); node.setMemoryNodeId(UUID.randomUUID().toString()); - node.setMemorySliceList(new ArrayList<>()); + node.setMemorySliceList(new CopyOnWriteArrayList<>()); lastTopicNode.getMemoryNodes().add(node); lastTopicNode.getMemoryNodes().sort(null); } @@ -312,7 +318,7 @@ public class MemoryGraph extends PersistableObject { if (memorySliceCache.containsKey(topicPath)) { return memorySliceCache.get(topicPath); } - List targetSliceList = new ArrayList<>(); + CopyOnWriteArrayList targetSliceList = new CopyOnWriteArrayList<>(); topicPath = new ArrayList<>(topicPath); String targetTopic = topicPath.getLast(); TopicNode targetParentNode = getTargetParentNode(topicPath, targetTopic); @@ -323,10 +329,14 @@ public class MemoryGraph extends PersistableObject { for (MemoryNode memoryNode : targetParentNode.getTopicNodes().get(targetTopic).getMemoryNodes()) { List endpointMemorySliceList = memoryNode.loadMemorySliceList(); for (MemorySlice memorySlice : endpointMemorySliceList) { + if (selectedSlices.contains(memorySlice.getTimestamp())){ + continue; + } sliceResult.setSliceBefore(memorySlice.getSliceBefore()); sliceResult.setMemorySlice(memorySlice); sliceResult.setSliceAfter(memorySlice.getSliceAfter()); targetSliceList.add(sliceResult); + selectedSlices.add(memorySlice.getTimestamp()); } for (MemorySlice memorySlice : endpointMemorySliceList) { if (memorySlice.getRelatedTopics() != null) { @@ -345,17 +355,11 @@ public class MemoryGraph extends PersistableObject { TopicNode tempTargetParentNode = getTargetParentNode(tempTopicPath, tempTargetTopic); //获取终点节点及其最新记忆节点 TopicNode tempTargetNode = tempTargetParentNode.getTopicNodes().get(tempTopicPath.getLast()); - List tempMemoryNodes = tempTargetNode.getMemoryNodes(); - if (!tempMemoryNodes.isEmpty()) { - relatedMemorySlice.addAll(tempMemoryNodes.getFirst().loadMemorySliceList()); - } + setRelatedMemorySlices(tempTargetNode, relatedMemorySlice); } //邻近记忆节点 父级 - List targetParentMemoryNodes = targetParentNode.getMemoryNodes(); - if (!targetParentMemoryNodes.isEmpty()) { - relatedMemorySlice.addAll(targetParentMemoryNodes.getFirst().loadMemorySliceList()); - } + setRelatedMemorySlices(targetParentNode, relatedMemorySlice); //将上述结果包装为MemoryResult memoryResult.setRelatedMemorySliceResult(relatedMemorySlice); @@ -365,6 +369,19 @@ public class MemoryGraph extends PersistableObject { return memoryResult; } + private void setRelatedMemorySlices(TopicNode targetParentNode, List relatedMemorySlice) throws IOException, ClassNotFoundException { + List targetParentMemoryNodes = targetParentNode.getMemoryNodes(); + if (!targetParentMemoryNodes.isEmpty()) { + for (MemorySlice memorySlice : targetParentMemoryNodes.getFirst().loadMemorySliceList()) { + if (selectedSlices.contains(memorySlice.getTimestamp())) { + continue; + } + relatedMemorySlice.add(memorySlice); + selectedSlices.add(memorySlice.getTimestamp()); + } + } + } + private void updateCache(List topicPath, MemoryResult memoryResult) { Integer tempCount = memoryNodeCacheCounter.get(topicPath); if (tempCount >= 5) { @@ -390,12 +407,16 @@ public class MemoryGraph extends PersistableObject { public MemoryResult selectMemory(LocalDate date) { MemoryResult memoryResult = new MemoryResult(); - List targetSliceList = new ArrayList<>(); + CopyOnWriteArrayList targetSliceList = new CopyOnWriteArrayList<>(); for (List value : dateIndex.get(date).values()) { for (MemorySlice memorySlice : value) { + if (selectedSlices.contains(memorySlice.getTimestamp())){ + continue; + } MemorySliceResult memorySliceResult = new MemorySliceResult(); memorySliceResult.setMemorySlice(memorySlice); targetSliceList.add(memorySliceResult); + selectedSlices.add(memorySlice.getTimestamp()); } } memoryResult.setMemorySliceResult(targetSliceList); diff --git a/src/main/java/work/slhaf/agent/core/memory/MemoryManager.java b/src/main/java/work/slhaf/agent/core/memory/MemoryManager.java index aa18cb34..5adcf0f1 100644 --- a/src/main/java/work/slhaf/agent/core/memory/MemoryManager.java +++ b/src/main/java/work/slhaf/agent/core/memory/MemoryManager.java @@ -2,6 +2,7 @@ package work.slhaf.agent.core.memory; import lombok.Data; import lombok.extern.slf4j.Slf4j; +import work.slhaf.agent.common.chat.pojo.Message; import work.slhaf.agent.common.config.Config; import work.slhaf.agent.core.interaction.InteractionModule; import work.slhaf.agent.core.interaction.data.InteractionContext; @@ -50,6 +51,10 @@ public class MemoryManager implements InteractionModule { return memoryGraph.selectMemory(date); } + public void cleanSelectedSliceFilter(){ + memoryGraph.getSelectedSlices().clear(); + } + public String getUserId(String userInfo,String nickName) { String userId = null; for (User user : memoryGraph.getUsers()) { @@ -65,6 +70,10 @@ public class MemoryManager implements InteractionModule { return userId; } + public List getChatMessages(){ + return memoryGraph.getChatMessages(); + } + private static User setNewUser(String userInfo, String nickName) { User newUser = new User(); newUser.setUuid(UUID.randomUUID().toString()); @@ -75,4 +84,7 @@ public class MemoryManager implements InteractionModule { return newUser; } + public String getTopicTree() { + return memoryManager.getTopicTree(); + } } diff --git a/src/main/java/work/slhaf/agent/core/memory/node/MemoryNode.java b/src/main/java/work/slhaf/agent/core/memory/node/MemoryNode.java index c47174d6..db3476b6 100644 --- a/src/main/java/work/slhaf/agent/core/memory/node/MemoryNode.java +++ b/src/main/java/work/slhaf/agent/core/memory/node/MemoryNode.java @@ -13,6 +13,7 @@ import java.nio.file.Path; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; @EqualsAndHashCode(callSuper = true) @Data @@ -37,7 +38,7 @@ public class MemoryNode extends PersistableObject implements Comparable memorySliceList; + private CopyOnWriteArrayList memorySliceList; @Override public int compareTo(MemoryNode memoryNode) { @@ -56,7 +57,7 @@ public class MemoryNode extends PersistableObject implements Comparable(); + this.memorySliceList = new CopyOnWriteArrayList<>(); } return this.memorySliceList; } @@ -74,9 +75,9 @@ public class MemoryNode extends PersistableObject implements Comparable deserialize(File file) throws IOException, ClassNotFoundException { + private CopyOnWriteArrayList deserialize(File file) throws IOException, ClassNotFoundException { try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) { - List sliceList = (List) ois.readObject(); + CopyOnWriteArrayList sliceList = (CopyOnWriteArrayList) ois.readObject(); log.info("读取记忆切片成功"); return sliceList; } diff --git a/src/main/java/work/slhaf/agent/core/memory/pojo/MemoryResult.java b/src/main/java/work/slhaf/agent/core/memory/pojo/MemoryResult.java index 86d713d6..0a253c39 100644 --- a/src/main/java/work/slhaf/agent/core/memory/pojo/MemoryResult.java +++ b/src/main/java/work/slhaf/agent/core/memory/pojo/MemoryResult.java @@ -3,9 +3,10 @@ package work.slhaf.agent.core.memory.pojo; import lombok.Data; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; @Data public class MemoryResult { - private List memorySliceResult; + private CopyOnWriteArrayList memorySliceResult; private List relatedMemorySliceResult; } diff --git a/src/main/java/work/slhaf/agent/core/memory/pojo/MemorySlice.java b/src/main/java/work/slhaf/agent/core/memory/pojo/MemorySlice.java index f223ab0b..7b5ac894 100644 --- a/src/main/java/work/slhaf/agent/core/memory/pojo/MemorySlice.java +++ b/src/main/java/work/slhaf/agent/core/memory/pojo/MemorySlice.java @@ -57,6 +57,16 @@ public class MemorySlice extends PersistableObject implements Comparable this.getTimestamp()) { diff --git a/src/main/java/work/slhaf/agent/core/model/CoreModel.java b/src/main/java/work/slhaf/agent/core/module/CoreModel.java similarity index 97% rename from src/main/java/work/slhaf/agent/core/model/CoreModel.java rename to src/main/java/work/slhaf/agent/core/module/CoreModel.java index 3659b118..2485e1a6 100644 --- a/src/main/java/work/slhaf/agent/core/model/CoreModel.java +++ b/src/main/java/work/slhaf/agent/core/module/CoreModel.java @@ -1,4 +1,4 @@ -package work.slhaf.agent.core.model; +package work.slhaf.agent.core.module; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/src/main/java/work/slhaf/agent/gateway/AgentWebSocketServer.java b/src/main/java/work/slhaf/agent/gateway/AgentWebSocketServer.java index bb8b9ca4..ef9512e9 100644 --- a/src/main/java/work/slhaf/agent/gateway/AgentWebSocketServer.java +++ b/src/main/java/work/slhaf/agent/gateway/AgentWebSocketServer.java @@ -42,7 +42,7 @@ public class AgentWebSocketServer extends WebSocketServer implements MessageSend userSessions.put(inputData.getUserInfo(), webSocket); // 注册连接 try { agent.receiveUserInput(inputData); - } catch (IOException | ClassNotFoundException e) { + } catch (IOException | ClassNotFoundException | InterruptedException e) { throw new RuntimeException(e); } } diff --git a/src/main/java/work/slhaf/agent/modules/memory/MemorySelectExtractor.java b/src/main/java/work/slhaf/agent/modules/memory/MemorySelectExtractor.java index 1cb04155..1adb7e96 100644 --- a/src/main/java/work/slhaf/agent/modules/memory/MemorySelectExtractor.java +++ b/src/main/java/work/slhaf/agent/modules/memory/MemorySelectExtractor.java @@ -1,20 +1,30 @@ package work.slhaf.agent.modules.memory; +import cn.hutool.json.JSONUtil; import com.alibaba.fastjson2.JSONObject; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; import work.slhaf.agent.common.config.Config; import work.slhaf.agent.common.model.Model; import work.slhaf.agent.common.model.ModelConstant; +import work.slhaf.agent.core.interaction.data.InteractionContext; +import work.slhaf.agent.core.memory.MemoryManager; +import work.slhaf.agent.modules.memory.data.extractor.ExtractorInput; +import work.slhaf.agent.modules.memory.data.extractor.ExtractorResult; import java.io.IOException; +import java.util.List; @EqualsAndHashCode(callSuper = true) @Data +@Slf4j public class MemorySelectExtractor extends Model { public static final String MODEL_KEY = "topic_extractor"; private static MemorySelectExtractor memorySelectExtractor; + private MemoryManager memoryManager; + private MemorySelectExtractor() { } @@ -22,14 +32,33 @@ public class MemorySelectExtractor extends Model { if (memorySelectExtractor == null) { Config config = Config.getConfig(); memorySelectExtractor = new MemorySelectExtractor(); + memorySelectExtractor.setMemoryManager(MemoryManager.getInstance()); setModel(config, memorySelectExtractor, MODEL_KEY, ModelConstant.TOPIC_EXTRACTOR_PROMPT); } return memorySelectExtractor; } - public JSONObject execute(String input) { - return JSONObject.parseObject(singleChat(input).getMessage()); + public ExtractorResult execute(InteractionContext context) { + //结构化为指定格式 + ExtractorInput extractorInput = ExtractorInput.builder() + .text(context.getInput()) + .date(context.getDateTime().toLocalDate()) + .history(memoryManager.getChatMessages()) + .topic_tree(memoryManager.getTopicTree()) + .build(); + String responseStr = singleChat(JSONUtil.toJsonPrettyStr(extractorInput)).getMessage(); + + ExtractorResult extractorResult; + try { + extractorResult = JSONObject.parseObject(responseStr, ExtractorResult.class); + } catch (Exception e) { + log.error("主题提取出错: {}", e.getLocalizedMessage()); + extractorResult = new ExtractorResult(); + extractorResult.setRecall(false); + extractorResult.setMatches(List.of()); + } + return extractorResult; } public static class Constant { diff --git a/src/main/java/work/slhaf/agent/modules/memory/MemorySelector.java b/src/main/java/work/slhaf/agent/modules/memory/MemorySelector.java index 1e29dd70..b61ee161 100644 --- a/src/main/java/work/slhaf/agent/modules/memory/MemorySelector.java +++ b/src/main/java/work/slhaf/agent/modules/memory/MemorySelector.java @@ -1,14 +1,21 @@ package work.slhaf.agent.modules.memory; -import com.alibaba.fastjson2.JSONObject; import lombok.Data; import work.slhaf.agent.core.interaction.InteractionModule; import work.slhaf.agent.core.interaction.data.InteractionContext; import work.slhaf.agent.core.memory.MemoryManager; import work.slhaf.agent.core.memory.pojo.MemoryResult; +import work.slhaf.agent.core.memory.pojo.MemorySlice; +import work.slhaf.agent.modules.memory.data.evaluator.EvaluatedSlice; +import work.slhaf.agent.modules.memory.data.evaluator.EvaluatorInput; +import work.slhaf.agent.modules.memory.data.evaluator.EvaluatorResult; +import work.slhaf.agent.modules.memory.data.extractor.ExtractorMatchData; +import work.slhaf.agent.modules.memory.data.extractor.ExtractorResult; import java.io.IOException; import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; @Data public class MemorySelector implements InteractionModule { @@ -33,24 +40,53 @@ public class MemorySelector implements InteractionModule { } @Override - public void execute(InteractionContext interactionContext) throws IOException, ClassNotFoundException { + public void execute(InteractionContext interactionContext) throws IOException, ClassNotFoundException, InterruptedException { //获取主题路径 - JSONObject extractorResult = memorySelectExtractor.execute(interactionContext.getInput()); - String selectType = extractorResult.getString("type"); - //根据主结果进行操作查找切片 - MemoryResult memoryResult = switch (selectType) { - case MemorySelectExtractor.Constant.DATE -> - memoryManager.selectMemory(LocalDate.parse(extractorResult.getString(MemorySelectExtractor.Constant.DATE))); - case MemorySelectExtractor.Constant.TOPIC -> - memoryManager.selectMemory(MemorySelectExtractor.Constant.TOPIC); - default -> null; - }; - //评估切片 - if (memoryResult == null) { - memoryResult = sliceEvaluator.execute(memoryResult,interactionContext); + ExtractorResult extractorResult = memorySelectExtractor.execute(interactionContext); + if (extractorResult.isRecall()) { + //查找切片 + List memoryResultList = new ArrayList<>(); + setMemoryResultList(memoryResultList, extractorResult.getMatches(), interactionContext.getUserInfo(), interactionContext.getUserNickname()); + //评估切片 + EvaluatorInput evaluatorInput = EvaluatorInput.builder() + .input(interactionContext.getInput()) + .memoryResults(memoryResultList) + .messages(memoryManager.getChatMessages()) + .build(); + List memorySlices = sliceEvaluator.execute(evaluatorInput); + //设置上下文 + interactionContext.getModuleContext().put("memory_slices",memorySlices); } - //设置上下文 + } + private void setMemoryResultList(List memoryResultList, List matches, String userInfo, String nickName) throws IOException, ClassNotFoundException { + for (ExtractorMatchData match : matches) { + MemoryResult memoryResult = switch (match.getType()) { + case ExtractorMatchData.Constant.DATE -> memoryManager.selectMemory(match.getText()); + case ExtractorMatchData.Constant.TOPIC -> memoryManager.selectMemory(LocalDate.parse(match.getText())); + default -> null; + }; + if (memoryResult == null) continue; + memoryResultList.add(memoryResult); + } + //清理切片记录 + memoryManager.cleanSelectedSliceFilter(); + + //根据userInfo过滤是否为私人记忆 + for (MemoryResult memoryResult : memoryResultList) { + //过滤终点记忆 + memoryResult.getMemorySliceResult().removeIf(m -> removeOrNot(m.getMemorySlice(), userInfo, nickName)); + //过滤邻近记忆 + memoryResult.getRelatedMemorySliceResult().removeIf(m -> removeOrNot(m, userInfo, nickName)); + } + } + + private boolean removeOrNot(MemorySlice memorySlice, String userInfo, String nickName) { + if (memorySlice.isPrivate()) { + String userId = memoryManager.getUserId(userInfo, nickName); + return memorySlice.getStartUserId().equals(userId); + } + return true; } } diff --git a/src/main/java/work/slhaf/agent/modules/memory/SliceEvaluator.java b/src/main/java/work/slhaf/agent/modules/memory/SliceEvaluator.java index 1b85d42f..b2903ee8 100644 --- a/src/main/java/work/slhaf/agent/modules/memory/SliceEvaluator.java +++ b/src/main/java/work/slhaf/agent/modules/memory/SliceEvaluator.java @@ -1,22 +1,26 @@ package work.slhaf.agent.modules.memory; +import cn.hutool.core.date.DateUtil; import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson2.JSONObject; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; import work.slhaf.agent.common.config.Config; import work.slhaf.agent.common.model.Model; import work.slhaf.agent.common.model.ModelConstant; -import work.slhaf.agent.core.interaction.data.InteractionContext; +import work.slhaf.agent.core.interaction.InteractionThreadPoolExecutor; import work.slhaf.agent.core.memory.MemoryManager; import work.slhaf.agent.core.memory.pojo.MemoryResult; import work.slhaf.agent.core.memory.pojo.MemorySlice; import work.slhaf.agent.core.memory.pojo.MemorySliceResult; -import work.slhaf.agent.modules.memory.data.SliceSummary; +import work.slhaf.agent.modules.memory.data.evaluator.*; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.TimeUnit; @EqualsAndHashCode(callSuper = true) @Data @@ -26,6 +30,7 @@ public class SliceEvaluator extends Model { private static SliceEvaluator sliceEvaluator; private MemoryManager memoryManager; + private InteractionThreadPoolExecutor executor; private SliceEvaluator() { } @@ -42,30 +47,59 @@ public class SliceEvaluator extends Model { return sliceEvaluator; } - public MemoryResult execute(MemoryResult memoryResult, InteractionContext context) { - List sliceSummaryList = new ArrayList<>(); - setSliceSummaryList(memoryResult, context, sliceSummaryList); - String primaryJsonStr = singleChat(JSONUtil.toJsonStr(sliceSummaryList)).getMessage(); - //TODO 解析并转换为过滤后的MemoryResult + public List execute(EvaluatorInput evaluatorInput) throws InterruptedException { + List memoryResultList = evaluatorInput.getMemoryResults(); + List> tasks = new ArrayList<>(); + Queue queue = new ConcurrentLinkedDeque<>(); + for (MemoryResult memoryResult : memoryResultList) { + tasks.add(() -> { + List sliceSummaryList = new ArrayList<>(); + //映射查找键值 + Map map = new HashMap<>(); + setSliceSummaryList(memoryResult, sliceSummaryList, map); + try { + EvaluatorBatchInput batchInput = EvaluatorBatchInput.builder() + .text(evaluatorInput.getInput()) + .memory_slices(sliceSummaryList) + .history(evaluatorInput.getMessages()) + .build(); + EvaluatorResult evaluatorResult = JSONObject.parseObject(singleChat(JSONUtil.toJsonStr(batchInput)).getMessage(), EvaluatorResult.class); + for (Long result : evaluatorResult.getResults()) { + SliceSummary sliceSummary = map.get(result); + EvaluatedSlice evaluatedSlice = EvaluatedSlice.builder() + .summary(sliceSummary.getSummary()) + .date(sliceSummary.getDate()) + .build(); + queue.offer(evaluatedSlice); + } + } catch (Exception e) { + log.error("切片评估: {}", e.getLocalizedMessage()); + } + return null; + }); + } - return null; + executor.invokeAll(tasks, 30, TimeUnit.SECONDS); + + return queue.stream().toList(); } - private void setSliceSummaryList(MemoryResult memoryResult, InteractionContext context, List sliceSummaryList) { + private void setSliceSummaryList(MemoryResult memoryResult, List sliceSummaryList, Map map) { for (MemorySliceResult memorySliceResult : memoryResult.getMemorySliceResult()) { - //判断是否为发起用户 - if (accessible(memorySliceResult.getMemorySlice(), context)) { - SliceSummary sliceSummary = new SliceSummary(); - sliceSummary.setId(memorySliceResult.getMemorySlice().getTimestamp()); - String stringBuilder = memorySliceResult.getSliceBefore().getSummary() + - "\r\n" + - memorySliceResult.getMemorySlice().getSummary() + - "\r\n" + - memorySliceResult.getSliceAfter().getSummary(); - sliceSummary.setSummary(stringBuilder); - sliceSummaryList.add(sliceSummary); - } + SliceSummary sliceSummary = new SliceSummary(); + sliceSummary.setId(memorySliceResult.getMemorySlice().getTimestamp()); + String stringBuilder = memorySliceResult.getSliceBefore().getSummary() + + "\r\n" + + memorySliceResult.getMemorySlice().getSummary() + + "\r\n" + + memorySliceResult.getSliceAfter().getSummary(); + sliceSummary.setSummary(stringBuilder); + Long timestamp = memorySliceResult.getMemorySlice().getTimestamp(); + sliceSummary.setDate(DateUtil.date(timestamp).toLocalDateTime().toLocalDate()); + + sliceSummaryList.add(sliceSummary); + map.put(timestamp, sliceSummary); } for (MemorySlice memorySlice : memoryResult.getRelatedMemorySliceResult()) { @@ -74,23 +108,9 @@ public class SliceEvaluator extends Model { sliceSummary.setSummary(memorySlice.getSummary()); sliceSummaryList.add(sliceSummary); + map.put(memorySlice.getTimestamp(), sliceSummary); } } - private boolean accessible(MemorySlice slice, InteractionContext context) { - boolean ok; - String startUserId = slice.getStartUserId(); - String userInfo = context.getUserInfo(); - String nickName = context.getUserNickname(); - - if (memoryManager.getUserId(userInfo, nickName).equals(startUserId)) { - ok = true; - } else { - ok = !slice.isPrivate(); - } - - return ok; - } - } diff --git a/src/main/java/work/slhaf/agent/modules/memory/data/SliceSummary.java b/src/main/java/work/slhaf/agent/modules/memory/data/SliceSummary.java deleted file mode 100644 index 3b15763a..00000000 --- a/src/main/java/work/slhaf/agent/modules/memory/data/SliceSummary.java +++ /dev/null @@ -1,9 +0,0 @@ -package work.slhaf.agent.modules.memory.data; - -import lombok.Data; - -@Data -public class SliceSummary { - private String summary; - private Long id; -} diff --git a/src/main/java/work/slhaf/agent/modules/memory/data/evaluator/EvaluatedSlice.java b/src/main/java/work/slhaf/agent/modules/memory/data/evaluator/EvaluatedSlice.java new file mode 100644 index 00000000..bec0b846 --- /dev/null +++ b/src/main/java/work/slhaf/agent/modules/memory/data/evaluator/EvaluatedSlice.java @@ -0,0 +1,16 @@ +package work.slhaf.agent.modules.memory.data.evaluator; + +import lombok.Builder; +import lombok.Data; +import work.slhaf.agent.common.chat.pojo.Message; + +import java.time.LocalDate; +import java.util.List; + +@Data +@Builder +public class EvaluatedSlice { +// private List chatMessages; + private LocalDate date; + private String summary; +} diff --git a/src/main/java/work/slhaf/agent/modules/memory/data/evaluator/EvaluatorBatchInput.java b/src/main/java/work/slhaf/agent/modules/memory/data/evaluator/EvaluatorBatchInput.java new file mode 100644 index 00000000..15530c5d --- /dev/null +++ b/src/main/java/work/slhaf/agent/modules/memory/data/evaluator/EvaluatorBatchInput.java @@ -0,0 +1,15 @@ +package work.slhaf.agent.modules.memory.data.evaluator; + +import lombok.Builder; +import lombok.Data; +import work.slhaf.agent.common.chat.pojo.Message; + +import java.util.List; + +@Data +@Builder +public class EvaluatorBatchInput { + private String text; + private List history; + private List memory_slices; +} diff --git a/src/main/java/work/slhaf/agent/modules/memory/data/evaluator/EvaluatorInput.java b/src/main/java/work/slhaf/agent/modules/memory/data/evaluator/EvaluatorInput.java new file mode 100644 index 00000000..4cee4869 --- /dev/null +++ b/src/main/java/work/slhaf/agent/modules/memory/data/evaluator/EvaluatorInput.java @@ -0,0 +1,16 @@ +package work.slhaf.agent.modules.memory.data.evaluator; + +import lombok.Builder; +import lombok.Data; +import work.slhaf.agent.common.chat.pojo.Message; +import work.slhaf.agent.core.memory.pojo.MemoryResult; + +import java.util.List; + +@Data +@Builder +public class EvaluatorInput { + private String input; + private List messages; + private List memoryResults; +} diff --git a/src/main/java/work/slhaf/agent/modules/memory/data/evaluator/EvaluatorResult.java b/src/main/java/work/slhaf/agent/modules/memory/data/evaluator/EvaluatorResult.java new file mode 100644 index 00000000..f35c2822 --- /dev/null +++ b/src/main/java/work/slhaf/agent/modules/memory/data/evaluator/EvaluatorResult.java @@ -0,0 +1,10 @@ +package work.slhaf.agent.modules.memory.data.evaluator; + +import lombok.Data; + +import java.util.List; + +@Data +public class EvaluatorResult { + private List results; +} diff --git a/src/main/java/work/slhaf/agent/modules/memory/data/evaluator/SliceSummary.java b/src/main/java/work/slhaf/agent/modules/memory/data/evaluator/SliceSummary.java new file mode 100644 index 00000000..3a586774 --- /dev/null +++ b/src/main/java/work/slhaf/agent/modules/memory/data/evaluator/SliceSummary.java @@ -0,0 +1,12 @@ +package work.slhaf.agent.modules.memory.data.evaluator; + +import lombok.Data; + +import java.time.LocalDate; + +@Data +public class SliceSummary { + private String summary; + private Long id; + private LocalDate date; +} diff --git a/src/main/java/work/slhaf/agent/modules/memory/data/extractor/ExtractorInput.java b/src/main/java/work/slhaf/agent/modules/memory/data/extractor/ExtractorInput.java new file mode 100644 index 00000000..a2b97afe --- /dev/null +++ b/src/main/java/work/slhaf/agent/modules/memory/data/extractor/ExtractorInput.java @@ -0,0 +1,17 @@ +package work.slhaf.agent.modules.memory.data.extractor; + +import lombok.Builder; +import lombok.Data; +import work.slhaf.agent.common.chat.pojo.Message; + +import java.time.LocalDate; +import java.util.List; + +@Data +@Builder +public class ExtractorInput { + private String text; + private String topic_tree; + private LocalDate date; + private List history; +} diff --git a/src/main/java/work/slhaf/agent/modules/memory/data/extractor/ExtractorMatchData.java b/src/main/java/work/slhaf/agent/modules/memory/data/extractor/ExtractorMatchData.java new file mode 100644 index 00000000..0f420147 --- /dev/null +++ b/src/main/java/work/slhaf/agent/modules/memory/data/extractor/ExtractorMatchData.java @@ -0,0 +1,14 @@ +package work.slhaf.agent.modules.memory.data.extractor; + +import lombok.Data; + +@Data +public class ExtractorMatchData { + private String type; + private String text; + + public static class Constant { + public static final String DATE = "date"; + public static final String TOPIC = "topic"; + } +} diff --git a/src/main/java/work/slhaf/agent/modules/memory/data/extractor/ExtractorResult.java b/src/main/java/work/slhaf/agent/modules/memory/data/extractor/ExtractorResult.java new file mode 100644 index 00000000..778471cf --- /dev/null +++ b/src/main/java/work/slhaf/agent/modules/memory/data/extractor/ExtractorResult.java @@ -0,0 +1,11 @@ +package work.slhaf.agent.modules.memory.data.extractor; + +import lombok.Data; + +import java.util.List; + +@Data +public class ExtractorResult { + private boolean recall; + private List matches; +} diff --git a/src/main/java/work/slhaf/agent/modules/preprocess/PreprocessExecutor.java b/src/main/java/work/slhaf/agent/modules/preprocess/PreprocessExecutor.java index d02755b3..a28ce03c 100644 --- a/src/main/java/work/slhaf/agent/modules/preprocess/PreprocessExecutor.java +++ b/src/main/java/work/slhaf/agent/modules/preprocess/PreprocessExecutor.java @@ -1,18 +1,29 @@ package work.slhaf.agent.modules.preprocess; import com.alibaba.fastjson2.JSONObject; +import lombok.Data; import work.slhaf.agent.core.interaction.data.InteractionContext; import work.slhaf.agent.core.interaction.data.InteractionInputData; +import work.slhaf.agent.core.memory.MemoryManager; +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +@Data public class PreprocessExecutor { private static PreprocessExecutor preprocessExecutor; - private PreprocessExecutor(){} + private MemoryManager memoryManager; - public static PreprocessExecutor getInstance() { + private PreprocessExecutor() { + } + + public static PreprocessExecutor getInstance() throws IOException, ClassNotFoundException { if (preprocessExecutor == null) { preprocessExecutor = new PreprocessExecutor(); + preprocessExecutor.setMemoryManager(MemoryManager.getInstance()); } return preprocessExecutor; } @@ -28,6 +39,9 @@ public class PreprocessExecutor { context.setInput(inputData.getContent()); context.setModuleContext(new JSONObject()); + context.getModuleContext().put("text", inputData.getContent()); + context.getModuleContext().put("datetime", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + context.getModuleContext().put("character",memoryManager.getMemoryGraph().getModelPrompt()); return context; } diff --git a/src/test/java/memory/AITest.java b/src/test/java/memory/AITest.java index be60b75d..96bd516a 100644 --- a/src/test/java/memory/AITest.java +++ b/src/test/java/memory/AITest.java @@ -4,194 +4,100 @@ import org.junit.jupiter.api.Test; import work.slhaf.agent.common.chat.ChatClient; import work.slhaf.agent.common.chat.constant.ChatConstant; import work.slhaf.agent.common.chat.pojo.Message; +import work.slhaf.agent.common.model.ModelConstant; import java.util.ArrayList; import java.util.List; -import java.util.Scanner; public class AITest { @Test - public void test1() { + public void topicExtractorTest() { String input = """ { - "text": "之前处理过Node.js的并发问题,还有Express中间件开发", - "topic_tree": " - 编程 (3)[root] - ├── JavaScript (3) - │ ├── NodeJS (2) - │ │ ├── 并发处理 (0) - │ │ └── 事件循环 (0) - │ └── Express (1) - │ └── 中间件 (0) - └── Python (2)", - "date": "2024-04-10" + "text": "回到昨天讨论的TypeScript装饰器实现AOP,结合现在需要的微服务熔断机制", + "topic_tree": "编程\\n├── JavaScript\\n│ ├── NodeJS\\n│ └── TypeScript\\n│ ├── 类型系统\\n│ ├── 装饰器\\n│ │ ├── 类装饰器\\n│ │ └── 方法装饰器\\n│ └── 编译配置\\n└── 系统设计\\n ├── 微服务\\n │ ├── 服务发现\\n │ └── 熔断机制\\n └── 消息队列", + "date": "2024-05-20", + "history": [ + { + "role": "user", + "content": "我们的TS项目需要实现日志切面,装饰器方案和中间件方案哪个更合适?" + }, + { + "role": "assistant", + "content": "两种方案各有优劣:装饰器的优势在于1) 声明式编程 2) 精确到方法级别 3) 元编程能力。中间件方案则更适合请求级别的处理。具体到实现细节,方法装饰器可以通过Reflect Metadata..." + }, + { + "role": "user", + "content": "在微服务架构中,如何设计跨服务的统一日志收集?特别是Kubernetes环境下的实现" + }, + { + "role": "assistant", + "content": "K8s环境下的日志方案需要考虑:1) DaemonSet部署Fluentd 2) 应用层的日志规范 3) EFK栈的索引策略。对于NodeJS应用,建议使用Winston配合..."\s + }, + { + "role": "user", + "content": "现在遇到服务雪崩问题,需要实现熔断降级" + } + ] } - """; - run(input); + run(input, ModelConstant.TOPIC_EXTRACTOR_PROMPT); } - private void run(String input) { + @Test + public void sliceEvaluatorTest(){ + String input = """ + { + "text": "请结合我们之前讨论的美联储加息影响,分析下当前黄金ETF和国债逆回购的组合策略", + "history": [ + { + "role": "user", + "content": "美联储连续加息对A股有什么影响?" + }, + { + "role": "assistant", + "content": "历史数据分析显示:\\n1. 北上资金流动:利空流动性敏感板块\\n2. 汇率压力:增加出口企业汇兑收益\\n3. 行业分化:利好银行/出口,利空地产/消费\\n具体机制..." + }, + { + "role": "user", + "content": "黄金作为避险资产现在可以配置吗?" + }, + { + "role": "assistant", + "content": "黄金配置建议:\\n• 实际利率是核心影响因素\\n• 短期受美元指数压制\\n• 长期抗通胀属性仍在\\n建议比例..." + } + ], + "memory_slices": [ + { + "summary": "美联储加息周期的大类资产配置策略研究,包含历史回测数据(2004-2006、2015-2018两次加息周期中黄金、美债、新兴市场股市的表现),以及当前特殊环境(高通胀+地缘冲突)下的策略调整建议。", + "id": 1685587200, + "date": "2025-06-20" + }, + { + "summary": "黄金ETF与国债逆回购的组合优化模型,通过波动率分析给出了不同风险偏好下的最优配置比例,特别讨论了在流动性紧张时期如何利用逆回购对冲黄金波动。", + "id": 1685673600, + "date": "2025-06-21" + }, + { + "summary": "A股行业轮动与美联储政策的相关性研究,建立了包含利率敏感度、外资持仓比例、出口依赖度等因子的分析框架,并给出了当前环境下的行业配置建议。", + "id": 1685760000, + "date": "2025-06-22" + }, + { + "summary": "跨境资本流动监测指标体系详解,包含利差模型、风险偏好指标(VIX指数)、以及中国特有的资本管制有效性分析。", + "id": 1685846400, + "date": "2025-06-23" + } + ] + } + """; + run(input,ModelConstant.SLICE_EVALUATOR_PROMPT); + } + + private void run(String input, String prompt) { ChatClient client = new ChatClient("https://open.bigmodel.cn/api/paas/v4/chat/completions", "3db444552530b7742b0c53425fb93dcc.LcVwYjByht9AC3N9", "glm-4-flash-250414"); List messages = new ArrayList<>(); - messages.add(new Message(ChatConstant.Character.SYSTEM, """ - MemorySelectExtractor 提示词 - - 功能说明 - 你需要根据用户输入的JSON数据,分析其`text`和`history`字段内容,判断是否需要通过主题路径或日期进行记忆查询,并返回标准化格式的JSON响应。 - 注意:你只需要返回对应的JSON文本 - - 输入字段说明 - • `text`: 用户当前输入的文本内容 - - • `topic_tree`: 当前可用的主题树结构(多层级结构,需返回从根节点到目标节点的完整路径) - - • `date`: 当前对话发生的日期(用于时间推理) - - • `history`: 用户与LLM的完整对话历史(用于主题连续性判断) - - - 输出规则 - 1. 基本响应格式: - { - "recall": boolean, - "matches": [ - // 匹配项列表 - ] - } - - 2. 主题提取规则: - • 当当前`text`涉及新主题(与`history`最后N轮对话主题明显不同)时: - - ◦ 必须进行主题提取 - - ◦ 匹配`topic_tree`中最接近的完整路径(从根节点到目标节点,如"编程->JavaScript->NodeJS->并发处理") - - • 当主题与历史对话连续时: - - ◦ 除非包含明确的新子主题,否则不重复提取相同主题路径 - - - 3. 日期提取规则(保持不变): - • 仅接受具体日期(YYYY-MM-DD格式) - - • 拒绝所有模糊日期表达 - - - 4. 特殊处理: - • 当检测到主题切换但无法匹配`topic_tree`时: - - { - "recall": false, - "matches": [] - } - • 当历史对话为空时: - - ◦ 视为新主题,按常规规则处理 - - - 决策流程 - 1. 首先分析`history`判断当前对话主题上下文 - 2. 然后分析`text`: - a. 检测是否包含具体日期→添加date类型 - b. 检测是否包含新主题→添加topic类型 - 3. 最终综合判断`recall`值 - - 完整示例 - 示例1(主题延续): - 输入:{ - "text": "关于NodeJS的并发处理,还有哪些要注意的", - "topic_tree": " - 编程 - ├── JavaScript - │ ├── NodeJS - │ │ ├── 并发处理 - │ │ └── 事件循环 - │ └── Express - │ └── 中间件 - └── Python", - "date": "2024-04-20", - "history": [ - {"role": "user", "content": "说说NodeJS的并发处理机制"}, - {"role": "assistant", "content": "NodeJS的并发处理主要通过..."} - ] - } - 输出:{ - "recall": false, - "matches": [] - } - - 示例2(主题切换): - 输入:{ - "text": "现在我想了解Express中间件的原理", - "topic_tree": " - 编程 - ├── JavaScript - │ ├── NodeJS - │ │ ├── 并发处理 - │ │ └── 事件循环 - │ └── Express - │ └── 中间件 - └── Python", - "date": "2024-04-20", - "history": [ - {"role": "user", "content": "NodeJS的并发处理怎么实现"}, - {"role": "assistant", "content": "需要..."} - ] - } - 输出:{ - "recall": true, - "matches": [ - {"type": "topic", "text": "编程->JavaScript->Express->中间件"} - ] - } - - 示例3(混合情况): - 输入:{ - "text": "2024-04-15讨论的Python内容和现在的Express需求", - "topic_tree": " - 编程 - ├── JavaScript - │ ├── NodeJS - │ │ ├── 并发处理 - │ │ └── 事件循环 - │ └── Express - │ └── 中间件 - └── Python", - "date": "2024-04-20", - "history": [ - {"role": "user", "content": "需要了解Express框架"}, - {"role": "assistant", "content": "Express是..."} - ] - } - 输出:{ - "recall": true, - "matches": [ - {"type": "date", "text": "2024-04-15"}, - {"type": "topic", "text": "编程->Python"} - ] - } - - 示例4(模糊日期): - 输入:{ - "text": "上周说的那个JavaScript特性", - "topic_tree": " - 编程 - ├── JavaScript - │ ├── NodeJS - │ │ ├── 并发处理 - │ │ └── 事件循环 - │ └── Express - │ └── 中间件 - └── Python", - "date": "2024-04-20", - "history": [...] - } - 输出:{ - "recall": false, - "matches": [] - } - """)); + messages.add(new Message(ChatConstant.Character.SYSTEM, prompt)); messages.add(new Message(ChatConstant.Character.USER, input)); System.out.println(client.runChat(messages).getMessage()); diff --git a/src/test/java/memory/SearchTest.java b/src/test/java/memory/SearchTest.java index 4fcf8628..a3b8dd6b 100644 --- a/src/test/java/memory/SearchTest.java +++ b/src/test/java/memory/SearchTest.java @@ -39,7 +39,7 @@ class SearchTest { MemorySlice oldJavaMemory = createMemorySlice("javaOld"); MemoryNode oldNode = new MemoryNode(); oldNode.setLocalDate(yesterday); - oldNode.setMemorySliceList(List.of(oldJavaMemory)); +// oldNode.setMemorySliceList(List.of(oldJavaMemory)); } // 场景1:查询存在的完整主题路径(含相关主题) @@ -121,7 +121,7 @@ class SearchTest { // 创建昨日记忆节点并添加到主题节点 MemoryNode oldMemoryNode = new MemoryNode(); oldMemoryNode.setLocalDate(yesterday); - oldMemoryNode.setMemorySliceList(new ArrayList<>(List.of(oldDbMemory))); +// oldMemoryNode.setMemorySliceList(new ArrayList<>(List.of(oldDbMemory))); dbTopicNode.getMemoryNodes().add(oldMemoryNode); // 对记忆节点进行日期排序(根据compareTo方法)