Commit ff5f5a198c776e7db6654be94c29fa4b0e003637

Authored by qianbao
1 parent 6f97d3bd

AI 对于时间的处理

src/main/java/com/xly/config/OperableChatMemoryProvider.java
1 1 package com.xly.config;
2 2  
3   -
4 3 import dev.langchain4j.memory.ChatMemory;
5 4 import dev.langchain4j.memory.chat.ChatMemoryProvider;
6 5 import dev.langchain4j.memory.chat.MessageWindowChatMemory;
7 6 import dev.langchain4j.data.message.ChatMessage;
8 7 import org.springframework.stereotype.Component;
9 8  
  9 +import java.util.ArrayList;
10 10 import java.util.List;
11 11 import java.util.Map;
12 12 import java.util.Objects;
13 13 import java.util.concurrent.ConcurrentHashMap;
  14 +import java.util.stream.Collectors;
14 15  
15 16 /**
16 17 * 可操作的ChatMemoryProvider:获取消息对象+清除记忆(指定/全量)+删除单条消息
... ... @@ -20,7 +21,7 @@ import java.util.concurrent.ConcurrentHashMap;
20 21 public class OperableChatMemoryProvider implements ChatMemoryProvider {
21 22 // 核心缓存:memoryId -> ChatMemory,保证一个会话/用户对应唯一记忆实例
22 23 private final Map<Object, ChatMemory> memoryCache = new ConcurrentHashMap<>();
23   - // 记忆最大消息数,根据业务需求调整(原配置为10)
  24 + // 记忆最大消息数,根据业务需求调整
24 25 private static final int MAX_MESSAGE_SIZE = 100;
25 26  
26 27 /**
... ... @@ -35,56 +36,193 @@ public class OperableChatMemoryProvider implements ChatMemoryProvider {
35 36 return memoryCache.computeIfAbsent(finalMemId, k -> MessageWindowChatMemory.withMaxMessages(MAX_MESSAGE_SIZE));
36 37 }
37 38  
38   - // ===================== 1. 获取消息对象(当前memoryId) =====================
  39 + // ===================== 1. 获取消息对象 =====================
  40 +
39 41 /**
40   - * 获取当前会话/用户的全部消息列表(含用户消息、AI消息,按对话顺序)
  42 + * 获取当前会话/用户的全部消息列表
41 43 */
42 44 public List<ChatMessage> getCurrentChatMessages(Object memoryId) {
43 45 if (Objects.isNull(memoryId)) {
44   - return List.of();
  46 + return new ArrayList<>();
45 47 }
46   - // 从ChatMemory中获取原生消息列表
47   - return this.get(memoryId).messages();
  48 + // 返回一个新的列表,避免直接操作内部列表
  49 + return new ArrayList<>(this.get(memoryId).messages());
48 50 }
49 51  
50   - // ===================== 2. 清除记忆(核心需求) =====================
  52 + // ===================== 2. 清除记忆 =====================
  53 +
51 54 /**
52   - * 清空【指定memoryId】的全部记忆(最常用,如前端「清空对话」)
  55 + * 清空【指定memoryId】的全部记忆
53 56 */
54 57 public void clearSpecifiedMemory(Object memoryId) {
55 58 if (Objects.nonNull(memoryId)) {
56   - // 调用ChatMemory原生clear(),清空该实例所有消息
57 59 this.get(memoryId).clear();
58 60 }
59 61 }
60 62  
61 63 /**
62   - * 全量清除【所有memoryId】的记忆(系统级清理,如定时任务/后台操作)
  64 + * 全量清除【所有memoryId】的记忆
63 65 */
64 66 public void clearAllMemory() {
65   - // 遍历所有记忆实例,逐个调用原生clear()清空
66 67 memoryCache.values().forEach(ChatMemory::clear);
67   - // 可选:彻底清空缓存,销毁所有实例(释放内存,后续会重新创建)
68   - // memoryCache.clear();
69 68 }
70 69  
71   - // ===================== 3. 精准操作:删除单条消息 =====================
  70 + // ===================== 3. 删除单条消息(完全重新设置方案) =====================
  71 +
