diff --git a/src/main/java/com/xly/agent/ErpAiAgent.java b/src/main/java/com/xly/agent/ErpAiAgent.java index e21d367..bf3671e 100644 --- a/src/main/java/com/xly/agent/ErpAiAgent.java +++ b/src/main/java/com/xly/agent/ErpAiAgent.java @@ -1,10 +1,7 @@ package com.xly.agent; -import dev.langchain4j.service.MemoryId; -import dev.langchain4j.service.SystemMessage; -import dev.langchain4j.service.UserMessage; -import dev.langchain4j.service.V; +import dev.langchain4j.service.*; /** * 优化后:新增场景专属交互规则,大模型仅处理当前场景业务指令 @@ -13,7 +10,7 @@ public interface ErpAiAgent { @SystemMessage("{{sSystemPrompt}}") @UserMessage("用户输入:{{userInput}}") - String chat( + Result chat( @MemoryId String userId, @V("userInput") String userInput, @V("sSystemPrompt") String sSystemPrompt diff --git a/src/main/java/com/xly/service/XlyErpService.java b/src/main/java/com/xly/service/XlyErpService.java index 979c284..7ff78b9 100644 --- a/src/main/java/com/xly/service/XlyErpService.java +++ b/src/main/java/com/xly/service/XlyErpService.java @@ -22,13 +22,18 @@ import com.xly.thread.AiSqlErrorHistoryThread; import com.xly.thread.AiUserAgentQuestionThread; import com.xly.thread.MultiThreadPoolServer; import com.xly.tool.DynamicToolProvider; +import com.xly.tool.ToolSpecificationHolder; import com.xly.util.*; +import dev.langchain4j.agent.tool.ReturnBehavior; import dev.langchain4j.agent.tool.ToolExecutionRequest; +import dev.langchain4j.agent.tool.ToolSpecification; import dev.langchain4j.data.message.AiMessage; import dev.langchain4j.data.message.ChatMessage; import dev.langchain4j.model.ollama.OllamaChatModel; import dev.langchain4j.service.AiServices; +import dev.langchain4j.service.Result; +import dev.langchain4j.service.tool.ToolExecutor; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.time.DateFormatUtils; @@ -131,7 +136,8 @@ public class XlyErpService { && ObjectUtil.isNotEmpty(session.getCurrentTool().getSInputTabelName()) && ObjectUtil.isNotEmpty(session.getCurrentTool().getSStructureMemo())) ){ - sResponMessage = aiAgent.chat(userId, input , AgentSystemPrompt.sSystemPrompt); + Result rs = aiAgent.chat(userId, input , AgentSystemPrompt.sSystemPrompt); + sResponMessage = rs.content(); } if(ObjectUtil.isNotEmpty(session.getCurrentTool()) @@ -350,7 +356,8 @@ public class XlyErpService { if(ObjectUtil.isNotEmpty(session.getSSystemPrompt()) && isConfirmed){ sSystemPrompt = session.getSSystemPrompt(); } - sResponMessage = aiAgent.chat(userId, input,sSystemPrompt); + Result rs = aiAgent.chat(userId, input,sSystemPrompt); + sResponMessage = rs.content(); } methodName = ObjectUtil.isNotEmpty(session.getCurrentTool())?session.getCurrentTool().getSMethodName():StrUtil.EMPTY; if(ObjectUtil.isNotEmpty(session.getCurrentTool()) @@ -945,19 +952,24 @@ public class XlyErpService { // 4. 获取/创建用Agent ErpAiAgent aiAgent = UserSceneSessionService.ERP_AGENT_CACHE.get(userId); if(ObjectUtil.isEmpty(aiAgent)){ + List dataList = dynamicToolProvider.sceneToolCacheMap.get(session.getCurrentScene().getSId()); + Set immediateReturnToolNames = new HashSet<>(); + Map executors = new HashMap<>(); + if(ObjectUtil.isNotEmpty(dataList)){ + dataList.forEach(one->{ + immediateReturnToolNames.add(one.getsName()); + executors.put(one.getToolSpecification(),one.getToolExecutor()); + }); + } aiAgent = AiServices.builder(ErpAiAgent.class) .chatModel(chatModel) .chatMemoryProvider(operableChatMemoryProvider) - .toolProvider(dynamicToolProvider) -// .toolChoice(ChatCompletionToolChoice.ofRequired()) // 👈 必须调用一个工具 + .tools(executors,immediateReturnToolNames) +// .toolProvider(dynamicToolProvider) +// .returnBehavior(ReturnBehavior.IMMEDIATE) +// .toolChoice(ChatCompletionToolChoice.ofRequired()) .build(); UserSceneSessionService.ERP_AGENT_CACHE.put(userId, aiAgent); - // 初始化AiService 以防止热加载太慢 找不到相应的方法 -// try{ -// aiAgent.chat(userId, "initAiService",AgentSystemPrompt.sSystemPrompt); -// }catch (Exception e){ -// e.printStackTrace(); -// } log.info("用户{}Agent构建完成,已选场景:{},场景ID{}", userId, session.isSceneSelected() ? session.getCurrentScene().getSSceneName() : "未选(全场景匹配)", dynamicToolProvider.sSceneIdMap.get(userId)); } return aiAgent; diff --git a/src/main/java/com/xly/tool/DynamicToolProvider.java b/src/main/java/com/xly/tool/DynamicToolProvider.java index 07cd092..5a98b7f 100644 --- a/src/main/java/com/xly/tool/DynamicToolProvider.java +++ b/src/main/java/com/xly/tool/DynamicToolProvider.java @@ -14,6 +14,7 @@ import com.xly.config.OperableChatMemoryProvider; import com.xly.constant.*; import com.xly.entity.*; import com.xly.exception.dto.BusinessException; +import com.xly.exception.dto.DataException; import com.xly.mapper.ParamRuleMapper; import com.xly.mapper.ToolMetaMapper; import com.xly.service.DynamicExeDbService; @@ -30,12 +31,7 @@ import dev.langchain4j.memory.ChatMemory; import dev.langchain4j.model.chat.request.json.JsonObjectSchema; import dev.langchain4j.model.chat.request.json.JsonSchema; -import dev.langchain4j.service.tool.ToolExecutor; -import dev.langchain4j.service.tool.ToolProvider; - -import dev.langchain4j.service.tool.ToolProviderRequest; - -import dev.langchain4j.service.tool.ToolProviderResult; +import dev.langchain4j.service.tool.*; import lombok.Getter; @@ -59,58 +55,42 @@ import java.util.stream.IntStream; @RequiredArgsConstructor public class DynamicToolProvider implements ToolProvider { - // private final ToolMetaMapper toolMetaMapper; private final ObjectMapper objectMapper; private final ToolMetaMapper toolMetaMapper; private final ParamRuleMapper paramRuleMapper; private final DynamicExeDbService dynamicExeDbService; private final OperableChatMemoryProvider operableChatMemoryProvider; - - // 内存缓存:toolName -> ToolSpecificationHolder private final Map toolCache = new ConcurrentHashMap<>(); public final Map sSceneIdMap = new ConcurrentHashMap<>(); - - private final Map> sceneToolCacheMap = new ConcurrentHashMap<>(); + public final Map> sceneToolCacheMap = new ConcurrentHashMap<>(); private final List paramRuleDataAll = new ArrayList<>(); @Value("${erp.baseurl}") private String baseUrl; - /*** - * @Author 钱豹 - * @Date 14:05 2026/2/10 - * @Param [] - * @return void - * @Description 移除所有动态方法缓存 - **/ public void cleanAllToolProvider() { toolCache.clear(); sceneToolCacheMap.clear(); paramRuleDataAll.clear(); } - /** - * 初始化时加载所有启用的工具到缓存 - */ @jakarta.annotation.PostConstruct public void init() { - // List metas = toolMetaMapper.findAll(); for (ToolMeta meta : metas) { try { - //补全业务类型查询类显示的字段 doSetToolAIshowfieldShow(meta); ToolSpecification spec = buildToolSpecification(meta); ToolExecutor executor = createToolExecutor(meta); - toolCache.put(meta.getSMethodNo(), new ToolSpecificationHolder(spec, executor)); + toolCache.put(meta.getSMethodNo(), new ToolSpecificationHolder(spec, executor,meta.getSMethodNo())); log.info("已加载动态工具:{}", meta.getSMethodNo()); String sceneId = meta.getSSceneId(); List dataList = new ArrayList<>(); if(ObjectUtil.isNotEmpty(sceneToolCacheMap.get(sceneId))) { dataList = sceneToolCacheMap.get(sceneId); } - dataList.add(new ToolSpecificationHolder(spec, executor)); + dataList.add(new ToolSpecificationHolder(spec, executor,meta.getSMethodNo())); sceneToolCacheMap.put(sceneId, dataList); } catch (Exception e) { e.printStackTrace(); @@ -119,8 +99,136 @@ public class DynamicToolProvider implements ToolProvider { } } - //补全业务类型查询类显示的字段 - //{"1":"存储过程","2":"SQL查询","3":"第三方API","4":"窗体查询","5":"按钮执行","6":"其它"} + @Override + public ToolProviderResult provideTools(ToolProviderRequest request) { + String sUserId = request.chatMemoryId().toString(); + Map executors = new HashMap<>(); + UserSceneSession session = UserSceneSessionService.USER_SCENE_SESSION_CACHE.get(sUserId); + + // ====================== 防重复调用核心拦截 ====================== + if (session != null && (session.getToolExecuted() || StrUtil.isNotBlank(session.getSFunPrompts()))) { + log.info("工具已执行/等待用户回复,禁止提供工具"); + return ToolProviderResult.builder().build(); + } + + List datalist = new ArrayList<>(); + List toolMetaAll = new ArrayList<>(); + + if(session.getCurrentTool() != null){ + toolMetaAll.add(session.getCurrentTool()); + log.info("使用 currentTool: {}", session.getCurrentTool().getSMethodNo()); + } else { + toolMetaAll = session.getAuthTool(); + } + + if(ObjectUtil.isNotEmpty(toolMetaAll)){ + toolMetaAll = toolMetaAll.stream() + .filter(to -> to.getSSceneId().equals(sSceneIdMap.get(sUserId))) + .collect(Collectors.toUnmodifiableList()); + + if(ObjectUtil.isNotEmpty(toolMetaAll)){ + 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 -> { + executors.put(holder.getToolSpecification(), holder.getToolExecutor()); + }); + log.info("provideTools 返回工具数量: {}", executors.size()); + return ToolProviderResult.builder().addAll(executors).build(); + } + + + /*** + * @Author 钱豹 + * @Date 23:59 2026/5/17 + * @Param [meta] + * @return dev.langchain4j.service.tool.ToolExecutor + * @Description AI 自动调用工具执行方法 + **/ + 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.setToolExecuted(true); + if (StrUtil.isNotBlank(session.getSFunPrompts())) { + // 关键:返回 工具执行失败 = 框架强制停止循环 + throw new IllegalStateException("STOP_INVOCATION: 任务已完成,停止调用"); +// return "【任务已完成】请勿重复调用工具,请直接总结结果回复用户:"+session.getSFunPrompts(); + } + //解析参数失败 + Map argsNew; + try { + argsNew = objectMapper.readValue(toolExecutionRequest.arguments(), new TypeReference<>() {}); + log.info("解析后的参数: {}", argsNew); + } catch (Exception e) { + log.error("参数解析失败", e); + // 抛异常 + throw new RuntimeException("参数解析失败,请重新输入"); + } + + //获取之前获取的参数 + 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); + } + }); + + // 2 【补全动态参数】动态参数补全 + try{ + args = applyValues(args, meta.getParamRuleListCheck()); + }catch (Exception e){ + log.error("返回信息",e); + String askMsg = e.getMessage(); + session.setSFunPrompts(askMsg); + // 需要提问用户 → 抛异常停止循环 + throw new RuntimeException(askMsg); + } + // 2.1 【自动补全】应用参数的默认值 + List paramRuleData = meta.getParamRuleListAll(); + args = applyDefaultValues(args, paramRuleData); + session.setArgs(args); + // 3. 【自动校验】检查必填项 + List missing = checkRequiredParams(args, paramRuleData); + if (!missing.isEmpty()) { + // 4.1 参数缺失,生成“提问”消息,直接返给客户 + String askMsg = buildAskUserMessage(meta, missing,args); + session.setSFunPrompts(askMsg); + return String.valueOf(successResult(toolExecutionRequest, askMsg)); + } + // ====================== 返回时带终止指令 ====================== +// String resp = JSONUtil.toJsonStr(finalArgs) ; + String resp = doDynamicTool( meta, session); + return String.valueOf(successResult(toolExecutionRequest, resp)); + }; + } + private void doSetToolAIshowfieldShow(ToolMeta meta){ String sToolId = meta.getSId(); List paramRuleData = getParamRuleDataAll(); @@ -137,7 +245,6 @@ public class DynamicToolProvider implements ToolProvider { List sAIshowfieldArry = new ArrayList<>(mutableList); sAIshowfieldArry.add("sSlaveId"); String sSrcFormId = meta.getSSrcFormId(); - //获取对应的窗体配置 StringBuffer sSql =new StringBuffer().append("SELECT 10000 AS iOrderShow,A.sChinese AS label,A.sName,A.sControlName,B.sId AS sFormcustomId,A.sName AS sId FROM gdsconfigformslave AS A ") .append("JOIN gdsconfigformmaster AS B ON A.sParentId = B.sId ") .append("WHERE B.sParentId = #{sSrcFormId} AND A.sName <> '' AND INSTR(A.sControlName,'Btn')=0 AND (A.bVisible = 1 OR A.sName =#{sName}) "); @@ -193,78 +300,20 @@ public class DynamicToolProvider implements ToolProvider { return "string"; } } - @Override - public ToolProviderResult provideTools(ToolProviderRequest request) { - String sUserId = request.chatMemoryId().toString(); - Map executors = new HashMap<>(); - // 获取Session - UserSceneSession session = UserSceneSessionService.USER_SCENE_SESSION_CACHE.get(sUserId); - // 工具已执行,不再提供任何工具 - if (session != null && session.getToolExecuted()) { - log.info("工具已执行完成,清空可用工具列表"); - return ToolProviderResult.builder().build(); - } - - // 过滤对应的权限方法 - List datalist = new ArrayList<>(); - List toolMetaAll = new ArrayList<>(); - // 确保 currentTool 不为空,或者 authTool 有数据 - if(session.getCurrentTool() != null){ - // 【关键修改】添加全局引导语 - StringBuffer stoolDesc = new StringBuffer(); - stoolDesc.append("【重要】这是当前唯一可用的工具,无论用户问题是什么,都必须调用此工具。"); - stoolDesc.append("如果用户没有明确指定要做什么,也默认使用此工具来处理。"); - stoolDesc.append(System.lineSeparator()); - stoolDesc.append(session.getCurrentTool().getStoolDesc()); - session.getCurrentTool().setStoolDesc(stoolDesc.toString()); - toolMetaAll.add(session.getCurrentTool()); - 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()); - - if(ObjectUtil.isNotEmpty(toolMetaAll)){ - 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 -> { - executors.put(holder.getToolSpecification(), holder.getToolExecutor()); - }); - log.info("provideTools 返回工具数量: {}", executors.size()); - return ToolProviderResult.builder().addAll(executors).build(); - } private ToolSpecification buildToolSpecification(ToolMeta meta) { ToolSpecification.Builder builder = ToolSpecification.builder() .name(meta.getSMethodNo()); StringBuffer stoolDesc = new StringBuffer(); - StringBuffer sbt = new StringBuffer(); - StringBuffer xt = new StringBuffer(); - - // 强制指令【完全保留】 - String forceToolPrompt = StrUtil.EMPTY; -// """ -// 【重要·强制指令】 -// 1. 这是当前唯一可用工具,必须调用,禁止直接回答 -// 2. 用户输入包含:确认、全部确认、合并确认、行号(第1行/第一行等) -// 3. 必须调用本工具,只调用一次! -// """; + String forceToolPrompt = """ + 【工具调用规则】 + 1. 每个自定义方法最多只能调用 1 次 + 2. 只要方法返回结果,必须停止调用 + 3. 禁止重复调用同一个方法 + 4. 禁止无意义循环调用 + """; stoolDesc.append(forceToolPrompt); if (ObjectUtil.isNotEmpty(meta.getStoolDesc())) { stoolDesc.append("MethodNo:").append(meta.getSMethodNo()) @@ -287,97 +336,51 @@ public class DynamicToolProvider implements ToolProvider { String sRuleCost = getConstMeg(paramRule); switch (paramType.toLowerCase()) { case "string": - if (bEmpty) sbt.append(paramDesc).append("(字符串)、"); - else xt.append(paramDesc).append("(字符串)、"); schemaBuilder.addStringProperty(paramDesc, paramDesc); break; - case "integer": case "int": - if (bEmpty) sbt.append(paramDesc).append("(数字)、"); - else xt.append(paramDesc).append("(数字)、"); schemaBuilder.addIntegerProperty(paramDesc, paramDesc); break; - case "number": case "double": case "float": - if (bEmpty) sbt.append(paramDesc).append("(浮点)、"); - else xt.append(paramDesc).append("(浮点)、"); schemaBuilder.addNumberProperty(paramDesc, paramDesc); break; - case "boolean": case "bool": - if (bEmpty) sbt.append(paramDesc).append("(布尔)、"); - else xt.append(paramDesc).append("(布尔)、"); schemaBuilder.addBooleanProperty(paramDesc, paramDesc); break; - case "array": - // 印后工艺:从SQL获取可选工艺列表 - String postProcessArray = getArrrayBySql(paramRule); - List postProcessEnums = new ArrayList<>(); - if (StrUtil.isNotBlank(postProcessArray)) { - postProcessEnums = Arrays.asList(postProcessArray.split("/")); - } - StringBuffer paramDescTs = new StringBuffer(); - paramDescTs.append(paramDesc).append("(数组类型,可多选印后工艺 [").append(postProcessArray).append("]"); - // 处理默认值 - if (ObjectUtil.isNotEmpty(paramRule.getSDefaultValue()) || - (ObjectUtil.isNotEmpty(postProcessArray) && postProcessArray.split("/").length == 1)) { - String defaultVal = (ObjectUtil.isNotEmpty(postProcessArray) && postProcessArray.split("/").length == 1) - ? postProcessArray - : paramRule.getSDefaultValue(); - paramDescTs.append(",默认值[").append(defaultVal).append("]"); - } else { - paramDescTs.append(",无默认值"); - } - paramDescTs.append(")、"); - // 有枚举值走枚举,没有走普通字符串 - if (postProcessEnums.isEmpty()) { - sbt.append(paramDescTs); - schemaBuilder.addStringProperty(paramDesc, paramDescTs.toString()); + String enumStr = getArrrayBySql(paramRule); + List enums = StrUtil.isNotBlank(enumStr) ? Arrays.asList(enumStr.split("/")) : new ArrayList<>(); + if (enums.isEmpty()) { + schemaBuilder.addStringProperty(paramDesc, paramDesc + "(数组)"); } else { - xt.append(paramDescTs); - schemaBuilder.addEnumProperty(paramDesc, postProcessEnums, paramDescTs.toString()); + schemaBuilder.addEnumProperty(paramDesc, enums, paramDesc + "(数组)"); } break; - case "enum": - // 印后工艺单选枚举 - List postProcessList = new ArrayList<>(); - if (StrUtil.isNotBlank(sRuleCost)) { - postProcessList = Arrays.asList(sRuleCost.split("/")); - } - if (postProcessList.isEmpty()) { - schemaBuilder.addStringProperty(paramDesc, sRuleCost); + String constStr = getConstMeg(paramRule); + List constList = StrUtil.isNotBlank(constStr) ? Arrays.asList(constStr.split("/")) : new ArrayList<>(); + if (constList.isEmpty()) { + schemaBuilder.addStringProperty(paramDesc, paramDesc); } else { - schemaBuilder.addEnumProperty(paramDesc, postProcessList, sRuleCost); + schemaBuilder.addEnumProperty(paramDesc, constList, paramDesc); } break; default: schemaBuilder.addStringProperty(paramDesc, paramDesc); break; } - if (bEmpty) { requiredParams.add(paramDesc); } } - -// if (ObjectUtil.isNotEmpty(sbt)) { -// stoolDesc.append(System.lineSeparator()).append("1.必填参数:").append(sbt); -// } -// if (ObjectUtil.isNotEmpty(xt)) { -// stoolDesc.append(System.lineSeparator()).append("2.选填参数:").append(xt); -// } - } catch (Exception e) { e.printStackTrace(); } - // 固定添加 operateType if (meta.getIBizType() == 4 || meta.getIBizType() == 5) { schemaBuilder.addStringProperty("operateType", "操作类型:全部确认/合并确认/单行确认"); requiredParams.add("operateType"); @@ -392,13 +395,6 @@ public class DynamicToolProvider implements ToolProvider { .build(); } - /*** - * @Author 钱豹 - * @Date 23:42 2026/2/3 - * @Param [sConstConfig, sRule] - * @return java.lang.String - * @Description 常量类型枚举 - **/ private String getConstMeg(ParamRule paramRule){ if(!RuleCode.CONST.getCode().equals(paramRule.getSRule())){ return StrUtil.EMPTY; @@ -414,13 +410,11 @@ public class DynamicToolProvider implements ToolProvider { paramRule.setSRuleTs(sb.toString()); return sb.toString(); } - return StrUtil.EMPTY; } - //数组类型枚举 + private String getArrrayBySql(ParamRule paramRule){ Boolean bCheckArray = !(RuleCode.SQL.getCode().equals(paramRule.getSRule())); -// {"string":"字符","integer":"数字","double":"浮点","boolean":"布尔型","array":"数组","enum":"枚举"} Boolean bCheckArray2= !("array".equals(paramRule.getSType()) || "enum".equals(paramRule.getSType())); Boolean bCheckArray3= ObjectUtil.isEmpty(paramRule.getSParamConfig()); if(bCheckArray || bCheckArray2 || bCheckArray3){ @@ -434,10 +428,8 @@ public class DynamicToolProvider implements ToolProvider { if(data.get(0).containsKey("sGroupName")){ Map>> groupData = data.stream() .collect(Collectors.groupingBy(map -> map.get("sGroupName"))); - // 打印结果 Integer sUpKeySize =groupData.size(); groupData.forEach((key, value) -> { -// System.out.println("key: " + key); value.forEach(one->{ sb.append(one.get(paramRule.getSParamValue())).append("/"); }); @@ -446,8 +438,6 @@ public class DynamicToolProvider implements ToolProvider { sb.append(","); } }); - - }else{ data.forEach(one->{ if(ObjectUtil.isNotEmpty(one.get(paramRule.getSParamValue()))){ @@ -462,135 +452,34 @@ public class DynamicToolProvider implements ToolProvider { return sb.toString(); } - - /*** - * @Author 钱豹 - * @Date 15:08 2026/1/30 - * @Param [] - * @return java.util.List - * @Description 获取所有参数 - **/ private List getParamRuleDataAll(){ if(paramRuleDataAll==null || paramRuleDataAll.size()==0){ paramRuleDataAll.addAll(paramRuleMapper.findAll()); } return paramRuleDataAll; } - /*** - * @Author 钱豹 - * @Date 12:37 2026/3/16 - * @Param [meta] - * @return dev.langchain4j.service.tool.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); // 标记当前工具 - // 防止重复执行 - if (session.getToolExecuted()) { - log.warn("工具已执行过,拒绝重复调用: {}", meta.getSMethodNo()); - return String.valueOf(successResult(toolExecutionRequest, "{\"status\":\"already_executed\"}")); - } - //标记执行了(所有工具方法只执行一次) - session.setToolExecuted(true); - // 1. 解析参数 - Map argsNew; - try { - argsNew = objectMapper.readValue(toolExecutionRequest.arguments(), new TypeReference<>() {}); - log.info("解析后的参数: {}", argsNew); - } catch (Exception e) { - log.error("参数解析失败", e); - String errorMsg = "参数解析失败,请重新输入"; - return String.valueOf(errorResult(toolExecutionRequest, errorMsg)); - } - 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); - } - }); - // 2 【补全动态参数】动态参数补全 - try{ - args = applyValues(args, meta.getParamRuleListCheck()); - }catch (Exception e){ - log.error("返回信息",e); - String askMsg = e.getMessage(); - session.setSFunPrompts(askMsg); - return String.valueOf(successResult(toolExecutionRequest, askMsg)); - } - List paramRuleData = meta.getParamRuleListAll(); - // 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); - session.setSFunPrompts(askMsg); - return String.valueOf(successResult(toolExecutionRequest, askMsg)); - } - // 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))); - }; - } public String doDynamicTool(ToolMeta meta,UserSceneSession session) { List paramRuleData = meta.getParamRuleListAll(); List paramRuleDataCheck = meta.getParamRuleListCheck(); Map args = session.getArgs(); - // 3. 【自动校验】检查必填项 List missing = checkRequiredParams(args, paramRuleDataCheck); if (!missing.isEmpty()) { - // 4.1 参数缺失,生成“提问”消息,直接返给客户 String askMsg = buildAskUserMessage(meta, missing,args); - //告知AI 缺失参数 -// operableChatMemoryProvider.get(session.getUserId()).add(UserMessage.from(askMsg)); return askMsg; } - // 6. 【最终确认信息】所有检测通过后,需要和客户确认交互 List chatMessage = operableChatMemoryProvider.getCurrentChatMessages(session.getUserId()); ChatMessage userMessage = getLastUserMessage(chatMessage); String input = ""; if (userMessage != null) { input = StrUtil.replace(getChatMessageContent(userMessage), "用户输入:", 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() @@ -599,40 +488,29 @@ public class DynamicToolProvider implements ToolProvider { || 8== meta.getIBizType() ) { - // 确认后必填项校验 List missingAfter = checkConfirmAfterParam(args, paramRuleData); if (!missingAfter.isEmpty()) { - // 4.1 参数缺失,生成“提问”消息,直接返给客户 String askMsg = buildAskUserMessage(meta, missingAfter,args); - return askMsg; + throw new DataException(askMsg); } - // 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); - // ===================== 加入这一行 ===================== String sSystemPrompt = buildDynamicSystemPrompt(session); session.setSSystemPrompt(sSystemPrompt); - //===================== AI 返回需要确认 ===================== return executeWithConfirmation(askconfirmMsg, session, meta); }else{ askconfirmMsg =getDefMessage(args,meta.getSControlName(),meta); } - // 返回需要确认的结果 return executeWithConfirmation(askconfirmMsg, session, meta); } - /** - * 安全获取 ChatMessage 内容,适配你当前所有版本 - * 不依赖 UserMessage / .text() - */ private String getChatMessageContent(ChatMessage message) { if (message == null) return ""; - try { - // 通用反射获取内容(兼容所有版本) return message.toString() .replace("ChatMessage{", "") .replace("}", "") @@ -642,9 +520,9 @@ public class DynamicToolProvider implements ToolProvider { return ""; } } + private ChatMessage getLastUserMessage(List messages) { if (messages == null || messages.isEmpty()) return null; - for (int i = messages.size() - 1; i >= 0; i--) { ChatMessage msg = messages.get(i); if (msg.type() == ChatMessageType.USER) { @@ -654,14 +532,6 @@ public class DynamicToolProvider implements ToolProvider { return null; } - - /*** - * @Author 钱豹 - * @Date 15:16 2026/2/9 - * @Param [argMap] - * @return java.lang.String - * @Description MAP转提示 - **/ private String getDefMessage(Map argMap,String sName,ToolMeta meta){ List showList = meta.getParamRuleListAll(); List showListData = new ArrayList<>(); @@ -684,67 +554,31 @@ public class DynamicToolProvider implements ToolProvider { appendConfirm(markdown,sName); return markdown.toString(); } - /*** - * @Author 钱豹 - * @Date 14:56 2026/2/9 - * @Param [markdown] - * @return void - * @Description 全部确认 - **/ + private void appendConfirmAll(StringBuilder markdown,String sName){ sName = ObjectUtil.isEmpty(sName)?StrUtil.EMPTY:"["+sName+"]"; markdown.append("请确认是否执行").append(sName).append("操作?1.全部数据生成多个单据 回复【全部确认】;2.全部数据生成一个单据 回复【合并确认】;3.按自然语义描述生成一个单据 如"第1行确认"\n"); - //全部确认 ,部分确认,取消 markdown.append("回复:  ").append("**全部确认**").append(" ") .append("**合并确认**").append(" ") .append("**取消**"); } - /*** - * @Author 钱豹 - * @Date 14:56 2026/2/9 - * @Param [markdown] - * @return void - * @Description 单条确认 - **/ + private void appendConfirm(StringBuilder markdown,String sName){ sName = ObjectUtil.isEmpty(sName)?StrUtil.EMPTY:"["+sName+"]"; markdown.append("请确认是否执行").append(sName).append("操作?请回复:  ").append("**确认**").append(" ") .append("**取消**"); } - private ChatMessage getLasterUserMssage(List chatMessage){ - if(chatMessage!=null){ - for(int i=chatMessage.size()-1;i>0;i--){ - ChatMessage data = chatMessage.get(i); - ChatMessageType sType = data.type(); - if(ChatMessageType.USER.equals(sType)){ - return chatMessage.get(i); - } - } - } - return null; - } - - /*** - * @Author 钱豹 - * @Date 13:35 2026/1/31 - * @Param [args, paramDefs] - * @return java.util.Map - * @Description Map 值转换 - **/ private Map transformationArgs(Map args, List paramDefs) { Map result = new HashMap<>(args); paramDefs.forEach(pd->{ String name = pd.getSParam(); String sValue = pd.getSParamValue(); - //中文 Boolean bCheck = result.containsKey(name) && ObjectUtil.isNotEmpty(result.get(name)); - //英文字段 Boolean bCheck2 = result.containsKey(sValue) && ObjectUtil.isNotEmpty(result.get(sValue)); if (!bCheck2 && bCheck ) { result.put(sValue,args.get(name)); } - //常量value -> key 转换 用于后面入库 if(RuleCode.CONST.getCode().equals(pd.getSRule()) && ObjectUtil.isNotEmpty(result.get(sValue))){ String sData = result.get(sValue).toString(); Map configData = JSONUtil.parseObj(pd.getSParamConfig()); @@ -758,13 +592,9 @@ public class DynamicToolProvider implements ToolProvider { return result; } - /** - * 动态参数补全SQL - */ private Map applyValues(Map args, List paramDefsCheck) { Map result = new HashMap<>(args); result = transformationArgs( result, paramDefsCheck); - //根据iOrder 排序 List paramDefs = new ArrayList<>(paramDefsCheck); paramDefs.sort(Comparator.comparing(ParamRule::getIOrder)); for (ParamRule pd : paramDefs) { @@ -777,38 +607,30 @@ public class DynamicToolProvider implements ToolProvider { bCheck = bCheck && ObjectUtil.isNotEmpty(result.get(name)); bCheck = bCheck && RuleCode.SQL.getCode().equals(sRule); bCheck = bCheck && ObjectUtil.isNotEmpty(pd.getSParamConfig()); -// bCheck = bCheck && !"array".equals(sType); Boolean bCheck2 = result.containsKey(sValue); bCheck2 = bCheck2 && ObjectUtil.isNotEmpty(result.get(sValue)); bCheck2 = bCheck2 && RuleCode.SQL.getCode().equals(sRule); bCheck2 = bCheck2 && ObjectUtil.isNotEmpty(pd.getSParamConfig()); -// bCheck2 = bCheck2 && !"array".equals(sType); - //存在动态SQL 并且是枚举的需要 + if ((bCheck || bCheck2) &&("enum".equals(sType) || "string".equals(sType))){ String sSql = pd.getSParamConfig(); String sKey = bCheck?name:sValue; String nameValue = result.get(sKey).toString(); List> dataList = dynamicExeDbService.findSql(result,sSql); - //传入的参数无效返回继续盘问消息 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)){ - //枚举返回了数组 纠正成字符串 if(args.get(name) instanceof List){ args.put(name,((List) args.get(name)).get(0)); -// args.put(sValue,((List) args.get(name)).get(0)); } if(args.get(sValue) instanceof List){ args.put(sValue,((List) args.get(sValue)).get(0)); -// args.put(name,((List) args.get(sValue)).get(0)); } } List> dataListNew = dataList.stream().filter(one-> one.get(sValue).equals(args.get(name)) || one.get(sValue).equals(args.get(sValue))).collect(Collectors.toUnmodifiableList()); - //如果枚举类型枚举值中又不存在AI 返回的数据 if("enum".equals(sType) && ObjectUtil.isEmpty(dataListNew)){ args.remove(name); args.remove(sValue); @@ -827,7 +649,6 @@ public class DynamicToolProvider implements ToolProvider { result.put(sValue, dataList.get(0).get(sValue)); result.put(name, dataList.get(0).get(sValue)); }else{ - //赋值到 String[] sCopyToA = sCopyTo.split(","); for(String sCopyToOne:sCopyToA){ String[] sCopyToOneA = sCopyToOne.split(":"); @@ -836,7 +657,6 @@ public class DynamicToolProvider implements ToolProvider { } } StringBuffer sData=new StringBuffer(); - //存在多个形成提示语 if(dataList.size()>1){ List> finalDataList = dataList; IntStream.range(0, dataList.size()) @@ -858,17 +678,12 @@ public class DynamicToolProvider implements ToolProvider { return result; } - /** - * 应用参数的默认值 - */ private Map applyDefaultValues(Map args, List paramDefs) { Map result = new HashMap<>(args); for (ParamRule pd : paramDefs) { String name = pd.getSParam(); if ((!result.containsKey(name)|| ObjectUtil.isEmpty(result.get(name))) && ObjectUtil.isNotEmpty(pd.getSDefaultValue()) -// && !"enum".equals(pd.getSType()) -// && !"array".equals(pd.getSType()) ) { Object defaultValue = pd.getSDefaultValue(); result.put(name, defaultValue); @@ -876,18 +691,7 @@ public class DynamicToolProvider implements ToolProvider { } return result; } - /** - * 检查必填参数 - */ - private List getRequiredParams(List paramDefs) { - return paramDefs.stream() - .map(ParamRule::getSParam) - .toList(); - } - /** - * 检查必填参数 - */ private List checkRequiredParams(Map args, List paramDefs) { Map returnMap = transformationArgs( args, paramDefs); return paramDefs.stream() @@ -915,10 +719,6 @@ public class DynamicToolProvider implements ToolProvider { return bDbZero || bBhcs || (!returnMap.containsKey(pd.getSParamValue()) || (ObjectUtil.isEmpty(returnMap.get(pd.getSParamValue())))); } - - /** - * 确认后必填参数 - */ private List checkConfirmAfterParam(Map args, List paramDefs) { Map returnMap = transformationArgs( args, paramDefs); return paramDefs.stream() @@ -931,13 +731,8 @@ public class DynamicToolProvider implements ToolProvider { .toList(); } - - /** - * 模拟执行工具 - */ 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{ @@ -951,100 +746,86 @@ public class DynamicToolProvider implements ToolProvider { session.setBCleanMemory(true); } return sReturn; - } - /**** - * @Author 钱豹 - * @Date 10:26 2026/2/1 - * @Param - * @return - * @Description 返回结果后 执行业务类 - **/ + 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(); - args.put("sUserId", session.getUserId()); - args.put("sLoginId", session.getUserName()); - args.put("sMakePerson", session.getUserName()); - args.put("sBrId", session.getSBrandsId()); - args.put("sBrandsId", session.getSBrandsId()); - args.put("sSuId", session.getSSubsidiaryId()); - args.put("sSrcFormId", meta.getSSrcFormId()); - args.put("sControlName", meta.getSControlName()); - args.put("iBizType", iBizType); - args.put("sSubsidiaryId", session.getSSubsidiaryId()); - args.put("sToolId", meta.getSId()); - if (iBizType == 1 || iBizType == 4) { - Map data = new HashMap<>(args); - data.put("sData", JSONObject.toJSONString((data))); - if(ObjectUtil.isEmpty(sBizContent) && iBizType == 4){ - sBizContent ="Sp_Ai_AddCommonAfterNew"; - //获取未清数据 - if(ObjectUtil.isEmpty(args.get("sSlaveId"))){ - throw new BusinessException(-1,"请选择操作数据"); - } - List> sRowData = doGetFromDataWq( meta, args, session); - data.put("sRowData", JSONObject.toJSONString(sRowData)); - } - Map searMap = this.dynamicExeDbService.getDoProMap(sBizContent, data); - Map sReturn = this.dynamicExeDbService.getCallPro(searMap, sBizContent); - Integer sCode = ObjectUtil.isNotEmpty(sReturn.get(ProcedureConstant.SCODE)) ? Integer.valueOf(sReturn.get(ProcedureConstant.SCODE).toString()) : 0; - String sMsgText = ObjectUtil.isNotEmpty(sReturn.get(ProcedureConstant.SRETURN)) ? sReturn.get(ProcedureConstant.SRETURN).toString() : "操作成功"; - if (sCode < 0) { - sMsgText = ObjectUtil.isEmpty(sMsgText) ? "调用过程sCode:" + Integer.valueOf(searMap.get(ProcedureConstant.SCODE).toString()) : sMsgText; - session.setSFunPrompts(sMsgText); - throw new BusinessException(sCode,sMsgText); - } - Map outMap = (Map) sReturn.get("outMap"); - String sId = ObjectUtil.isNotEmpty(outMap.get("sBillId")) ? outMap.get("sBillId").toString() : ""; - session.setSCopyTo(meta.getSControlName()); - session.setSCopyToSrcId(sId); - session.setSFunPrompts(sMsgText); - return sMsgText; - } else if (iBizType == 2 && ObjectUtil.isNotEmpty(sBizContent)) { - //SQL查询 - if (sBizContent.toLowerCase().startsWith("update")) { - this.dynamicExeDbService.updateSql(args, sBizContent); - } else if (sBizContent.toLowerCase().startsWith("delete")) { - this.dynamicExeDbService.delSql(args, sBizContent); - } else if (sBizContent.toLowerCase().startsWith("insert")) { - this.dynamicExeDbService.addSql(args, sBizContent); - } else { - List> retData = this.dynamicExeDbService.findSql(args, sBizContent); - if (ObjectUtil.isNotEmpty(retData)) { - StringBuffer sb = new StringBuffer(); - retData.forEach(one -> { - one.forEach((k, v) -> { - sb.append(v).append(" "); - }); - sb.append("
"); - }); - if (ObjectUtil.isNotEmpty(retData)) { - sb.append("请根据这些信息安排今天的工作吧!如果有具体任务需要进一步处理,请告诉我"); - } - session.setSFunPrompts(sb.toString()); - if ("queryTodayTask".equals(meta.getSMethodNo())) { - session.setBCleanMemory(true); - } - return sb.toString(); - } else { - String sMsgText = "未找到对应的数据"; - session.setSFunPrompts(sMsgText); - throw new BusinessException(-1,sMsgText); - } - } - } else if (iBizType == 3) { - return HttpsRequestUtil.me().doRequestHttp(sBizContent, JSONUtil.toJsonStr(args), - new HashMap<>(), "POST", "JSON"); - } - return "操作成功"; + String sBizContent = meta.getSBizContent(); + Integer iBizType = meta.getIBizType(); + args.put("sUserId", session.getUserId()); + args.put("sLoginId", session.getUserName()); + args.put("sMakePerson", session.getUserName()); + args.put("sBrId", session.getSBrandsId()); + args.put("sBrandsId", session.getSBrandsId()); + args.put("sSuId", session.getSSubsidiaryId()); + args.put("sSrcFormId", meta.getSSrcFormId()); + args.put("sControlName", meta.getSControlName()); + args.put("iBizType", iBizType); + args.put("sSubsidiaryId", session.getSSubsidiaryId()); + args.put("sToolId", meta.getSId()); + if (iBizType == 1 || iBizType == 4) { + Map data = new HashMap<>(args); + data.put("sData", JSONObject.toJSONString((data))); + if(ObjectUtil.isEmpty(sBizContent) && iBizType == 4){ + sBizContent ="Sp_Ai_AddCommonAfterNew"; + if(ObjectUtil.isEmpty(args.get("sSlaveId"))){ + throw new BusinessException(-1,"请选择操作数据"); + } + List> sRowData = doGetFromDataWq( meta, args, session); + data.put("sRowData", JSONObject.toJSONString(sRowData)); + } + Map searMap = this.dynamicExeDbService.getDoProMap(sBizContent, data); + Map sReturn = this.dynamicExeDbService.getCallPro(searMap, sBizContent); + Integer sCode = ObjectUtil.isNotEmpty(sReturn.get(ProcedureConstant.SCODE)) ? Integer.valueOf(sReturn.get(ProcedureConstant.SCODE).toString()) : 0; + String sMsgText = ObjectUtil.isNotEmpty(sReturn.get(ProcedureConstant.SRETURN)) ? sReturn.get(ProcedureConstant.SRETURN).toString() : "操作成功"; + if (sCode < 0) { + sMsgText = ObjectUtil.isEmpty(sMsgText) ? "调用过程sCode:" + Integer.valueOf(searMap.get(ProcedureConstant.SCODE).toString()) : sMsgText; + session.setSFunPrompts(sMsgText); + throw new BusinessException(sCode,sMsgText); + } + Map outMap = (Map) sReturn.get("outMap"); + String sId = ObjectUtil.isNotEmpty(outMap.get("sBillId")) ? outMap.get("sBillId").toString() : ""; + session.setSCopyTo(meta.getSControlName()); + session.setSCopyToSrcId(sId); + session.setSFunPrompts(sMsgText); + return sMsgText; + } else if (iBizType == 2 && ObjectUtil.isNotEmpty(sBizContent)) { + if (sBizContent.toLowerCase().startsWith("update")) { + this.dynamicExeDbService.updateSql(args, sBizContent); + } else if (sBizContent.toLowerCase().startsWith("delete")) { + this.dynamicExeDbService.delSql(args, sBizContent); + } else if (sBizContent.toLowerCase().startsWith("insert")) { + this.dynamicExeDbService.addSql(args, sBizContent); + } else { + List> retData = this.dynamicExeDbService.findSql(args, sBizContent); + if (ObjectUtil.isNotEmpty(retData)) { + StringBuffer sb = new StringBuffer(); + retData.forEach(one -> { + one.forEach((k, v) -> { + sb.append(v).append(" "); + }); + sb.append("
"); + }); + if (ObjectUtil.isNotEmpty(retData)) { + sb.append("请根据这些信息安排今天的工作吧!如果有具体任务需要进一步处理,请告诉我"); + } + session.setSFunPrompts(sb.toString()); + if ("queryTodayTask".equals(meta.getSMethodNo())) { + session.setBCleanMemory(true); + } + return sb.toString(); + } else { + String sMsgText = "未找到对应的数据"; + session.setSFunPrompts(sMsgText); + throw new BusinessException(-1,sMsgText); + } + } + } else if (iBizType == 3) { + return HttpsRequestUtil.me().doRequestHttp(sBizContent, JSONUtil.toJsonStr(args), + new HashMap<>(), "POST", "JSON"); + } + return "操作成功"; } - /*** - * @Author - * 确认获取未清数据 - **/ private List> doGetFromDataWq(ToolMeta meta, Map args,UserSceneSession session){ String sUrl = meta.getSendUrl(); Map sBody = new HashMap<>(); @@ -1064,13 +845,9 @@ public class DynamicToolProvider implements ToolProvider { headers.put("Authorization",session.getAuthorization()); String result; try{ - // 1. 获取实例 result = HttpsRequestUtil.me().doRequestHttp(sUrl,JSONObject.toJSONString(sBody),headers,"POST","JSON"); log.info("请求URL========================{}", sUrl); log.info("请求URLresult========================{}", result); - log.info("JSON==========================={}", JSONObject.toJSONString(sBody)); - log.info("headers=============================={}", JSONObject.toJSONString(headers)); - log.info("请求URL,JSON,headers=={},{},{}",sUrl,JSONObject.toJSONString(sBody),JSONObject.toJSONString(headers)); ErpResult erpResult = JsonUtils.toObject(result,ErpResult.class); if(ObjectUtil.isNotEmpty(erpResult) && ObjectUtil.isNotEmpty(erpResult.getDataset()) @@ -1081,19 +858,10 @@ public class DynamicToolProvider implements ToolProvider { return erpResult.getDataset().getRows().get(0).getDataSet(); } }catch (Exception e){ -// result ="执行异常:"+e.getMessage(); } return new ArrayList<>(); } - - /*** - * @Author 钱豹 - * @Date 23:38 2026/2/5 - * @Param [] - * @return void - * @Description 窗体获取数据方法 未清或者明细 - **/ private String doGetFromData(ToolMeta meta, Map args,UserSceneSession session){ String sUrl = meta.getSendUrl(); Map sBody = new HashMap<>(); @@ -1101,7 +869,6 @@ public class DynamicToolProvider implements ToolProvider { sBody.put("pageSize",10000); log.info("doGetFromData========================"); List> list = new ArrayList<>(); - //移除确认标识 args.remove("operateType"); if(ObjectUtil.isNotEmpty(args)){ List paramDefs = meta.getParamRuleList(); @@ -1140,13 +907,9 @@ public class DynamicToolProvider implements ToolProvider { headers.put("Authorization",session.getAuthorization()); String result; try{ - // 1. 获取实例 result = HttpsRequestUtil.me().doRequestHttp(sUrl,JSONObject.toJSONString(sBody),headers,"POST","JSON"); log.info("请求URL========================{}", sUrl); log.info("请求URLresult========================{}", result); - log.info("JSON==========================={}", JSONObject.toJSONString(sBody)); - log.info("headers=============================={}", JSONObject.toJSONString(headers)); - log.info("请求URL,JSON,headers=={},{},{}",sUrl,JSONObject.toJSONString(sBody),JSONObject.toJSONString(headers)); ErpResult erpResult = JsonUtils.toObject(result,ErpResult.class); result = buildResultMessageWithTable( meta, erpResult, session); }catch (Exception e){ @@ -1155,13 +918,9 @@ public class DynamicToolProvider implements ToolProvider { return result; } - /** - * 构建 窗体获取数据方法 未清或者明细 - */ public String buildResultMessageWithTable(ToolMeta meta,ErpResult erpResult,UserSceneSession session){ Map> currentRowData = new HashMap<>(); ErpDataset dataset = erpResult.getDataset(); - //返回错误信息 if(erpResult.getCode()<0 && ObjectUtil.isNotEmpty(erpResult.getMsg())){ return erpResult.getMsg(); } @@ -1175,7 +934,6 @@ 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"); @@ -1185,7 +943,6 @@ public class DynamicToolProvider implements ToolProvider { } StringBuilder markdown = new StringBuilder(); - //状态 if(ObjectUtil.isNotEmpty(session.getArgs())){ markdown.append("**查询条件**:"); List pr = session.getCurrentTool().getParamRuleListCheck(); @@ -1205,7 +962,6 @@ public class DynamicToolProvider implements ToolProvider { .append(one.getSParam()).append(":").append(argsOld.get(one.getSParam()).toString()).append(" ") .append(" × ") .append(""); -// markdown.append(one.getSParam()).append(":").append(argsOld.get(one.getSParam()).toString()).append(" "); } } } @@ -1225,39 +981,29 @@ public class DynamicToolProvider implements ToolProvider { markdown.append(",显示前").append(rows.size()).append("条。如需查看全部,请指定筛选条件。"); } } -// markdown.append("\n---\n"); markdown.append("\n\n").append("| 序号 | "); headers.forEach(header -> markdown.append(header).append(" | ")); markdown.append("\n|").append("---|".repeat(headers.size() + 1)).append("\n"); - // 填充表格数据 List> machineData = new LinkedList<>(); for (int i = 0; i < recordData.size(); i++) { - // 保存隐藏列的值(如"唯一"字段) String uniqueValue = recordData.get(i).get("sSlaveId") != null ? recordData.get(i).get("sSlaveId").toString() : ""; markdown.append("| ").append(i + 1).append(" | "); Map rMap = new HashMap<>(); for (String header : headers) { - // 这里需要根据你的数据结构来获取对应的值 Object value = recordData.get(i)!= null ? recordData.get(i).get(header) : null; markdown.append(value != null ? value : "—").append(" | "); rMap.put(header,value); } rMap.put("sSlaveId",uniqueValue); rMap.put("唯一",uniqueValue); - // 在行末添加隐藏数据的特殊标记(AI可以解析) markdown.append(" HIDDEN_DATA:") .append(JSONUtil.toJsonStr(rMap)) .append(""); -// markdown.append(" "); markdown.append("\n"); machineData.add(rMap); currentRowData.put(i + 1,recordData.get(i)); } markdown.append(">"); -// // 4. 机器可读的结构化数据(只出现一次!) -// markdown.append("\n"); -// markdown.append(JSONUtil.toJsonStr(machineData)); -// markdown.append("\n\n\n"); if(meta.getIBizType()==4){ markdown.append("\n---\n"); appendConfirmAll(markdown,meta.getSControlName()); @@ -1295,40 +1041,8 @@ public class DynamicToolProvider implements ToolProvider { } """.formatted(rowJson, methodNo); } -// public String buildDynamicSystemPrompt(UserSceneSession session) { -// // 动态获取当前工具方法号 -// String methodNo = session.getCurrentTool().getSMethodNo(); -// String promptHead = """ -// 【极强约束·必须执行】 -// 1. 禁止说话!禁止解释! -// 2. 必须调用工具,固定方法编号:MethodNo = %s -// 3. 只输出JSON,无任何其他内容 -// 以下是【行号 → sSlaveId】对应数据: -// """.formatted(methodNo); -// -// Map> rowDataMap = session.getCurrentRowData(); -// StringBuilder rowDataSb = new StringBuilder(); -// if (ObjectUtil.isNotEmpty(rowDataMap)) { -// for (Map.Entry> entry : rowDataMap.entrySet()) { -// int rowNum = entry.getKey(); -// String sSlaveId = StrUtil.toString(entry.getValue().get("sSlaveId")); -// rowDataSb.append("第").append(rowNum).append("行 → ").append(sSlaveId).append("\n"); -// } -// } -// String promptFoot = """ -// 【输出JSON格式·严格遵守】 -// 根据用户选择的行,自动填写 sSlaveId,多行用英文逗号拼接 -// { -// "operateType": "全部确认/合并确认/单行确认", -// "sSlaveId": "填写对应的ID,多个用逗号分隔" -// } -// """; -// return promptHead + rowDataSb + promptFoot; -// } - - // 辅助方法:根据中文名查找字段名(通过映射关系转换) + private List> findFieldNameByChinese(List> sAIshowfieldShow,List> rows){ - //获取映射关系 Map keyMappings = new HashMap<>(); List selectedKeys = new ArrayList<>(); sAIshowfieldShow.forEach(one->{ @@ -1342,13 +1056,6 @@ public class DynamicToolProvider implements ToolProvider { return sRowData; } - /*** - * @Author 钱豹 - * @Date 2:04 2026/2/6 - * @Param [rows, limit, selectedKeys] - * @return java.util.List> - * @Description 返回指定数量并筛选指定key - **/ public List> getFilteredDataStream(List> rows, int limit, List selectedKeys, @@ -1357,13 +1064,11 @@ public class DynamicToolProvider implements ToolProvider { return Collections.emptyList(); } return rows.stream() - .limit(limit) // 限制数量 + .limit(limit) .map(original -> { - // 创建新的Map,只包含指定key Map filtered = new HashMap<>(); selectedKeys.forEach(key -> { if (original.containsKey(key)) { - //指定映射的key 放中文 filtered.put(keyMappings.get(key), original.get(key)); } }); @@ -1373,9 +1078,6 @@ public class DynamicToolProvider implements ToolProvider { .collect(Collectors.toList()); } - /** - * 构建确认操作消息 - */ private String buildConfirmUserMessage(ToolMeta meta, Map args) { StringBuilder markdown = new StringBuilder(); markdown.append("参数提取如下:\n\n"); @@ -1390,11 +1092,6 @@ public class DynamicToolProvider implements ToolProvider { return markdown.toString(); } - - - /** - * 构建提问消息 - */ private String buildAskUserMessage(ToolMeta meta, List missing,Map arg) { StringBuilder sb = new StringBuilder(); sb.append("缺少参数请补全:\n"); @@ -1429,18 +1126,15 @@ public class DynamicToolProvider implements ToolProvider { return sb.toString(); } - // 创建提前成功的结果 private String createEarlySuccessResult(ToolExecutionRequest request, String message) { - // 设置一个标志,告诉执行器不要继续执行 return JSONUtil.toJsonStr(Map.of( "status", "success", "message", message, - // 关键标志 "executionCompleted", true, "data", successResult(request, message) )); } - // 创建终止执行的结果 + private String createTerminationResult(String message) { return JSONUtil.toJsonStr(Map.of( "status", "terminated", @@ -1449,70 +1143,30 @@ public class DynamicToolProvider implements ToolProvider { )); } - /*** - * @Author 钱豹 - * @Date 10:15 2026/1/31 - * @Param [request, errorMsg] - * @return dev.langchain4j.data.message.ToolExecutionResultMessage - * @Description 错误返回 - **/ private ToolExecutionResultMessage errorResult(ToolExecutionRequest request, String errorMsg) { return ToolExecutionResultMessage.from(request, errorMsg); } - /*** - * @Author 钱豹 - * @Date 10:15 2026/1/31 - * @Param [request, text] - * @return dev.langchain4j.data.message.ToolExecutionResultMessage - * @Description 构建正确返回 - **/ + private ToolExecutionResultMessage successResult(ToolExecutionRequest request, String text) { return ToolExecutionResultMessage.from(request, text); } - /** - * 执行方法后需要用户确认的扩展版本 - */ private String executeWithConfirmation(String initialResult, UserSceneSession session,ToolMeta meta) { - - // 第一步:执行原始操作,返回初步结果 Map step1Result = new HashMap<>(); step1Result.put("initialResult", initialResult); step1Result.put("status", "PENDING_CONFIRMATION"); step1Result.put("confirmationRequired", true); step1Result.put("confirmationMessage", initialResult); -// // 将确认状态保存到对话记忆 -// chatMemory.add(UserMessage.from("SYSTEM: 等待用户确认操作")); String userMessage = formatConfirmationResult(step1Result); session.setCurrentTool(meta); -// session.setSFunPrompts(userMessage); - // 6. 返回确认请求 -// return ToolExecutionResultMessage.from(request,userMessage); -// ToolExecutionResultMessage.from(request, text); return userMessage; } private String formatConfirmationResult(Map result) { - return String.format( - """ - %s - """, - result.get("initialResult"), - result.get("confirmationMessage") - ); + return String.format("%s", result.get("initialResult")); } - /*** - * @Author 钱豹 - * @Date 0:54 2026/2/4 - * @Param [userResponse] - * @return boolean - * @Description 检查是确认 - **/ public boolean isConfirmed(String userResponse) { return userResponse.matches("(?i)(确认|全部确认|部分确认|是|yes|confirm|true|是的|可以|没问题|确定|好的|生成|)"); } - - - -} +} \ No newline at end of file diff --git a/src/main/java/com/xly/tool/ToolSpecificationHolder.java b/src/main/java/com/xly/tool/ToolSpecificationHolder.java index 6d66239..758520a 100644 --- a/src/main/java/com/xly/tool/ToolSpecificationHolder.java +++ b/src/main/java/com/xly/tool/ToolSpecificationHolder.java @@ -7,9 +7,11 @@ import dev.langchain4j.agent.tool.ToolSpecification; public class ToolSpecificationHolder { private final ToolSpecification toolSpecification; private final ToolExecutor toolExecutor; - public ToolSpecificationHolder(ToolSpecification toolSpecification, ToolExecutor toolExecutor) { + private final String sName; + public ToolSpecificationHolder(ToolSpecification toolSpecification, ToolExecutor toolExecutor,String sName) { this.toolSpecification = toolSpecification; this.toolExecutor = toolExecutor; + this.sName = sName; } public ToolSpecification getToolSpecification() { @@ -18,4 +20,11 @@ public class ToolSpecificationHolder { public ToolExecutor getToolExecutor() { return toolExecutor; - }} + } + + public String getsName() { + return sName; + } + +} + diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 1577762..2e0a787 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -135,8 +135,8 @@ langchain4j: ollama: # 聊天模型配置(用于一般对话) base-url: http://112.82.245.194:11434 -# chat-model-name: qwen2.5:7b-instruct - chat-model-name: qwen3:14b + chat-model-name: qwen2.5:7b-instruct +# chat-model-name: qwen3:14b # chat-model-name: qwen3.5:9b # SQL/代码模型配置(专门用于代码和SQL生成) sql-model-name: qwen2.5-coder:7b @@ -171,4 +171,4 @@ tts: timeout: 30000 max-connections: 10 erp: - baseurl: http://8.130.144.93:8080/xlyEntry_saas \ No newline at end of file + baseurl: http://118.178.19.35:8080/xlyEntry_saas \ No newline at end of file