diff --git a/src/main/java/com/xly/agent/ErpAiAgent.java b/src/main/java/com/xly/agent/ErpAiAgent.java index aab7f71..d44f8a1 100644 --- a/src/main/java/com/xly/agent/ErpAiAgent.java +++ b/src/main/java/com/xly/agent/ErpAiAgent.java @@ -14,14 +14,23 @@ public interface ErpAiAgent { 1. 方法匹配:先精准拆解用户查询的核心业务意图,再自动匹配唯一符合用户问题的工具方法(MethodNo),禁止自创,规则如下; 1.1 匹配方法时,无需考虑工具描述(@TOOL)中 1.必填参数,2.选填参数,示例,parameters内容 四个部分的内容; 1.2 匹配方法时,只关注工具描述(@TOOL)中 “当用户” 和 “时,必须调用本工具”两个短语之间的内容; - 1.3 调用工具前,不需要询问用户提供缺失的参数 2. 参数提取:提取该工具的全部参数,与描述完全一致,严格按标注类型赋值,规则如下: 2.1 数字无引号,为空时禁止赋值0; 2.2 如果有空格需要去掉空格后再提取。 + 2.3 每次都需要进行参数提取 """) @UserMessage("用户输入:{{userInput}}") String chat(@MemoryId String userId, @V("userInput") String userInput); + @SystemMessage("{{stoolDesc}}") + @UserMessage("用户输入:{{userInput}}") + String chatCurrentTool(@MemoryId String userId, + @V("userInput") String userInput, + @V("sMethodNo") String sMethodNo, + @V("sMethodName") String sMethodName, + @V("stoolDesc") String stoolDesc + ); + /** * 动态表结构:自然语言解释SQL执行结果 * 入参:用户问题、执行的SQL、表结构、JSON格式结果 diff --git a/src/main/java/com/xly/entity/UserSceneSession.java b/src/main/java/com/xly/entity/UserSceneSession.java index b1c1965..8de87f9 100644 --- a/src/main/java/com/xly/entity/UserSceneSession.java +++ b/src/main/java/com/xly/entity/UserSceneSession.java @@ -63,6 +63,9 @@ public class UserSceneSession { private String sFunPrompts; //方法返回的参数补全提示 private Boolean bCleanMemory = false; + + private Map args; + /** * 构建场景选择提示语:展示权限内场景,引导用户选择 * @return 自然语言提示语 diff --git a/src/main/java/com/xly/service/XlyErpService.java b/src/main/java/com/xly/service/XlyErpService.java index 6983d30..cbccb7f 100644 --- a/src/main/java/com/xly/service/XlyErpService.java +++ b/src/main/java/com/xly/service/XlyErpService.java @@ -83,11 +83,12 @@ public class XlyErpService { String authorization) { String sceneName = StrUtil.EMPTY; String methodName = StrUtil.EMPTY; + UserSceneSession session=null; try { // 0. 预处理用户输入:去空格、转小写(方便匹配) String input= InputPreprocessor.preprocessWithCommons(userInput); // 1. 初始化用户场景会话(权限内场景) - UserSceneSession session = userSceneSessionService.getUserSceneSession(userId,sUserName,sBrandsId,sSubsidiaryId,sUserType,authorization); + session = userSceneSessionService.getUserSceneSession(userId,sUserName,sBrandsId,sSubsidiaryId,sUserType,authorization); session.setAuthorization(authorization); session.setSFunPrompts(null); sceneName = ObjectUtil.isNotEmpty(session.getCurrentScene())?session.getCurrentScene().getSSceneName():StrUtil.EMPTY; @@ -102,9 +103,8 @@ public class XlyErpService { if (session.getCurrentScene() != null && Objects.equals(session.getCurrentScene().getSSceneNo(), "ChatZone")) { - return getChatiAgent(input, session,StrUtil.EMPTY); + return getChatiAgent(input, session); } - // 3. 未选场景:先展示场景选择界面,处理用户序号选择 if (!session.isSceneSelected() && ValiDataUtil.me().isPureNumber(input)){ // 3.1 尝试处理场景选择(输入序号则匹配,否则展示选择提示) @@ -114,49 +114,44 @@ public class XlyErpService { ErpAiAgent aiAgent = createErpAiAgent(userId, input, session); // 没有选择到场景,进闲聊模式 if (aiAgent == null){ - return getChatiAgent (input, session,StrUtil.EMPTY); + return getChatiAgent (input,session); } -// 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 + //用户输入添加方法 + String sResponMessage = StrUtil.EMPTY; + if(ObjectUtil.isNotEmpty(session.getCurrentTool()) + && !ObjectUtil.isNotEmpty(session.getCurrentTool().getSInputTabelName()) ){ - // 缺失的参数明细 - sResponMessage = session.getSFunPrompts(); - } - if (session.getCurrentTool()== null){ - sResponMessageOld = sResponMessage; - sResponMessage = StrUtil.EMPTY; - } - //5.执行工具方法后,清除记忆 - if(session.getBCleanMemory()){ - doCleanUserMemory(session,userId); + input = session.getCurrentTool().getSMethodName()+","+input; + sResponMessage = aiAgent.chatCurrentTool(userId, input,session.getCurrentTool().getSMethodNo(),session.getCurrentTool().getSMethodName(),session.getCurrentTool().getStoolDesc()); + }else{ + sResponMessage = aiAgent.chat(userId, input); } -// 6.找到方法并且本方法带表结构描述时,需要调用 自然语言转SQL智能体 + +// 1.找到方法并且本方法带表结构描述时,需要调用 自然语言转SQL智能体 if((ObjectUtil.isNotEmpty(session.getCurrentTool()) && ObjectUtil.isNotEmpty(session.getCurrentTool().getSInputTabelName()) && ObjectUtil.isNotEmpty(session.getCurrentTool().getSStructureMemo())) ){ sResponMessage = getDynamicTableSql(session, input, userId, userInput,0,StrUtil.EMPTY,StrUtil.EMPTY,"0",StrUtil.EMPTY, aiAgent); - } - //如果返回空的进入闲聊模式 - if (ObjectUtil.isEmpty(sResponMessage)){ - return getChatiAgent (input, session,sResponMessageOld); - } - if (session.getCurrentScene()!= null ){ - return AiResponseDTO.builder().aiText(sResponMessage) - .sSceneName(session.getCurrentScene().getSSceneName()) - .sMethodName((ObjectUtil.isEmpty(session.getCurrentTool()))?StrUtil.EMPTY:session.getCurrentTool().getSMethodName()) - .sReturnType(ReturnTypeCode.MAKEDOWN.getCode()) - .build(); - }else { + return AiResponseDTO.builder().sSceneName(sceneName).sMethodName(methodName).aiText(sResponMessage).sReturnType(ReturnTypeCode.HTML.getCode()).build(); + } else if (ObjectUtil.isNotEmpty(session.getCurrentTool())) { + //2.处理工具参数采集结束后业务逻辑处理 + //调用方法,参数缺失部分提示,就直接使用方法返回的 + sResponMessage = dynamicToolProvider.doDynamicTool(session.getCurrentTool(),session); + return AiResponseDTO.builder().sSceneName(sceneName).sMethodName(methodName).aiText(sResponMessage).sReturnType(ReturnTypeCode.HTML.getCode()).build(); + }else if(session.getCurrentScene()== null ){ return AiResponseDTO.builder().sSceneName(sceneName).sMethodName(methodName).aiText("当前场景:没有选择 退回当前场景 请输入 "+ CommonConstant.RESET + sResponMessage).sReturnType(ReturnTypeCode.HTML.getCode()).build(); + }else{ + return getChatiAgent (input, session); } } catch (Exception e) { + e.printStackTrace(); return AiResponseDTO.builder().sSceneName(sceneName).sMethodName(methodName).aiText("系统异常:" + e.getMessage() + ",请稍后重试!").sReturnType(ReturnTypeCode.HTML.getCode()).build(); + }finally { + //5.执行工具方法后,清除记忆 + if(session !=null && session.getBCleanMemory()){ + doCleanUserMemory(session,userId); + } } } @@ -170,6 +165,7 @@ public class XlyErpService { UserSceneSession session = userSceneSessionService.getUserSceneSession(userId,sUserName,sBrandsId,sSubsidiaryId,sUserType,authorization); operableChatMemoryProvider.clearSpecifiedMemory(userId); session.setCurrentTool(null); + session.setArgs(null); session.setSUserQuestionList(new ArrayList<>()); UserSceneSessionService.ERP_AGENT_CACHE.remove(userId); UserSceneSessionService.CHAT_AGENT_CACHE.remove(userId); @@ -246,6 +242,7 @@ public class XlyErpService { operableChatMemoryProvider.clearSpecifiedMemory(userId); session.setCurrentTool(null); session.setSUserQuestionList(new ArrayList<>()); + session.setArgs(new HashMap<>()); // session.setSceneSelected(false); UserSceneSessionService.ERP_AGENT_CACHE.remove(userId); UserSceneSessionService.CHAT_AGENT_CACHE.remove(userId); @@ -593,12 +590,9 @@ public class XlyErpService { * @return java.lang.String * @Description 获取智普通智能体 **/ - private AiResponseDTO getChatiAgent (String input,UserSceneSession session,String sResponMessageOld){ + private AiResponseDTO getChatiAgent (String input,UserSceneSession session){ 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) diff --git a/src/main/java/com/xly/tool/DynamicToolProvider.java b/src/main/java/com/xly/tool/DynamicToolProvider.java index 26a8d6d..4460659 100644 --- a/src/main/java/com/xly/tool/DynamicToolProvider.java +++ b/src/main/java/com/xly/tool/DynamicToolProvider.java @@ -1,6 +1,7 @@ package com.xly.tool; +import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; @@ -165,7 +166,7 @@ public class DynamicToolProvider implements ToolProvider { pr.setSParamValue(sAIshowfieldShowAll.get(i).get("sName").toString()); pr.setSParam(sAIshowfieldShowAll.get(i).get("label").toString()); pr.setBEmpty(false); - pr.setSType("array"); + pr.setSType(getDefType(sAIshowfieldShowAll.get(i).get("sName").toString())); if("sSlaveId".equals(sAIshowfieldShowAll.get(i).get("sName"))){ pr.setSParam("sSlaveId"); } @@ -186,35 +187,56 @@ public class DynamicToolProvider implements ToolProvider { meta.setParamRuleListAll(paramRuleListAll); } + private String getDefType(String sColumn){ + if(sColumn.startsWith("d")){ + return "double"; + }else{ + return "string"; + } + } @Override public ToolProviderResult provideTools(ToolProviderRequest request) { -// List specs = new ArrayList<>(); String sUserId = request.chatMemoryId().toString(); Map executors = new HashMap<>(); - // sceneToolCacheMap.get(sSceneIdMap.get(sUserId)); - //获取Session + // 获取Session UserSceneSession session = UserSceneSessionService.USER_SCENE_SESSION_CACHE.get(sUserId); - //过滤对应的权限方法 + + // 过滤对应的权限方法 List datalist = new ArrayList<>(); List toolMetaAll = new ArrayList<>(); - if(session.getCurrentTool()!=null){ + + // 确保 currentTool 不为空,或者 authTool 有数据 + if(session.getCurrentTool() != null){ toolMetaAll.add(session.getCurrentTool()); - }else{ + log.info("使用 currentTool: {}", session.getCurrentTool().getSMethodNo()); + } else { toolMetaAll = session.getAuthTool(); + log.info("使用 authTool, 数量: {}", toolMetaAll.size()); } + if(ObjectUtil.isNotEmpty(toolMetaAll)){ - toolMetaAll = toolMetaAll.stream().filter(to-> to.getSSceneId().equals(sSceneIdMap.get(sUserId))).collect(Collectors.toUnmodifiableList()); + toolMetaAll = toolMetaAll.stream() + .filter(to -> to.getSSceneId().equals(sSceneIdMap.get(sUserId))) + .collect(Collectors.toUnmodifiableList()); + if(ObjectUtil.isNotEmpty(toolMetaAll)){ - toolMetaAll.forEach(to->{ - datalist.add(toolCache.get(to.getSMethodNo())); + toolMetaAll.forEach(to -> { + ToolSpecificationHolder holder = toolCache.get(to.getSMethodNo()); + if (holder != null) { + datalist.add(holder); + log.debug("添加工具到提供器: {}", to.getSMethodNo()); + } else { + log.warn("工具缓存缺失: {}", to.getSMethodNo()); + } }); } } - datalist.forEach(holder->{ -// specs.add(holder.getToolSpecification()); - executors.put(holder.getToolSpecification(),holder.getToolExecutor()); -// executors.put(holder.getToolSpecification(), holder.getToolExecutor()); + + // 将工具添加到返回结果中 + datalist.forEach(holder -> { + executors.put(holder.getToolSpecification(), holder.getToolExecutor()); }); + log.info("provideTools 返回工具数量: {}", executors.size()); return ToolProviderResult.builder().addAll(executors).build(); } @@ -240,10 +262,10 @@ public class DynamicToolProvider implements ToolProvider { // stoolDesc.append(",").append("并选择数据后执行["+meta.getSControlName()+"]操作"); // } stoolDesc.append("时,必须调用本工具").append(meta.getSMethodNo()).append(",").append(meta.getStoolDesc()); - if (meta.getIBizType()==4){ - stoolDesc.append(",").append("并选择数据后执行 "+meta.getSControlName()+" 操作"); -// .append("1.全部数据生成多个单据 回复【全部确认】;2.全部数据生成一个单据 回复【合并确认】;3.按自然语义描述生成一个单据 如"1,3行确认""); - } +// if (meta.getIBizType()==4){ +// stoolDesc.append(",").append("并选择数据后执行 "+meta.getSControlName()+" 操作"); +//// .append("1.全部数据生成多个单据 回复【全部确认】;2.全部数据生成一个单据 回复【合并确认】;3.按自然语义描述生成一个单据 如"1,3行确认""); +// } } try { @@ -504,115 +526,129 @@ public class DynamicToolProvider implements ToolProvider { } return paramRuleDataAll; } - - /*** * @Author 钱豹 - * @Date 15:09 2026/1/30 + * @Date 12:37 2026/3/16 * @Param [meta] * @return dev.langchain4j.service.tool.ToolExecutor - * @Description 创建 ToolExecutor,内部包含参数自动补全与校验逻辑(创建执行器) + * @Description 参数采集执行器 **/ private ToolExecutor createToolExecutor(ToolMeta meta) { + log.info("创建工具执行器: {}", meta.getSMethodNo()); + return (toolExecutionRequest, memoryId) -> { + log.info("===== 工具执行器开始执行 ====="); + log.info("工具编号: {}", meta.getSMethodNo()); + log.info("工具名称: {}", meta.getSMethodName()); + log.info("memoryId: {}", memoryId); + log.info("请求参数: {}", toolExecutionRequest.arguments()); + UserSceneSession session = UserSceneSessionService.USER_SCENE_SESSION_CACHE.get(memoryId.toString()); - session.setCurrentTool(meta); // 标记一下找到了相应方法 - session.setSFunPrompts(null); - // 检查条件 - 如果条件满足,直接返回成功结果,不再执行后续逻辑 - if (ObjectUtil.isNotEmpty(meta.getSInputTabelName()) - && ObjectUtil.isNotEmpty(meta.getSStructureMemo())) { - // 直接返回成功结果,阻止后续执行 - return createEarlySuccessResult(toolExecutionRequest, "执行成功,终止后续执行"); - } + session.setCurrentTool(meta); // 标记当前工具 - // 1. 解析模型传入的参数 - Map args; + // 1. 解析参数 + Map argsNew; try { - args = objectMapper.readValue(toolExecutionRequest.arguments(), new TypeReference<>() {}); + argsNew = objectMapper.readValue(toolExecutionRequest.arguments(), new TypeReference<>() {}); + log.info("解析后的参数: {}", argsNew); } catch (Exception e) { - String errorMsg = "参数 JSON 解析失败,请检查参数格式是否正确。" - + "错误详情:" + e.getMessage(); - log.warn("参数解析失败,tool={}, args={}", meta.getSMethodNo(), toolExecutionRequest.arguments(), e); + log.error("参数解析失败", e); + String errorMsg = "参数解析失败,请重新输入"; return String.valueOf(errorResult(toolExecutionRequest, errorMsg)); } -// Map argsOld = DeepCopyUtils.deepCopy(args); - List paramRuleData = meta.getParamRuleListAll(); - - // 2 【补全动态参数】动态参数补全 - try{ - args = applyValues(args, meta.getParamRuleListCheck()); - }catch (Exception e){ - log.error("返回信息",e); - String sTsMsg = e.getMessage(); - session.setSFunPrompts(sTsMsg); - //存在多个数据返回大模型,需要继续盘问选择出唯一结果 - return String.valueOf(askUserResult(toolExecutionRequest, sTsMsg)); - } - - // 2.1 【自动补全】应用参数的默认值 - args = applyDefaultValues(args, paramRuleData); - - // 3. 【自动校验】检查必填项 - List missing = checkRequiredParams(args, paramRuleData); - if (!missing.isEmpty()) { - // 4.1 参数缺失,生成“提问”消息,直接返给客户 - String askMsg = buildAskUserMessage(meta, missing,args); - session.setSFunPrompts(askMsg); - return String.valueOf(askUserResult(toolExecutionRequest, askMsg)); + Map args = session.getArgs(); + if(ObjectUtil.isEmpty(args)){ + args = new HashMap<>(); } + Map finalArgs = args; + argsNew.forEach((k, v)->{ + //获取对应的英文参数 + List data = meta.getParamRuleList().stream().filter(one->one.getSParam().equals(k)).collect(Collectors.toUnmodifiableList()); + if(ObjectUtil.isNotEmpty(data) && data.size()>0){ + finalArgs.remove(data.get(0).getSParamValue()); + finalArgs.remove(data.get(0).getSParam()); + } + if(ObjectUtil.isNotEmpty(v)){ + finalArgs.put(k,v); + } + }); + session.setArgs(finalArgs); + // 2. 获取必填参数规则 +// List sParamRules = meta.getParamRuleListCheck(); +// List missingParams = getRequiredParams(sParamRules); +// String collectPrompt = buildCollectParamsPrompt(meta, missingParams, args); +// log.info("参数缺失,返回收集提示: {}", collectPrompt); + return String.valueOf(successResult(toolExecutionRequest, JSONUtil.toJsonStr(finalArgs))); + }; + } - // 6. 【最终确认信息】所有检测通过后,需要和客户确认交互 - List chatMessage = operableChatMemoryProvider.getCurrentChatMessages(memoryId.toString()); - ChatMessage userMessage = getLasterUserMssage(chatMessage); - String input = StrUtil.replace(userMessage.text(),"用户输入:",StrUtil.EMPTY); + public String doDynamicTool(ToolMeta meta,UserSceneSession session) { + List paramRuleData = meta.getParamRuleListAll(); + Map args = session.getArgs(); + Map argsOld = new HashMap<>(args); + // 2 【补全动态参数】动态参数补全 + try{ + args = applyValues(args, meta.getParamRuleListCheck()); + }catch (Exception e){ + log.error("返回信息",e); + String sTsMsg = e.getMessage(); + return sTsMsg; + } + // 2.1 【自动补全】应用参数的默认值 + args = applyDefaultValues(args, paramRuleData); + session.setArgs(args); + // 3. 【自动校验】检查必填项 + List missing = checkRequiredParams(args, paramRuleData); + if (!missing.isEmpty()) { + // 4.1 参数缺失,生成“提问”消息,直接返给客户 + String askMsg = buildAskUserMessage(meta, missing,args); + return askMsg; + } + // 6. 【最终确认信息】所有检测通过后,需要和客户确认交互 + List chatMessage = operableChatMemoryProvider.getCurrentChatMessages(session.getUserId()); + ChatMessage userMessage = getLasterUserMssage(chatMessage); + String input = StrUtil.replace(userMessage.text(),"用户输入:",StrUtil.EMPTY); // {"0":"查询","1":"执行"} 查询不需要确认 - Boolean isConfirmed = isConfirmed(input) || input.contains("生成") || input.contains("确认"); - //判断是否生成数据 - List> sRowData = new ArrayList<>(); - String sHandleType = "merge"; - if(4== meta.getIBizType() && ObjectUtil.isNotEmpty(session.getCurrentRowData())){ - Map sRowDataMap = UserChoseIntentParser.getSelectedRows( input, session.getCurrentRowData()); - sRowData = (List>) sRowDataMap.get("sRowData"); - sHandleType = sRowDataMap.get("sHandleType").toString(); - } - //{"1":"存储过程","2":"SQL查询","3":"第三方API","4":"ERP未清","5":"ERP列表(报表)","6":"ERP单据","7":"其它","8":"自然语言TEXT2SQL"} - - if((isConfirmed && (4== meta.getIBizType() ||1== meta.getIBizType())) - || 2== meta.getIBizType() - || 3== meta.getIBizType() - || 6== meta.getIBizType() - || 7== meta.getIBizType() - || 8== meta.getIBizType() - ) - { - // 确认后必填项校验 - List missingAfter = checkConfirmAfterParam(args, paramRuleData); - if (!missingAfter.isEmpty()) { - // 4.1 参数缺失,生成“提问”消息,直接返给客户 - String askMsg = buildAskUserMessage(meta, missingAfter,args); - session.setSFunPrompts(askMsg); - return String.valueOf(askUserResult(toolExecutionRequest, askMsg)); - } - // 7. 【业务校验】执行业务层面的逻辑校验 + 所有校验通过,执行核心业务逻辑 - return executeTool(toolExecutionRequest, meta, args, paramRuleData, memoryId.toString(), session); - } - String askconfirmMsg =StrUtil.EMPTY; -// if(0== meta.getIActionType() && 4!= meta.getIBizType() && 5!= meta.getIBizType()){ -// askconfirmMsg = buildConfirmUserMessage(meta, args); -// }else - if(4== meta.getIBizType() || meta.getIBizType()==5){ - askconfirmMsg = doGetFromData( meta,args,session); -// session.setSFunPrompts(askconfirmMsg); -// operableChatMemoryProvider.get(memoryId).add(UserMessage.from("SYSTEM: 等待用户确认或选择部分数据操作")); - return executeWithConfirmation(toolExecutionRequest,askconfirmMsg,operableChatMemoryProvider.get(memoryId), session, meta).text(); - }else{ - askconfirmMsg =getDefMessage(args,meta.getSControlName(),meta); + Boolean isConfirmed = isConfirmed(input) || input.contains("生成") || input.contains("确认"); + //判断是否生成数据 + List> sRowData = new ArrayList<>(); + String sHandleType = "merge"; + if(4== meta.getIBizType() && ObjectUtil.isNotEmpty(session.getCurrentRowData())){ + Map sRowDataMap = UserChoseIntentParser.getSelectedRows( input, session.getCurrentRowData()); + sRowData = (List>) sRowDataMap.get("sRowData"); + sHandleType = sRowDataMap.get("sHandleType").toString(); + } + //{"1":"存储过程","2":"SQL查询","3":"第三方API","4":"ERP未清","5":"ERP列表(报表)","6":"ERP单据","7":"其它","8":"自然语言TEXT2SQL"} + if((isConfirmed && (4== meta.getIBizType() ||1== meta.getIBizType())) + || 2== meta.getIBizType() + || 3== meta.getIBizType() + || 6== meta.getIBizType() + || 7== meta.getIBizType() + || 8== meta.getIBizType() + ) + { + // 确认后必填项校验 + List missingAfter = checkConfirmAfterParam(args, paramRuleData); + if (!missingAfter.isEmpty()) { + // 4.1 参数缺失,生成“提问”消息,直接返给客户 + String askMsg = buildAskUserMessage(meta, missingAfter,args); + return askMsg; } - // 返回需要确认的结果 - return executeWithConfirmation(toolExecutionRequest,askconfirmMsg,operableChatMemoryProvider.get(memoryId), session, meta).text(); - }; + // 7. 【业务校验】执行业务层面的逻辑校验 + 所有校验通过,执行核心业务逻辑 + return executeTool(meta, args, paramRuleData, session.getUserId(), session); + } + String askconfirmMsg =StrUtil.EMPTY; + if(4== meta.getIBizType() || meta.getIBizType()==5){ + askconfirmMsg = doGetFromData( meta,args,session); + return executeWithConfirmation(askconfirmMsg,operableChatMemoryProvider.get(session.getUserId()), session, meta); + }else{ + askconfirmMsg =getDefMessage(args,meta.getSControlName(),meta); + } + // 返回需要确认的结果 + return executeWithConfirmation(askconfirmMsg,operableChatMemoryProvider.get(session.getUserId()), session, meta); } + /*** * @Author 钱豹 * @Date 15:16 2026/2/9 @@ -651,7 +687,7 @@ public class DynamicToolProvider implements ToolProvider { **/ private void appendConfirmAll(StringBuilder markdown,String sName){ sName = ObjectUtil.isEmpty(sName)?StrUtil.EMPTY:"["+sName+"]"; - markdown.append("请确认是否执行").append(sName).append("操作?1.全部数据生成多个单据 回复【全部确认】;2.全部数据生成一个单据 回复【合并确认】;3.按自然语义描述生成一个单据 如"1,3行确认"\n"); + markdown.append("请确认是否执行").append(sName).append("操作?1.全部数据生成多个单据 回复【全部确认】;2.全部数据生成一个单据 回复【合并确认】;3.按自然语义描述生成一个单据 如"第1行确认"\n"); //全部确认 ,部分确认,取消 markdown.append("回复:  ").append("**全部确认**").append(" ") .append("**合并确认**").append(" ") @@ -666,9 +702,7 @@ public class DynamicToolProvider implements ToolProvider { **/ private void appendConfirm(StringBuilder markdown,String sName){ sName = ObjectUtil.isEmpty(sName)?StrUtil.EMPTY:"["+sName+"]"; - markdown.append("请确认是否执行").append(sName).append("操作?请直接回复"确认"或"取消"\n"); - //全部确认 ,部分确认,取消 - markdown.append("回复:  ").append("**确认**").append(" ") + markdown.append("请确认是否执行").append(sName).append("操作?请回复:  ").append("**确认**").append(" ") .append("**取消**"); } @@ -836,6 +870,14 @@ public class DynamicToolProvider implements ToolProvider { } return result; } + /** + * 检查必填参数 + */ + private List getRequiredParams(List paramDefs) { + return paramDefs.stream() + .map(ParamRule::getSParam) + .toList(); + } /** * 检查必填参数 @@ -845,8 +887,11 @@ public class DynamicToolProvider implements ToolProvider { return paramDefs.stream() .filter(pd -> Boolean.TRUE.equals(pd.getBEmpty()) && pd.getBTipModel()) .filter(pd -> - (!returnMap.containsKey(pd.getSParam()) || (ObjectUtil.isEmpty(returnMap.get(pd.getSParam())))) - && (!returnMap.containsKey(pd.getSParamValue()) || (ObjectUtil.isEmpty(returnMap.get(pd.getSParamValue())))) + (!returnMap.containsKey(pd.getSParam()) + || (ObjectUtil.isEmpty(returnMap.get(pd.getSParam()))) + || (pd.getSParamValue().startsWith("d") && 0==Double.valueOf (returnMap.get(pd.getSParam()).toString())) + ) + && (!returnMap.containsKey(pd.getSParamValue()) || (ObjectUtil.isEmpty(returnMap.get(pd.getSParamValue())))) ) .map(ParamRule::getSParam) .toList(); @@ -867,16 +912,17 @@ public class DynamicToolProvider implements ToolProvider { .toList(); } + /** * 模拟执行工具 */ - public String executeTool(ToolExecutionRequest toolExecutionRequest,ToolMeta meta, Map args, List paramRuleData,String userId,UserSceneSession session ) { + public String executeTool(ToolMeta meta, Map args, List paramRuleData,String userId,UserSceneSession session ) { log.info("执行工具:{},参数:{}", meta.getSMethodNo(), args); // 2.2 将中文key转换成英文key args = transformationArgs( args, paramRuleData); String sReturn ="执行成功"; try{ - sReturn = executeToolAfter(meta, args,toolExecutionRequest,paramRuleData,session); + sReturn = executeToolAfter(meta, args,paramRuleData,session); }catch (BusinessException e) { return e.getMessage(); } @@ -893,7 +939,7 @@ public class DynamicToolProvider implements ToolProvider { * @return * @Description 返回结果后 执行业务类 **/ - private String executeToolAfter(ToolMeta meta, Map args,ToolExecutionRequest toolExecutionRequest,List paramDefs,UserSceneSession session) { + private String executeToolAfter(ToolMeta meta, Map args,List paramDefs,UserSceneSession session) { // {"1":"存储过程","2":"SQL查询","3":"第三方API","4":"ERP未清","5":"ERP列表(报表)","6":"ERP单据","7":"其它","8":"自然语言TEXT2SQL"} String sBizContent = meta.getSBizContent(); Integer iBizType = meta.getIBizType(); @@ -950,7 +996,7 @@ public class DynamicToolProvider implements ToolProvider { if ("queryTodayTask".equals(meta.getSMethodNo())) { session.setBCleanMemory(true); } - return String.valueOf(successResult(toolExecutionRequest, sb.toString())); + return sb.toString(); } else { String sMsgText = "未找到对应的数据"; session.setSFunPrompts(sMsgText); @@ -961,7 +1007,7 @@ public class DynamicToolProvider implements ToolProvider { return HttpsRequestUtil.me().doRequestHttp(sBizContent, JSONUtil.toJsonStr(args), new HashMap<>(), "POST", "JSON"); } - return String.valueOf(successResult(toolExecutionRequest, "操作成功")); + return "操作成功"; } @@ -1051,8 +1097,37 @@ public class DynamicToolProvider implements ToolProvider { } List> recordData = findFieldNameByChinese(sAIshowfieldShow, rows); int recordCount = dataset != null ? dataset.getTotalCount() : 0; + // 动态生成表头 + Set headers = new LinkedHashSet<>(); + for (Map record : sAIshowfieldShow) { + String chineseName = (String) record.get("label"); + if (chineseName != null && !"sSlaveId".equals(record.get("sName"))) { + headers.add(chineseName); + } + } + StringBuilder markdown = new StringBuilder(); //状态 + if(ObjectUtil.isNotEmpty(session.getArgs())){ + markdown.append("**查询条件**:"); + List pr = session.getCurrentTool().getParamRuleListCheck(); + Map argsOld = new HashMap<>(session.getArgs()); + pr.forEach(one->{ + if(argsOld.containsKey(one.getSParam())){ + if(ObjectUtil.isNotEmpty(argsOld.get(one.getSParam()))){ + if(one.getSParamValue().startsWith("d")){ + if(Double.valueOf(argsOld.get(one.getSParam()).toString())>0){ + markdown.append(one.getSParam()).append(":").append(argsOld.get(one.getSParam()).toString()).append(" "); + } + }else{ + markdown.append(one.getSParam()).append(":").append(argsOld.get(one.getSParam()).toString()).append(" "); + } + } + } + }); + markdown.append("\n\n"); + } + markdown.append("**结果**:"); String sStatus = erpResult.getCode()<0?ErrorCode.ERRORMSG.getMessage():ErrorCode.SUCCESSMSG.getMessage(); markdown.append(sStatus).append("\n"); if(erpResult.getCode()<0){ @@ -1067,14 +1142,6 @@ public class DynamicToolProvider implements ToolProvider { } // markdown.append("\n---\n"); markdown.append("\n\n").append("| 序号 | "); - // 动态生成表头 - Set headers = new LinkedHashSet<>(); - for (Map record : sAIshowfieldShow) { - String chineseName = (String) record.get("label"); - if (chineseName != null && !"sSlaveId".equals(record.get("sName"))) { - headers.add(chineseName); - } - } headers.forEach(header -> markdown.append(header).append(" | ")); markdown.append("\n|").append("---|".repeat(headers.size() + 1)).append("\n"); // 填充表格数据 @@ -1256,20 +1323,9 @@ public class DynamicToolProvider implements ToolProvider { } /** - * 询问用户工具执行结果 - * @param request 工具执行请求 - * @param text 回复文本内容 - * @return 工具执行结果消息 - * @author 钱豹 - */ - private ToolExecutionResultMessage askUserResult(ToolExecutionRequest request, String text) { - // 直接返回标准结果 - return ToolExecutionResultMessage.from(request, text); - } - /** * 执行方法后需要用户确认的扩展版本 */ - private ToolExecutionResultMessage executeWithConfirmation(ToolExecutionRequest request, String initialResult,ChatMemory chatMemory, UserSceneSession session,ToolMeta meta) { + private String executeWithConfirmation(String initialResult,ChatMemory chatMemory, UserSceneSession session,ToolMeta meta) { // 第一步:执行原始操作,返回初步结果 Map step1Result = new HashMap<>(); @@ -1278,18 +1334,19 @@ public class DynamicToolProvider implements ToolProvider { step1Result.put("confirmationRequired", true); step1Result.put("confirmationMessage", initialResult); // // 将确认状态保存到对话记忆 - chatMemory.add(UserMessage.from("SYSTEM: 等待用户确认操作")); +// chatMemory.add(UserMessage.from("SYSTEM: 等待用户确认操作")); String userMessage = formatConfirmationResult(step1Result); session.setCurrentTool(meta); - session.setSFunPrompts(userMessage); +// session.setSFunPrompts(userMessage); // 6. 返回确认请求 - return ToolExecutionResultMessage.from(request,userMessage); +// return ToolExecutionResultMessage.from(request,userMessage); + return userMessage; } private String formatConfirmationResult(Map result) { return String.format( """ - **结果** : %s + %s """, result.get("initialResult"), result.get("confirmationMessage") diff --git a/src/main/resources/templates/chat.html b/src/main/resources/templates/chat.html index 3cc9564..5882d92 100644 --- a/src/main/resources/templates/chat.html +++ b/src/main/resources/templates/chat.html @@ -468,7 +468,7 @@ let subsidiaryid= "1111111111"; let usertype= "sysadmin"; // let usertype= "General"; - let authorization="CE444885A9BCFDDE1FD793F8A0931301E9D7DE6CEDD9DE4B83ECE2219C7829A8F3419238942A93E9AD666629E18D159AF7FE144A6407DE745BA0AEC8B235FC1D59FCE41E63C9FE20948A232F9F67AAE5687D4EF8281542FFC87C4EB776974C6A538155B7ADAEE71E899235DC1122F426"; + let authorization="CE444885A9BCFDDE1FD793F8A0931301E9D7DE6CEDD9DE4B83ECE2219C7829A8F3419238942A93E9AD666629E18D159AF7FE144A6407DE745BA0AEC8B235FC1D0771D239F1C61B14219E1D1B3A166D73495FD8E662F2065F9430347C7E4472B5538155B7ADAEE71E899235DC1122F426"; let hrefLock = window.location.origin+"/xlyAi"; // ==================== 配置部分 ==================== const CONFIG = {