72 72 /**
73   - * 删除当前memoryId的指定单条消息(如删除错误消息)
74   - * @param message 要删除的消息对象(从getCurrentChatMessages中获取)
  73 + * 删除指定消息(通过完全重新设置消息列表的方式)
  74 + * @param memoryId 会话ID
  75 + * @param messageToDelete 要删除的消息对象
  76 + * @return 删除后的最新消息列表
75 77 */
76   - public void deleteSingleMessage(Object memoryId, ChatMessage message) {
77   - if (Objects.nonNull(memoryId) && Objects.nonNull(message)) {
78   - ChatMemory currentMemory = this.get(memoryId);
79   - // 删除指定消息
80   - currentMemory.messages().remove(message);
81   - // 刷新记忆,保证MessageWindowChatMemory的最大消息数限制生效
82   -// currentMemory.update(currentMemory.messages());
  78 + public List<ChatMessage> deleteSingleMessage(Object memoryId, ChatMessage messageToDelete) {
  79 + if (Objects.isNull(memoryId) || Objects.isNull(messageToDelete)) {
  80 + return getCurrentChatMessages(memoryId);
  81 + }
  82 +
  83 + // 步骤1: 获取当前所有消息
  84 + ChatMemory currentMemory = this.get(memoryId);
  85 + List<ChatMessage> currentMessages = new ArrayList<>(currentMemory.messages());
  86 +
  87 + // 从后往前查找内容匹配的最后一条消息
  88 + int indexToDelete = -1;
  89 + for (int i = currentMessages.size() - 1; i >= 0; i--) {
  90 + ChatMessage msg = currentMessages.get(i);
  91 + if (isSameMessage(msg, messageToDelete)) {
  92 + indexToDelete = i;
  93 + break;
  94 + }
  95 + }
  96 + // 如果找到匹配的消息
  97 + if (indexToDelete >= 0) {
  98 + List<ChatMessage> filteredMessages = new ArrayList<>(currentMessages);
  99 + filteredMessages.remove(indexToDelete);
  100 + return rebuildMemoryWithMessages(memoryId, filteredMessages);
  101 + }
  102 + // 步骤4: 完全重新设置消息列表
  103 + return rebuildMemoryWithMessages(memoryId, currentMessages);
  104 + }
  105 + public List<ChatMessage> deleteUserLasterMessage(Object memoryId) {
  106 + if (Objects.isNull(memoryId)) {
  107 + return getCurrentChatMessages(memoryId);
83 108 }
  109 + // 步骤1: 获取当前所有消息
  110 + ChatMemory currentMemory = this.get(memoryId);
  111 + List<ChatMessage> currentMessages = new ArrayList<>(currentMemory.messages());
  112 +
  113 + // 从后往前查找内容匹配的最后一条消息
  114 + int indexToDelete = currentMessages.size()-1;
  115 + // 如果找到匹配的消息
  116 + if (indexToDelete >= 0) {
  117 + List<ChatMessage> filteredMessages = new ArrayList<>(currentMessages);
  118 + filteredMessages.remove(indexToDelete);
  119 + return rebuildMemoryWithMessages(memoryId, filteredMessages);
  120 + }
  121 + // 步骤4: 完全重新设置消息列表
  122 + return rebuildMemoryWithMessages(memoryId, currentMessages);
  123 + }
  124 + /**
  125 + * 批量删除多条消息
  126 + * @param memoryId 会话ID
  127 + * @param messagesToDelete 要删除的消息列表
  128 + * @return 删除后的最新消息列表
  129 + */
  130 + public List<ChatMessage> deleteMessages(Object memoryId, List<ChatMessage> messagesToDelete) {
  131 + if (Objects.isNull(memoryId) || messagesToDelete == null || messagesToDelete.isEmpty()) {
  132 + return getCurrentChatMessages(memoryId);
  133 + }
  134 +
  135 + // 获取当前所有消息
  136 + ChatMemory currentMemory = this.get(memoryId);
  137 + List<ChatMessage> currentMessages = new ArrayList<>(currentMemory.messages());
  138 +
  139 + // 过滤掉所有要删除的消息
  140 + List<ChatMessage> filteredMessages = currentMessages.stream()
  141 + .filter(msg -> messagesToDelete.stream().noneMatch(delMsg -> isSameMessage(msg, delMsg)))
  142 + .collect(Collectors.toList());
  143 +
  144 + // 如果消息数量没有变化,直接返回
  145 + if (filteredMessages.size() == currentMessages.size()) {
  146 + return currentMessages;
  147 + }
  148 +
  149 + // 重新设置消息列表
  150 + return rebuildMemoryWithMessages(memoryId, filteredMessages);
  151 + }
  152 +
  153 + /**
  154 + * 判断两条消息是否相同(支持根据内容、类型等多维度匹配)
  155 + */
  156 + private boolean isSameMessage(ChatMessage msg1, ChatMessage msg2) {
  157 +// if (msg1 == msg2) return true; // 同一对象引用
  158 +// if (msg1 == null || msg2 == null) return false;
  159 +//
  160 +// // 根据消息类型和内容判断
  161 +// // 方式1: 根据对象相等性
  162 +// if (msg1.equals(msg2)) return true;
  163 + // 方式2: 根据消息类型和文本内容(适用于大多数场景)
  164 + if (msg1.type() == msg2.type()) {
  165 + // 如果有唯一ID或时间戳,也可以加入判断
  166 + // 这里简单使用文本内容判断
  167 + String text1 = extractText(msg1);
  168 + String text2 = extractText(msg2);
  169 + return Objects.equals(text1, text2);
  170 + }
  171 +
  172 + return false;
  173 + }
  174 +
  175 + /**
  176 + * 从消息中提取文本内容
  177 + */
  178 + private String extractText(ChatMessage message) {
  179 + // 根据ChatMessage的实际类型提取文本
  180 + // 这里需要根据您的具体消息实现来调整
  181 + try {
  182 + // 尝试通过toString获取内容
  183 + return message.toString();
  184 + } catch (Exception e) {
  185 + return "";
  186 + }
  187 + }
  188 +
  189 + /**
  190 + * 核心方法:使用过滤后的消息列表重建记忆
  191 + */
  192 + private List<ChatMessage> rebuildMemoryWithMessages(Object memoryId, List<ChatMessage> messages) {
  193 + // 从缓存中移除原实例
  194 + ChatMemory oldMemory = memoryCache.remove(memoryId);
  195 +
  196 + // 创建新的ChatMemory实例
  197 + MessageWindowChatMemory newMemory = MessageWindowChatMemory.withMaxMessages(MAX_MESSAGE_SIZE);
  198 +
  199 + // 由于ChatMemory接口没有直接添加消息的方法,我们需要通过特定方式添加
  200 + // 方式1: 如果MessageWindowChatMemory有add方法(需要通过反射或强制类型转换)
  201 + try {
  202 + // 通过反射调用add方法(如果存在)
  203 + java.lang.reflect.Method addMethod = newMemory.getClass().getMethod("add", ChatMessage.class);
  204 + addMethod.setAccessible(true);
  205 + for (ChatMessage message : messages) {
  206 + addMethod.invoke(newMemory, message);
  207 + }
  208 + } catch (Exception e) {
  209 + // 方式2: 如果无法通过反射添加,我们需要使用替代方案
  210 + // 这里可以记录日志
  211 + System.err.println("无法通过反射添加消息: " + e.getMessage());
  212 +
  213 + // 方式3: 使用clear后通过某种方式重新添加
  214 + // 注意:这取决于具体的ChatMemory实现
  215 + }
  216 +
  217 + // 将新实例放入缓存
  218 + memoryCache.put(memoryId, newMemory);
  219 +
  220 + // 返回重建后的消息列表
  221 + return new ArrayList<>(newMemory.messages());
84 222 }
85 223  
86 224 /**
87   - * 移除并清除指定记忆(清空消息+从缓存删除实例,彻底释放资源,适用于过期会话
  225 + * 移除并清除指定记忆(清空消息+从缓存删除实例
88 226 */
89 227 public void removeAndClearMemory(Object memoryId) {
90 228 if (Objects.nonNull(memoryId)) {
... ... @@ -94,4 +232,4 @@ public class OperableChatMemoryProvider implements ChatMemoryProvider {
94 232 }
95 233 }
96 234 }
97 235 -}
  236 +}
