Commit a3e3d13989a505e3f50c1f87e48c99130946e2cb

Authored by qianbao
1 parent 6886121d

AI添加问题 历史问题记录

src/main/java/com/xly/agent/ErpAiAgent.java
... ... @@ -14,14 +14,23 @@ public interface ErpAiAgent {
14 14 1. 方法匹配:先精准拆解用户查询的核心业务意图,再自动匹配唯一符合用户问题的工具方法(MethodNo),禁止自创,规则如下;
15 15 1.1 匹配方法时,无需考虑工具描述(@TOOL)中 1.必填参数,2.选填参数,示例,parameters内容 四个部分的内容;
16 16 1.2 匹配方法时,只关注工具描述(@TOOL)中 “当用户” 和 “时,必须调用本工具”两个短语之间的内容;
17   - 1.3 调用工具前,不需要询问用户提供缺失的参数
18 17 2. 参数提取:提取该工具的全部参数,与描述完全一致,严格按标注类型赋值,规则如下:
19 18 2.1 数字无引号,为空时禁止赋值0;
20 19 2.2 如果有空格需要去掉空格后再提取。
  20 + 2.3 每次都需要进行参数提取
21 21 """)
22 22 @UserMessage("用户输入:{{userInput}}")
23 23 String chat(@MemoryId String userId, @V("userInput") String userInput);
24 24  
  25 + @SystemMessage("{{stoolDesc}}")
  26 + @UserMessage("用户输入:{{userInput}}")
  27 + String chatCurrentTool(@MemoryId String userId,
  28 + @V("userInput") String userInput,
  29 + @V("sMethodNo") String sMethodNo,
  30 + @V("sMethodName") String sMethodName,
  31 + @V("stoolDesc") String stoolDesc
  32 + );
  33 +
25 34 /**
26 35 * 动态表结构:自然语言解释SQL执行结果
27 36 * 入参:用户问题、执行的SQL、表结构、JSON格式结果
... ...
src/main/java/com/xly/entity/UserSceneSession.java
... ... @@ -63,6 +63,9 @@ public class UserSceneSession {
63 63 private String sFunPrompts; //方法返回的参数补全提示
64 64  
65 65 private Boolean bCleanMemory = false;
  66 +
  67 + private Map<String, Object> args;
  68 +
66 69 /**
67 70 * 构建场景选择提示语:展示权限内场景,引导用户选择
68 71 * @return 自然语言提示语
... ...
src/main/java/com/xly/service/XlyErpService.java
... ... @@ -83,11 +83,12 @@ public class XlyErpService {
83 83 String authorization) {
84 84 String sceneName = StrUtil.EMPTY;
85 85 String methodName = StrUtil.EMPTY;
  86 + UserSceneSession session=null;
86 87 try {
87 88 // 0. 预处理用户输入:去空格、转小写(方便匹配)
88 89 String input= InputPreprocessor.preprocessWithCommons(userInput);
89 90 // 1. 初始化用户场景会话(权限内场景)
90   - UserSceneSession session = userSceneSessionService.getUserSceneSession(userId,sUserName,sBrandsId,sSubsidiaryId,sUserType,authorization);
  91 + session = userSceneSessionService.getUserSceneSession(userId,sUserName,sBrandsId,sSubsidiaryId,sUserType,authorization);
91 92 session.setAuthorization(authorization);
92 93 session.setSFunPrompts(null);
93 94 sceneName = ObjectUtil.isNotEmpty(session.getCurrentScene())?session.getCurrentScene().getSSceneName():StrUtil.EMPTY;
... ... @@ -102,9 +103,8 @@ public class XlyErpService {
102 103 if (session.getCurrentScene() != null
103 104 && Objects.equals(session.getCurrentScene().getSSceneNo(), "ChatZone"))
104 105 {
105   - return getChatiAgent(input, session,StrUtil.EMPTY);
  106 + return getChatiAgent(input, session);
106 107 }
107   -
108 108 // 3. 未选场景:先展示场景选择界面,处理用户序号选择
109 109 if (!session.isSceneSelected() && ValiDataUtil.me().isPureNumber(input)){
110 110 // 3.1 尝试处理场景选择(输入序号则匹配,否则展示选择提示)
... ... @@ -114,49 +114,44 @@ public class XlyErpService {
114 114 ErpAiAgent aiAgent = createErpAiAgent(userId, input, session);
115 115 // 没有选择到场景,进闲聊模式
116 116 if (aiAgent == null){
117   - return getChatiAgent (input, session,StrUtil.EMPTY);
  117 + return getChatiAgent (input,session);
118 118 }
119   -// List<ChatMessage> chatMessage = operableChatMemoryProvider.getCurrentChatMessages(session.getUserId());
120   - String sResponMessage = aiAgent.chat(userId, input);
121   -// List<ChatMessage> chatMessage2 = operableChatMemoryProvider.getCurrentChatMessages(session.getUserId());
122   - String sResponMessageOld = StrUtil.EMPTY;
123   -// 调用方法,参数缺失部分提示,就直接使用方法返回的
124   - if(session.getCurrentTool() != null
125   - && session.getSFunPrompts()!=null
  119 + //用户输入添加方法
  120 + String sResponMessage = StrUtil.EMPTY;
  121 + if(ObjectUtil.isNotEmpty(session.getCurrentTool())
  122 + && !ObjectUtil.isNotEmpty(session.getCurrentTool().getSInputTabelName())
126 123 ){
127   - // 缺失的参数明细
128   - sResponMessage = session.getSFunPrompts();
129   - }
130   - if (session.getCurrentTool()== null){
131   - sResponMessageOld = sResponMessage;
132   - sResponMessage = StrUtil.EMPTY;
133   - }
134   - //5.执行工具方法后,清除记忆
135   - if(session.getBCleanMemory()){
136   - doCleanUserMemory(session,userId);
  124 + input = session.getCurrentTool().getSMethodName()+","+input;
  125 + sResponMessage = aiAgent.chatCurrentTool(userId, input,session.getCurrentTool().getSMethodNo(),session.getCurrentTool().getSMethodName(),session.getCurrentTool().getStoolDesc());
  126 + }else{
  127 + sResponMessage = aiAgent.chat(userId, input);
137 128 }
138   -// 6.找到方法并且本方法带表结构描述时,需要调用 自然语言转SQL智能体
  129 +
  130 +// 1.找到方法并且本方法带表结构描述时,需要调用 自然语言转SQL智能体
139 131 if((ObjectUtil.isNotEmpty(session.getCurrentTool())
140 132 && ObjectUtil.isNotEmpty(session.getCurrentTool().getSInputTabelName())
141 133 && ObjectUtil.isNotEmpty(session.getCurrentTool().getSStructureMemo()))
142 134 ){
143 135 sResponMessage = getDynamicTableSql(session, input, userId, userInput,0,StrUtil.EMPTY,StrUtil.EMPTY,"0",StrUtil.EMPTY, aiAgent);
144   - }
145   - //如果返回空的进入闲聊模式
146   - if (ObjectUtil.isEmpty(sResponMessage)){
147   - return getChatiAgent (input, session,sResponMessageOld);
148   - }
149   - if (session.getCurrentScene()!= null ){
150   - return AiResponseDTO.builder().aiText(sResponMessage)
151   - .sSceneName(session.getCurrentScene().getSSceneName())
152   - .sMethodName((ObjectUtil.isEmpty(session.getCurrentTool()))?StrUtil.EMPTY:session.getCurrentTool().getSMethodName())
153   - .sReturnType(ReturnTypeCode.MAKEDOWN.getCode())
154   - .build();
155   - }else {
  136 + return AiResponseDTO.builder().sSceneName(sceneName).sMethodName(methodName).aiText(sResponMessage).sReturnType(ReturnTypeCode.HTML.getCode()).build();
  137 + } else if (ObjectUtil.isNotEmpty(session.getCurrentTool())) {
  138 + //2.处理工具参数采集结束后业务逻辑处理
  139 + //调用方法,参数缺失部分提示,就直接使用方法返回的
  140 + sResponMessage = dynamicToolProvider.doDynamicTool(session.getCurrentTool(),session);
  141 + return AiResponseDTO.builder().sSceneName(sceneName).sMethodName(methodName).aiText(sResponMessage).sReturnType(ReturnTypeCode.HTML.getCode()).build();
  142 + }else if(session.getCurrentScene()== null ){
156 143 return AiResponseDTO.builder().sSceneName(sceneName).sMethodName(methodName).aiText("当前场景:没有选择 退回当前场景 请输入 "+ CommonConstant.RESET + sResponMessage).sReturnType(ReturnTypeCode.HTML.getCode()).build();
  144 + }else{
  145 + return getChatiAgent (input, session);
157 146 }
158 147 } catch (Exception e) {
  148 + e.printStackTrace();
159 149 return AiResponseDTO.builder().sSceneName(sceneName).sMethodName(methodName).aiText("系统异常:" + e.getMessage() + ",请稍后重试!").sReturnType(ReturnTypeCode.HTML.getCode()).build();
  150 + }finally {
  151 + //5.执行工具方法后,清除记忆
  152 + if(session !=null && session.getBCleanMemory()){
  153 + doCleanUserMemory(session,userId);
  154 + }
160 155 }
161 156 }
162 157  
... ... @@ -170,6 +165,7 @@ public class XlyErpService {
170 165 UserSceneSession session = userSceneSessionService.getUserSceneSession(userId,sUserName,sBrandsId,sSubsidiaryId,sUserType,authorization);
171 166 operableChatMemoryProvider.clearSpecifiedMemory(userId);
172 167 session.setCurrentTool(null);
  168 + session.setArgs(null);
173 169 session.setSUserQuestionList(new ArrayList<>());
174 170 UserSceneSessionService.ERP_AGENT_CACHE.remove(userId);
175 171 UserSceneSessionService.CHAT_AGENT_CACHE.remove(userId);
... ... @@ -246,6 +242,7 @@ public class XlyErpService {
246 242 operableChatMemoryProvider.clearSpecifiedMemory(userId);
247 243 session.setCurrentTool(null);
248 244 session.setSUserQuestionList(new ArrayList<>());
  245 + session.setArgs(new HashMap<>());
249 246 // session.setSceneSelected(false);
250 247 UserSceneSessionService.ERP_AGENT_CACHE.remove(userId);
251 248 UserSceneSessionService.CHAT_AGENT_CACHE.remove(userId);
... ... @@ -593,12 +590,9 @@ public class XlyErpService {
593 590 * @return java.lang.String
594 591 * @Description 获取智普通智能体
595 592 **/
596   - private AiResponseDTO getChatiAgent (String input,UserSceneSession session,String sResponMessageOld){
  593 + private AiResponseDTO getChatiAgent (String input,UserSceneSession session){
597 594 String sceneName = ObjectUtil.isNotEmpty(session.getCurrentScene())?session.getCurrentScene().getSSceneName():StrUtil.EMPTY;
598 595 String methodName = ObjectUtil.isNotEmpty(session.getCurrentTool())?session.getCurrentTool().getSMethodName():"随便聊聊";
599   - if(ObjectUtil.isNotEmpty(sResponMessageOld)){
600   - return AiResponseDTO.builder().sSceneName(sceneName).sMethodName(methodName).aiText(sResponMessageOld).systemText(StrUtil.EMPTY).sReturnType(ReturnTypeCode.HTML.getCode()).build();
601   - }
602 596 ChatiAgent chatiAgent = UserSceneSessionService.CHAT_AGENT_CACHE.get(session.getUserId());
603 597 if(ObjectUtil.isEmpty(chatiAgent)){
604 598 chatiAgent = AiServices.builder(ChatiAgent.class)
... ...
src/main/java/com/xly/tool/DynamicToolProvider.java
1 1 package com.xly.tool;
2 2  
3 3  
  4 +import cn.hutool.core.util.NumberUtil;
4 5 import cn.hutool.core.util.ObjectUtil;
5 6 import cn.hutool.core.util.StrUtil;
6 7 import cn.hutool.json.JSONUtil;
... ... @@ -165,7 +166,7 @@ public class DynamicToolProvider implements ToolProvider {
165 166 pr.setSParamValue(sAIshowfieldShowAll.get(i).get("sName").toString());
166 167 pr.setSParam(sAIshowfieldShowAll.get(i).get("label").toString());
167 168 pr.setBEmpty(false);
168   - pr.setSType("array");
  169 + pr.setSType(getDefType(sAIshowfieldShowAll.get(i).get("sName").toString()));
169 170 if("sSlaveId".equals(sAIshowfieldShowAll.get(i).get("sName"))){
170 171 pr.setSParam("sSlaveId");
171 172 }
... ... @@ -186,35 +187,56 @@ public class DynamicToolProvider implements ToolProvider {
186 187 meta.setParamRuleListAll(paramRuleListAll);
187 188 }
188 189  
  190 + private String getDefType(String sColumn){
  191 + if(sColumn.startsWith("d")){
  192 + return "double";
  193 + }else{
  194 + return "string";
  195 + }
  196 + }
189 197 @Override
190 198 public ToolProviderResult provideTools(ToolProviderRequest request) {
191   -// List<ToolSpecification> specs = new ArrayList<>();
192 199 String sUserId = request.chatMemoryId().toString();
193 200 Map<ToolSpecification, ToolExecutor> executors = new HashMap<>();
194   - // sceneToolCacheMap.get(sSceneIdMap.get(sUserId));
195   - //获取Session
  201 + // 获取Session
196 202 UserSceneSession session = UserSceneSessionService.USER_SCENE_SESSION_CACHE.get(sUserId);
197   - //过滤对应的权限方法
  203 +
  204 + // 过滤对应的权限方法
198 205 List<ToolSpecificationHolder> datalist = new ArrayList<>();
199 206 List<ToolMeta> toolMetaAll = new ArrayList<>();
200   - if(session.getCurrentTool()!=null){
  207 +
  208 + // 确保 currentTool 不为空,或者 authTool 有数据
  209 + if(session.getCurrentTool() != null){
201 210 toolMetaAll.add(session.getCurrentTool());
202   - }else{
  211 + log.info("使用 currentTool: {}", session.getCurrentTool().getSMethodNo());
  212 + } else {
203 213 toolMetaAll = session.getAuthTool();
  214 + log.info("使用 authTool, 数量: {}", toolMetaAll.size());
204 215 }
  216 +
205 217 if(ObjectUtil.isNotEmpty(toolMetaAll)){
206   - toolMetaAll = toolMetaAll.stream().filter(to-> to.getSSceneId().equals(sSceneIdMap.get(sUserId))).collect(Collectors.toUnmodifiableList());
  218 + toolMetaAll = toolMetaAll.stream()
  219 + .filter(to -> to.getSSceneId().equals(sSceneIdMap.get(sUserId)))
  220 + .collect(Collectors.toUnmodifiableList());
  221 +
207 222 if(ObjectUtil.isNotEmpty(toolMetaAll)){
208   - toolMetaAll.forEach(to->{
209   - datalist.add(toolCache.get(to.getSMethodNo()));
  223 + toolMetaAll.forEach(to -> {
  224 + ToolSpecificationHolder holder = toolCache.get(to.getSMethodNo());
  225 + if (holder != null) {
  226 + datalist.add(holder);
  227 + log.debug("添加工具到提供器: {}", to.getSMethodNo());
  228 + } else {
  229 + log.warn("工具缓存缺失: {}", to.getSMethodNo());
  230 + }
210 231 });
211 232 }
212 233 }
213   - datalist.forEach(holder->{
214   -// specs.add(holder.getToolSpecification());
215   - executors.put(holder.getToolSpecification(),holder.getToolExecutor());
216   -// executors.put(holder.getToolSpecification(), holder.getToolExecutor());
  234 +
  235 + // 将工具添加到返回结果中
  236 + datalist.forEach(holder -> {
  237 + executors.put(holder.getToolSpecification(), holder.getToolExecutor());
217 238 });
  239 + log.info("provideTools 返回工具数量: {}", executors.size());
218 240 return ToolProviderResult.builder().addAll(executors).build();
219 241 }
220 242  
... ... @@ -240,10 +262,10 @@ public class DynamicToolProvider implements ToolProvider {
240 262 // stoolDesc.append(",").append("并选择数据后执行["+meta.getSControlName()+"]操作");
241 263 // }
242 264 stoolDesc.append("时,必须调用本工具").append(meta.getSMethodNo()).append(",").append(meta.getStoolDesc());
243   - if (meta.getIBizType()==4){
244   - stoolDesc.append(",").append("并选择数据后执行 "+meta.getSControlName()+" 操作");
245   -// .append("1.全部数据生成多个单据 回复【全部确认】;2.全部数据生成一个单据 回复【合并确认】;3.按自然语义描述生成一个单据 如"1,3行确认"");
246   - }
  265 +// if (meta.getIBizType()==4){
  266 +// stoolDesc.append(",").append("并选择数据后执行 "+meta.getSControlName()+" 操作");
  267 +//// .append("1.全部数据生成多个单据 回复【全部确认】;2.全部数据生成一个单据 回复【合并确认】;3.按自然语义描述生成一个单据 如"1,3行确认"");
  268 +// }
247 269 }
248 270  
249 271 try {
... ... @@ -504,115 +526,129 @@ public class DynamicToolProvider implements ToolProvider {
504 526 }
505 527 return paramRuleDataAll;
506 528 }
507   -
508   -
509 529 /***
510 530 * @Author 钱豹
511   - * @Date 15:09 2026/1/30
  531 + * @Date 12:37 2026/3/16
512 532 * @Param [meta]
513 533 * @return dev.langchain4j.service.tool.ToolExecutor
514   - * @Description 创建 ToolExecutor,内部包含参数自动补全与校验逻辑(创建执行器)
  534 + * @Description 参数采集执行器
515 535 **/
516 536 private ToolExecutor createToolExecutor(ToolMeta meta) {
  537 + log.info("创建工具执行器: {}", meta.getSMethodNo());
  538 +
517 539 return (toolExecutionRequest, memoryId) -> {
  540 + log.info("===== 工具执行器开始执行 =====");
  541 + log.info("工具编号: {}", meta.getSMethodNo());
  542 + log.info("工具名称: {}", meta.getSMethodName());
  543 + log.info("memoryId: {}", memoryId);
  544 + log.info("请求参数: {}", toolExecutionRequest.arguments());
  545 +
518 546 UserSceneSession session = UserSceneSessionService.USER_SCENE_SESSION_CACHE.get(memoryId.toString());
519   - session.setCurrentTool(meta); // 标记一下找到了相应方法
520   - session.setSFunPrompts(null);
521   - // 检查条件 - 如果条件满足,直接返回成功结果,不再执行后续逻辑
522   - if (ObjectUtil.isNotEmpty(meta.getSInputTabelName())
523   - && ObjectUtil.isNotEmpty(meta.getSStructureMemo())) {
524   - // 直接返回成功结果,阻止后续执行
525   - return createEarlySuccessResult(toolExecutionRequest, "执行成功,终止后续执行");
526   - }
  547 + session.setCurrentTool(meta); // 标记当前工具
527 548  
528   - // 1. 解析模型传入的参数
529   - Map<String, Object> args;
  549 + // 1. 解析参数
  550 + Map<String, Object> argsNew;
530 551 try {
531   - args = objectMapper.readValue(toolExecutionRequest.arguments(), new TypeReference<>() {});
  552 + argsNew = objectMapper.readValue(toolExecutionRequest.arguments(), new TypeReference<>() {});
  553 + log.info("解析后的参数: {}", argsNew);
532 554 } catch (Exception e) {
533   - String errorMsg = "参数 JSON 解析失败,请检查参数格式是否正确。"
534   - + "错误详情:" + e.getMessage();
535   - log.warn("参数解析失败,tool={}, args={}", meta.getSMethodNo(), toolExecutionRequest.arguments(), e);
  555 + log.error("参数解析失败", e);
  556 + String errorMsg = "参数解析失败,请重新输入";
536 557 return String.valueOf(errorResult(toolExecutionRequest, errorMsg));
537 558 }
538   -// Map<String, Object> argsOld = DeepCopyUtils.deepCopy(args);
539   - List<ParamRule> paramRuleData = meta.getParamRuleListAll();
540   -
541   - // 2 【补全动态参数】动态参数补全
542   - try{
543   - args = applyValues(args, meta.getParamRuleListCheck());
544   - }catch (Exception e){
545   - log.error("返回信息",e);
546   - String sTsMsg = e.getMessage();
547   - session.setSFunPrompts(sTsMsg);
548   - //存在多个数据返回大模型,需要继续盘问选择出唯一结果
549   - return String.valueOf(askUserResult(toolExecutionRequest, sTsMsg));
550   - }
551   -
552   - // 2.1 【自动补全】应用参数的默认值
553   - args = applyDefaultValues(args, paramRuleData);
554   -
555   - // 3. 【自动校验】检查必填项
556   - List<String> missing = checkRequiredParams(args, paramRuleData);
557   - if (!missing.isEmpty()) {
558   - // 4.1 参数缺失,生成“提问”消息,直接返给客户
559   - String askMsg = buildAskUserMessage(meta, missing,args);
560   - session.setSFunPrompts(askMsg);
561   - return String.valueOf(askUserResult(toolExecutionRequest, askMsg));
  559 + Map<String, Object> args = session.getArgs();
  560 + if(ObjectUtil.isEmpty(args)){
  561 + args = new HashMap<>();
562 562 }
  563 + Map<String, Object> finalArgs = args;
  564 + argsNew.forEach((k, v)->{
  565 + //获取对应的英文参数
  566 + List<ParamRule> data = meta.getParamRuleList().stream().filter(one->one.getSParam().equals(k)).collect(Collectors.toUnmodifiableList());
  567 + if(ObjectUtil.isNotEmpty(data) && data.size()>0){
  568 + finalArgs.remove(data.get(0).getSParamValue());
  569 + finalArgs.remove(data.get(0).getSParam());
  570 + }
  571 + if(ObjectUtil.isNotEmpty(v)){
  572 + finalArgs.put(k,v);
  573 + }
  574 + });
  575 + session.setArgs(finalArgs);
  576 + // 2. 获取必填参数规则
  577 +// List<ParamRule> sParamRules = meta.getParamRuleListCheck();
  578 +// List<String> missingParams = getRequiredParams(sParamRules);
  579 +// String collectPrompt = buildCollectParamsPrompt(meta, missingParams, args);
  580 +// log.info("参数缺失,返回收集提示: {}", collectPrompt);
  581 + return String.valueOf(successResult(toolExecutionRequest, JSONUtil.toJsonStr(finalArgs)));
  582 + };
  583 + }
563 584  
564   - // 6. 【最终确认信息】所有检测通过后,需要和客户确认交互
565   - List<ChatMessage> chatMessage = operableChatMemoryProvider.getCurrentChatMessages(memoryId.toString());
566   - ChatMessage userMessage = getLasterUserMssage(chatMessage);
567   - String input = StrUtil.replace(userMessage.text(),"用户输入:",StrUtil.EMPTY);
  585 + public String doDynamicTool(ToolMeta meta,UserSceneSession session) {
  586 + List<ParamRule> paramRuleData = meta.getParamRuleListAll();
  587 + Map<String, Object> args = session.getArgs();
  588 + Map<String, Object> argsOld = new HashMap<>(args);
  589 + // 2 【补全动态参数】动态参数补全
  590 + try{
  591 + args = applyValues(args, meta.getParamRuleListCheck());
  592 + }catch (Exception e){
  593 + log.error("返回信息",e);
  594 + String sTsMsg = e.getMessage();
  595 + return sTsMsg;
  596 + }
  597 + // 2.1 【自动补全】应用参数的默认值
  598 + args = applyDefaultValues(args, paramRuleData);
  599 + session.setArgs(args);
  600 + // 3. 【自动校验】检查必填项
  601 + List<String> missing = checkRequiredParams(args, paramRuleData);
  602 + if (!missing.isEmpty()) {
  603 + // 4.1 参数缺失,生成“提问”消息,直接返给客户
  604 + String askMsg = buildAskUserMessage(meta, missing,args);
  605 + return askMsg;
  606 + }
568 607  
  608 + // 6. 【最终确认信息】所有检测通过后,需要和客户确认交互
  609 + List<ChatMessage> chatMessage = operableChatMemoryProvider.getCurrentChatMessages(session.getUserId());
  610 + ChatMessage userMessage = getLasterUserMssage(chatMessage);
  611 + String input = StrUtil.replace(userMessage.text(),"用户输入:",StrUtil.EMPTY);
569 612 // {"0":"查询","1":"执行"} 查询不需要确认
570   - Boolean isConfirmed = isConfirmed(input) || input.contains("生成") || input.contains("确认");
571   - //判断是否生成数据
572   - List<Map<String,Object>> sRowData = new ArrayList<>();
573   - String sHandleType = "merge";
574   - if(4== meta.getIBizType() && ObjectUtil.isNotEmpty(session.getCurrentRowData())){
575   - Map<String,Object> sRowDataMap = UserChoseIntentParser.getSelectedRows( input, session.getCurrentRowData());
576   - sRowData = (List<Map<String, Object>>) sRowDataMap.get("sRowData");
577   - sHandleType = sRowDataMap.get("sHandleType").toString();
578   - }
579   - //{"1":"存储过程","2":"SQL查询","3":"第三方API","4":"ERP未清","5":"ERP列表(报表)","6":"ERP单据","7":"其它","8":"自然语言TEXT2SQL"}
580   -
581   - if((isConfirmed && (4== meta.getIBizType() ||1== meta.getIBizType()))
582   - || 2== meta.getIBizType()
583   - || 3== meta.getIBizType()
584   - || 6== meta.getIBizType()
585   - || 7== meta.getIBizType()
586   - || 8== meta.getIBizType()
587   - )
588   - {
589   - // 确认后必填项校验
590   - List<String> missingAfter = checkConfirmAfterParam(args, paramRuleData);
591   - if (!missingAfter.isEmpty()) {
592   - // 4.1 参数缺失,生成“提问”消息,直接返给客户
593   - String askMsg = buildAskUserMessage(meta, missingAfter,args);
594   - session.setSFunPrompts(askMsg);
595   - return String.valueOf(askUserResult(toolExecutionRequest, askMsg));
596   - }
597   - // 7. 【业务校验】执行业务层面的逻辑校验 + 所有校验通过,执行核心业务逻辑
598   - return executeTool(toolExecutionRequest, meta, args, paramRuleData, memoryId.toString(), session);
599   - }
600   - String askconfirmMsg =StrUtil.EMPTY;
601   -// if(0== meta.getIActionType() && 4!= meta.getIBizType() && 5!= meta.getIBizType()){
602   -// askconfirmMsg = buildConfirmUserMessage(meta, args);
603   -// }else
604   - if(4== meta.getIBizType() || meta.getIBizType()==5){
605   - askconfirmMsg = doGetFromData( meta,args,session);
606   -// session.setSFunPrompts(askconfirmMsg);
607   -// operableChatMemoryProvider.get(memoryId).add(UserMessage.from("SYSTEM: 等待用户确认或选择部分数据操作"));
608   - return executeWithConfirmation(toolExecutionRequest,askconfirmMsg,operableChatMemoryProvider.get(memoryId), session, meta).text();
609   - }else{
610   - askconfirmMsg =getDefMessage(args,meta.getSControlName(),meta);
  613 + Boolean isConfirmed = isConfirmed(input) || input.contains("生成") || input.contains("确认");
  614 + //判断是否生成数据
  615 + List<Map<String,Object>> sRowData = new ArrayList<>();
  616 + String sHandleType = "merge";
  617 + if(4== meta.getIBizType() && ObjectUtil.isNotEmpty(session.getCurrentRowData())){
  618 + Map<String,Object> sRowDataMap = UserChoseIntentParser.getSelectedRows( input, session.getCurrentRowData());
  619 + sRowData = (List<Map<String, Object>>) sRowDataMap.get("sRowData");
  620 + sHandleType = sRowDataMap.get("sHandleType").toString();
  621 + }
  622 + //{"1":"存储过程","2":"SQL查询","3":"第三方API","4":"ERP未清","5":"ERP列表(报表)","6":"ERP单据","7":"其它","8":"自然语言TEXT2SQL"}
  623 + if((isConfirmed && (4== meta.getIBizType() ||1== meta.getIBizType()))
  624 + || 2== meta.getIBizType()
  625 + || 3== meta.getIBizType()
  626 + || 6== meta.getIBizType()
  627 + || 7== meta.getIBizType()
  628 + || 8== meta.getIBizType()
  629 + )
  630 + {
  631 + // 确认后必填项校验
  632 + List<String> missingAfter = checkConfirmAfterParam(args, paramRuleData);
  633 + if (!missingAfter.isEmpty()) {
  634 + // 4.1 参数缺失,生成“提问”消息,直接返给客户
  635 + String askMsg = buildAskUserMessage(meta, missingAfter,args);
  636 + return askMsg;
611 637 }
612   - // 返回需要确认的结果
613   - return executeWithConfirmation(toolExecutionRequest,askconfirmMsg,operableChatMemoryProvider.get(memoryId), session, meta).text();
614   - };
  638 + // 7. 【业务校验】执行业务层面的逻辑校验 + 所有校验通过,执行核心业务逻辑
  639 + return executeTool(meta, args, paramRuleData, session.getUserId(), session);
  640 + }
  641 + String askconfirmMsg =StrUtil.EMPTY;
  642 + if(4== meta.getIBizType() || meta.getIBizType()==5){
  643 + askconfirmMsg = doGetFromData( meta,args,session);
  644 + return executeWithConfirmation(askconfirmMsg,operableChatMemoryProvider.get(session.getUserId()), session, meta);
  645 + }else{
  646 + askconfirmMsg =getDefMessage(args,meta.getSControlName(),meta);
  647 + }
  648 + // 返回需要确认的结果
  649 + return executeWithConfirmation(askconfirmMsg,operableChatMemoryProvider.get(session.getUserId()), session, meta);
615 650 }
  651 +
616 652 /***
617 653 * @Author 钱豹
618 654 * @Date 15:16 2026/2/9
... ... @@ -651,7 +687,7 @@ public class DynamicToolProvider implements ToolProvider {
651 687 **/
652 688 private void appendConfirmAll(StringBuilder markdown,String sName){
653 689 sName = ObjectUtil.isEmpty(sName)?StrUtil.EMPTY:"["+sName+"]";
654   - markdown.append("请确认是否执行").append(sName).append("操作?1.全部数据生成多个单据 回复【全部确认】;2.全部数据生成一个单据 回复【合并确认】;3.按自然语义描述生成一个单据 如"1,3行确认"\n");
  690 + markdown.append("请确认是否执行").append(sName).append("操作?1.全部数据生成多个单据 回复【全部确认】;2.全部数据生成一个单据 回复【合并确认】;3.按自然语义描述生成一个单据 如"第1行确认"\n");
655 691 //全部确认 ,部分确认,取消
656 692 markdown.append("回复:&emsp;&emsp;").append("**<a href=\"#\" data-action=\"reset\" data-text=\"全部确认\" >全部确认</a>**").append("&emsp;")
657 693 .append("**<a href=\"#\" data-action=\"reset\" data-text=\"合并确认\" >合并确认</a>**").append("&emsp;")
... ... @@ -666,9 +702,7 @@ public class DynamicToolProvider implements ToolProvider {
666 702 **/
667 703 private void appendConfirm(StringBuilder markdown,String sName){
668 704 sName = ObjectUtil.isEmpty(sName)?StrUtil.EMPTY:"["+sName+"]";
669   - markdown.append("请确认是否执行").append(sName).append("操作?请直接回复"确认"或"取消"\n");
670   - //全部确认 ,部分确认,取消
671   - markdown.append("回复:&emsp;&emsp;").append("**<a href=\"#\" data-action=\"reset\" data-text=\"确认\">确认</a>**").append("&emsp;")
  705 + markdown.append("请确认是否执行").append(sName).append("操作?请回复:&emsp;&emsp;").append("**<a href=\"#\" data-action=\"reset\" data-text=\"确认\">确认</a>**").append("&emsp;")
672 706 .append("**<a href=\"#\" data-action=\"reset\" data-text=\"取消\">取消</a>**");
673 707 }
674 708  
... ... @@ -836,6 +870,14 @@ public class DynamicToolProvider implements ToolProvider {
836 870 }
837 871 return result;
838 872 }
  873 + /**
  874 + * 检查必填参数
  875 + */
  876 + private List<String> getRequiredParams(List<ParamRule> paramDefs) {
  877 + return paramDefs.stream()
  878 + .map(ParamRule::getSParam)
  879 + .toList();
  880 + }
839 881  
840 882 /**
841 883 * 检查必填参数
... ... @@ -845,8 +887,11 @@ public class DynamicToolProvider implements ToolProvider {
845 887 return paramDefs.stream()
846 888 .filter(pd -> Boolean.TRUE.equals(pd.getBEmpty()) && pd.getBTipModel())
847 889 .filter(pd ->
848   - (!returnMap.containsKey(pd.getSParam()) || (ObjectUtil.isEmpty(returnMap.get(pd.getSParam()))))
849   - && (!returnMap.containsKey(pd.getSParamValue()) || (ObjectUtil.isEmpty(returnMap.get(pd.getSParamValue()))))
  890 + (!returnMap.containsKey(pd.getSParam())
  891 + || (ObjectUtil.isEmpty(returnMap.get(pd.getSParam())))
  892 + || (pd.getSParamValue().startsWith("d") && 0==Double.valueOf (returnMap.get(pd.getSParam()).toString()))
  893 + )
  894 + && (!returnMap.containsKey(pd.getSParamValue()) || (ObjectUtil.isEmpty(returnMap.get(pd.getSParamValue()))))
850 895 )
851 896 .map(ParamRule::getSParam)
852 897 .toList();
... ... @@ -867,16 +912,17 @@ public class DynamicToolProvider implements ToolProvider {
867 912 .toList();
868 913 }
869 914  
  915 +
870 916 /**
871 917 * 模拟执行工具
872 918 */
873   - public String executeTool(ToolExecutionRequest toolExecutionRequest,ToolMeta meta, Map<String, Object> args, List<ParamRule> paramRuleData,String userId,UserSceneSession session ) {
  919 + public String executeTool(ToolMeta meta, Map<String, Object> args, List<ParamRule> paramRuleData,String userId,UserSceneSession session ) {
874 920 log.info("执行工具:{},参数:{}", meta.getSMethodNo(), args);
875 921 // 2.2 将中文key转换成英文key
876 922 args = transformationArgs( args, paramRuleData);
877 923 String sReturn ="执行成功";
878 924 try{
879   - sReturn = executeToolAfter(meta, args,toolExecutionRequest,paramRuleData,session);
  925 + sReturn = executeToolAfter(meta, args,paramRuleData,session);
880 926 }catch (BusinessException e) {
881 927 return e.getMessage();
882 928 }
... ... @@ -893,7 +939,7 @@ public class DynamicToolProvider implements ToolProvider {
893 939 * @return
894 940 * @Description 返回结果后 执行业务类
895 941 **/
896   - private String executeToolAfter(ToolMeta meta, Map<String, Object> args,ToolExecutionRequest toolExecutionRequest,List<ParamRule> paramDefs,UserSceneSession session) {
  942 + private String executeToolAfter(ToolMeta meta, Map<String, Object> args,List<ParamRule> paramDefs,UserSceneSession session) {
897 943 // {"1":"存储过程","2":"SQL查询","3":"第三方API","4":"ERP未清","5":"ERP列表(报表)","6":"ERP单据","7":"其它","8":"自然语言TEXT2SQL"}
898 944 String sBizContent = meta.getSBizContent();
899 945 Integer iBizType = meta.getIBizType();
... ... @@ -950,7 +996,7 @@ public class DynamicToolProvider implements ToolProvider {
950 996 if ("queryTodayTask".equals(meta.getSMethodNo())) {
951 997 session.setBCleanMemory(true);
952 998 }
953   - return String.valueOf(successResult(toolExecutionRequest, sb.toString()));
  999 + return sb.toString();
954 1000 } else {
955 1001 String sMsgText = "未找到对应的数据";
956 1002 session.setSFunPrompts(sMsgText);
... ... @@ -961,7 +1007,7 @@ public class DynamicToolProvider implements ToolProvider {
961 1007 return HttpsRequestUtil.me().doRequestHttp(sBizContent, JSONUtil.toJsonStr(args),
962 1008 new HashMap<>(), "POST", "JSON");
963 1009 }
964   - return String.valueOf(successResult(toolExecutionRequest, "操作成功"));
  1010 + return "操作成功";
965 1011 }
966 1012  
967 1013  
... ... @@ -1051,8 +1097,37 @@ public class DynamicToolProvider implements ToolProvider {
1051 1097 }
1052 1098 List<Map<String, Object>> recordData = findFieldNameByChinese(sAIshowfieldShow, rows);
1053 1099 int recordCount = dataset != null ? dataset.getTotalCount() : 0;
  1100 + // 动态生成表头
  1101 + Set<String> headers = new LinkedHashSet<>();
  1102 + for (Map<String, Object> record : sAIshowfieldShow) {
  1103 + String chineseName = (String) record.get("label");
  1104 + if (chineseName != null && !"sSlaveId".equals(record.get("sName"))) {
  1105 + headers.add(chineseName);
  1106 + }
  1107 + }
  1108 +
1054 1109 StringBuilder markdown = new StringBuilder();
1055 1110 //状态
  1111 + if(ObjectUtil.isNotEmpty(session.getArgs())){
  1112 + markdown.append("**查询条件**:");
  1113 + List<ParamRule> pr = session.getCurrentTool().getParamRuleListCheck();
  1114 + Map<String, Object> argsOld = new HashMap<>(session.getArgs());
  1115 + pr.forEach(one->{
  1116 + if(argsOld.containsKey(one.getSParam())){
  1117 + if(ObjectUtil.isNotEmpty(argsOld.get(one.getSParam()))){
  1118 + if(one.getSParamValue().startsWith("d")){
  1119 + if(Double.valueOf(argsOld.get(one.getSParam()).toString())>0){
  1120 + markdown.append(one.getSParam()).append(":").append(argsOld.get(one.getSParam()).toString()).append(" ");
  1121 + }
  1122 + }else{
  1123 + markdown.append(one.getSParam()).append(":").append(argsOld.get(one.getSParam()).toString()).append(" ");
  1124 + }
  1125 + }
  1126 + }
  1127 + });
  1128 + markdown.append("\n\n");
  1129 + }
  1130 + markdown.append("**结果**:");
1056 1131 String sStatus = erpResult.getCode()<0?ErrorCode.ERRORMSG.getMessage():ErrorCode.SUCCESSMSG.getMessage();
1057 1132 markdown.append(sStatus).append("\n");
1058 1133 if(erpResult.getCode()<0){
... ... @@ -1067,14 +1142,6 @@ public class DynamicToolProvider implements ToolProvider {
1067 1142 }
1068 1143 // markdown.append("\n---\n");
1069 1144 markdown.append("\n\n").append("| 序号 | ");
1070   - // 动态生成表头
1071   - Set<String> headers = new LinkedHashSet<>();
1072   - for (Map<String, Object> record : sAIshowfieldShow) {
1073   - String chineseName = (String) record.get("label");
1074   - if (chineseName != null && !"sSlaveId".equals(record.get("sName"))) {
1075   - headers.add(chineseName);
1076   - }
1077   - }
1078 1145 headers.forEach(header -> markdown.append(header).append(" | "));
1079 1146 markdown.append("\n|").append("---|".repeat(headers.size() + 1)).append("\n");
1080 1147 // 填充表格数据
... ... @@ -1256,20 +1323,9 @@ public class DynamicToolProvider implements ToolProvider {
1256 1323 }
1257 1324  
1258 1325 /**
1259   - * 询问用户工具执行结果
1260   - * @param request 工具执行请求
1261   - * @param text 回复文本内容
1262   - * @return 工具执行结果消息
1263   - * @author 钱豹
1264   - */
1265   - private ToolExecutionResultMessage askUserResult(ToolExecutionRequest request, String text) {
1266   - // 直接返回标准结果
1267   - return ToolExecutionResultMessage.from(request, text);
1268   - }
1269   - /**
1270 1326 * 执行方法后需要用户确认的扩展版本
1271 1327 */
1272   - private ToolExecutionResultMessage executeWithConfirmation(ToolExecutionRequest request, String initialResult,ChatMemory chatMemory, UserSceneSession session,ToolMeta meta) {
  1328 + private String executeWithConfirmation(String initialResult,ChatMemory chatMemory, UserSceneSession session,ToolMeta meta) {
1273 1329  
1274 1330 // 第一步:执行原始操作,返回初步结果
1275 1331 Map<String, Object> step1Result = new HashMap<>();
... ... @@ -1278,18 +1334,19 @@ public class DynamicToolProvider implements ToolProvider {
1278 1334 step1Result.put("confirmationRequired", true);
1279 1335 step1Result.put("confirmationMessage", initialResult);
1280 1336 // // 将确认状态保存到对话记忆
1281   - chatMemory.add(UserMessage.from("SYSTEM: 等待用户确认操作"));
  1337 +// chatMemory.add(UserMessage.from("SYSTEM: 等待用户确认操作"));
1282 1338 String userMessage = formatConfirmationResult(step1Result);
1283 1339 session.setCurrentTool(meta);
1284   - session.setSFunPrompts(userMessage);
  1340 +// session.setSFunPrompts(userMessage);
1285 1341 // 6. 返回确认请求
1286   - return ToolExecutionResultMessage.from(request,userMessage);
  1342 +// return ToolExecutionResultMessage.from(request,userMessage);
  1343 + return userMessage;
1287 1344 }
1288 1345  
1289 1346 private String formatConfirmationResult(Map<String, Object> result) {
1290 1347 return String.format(
1291 1348 """
1292   - **结果** : %s
  1349 + %s
1293 1350 """,
1294 1351 result.get("initialResult"),
1295 1352 result.get("confirmationMessage")
... ...
src/main/resources/templates/chat.html
... ... @@ -468,7 +468,7 @@
468 468 let subsidiaryid= "1111111111";
469 469 let usertype= "sysadmin";
470 470 // let usertype= "General";
471   - let authorization="CE444885A9BCFDDE1FD793F8A0931301E9D7DE6CEDD9DE4B83ECE2219C7829A8F3419238942A93E9AD666629E18D159AF7FE144A6407DE745BA0AEC8B235FC1D59FCE41E63C9FE20948A232F9F67AAE5687D4EF8281542FFC87C4EB776974C6A538155B7ADAEE71E899235DC1122F426";
  471 + let authorization="CE444885A9BCFDDE1FD793F8A0931301E9D7DE6CEDD9DE4B83ECE2219C7829A8F3419238942A93E9AD666629E18D159AF7FE144A6407DE745BA0AEC8B235FC1D0771D239F1C61B14219E1D1B3A166D73495FD8E662F2065F9430347C7E4472B5538155B7ADAEE71E899235DC1122F426";
472 472 let hrefLock = window.location.origin+"/xlyAi";
473 473 // ==================== 配置部分 ====================
474 474 const CONFIG = {
... ...