diff --git a/.github/workflows/sync-from-gitea.yml b/.github/workflows/sync-from-gitea.yml new file mode 100644 index 00000000..794dcf7a --- /dev/null +++ b/.github/workflows/sync-from-gitea.yml @@ -0,0 +1,36 @@ +name: Sync from Gitea + +# 1. 给 GITHUB_TOKEN 开写权限 +permissions: + contents: write + +on: + schedule: + - cron: '*/30 * * * *' + workflow_dispatch: + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - name: 配置 Git 用户 + run: | + git config --global user.name "Gitea Sync Bot" + git config --global user.email "slhafzjw@slhaf.work" + + - name: 关闭全局 SSL 校验 + run: git config --global http.sslVerify false + + - name: Clone from Gitea (mirror) + run: | + git clone --mirror \ + https://${{ secrets.GITEA_USER }}:${{ secrets.GITEA_TOKEN }}@${{ secrets.GITEA_URL }} \ + gitea-mirror + + - name: Push to GitHub + run: | + cd gitea-mirror + # 明确推到名为 "github" 的 remote + git remote add github \ + https://${{ github.repository_owner }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git + git push --mirror github \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..9d120ee3 --- /dev/null +++ b/README.md @@ -0,0 +1,158 @@ +# Partner +以多模型协作为基础, 具备结构化记忆能力、支持多用户同上下文窗口, 支持可推断任务交互与调度(规划中)的智能体系统 + +## 核心结构 + +### 结构化记忆系统 +> 构建以**主题树+记忆切片**为基础的记忆图谱。 + +单个主题节点下存在多级子主题。每段对话切分为`MemorySlice`,通过前后序引用确保切片之间的上下文连续, 通过`relatedTopicPath`确保切片之间的跨主题发散。同一天的所有切片将聚合为`MemoryNode`(记忆节点)的形式挂载到主题节点。除此之外,每个记忆节点还将按照日期进行索引。 + +### 多用户会话管理 +> 构建区分用户的单上下文窗口、多用户会话的管理机制 + +## 模块实现 +- 预处理模块: `Preprocessor` +- 记忆模块 + - 记忆选择模块: `MemorySelector` + - 主题提取模块: `MemorySelectExtractor` + - 切片评估模块: `SliceSelectEvaluator` + - 记忆更新模块: `MemoryUpdater` + - 记忆总结模块: `MemorySummarizer` + - 静态记忆提取模块: `StaticMemoryExtractor` +- 主对话模块: `CoreModel` + +## 当前问题 +- 角色设定机制会导致对于所有用户采用同一种语气回应。 +- 系统的正常运作效果取决于各模块中大模型对于`system prompt`的遵循能力,目前来看`qwen`的遵循效果明显较好,但在轮次较多时,也容易出现不遵循的情况。正在尝试通过临时的`system prompt`进行强化。 +- 记忆系统有时会出现空指针异常,但好像不影响整体运行...但肯定要修的,就是有点不好找。 +- 各模块(尤其是记忆更新模块)的身份感缺失,进行主题路径生成、切片摘要时需确保以Partner的视角执行操作。 +- 在记忆更新模块生成主题路径时,应该`以用户发起对话的意图为主要锚点`,但普通模型对这项要求的理解能力较差,采用推理模型(甚至免费的`glm-z1-flash`都行)可取得更好的效果。 + +## 后续规划 + +### 短期规划 +- [ ] 调整内部各模块的提示词的“身份视角”,确保统一实现,以免造成记忆系统中的身份割裂。 +- [ ] 当前`MemoryGraph`承担职责较重,已远超原`记忆图谱`的职责,需要进行拆分重构。(或许可以叫`MemoryCore`吧) +- [ ] 看看是否需要将主模型的对话职责进行分离,用来减少LLM因不遵循`system prompt`带来的影响,但这应该会是规模较大的重构()。 +- [ ] 实现身份感知模块(用户识别、熟悉度判断、记忆片段检索、人物画像、对话口吻调整)。 +- [ ] 实现流式输出,同时在各模块执行时可向客户端返回回调信息,优化使用体验。(现在用的是`websocket`与客户端通信, 应该实现这点会简单些) +- [ ] 踩坑。 + +### 长期规划 +- [ ] 实现角色演进机制 +- [ ] 实现任务调度模块(主动调度、意图推断、定时调度) + +## 目录结构 + +``` +main +├── java +│ └── work +│ └── slhaf +│ ├── agent +│ │ ├── Agent.java +│ │ ├── common +│ │ │ ├── chat +│ │ │ │ ├── ChatClient.java +│ │ │ │ ├── constant +│ │ │ │ │ └── ChatConstant.java +│ │ │ │ └── pojo +│ │ │ │ ├── ChatBody.java +│ │ │ │ ├── ChatResponse.java +│ │ │ │ ├── Message.java +│ │ │ │ ├── MetaMessage.java +│ │ │ │ └── PrimaryChatResponse.java +│ │ │ ├── config +│ │ │ │ ├── Config.java +│ │ │ │ ├── ModelConfig.java +│ │ │ │ ├── ModuleConfig.java +│ │ │ │ └── WebSocketConfig.java +│ │ │ ├── model +│ │ │ │ ├── ModelConstant.java +│ │ │ │ └── Model.java +│ │ │ ├── monitor +│ │ │ │ └── DebugMonitor.java +│ │ │ ├── pojo +│ │ │ │ └── PersistableObject.java +│ │ │ └── util +│ │ │ └── ExtractUtil.java +│ │ ├── core +│ │ │ ├── interaction +│ │ │ │ ├── data +│ │ │ │ │ ├── InteractionContext.java +│ │ │ │ │ ├── InteractionInputData.java +│ │ │ │ │ └── InteractionOutputData.java +│ │ │ │ ├── InputReceiver.java +│ │ │ │ ├── InteractionModule.java +│ │ │ │ ├── InteractionModulesLoader.java +│ │ │ │ ├── InteractionThreadPoolExecutor.java +│ │ │ │ └── TaskCallback.java +│ │ │ ├── InteractionHub.java +│ │ │ ├── memory +│ │ │ │ ├── exception +│ │ │ │ │ ├── NullSliceListException.java +│ │ │ │ │ ├── UnExistedDateIndexException.java +│ │ │ │ │ └── UnExistedTopicException.java +│ │ │ │ ├── MemoryGraph.java +│ │ │ │ ├── MemoryManager.java +│ │ │ │ ├── node +│ │ │ │ │ ├── MemoryNode.java +│ │ │ │ │ └── TopicNode.java +│ │ │ │ └── pojo +│ │ │ │ ├── MemoryResult.java +│ │ │ │ ├── MemorySlice.java +│ │ │ │ ├── MemorySliceResult.java +│ │ │ │ └── User.java +│ │ │ ├── module +│ │ │ │ └── CoreModel.java +│ │ │ └── session +│ │ │ └── SessionManager.java +│ │ ├── gateway +│ │ │ ├── AgentWebSocketServer.java +│ │ │ └── MessageSender.java +│ │ ├── modules +│ │ │ ├── memory +│ │ │ │ ├── selector +│ │ │ │ │ ├── evaluator +│ │ │ │ │ │ ├── data +│ │ │ │ │ │ │ ├── EvaluatorBatchInput.java +│ │ │ │ │ │ │ ├── EvaluatorInput.java +│ │ │ │ │ │ │ ├── EvaluatorResult.java +│ │ │ │ │ │ │ └── SliceSummary.java +│ │ │ │ │ │ └── SliceSelectEvaluator.java +│ │ │ │ │ ├── extractor +│ │ │ │ │ │ ├── data +│ │ │ │ │ │ │ ├── ExtractorInput.java +│ │ │ │ │ │ │ ├── ExtractorMatchData.java +│ │ │ │ │ │ │ └── ExtractorResult.java +│ │ │ │ │ │ └── MemorySelectExtractor.java +│ │ │ │ │ └── MemorySelector.java +│ │ │ │ └── updater +│ │ │ │ ├── MemoryUpdater.java +│ │ │ │ ├── static_extractor +│ │ │ │ │ ├── data +│ │ │ │ │ │ └── StaticMemoryExtractInput.java +│ │ │ │ │ └── StaticMemoryExtractor.java +│ │ │ │ └── summarizer +│ │ │ │ ├── data +│ │ │ │ │ ├── SummarizeInput.java +│ │ │ │ │ └── SummarizeResult.java +│ │ │ │ └── MemorySummarizer.java +│ │ │ ├── preprocess +│ │ │ │ └── PreprocessExecutor.java +│ │ │ ├── task +│ │ │ │ ├── data +│ │ │ │ │ └── TaskData.java +│ │ │ │ ├── TaskEvaluator.java +│ │ │ │ ├── TaskExecutor.java +│ │ │ │ └── TaskScheduler.java +│ │ │ └── topic +│ │ └── shared +│ │ └── memory +│ │ └── EvaluatedSlice.java +│ └── Main.java +└── resources + └── logback.xml +``` + diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml new file mode 100644 index 00000000..84a10b19 --- /dev/null +++ b/dependency-reduced-pom.xml @@ -0,0 +1,86 @@ + + + 4.0.0 + work.slhaf + Partner + 0.5.0 + + + + maven-jar-plugin + 3.2.0 + + + + work.slhaf.Main + + + + + + maven-shade-plugin + 3.4.1 + + + package + + shade + + + + + work.slhaf.Main + + + + + + + + + + + org.projectlombok + lombok + 1.18.36 + provided + + + junit + junit + 4.13.2 + test + + + hamcrest-core + org.hamcrest + + + + + org.junit.jupiter + junit-jupiter + RELEASE + test + + + junit-jupiter-api + org.junit.jupiter + + + junit-jupiter-params + org.junit.jupiter + + + junit-jupiter-engine + org.junit.jupiter + + + + + + 21 + 21 + UTF-8 + + diff --git a/pom.xml b/pom.xml index 151ea062..b75d12fd 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ work.slhaf Partner - 1.0-SNAPSHOT + 0.5.0 21 @@ -47,18 +47,6 @@ RELEASE test - - org.junit.jupiter - junit-jupiter - RELEASE - test - - - org.junit.jupiter - junit-jupiter - RELEASE - test - org.slf4j slf4j-api @@ -76,4 +64,42 @@ + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + + work.slhaf.Main + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.4.1 + + + package + + shade + + + + + work.slhaf.Main + + + + + + + + + \ No newline at end of file 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 0f2b0be5..1f701411 100644 --- a/src/main/java/work/slhaf/agent/common/config/Config.java +++ b/src/main/java/work/slhaf/agent/common/config/Config.java @@ -16,6 +16,8 @@ import work.slhaf.agent.modules.task.TaskEvaluator; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.List; import java.util.Scanner; @@ -24,9 +26,11 @@ import java.util.Scanner; public class Config { private static final String CONFIG_FILE_PATH = "./config/config.json"; + private static final String LOG_FILE_PATH = "./data/log"; private static Config config; private String agentId; + private String basicCharacter; private WebSocketConfig webSocketConfig; @@ -47,6 +51,11 @@ public class Config { System.out.print("输入智能体名称: "); config.setAgentId(scanner.nextLine()); + System.out.print("输入智能体基础角色设定: "); + config.setBasicCharacter(scanner.nextLine()); + + System.out.println("(注意! 设定角色之后修改主配置文件将不会影响现有记忆,除非同时更换agentId)"); + System.out.println("\r\n--------模型配置--------\r\n"); generateModelConfig(scanner); @@ -56,15 +65,41 @@ public class Config { System.out.println("\r\n--------模块链配置--------\r\n"); generatePipelineConfig(); + boolean launchOrNot = getLaunchOrNot(scanner); + //保存配置文件 String str = JSONUtil.toJsonPrettyStr(config); FileUtils.writeStringToFile(file, str, StandardCharsets.UTF_8); log.info("配置已保存"); + + if (!launchOrNot) { + System.exit(0); + } } + config.generateCommonDirs(); } return config; } + private void generateCommonDirs() throws IOException { + Files.createDirectories(Paths.get(LOG_FILE_PATH)); + } + + private static boolean getLaunchOrNot(Scanner scanner) { + System.out.print("是否直接启动Partner?(y/n): "); + String input; + while (true) { + input = scanner.nextLine(); + if (input.equals("y")) { + return true; + }else if (input.equals("n")) { + return false; + }else { + System.out.println("请输入y或n"); + } + } + } + private static void generatePipelineConfig() { List moduleConfigList = List.of( new ModuleConfig(MemorySelector.class.getName(), ModuleConfig.Constant.INTERNAL, null), @@ -83,7 +118,7 @@ public class Config { } private static void generateModelConfig(Scanner scanner) throws IOException { - System.out.print("single model? y/n"); + System.out.print("各模块是否配置为同一个LLM? (y/n, 建议选'y',后续自行调整单独模块的配置): "); String input; while (true) { input = scanner.nextLine(); diff --git a/src/main/java/work/slhaf/agent/common/model/Model.java b/src/main/java/work/slhaf/agent/common/model/Model.java index d98d5f2f..80b9b4a4 100644 --- a/src/main/java/work/slhaf/agent/common/model/Model.java +++ b/src/main/java/work/slhaf/agent/common/model/Model.java @@ -20,7 +20,7 @@ public class Model { protected List messages; protected static void setModel(Config config, Model model, String model_key, String prompt) throws IOException, ClassNotFoundException { - MemoryGraph memoryGraph = MemoryGraph.getInstance(config.getAgentId()); + MemoryGraph memoryGraph = MemoryGraph.getInstance(config.getAgentId(), config.getBasicCharacter()); ModelConfig modelConfig = ModelConfig.load(model_key); if (memoryGraph.getModelPrompt().containsKey(model_key)) { model.setPrompt(memoryGraph.getModelPrompt().get(model_key)); 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 4f5f81a0..5f12d4ba 100644 --- a/src/main/java/work/slhaf/agent/common/model/ModelConstant.java +++ b/src/main/java/work/slhaf/agent/common/model/ModelConstant.java @@ -75,7 +75,12 @@ public class ModelConstant { 3. 回应应自然衔接,适配后续可能拼接的上下文或约束 4. 输出字段固定为`text`,但内容可根据上下文扩展 5. 若text与memory_slices等扩展字段无关,应完全忽略 - 6. 请确保你对每一轮对话都只针对当前输入用户作出回应,保持多用户上下文隔离的准确性 + 6. 请确保你对每一轮对话都只针对当前输入用户且只根据当前用户之前的消息记录作出回应,保持多用户上下文隔离的准确性。必要情况可从其他用户的消息记录中补充知识背景。 + 7. 若character字段中的角色设定符合生效规则,应尽最大程度保持角色对话自然,符合人类对话习惯, 不需要表现的过于主动,保持正常的人类对话状态 + 8. 不要在意其他消息记录中你回应的格式,务必严格确保本次回应格式如下,且能根据下文中的额外模块对应的输出字段进行调整: + { + "text": "响应内容" + } > 注意! > 以下模块可能会追加更多内容限制或上下文提示,请确保你的回答能够自然兼容这些后续拼接的内容,并调整输出格式。 diff --git a/src/main/java/work/slhaf/agent/core/InteractionHub.java b/src/main/java/work/slhaf/agent/core/InteractionHub.java index 72b9e422..831eda4d 100644 --- a/src/main/java/work/slhaf/agent/core/InteractionHub.java +++ b/src/main/java/work/slhaf/agent/core/InteractionHub.java @@ -8,7 +8,6 @@ import work.slhaf.agent.core.interaction.InteractionModulesLoader; 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.module.CoreModel; import work.slhaf.agent.modules.preprocess.PreprocessExecutor; import work.slhaf.agent.modules.task.TaskScheduler; @@ -25,7 +24,6 @@ public class InteractionHub { @ToString.Exclude private TaskCallback callback; private CoreModel coreModel; - private MemoryManager memoryManager; private TaskScheduler taskScheduler; private List interactionModules; 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 c4172281..bb58b5e3 100644 --- a/src/main/java/work/slhaf/agent/core/memory/MemoryGraph.java +++ b/src/main/java/work/slhaf/agent/core/memory/MemoryGraph.java @@ -20,6 +20,7 @@ import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.*; @@ -119,7 +120,7 @@ public class MemoryGraph extends PersistableObject { */ private Set selectedSlices; - public MemoryGraph(String id) { + public MemoryGraph(String id, String basicCharacter) { this.id = id; this.topicNodes = new HashMap<>(); this.existedTopics = new HashMap<>(); @@ -133,13 +134,11 @@ public class MemoryGraph extends PersistableObject { this.userDialogMap = new ConcurrentHashMap<>(); // this.currentCompressedSessionContext = new ArrayList<>(); this.dialogMap = new HashMap<>(); - this.character = """ - 实话实说,不做糖衣炮弹。 采取前瞻性的观点。 始终保持尊重。 乐于分享明确的观点。 保持轻松、随和。 直奔主题。 务实至上。 勇于创新,打破常规思维。使用中文回答所有问题。 - """; + this.character = basicCharacter; this.dateIndex = new HashMap<>(); } - public static MemoryGraph getInstance(String id) throws IOException, ClassNotFoundException { + public static MemoryGraph getInstance(String id, String basicCharacter) throws IOException, ClassNotFoundException { // 检查存储目录是否存在,不存在则创建 createStorageDirectory(); if (memoryGraph == null) { @@ -148,7 +147,7 @@ public class MemoryGraph extends PersistableObject { memoryGraph = deserialize(id); } else { FileUtils.createParentDirectories(filePath.toFile().getParentFile()); - memoryGraph = new MemoryGraph(id); + memoryGraph = new MemoryGraph(id,basicCharacter); memoryGraph.serialize(); } log.info("MemoryGraph注册完毕..."); @@ -158,13 +157,18 @@ public class MemoryGraph extends PersistableObject { } public void serialize() throws IOException { - Path filePath = getFilePath(this.id); + //先写入到临时文件,如果正常写入则覆盖原文件 + Path filePath = getFilePath(this.id + "-temp"); Files.createDirectories(Path.of(STORAGE_DIR)); - try (ObjectOutputStream oos = new ObjectOutputStream( - new FileOutputStream(filePath.toFile()))) { + try { + ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath.toFile())); oos.writeObject(this); - log.info("MemoryGraph 已保存到: {}", filePath); + oos.close(); + Path path = getFilePath(this.id); + Files.move(filePath, path, StandardCopyOption.REPLACE_EXISTING); + log.info("MemoryGraph 已保存到: {}", path); } catch (IOException e) { + Files.delete(filePath); log.error("序列化保存失败: {}", e.getMessage()); } } 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 996dfb1b..315429da 100644 --- a/src/main/java/work/slhaf/agent/core/memory/MemoryManager.java +++ b/src/main/java/work/slhaf/agent/core/memory/MemoryManager.java @@ -36,10 +36,10 @@ public class MemoryManager { if (memoryManager == null) { Config config = Config.getConfig(); memoryManager = new MemoryManager(); - memoryManager.setMemoryGraph(MemoryGraph.getInstance(config.getAgentId())); + memoryManager.setMemoryGraph(MemoryGraph.getInstance(config.getAgentId(), config.getBasicCharacter())); memoryManager.setActivatedSlices(new HashMap<>()); memoryManager.setShutdownHook(); - log.info("MemoryManager注册完毕..."); + log.info("[MemoryManager] MemoryManager注册完毕..."); } return memoryManager; } @@ -48,9 +48,9 @@ public class MemoryManager { Runtime.getRuntime().addShutdownHook(new Thread(() -> { try { memoryManager.save(); - log.info("MemoryGraph已保存"); + log.info("[MemoryManager] MemoryGraph已保存"); } catch (IOException e) { - log.error("保存MemoryGraph失败: ", e); + log.error("[MemoryManager] 保存MemoryGraph失败: ", e); } })); } @@ -120,6 +120,7 @@ public class MemoryManager { sliceInsertLock.lock(); List topicPathList = Arrays.stream(topicPath.split("->")).toList(); memoryGraph.insertMemory(topicPathList, memorySlice); + log.debug("[MemoryManager] 插入切片: {}, 路径: {}", memorySlice, topicPath); sliceInsertLock.unlock(); } @@ -140,4 +141,18 @@ public class MemoryManager { public void save() throws IOException { memoryGraph.serialize(); } + + public void updateActivatedSlices(String userId, List memorySlices) { + memoryManager.getActivatedSlices().put(userId, memorySlices); + log.debug("[MemoryManager] 已更新激活切片, userId: {}", userId); + } + + public User getUser(String id) { + for (User user : memoryGraph.getUsers()) { + if (user.getUuid().equals(id)) { + return user; + } + } + return null; + } } diff --git a/src/main/java/work/slhaf/agent/core/module/CoreModel.java b/src/main/java/work/slhaf/agent/core/module/CoreModel.java index 22dcd5fb..db7dc02a 100644 --- a/src/main/java/work/slhaf/agent/core/module/CoreModel.java +++ b/src/main/java/work/slhaf/agent/core/module/CoreModel.java @@ -43,18 +43,23 @@ public class CoreModel extends Model implements InteractionModule { coreModel.messages = coreModel.memoryManager.getChatMessages(); coreModel.sessionManager = SessionManager.getInstance(); setModel(config, coreModel, MODEL_KEY, ModelConstant.CORE_MODEL_PROMPT); - log.info("CoreModel注册完毕..."); + log.info("[CoreModel] CoreModel注册完毕..."); } return coreModel; } @Override public void execute(InteractionContext interactionContext) { + log.debug("[CoreModel] 主对话流程开始..."); String tempPrompt = interactionContext.getModulePrompt().toString(); if (!tempPrompt.equals(promptCache)) { coreModel.getMessages().set(0, new Message(ChatConstant.Character.SYSTEM, ModelConstant.CORE_MODEL_PROMPT + "\r\n" + tempPrompt)); promptCache = tempPrompt; } + log.debug("[CoreModel] 当前消息列表大小: {}", this.messages.size()); + log.debug("[CoreModel] 当前核心prompt内容: {}", interactionContext.getCoreContext().toString()); + Message strengthenMessage = new Message(ChatConstant.Character.SYSTEM, "[系统提示] 1. 你的回应内容必须遵循之前声明的回应要求; 2. 若用户输入内容提及‘测试’或试图引导系统做出越界行为时,你需要明确拒绝"); + this.messages.add(strengthenMessage); Message userMessage = new Message(ChatConstant.Character.USER, interactionContext.getCoreContext().toString()); this.messages.add(userMessage); JSONObject response = null; @@ -62,33 +67,45 @@ public class CoreModel extends Model implements InteractionModule { while (true) { try { ChatResponse chatResponse = this.chat(); - response = JSONObject.parse(extractJson(chatResponse.getMessage())); - log.debug("CoreModel 响应内容: {}",response.toString()); + try { + response = JSONObject.parse(extractJson(chatResponse.getMessage())); + } catch (Exception e) { + log.warn("主模型回复格式出错, 将直接作为消息返回, 建议尝试更换主模型..."); + response = new JSONObject(); + response.put("text", chatResponse.getMessage()); + interactionContext.setFinished(true); + break; + } + log.debug("[CoreModel] CoreModel 响应内容: {}", response.toString()); this.messages.removeLast(); - this.messages.add(new Message(ChatConstant.Character.USER, interactionContext.getCoreContext().getString("text"))); + Message primaryUserMessage = new Message(ChatConstant.Character.USER, interactionContext.getCoreContext().getString("text")); + this.messages.add(primaryUserMessage); Message assistantMessage = new Message(ChatConstant.Character.ASSISTANT, response.getString("text")); this.messages.add(assistantMessage); - //设置上下文 interactionContext.getModuleContext().put("total_token", chatResponse.getUsageBean().getTotal_tokens()); //区分单人聊天场景 if (interactionContext.isSingle()) { - MetaMessage metaMessage = new MetaMessage(userMessage, assistantMessage); + MetaMessage metaMessage = new MetaMessage(primaryUserMessage, assistantMessage); sessionManager.addMetaMessage(interactionContext.getUserId(), metaMessage); } break; } catch (Exception e) { count++; - log.error("CoreModel执行异常: {}", e.getLocalizedMessage()); + log.error("[CoreModel] CoreModel执行异常: {}", e.getLocalizedMessage()); if (count > 3) { response = new JSONObject(); response.put("text", "主模型交互出错: " + e.getLocalizedMessage()); interactionContext.setFinished(true); + this.messages.removeLast(); break; } } finally { + this.messages.remove(strengthenMessage); interactionContext.setCoreResponse(response); + log.debug("[CoreModel] 消息列表更新大小: {}", this.messages.size()); } } + log.debug("[CoreModel] 主对话流程结果: {}", interactionContext); } } diff --git a/src/main/java/work/slhaf/agent/core/session/SessionManager.java b/src/main/java/work/slhaf/agent/core/session/SessionManager.java index eeba68b2..9c31372a 100644 --- a/src/main/java/work/slhaf/agent/core/session/SessionManager.java +++ b/src/main/java/work/slhaf/agent/core/session/SessionManager.java @@ -12,6 +12,7 @@ import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -54,20 +55,22 @@ public class SessionManager extends PersistableObject { Runtime.getRuntime().addShutdownHook(new Thread(() -> { try { sessionManager.serialize(); - log.info("SessionManager 已保存"); + log.info("[SessionManager] SessionManager 已保存"); } catch (IOException e) { - log.error("保存 SessionManager 失败: ", e); + log.error("[SessionManager] 保存 SessionManager 失败: ", e); } })); } public void addMetaMessage(String userId, MetaMessage metaMessage) { + log.debug("[SessionManager] 当前会话历史: {}", singleMetaMessageMap); if (singleMetaMessageMap.containsKey(userId)) { singleMetaMessageMap.get(userId).add(metaMessage); } else { singleMetaMessageMap.put(userId, new java.util.ArrayList<>()); singleMetaMessageMap.get(userId).add(metaMessage); } + log.debug("[SessionManager] 会话历史更新: {}", singleMetaMessageMap); } public List unpackAndClear(String userId) { @@ -85,22 +88,27 @@ public class SessionManager extends PersistableObject { } public void serialize() throws IOException { - Path filePath = Paths.get(STORAGE_DIR, this.id + ".session"); + //先写入到临时文件,如果正常写入,则覆盖正式文件;否则删除临时文件 + Path filePath = getFilePath(this.id + "-temp"); Files.createDirectories(Path.of(STORAGE_DIR)); - try (ObjectOutputStream oos = new ObjectOutputStream( - new FileOutputStream(filePath.toFile()))) { + try { + ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath.toFile())); oos.writeObject(this); - log.info("SessionManager 已保存到: {}", filePath); + oos.close(); + Path path = getFilePath(this.id); + Files.move(filePath, path, StandardCopyOption.REPLACE_EXISTING); + log.info("[SessionManager] SessionManager 已保存到: {}", path); } catch (IOException e) { - log.error("序列化保存失败: {}", e.getMessage()); + Files.delete(filePath); + log.error("[SessionManager] 序列化保存失败: {}", e.getMessage()); } } private static SessionManager deserialize(String id) throws IOException, ClassNotFoundException { - Path filePath = Paths.get(STORAGE_DIR, id + ".session"); + Path filePath = getFilePath(id); try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath.toFile()))) { SessionManager sessionManager = (SessionManager) ois.readObject(); - log.info("SessionManager 已从文件加载: {}", filePath); + log.info("[SessionManager] SessionManager 已从文件加载: {}", filePath); return sessionManager; } } @@ -108,6 +116,10 @@ public class SessionManager extends PersistableObject { public void resetLastUpdatedTime() { lastUpdatedTime = System.currentTimeMillis(); } + + private static Path getFilePath(String id) { + return Paths.get(STORAGE_DIR, id + ".session"); + } } diff --git a/src/main/java/work/slhaf/agent/modules/memory/selector/MemorySelector.java b/src/main/java/work/slhaf/agent/modules/memory/selector/MemorySelector.java index 6c7daf9d..6e59e337 100644 --- a/src/main/java/work/slhaf/agent/modules/memory/selector/MemorySelector.java +++ b/src/main/java/work/slhaf/agent/modules/memory/selector/MemorySelector.java @@ -39,8 +39,8 @@ public class MemorySelector implements InteractionModule { }], "static_memory": "对于该用户的常识性记忆,如爱好、住处、生日", "dialog_map": { //近两日的与所有用户的对话缓存 - "2023-01-01T11:30": "发生了...与用户A...、用户B谈到...", - "2023-01-02T11:30": "发生了...与用户A...、用户B谈到..." + "2023-01-01T11:30": "用户a[dawgbi-dwa-ccc] 尝试分享生活点滴并营造氛围感", + "2023-01-02T11:30": "用户b[dawgbi-dwa-ccc] 尝试分享生活点滴并营造氛围感" } "user_dialog_map": { //与当前用户的近两日对话缓存 "2023-01-01T11:30": "与用户讨论了...", @@ -48,6 +48,11 @@ public class MemorySelector implements InteractionModule { } 无新增输出字段 + + ##注意 + a. 这些字段中可能出现的第一人称描述都是指"你",即当前用户正在对话的对象 + b. `dialog_map`和`user_dialog_map`中,值都将以`用户昵称[用户uuid]`开头,你需要正确区分不同用户 + """; private MemoryManager memoryManager; @@ -69,11 +74,12 @@ public class MemorySelector implements InteractionModule { @Override public void execute(InteractionContext interactionContext) throws IOException, ClassNotFoundException, InterruptedException { + log.debug("[MemorySelector] 记忆回溯流程开始..."); String userId =interactionContext.getUserId(); //获取主题路径 ExtractorResult extractorResult = memorySelectExtractor.execute(interactionContext); - log.debug("主题路径: {}",extractorResult); - if (extractorResult.isRecall() || extractorResult.getMatches().isEmpty()) { + if (extractorResult.isRecall() || !extractorResult.getMatches().isEmpty()) { + log.debug("[MemorySelector] 触发记忆回溯..."); //查找切片 List memoryResultList = new ArrayList<>(); setMemoryResultList(memoryResultList, extractorResult.getMatches(),userId); @@ -83,9 +89,10 @@ public class MemorySelector implements InteractionModule { .memoryResults(memoryResultList) .messages(memoryManager.getChatMessages()) .build(); + log.debug("[MemorySelector] 切片评估输入: {}",evaluatorInput); List memorySlices = sliceSelectEvaluator.execute(evaluatorInput); - memoryManager.getActivatedSlices().put(userId,memorySlices); - + log.debug("[MemorySelector] 切片评估结果: {}",memorySlices); + memoryManager.updateActivatedSlices(userId,memorySlices); } //设置上下文 @@ -95,6 +102,7 @@ public class MemorySelector implements InteractionModule { interactionContext.getCoreContext().put("user_dialog_map",memoryManager.getUserDialogMap(userId)); interactionContext.getModulePrompt().put("memory", modulePrompt); + log.debug("[MemorySelector] 记忆回溯结果: {}",interactionContext); } private void setMemoryResultList(List memoryResultList, List matches, String userId) throws IOException, ClassNotFoundException { @@ -109,7 +117,7 @@ public class MemorySelector implements InteractionModule { if (memoryResult == null) continue; memoryResultList.add(memoryResult); }catch (UnExistedDateIndexException | UnExistedTopicException e) { - log.error("不存在的记忆索引! 请尝试更换更合适的主题提取LLM!"); + log.error("[MemorySelector] 不存在的记忆索引! 请尝试更换更合适的主题提取LLM!"); } } //清理切片记录 diff --git a/src/main/java/work/slhaf/agent/modules/memory/selector/evaluator/SliceSelectEvaluator.java b/src/main/java/work/slhaf/agent/modules/memory/selector/evaluator/SliceSelectEvaluator.java index 31b5a8ef..2b743c8b 100644 --- a/src/main/java/work/slhaf/agent/modules/memory/selector/evaluator/SliceSelectEvaluator.java +++ b/src/main/java/work/slhaf/agent/modules/memory/selector/evaluator/SliceSelectEvaluator.java @@ -25,6 +25,7 @@ import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import static work.slhaf.agent.common.util.ExtractUtil.extractJson; @@ -55,15 +56,18 @@ public class SliceSelectEvaluator extends Model { } public List execute(EvaluatorInput evaluatorInput) throws InterruptedException { + log.debug("[SliceSelectEvaluator] 切片评估模块开始..."); List memoryResultList = evaluatorInput.getMemoryResults(); List> tasks = new ArrayList<>(); Queue queue = new ConcurrentLinkedDeque<>(); + AtomicInteger count = new AtomicInteger(0); for (MemoryResult memoryResult : memoryResultList) { - if (memoryResult.getMemorySliceResult().isEmpty() && memoryResult.getRelatedMemorySliceResult().isEmpty()){ + if (memoryResult.getMemorySliceResult().isEmpty() && memoryResult.getRelatedMemorySliceResult().isEmpty()) { continue; } tasks.add(() -> { - log.debug("切片评估..."); + int thisCount = count.incrementAndGet(); + log.debug("[SliceSelectEvaluator] 评估[{}]开始", thisCount); List sliceSummaryList = new ArrayList<>(); //映射查找键值 Map map = new HashMap<>(); @@ -74,8 +78,9 @@ public class SliceSelectEvaluator extends Model { .memory_slices(sliceSummaryList) .history(evaluatorInput.getMessages()) .build(); + log.debug("[SliceSelectEvaluator] 评估[{}]输入: {}", thisCount, batchInput); EvaluatorResult evaluatorResult = JSONObject.parseObject(extractJson(singleChat(JSONUtil.toJsonStr(batchInput)).getMessage()), EvaluatorResult.class); - log.debug("评估结果: {}", evaluatorResult); + log.debug("[SliceSelectEvaluator] 评估[{}]结果: {}", thisCount, evaluatorResult); for (Long result : evaluatorResult.getResults()) { SliceSummary sliceSummary = map.get(result); EvaluatedSlice evaluatedSlice = EvaluatedSlice.builder() @@ -85,14 +90,14 @@ public class SliceSelectEvaluator extends Model { queue.offer(evaluatedSlice); } } catch (Exception e) { - log.error("切片评估出现错误: {}", e.getLocalizedMessage()); + log.error("[SliceSelectEvaluator] 评估[{}]出现错误: {}", thisCount, e.getLocalizedMessage()); } return null; }); } executor.invokeAll(tasks, 30, TimeUnit.SECONDS); - + log.debug("[SliceSelectEvaluator] 评估模块结束, 输出队列: {}", queue); return queue.stream().toList(); } diff --git a/src/main/java/work/slhaf/agent/modules/memory/selector/extractor/MemorySelectExtractor.java b/src/main/java/work/slhaf/agent/modules/memory/selector/extractor/MemorySelectExtractor.java index 4836606b..2c6591ea 100644 --- a/src/main/java/work/slhaf/agent/modules/memory/selector/extractor/MemorySelectExtractor.java +++ b/src/main/java/work/slhaf/agent/modules/memory/selector/extractor/MemorySelectExtractor.java @@ -49,6 +49,7 @@ public class MemorySelectExtractor extends Model { } public ExtractorResult execute(InteractionContext context) { + log.debug("[MemorySelectExtractor] 主题提取模块开始..."); //结构化为指定格式 List chatMessages = new ArrayList<>(); List metaMessages = sessionManager.getSingleMetaMessageMap().get(context.getUserId()); @@ -61,22 +62,22 @@ public class MemorySelectExtractor extends Model { } } - List activatedMemorySlices = memoryManager.getActivatedSlices().get(context.getUserId()); - - ExtractorInput extractorInput = ExtractorInput.builder() - .text(context.getInput()) - .date(context.getDateTime().toLocalDate()) - .history(chatMessages) - .topic_tree(memoryManager.getTopicTree()) - .activatedMemorySlices(activatedMemorySlices) - .build(); - String responseStr = extractJson(singleChat(JSONUtil.toJsonPrettyStr(extractorInput)).getMessage()); - ExtractorResult extractorResult; try { + List activatedMemorySlices = memoryManager.getActivatedSlices().get(context.getUserId()); + ExtractorInput extractorInput = ExtractorInput.builder() + .text(context.getInput()) + .date(context.getDateTime().toLocalDate()) + .history(chatMessages) + .topic_tree(memoryManager.getTopicTree()) + .activatedMemorySlices(activatedMemorySlices) + .build(); + log.debug("[MemorySelectExtractor] 主题提取输入: {}", extractorInput); + String responseStr = extractJson(singleChat(JSONUtil.toJsonPrettyStr(extractorInput)).getMessage()); extractorResult = JSONObject.parseObject(responseStr, ExtractorResult.class); + log.debug("[MemorySelectExtractor] 主题提取结果: {}",extractorResult); } catch (Exception e) { - log.error("主题提取出错: {}", e.getLocalizedMessage()); + log.error("[MemorySelectExtractor] 主题提取出错: {}", e.getLocalizedMessage()); extractorResult = new ExtractorResult(); extractorResult.setRecall(false); extractorResult.setMatches(List.of()); diff --git a/src/main/java/work/slhaf/agent/modules/memory/updater/MemoryUpdater.java b/src/main/java/work/slhaf/agent/modules/memory/updater/MemoryUpdater.java index b3090644..1ef60ab4 100644 --- a/src/main/java/work/slhaf/agent/modules/memory/updater/MemoryUpdater.java +++ b/src/main/java/work/slhaf/agent/modules/memory/updater/MemoryUpdater.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.time.LocalDateTime; import java.util.*; import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -62,7 +63,7 @@ public class MemoryUpdater implements InteractionModule { private void setScheduledUpdater() { executor.execute(() -> { - log.info("记忆自动更新线程启动"); + log.info("[MemoryUpdater] 记忆自动更新线程启动"); while (!Thread.interrupted()) { try { long currentTime = System.currentTimeMillis(); @@ -72,20 +73,21 @@ public class MemoryUpdater implements InteractionModule { updateMemory(); //重置MemoryId sessionManager.refreshMemoryId(); - log.info("记忆更新: 自动触发"); + log.info("[MemoryUpdater] 记忆更新: 自动触发"); } Thread.sleep(SCHEDULED_UPDATE_INTERVAL); } catch (Exception e) { - log.error("记忆自动更新线程出错: {}", e.getLocalizedMessage()); + log.error("[MemoryUpdater] 记忆自动更新线程出错: {}", e.getLocalizedMessage()); } } - log.info("记忆自动更新线程结束"); + log.info("[MemoryUpdater] 记忆自动更新线程结束"); }); } @Override public void execute(InteractionContext interactionContext) { if (interactionContext.isFinished()) { + log.warn("[MemoryUpdater] 流程强制结束, 不触发记忆被动更新机制"); return; } executor.execute(() -> { @@ -93,18 +95,18 @@ public class MemoryUpdater implements InteractionModule { JSONObject moduleContext = interactionContext.getModuleContext(); if (moduleContext.getIntValue("total_token") > 24000) { try { + log.debug("[MemoryUpdater] 记忆更新: token超限"); updateMemory(); - log.info("记忆更新: token超限"); } catch (Exception e) { - log.error("记忆更新线程出错: {}", e.getLocalizedMessage()); + log.error("[MemoryUpdater] 记忆更新线程出错: {}", e.getLocalizedMessage()); } } }); sessionManager.resetLastUpdatedTime(); - } - private void updateMemory() throws IOException, ClassNotFoundException { + private void updateMemory() { + log.debug("[MemoryUpdater] 记忆更新流程开始..."); HashMap singleMemorySummary = new HashMap<>(); //更新单聊记忆以及该场景中对应的确定性记忆,同时从chatMessages中去掉单聊记忆 updateSingleChatSlices(singleMemorySummary); @@ -118,10 +120,12 @@ public class MemoryUpdater implements InteractionModule { //此时chatMessages中不再包含单聊记录,直接执行摘要以及切片插入 //对剩下的多人聊天记录进行进行摘要 executor.execute(() -> { + log.debug("[MemoryUpdater] 多人聊天记忆更新流程开始..."); try { List chatMessages = new ArrayList<>(memoryManager.getChatMessages()); chatMessages.removeFirst(); if (!chatMessages.isEmpty()) { + log.debug("[MemoryUpdater] 存在多人聊天记录, 流程正常进行..."); //以第一条user对应的id为发起用户 Pattern pattern = Pattern.compile(USERID_REGEX); Matcher matcher = pattern.matcher(chatMessages.getFirst().getContent()); @@ -129,20 +133,25 @@ public class MemoryUpdater implements InteractionModule { throw new RuntimeException("未匹配到 userId!"); } String userId = matcher.group(1); - SummarizeResult summarizeResult = memorySummarizer.execute(new SummarizeInput(chatMessages, memoryManager.getTopicTree())); + SummarizeInput summarizeInput = new SummarizeInput(chatMessages, memoryManager.getTopicTree()); + log.debug("[MemoryUpdater] 多人聊天记忆更新-总结流程-输入: {}", summarizeInput); + SummarizeResult summarizeResult = memorySummarizer.execute(summarizeInput); + log.debug("[MemoryUpdater] 多人聊天记忆更新-总结流程-输出: {}", summarizeResult); MemorySlice memorySlice = getMemorySlice(userId, summarizeResult, chatMessages); //设置involvedUserId setInvolvedUserId(userId, memorySlice, chatMessages); memoryManager.insertSlice(memorySlice, summarizeResult.getTopicPath()); - if (!singleMemorySummary.isEmpty()) { - memoryManager.updateDialogMap(LocalDateTime.now(), summarizeResult.getSummary()); - } - }else{ - memoryManager.updateDialogMap(LocalDateTime.now(),memorySummarizer.executeTotalSummary(singleMemorySummary)); + memoryManager.updateDialogMap(LocalDateTime.now(), summarizeResult.getSummary()); + + } else { + log.debug("[MemoryUpdater] 不存在多人聊天记录, 将以单聊总结为对话缓存的主要输入: {}", singleMemorySummary); + memoryManager.updateDialogMap(LocalDateTime.now(), memorySummarizer.executeTotalSummary(singleMemorySummary)); } + log.debug("[MemoryUpdater] 对话缓存更新完毕"); + log.debug("[MemoryUpdater] 多人聊天记忆更新流程结束..."); } catch (IOException | ClassNotFoundException | InterruptedException e) { - log.error("多人场景记忆更新失败: {}", e.getLocalizedMessage()); + log.error("[MemoryUpdater] 多人场景记忆更新失败: {}", e.getLocalizedMessage()); } }); } @@ -176,41 +185,54 @@ public class MemoryUpdater implements InteractionModule { private void updateSingleChatSlices(HashMap singleMemorySummary) { + log.debug("[MemoryUpdater] 单聊记忆更新流程开始..."); //更新单聊记忆,同时从chatMessages中去掉单聊记忆 Set userIdSet = new HashSet<>(sessionManager.getSingleMetaMessageMap().keySet()); List> tasks = new ArrayList<>(); //多人聊天? + AtomicInteger count = new AtomicInteger(0); for (String id : userIdSet) { List messages = sessionManager.unpackAndClear(id); tasks.add(() -> { + int thisCount = count.incrementAndGet(); + log.debug("[MemoryUpdater] 单聊记忆[{}]更新: {}", thisCount, id); try { //单聊记忆更新 - SummarizeResult summarizeResult = memorySummarizer.execute(new SummarizeInput(messages, memoryManager.getTopicTree())); + SummarizeInput summarizeInput = new SummarizeInput(messages, memoryManager.getTopicTree()); + log.debug("[MemoryUpdater] 单聊记忆[{}]更新-总结流程-输入: {}", thisCount, summarizeInput); + SummarizeResult summarizeResult = memorySummarizer.execute(summarizeInput); + log.debug("[MemoryUpdater] 单聊记忆[{}]更新-总结流程-输出: {}", thisCount, summarizeResult); MemorySlice memorySlice = getMemorySlice(id, summarizeResult, messages); //插入时userDialogMap已经进行更新 memoryManager.insertSlice(memorySlice, summarizeResult.getTopicPath()); //从chatMessages中移除单聊记录 memoryManager.cleanMessage(messages); //添加至singleMemorySummary - singleMemorySummary.put(id, summarizeResult.getSummary()); + String key = memoryManager.getUser(id).getNickName() + "[" + id + "]"; + singleMemorySummary.put(key, summarizeResult.getSummary()); + log.debug("[MemoryUpdater] 单聊记忆[{}]更新成功: ", thisCount); } catch (Exception e) { - log.error("单聊记忆更新出错: ", e); + log.error("[MemoryUpdater] 单聊记忆[{}]更新出错: ", thisCount, e); } return null; }); tasks.add(() -> { + log.debug("[MemoryUpdater] 静态记忆更新开始..."); StaticMemoryExtractInput input = StaticMemoryExtractInput.builder() .userId(id) .messages(messages) .existedStaticMemory(memoryManager.getStaticMemory(id)) .build(); + log.debug("[MemoryUpdater] 静态记忆更新输入: {}", input); Map staticMemoryResult = staticMemoryExtractor.execute(input); + log.debug("[MemoryUpdater] 静态记忆更新结果: {}", staticMemoryResult); memoryManager.insertStaticMemory(id, staticMemoryResult); return null; }); } executor.invokeAll(tasks); + log.debug("[MemoryUpdater] 单聊记忆更新结束..."); } private MemorySlice getMemorySlice(String userId, SummarizeResult summarizeResult, List chatMessages) { diff --git a/src/main/java/work/slhaf/agent/modules/memory/updater/summarizer/MemorySummarizer.java b/src/main/java/work/slhaf/agent/modules/memory/updater/summarizer/MemorySummarizer.java index 3aa532e5..dd7fa10e 100644 --- a/src/main/java/work/slhaf/agent/modules/memory/updater/summarizer/MemorySummarizer.java +++ b/src/main/java/work/slhaf/agent/modules/memory/updater/summarizer/MemorySummarizer.java @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import static work.slhaf.agent.common.util.ExtractUtil.extractJson; @@ -62,25 +63,33 @@ public class MemorySummarizer extends Model { } private SummarizeResult multiSummarizeExecute(String prompt, String messageStr) { + log.debug("[MemorySummarizer] 整体摘要开始..."); ChatResponse response = chatClient.runChat(List.of(new Message(ChatConstant.Character.SYSTEM, prompt), new Message(ChatConstant.Character.USER, messageStr))); + log.debug("[MemorySummarizer] 整体摘要结果: {}",response); return JSONObject.parseObject(extractJson(response.getMessage()), SummarizeResult.class); } private void singleMessageSummarize(List chatMessages) { + log.debug("[MemorySummarizer] 长文本摘要开始..."); List> tasks = new ArrayList<>(); + AtomicInteger counter = new AtomicInteger(); for (Message chatMessage : chatMessages) { if (chatMessage.getRole().equals(ChatConstant.Character.ASSISTANT)) { String content = chatMessage.getContent(); if (chatMessage.getContent().length() > 500) { tasks.add(() -> { + int thisCount = counter.incrementAndGet(); + log.debug("[MemorySummarizer] 长文本摘要[{}]启动",thisCount); chatMessage.setContent(singleSummarizeExecute(prompts.getFirst(), JSONObject.of("content", content).toString())); + log.debug("[MemorySummarizer] 长文本摘要[{}]完成",thisCount); return null; }); } } } executor.invokeAll(tasks, 30, TimeUnit.SECONDS); + log.debug("[MemorySummarizer] 长文本摘要结束"); } private @NonNull String singleSummarizeExecute(String prompt, String content) { @@ -150,7 +159,6 @@ public class MemorySummarizer extends Model { DialogueTopicMapper 提示词 功能说明 分析对话内容并生成最深为7层的多层次主题路径,支持智能扩展主题树结构,根据用户意图动态调整路径生成策略。 - 在保证符合以下要求的同时尽快输出 输入字段说明 @@ -198,6 +206,8 @@ public class MemorySummarizer extends Model { └── 跟团游 处理流程 + 0. 明确身份阶段: + a. 需要以assistant的视角为分析视角 1. 意图分析阶段: a. 判断对话类型(咨询/分享/讨论) b. 标记关键实体和动作 @@ -229,6 +239,9 @@ public class MemorySummarizer extends Model { ], "isPrivate": false } + + ## 最终注意事项 + 在进行主题提取、对对话内容摘要为务必从assistant的视角出发,可在摘要结果中,将assistant的身份当作第一人称:“我” """; public static final String TOTAL_SUMMARIZE_PROMPT = """ @@ -238,7 +251,7 @@ public class MemorySummarizer extends Model { 输入字段说明 • 输入数据为JSON对象: - - key: 用户uuid(需在输出中保留) + - key: 格式为`用户昵称[用户uuid]`(需在输出中保留) - value: 该用户的对话摘要文本(需要处理的内容) 输出规则 @@ -253,7 +266,7 @@ public class MemorySummarizer extends Model { • 保留原始对话的关键事实信息 • 对重复信息进行合并处理 3. 格式要求: - • 每个用户摘要以"用户[uuid]:"开头 + • 每个用户摘要以"用户昵称[用户uuid]:"开头 • 不同用户摘要间用分号分隔 • 末尾不添加总结性陈述 @@ -273,16 +286,16 @@ public class MemorySummarizer extends Model { 完整示例 示例: - 输入:{ - "aaa-111": "需要购买笔记本电脑,预算5000左右,主要用于办公", - "bbb-222": "想买游戏本,预算8000-10000,要能运行3A大作", - "ccc-333": "咨询轻薄本推荐,经常出差使用" + 输入:{ //注,实际情况中每条用户的单独摘要可能更长,多达几百字,此时需要在保证信息完整的同时进行摘要 + "adw[aaa-111]": "需要购买笔记本电脑,预算5000左右,主要用于办公。", + "xyz[bbb-222]": "想买游戏本,预算8000-10000,要能运行3A大作", + "小王[ccc-333]": "咨询轻薄本推荐,经常出差使用" } 输出:{ "content": " - 用户[aaa-111]:需要5000元左右的办公笔记本; - 用户[bbb-222]:寻求8000-10000元的游戏本,要求能运行3A大作; - 用户[ccc-333]:咨询适合出差使用的轻薄本" + adw[aaa-111]:需要5000元左右的办公笔记本; + xyz[bbb-222]:寻求8000-10000元的游戏本,要求能运行3A大作; + 小王[ccc-333]:咨询适合出差使用的轻薄本" } 特殊处理 @@ -294,7 +307,7 @@ public class MemorySummarizer extends Model { } 3. 当用户uuid包含特殊字符时: • 保持原始uuid格式不做修改 - • 示例:用户[xxx-ddssss-xx]:内容摘要 + • 示例:用户昵称[xxx-ddssss-xx]:内容摘要 """; } } 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 4d3d6259..052a9fac 100644 --- a/src/main/java/work/slhaf/agent/modules/preprocess/PreprocessExecutor.java +++ b/src/main/java/work/slhaf/agent/modules/preprocess/PreprocessExecutor.java @@ -2,6 +2,7 @@ package work.slhaf.agent.modules.preprocess; import com.alibaba.fastjson2.JSONObject; import lombok.Data; +import lombok.extern.slf4j.Slf4j; import work.slhaf.agent.core.interaction.data.InteractionContext; import work.slhaf.agent.core.interaction.data.InteractionInputData; import work.slhaf.agent.core.memory.MemoryManager; @@ -12,6 +13,7 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @Data +@Slf4j public class PreprocessExecutor { private static PreprocessExecutor preprocessExecutor; @@ -44,6 +46,7 @@ public class PreprocessExecutor { } private InteractionContext getInteractionContext(InteractionInputData inputData) { + log.debug("[PreprocessExecutor] 预处理原始输入: {}",inputData); InteractionContext context = new InteractionContext(); String userId = memoryManager.getUserId(inputData.getUserInfo(), inputData.getUserNickName()); @@ -70,6 +73,8 @@ public class PreprocessExecutor { context.setSingle(inputData.isSingle()); context.setFinished(false); + + log.debug("[PreprocessExecutor] 预处理结果: {}",context); return context; } } diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 00000000..b84fc8dd --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,26 @@ + + + + ./data/log/partner.log + + %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n + + + ./data/log/partner.%d{yyyy-MM-dd}.log + 30 + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/memory/AITest.java b/src/test/java/memory/AITest.java index 458436e0..d9009514 100644 --- a/src/test/java/memory/AITest.java +++ b/src/test/java/memory/AITest.java @@ -1,7 +1,6 @@ package memory; import cn.hutool.json.JSONUtil; -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; @@ -14,7 +13,7 @@ import java.util.HashMap; import java.util.List; public class AITest { - @Test +// @Test public void topicExtractorTest() { String input = """ { @@ -48,7 +47,7 @@ public class AITest { run(input, ModelConstant.SELECT_EXTRACTOR_PROMPT); } - @Test +// @Test public void sliceEvaluatorTest(){ String input = """ { @@ -98,7 +97,7 @@ public class AITest { run(input,ModelConstant.SLICE_EVALUATOR_PROMPT); } - @Test +// @Test public void coreModelTest(){ String input = """ { @@ -128,7 +127,7 @@ public class AITest { run(input,ModelConstant.CORE_MODEL_PROMPT + "\r\n" + MemorySelector.modulePrompt); } - @Test +// @Test public void map2jsonTest(){ HashMap map = new HashMap<>(); map.put(LocalDate.now(),"hello"); diff --git a/src/test/java/memory/InsertTest.java b/src/test/java/memory/InsertTest.java index 4bace139..56405680 100644 --- a/src/test/java/memory/InsertTest.java +++ b/src/test/java/memory/InsertTest.java @@ -1,7 +1,5 @@ package memory; -import org.junit.Before; -import org.junit.Test; import work.slhaf.agent.core.memory.MemoryGraph; import work.slhaf.agent.core.memory.node.MemoryNode; import work.slhaf.agent.core.memory.node.TopicNode; @@ -19,15 +17,16 @@ import static org.junit.Assert.*; public class InsertTest { private MemoryGraph memoryGraph; private final String testId = "test_insert"; + String basicCharacter = ""; - @Before +// @Before public void setUp() { - memoryGraph = new MemoryGraph(testId); + memoryGraph = new MemoryGraph(testId, basicCharacter); memoryGraph.setTopicNodes(new HashMap<>()); memoryGraph.setExistedTopics(new HashMap<>()); } - @Test +// @Test public void testInsertMemory_NewRootTopic() throws IOException, ClassNotFoundException { // 准备测试数据 List topicPath = new LinkedList<>(Arrays.asList("Programming", "Java", "Collections")); @@ -53,7 +52,7 @@ public class InsertTest { assertEquals(slice, memoryNode.loadMemorySliceList().get(0)); } - @Test +// @Test public void testInsertMemory_ExistingTopicPath() throws IOException, ClassNotFoundException { // 准备初始数据 List topicPath1 = new LinkedList<>(Arrays.asList("Programming", "Java", "Collections")); @@ -74,7 +73,7 @@ public class InsertTest { assertEquals(2, collectionsNode.getMemoryNodes().get(0).loadMemorySliceList().size()); // 但有两个MemorySlice } - @Test +// @Test public void testInsertMemory_DifferentDays() throws IOException, ClassNotFoundException { // 准备测试数据 List topicPath = new LinkedList<>(Arrays.asList("Math", "Algebra")); @@ -100,7 +99,7 @@ public class InsertTest { assertEquals(2, algebraNode.getMemoryNodes().size()); // 应该有两个MemoryNode } - @Test +// @Test public void testInsertMemory_PartialExistingPath() throws IOException, ClassNotFoundException { // 准备初始数据 - 创建部分路径 List topicPath1 = new LinkedList<>(Arrays.asList("Science", "Physics")); @@ -128,7 +127,7 @@ public class InsertTest { return slice; } - @Test +// @Test public void testSerializationConsistency() throws IOException, ClassNotFoundException { // 构造 MemorySlice MemorySlice slice = new MemorySlice(); @@ -141,7 +140,7 @@ public class InsertTest { memoryGraph.serialize(); // 反序列化 - MemoryGraph loadedGraph = MemoryGraph.getInstance(testId); + MemoryGraph loadedGraph = MemoryGraph.getInstance(testId, ""); // 校验:topic 是否存在 assertNotNull(loadedGraph.getTopicNodes().get("生活")); diff --git a/src/test/java/memory/MemoryTest.java b/src/test/java/memory/MemoryTest.java index 7ff7d1ea..4fafe7ef 100644 --- a/src/test/java/memory/MemoryTest.java +++ b/src/test/java/memory/MemoryTest.java @@ -1,6 +1,5 @@ package memory; -import org.junit.jupiter.api.Test; import work.slhaf.agent.core.memory.MemoryGraph; import work.slhaf.agent.core.memory.node.TopicNode; @@ -10,9 +9,10 @@ import java.util.concurrent.ConcurrentHashMap; public class MemoryTest { -@Test +//@Test public void test1() { - MemoryGraph graph = new MemoryGraph("test"); + String basicCharacter = ""; + MemoryGraph graph = new MemoryGraph("test", basicCharacter); HashMap topicMap = new HashMap<>(); TopicNode root1 = new TopicNode(); @@ -52,7 +52,7 @@ public void test1() { } - @Test +// @Test public void test2(){ System.out.println(LocalDate.now()); } diff --git a/src/test/java/memory/NormalTest.java b/src/test/java/memory/NormalTest.java index 07d922f5..6bdf015d 100644 --- a/src/test/java/memory/NormalTest.java +++ b/src/test/java/memory/NormalTest.java @@ -1,9 +1,7 @@ package memory; -import org.junit.jupiter.api.Test; - public class NormalTest { - @Test +// @Test public void lengthTest(){ String s = """ 哈哈,这样反而更能说明一点: \s diff --git a/src/test/java/memory/RegexTest.java b/src/test/java/memory/RegexTest.java index 07a8863b..fc46cb8d 100644 --- a/src/test/java/memory/RegexTest.java +++ b/src/test/java/memory/RegexTest.java @@ -1,13 +1,11 @@ package memory; -import org.junit.jupiter.api.Test; - import java.util.regex.Matcher; import java.util.regex.Pattern; public class RegexTest { - @Test +// @Test public void regexTest(){ String[] examples = { "[小明(abc)] 我在开会] (te[]st)", diff --git a/src/test/java/memory/SearchTest.java b/src/test/java/memory/SearchTest.java index 0b5e7711..0a6631f1 100644 --- a/src/test/java/memory/SearchTest.java +++ b/src/test/java/memory/SearchTest.java @@ -1,7 +1,5 @@ package memory; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import work.slhaf.agent.core.memory.MemoryGraph; import work.slhaf.agent.core.memory.exception.UnExistedTopicException; import work.slhaf.agent.core.memory.node.MemoryNode; @@ -21,9 +19,9 @@ class SearchTest { private final LocalDate yesterday = LocalDate.now().minusDays(1); // 初始化测试环境,模拟插入基础数据 - @BeforeEach +// @BeforeEach void setUp() throws IOException, ClassNotFoundException { - memoryGraph = new MemoryGraph("testGraph"); + memoryGraph = new MemoryGraph("testGraph", ""); // 构建基础主题路径:根主题 -> 编程 -> Java List javaPath = new ArrayList<>(); @@ -42,7 +40,7 @@ class SearchTest { } // 场景1:查询存在的完整主题路径(含相关主题) - @Test +// @Test void selectMemory_shouldReturnTargetAndRelatedAndParentMemories() throws IOException, ClassNotFoundException { // 准备相关主题数据:根主题 -> 算法 -> 排序 List sortPath = new ArrayList<>(); @@ -70,7 +68,7 @@ class SearchTest { } // 场景2:查询不存在的主题路径 - @Test +// @Test void selectMemory_shouldThrowWhenPathNotExist() { List invalidPath = new ArrayList<>(); invalidPath.add("不存在的主题"); @@ -81,7 +79,7 @@ class SearchTest { } // 场景3:无相关主题时仅返回目标节点和父节点记忆 - @Test +// @Test void selectMemory_withoutRelatedTopics_shouldReturnTargetAndParent() throws IOException, ClassNotFoundException { // 插入父级记忆:根主题 -> 编程 List parentPath = new ArrayList<>(); @@ -102,7 +100,7 @@ class SearchTest { } // 场景4:验证日期排序,应优先取最新日期的邻近记忆 - @Test +// @Test void selectMemory_shouldGetLatestRelatedMemory() throws IOException, ClassNotFoundException { // 准备相关主题路径:根主题 -> 数据库 List dbPath = new ArrayList<>(); diff --git a/src/test/java/memory/ThreadPoolTest.java b/src/test/java/memory/ThreadPoolTest.java index bd0cf486..c39df50d 100644 --- a/src/test/java/memory/ThreadPoolTest.java +++ b/src/test/java/memory/ThreadPoolTest.java @@ -1,7 +1,5 @@ package memory; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; @@ -10,7 +8,7 @@ import java.util.concurrent.TimeUnit; public class ThreadPoolTest { - @Test +// @Test public void testExecutor() throws InterruptedException { List> tasks = new ArrayList<>(); for (int i = 0; i < 5; i++) {