98 237 \ No newline at end of file
... ...
src/main/java/com/xly/service/XlyErpService.java
... ... @@ -24,6 +24,8 @@ import com.xly.util.SqlValidateUtil;
24 24 import com.xly.util.ValiDataUtil;
25 25 import dev.langchain4j.agent.tool.ToolExecutionRequest;
26 26 import dev.langchain4j.data.message.AiMessage;
  27 +import dev.langchain4j.data.message.ChatMessage;
  28 +import dev.langchain4j.data.message.ChatMessageType;
27 29 import dev.langchain4j.model.chat.ChatLanguageModel;
28 30 import dev.langchain4j.model.ollama.OllamaChatModel;
29 31 import dev.langchain4j.service.AiServices;
... ... @@ -96,7 +98,7 @@ public class XlyErpService {
96 98 if (session.getCurrentScene() != null
97 99 && Objects.equals(session.getCurrentScene().getSSceneNo(), "ChatZone"))
98 100 {
99   - return getChatiAgent(input, session);
  101 + return getChatiAgent(input, session,StrUtil.EMPTY);
100 102 }
101 103  
102 104 // 3. 未选场景:先展示场景选择界面,处理用户序号选择
... ... @@ -108,9 +110,12 @@ public class XlyErpService {
108 110 ErpAiAgent aiAgent = createErpAiAgent(userId, input, session);
109 111 // 没有选择到场景,进闲聊模式
110 112 if (aiAgent == null){
111   - return getChatiAgent (input, session);
  113 + return getChatiAgent (input, session,StrUtil.EMPTY);
112 114 }
  115 + List<ChatMessage> chatMessage = operableChatMemoryProvider.getCurrentChatMessages(session.getUserId());
113 116 String sResponMessage = aiAgent.chat(userId, input);
  117 + List<ChatMessage> chatMessage2 = operableChatMemoryProvider.getCurrentChatMessages(session.getUserId());
  118 + String sResponMessageOld = StrUtil.EMPTY;
114 119 // 调用方法,参数缺失部分提示,就直接使用方法返回的
115 120 if(session.getCurrentTool() != null
116 121 && session.getSFunPrompts()!=null
... ... @@ -118,9 +123,6 @@ public class XlyErpService {
118 123 // 缺失的参数明细
119 124 sResponMessage = session.getSFunPrompts();
120 125 }
121   - if (session.getCurrentTool()== null){
122   - sResponMessage = StrUtil.EMPTY;
123   - }
124 126 // //说明没有找到动态方法重新走一次
125 127 // if(maxToolRetries==1 && session.getCurrentTool()== null){
126 128 // Thread.sleep(1000);
... ... @@ -136,6 +138,7 @@ public class XlyErpService {
136 138 // }
137 139  
138 140 if (session.getCurrentTool()== null){
  141 + sResponMessageOld = sResponMessage;
139 142 sResponMessage = StrUtil.EMPTY;
140 143 }
141 144 //5.执行工具方法后,清除记忆
... ... @@ -155,7 +158,7 @@ public class XlyErpService {
155 158 }
156 159 //如果返回空的进入闲聊模式
157 160 if (ObjectUtil.isEmpty(sResponMessage)){
158   - return getChatiAgent (input, session);
  161 + return getChatiAgent (input, session,sResponMessageOld);
159 162 }
160 163 if (session.getCurrentScene()!= null ){
161 164 return AiResponseDTO.builder().aiText(sResponMessage)
... ... @@ -463,7 +466,12 @@ public class XlyErpService {
463 466 * @return java.lang.String
464 467 * @Description 获取智普通智能体
465 468 **/
466   - private AiResponseDTO getChatiAgent (String input,UserSceneSession session){
  469 + private AiResponseDTO getChatiAgent (String input,UserSceneSession session,String sResponMessageOld){
  470 + String sceneName = ObjectUtil.isNotEmpty(session.getCurrentScene())?session.getCurrentScene().getSSceneName():StrUtil.EMPTY;
  471 + String methodName = ObjectUtil.isNotEmpty(session.getCurrentTool())?session.getCurrentTool().getSMethodName():"随便聊聊";
  472 + if(ObjectUtil.isNotEmpty(sResponMessageOld)){
  473 + return AiResponseDTO.builder().sSceneName(sceneName).sMethodName(methodName).aiText(sResponMessageOld).systemText(StrUtil.EMPTY).sReturnType(ReturnTypeCode.HTML.getCode()).build();
  474 + }
467 475 ChatiAgent chatiAgent = UserSceneSessionService.CHAT_AGENT_CACHE.get(session.getUserId());
468 476 if(ObjectUtil.isEmpty(chatiAgent)){
469 477 chatiAgent = AiServices.builder(ChatiAgent.class)
... ... @@ -472,9 +480,32 @@ public class XlyErpService {
472 480 .build();
473 481 UserSceneSessionService.CHAT_AGENT_CACHE.put(session.getUserId(), chatiAgent); }
474 482 String sChatMessage = chatiAgent.chat(session.getUserId(), input);
475   - String sceneName = ObjectUtil.isNotEmpty(session.getCurrentScene())?session.getCurrentScene().getSSceneName():StrUtil.EMPTY;
476   - String methodName = ObjectUtil.isNotEmpty(session.getCurrentTool())?session.getCurrentTool().getSMethodName():"随便聊聊";
  483 + //随便聊聊移除系统返回的提示记忆
  484 +// List<ChatMessage> chatMessage = operableChatMemoryProvider.getCurrentChatMessages(session.getUserId());
  485 +// removeMssageSbll(chatMessage, session.getUserId());
  486 +// operableChatMemoryProvider.deleteUserLasterMessage(session.getUserId());
  487 +// chatMessage = operableChatMemoryProvider.getCurrentChatMessages(session.getUserId());
477 488 return AiResponseDTO.builder().sSceneName(sceneName).sMethodName(methodName).aiText(sChatMessage).systemText(StrUtil.EMPTY).sReturnType(ReturnTypeCode.HTML.getCode()).build();
478 489 }
  490 + /***
  491 + * @Author 钱豹
  492 + * @Date 12:14 2026/3/8
  493 + * @Param [chatMessage, memoryId]
  494 + * @return void
  495 + * @Description 随便聊聊移除最后一个AI 返回信息 跟系统询问的随便聊聊
  496 + **/
  497 + private void removeMssageSbll(List<ChatMessage> chatMessage,String memoryId){
  498 + if(chatMessage!=null){
  499 +// operableChatMemoryProvider.deleteSingleMessage(memoryId,chatMessage.get(chatMessage.size()-1));
  500 + for(int i=chatMessage.size()-1;i>0;i--){
  501 + ChatMessage data = chatMessage.get(i);
  502 + ChatMessageType sType = data.type();
  503 + if(ChatMessageType.SYSTEM.equals(sType)){
  504 + operableChatMemoryProvider.deleteSingleMessage(memoryId,data);
  505 + return;
  506 + }
  507 + }
  508 + }
  509 + }
479 510  
480 511 }
481 512 \ No newline at end of file
... ...
src/main/java/com/xly/tool/DynamicToolProvider.java
... ... @@ -551,7 +551,7 @@ public class DynamicToolProvider implements ToolProvider {
551 551 List<String> missing = checkRequiredParams(args, paramRuleData);
552 552 if (!missing.isEmpty()) {
553 553 // 4.1 参数缺失,生成“提问”消息,直接返给客户
554   - String askMsg = buildAskUserMessage(meta, missing);
  554 + String askMsg = buildAskUserMessage(meta, missing,args);
555 555 session.setSFunPrompts(askMsg);
556 556 return String.valueOf(askUserResult(toolExecutionRequest, askMsg));
557 557 }
... ... @@ -576,7 +576,7 @@ public class DynamicToolProvider implements ToolProvider {
576 576 List<String> missingAfter = checkConfirmAfterParam(args, paramRuleData);
577 577 if (!missingAfter.isEmpty()) {
578 578 // 4.1 参数缺失,生成“提问”消息,直接返给客户
579   - String askMsg = buildAskUserMessage(meta, missingAfter);
  579 + String askMsg = buildAskUserMessage(meta, missingAfter,args);
580 580 session.setSFunPrompts(askMsg);
581 581 return String.valueOf(askUserResult(toolExecutionRequest, askMsg));
582 582 }
... ... @@ -739,7 +739,6 @@ public class DynamicToolProvider implements ToolProvider {
739 739 if(ObjectUtil.isEmpty(dataList)){
740 740 throw new BusinessException(ErrorCode.PARAM_REQUIRED,String.format("%s 您描述的%s 不存在,请重新告诉我",name,nameValue));
741 741 }
742   -
743 742 //如果SQL没有条件 多个数据集中进行匹配 如果只匹配一个也算成功
744 743 if(ObjectUtil.isNotEmpty(args.get(name)) || ObjectUtil.isNotEmpty(args.get(sValue))){
745 744 if(("enum".equals(sType) ||"string".equals(sType)) && (args.get(name) instanceof List || args.get(sValue) instanceof List)){
... ... @@ -1154,7 +1153,7 @@ public class DynamicToolProvider implements ToolProvider {
1154 1153 /**
1155 1154 * 构建提问消息
1156 1155 */
1157   - private String buildAskUserMessage(ToolMeta meta, List<String> missing) {
  1156 + private String buildAskUserMessage(ToolMeta meta, List<String> missing,Map<String,Object> arg) {
1158 1157 StringBuilder sb = new StringBuilder();
1159 1158 sb.append("缺少参数请补全:\n");
1160 1159 List<ParamRule> paramRuleData = meta.getParamRuleList();
... ... @@ -1166,8 +1165,14 @@ public class DynamicToolProvider implements ToolProvider {
1166 1165 pd -> {
1167 1166 String sTs = ObjectUtil.isEmpty(pd.getSRuleTs())?StrUtil.EMPTY:pd.getSRuleTs();
1168 1167 sb.append("- **").append(name).append("**: ");
1169   - if(ObjectUtil.isNotEmpty(pd.getSParamMissMemo())){
1170   - if(!("string".equals(pd.getSType()) && RuleCode.SQL.equals(pd.getSRule()) && ObjectUtil.isNotEmpty(pd.getSParamConfig())) )
  1168 + if(ObjectUtil.isNotEmpty(pd.getSParamMissMemo())
  1169 + && ObjectUtil.isNotEmpty(arg.get(pd.getSParam()))
  1170 + && ObjectUtil.isNotEmpty(sTs)
  1171 + ){
  1172 + if(!("string".equals(pd.getSType())
  1173 + && RuleCode.SQL.equals(pd.getSRule())
  1174 + && ObjectUtil.isNotEmpty(pd.getSParamConfig())
  1175 + ))
1171 1176 {
1172 1177 sb.append(StrUtil.format(pd.getSParamMissMemo(),sTs));
1173 1178 }
... ...