From ff5f5a198c776e7db6654be94c29fa4b0e003637 Mon Sep 17 00:00:00 2001 From: qianbao Date: Sun, 8 Mar 2026 22:40:15 +0800 Subject: [PATCH] AI 对于时间的处理 --- src/main/java/com/xly/config/OperableChatMemoryProvider.java | 190 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------- src/main/java/com/xly/service/XlyErpService.java | 49 ++++++++++++++++++++++++++++++++++++++++--------- src/main/java/com/xly/tool/DynamicToolProvider.java | 17 +++++++++++------ 3 files changed, 215 insertions(+), 41 deletions(-) diff --git a/src/main/java/com/xly/config/OperableChatMemoryProvider.java b/src/main/java/com/xly/config/OperableChatMemoryProvider.java index 6ca52f9..24f2952 100644 --- a/src/main/java/com/xly/config/OperableChatMemoryProvider.java +++ b/src/main/java/com/xly/config/OperableChatMemoryProvider.java @@ -1,16 +1,17 @@ package com.xly.config; - import dev.langchain4j.memory.ChatMemory; import dev.langchain4j.memory.chat.ChatMemoryProvider; import dev.langchain4j.memory.chat.MessageWindowChatMemory; import dev.langchain4j.data.message.ChatMessage; import org.springframework.stereotype.Component; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; /** * 可操作的ChatMemoryProvider:获取消息对象+清除记忆(指定/全量)+删除单条消息 @@ -20,7 +21,7 @@ import java.util.concurrent.ConcurrentHashMap; public class OperableChatMemoryProvider implements ChatMemoryProvider { // 核心缓存:memoryId -> ChatMemory,保证一个会话/用户对应唯一记忆实例 private final Map memoryCache = new ConcurrentHashMap<>(); - // 记忆最大消息数,根据业务需求调整(原配置为10) + // 记忆最大消息数,根据业务需求调整 private static final int MAX_MESSAGE_SIZE = 100; /** @@ -35,56 +36,193 @@ public class OperableChatMemoryProvider implements ChatMemoryProvider { return memoryCache.computeIfAbsent(finalMemId, k -> MessageWindowChatMemory.withMaxMessages(MAX_MESSAGE_SIZE)); } - // ===================== 1. 获取消息对象(当前memoryId) ===================== + // ===================== 1. 获取消息对象 ===================== + /** - * 获取当前会话/用户的全部消息列表(含用户消息、AI消息,按对话顺序) + * 获取当前会话/用户的全部消息列表 */ public List getCurrentChatMessages(Object memoryId) { if (Objects.isNull(memoryId)) { - return List.of(); + return new ArrayList<>(); } - // 从ChatMemory中获取原生消息列表 - return this.get(memoryId).messages(); + // 返回一个新的列表,避免直接操作内部列表 + return new ArrayList<>(this.get(memoryId).messages()); } - // ===================== 2. 清除记忆(核心需求) ===================== + // ===================== 2. 清除记忆 ===================== + /** - * 清空【指定memoryId】的全部记忆(最常用,如前端「清空对话」) + * 清空【指定memoryId】的全部记忆 */ public void clearSpecifiedMemory(Object memoryId) { if (Objects.nonNull(memoryId)) { - // 调用ChatMemory原生clear(),清空该实例所有消息 this.get(memoryId).clear(); } } /** - * 全量清除【所有memoryId】的记忆(系统级清理,如定时任务/后台操作) + * 全量清除【所有memoryId】的记忆 */ public void clearAllMemory() { - // 遍历所有记忆实例,逐个调用原生clear()清空 memoryCache.values().forEach(ChatMemory::clear); - // 可选:彻底清空缓存,销毁所有实例(释放内存,后续会重新创建) - // memoryCache.clear(); } - // ===================== 3. 精准操作:删除单条消息 ===================== + // ===================== 3. 删除单条消息(完全重新设置方案) ===================== + /** - * 删除当前memoryId的指定单条消息(如删除错误消息) - * @param message 要删除的消息对象(从getCurrentChatMessages中获取) + * 删除指定消息(通过完全重新设置消息列表的方式) + * @param memoryId 会话ID + * @param messageToDelete 要删除的消息对象 + * @return 删除后的最新消息列表 */ - public void deleteSingleMessage(Object memoryId, ChatMessage message) { - if (Objects.nonNull(memoryId) && Objects.nonNull(message)) { - ChatMemory currentMemory = this.get(memoryId); - // 删除指定消息 - currentMemory.messages().remove(message); - // 刷新记忆,保证MessageWindowChatMemory的最大消息数限制生效 -// currentMemory.update(currentMemory.messages()); + public List deleteSingleMessage(Object memoryId, ChatMessage messageToDelete) { + if (Objects.isNull(memoryId) || Objects.isNull(messageToDelete)) { + return getCurrentChatMessages(memoryId); + } + + // 步骤1: 获取当前所有消息 + ChatMemory currentMemory = this.get(memoryId); + List currentMessages = new ArrayList<>(currentMemory.messages()); + + // 从后往前查找内容匹配的最后一条消息 + int indexToDelete = -1; + for (int i = currentMessages.size() - 1; i >= 0; i--) { + ChatMessage msg = currentMessages.get(i); + if (isSameMessage(msg, messageToDelete)) { + indexToDelete = i; + break; + } + } + // 如果找到匹配的消息 + if (indexToDelete >= 0) { + List filteredMessages = new ArrayList<>(currentMessages); + filteredMessages.remove(indexToDelete); + return rebuildMemoryWithMessages(memoryId, filteredMessages); + } + // 步骤4: 完全重新设置消息列表 + return rebuildMemoryWithMessages(memoryId, currentMessages); + } + public List deleteUserLasterMessage(Object memoryId) { + if (Objects.isNull(memoryId)) { + return getCurrentChatMessages(memoryId); } + // 步骤1: 获取当前所有消息 + ChatMemory currentMemory = this.get(memoryId); + List currentMessages = new ArrayList<>(currentMemory.messages()); + + // 从后往前查找内容匹配的最后一条消息 + int indexToDelete = currentMessages.size()-1; + // 如果找到匹配的消息 + if (indexToDelete >= 0) { + List filteredMessages = new ArrayList<>(currentMessages); + filteredMessages.remove(indexToDelete); + return rebuildMemoryWithMessages(memoryId, filteredMessages); + } + // 步骤4: 完全重新设置消息列表 + return rebuildMemoryWithMessages(memoryId, currentMessages); + } + /** + * 批量删除多条消息 + * @param memoryId 会话ID + * @param messagesToDelete 要删除的消息列表 + * @return 删除后的最新消息列表 + */ + public List deleteMessages(Object memoryId, List messagesToDelete) { + if (Objects.isNull(memoryId) || messagesToDelete == null || messagesToDelete.isEmpty()) { + return getCurrentChatMessages(memoryId); + } + + // 获取当前所有消息 + ChatMemory currentMemory = this.get(memoryId); + List currentMessages = new ArrayList<>(currentMemory.messages()); + + // 过滤掉所有要删除的消息 + List filteredMessages = currentMessages.stream() + .filter(msg -> messagesToDelete.stream().noneMatch(delMsg -> isSameMessage(msg, delMsg))) + .collect(Collectors.toList()); + + // 如果消息数量没有变化,直接返回 + if (filteredMessages.size() == currentMessages.size()) { + return currentMessages; + } + + // 重新设置消息列表 + return rebuildMemoryWithMessages(memoryId, filteredMessages); + } + + /** + * 判断两条消息是否相同(支持根据内容、类型等多维度匹配) + */ + private boolean isSameMessage(ChatMessage msg1, ChatMessage msg2) { +// if (msg1 == msg2) return true; // 同一对象引用 +// if (msg1 == null || msg2 == null) return false; +// +// // 根据消息类型和内容判断 +// // 方式1: 根据对象相等性 +// if (msg1.equals(msg2)) return true; + // 方式2: 根据消息类型和文本内容(适用于大多数场景) + if (msg1.type() == msg2.type()) { + // 如果有唯一ID或时间戳,也可以加入判断 + // 这里简单使用文本内容判断 + String text1 = extractText(msg1); + String text2 = extractText(msg2); + return Objects.equals(text1, text2); + } + + return false; + } + + /** + * 从消息中提取文本内容 + */ + private String extractText(ChatMessage message) { + // 根据ChatMessage的实际类型提取文本 + // 这里需要根据您的具体消息实现来调整 + try { + // 尝试通过toString获取内容 + return message.toString(); + } catch (Exception e) { + return ""; + } + } + + /** + * 核心方法:使用过滤后的消息列表重建记忆 + */ + private List rebuildMemoryWithMessages(Object memoryId, List messages) { + // 从缓存中移除原实例 + ChatMemory oldMemory = memoryCache.remove(memoryId); + + // 创建新的ChatMemory实例 + MessageWindowChatMemory newMemory = MessageWindowChatMemory.withMaxMessages(MAX_MESSAGE_SIZE); + + // 由于ChatMemory接口没有直接添加消息的方法,我们需要通过特定方式添加 + // 方式1: 如果MessageWindowChatMemory有add方法(需要通过反射或强制类型转换) + try { + // 通过反射调用add方法(如果存在) + java.lang.reflect.Method addMethod = newMemory.getClass().getMethod("add", ChatMessage.class); + addMethod.setAccessible(true); + for (ChatMessage message : messages) { + addMethod.invoke(newMemory, message); + } + } catch (Exception e) { + // 方式2: 如果无法通过反射添加,我们需要使用替代方案 + // 这里可以记录日志 + System.err.println("无法通过反射添加消息: " + e.getMessage()); + + // 方式3: 使用clear后通过某种方式重新添加 + // 注意:这取决于具体的ChatMemory实现 + } + + // 将新实例放入缓存 + memoryCache.put(memoryId, newMemory); + + // 返回重建后的消息列表 + return new ArrayList<>(newMemory.messages()); } /** - * 移除并清除指定记忆(清空消息+从缓存删除实例,彻底释放资源,适用于过期会话) + * 移除并清除指定记忆(清空消息+从缓存删除实例) */ public void removeAndClearMemory(Object memoryId) { if (Objects.nonNull(memoryId)) { @@ -94,4 +232,4 @@ public class OperableChatMemoryProvider implements ChatMemoryProvider { } } } -} +} \ No newline at end of file diff --git a/src/main/java/com/xly/service/XlyErpService.java b/src/main/java/com/xly/service/XlyErpService.java index f3b0500..98fe0a5 100644 --- a/src/main/java/com/xly/service/XlyErpService.java +++ b/src/main/java/com/xly/service/XlyErpService.java @@ -24,6 +24,8 @@ import com.xly.util.SqlValidateUtil; import com.xly.util.ValiDataUtil; import dev.langchain4j.agent.tool.ToolExecutionRequest; import dev.langchain4j.data.message.AiMessage; +import dev.langchain4j.data.message.ChatMessage; +import dev.langchain4j.data.message.ChatMessageType; import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.ollama.OllamaChatModel; import dev.langchain4j.service.AiServices; @@ -96,7 +98,7 @@ public class XlyErpService { if (session.getCurrentScene() != null && Objects.equals(session.getCurrentScene().getSSceneNo(), "ChatZone")) { - return getChatiAgent(input, session); + return getChatiAgent(input, session,StrUtil.EMPTY); } // 3. 未选场景:先展示场景选择界面,处理用户序号选择 @@ -108,9 +110,12 @@ public class XlyErpService { ErpAiAgent aiAgent = createErpAiAgent(userId, input, session); // 没有选择到场景,进闲聊模式 if (aiAgent == null){ - return getChatiAgent (input, session); + return getChatiAgent (input, session,StrUtil.EMPTY); } + List chatMessage = operableChatMemoryProvider.getCurrentChatMessages(session.getUserId()); String sResponMessage = aiAgent.chat(userId, input); + List chatMessage2 = operableChatMemoryProvider.getCurrentChatMessages(session.getUserId()); + String sResponMessageOld = StrUtil.EMPTY; // 调用方法,参数缺失部分提示,就直接使用方法返回的 if(session.getCurrentTool() != null && session.getSFunPrompts()!=null @@ -118,9 +123,6 @@ public class XlyErpService { // 缺失的参数明细 sResponMessage = session.getSFunPrompts(); } - if (session.getCurrentTool()== null){ - sResponMessage = StrUtil.EMPTY; - } // //说明没有找到动态方法重新走一次 // if(maxToolRetries==1 && session.getCurrentTool()== null){ // Thread.sleep(1000); @@ -136,6 +138,7 @@ public class XlyErpService { // } if (session.getCurrentTool()== null){ + sResponMessageOld = sResponMessage; sResponMessage = StrUtil.EMPTY; } //5.执行工具方法后,清除记忆 @@ -155,7 +158,7 @@ public class XlyErpService { } //如果返回空的进入闲聊模式 if (ObjectUtil.isEmpty(sResponMessage)){ - return getChatiAgent (input, session); + return getChatiAgent (input, session,sResponMessageOld); } if (session.getCurrentScene()!= null ){ return AiResponseDTO.builder().aiText(sResponMessage) @@ -463,7 +466,12 @@ public class XlyErpService { * @return java.lang.String * @Description 获取智普通智能体 **/ - private AiResponseDTO getChatiAgent (String input,UserSceneSession session){ + private AiResponseDTO getChatiAgent (String input,UserSceneSession session,String sResponMessageOld){ + String sceneName = ObjectUtil.isNotEmpty(session.getCurrentScene())?session.getCurrentScene().getSSceneName():StrUtil.EMPTY; + String methodName = ObjectUtil.isNotEmpty(session.getCurrentTool())?session.getCurrentTool().getSMethodName():"随便聊聊"; + if(ObjectUtil.isNotEmpty(sResponMessageOld)){ + return AiResponseDTO.builder().sSceneName(sceneName).sMethodName(methodName).aiText(sResponMessageOld).systemText(StrUtil.EMPTY).sReturnType(ReturnTypeCode.HTML.getCode()).build(); + } ChatiAgent chatiAgent = UserSceneSessionService.CHAT_AGENT_CACHE.get(session.getUserId()); if(ObjectUtil.isEmpty(chatiAgent)){ chatiAgent = AiServices.builder(ChatiAgent.class) @@ -472,9 +480,32 @@ public class XlyErpService { .build(); UserSceneSessionService.CHAT_AGENT_CACHE.put(session.getUserId(), chatiAgent); } String sChatMessage = chatiAgent.chat(session.getUserId(), input); - String sceneName = ObjectUtil.isNotEmpty(session.getCurrentScene())?session.getCurrentScene().getSSceneName():StrUtil.EMPTY; - String methodName = ObjectUtil.isNotEmpty(session.getCurrentTool())?session.getCurrentTool().getSMethodName():"随便聊聊"; + //随便聊聊移除系统返回的提示记忆 +// List chatMessage = operableChatMemoryProvider.getCurrentChatMessages(session.getUserId()); +// removeMssageSbll(chatMessage, session.getUserId()); +// operableChatMemoryProvider.deleteUserLasterMessage(session.getUserId()); +// chatMessage = operableChatMemoryProvider.getCurrentChatMessages(session.getUserId()); return AiResponseDTO.builder().sSceneName(sceneName).sMethodName(methodName).aiText(sChatMessage).systemText(StrUtil.EMPTY).sReturnType(ReturnTypeCode.HTML.getCode()).build(); } + /*** + * @Author 钱豹 + * @Date 12:14 2026/3/8 + * @Param [chatMessage, memoryId] + * @return void + * @Description 随便聊聊移除最后一个AI 返回信息 跟系统询问的随便聊聊 + **/ + private void removeMssageSbll(List chatMessage,String memoryId){ + if(chatMessage!=null){ +// operableChatMemoryProvider.deleteSingleMessage(memoryId,chatMessage.get(chatMessage.size()-1)); + for(int i=chatMessage.size()-1;i>0;i--){ + ChatMessage data = chatMessage.get(i); + ChatMessageType sType = data.type(); + if(ChatMessageType.SYSTEM.equals(sType)){ + operableChatMemoryProvider.deleteSingleMessage(memoryId,data); + return; + } + } + } + } } \ No newline at end of file diff --git a/src/main/java/com/xly/tool/DynamicToolProvider.java b/src/main/java/com/xly/tool/DynamicToolProvider.java index e184014..5210ae8 100644 --- a/src/main/java/com/xly/tool/DynamicToolProvider.java +++ b/src/main/java/com/xly/tool/DynamicToolProvider.java @@ -551,7 +551,7 @@ public class DynamicToolProvider implements ToolProvider { List missing = checkRequiredParams(args, paramRuleData); if (!missing.isEmpty()) { // 4.1 参数缺失,生成“提问”消息,直接返给客户 - String askMsg = buildAskUserMessage(meta, missing); + String askMsg = buildAskUserMessage(meta, missing,args); session.setSFunPrompts(askMsg); return String.valueOf(askUserResult(toolExecutionRequest, askMsg)); } @@ -576,7 +576,7 @@ public class DynamicToolProvider implements ToolProvider { List missingAfter = checkConfirmAfterParam(args, paramRuleData); if (!missingAfter.isEmpty()) { // 4.1 参数缺失,生成“提问”消息,直接返给客户 - String askMsg = buildAskUserMessage(meta, missingAfter); + String askMsg = buildAskUserMessage(meta, missingAfter,args); session.setSFunPrompts(askMsg); return String.valueOf(askUserResult(toolExecutionRequest, askMsg)); } @@ -739,7 +739,6 @@ public class DynamicToolProvider implements ToolProvider { if(ObjectUtil.isEmpty(dataList)){ throw new BusinessException(ErrorCode.PARAM_REQUIRED,String.format("%s 您描述的%s 不存在,请重新告诉我",name,nameValue)); } - //如果SQL没有条件 多个数据集中进行匹配 如果只匹配一个也算成功 if(ObjectUtil.isNotEmpty(args.get(name)) || ObjectUtil.isNotEmpty(args.get(sValue))){ 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 { /** * 构建提问消息 */ - private String buildAskUserMessage(ToolMeta meta, List missing) { + private String buildAskUserMessage(ToolMeta meta, List missing,Map arg) { StringBuilder sb = new StringBuilder(); sb.append("缺少参数请补全:\n"); List paramRuleData = meta.getParamRuleList(); @@ -1166,8 +1165,14 @@ public class DynamicToolProvider implements ToolProvider { pd -> { String sTs = ObjectUtil.isEmpty(pd.getSRuleTs())?StrUtil.EMPTY:pd.getSRuleTs(); sb.append("- **").append(name).append("**: "); - if(ObjectUtil.isNotEmpty(pd.getSParamMissMemo())){ - if(!("string".equals(pd.getSType()) && RuleCode.SQL.equals(pd.getSRule()) && ObjectUtil.isNotEmpty(pd.getSParamConfig())) ) + if(ObjectUtil.isNotEmpty(pd.getSParamMissMemo()) + && ObjectUtil.isNotEmpty(arg.get(pd.getSParam())) + && ObjectUtil.isNotEmpty(sTs) + ){ + if(!("string".equals(pd.getSType()) + && RuleCode.SQL.equals(pd.getSRule()) + && ObjectUtil.isNotEmpty(pd.getSParamConfig()) + )) { sb.append(StrUtil.format(pd.getSParamMissMemo(),sTs)); } -- libgit2 0.22.2