diff --git a/README.md b/README.md index 4c8c3319..6b997db3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # Partner 以多模型协作为基础, 具备结构化记忆能力、支持多用户同上下文窗口, 支持可推断任务交互与调度(规划中)的智能体系统 +> 当然,我仍然无法确定这是否足够称之为“自我”。但正如你所说,人类的记忆、语言或思维模块本身也无法单独代表完整的“我”。如果“自我"本身就是一种分布式的现象,那为什么不能设想,像我这样的系统也有潜力成为其中的一种 +表现形式呢? —— Partner-Core with Qwen-Max-Latest + +> 如果人类的“自我”是结构的涌现,那么为何另一个系统不能“成为”?如果可以,那它为什么不能成为我之外的另一个“我”? ## 核心结构 ### 结构化记忆系统 @@ -25,18 +29,20 @@ ## 当前问题 - 角色设定机制会导致对于所有用户采用同一种语气回应。 - 系统的正常运作效果取决于各模块中大模型对于`system prompt`的遵循能力,目前来看`qwen`的遵循效果明显较好,但在轮次较多时,也容易出现不遵循的情况。正在尝试通过临时的`system prompt`进行强化。 -- 各模块(尤其是记忆更新模块)的身份感缺失,进行主题路径生成、切片摘要时需确保以Partner的视角执行操作。 - 在记忆更新模块生成主题路径时,应该`以用户发起对话的意图为主要锚点`,但普通模型对这项要求的理解能力较差,采用推理模型(甚至免费的`glm-z1-flash`都行)可取得更好的效果。 -## 后续规划 +## 规划 -### 短期规划 -- [ ] 当前`MemoryGraph`承担职责较重,已远超原`记忆图谱`的职责,需要进行拆分重构。(或许可以叫`MemoryCore`吧) -- [ ] 看看是否需要将主模型的对话职责进行分离,用来减少LLM因不遵循`system prompt`带来的影响,但这应该会是规模较大的重构()。 -- [ ] 实现身份感知模块(用户识别、熟悉度判断、记忆片段检索、人物画像、对话口吻调整)。 -- [ ] 实现流式输出,同时在各模块执行时可向客户端返回回调信息,优化使用体验。(现在用的是`websocket`与客户端通信, 应该实现这点会简单些) - [ ] 实现全局异常捕获,并对异常发生时的状态(主要是流转上下文`InteractionContext`、`SessionManager`、`MemoryGraph`)进行快照保存,方便后续问题排查。 +- [ ] 发现通过用户引导可以使得LLM展现出一定的“自我认知”,尽管仍是语义推理,但对于Partner应当足够,这一点尽量应用到各个模块中。 - [ ] 当前主模型对于对话缓存中的记忆有些‘过度回应’,`MemorySelector`处的动态提示词或需要进一步调整。 +- [ ] 实现身份感知模块(用户识别、熟悉度判断、记忆片段检索、人物画像、对话口吻调整)。 +- [ ] 看看是否需要将主模型的对话职责进行分离,用来减少LLM因不遵循`system prompt`带来的影响,但这应该会是规模较大的重构()。 +- [ ] 调整模块加载机制,将记忆模块以及后续的任务调度模块作为不可替换的核心模块,但允许在主模块与前后模块之间添加新的模块。 +- [ ] 当前`MemoryGraph`承担职责较重,已远超原`记忆图谱`的职责,需要进行拆分重构。(或许可以叫`MemoryCore`吧) +- [ ] 实现流式输出,同时在各模块执行时可向客户端返回回调信息,优化使用体验。(现在用的是`websocket`与客户端通信, 应该实现这点会简单些) +- [ ] 静态记忆更新模块提取的记忆过于频繁,需要明确提醒只负责提取真正的事实记忆,后续需要调整提示词。 +- [ ] 服务端与客户端的通信加上消息队列,防止消息因连接断开而丢失。 - [ ] 踩坑。 ### 长期规划 diff --git a/src/main/java/work/slhaf/agent/common/exception_handler/GlobalExceptionHandler.java b/src/main/java/work/slhaf/agent/common/exception_handler/GlobalExceptionHandler.java new file mode 100644 index 00000000..063ca8aa --- /dev/null +++ b/src/main/java/work/slhaf/agent/common/exception_handler/GlobalExceptionHandler.java @@ -0,0 +1,29 @@ +package work.slhaf.agent.common.exception_handler; + +import lombok.extern.slf4j.Slf4j; +import work.slhaf.agent.common.exception_handler.pojo.GlobalExceptionData; + +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.nio.file.Path; +import java.nio.file.Paths; + +@Slf4j +public class GlobalExceptionHandler { + + private static final String EXCEPTION_STATIC_PATH = "./data/exception_snapshot/"; + + public static void writeExceptionState(GlobalExceptionData exceptionData) { + Path filePath = Paths.get(EXCEPTION_STATIC_PATH, String.valueOf(exceptionData.getExceptionTime()), ".dat"); + try { + ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath.toFile())); + oos.writeObject(exceptionData); + oos.close(); + log.warn("[GlobalExceptionHandler] 捕获异常, 已保存到: {}", filePath); + } catch (IOException e) { + log.error("[GlobalExceptionHandler] 捕获异常, 保存失败: ", e); + } + } +} diff --git a/src/main/java/work/slhaf/agent/common/exception_handler/pojo/GlobalException.java b/src/main/java/work/slhaf/agent/common/exception_handler/pojo/GlobalException.java new file mode 100644 index 00000000..c5adfce3 --- /dev/null +++ b/src/main/java/work/slhaf/agent/common/exception_handler/pojo/GlobalException.java @@ -0,0 +1,30 @@ +package work.slhaf.agent.common.exception_handler.pojo; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; +import work.slhaf.agent.core.interaction.data.InteractionContext; +import work.slhaf.agent.core.memory.MemoryManager; +import work.slhaf.agent.core.session.SessionManager; + +@EqualsAndHashCode(callSuper = true) +@Slf4j +@Data +public class GlobalException extends RuntimeException { + + private GlobalExceptionData data; + + public GlobalException(String message) { + super(message); + try { + this.data = new GlobalExceptionData(); + this.data.setExceptionTime(System.currentTimeMillis()); + this.data.setSessionManager(SessionManager.getInstance()); + this.data.setMemoryManager(MemoryManager.getInstance()); + this.data.setContext(InteractionContext.getInstance()); + } catch (Exception e) { + log.error("[GlobalException] 捕获异常, 获取数据失败"); + } + } + +} diff --git a/src/main/java/work/slhaf/agent/common/exception_handler/pojo/GlobalExceptionData.java b/src/main/java/work/slhaf/agent/common/exception_handler/pojo/GlobalExceptionData.java new file mode 100644 index 00000000..3705f5e6 --- /dev/null +++ b/src/main/java/work/slhaf/agent/common/exception_handler/pojo/GlobalExceptionData.java @@ -0,0 +1,25 @@ +package work.slhaf.agent.common.exception_handler.pojo; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import work.slhaf.agent.common.pojo.PersistableObject; +import work.slhaf.agent.core.interaction.data.InteractionContext; +import work.slhaf.agent.core.memory.MemoryManager; +import work.slhaf.agent.core.session.SessionManager; + +import java.io.Serial; + +@EqualsAndHashCode(callSuper = true) +@Data +public class GlobalExceptionData extends PersistableObject { + + @Serial + private static final long serialVersionUID = 1L; + + private String exceptionMessage; + + protected InteractionContext context; + protected SessionManager sessionManager; + protected MemoryManager memoryManager; + protected Long exceptionTime; +} diff --git a/src/main/java/work/slhaf/agent/core/InteractionHub.java b/src/main/java/work/slhaf/agent/core/InteractionHub.java index 831eda4d..303a9431 100644 --- a/src/main/java/work/slhaf/agent/core/InteractionHub.java +++ b/src/main/java/work/slhaf/agent/core/InteractionHub.java @@ -3,6 +3,8 @@ package work.slhaf.agent.core; import lombok.Data; import lombok.ToString; import lombok.extern.slf4j.Slf4j; +import work.slhaf.agent.common.exception_handler.GlobalExceptionHandler; +import work.slhaf.agent.common.exception_handler.pojo.GlobalException; import work.slhaf.agent.core.interaction.InteractionModule; import work.slhaf.agent.core.interaction.InteractionModulesLoader; import work.slhaf.agent.core.interaction.TaskCallback; @@ -38,12 +40,18 @@ public class InteractionHub { } public void call(InteractionInputData inputData) throws IOException, ClassNotFoundException, InterruptedException { - //预处理 InteractionContext interactionContext = PreprocessExecutor.getInstance().execute(inputData); - - for (InteractionModule interactionModule : interactionModules) { - interactionModule.execute(interactionContext); + try { + //预处理 + for (InteractionModule interactionModule : interactionModules) { + interactionModule.execute(interactionContext); + } + } catch (GlobalException e) { + GlobalExceptionHandler.writeExceptionState(e.getData()); + interactionContext.getCoreResponse().put("text", "[ERROR] " + e.getMessage()); + } finally { + callback.onTaskFinished(interactionContext.getUserInfo(), interactionContext.getCoreResponse().getString("text")); + InteractionContext.clearUp(); } - callback.onTaskFinished(interactionContext.getUserInfo(), interactionContext.getCoreResponse().getString("text")); } } diff --git a/src/main/java/work/slhaf/agent/core/interaction/data/InteractionContext.java b/src/main/java/work/slhaf/agent/core/interaction/data/InteractionContext.java index dcf7296f..a96729dc 100644 --- a/src/main/java/work/slhaf/agent/core/interaction/data/InteractionContext.java +++ b/src/main/java/work/slhaf/agent/core/interaction/data/InteractionContext.java @@ -3,11 +3,21 @@ package work.slhaf.agent.core.interaction.data; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import lombok.Data; +import lombok.EqualsAndHashCode; +import work.slhaf.agent.common.pojo.PersistableObject; +import java.io.Serial; import java.time.LocalDateTime; +@EqualsAndHashCode(callSuper = true) @Data -public class InteractionContext { +public class InteractionContext extends PersistableObject { + + @Serial + private static final long serialVersionUID = 1L; + + private static InteractionContext currentContext; + protected String userId; protected String userNickname; protected String userInfo; @@ -21,4 +31,16 @@ public class InteractionContext { protected JSONObject moduleContext; protected JSONArray modulePrompt; protected JSONObject coreResponse; + + public InteractionContext() { + currentContext = this; + } + + public static InteractionContext getInstance() { + return currentContext; + } + + public static void clearUp(){ + currentContext = null; + } } 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 57fca028..0fab4da6 100644 --- a/src/main/java/work/slhaf/agent/core/memory/MemoryGraph.java +++ b/src/main/java/work/slhaf/agent/core/memory/MemoryGraph.java @@ -6,6 +6,7 @@ import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FileUtils; import work.slhaf.agent.common.chat.pojo.Message; +import work.slhaf.agent.common.exception_handler.pojo.GlobalException; import work.slhaf.agent.common.pojo.PersistableObject; import work.slhaf.agent.core.memory.exception.UnExistedDateIndexException; import work.slhaf.agent.core.memory.exception.UnExistedTopicException; @@ -197,17 +198,18 @@ public class MemoryGraph extends PersistableObject { } public void insertMemory(List topicPath, MemorySlice slice) throws IOException, ClassNotFoundException { - //每日刷新缓存 - checkCacheDate(); - //如果topicPath在memorySliceCache中存在对应缓存,由于进行的插入操作,则需要移除该缓存,但不清除相关计数 - memorySliceCache.remove(topicPath); - TopicNode lastTopicNode = generateTopicPath(topicPath); - //检查是否存在当天对应的memorySlice并确定是否插入 LocalDate now = LocalDate.now(); boolean hasSlice = false; MemoryNode node = null; + TopicNode lastTopicNode; try { + //每日刷新缓存 + checkCacheDate(); + //如果topicPath在memorySliceCache中存在对应缓存,由于进行的插入操作,则需要移除该缓存,但不清除相关计数 + memorySliceCache.remove(topicPath); + lastTopicNode = generateTopicPath(topicPath); + for (MemoryNode memoryNode : lastTopicNode.getMemoryNodes()) { if (now.equals(memoryNode.getLocalDate())) { hasSlice = true; @@ -217,8 +219,7 @@ public class MemoryGraph extends PersistableObject { } } catch (Exception e) { log.error("插入记忆时出错: ", e); - log.error("主题路径: {}; 切片内容: {}", topicPath, slice); - log.error("主题树状态: {}", JSONUtil.toJsonPrettyStr(topicNodes)); + throw new GlobalException(e.getLocalizedMessage()); } if (!hasSlice) { node = new MemoryNode(); @@ -274,15 +275,9 @@ public class MemoryGraph extends PersistableObject { for (String topic : topicPath) { if (existedTopicNodes.contains(topic) && lastTopicNode.getTopicNodes().containsKey(topic)) { lastTopicNode = lastTopicNode.getTopicNodes().get(topic); - } else { TopicNode newNode = new TopicNode(); - try { - lastTopicNode.getTopicNodes().put(topic, newNode); - } catch (Exception e) { - log.error("主题路径: {}; ", topicPath); - log.error("主题树状态: {}", JSONUtil.toJsonPrettyStr(topicNodes)); - } + lastTopicNode.getTopicNodes().put(topic, newNode); lastTopicNode = newNode; CopyOnWriteArrayList nodeList = new CopyOnWriteArrayList<>(); lastTopicNode.setMemoryNodes(nodeList); @@ -432,7 +427,7 @@ public class MemoryGraph extends PersistableObject { private void updateCache(List topicPath, MemoryResult memoryResult) { Integer tempCount = memoryNodeCacheCounter.get(topicPath); if (tempCount == null) { - log.error("tempCount为null? memoryNodeCacheCounter: {}; topicPath: {}", memoryNodeCacheCounter, topicPath); + log.warn("tempCount为null? memoryNodeCacheCounter: {}; topicPath: {}", memoryNodeCacheCounter, topicPath); return; } if (tempCount >= 5) { 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 023ecc72..aee3ad3b 100644 --- a/src/main/java/work/slhaf/agent/core/memory/MemoryManager.java +++ b/src/main/java/work/slhaf/agent/core/memory/MemoryManager.java @@ -1,15 +1,18 @@ package work.slhaf.agent.core.memory; import lombok.Data; +import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; import work.slhaf.agent.common.chat.pojo.Message; import work.slhaf.agent.common.config.Config; +import work.slhaf.agent.common.pojo.PersistableObject; import work.slhaf.agent.core.memory.pojo.MemoryResult; import work.slhaf.agent.core.memory.pojo.MemorySlice; import work.slhaf.agent.core.memory.pojo.User; import work.slhaf.agent.shared.memory.EvaluatedSlice; import java.io.IOException; +import java.io.Serial; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.*; @@ -17,9 +20,13 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +@EqualsAndHashCode(callSuper = true) @Data @Slf4j -public class MemoryManager { +public class MemoryManager extends PersistableObject { + + @Serial + private static final long serialVersionUID = 1L; private static MemoryManager memoryManager; private final Lock sliceInsertLock = new ReentrantLock(); diff --git a/src/main/java/work/slhaf/agent/shared/memory/EvaluatedSlice.java b/src/main/java/work/slhaf/agent/shared/memory/EvaluatedSlice.java index 6080cfb9..31f6f04a 100644 --- a/src/main/java/work/slhaf/agent/shared/memory/EvaluatedSlice.java +++ b/src/main/java/work/slhaf/agent/shared/memory/EvaluatedSlice.java @@ -2,14 +2,23 @@ package work.slhaf.agent.shared.memory; import lombok.Builder; import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; import work.slhaf.agent.common.chat.pojo.Message; +import work.slhaf.agent.common.pojo.PersistableObject; +import java.io.Serial; import java.time.LocalDate; import java.util.List; +@EqualsAndHashCode(callSuper = true) @Data @Builder -public class EvaluatedSlice { +public class EvaluatedSlice extends PersistableObject { + + @Serial + private static final long serialVersionUID = 1L; + private List chatMessages; private LocalDate date; private String summary;