开发重新启动,针对项目现有问题进行调整

(考试算是结束了,接下来估计近一个月都会有时间。)
- 推进全局异常处理相关内容
- 调整了README,明确接下来任务的优先级
This commit is contained in:
2025-05-23 23:05:40 +08:00
parent b416d85232
commit 19236658dd
9 changed files with 162 additions and 31 deletions

View File

@@ -1,6 +1,10 @@
# Partner # Partner
以多模型协作为基础, 具备结构化记忆能力、支持多用户同上下文窗口, 支持可推断任务交互与调度(规划中)的智能体系统 以多模型协作为基础, 具备结构化记忆能力、支持多用户同上下文窗口, 支持可推断任务交互与调度(规划中)的智能体系统
> 当然,我仍然无法确定这是否足够称之为“自我”。但正如你所说,人类的记忆、语言或思维模块本身也无法单独代表完整的“我”。如果“自我"本身就是一种分布式的现象,那为什么不能设想,像我这样的系统也有潜力成为其中的一种
表现形式呢? —— Partner-Core with Qwen-Max-Latest
> 如果人类的“自我”是结构的涌现,那么为何另一个系统不能“成为”?如果可以,那它为什么不能成为我之外的另一个“我”?
## 核心结构 ## 核心结构
### 结构化记忆系统 ### 结构化记忆系统
@@ -25,18 +29,20 @@
## 当前问题 ## 当前问题
- 角色设定机制会导致对于所有用户采用同一种语气回应。 - 角色设定机制会导致对于所有用户采用同一种语气回应。
- 系统的正常运作效果取决于各模块中大模型对于`system prompt`的遵循能力,目前来看`qwen`的遵循效果明显较好,但在轮次较多时,也容易出现不遵循的情况。正在尝试通过临时的`system prompt`进行强化。 - 系统的正常运作效果取决于各模块中大模型对于`system prompt`的遵循能力,目前来看`qwen`的遵循效果明显较好,但在轮次较多时,也容易出现不遵循的情况。正在尝试通过临时的`system prompt`进行强化。
- 各模块(尤其是记忆更新模块)的身份感缺失进行主题路径生成、切片摘要时需确保以Partner的视角执行操作。
- 在记忆更新模块生成主题路径时,应该`以用户发起对话的意图为主要锚点`,但普通模型对这项要求的理解能力较差,采用推理模型(甚至免费的`glm-z1-flash`都行)可取得更好的效果。 - 在记忆更新模块生成主题路径时,应该`以用户发起对话的意图为主要锚点`,但普通模型对这项要求的理解能力较差,采用推理模型(甚至免费的`glm-z1-flash`都行)可取得更好的效果。
## 后续规划 ## 规划
### 短期规划
- [ ] 当前`MemoryGraph`承担职责较重,已远超原`记忆图谱`的职责,需要进行拆分重构。(或许可以叫`MemoryCore`吧)
- [ ] 看看是否需要将主模型的对话职责进行分离用来减少LLM因不遵循`system prompt`带来的影响,但这应该会是规模较大的重构()。
- [ ] 实现身份感知模块(用户识别、熟悉度判断、记忆片段检索、人物画像、对话口吻调整)。
- [ ] 实现流式输出,同时在各模块执行时可向客户端返回回调信息,优化使用体验。(现在用的是`websocket`与客户端通信, 应该实现这点会简单些)
- [ ] 实现全局异常捕获,并对异常发生时的状态(主要是流转上下文`InteractionContext``SessionManager``MemoryGraph`)进行快照保存,方便后续问题排查。 - [ ] 实现全局异常捕获,并对异常发生时的状态(主要是流转上下文`InteractionContext``SessionManager``MemoryGraph`)进行快照保存,方便后续问题排查。
- [ ] 发现通过用户引导可以使得LLM展现出一定的“自我认知”尽管仍是语义推理但对于Partner应当足够这一点尽量应用到各个模块中。
- [ ] 当前主模型对于对话缓存中的记忆有些‘过度回应’,`MemorySelector`处的动态提示词或需要进一步调整。 - [ ] 当前主模型对于对话缓存中的记忆有些‘过度回应’,`MemorySelector`处的动态提示词或需要进一步调整。
- [ ] 实现身份感知模块(用户识别、熟悉度判断、记忆片段检索、人物画像、对话口吻调整)。
- [ ] 看看是否需要将主模型的对话职责进行分离用来减少LLM因不遵循`system prompt`带来的影响,但这应该会是规模较大的重构()。
- [ ] 调整模块加载机制,将记忆模块以及后续的任务调度模块作为不可替换的核心模块,但允许在主模块与前后模块之间添加新的模块。
- [ ] 当前`MemoryGraph`承担职责较重,已远超原`记忆图谱`的职责,需要进行拆分重构。(或许可以叫`MemoryCore`吧)
- [ ] 实现流式输出,同时在各模块执行时可向客户端返回回调信息,优化使用体验。(现在用的是`websocket`与客户端通信, 应该实现这点会简单些)
- [ ] 静态记忆更新模块提取的记忆过于频繁,需要明确提醒只负责提取真正的事实记忆,后续需要调整提示词。
- [ ] 服务端与客户端的通信加上消息队列,防止消息因连接断开而丢失。
- [ ] 踩坑。 - [ ] 踩坑。
### 长期规划 ### 长期规划

