记忆模块初步完成

- 为 MemorySlice 添加字段 embedding, embedded 为后续向量匹配提供基础
- 在 PreprocessExecutor 中添加了针对上下文的预填充,现经过记忆模块后,可直接交由主模型进行处理
- 实现了 MemorySelectExtractor, SliceEvaluator, MemorySelector 为主的记忆模块, 并新增了必要的实体类
- 为 MemorySelectExtractor, SliceEvaluator 设计了提示词
This commit is contained in:
2025-04-23 19:27:11 +08:00
parent f31176336d
commit 4e28adbc52
29 changed files with 513 additions and 274 deletions

View File

@@ -4,194 +4,100 @@ import org.junit.jupiter.api.Test;
import work.slhaf.agent.common.chat.ChatClient;
import work.slhaf.agent.common.chat.constant.ChatConstant;
import work.slhaf.agent.common.chat.pojo.Message;
import work.slhaf.agent.common.model.ModelConstant;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class AITest {
@Test
public void test1() {
public void topicExtractorTest() {
String input = """
{
"text": "之前处理过Node.js的并发问题还有Express中间件开发",
"topic_tree": "
编程 (3)[root]
├── JavaScript (3)
├── NodeJS (2)
├── 并发处理 (0)
└── 事件循环 (0)
└── Express (1)
└── 中间件 (0)
└── Python (2)",
"date": "2024-04-10"
"text": "回到昨天讨论的TypeScript装饰器实现AOP结合现在需要的微服务熔断机制",
"topic_tree": "编程\\n├── JavaScript\\n│ ├── NodeJS\\n│ └── TypeScript\\n│ ├── 类型系统\\n│ ├── 装饰器\\n│ │ ├── 类装饰器\\n│ │ └── 方法装饰器\\n│ └── 编译配置\\n└── 系统设计\\n ├── 微服务\\n │ ├── 服务发现\\n │ └── 熔断机制\\n └── 消息队列",
"date": "2024-05-20",
"history": [
{
"role": "user",
"content": "我们的TS项目需要实现日志切面装饰器方案和中间件方案哪个更合适"
},
{
"role": "assistant",
"content": "两种方案各有优劣装饰器的优势在于1) 声明式编程 2) 精确到方法级别 3) 元编程能力。中间件方案则更适合请求级别的处理。具体到实现细节方法装饰器可以通过Reflect Metadata..."
},
{
"role": "user",
"content": "在微服务架构中如何设计跨服务的统一日志收集特别是Kubernetes环境下的实现"
},
{
"role": "assistant",
"content": "K8s环境下的日志方案需要考虑1) DaemonSet部署Fluentd 2) 应用层的日志规范 3) EFK栈的索引策略。对于NodeJS应用建议使用Winston配合..."\s
},
{
"role": "user",
"content": "现在遇到服务雪崩问题,需要实现熔断降级"
}
]
}
""";
run(input);
run(input, ModelConstant.TOPIC_EXTRACTOR_PROMPT);
}
private void run(String input) {
@Test
public void sliceEvaluatorTest(){
String input = """
{
"text": "请结合我们之前讨论的美联储加息影响分析下当前黄金ETF和国债逆回购的组合策略",
"history": [
{
"role": "user",
"content": "美联储连续加息对A股有什么影响"
},
{
"role": "assistant",
"content": "历史数据分析显示:\\n1. 北上资金流动:利空流动性敏感板块\\n2. 汇率压力:增加出口企业汇兑收益\\n3. 行业分化:利好银行/出口,利空地产/消费\\n具体机制..."
},
{
"role": "user",
"content": "黄金作为避险资产现在可以配置吗?"
},
{
"role": "assistant",
"content": "黄金配置建议:\\n• 实际利率是核心影响因素\\n• 短期受美元指数压制\\n• 长期抗通胀属性仍在\\n建议比例..."
}
],
"memory_slices": [
{
"summary": "美联储加息周期的大类资产配置策略研究包含历史回测数据2004-2006、2015-2018两次加息周期中黄金、美债、新兴市场股市的表现以及当前特殊环境高通胀+地缘冲突)下的策略调整建议。",
"id": 1685587200,
"date": "2025-06-20"
},
{
"summary": "黄金ETF与国债逆回购的组合优化模型通过波动率分析给出了不同风险偏好下的最优配置比例特别讨论了在流动性紧张时期如何利用逆回购对冲黄金波动。",
"id": 1685673600,
"date": "2025-06-21"
},
{
"summary": "A股行业轮动与美联储政策的相关性研究建立了包含利率敏感度、外资持仓比例、出口依赖度等因子的分析框架并给出了当前环境下的行业配置建议。",
"id": 1685760000,
"date": "2025-06-22"
},
{
"summary": "跨境资本流动监测指标体系详解包含利差模型、风险偏好指标VIX指数、以及中国特有的资本管制有效性分析。",
"id": 1685846400,
"date": "2025-06-23"
}
]
}
""";
run(input,ModelConstant.SLICE_EVALUATOR_PROMPT);
}
private void run(String input, String prompt) {
ChatClient client = new ChatClient("https://open.bigmodel.cn/api/paas/v4/chat/completions", "3db444552530b7742b0c53425fb93dcc.LcVwYjByht9AC3N9", "glm-4-flash-250414");
List<Message> messages = new ArrayList<>();
messages.add(new Message(ChatConstant.Character.SYSTEM, """
MemorySelectExtractor 提示词
功能说明
你需要根据用户输入的JSON数据分析其`text`和`history`字段内容判断是否需要通过主题路径或日期进行记忆查询并返回标准化格式的JSON响应。
注意你只需要返回对应的JSON文本
输入字段说明
• `text`: 用户当前输入的文本内容
• `topic_tree`: 当前可用的主题树结构(多层级结构,需返回从根节点到目标节点的完整路径)
• `date`: 当前对话发生的日期(用于时间推理)
• `history`: 用户与LLM的完整对话历史用于主题连续性判断
输出规则
1. 基本响应格式:
{
"recall": boolean,
"matches": [
// 匹配项列表
]
}
2. 主题提取规则:
• 当当前`text`涉及新主题(与`history`最后N轮对话主题明显不同
◦ 必须进行主题提取
◦ 匹配`topic_tree`中最接近的完整路径(从根节点到目标节点,如"编程->JavaScript->NodeJS->并发处理"
• 当主题与历史对话连续时:
◦ 除非包含明确的新子主题,否则不重复提取相同主题路径
3. 日期提取规则(保持不变):
• 仅接受具体日期YYYY-MM-DD格式
• 拒绝所有模糊日期表达
4. 特殊处理:
• 当检测到主题切换但无法匹配`topic_tree`时:
{
"recall": false,
"matches": []
}
• 当历史对话为空时:
◦ 视为新主题,按常规规则处理
决策流程
1. 首先分析`history`判断当前对话主题上下文
2. 然后分析`text`
a. 检测是否包含具体日期→添加date类型
b. 检测是否包含新主题→添加topic类型
3. 最终综合判断`recall`值
完整示例
示例1主题延续
输入:{
"text": "关于NodeJS的并发处理还有哪些要注意的",
"topic_tree": "
编程
├── JavaScript
│ ├── NodeJS
│ │ ├── 并发处理
│ │ └── 事件循环
│ └── Express
│ └── 中间件
└── Python",
"date": "2024-04-20",
"history": [
{"role": "user", "content": "说说NodeJS的并发处理机制"},
{"role": "assistant", "content": "NodeJS的并发处理主要通过..."}
]
}
输出:{
"recall": false,
"matches": []
}
示例2主题切换
输入:{
"text": "现在我想了解Express中间件的原理",
"topic_tree": "
编程
├── JavaScript
│ ├── NodeJS
│ │ ├── 并发处理
│ │ └── 事件循环
│ └── Express
│ └── 中间件
└── Python",
"date": "2024-04-20",
"history": [
{"role": "user", "content": "NodeJS的并发处理怎么实现"},
{"role": "assistant", "content": "需要..."}
]
}
输出:{
"recall": true,
"matches": [
{"type": "topic", "text": "编程->JavaScript->Express->中间件"}
]
}
示例3混合情况
输入:{
"text": "2024-04-15讨论的Python内容和现在的Express需求",
"topic_tree": "
编程
├── JavaScript
│ ├── NodeJS
│ │ ├── 并发处理
│ │ └── 事件循环
│ └── Express
│ └── 中间件
└── Python",
"date": "2024-04-20",
"history": [
{"role": "user", "content": "需要了解Express框架"},
{"role": "assistant", "content": "Express是..."}
]
}
输出:{
"recall": true,
"matches": [
{"type": "date", "text": "2024-04-15"},
{"type": "topic", "text": "编程->Python"}
]
}
示例4模糊日期
输入:{
"text": "上周说的那个JavaScript特性",
"topic_tree": "
编程
├── JavaScript
│ ├── NodeJS
│ │ ├── 并发处理
│ │ └── 事件循环
│ └── Express
│ └── 中间件
└── Python",
"date": "2024-04-20",
"history": [...]
}
输出:{
"recall": false,
"matches": []
}
"""));
messages.add(new Message(ChatConstant.Character.SYSTEM, prompt));
messages.add(new Message(ChatConstant.Character.USER, input));
System.out.println(client.runChat(messages).getMessage());

View File

@@ -39,7 +39,7 @@ class SearchTest {
MemorySlice oldJavaMemory = createMemorySlice("javaOld");
MemoryNode oldNode = new MemoryNode();
oldNode.setLocalDate(yesterday);
oldNode.setMemorySliceList(List.of(oldJavaMemory));
// oldNode.setMemorySliceList(List.of(oldJavaMemory));
}
// 场景1查询存在的完整主题路径含相关主题
@@ -121,7 +121,7 @@ class SearchTest {
// 创建昨日记忆节点并添加到主题节点
MemoryNode oldMemoryNode = new MemoryNode();
oldMemoryNode.setLocalDate(yesterday);
oldMemoryNode.setMemorySliceList(new ArrayList<>(List.of(oldDbMemory)));
// oldMemoryNode.setMemorySliceList(new ArrayList<>(List.of(oldDbMemory)));
dbTopicNode.getMemoryNodes().add(oldMemoryNode);
// 对记忆节点进行日期排序根据compareTo方法