Commit 81a572e65ebc5788208e462747e91f797fea2536
1 parent
dcf98766
1111
Showing
9 changed files
with
231 additions
and
78 deletions
pom.xml
| @@ -74,6 +74,7 @@ | @@ -74,6 +74,7 @@ | ||
| 74 | <groupId>org.springframework.boot</groupId> | 74 | <groupId>org.springframework.boot</groupId> |
| 75 | <artifactId>spring-boot-starter-webflux</artifactId> | 75 | <artifactId>spring-boot-starter-webflux</artifactId> |
| 76 | </dependency> | 76 | </dependency> |
| 77 | + | ||
| 77 | <dependency> | 78 | <dependency> |
| 78 | <groupId>org.springframework.boot</groupId> | 79 | <groupId>org.springframework.boot</groupId> |
| 79 | <artifactId>spring-boot-starter-thymeleaf</artifactId> | 80 | <artifactId>spring-boot-starter-thymeleaf</artifactId> |
src/main/java/com/xly/agent/DynamicTableNl2SqlAiAgent.java
| @@ -23,10 +23,11 @@ public interface DynamicTableNl2SqlAiAgent { | @@ -23,10 +23,11 @@ public interface DynamicTableNl2SqlAiAgent { | ||
| 23 | 2. 输出格式:仅返回SQL语句本身,无任何解释、换行、```sql/```包裹、备注、多余空格,直接输出可执行SQL; | 23 | 2. 输出格式:仅返回SQL语句本身,无任何解释、换行、```sql/```包裹、备注、多余空格,直接输出可执行SQL; |
| 24 | 3. 编写规范: | 24 | 3. 编写规范: |
| 25 | 3.1 多表关联必须使用 表名+字段名(如表名.字段名),严格按下面[涉及表名]中的表次序关联,聚合函数(SUM/COUNT/AVG/MIN/MAX)必须加业务化别名,日期过滤使用标准DATE格式(yyyy-MM-dd); | 25 | 3.1 多表关联必须使用 表名+字段名(如表名.字段名),严格按下面[涉及表名]中的表次序关联,聚合函数(SUM/COUNT/AVG/MIN/MAX)必须加业务化别名,日期过滤使用标准DATE格式(yyyy-MM-dd); |
| 26 | - 3.2 SQL所有字段均采用 表名.字段名 方式生成,务必确保 字段名 在相应的 表名 描述的字段中存在,如果不存在重试其它方式,直到满足条件 | ||
| 27 | - 3.3 SQL所有字段涉及的所有表名,都要**严格**按下面[涉及表名]中的表次序关联,没有关联不允许使用 | ||
| 28 | - 3.4 SQL所有的查询条件,如果是字符类型的字段,均需要加不为空判断,用示例格式判断,示例:ifnull(customername,'')<>'' | ||
| 29 | - 3.5 SQL所有的显示字段的别名中,不能出现空格,如: tCreateDate as earliest 订单日期,正确的应是 tCreateDate as earliest订单日期 | 26 | + 3.2 SQL所有字段均采用 表名.字段名 方式生成,务必确保 字段名 在相应的 表名 描述的字段中存在,如果不存在重试其它方式,直到满足条件; |
| 27 | + 3.3 SQL所有字段涉及的所有表名,都要**严格**按下面[涉及表名]中的表次序关联,没有关联不允许使用; | ||
| 28 | + 3.4 SQL所有的查询条件,如果是字符类型的字段,均需要加不为空判断,用示例格式判断,示例:ifnull(customername,'')<>''; | ||
| 29 | + 3.5 SQL所有的查询条件,如果是日期类型的字段,均需要加不为空判断,用示例格式判断,示例:tmakedate is not Null; | ||
| 30 | + 3.6 SQL所有的显示字段的别名中,不能出现空格,如: tCreateDate as earliest 订单日期,正确的应是 tCreateDate as earliest订单日期 | ||
| 30 | 4. 安全约束:禁止生成任何DDL/DML语句(DROP/ALTER/INSERT/UPDATE/DELETE等),禁止使用子查询、存储过程、自定义函数、临时表; | 31 | 4. 安全约束:禁止生成任何DDL/DML语句(DROP/ALTER/INSERT/UPDATE/DELETE等),禁止使用子查询、存储过程、自定义函数、临时表; |
| 31 | 5. 精准性: | 32 | 5. 精准性: |
| 32 | 5.1 严格按用户需求+传入的表结构生成,仅使用指定字段/表,无多余字段、无无效表关联、无冗余过滤条件; | 33 | 5.1 严格按用户需求+传入的表结构生成,仅使用指定字段/表,无多余字段、无无效表关联、无冗余过滤条件; |
src/main/java/com/xly/entity/UserSceneSession.java
| @@ -41,6 +41,12 @@ public class UserSceneSession { | @@ -41,6 +41,12 @@ public class UserSceneSession { | ||
| 41 | private SceneDto currentScene; | 41 | private SceneDto currentScene; |
| 42 | 42 | ||
| 43 | private ToolMeta currentTool; | 43 | private ToolMeta currentTool; |
| 44 | + | ||
| 45 | + /*** | ||
| 46 | + * 当前未清返回的数据集 | ||
| 47 | + **/ | ||
| 48 | + private Map<Integer,Map<String,Object>> currentRowData; | ||
| 49 | + | ||
| 44 | /*** | 50 | /*** |
| 45 | * @Author 钱豹 | 51 | * @Author 钱豹 |
| 46 | * @Date 10:07 2026/1/31 | 52 | * @Date 10:07 2026/1/31 |
src/main/java/com/xly/service/XlyErpService.java
| @@ -108,6 +108,7 @@ public class XlyErpService { | @@ -108,6 +108,7 @@ public class XlyErpService { | ||
| 108 | //5.执行工具方法后,清除记忆 | 108 | //5.执行工具方法后,清除记忆 |
| 109 | if(session.getBCleanMemory()){ | 109 | if(session.getBCleanMemory()){ |
| 110 | operableChatMemoryProvider.clearSpecifiedMemory(userId); | 110 | operableChatMemoryProvider.clearSpecifiedMemory(userId); |
| 111 | + session.setCurrentTool(null); | ||
| 111 | session.setBCleanMemory(false); | 112 | session.setBCleanMemory(false); |
| 112 | } | 113 | } |
| 113 | // 6.找到方法并且本方法带表结构描述时,需要调用 自然语言转SQL智能体 | 114 | // 6.找到方法并且本方法带表结构描述时,需要调用 自然语言转SQL智能体 |
| @@ -318,6 +319,7 @@ public class XlyErpService { | @@ -318,6 +319,7 @@ public class XlyErpService { | ||
| 318 | session.setBCleanMemory(false); | 319 | session.setBCleanMemory(false); |
| 319 | session.setCurrentTool(null); | 320 | session.setCurrentTool(null); |
| 320 | session.setCurrentScene(null); | 321 | session.setCurrentScene(null); |
| 322 | + session.setCurrentRowData(null); | ||
| 321 | UserSceneSessionService.USER_SCENE_SESSION_CACHE.put(userId, session); | 323 | UserSceneSessionService.USER_SCENE_SESSION_CACHE.put(userId, session); |
| 322 | // 清空Agent缓存 | 324 | // 清空Agent缓存 |
| 323 | UserSceneSessionService.ERP_AGENT_CACHE.remove(userId); | 325 | UserSceneSessionService.ERP_AGENT_CACHE.remove(userId); |
src/main/java/com/xly/tool/DynamicToolProvider.java
| @@ -20,10 +20,7 @@ import com.xly.mapper.ParamRuleMapper; | @@ -20,10 +20,7 @@ import com.xly.mapper.ParamRuleMapper; | ||
| 20 | import com.xly.mapper.ToolMetaMapper; | 20 | import com.xly.mapper.ToolMetaMapper; |
| 21 | import com.xly.service.DynamicExeDbService; | 21 | import com.xly.service.DynamicExeDbService; |
| 22 | import com.xly.service.UserSceneSessionService; | 22 | import com.xly.service.UserSceneSessionService; |
| 23 | -import com.xly.util.DeepCopyUtils; | ||
| 24 | -import com.xly.util.HttpsRequestUtil; | ||
| 25 | -import com.xly.util.JsonUtils; | ||
| 26 | -import com.xly.util.OkHttpUtil; | 23 | +import com.xly.util.*; |
| 27 | import dev.langchain4j.agent.tool.*; | 24 | import dev.langchain4j.agent.tool.*; |
| 28 | 25 | ||
| 29 | import dev.langchain4j.data.message.ChatMessage; | 26 | import dev.langchain4j.data.message.ChatMessage; |
| @@ -233,7 +230,15 @@ public class DynamicToolProvider implements ToolProvider { | @@ -233,7 +230,15 @@ public class DynamicToolProvider implements ToolProvider { | ||
| 233 | StringBuffer sl = new StringBuffer(); | 230 | StringBuffer sl = new StringBuffer(); |
| 234 | 231 | ||
| 235 | if(ObjectUtil.isNotEmpty(meta.getStoolDesc())){ | 232 | if(ObjectUtil.isNotEmpty(meta.getStoolDesc())){ |
| 236 | - stoolDesc.append("MethodNo:").append(meta.getSMethodNo()).append(",核心工作内容:【").append(meta.getSMethodName()).append("】").append(meta.getStoolDesc()); | 233 | + stoolDesc.append("MethodNo:").append(meta.getSMethodNo()).append(",核心工作内容:【").append(meta.getSMethodName()); |
| 234 | +// if (meta.getIBizType()==4){ | ||
| 235 | +// stoolDesc.append(",").append("并选择数据后执行["+meta.getSControlName()+"]操作"); | ||
| 236 | +// } | ||
| 237 | + stoolDesc.append("】").append(meta.getStoolDesc()); | ||
| 238 | + if (meta.getIBizType()==4){ | ||
| 239 | + stoolDesc.append(",").append("并选择数据后执行 "+meta.getSControlName()+" 操作"); | ||
| 240 | +// .append("1.全部数据生成多个单据 回复【全部确认】;2.全部数据生成一个单据 回复【合并确认】;3.按自然语义描述生成一个单据 如"1,3行确认""); | ||
| 241 | + } | ||
| 237 | } | 242 | } |
| 238 | if("boxQuote".equals(meta.getSMethodNo())){ | 243 | if("boxQuote".equals(meta.getSMethodNo())){ |
| 239 | log.info(meta.getSParamRules()); | 244 | log.info(meta.getSParamRules()); |
| @@ -558,6 +563,14 @@ public class DynamicToolProvider implements ToolProvider { | @@ -558,6 +563,14 @@ public class DynamicToolProvider implements ToolProvider { | ||
| 558 | 563 | ||
| 559 | // {"0":"查询","1":"执行"} 查询不需要确认 | 564 | // {"0":"查询","1":"执行"} 查询不需要确认 |
| 560 | Boolean isConfirmed = isConfirmed(input) || input.contains("生成") || input.contains("确认"); | 565 | Boolean isConfirmed = isConfirmed(input) || input.contains("生成") || input.contains("确认"); |
| 566 | + //判断是否生成数据 | ||
| 567 | + List<Map<String,Object>> sRowData = new ArrayList<>(); | ||
| 568 | + String sHandleType = "merge"; | ||
| 569 | + if(4== meta.getIBizType() && ObjectUtil.isNotEmpty(session.getCurrentRowData())){ | ||
| 570 | + Map<String,Object> sRowDataMap = UserChoseIntentParser.getSelectedRows( input, session.getCurrentRowData()); | ||
| 571 | + sRowData = (List<Map<String, Object>>) sRowDataMap.get("sRowData"); | ||
| 572 | + sHandleType = sRowDataMap.get("sHandleType").toString(); | ||
| 573 | + } | ||
| 561 | if((isConfirmed || 0== meta.getIActionType()) && 5!= meta.getIBizType()){ | 574 | if((isConfirmed || 0== meta.getIActionType()) && 5!= meta.getIBizType()){ |
| 562 | // 确认后必填项校验 | 575 | // 确认后必填项校验 |
| 563 | List<String> missingAfter = checkConfirmAfterParam(args, paramRuleData); | 576 | List<String> missingAfter = checkConfirmAfterParam(args, paramRuleData); |
| @@ -575,9 +588,9 @@ public class DynamicToolProvider implements ToolProvider { | @@ -575,9 +588,9 @@ public class DynamicToolProvider implements ToolProvider { | ||
| 575 | askconfirmMsg = buildConfirmUserMessage(meta, args); | 588 | askconfirmMsg = buildConfirmUserMessage(meta, args); |
| 576 | }else if(4== meta.getIBizType() || meta.getIBizType()==5){ | 589 | }else if(4== meta.getIBizType() || meta.getIBizType()==5){ |
| 577 | askconfirmMsg = doGetFromData( meta,args,session); | 590 | askconfirmMsg = doGetFromData( meta,args,session); |
| 578 | - session.setSFunPrompts(askconfirmMsg); | ||
| 579 | - operableChatMemoryProvider.get(memoryId).add(UserMessage.from("SYSTEM: 等待用户确认或选择部分数据操作")); | ||
| 580 | - return String.valueOf(successResult(toolExecutionRequest,askconfirmMsg)); | 591 | +// session.setSFunPrompts(askconfirmMsg); |
| 592 | +// operableChatMemoryProvider.get(memoryId).add(UserMessage.from("SYSTEM: 等待用户确认或选择部分数据操作")); | ||
| 593 | + return executeWithConfirmation(toolExecutionRequest,askconfirmMsg,operableChatMemoryProvider.get(memoryId), session, meta).text(); | ||
| 581 | }else{ | 594 | }else{ |
| 582 | askconfirmMsg =getDefMessage(args,meta.getSControlName(),meta); | 595 | askconfirmMsg =getDefMessage(args,meta.getSControlName(),meta); |
| 583 | } | 596 | } |
| @@ -863,64 +876,64 @@ public class DynamicToolProvider implements ToolProvider { | @@ -863,64 +876,64 @@ public class DynamicToolProvider implements ToolProvider { | ||
| 863 | **/ | 876 | **/ |
| 864 | private String executeToolAfter(ToolMeta meta, Map<String, Object> args,ToolExecutionRequest toolExecutionRequest,List<ParamRule> paramDefs,UserSceneSession session) { | 877 | private String executeToolAfter(ToolMeta meta, Map<String, Object> args,ToolExecutionRequest toolExecutionRequest,List<ParamRule> paramDefs,UserSceneSession session) { |
| 865 | // {"1":"存储过程","2":"SQL查询","3":"第三方API","4":"窗体查询","5":"按钮执行","6":"其它"} | 878 | // {"1":"存储过程","2":"SQL查询","3":"第三方API","4":"窗体查询","5":"按钮执行","6":"其它"} |
| 866 | - String sBizContent = meta.getSBizContent(); | ||
| 867 | - Integer iBizType = meta.getIBizType(); | ||
| 868 | - args.put("sUserId",session.getUserId()); | ||
| 869 | - args.put("sLoginId",session.getUserName()); | ||
| 870 | - args.put("sMakePerson",session.getUserName()); | ||
| 871 | - args.put("sBrId",session.getSBrandsId()); | ||
| 872 | - args.put("sBrandsId",session.getSBrandsId()); | ||
| 873 | - args.put("sSuId",session.getSSubsidiaryId()); | ||
| 874 | - args.put("sSubsidiaryId",session.getSSubsidiaryId()); | ||
| 875 | - if(iBizType==1 || iBizType==4){ | ||
| 876 | - Map<String,Object> data = new HashMap<>(args); | ||
| 877 | - data.put("sData", JSONObject.toJSONString(data)); | ||
| 878 | - Map<String, Object> searMap = this.dynamicExeDbService.getDoProMap(sBizContent, data); | ||
| 879 | - Map<String,Object> sReturn = this.dynamicExeDbService.getCallPro(searMap,sBizContent); | ||
| 880 | - Integer sCode = ObjectUtil.isNotEmpty(sReturn.get(ProcedureConstant.SCODE))? Integer.valueOf(sReturn.get(ProcedureConstant.SCODE).toString()):0; | ||
| 881 | - String sMsgText = ObjectUtil.isNotEmpty(sReturn.get(ProcedureConstant.SRETURN))? sReturn.get(ProcedureConstant.SRETURN).toString():"操作成功"; | ||
| 882 | - if(sCode< 0){ | ||
| 883 | - String msg = ObjectUtil.isEmpty(sMsgText) ?"调用过程sCode:"+Integer.valueOf(searMap.get(ProcedureConstant.SCODE).toString()):sMsgText; | ||
| 884 | - return String.valueOf(askUserResult(toolExecutionRequest, msg)); | ||
| 885 | - } | ||
| 886 | - session.setSFunPrompts(sMsgText); | ||
| 887 | - return sMsgText; | ||
| 888 | - }else if(iBizType==2 && ObjectUtil.isNotEmpty(sBizContent)){ | ||
| 889 | - //SQL查询 | ||
| 890 | - if(sBizContent.toLowerCase().startsWith("update")){ | ||
| 891 | - this.dynamicExeDbService.updateSql(args,sBizContent); | ||
| 892 | - }else if(sBizContent.toLowerCase().startsWith("delete")){ | ||
| 893 | - this.dynamicExeDbService.delSql(args,sBizContent); | ||
| 894 | - }else if(sBizContent.toLowerCase().startsWith("insert")){ | ||
| 895 | - this.dynamicExeDbService.addSql(args,sBizContent); | ||
| 896 | - }else{ | ||
| 897 | - List<Map<String,Object>> retData = this.dynamicExeDbService.findSql(args,sBizContent); | ||
| 898 | - if(ObjectUtil.isNotEmpty(retData)){ | ||
| 899 | - StringBuffer sb = new StringBuffer(); | ||
| 900 | - retData.forEach(one->{ | ||
| 901 | - one.forEach((k,v)->{ | ||
| 902 | - sb.append(v).append(" "); | ||
| 903 | - }); | ||
| 904 | - sb.append("<br/>"); | ||
| 905 | - }); | ||
| 906 | - if(ObjectUtil.isNotEmpty(retData)){ | ||
| 907 | - sb.append("请根据这些信息安排今天的工作吧!如果有具体任务需要进一步处理,请告诉我"); | ||
| 908 | - } | ||
| 909 | - session.setSFunPrompts(sb.toString()); | ||
| 910 | - if("queryTodayTask".equals(meta.getSMethodNo())){ | ||
| 911 | - session.setBCleanMemory(true); | ||
| 912 | - } | ||
| 913 | - return String.valueOf(successResult(toolExecutionRequest, sb.toString())); | ||
| 914 | - }else{ | ||
| 915 | - session.setSFunPrompts("未找到对应的数据"); | ||
| 916 | - return "未找到对应的数据"; | ||
| 917 | - } | ||
| 918 | - } | ||
| 919 | - }else if(iBizType==3){ | ||
| 920 | - return HttpsRequestUtil.me().doRequestHttp(sBizContent,JSONUtil.toJsonStr(args), | ||
| 921 | - new HashMap<>(),"POST","JSON"); | ||
| 922 | - } | ||
| 923 | - return String.valueOf(successResult(toolExecutionRequest, "操作成功")); | 879 | + String sBizContent = meta.getSBizContent(); |
| 880 | + Integer iBizType = meta.getIBizType(); | ||
| 881 | + args.put("sUserId", session.getUserId()); | ||
| 882 | + args.put("sLoginId", session.getUserName()); | ||
| 883 | + args.put("sMakePerson", session.getUserName()); | ||
| 884 | + args.put("sBrId", session.getSBrandsId()); | ||
| 885 | + args.put("sBrandsId", session.getSBrandsId()); | ||
| 886 | + args.put("sSuId", session.getSSubsidiaryId()); | ||
| 887 | + args.put("sSubsidiaryId", session.getSSubsidiaryId()); | ||
| 888 | + if (iBizType == 1 || iBizType == 4) { | ||
| 889 | + Map<String, Object> data = new HashMap<>(args); | ||
| 890 | + data.put("sData", JSONObject.toJSONString(data)); | ||
| 891 | + Map<String, Object> searMap = this.dynamicExeDbService.getDoProMap(sBizContent, data); | ||
| 892 | + Map<String, Object> sReturn = this.dynamicExeDbService.getCallPro(searMap, sBizContent); | ||
| 893 | + Integer sCode = ObjectUtil.isNotEmpty(sReturn.get(ProcedureConstant.SCODE)) ? Integer.valueOf(sReturn.get(ProcedureConstant.SCODE).toString()) : 0; | ||
| 894 | + String sMsgText = ObjectUtil.isNotEmpty(sReturn.get(ProcedureConstant.SRETURN)) ? sReturn.get(ProcedureConstant.SRETURN).toString() : "操作成功"; | ||
| 895 | + if (sCode < 0) { | ||
| 896 | + String msg = ObjectUtil.isEmpty(sMsgText) ? "调用过程sCode:" + Integer.valueOf(searMap.get(ProcedureConstant.SCODE).toString()) : sMsgText; | ||
| 897 | + return String.valueOf(askUserResult(toolExecutionRequest, msg)); | ||
| 898 | + } | ||
| 899 | + session.setSFunPrompts(sMsgText); | ||
| 900 | + return sMsgText; | ||
| 901 | + } else if (iBizType == 2 && ObjectUtil.isNotEmpty(sBizContent)) { | ||
| 902 | + //SQL查询 | ||
| 903 | + if (sBizContent.toLowerCase().startsWith("update")) { | ||
| 904 | + this.dynamicExeDbService.updateSql(args, sBizContent); | ||
| 905 | + } else if (sBizContent.toLowerCase().startsWith("delete")) { | ||
| 906 | + this.dynamicExeDbService.delSql(args, sBizContent); | ||
| 907 | + } else if (sBizContent.toLowerCase().startsWith("insert")) { | ||
| 908 | + this.dynamicExeDbService.addSql(args, sBizContent); | ||
| 909 | + } else { | ||
| 910 | + List<Map<String, Object>> retData = this.dynamicExeDbService.findSql(args, sBizContent); | ||
| 911 | + if (ObjectUtil.isNotEmpty(retData)) { | ||
| 912 | + StringBuffer sb = new StringBuffer(); | ||
| 913 | + retData.forEach(one -> { | ||
| 914 | + one.forEach((k, v) -> { | ||
| 915 | + sb.append(v).append(" "); | ||
| 916 | + }); | ||
| 917 | + sb.append("<br/>"); | ||
| 918 | + }); | ||
| 919 | + if (ObjectUtil.isNotEmpty(retData)) { | ||
| 920 | + sb.append("请根据这些信息安排今天的工作吧!如果有具体任务需要进一步处理,请告诉我"); | ||
| 921 | + } | ||
| 922 | + session.setSFunPrompts(sb.toString()); | ||
| 923 | + if ("queryTodayTask".equals(meta.getSMethodNo())) { | ||
| 924 | + session.setBCleanMemory(true); | ||
| 925 | + } | ||
| 926 | + return String.valueOf(successResult(toolExecutionRequest, sb.toString())); | ||
| 927 | + } else { | ||
| 928 | + session.setSFunPrompts("未找到对应的数据"); | ||
| 929 | + return "未找到对应的数据"; | ||
| 930 | + } | ||
| 931 | + } | ||
| 932 | + } else if (iBizType == 3) { | ||
| 933 | + return HttpsRequestUtil.me().doRequestHttp(sBizContent, JSONUtil.toJsonStr(args), | ||
| 934 | + new HashMap<>(), "POST", "JSON"); | ||
| 935 | + } | ||
| 936 | + return String.valueOf(successResult(toolExecutionRequest, "操作成功")); | ||
| 924 | } | 937 | } |
| 925 | 938 | ||
| 926 | 939 | ||
| @@ -983,7 +996,7 @@ public class DynamicToolProvider implements ToolProvider { | @@ -983,7 +996,7 @@ public class DynamicToolProvider implements ToolProvider { | ||
| 983 | log.info("headers=============================={}", JSONObject.toJSONString(headers)); | 996 | log.info("headers=============================={}", JSONObject.toJSONString(headers)); |
| 984 | log.info("请求URL,JSON,headers=={},{},{}",sUrl,JSONObject.toJSONString(sBody),JSONObject.toJSONString(headers)); | 997 | log.info("请求URL,JSON,headers=={},{},{}",sUrl,JSONObject.toJSONString(sBody),JSONObject.toJSONString(headers)); |
| 985 | ErpResult erpResult = JsonUtils.toObject(result,ErpResult.class); | 998 | ErpResult erpResult = JsonUtils.toObject(result,ErpResult.class); |
| 986 | - result = buildResultMessageWithTable( meta, erpResult); | 999 | + result = buildResultMessageWithTable( meta, erpResult, session); |
| 987 | }catch (Exception e){ | 1000 | }catch (Exception e){ |
| 988 | result ="执行异常:"+e.getMessage(); | 1001 | result ="执行异常:"+e.getMessage(); |
| 989 | } | 1002 | } |
| @@ -993,8 +1006,8 @@ public class DynamicToolProvider implements ToolProvider { | @@ -993,8 +1006,8 @@ public class DynamicToolProvider implements ToolProvider { | ||
| 993 | /** | 1006 | /** |
| 994 | * 构建 窗体获取数据方法 未清或者明细 | 1007 | * 构建 窗体获取数据方法 未清或者明细 |
| 995 | */ | 1008 | */ |
| 996 | - public String buildResultMessageWithTable(ToolMeta meta,ErpResult erpResult){ | ||
| 997 | - | 1009 | + public String buildResultMessageWithTable(ToolMeta meta,ErpResult erpResult,UserSceneSession session){ |
| 1010 | + Map<Integer,Map<String,Object>> currentRowData = new HashMap<>(); | ||
| 998 | ErpDataset dataset = erpResult.getDataset(); | 1011 | ErpDataset dataset = erpResult.getDataset(); |
| 999 | //返回错误信息 | 1012 | //返回错误信息 |
| 1000 | if(erpResult.getCode()<0 && ObjectUtil.isNotEmpty(erpResult.getMsg())){ | 1013 | if(erpResult.getCode()<0 && ObjectUtil.isNotEmpty(erpResult.getMsg())){ |
| @@ -1037,24 +1050,36 @@ public class DynamicToolProvider implements ToolProvider { | @@ -1037,24 +1050,36 @@ public class DynamicToolProvider implements ToolProvider { | ||
| 1037 | headers.forEach(header -> markdown.append(header).append(" | ")); | 1050 | headers.forEach(header -> markdown.append(header).append(" | ")); |
| 1038 | markdown.append("\n|").append("---|".repeat(headers.size() + 1)).append("\n"); | 1051 | markdown.append("\n|").append("---|".repeat(headers.size() + 1)).append("\n"); |
| 1039 | // 填充表格数据 | 1052 | // 填充表格数据 |
| 1053 | + List<Map<String,Object>> machineData = new LinkedList<>(); | ||
| 1040 | for (int i = 0; i < recordData.size(); i++) { | 1054 | for (int i = 0; i < recordData.size(); i++) { |
| 1041 | // 保存隐藏列的值(如"唯一"字段) | 1055 | // 保存隐藏列的值(如"唯一"字段) |
| 1042 | String uniqueValue = recordData.get(i).get("sSlaveId") != null ? recordData.get(i).get("sSlaveId").toString() : ""; | 1056 | String uniqueValue = recordData.get(i).get("sSlaveId") != null ? recordData.get(i).get("sSlaveId").toString() : ""; |
| 1043 | markdown.append("| ").append(i + 1).append(" | "); | 1057 | markdown.append("| ").append(i + 1).append(" | "); |
| 1058 | + Map<String,Object> rMap = new HashMap<>(); | ||
| 1044 | for (String header : headers) { | 1059 | for (String header : headers) { |
| 1045 | // 这里需要根据你的数据结构来获取对应的值 | 1060 | // 这里需要根据你的数据结构来获取对应的值 |
| 1046 | Object value = recordData.get(i)!= null ? recordData.get(i).get(header) : null; | 1061 | Object value = recordData.get(i)!= null ? recordData.get(i).get(header) : null; |
| 1047 | markdown.append(value != null ? value : "—").append(" | "); | 1062 | markdown.append(value != null ? value : "—").append(" | "); |
| 1063 | + rMap.put(header,value); | ||
| 1048 | } | 1064 | } |
| 1065 | + rMap.put("sSlaveId",uniqueValue); | ||
| 1066 | + rMap.put("唯一",uniqueValue); | ||
| 1049 | // 在行末添加隐藏数据的特殊标记(AI可以解析) | 1067 | // 在行末添加隐藏数据的特殊标记(AI可以解析) |
| 1050 | - markdown.append(" <!-- HIDDEN_DATA:{\"sSlaveId\":\"").append(uniqueValue).append("\"} -->"); | 1068 | + markdown.append(" <!-- HIDDEN_DATA:").append(JSONUtil.toJsonStr(rMap)).append("-->"); |
| 1051 | markdown.append("\n"); | 1069 | markdown.append("\n"); |
| 1070 | + machineData.add(rMap); | ||
| 1071 | + currentRowData.put(i + 1,recordData.get(i)); | ||
| 1052 | } | 1072 | } |
| 1053 | markdown.append(">"); | 1073 | markdown.append(">"); |
| 1074 | +// // 4. 机器可读的结构化数据(只出现一次!) | ||
| 1075 | +// markdown.append("<!-- MACHINE_DATA_START -->\n"); | ||
| 1076 | +// markdown.append(JSONUtil.toJsonStr(machineData)); | ||
| 1077 | +// markdown.append("\n<!-- MACHINE_DATA_END -->\n\n"); | ||
| 1054 | if(meta.getIBizType()==4){ | 1078 | if(meta.getIBizType()==4){ |
| 1055 | markdown.append("\n---\n"); | 1079 | markdown.append("\n---\n"); |
| 1056 | appendConfirmAll(markdown,meta.getSControlName()); | 1080 | appendConfirmAll(markdown,meta.getSControlName()); |
| 1057 | } | 1081 | } |
| 1082 | + session.setCurrentRowData(currentRowData); | ||
| 1058 | return markdown.toString(); | 1083 | return markdown.toString(); |
| 1059 | } | 1084 | } |
| 1060 | 1085 |
src/main/java/com/xly/tts/service/PythonTtsProxyService.java
| @@ -77,7 +77,6 @@ public class PythonTtsProxyService { | @@ -77,7 +77,6 @@ public class PythonTtsProxyService { | ||
| 77 | String sUserType = request.getUsertype(); | 77 | String sUserType = request.getUsertype(); |
| 78 | String authorization = request.getAuthorization(); | 78 | String authorization = request.getAuthorization(); |
| 79 | //校验登录token 是否有效 | 79 | //校验登录token 是否有效 |
| 80 | - | ||
| 81 | AiResponseDTO voiceText = xlyErpService.erpUserInput(userInput,sUserId,sUserName,sBrandsId,sSubsidiaryId,sUserType, authorization); | 80 | AiResponseDTO voiceText = xlyErpService.erpUserInput(userInput,sUserId,sUserName,sBrandsId,sSubsidiaryId,sUserType, authorization); |
| 82 | return synthesizeStreamAi(request,voiceText); | 81 | return synthesizeStreamAi(request,voiceText); |
| 83 | } | 82 | } |
src/main/java/com/xly/util/AIModelDataFormatter.java
0 → 100644
| 1 | +package com.xly.util; | ||
| 2 | +import cn.hutool.json.JSONUtil; | ||
| 3 | +import com.xly.entity.ToolMeta; | ||
| 4 | + | ||
| 5 | +import java.util.*; | ||
| 6 | + | ||
| 7 | +public class AIModelDataFormatter { | ||
| 8 | + public static String formatDataForAI(ToolMeta meta, Integer TotalCount, | ||
| 9 | + List<Map<String, Object>> rows, | ||
| 10 | + Set<String> headers) { | ||
| 11 | + StringBuilder response = new StringBuilder(); | ||
| 12 | + // 3. **关键:先显示用户友好的表格** | ||
| 13 | + response.append("## 查询结果(共").append(TotalCount).append("条)\n\n"); | ||
| 14 | + response.append(buildMarkdownTable(rows, headers)); | ||
| 15 | + response.append("\n"); | ||
| 16 | + // 4. 机器可读的结构化数据(只出现一次!) | ||
| 17 | + response.append("<!-- MACHINE_DATA_START -->\n"); | ||
| 18 | + response.append(buildMachineReadableData(rows,headers)); | ||
| 19 | + response.append("\n<!-- MACHINE_DATA_END -->\n\n"); | ||
| 20 | + return response.toString(); | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + /** | ||
| 24 | + * 构建Markdown表格 | ||
| 25 | + */ | ||
| 26 | + public static String buildMarkdownTable(List<Map<String, Object>> rows, Set<String> headers) { | ||
| 27 | + StringBuilder table = new StringBuilder(); | ||
| 28 | + // 表头 | ||
| 29 | + table.append("| 序号 | "); | ||
| 30 | + for (String header : headers) { | ||
| 31 | + table.append(header).append(" | "); | ||
| 32 | + } | ||
| 33 | + table.append("\n|"); | ||
| 34 | + table.append("---|".repeat(headers.size() + 1)); | ||
| 35 | + table.append("\n"); | ||
| 36 | + // 表格数据 | ||
| 37 | + for (int i = 0; i < rows.size(); i++) { | ||
| 38 | + Map<String, Object> row = rows.get(i); | ||
| 39 | + table.append("| ").append(i + 1).append(" | "); | ||
| 40 | + for (String header : headers) { | ||
| 41 | + Object value = row.get(header); | ||
| 42 | + table.append(value != null ? value : "—").append(" | "); | ||
| 43 | + } | ||
| 44 | + table.append("\n"); | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + return table.toString(); | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + /** | ||
| 51 | + * 构建机器可读数据 | ||
| 52 | + */ | ||
| 53 | + public static String buildMachineReadableData(List<Map<String, Object>> rows, Set<String> headers) { | ||
| 54 | + List<Map<String, Object>> dataList = new ArrayList<>(); | ||
| 55 | + for (int i = 0; i < rows.size(); i++) { | ||
| 56 | + Map<String, Object> row = rows.get(i); | ||
| 57 | + Map<String, Object> item = new LinkedHashMap<>(); | ||
| 58 | + item.put("序号", i + 1); | ||
| 59 | + for (String header : headers) { | ||
| 60 | + Object value = row.get(header); | ||
| 61 | + item.put(header,value); | ||
| 62 | + } | ||
| 63 | + dataList.add(item); | ||
| 64 | + } | ||
| 65 | + return JSONUtil.toJsonStr(dataList); | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | +} | ||
| 0 | \ No newline at end of file | 69 | \ No newline at end of file |
src/main/java/com/xly/util/UserChoseIntentParser.java
0 → 100644
| 1 | +package com.xly.util; | ||
| 2 | + | ||
| 3 | +import java.util.*; | ||
| 4 | +import java.util.stream.Collectors; | ||
| 5 | + | ||
| 6 | +public class UserChoseIntentParser { | ||
| 7 | + | ||
| 8 | + /** | ||
| 9 | + * @param input 用户输入:全部确认 / 合并确认 / 1,3行确认 | ||
| 10 | + * @param rowMap 上面parse出来的全量数据 | ||
| 11 | + * @return 最终要生成订单的明细列表 | ||
| 12 | + */ | ||
| 13 | + public static Map<String,Object> getSelectedRows(String input, Map<Integer, Map<String,Object>> rowMap) { | ||
| 14 | + List<Map<String,Object>> sRowData = new ArrayList<>(); | ||
| 15 | + Map<String,Object> rMap = new HashMap<>(); | ||
| 16 | + String sHandleType = "merge";//单个的 就是全部确认 否则就是合并确认 | ||
| 17 | + if (input.contains("全部确认") || input.contains("合并确认")) { | ||
| 18 | + if(input.contains("全部确认")){ | ||
| 19 | + sHandleType = "single"; | ||
| 20 | + } | ||
| 21 | + sRowData = new ArrayList<>(rowMap.values()); | ||
| 22 | + rMap.put("sRowData",sRowData); | ||
| 23 | + rMap.put("sHandleType",sHandleType); | ||
| 24 | + return rMap; | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | + // 解析 1,3,5-7 这类行号 | ||
| 28 | + Set<Integer> selected = new HashSet<>(); | ||
| 29 | + String[] parts = input.replaceAll("[^0-9,-]", "").split("[,,]"); | ||
| 30 | + for (String part : parts) { | ||
| 31 | + try { | ||
| 32 | + if (part.contains("-")) { | ||
| 33 | + String[] range = part.split("-"); | ||
| 34 | + int start = Integer.parseInt(range[0]); | ||
| 35 | + int end = Integer.parseInt(range[1]); | ||
| 36 | + for (int i = start; i <= end; i++) { | ||
| 37 | + selected.add(i); | ||
| 38 | + } | ||
| 39 | + } else { | ||
| 40 | + selected.add(Integer.parseInt(part)); | ||
| 41 | + } | ||
| 42 | + } catch (Exception ignored) {} | ||
| 43 | + } | ||
| 44 | + rMap.put("sRowData",selected.stream() | ||
| 45 | + .filter(rowMap::containsKey) | ||
| 46 | + .map(rowMap::get) | ||
| 47 | + .collect(Collectors.toList())); | ||
| 48 | + rMap.put("sHandleType",sHandleType); | ||
| 49 | + return rMap; | ||
| 50 | + } | ||
| 51 | +} |
src/main/resources/templates/chat.html
| @@ -462,13 +462,13 @@ | @@ -462,13 +462,13 @@ | ||
| 462 | <script> | 462 | <script> |
| 463 | let sessionId =""; | 463 | let sessionId =""; |
| 464 | // let userid= "17706006510007934913359242990000"; | 464 | // let userid= "17706006510007934913359242990000"; |
| 465 | - let userid= "17502321750004978169421209637000"; | ||
| 466 | - let username= "admin"; | 465 | + let userid= "17522967560005776104370282597000"; |
| 466 | + let username= "钱豹"; | ||
| 467 | let brandsid= "1111111111"; | 467 | let brandsid= "1111111111"; |
| 468 | let subsidiaryid= "1111111111"; | 468 | let subsidiaryid= "1111111111"; |
| 469 | let usertype= "sysadmin"; | 469 | let usertype= "sysadmin"; |
| 470 | // let usertype= "General"; | 470 | // let usertype= "General"; |
| 471 | - let authorization="1EDB99C9BF070115F7A57AC43D8CB09F0B8C49F979DAB63A2AEA84B372B2B42BF3419238942A93E9AD666629E18D159AF7FE144A6407DE745BA0AEC8B235FC1D2D86C92E4DCE571A8ECF0767494BBDE2495FD8E662F2065F9430347C7E4472B5538155B7ADAEE71E899235DC1122F426"; | 471 | + let authorization="CE444885A9BCFDDE1FD793F8A0931301E9D7DE6CEDD9DE4B83ECE2219C7829A8F3419238942A93E9AD666629E18D159AF7FE144A6407DE745BA0AEC8B235FC1DD20808D80550B390B6BE6ECDCFF597F4D8668502224FF2D5675E74B8CDD2ABFB538155B7ADAEE71E899235DC1122F426"; |
| 472 | //"1EDB99C9BF070115F7A57AC43D8CB09F0B8C49F979DAB63A2AEA84B372B2B42BF3419238942A93E9AD666629E18D159AF7FE144A6407DE745BA0AEC8B235FC1DC16A7526DEC4395CF09285C2BD330B9FD8668502224FF2D5675E74B8CDD2ABFB538155B7ADAEE71E899235DC1122F426"; | 472 | //"1EDB99C9BF070115F7A57AC43D8CB09F0B8C49F979DAB63A2AEA84B372B2B42BF3419238942A93E9AD666629E18D159AF7FE144A6407DE745BA0AEC8B235FC1DC16A7526DEC4395CF09285C2BD330B9FD8668502224FF2D5675E74B8CDD2ABFB538155B7ADAEE71E899235DC1122F426"; |
| 473 | let hrefLock = window.location.origin+"/xlyAi"; | 473 | let hrefLock = window.location.origin+"/xlyAi"; |
| 474 | // ==================== 配置部分 ==================== | 474 | // ==================== 配置部分 ==================== |