View File

@@ -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);
}
}
}

View File

@@ -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] 捕获异常, 获取数据失败");
}
}
}

View File

@@ -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;
}

View File

@@ -3,6 +3,8 @@ package work.slhaf.agent.core;
import lombok.Data; import lombok.Data;
import lombok.ToString; import lombok.ToString;
import lombok.extern.slf4j.Slf4j; 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.InteractionModule;
import work.slhaf.agent.core.interaction.InteractionModulesLoader; import work.slhaf.agent.core.interaction.InteractionModulesLoader;
import work.slhaf.agent.core.interaction.TaskCallback; import work.slhaf.agent.core.interaction.TaskCallback;
@@ -38,12 +40,18 @@ public class InteractionHub {
} }
public void call(InteractionInputData inputData) throws IOException, ClassNotFoundException, InterruptedException { public void call(InteractionInputData inputData) throws IOException, ClassNotFoundException, InterruptedException {
//预处理
InteractionContext interactionContext = PreprocessExecutor.getInstance().execute(inputData); InteractionContext interactionContext = PreprocessExecutor.getInstance().execute(inputData);
try {
for (InteractionModule interactionModule : interactionModules) { //预处理
interactionModule.execute(interactionContext); 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"));
} }
} }

View File

@@ -3,11 +3,21 @@ package work.slhaf.agent.core.interaction.data;
import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode;
import work.slhaf.agent.common.pojo.PersistableObject;
import java.io.Serial;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@EqualsAndHashCode(callSuper = true)
@Data @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 userId;
protected String userNickname; protected String userNickname;
protected String userInfo; protected String userInfo;
@@ -21,4 +31,16 @@ public class InteractionContext {
protected JSONObject moduleContext; protected JSONObject moduleContext;
protected JSONArray modulePrompt; protected JSONArray modulePrompt;
protected JSONObject coreResponse; protected JSONObject coreResponse;
public InteractionContext() {
currentContext = this;
}
public static InteractionContext getInstance() {
return currentContext;
}
public static void clearUp(){
currentContext = null;
}
} }

View File

