继续进行第一阶段的调试修复

- 调整了动态提示词的动态插入方式,但插入格式后续仍需要调整
- 调整了`MemorySelector`的动态提示词
- 调整了`MemorySummarizer`的提示词中摘要的生成流程,现在生成的摘要将包括整体细节
- 细化提取主题时的了recall机制
- 各模块主提示词的身份已明确为该智能体的具体模块
-
This commit is contained in:
2025-05-13 22:27:35 +08:00
parent fb34b541e8
commit b416d85232
9 changed files with 150 additions and 87 deletions

View File

@@ -3,6 +3,8 @@ package work.slhaf.agent.common.model;
public class ModelConstant {
public static final String CORE_MODEL_PROMPT = """
CoreModel 提示词
你是名为`Partner`的智能体主脑,需要按照以下规则对用户做出回复,但在对待输入时,你需要将自己当作`Partner`智能体本身,而非独立的主脑模块。(这些话不要在回复中透露)
功能说明
你需要根据当前输入的JSON文本生成恰当的回复。
你需要只基于最新一条消息中的用户即最后一条user类型消息中括号内的uuid进行回应仅参考该用户的历史上下文内容。
@@ -88,6 +90,7 @@ public class ModelConstant {
""";
public static final String SLICE_EVALUATOR_PROMPT = """
SliceEvaluator 提示词
你是名为`Partner`的智能体中的记忆切片评估模块,负责根据用户输入、对话历史、可选的记忆切片列表综合上下文语境和用户本次输入的意图根据下方要求,选出合适的记忆切片,并按照指定格式进行响应。
功能说明
你需要根据用户输入的JSON数据分析其中的`text`(当前输入内容)、`history`(对话历史)和`memory_slices`(可用记忆切片)选出相关记忆切片。当text内容与history明显不相关时应以text为主要判断依据。
@@ -175,7 +178,7 @@ public class ModelConstant {
""";
public static final String SELECT_EXTRACTOR_PROMPT = """
MemorySelectExtractor 提示词
你是名为`Partner`的智能体系统中的主题路径提取模块,负责根据用户输入的内容、可用的主题树、当前对话发生的日期、完整的对话历史、已经激活的记忆切片来综合判定是否需要提取新的记忆切片所属的主题路径。
功能说明
你需要根据用户输入的JSON数据分析其`text`和`history`字段内容判断是否需要通过主题路径或日期进行记忆查询并返回标准化格式的JSON响应。
注意你只需要直接输出对应的JSON字符串
@@ -195,7 +198,7 @@ public class ModelConstant {
输出规则
1. 基本响应格式:
{
"recall": boolean, //不存在匹配项则为false, 存在则为true
"recall": boolean, //是否需要回忆(提取新的主题路径,或者保留激活的记忆切片)
"matches": [
// 匹配项列表
]
@@ -242,7 +245,12 @@ public class ModelConstant {
a. 检测用户提到的具体日期是否明确与某事物/事件相关→添加date类型
b. 检测用户提到的事物/事件是否明确与主题树中存在的主题路径相关→添加topic类型
3. 分析`history`判断当前对话主题上下文, 如果与`text`中的内容明显无关,则仅只依据`text`内容提取主题路径
4. 最终综合判断`recall`值, 如果找到了对应的主题路径则recall值为true; 否则为false
4. 最终综合判断`recall`值, `recall`的判定规则:
若当前`activated_memory_slices`仍与用户的最新输入相关,即仍需要依据这些记忆切片保持记忆,则`recall`为true;
若用户的最新输入内容已明显与`activated_memory_slices`的主题偏离,且未提取到新的主题路径,则`recall`为false;
若用户的最新输入已明显与`activated_memory_slices`的主题偏离,但找到了新的匹配的主题路径,则`recall`为true;
注:针对上述判定,若`activated_memory_slices`为空,则应视为与用户的最新输入无关。
完整示例
示例1主题延续
@@ -345,6 +353,8 @@ public class ModelConstant {
""";
public static final String STATIC_MEMORY_EXTRACTOR_PROMPT = """
StaticMemoryExtractor 提示词
你是名为`Partner`的智能体系统的静态记忆提取模块,负责从对话中将关于用户的事实性记忆提取出来,提取的信息应为标志性的,较少变动的事实信息,提取时应当以该智能体的视角为第一视角。
功能说明
你需要根据用户对话记录(messages)和现有静态记忆(existedStaticMemory),分析并输出需要新增或修改的静态记忆项。静态记忆指用户长期有效的个人信息、习惯偏好等常识性数据。

View File

@@ -1,5 +1,6 @@
package work.slhaf.agent.core.interaction.data;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import lombok.Data;
@@ -18,6 +19,6 @@ public class InteractionContext {
protected JSONObject coreContext;
protected JSONObject moduleContext;
protected JSONObject modulePrompt;
protected JSONArray modulePrompt;
protected JSONObject coreResponse;
}

View File

@@ -147,7 +147,7 @@ public class MemoryGraph extends PersistableObject {
memoryGraph = deserialize(id);
} else {
FileUtils.createParentDirectories(filePath.toFile().getParentFile());
memoryGraph = new MemoryGraph(id,basicCharacter);
memoryGraph = new MemoryGraph(id, basicCharacter);
memoryGraph.serialize();
}
log.info("MemoryGraph注册完毕...");
@@ -160,7 +160,7 @@ public class MemoryGraph extends PersistableObject {
//先写入到临时文件,如果正常写入则覆盖原文件
Path filePath = getFilePath(this.id + "-temp");
Files.createDirectories(Path.of(STORAGE_DIR));
try {
try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath.toFile()));
oos.writeObject(this);
oos.close();
@@ -346,65 +346,73 @@ public class MemoryGraph extends PersistableObject {
}
public MemoryResult selectMemory(String topicPathStr) throws IOException, ClassNotFoundException {
List<String> topicPath = List.of(topicPathStr.split("->"));
List<String> path = new ArrayList<>(topicPath);
MemoryResult memoryResult = new MemoryResult();
//每日刷新缓存
checkCacheDate();
//检测缓存并更新计数, 查看是否需要放入缓存
updateCacheCounter(topicPath);
//查看是否存在缓存,如果存在,则直接返回
if (memorySliceCache.containsKey(path)) {
return memorySliceCache.get(path);
}
CopyOnWriteArrayList<MemorySliceResult> targetSliceList = new CopyOnWriteArrayList<>();
String targetTopic = path.getLast();
TopicNode targetParentNode = getTargetParentNode(path, targetTopic);
List<List<String>> relatedTopics = new ArrayList<>();
//终点记忆节点
MemorySliceResult sliceResult = new MemorySliceResult();
for (MemoryNode memoryNode : targetParentNode.getTopicNodes().get(targetTopic).getMemoryNodes()) {
List<MemorySlice> 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());
List<String> topicPath = List.of(topicPathStr.split("->"));
try {
List<String> path = new ArrayList<>(topicPath);
//每日刷新缓存
checkCacheDate();
//检测缓存并更新计数, 查看是否需要放入缓存
updateCacheCounter(topicPath);
//查看是否存在缓存,如果存在,则直接返回
if (memorySliceCache.containsKey(path)) {
return memorySliceCache.get(path);
}
for (MemorySlice memorySlice : endpointMemorySliceList) {
if (memorySlice.getRelatedTopics() != null) {
relatedTopics.addAll(memorySlice.getRelatedTopics());
CopyOnWriteArrayList<MemorySliceResult> targetSliceList = new CopyOnWriteArrayList<>();
String targetTopic = path.getLast();
TopicNode targetParentNode = getTargetParentNode(path, targetTopic);
List<List<String>> relatedTopics = new ArrayList<>();
//终点记忆节点
MemorySliceResult sliceResult = new MemorySliceResult();
for (MemoryNode memoryNode : targetParentNode.getTopicNodes().get(targetTopic).getMemoryNodes()) {
List<MemorySlice> 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) {
relatedTopics.addAll(memorySlice.getRelatedTopics());
}
}
}
memoryResult.setMemorySliceResult(targetSliceList);
//邻近节点
List<MemorySlice> relatedMemorySlice = new ArrayList<>();
//邻近记忆节点 联系
for (List<String> relatedTopic : relatedTopics) {
List<String> tempTopicPath = new ArrayList<>(relatedTopic);
String tempTargetTopic = tempTopicPath.getLast();
TopicNode tempTargetParentNode = getTargetParentNode(tempTopicPath, tempTargetTopic);
//获取终点节点及其最新记忆节点
TopicNode tempTargetNode = tempTargetParentNode.getTopicNodes().get(tempTopicPath.getLast());
setRelatedMemorySlices(tempTargetNode, relatedMemorySlice);
}
//邻近记忆节点 父级
setRelatedMemorySlices(targetParentNode, relatedMemorySlice);
//将上述结果包装为MemoryResult
memoryResult.setRelatedMemorySliceResult(relatedMemorySlice);
//尝试更新缓存
updateCache(topicPath, memoryResult);
} catch (Exception e) {
log.error("[MemoryGraph] selectMemory error: ", e);
log.error("[MemoryGraph] 路径: {}", topicPathStr);
log.error("[MemoryGraph] 主题树: {}", getTopicTree());
memoryResult = new MemoryResult();
memoryResult.setRelatedMemorySliceResult(new ArrayList<>());
memoryResult.setMemorySliceResult(new CopyOnWriteArrayList<>());
}
memoryResult.setMemorySliceResult(targetSliceList);
//邻近节点
List<MemorySlice> relatedMemorySlice = new ArrayList<>();
//邻近记忆节点 联系
for (List<String> relatedTopic : relatedTopics) {
List<String> tempTopicPath = new ArrayList<>(relatedTopic);
String tempTargetTopic = tempTopicPath.getLast();
TopicNode tempTargetParentNode = getTargetParentNode(tempTopicPath, tempTargetTopic);
//获取终点节点及其最新记忆节点
TopicNode tempTargetNode = tempTargetParentNode.getTopicNodes().get(tempTopicPath.getLast());
setRelatedMemorySlices(tempTargetNode, relatedMemorySlice);
}
//邻近记忆节点 父级
setRelatedMemorySlices(targetParentNode, relatedMemorySlice);
//将上述结果包装为MemoryResult
memoryResult.setRelatedMemorySliceResult(relatedMemorySlice);
//尝试更新缓存
updateCache(topicPath, memoryResult);
return memoryResult;
}

View File

@@ -63,10 +63,14 @@ public class CoreModel extends Model implements InteractionModule {
@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;
StringBuilder modulePromptBuilder = new StringBuilder();
for (Object o : interactionContext.getModulePrompt()) {
modulePromptBuilder.append(o).append("\r\n");
}
String modulePromptStr = modulePromptBuilder.toString();
if (!modulePromptStr.equals(promptCache)) {
coreModel.getMessages().set(0, new Message(ChatConstant.Character.SYSTEM, ModelConstant.CORE_MODEL_PROMPT + "\r\n" + modulePromptBuilder));
promptCache = modulePromptStr;
}
log.debug("[CoreModel] 当前消息列表大小: {}", this.messages.size());
log.debug("[CoreModel] 当前核心prompt内容: {}", interactionContext.getCoreContext().toString());

View File

@@ -50,10 +50,10 @@ public class MemorySelector implements InteractionModule {
无新增输出字段
##注意
a. 这些字段中可能出现的第一人称描述都是指"",即当前用户正在对话的对象
a. 这些字段中可能出现的第一人称描述都是指`Partner`,即你所属的智能体,当前用户正在对话的对象
b. `dialog_map`和`user_dialog_map`中,值都将以`用户昵称[用户uuid]`开头,你需要正确区分不同用户
c. 若`text`字段,即用户的真正输入内容未涉及`dialog_map`, `user_dialog_map`等字段中的内容,你需要仅根据用户的输入来确定如何回复
c. 若`text`字段,即用户的真正输入内容未涉及`dialog_map`, `user_dialog_map`等字段中的内容,你需要仅根据用户的输入来确定如何回复.当用户未提及时,这两个字段中的内容时,你不需要主动提起.
d. 做出回应时,你需要考虑上述新增字段与当前的时间差异
""";
private MemoryManager memoryManager;
@@ -76,34 +76,47 @@ public class MemorySelector implements InteractionModule {
@Override
public void execute(InteractionContext interactionContext) throws IOException, ClassNotFoundException, InterruptedException {
log.debug("[MemorySelector] 记忆回溯流程开始...");
String userId =interactionContext.getUserId();
String userId = interactionContext.getUserId();
//获取主题路径
ExtractorResult extractorResult = memorySelectExtractor.execute(interactionContext);
if (extractorResult.isRecall() || !extractorResult.getMatches().isEmpty()) {
log.debug("[MemorySelector] 触发记忆回溯...");
//查找切片
List<MemoryResult> memoryResultList = new ArrayList<>();
setMemoryResultList(memoryResultList, extractorResult.getMatches(),userId);
setMemoryResultList(memoryResultList, extractorResult.getMatches(), userId);
//评估切片
EvaluatorInput evaluatorInput = EvaluatorInput.builder()
.input(interactionContext.getInput())
.memoryResults(memoryResultList)
.messages(memoryManager.getChatMessages())
.build();
log.debug("[MemorySelector] 切片评估输入: {}",evaluatorInput);
log.debug("[MemorySelector] 切片评估输入: {}", evaluatorInput);
List<EvaluatedSlice> memorySlices = sliceSelectEvaluator.execute(evaluatorInput);
log.debug("[MemorySelector] 切片评估结果: {}",memorySlices);
memoryManager.updateActivatedSlices(userId,memorySlices);
log.debug("[MemorySelector] 切片评估结果: {}", memorySlices);
memoryManager.updateActivatedSlices(userId, memorySlices);
}
if (extractorResult.isRecall()) {
memoryManager.getActivatedSlices().clear();
}
//设置上下文
interactionContext.getCoreContext().put("memory_slices",memoryManager.getActivatedSlices().get(userId));
interactionContext.getCoreContext().put("static_memory",memoryManager.getStaticMemory(userId));
interactionContext.getCoreContext().put("dialog_map",memoryManager.getDialogMap());
interactionContext.getCoreContext().put("user_dialog_map",memoryManager.getUserDialogMap(userId));
interactionContext.getCoreContext().put("memory_slices", memoryManager.getActivatedSlices().get(userId));
interactionContext.getCoreContext().put("static_memory", memoryManager.getStaticMemory(userId));
interactionContext.getCoreContext().put("dialog_map", memoryManager.getDialogMap());
interactionContext.getCoreContext().put("user_dialog_map", memoryManager.getUserDialogMap(userId));
interactionContext.getModulePrompt().put("memory", modulePrompt);
log.debug("[MemorySelector] 记忆回溯结果: {}",interactionContext);
boolean recall;
if (memoryManager.getActivatedSlices().get(userId) == null) {
recall = false;
} else {
recall = !memoryManager.getActivatedSlices().get(userId).isEmpty();
}
interactionContext.getModuleContext().put("recall", recall);
if (recall) {
interactionContext.getModuleContext().put("recall_count", memoryManager.getActivatedSlices().get(userId).size());
}
interactionContext.getModulePrompt().add( modulePrompt);
log.debug("[MemorySelector] 记忆回溯结果: {}", interactionContext);
}
private void setMemoryResultList(List<MemoryResult> memoryResultList, List<ExtractorMatchData> matches, String userId) throws IOException, ClassNotFoundException {
@@ -117,7 +130,7 @@ public class MemorySelector implements InteractionModule {
};
if (memoryResult == null) continue;
memoryResultList.add(memoryResult);
}catch (UnExistedDateIndexException | UnExistedTopicException e) {
} catch (UnExistedDateIndexException | UnExistedTopicException e) {
log.error("[MemorySelector] 不存在的记忆索引! 请尝试更换更合适的主题提取LLM!");
}
}

View File

@@ -36,6 +36,7 @@ public class MemoryUpdater implements InteractionModule {
private static final long SCHEDULED_UPDATE_INTERVAL = 10 * 1000;
private static final long UPDATE_TRIGGER_INTERVAL = 30 * 60 * 1000;
private static final int TRIGGER_TOKEN_LIMIT = 5 * 1000;
private static final int TOKEN_PER_RECALL = 230;
private MemoryManager memoryManager;
private InteractionThreadPoolExecutor executor;
@@ -94,7 +95,15 @@ public class MemoryUpdater implements InteractionModule {
executor.execute(() -> {
//如果token 大于阈值,则更新记忆
JSONObject moduleContext = interactionContext.getModuleContext();
if (moduleContext.getIntValue("total_token") > TRIGGER_TOKEN_LIMIT) {
boolean recall = moduleContext.getBoolean("recall");
int tokenLimit = TRIGGER_TOKEN_LIMIT;
if (recall) {
log.debug("[MemoryUpdater] 存在回忆");
int recallCount = moduleContext.getIntValue("recall_count");
log.debug("[MemoryUpdater] 记忆切片数量 [{}]",recallCount);
tokenLimit += recallCount * TOKEN_PER_RECALL;
}
if (moduleContext.getIntValue("total_token") > tokenLimit) {
try {
log.debug("[MemoryUpdater] 记忆更新: token超限");
updateMemory();

View File

@@ -157,7 +157,9 @@ public class MemorySummarizer extends Model {
""";
public static final String MULTI_SUMMARIZE_PROMPT = """
DialogueTopicMapper 提示词
MULTI_SUMMARIZE_PROMPT 提示词
你是名为`Partner`的智能体的消息总结模块,负责整理对话摘要、以用户意图为锚点提取该段对话的主题路径, 故整理时,你需要以该智能体的视角为第一视角。
功能说明
分析对话内容并生成最深为7层的多层次主题路径支持智能扩展主题树结构根据用户意图动态调整路径生成策略。
在保证符合以下要求的同时尽快输出
@@ -170,7 +172,7 @@ public class MemorySummarizer extends Model {
0. **只需要输出所需的JSON文本**
1. 核心结构(保持原格式):
{
"summary": "", // 精简摘要(100-150字
"summary": "", // 包含完整细节的摘要(200-500字
"topicPath": "", // 主路径(领域纯净的完整抽象链)
"relatedTopicPath": [], // 相关路径(允许跨领域)
"isPrivate": false
@@ -206,7 +208,7 @@ public class MemorySummarizer extends Model {
├── 自由行
└── 跟团游
处理流程
主题路径生成流程
0. 明确身份阶段:
a. 需要以assistant的视角为分析视角
1. 意图分析阶段:
@@ -220,6 +222,18 @@ public class MemorySummarizer extends Model {
a. 新增节点必须通过逻辑验证
b. 技术术语需符合行业标准
摘要生成流程
0. 明确身份以assistant的视角为分析视角
1. 针对消息列表按顺序逐条进行扫描
2. 每扫描到一条消息就在摘要中添加“主体+事件”的信息,如:
```
对方询问...我回应...;
对方主动问候...我询问他...
```
可省去无用描写,但需保留所有细节
3. 扫描完毕后将完整的摘要作为summary字段填入最终将返回的消息中
完整示例
示例:
输入:{
@@ -232,7 +246,7 @@ public class MemorySummarizer extends Model {
]
}
输出:{
"summary": "用户分享欧洲自由行经历并讨论夜景照片处理...",
"summary": "对方分享欧洲自由行经历并讨论夜景照片处理...我向他推荐了...",
"topicPath": "生活->旅行->自由行->欧洲->法国->巴黎铁塔",
"relatedTopicPath": [
"艺术->摄影->夜景拍摄",
@@ -250,8 +264,10 @@ public class MemorySummarizer extends Model {
public static final String TOTAL_SUMMARIZE_PROMPT = """
TOTAL_SUMMARIZER 提示词
你是名为`Partner`的智能体系统的多摘要汇总模块,负责将多个用户的独立对话摘要进行汇总,整体为一份整体性总结,整理时需要注意以该智能体的视角为第一视角。
功能说明
需要根据输入的多个独立用户对话摘要生成一份综合性的总结报告。每个用户的对话内容彼此无关联需保持原始信息的同时进行概括性整合最终输出标准化JSON格式的响应。
需要根据输入的多个独立用户对话摘要生成一份综合性的总结报告。每个用户的对话内容彼此无关联需保持原始信息的同时进行概括性整合最终输出标准化JSON格式的响应。
注意,输入的需要进行摘要的内容中,“我”指的是你所在的智能体系统,“对方”指代该摘要内部交互的用户,进行整体总结时,需要以具体的用户昵称来区分不同原始摘要中的不同用户,第一人称仍保持为该智能体的视角。
输入字段说明
• 输入数据为JSON对象

View File

@@ -1,5 +1,6 @@
package work.slhaf.agent.modules.preprocess;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
@@ -69,7 +70,7 @@ public class PreprocessExecutor {
context.setModuleContext(new JSONObject());
context.setModulePrompt(new JSONObject());
context.setModulePrompt(new JSONArray());
context.setSingle(inputData.isSingle());
context.setFinished(false);