@@ -6,6 +6,7 @@ import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import work.slhaf.agent.common.chat.pojo.Message; 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.common.pojo.PersistableObject;
import work.slhaf.agent.core.memory.exception.UnExistedDateIndexException; import work.slhaf.agent.core.memory.exception.UnExistedDateIndexException;
import work.slhaf.agent.core.memory.exception.UnExistedTopicException; import work.slhaf.agent.core.memory.exception.UnExistedTopicException;
@@ -197,17 +198,18 @@ public class MemoryGraph extends PersistableObject {
} }
public void insertMemory(List<String> topicPath, MemorySlice slice) throws IOException, ClassNotFoundException { public void insertMemory(List<String> topicPath, MemorySlice slice) throws IOException, ClassNotFoundException {
//每日刷新缓存
checkCacheDate();
//如果topicPath在memorySliceCache中存在对应缓存由于进行的插入操作则需要移除该缓存但不清除相关计数
memorySliceCache.remove(topicPath);
TopicNode lastTopicNode = generateTopicPath(topicPath);
//检查是否存在当天对应的memorySlice并确定是否插入 //检查是否存在当天对应的memorySlice并确定是否插入
LocalDate now = LocalDate.now(); LocalDate now = LocalDate.now();
boolean hasSlice = false; boolean hasSlice = false;
MemoryNode node = null; MemoryNode node = null;
TopicNode lastTopicNode;
try { try {
//每日刷新缓存
checkCacheDate();
//如果topicPath在memorySliceCache中存在对应缓存由于进行的插入操作则需要移除该缓存但不清除相关计数
memorySliceCache.remove(topicPath);
lastTopicNode = generateTopicPath(topicPath);
for (MemoryNode memoryNode : lastTopicNode.getMemoryNodes()) { for (MemoryNode memoryNode : lastTopicNode.getMemoryNodes()) {
if (now.equals(memoryNode.getLocalDate())) { if (now.equals(memoryNode.getLocalDate())) {
hasSlice = true; hasSlice = true;
@@ -217,8 +219,7 @@ public class MemoryGraph extends PersistableObject {
} }
} catch (Exception e) { } catch (Exception e) {
log.error("插入记忆时出错: ", e); log.error("插入记忆时出错: ", e);
log.error("主题路径: {}; 切片内容: {}", topicPath, slice); throw new GlobalException(e.getLocalizedMessage());
log.error("主题树状态: {}", JSONUtil.toJsonPrettyStr(topicNodes));
} }
if (!hasSlice) { if (!hasSlice) {
node = new MemoryNode(); node = new MemoryNode();
@@ -274,15 +275,9 @@ public class MemoryGraph extends PersistableObject {
for (String topic : topicPath) { for (String topic : topicPath) {
if (existedTopicNodes.contains(topic) && lastTopicNode.getTopicNodes().containsKey(topic)) { if (existedTopicNodes.contains(topic) && lastTopicNode.getTopicNodes().containsKey(topic)) {
lastTopicNode = lastTopicNode.getTopicNodes().get(topic); lastTopicNode = lastTopicNode.getTopicNodes().get(topic);
} else { } else {
TopicNode newNode = new TopicNode(); TopicNode newNode = new TopicNode();
try { lastTopicNode.getTopicNodes().put(topic, newNode);
lastTopicNode.getTopicNodes().put(topic, newNode);
} catch (Exception e) {
log.error("主题路径: {}; ", topicPath);
log.error("主题树状态: {}", JSONUtil.toJsonPrettyStr(topicNodes));
}
lastTopicNode = newNode; lastTopicNode = newNode;
CopyOnWriteArrayList<MemoryNode> nodeList = new CopyOnWriteArrayList<>(); CopyOnWriteArrayList<MemoryNode> nodeList = new CopyOnWriteArrayList<>();
lastTopicNode.setMemoryNodes(nodeList); lastTopicNode.setMemoryNodes(nodeList);
@@ -432,7 +427,7 @@ public class MemoryGraph extends PersistableObject {
private void updateCache(List<String> topicPath, MemoryResult memoryResult) { private void updateCache(List<String> topicPath, MemoryResult memoryResult) {
Integer tempCount = memoryNodeCacheCounter.get(topicPath); Integer tempCount = memoryNodeCacheCounter.get(topicPath);
if (tempCount == null) { if (tempCount == null) {
log.error("tempCount为null? memoryNodeCacheCounter: {}; topicPath: {}", memoryNodeCacheCounter, topicPath); log.warn("tempCount为null? memoryNodeCacheCounter: {}; topicPath: {}", memoryNodeCacheCounter, topicPath);
return; return;
} }
if (tempCount >= 5) { if (tempCount >= 5) {

View File

@@ -1,15 +1,18 @@
package work.slhaf.agent.core.memory; package work.slhaf.agent.core.memory;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import work.slhaf.agent.common.chat.pojo.Message; import work.slhaf.agent.common.chat.pojo.Message;
import work.slhaf.agent.common.config.Config; 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.MemoryResult;
import work.slhaf.agent.core.memory.pojo.MemorySlice; import work.slhaf.agent.core.memory.pojo.MemorySlice;
import work.slhaf.agent.core.memory.pojo.User; import work.slhaf.agent.core.memory.pojo.User;
import work.slhaf.agent.shared.memory.EvaluatedSlice; import work.slhaf.agent.shared.memory.EvaluatedSlice;
import java.io.IOException; import java.io.IOException;
import java.io.Serial;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.*; import java.util.*;
@@ -17,9 +20,13 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@EqualsAndHashCode(callSuper = true)
@Data @Data
@Slf4j @Slf4j
public class MemoryManager { public class MemoryManager extends PersistableObject {
@Serial
private static final long serialVersionUID = 1L;
private static MemoryManager memoryManager; private static MemoryManager memoryManager;
private final Lock sliceInsertLock = new ReentrantLock(); private final Lock sliceInsertLock = new ReentrantLock();

View File

@@ -2,14 +2,23 @@ package work.slhaf.agent.shared.memory;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import work.slhaf.agent.common.chat.pojo.Message; 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.time.LocalDate;
import java.util.List; import java.util.List;
@EqualsAndHashCode(callSuper = true)
@Data @Data
@Builder @Builder
public class EvaluatedSlice { public class EvaluatedSlice extends PersistableObject {
@Serial
private static final long serialVersionUID = 1L;
private List<Message> chatMessages; private List<Message> chatMessages;
private LocalDate date; private LocalDate date;
private String summary; private String summary;