Commit e5fb3dd8f70f5116579991389476bd776b3799c1

Authored by qianbao
1 parent 781d3448

添加未清选择 改成动态引导语

src/main/java/com/xly/agent/AgentSystemPrompt.java 0 → 100644
  1 +package com.xly.agent;
  2 +
  3 +public class AgentSystemPrompt {
  4 +
  5 + public static final String sSystemPrompt= "1. 方法匹配:先精准拆解用户查询的核心业务意图,再自动匹配唯一符合用户问题的工具方法(MethodNo),禁止自创,规则如下;\n" +
  6 + " 1.1 匹配方法时,无需考虑工具描述(@TOOL)中 1.必填参数,2.选填参数,示例,parameters内容 四个部分的内容;\n" +
  7 + " 1.2 匹配方法时,只关注工具描述(@TOOL)中 “当用户” 和 “时,必须调用本工具”两个短语之间的内容;\n" +
  8 + " 1.3 调用工具前,不需要询问用户提供缺失的参数\n" +
  9 + " 2. 参数提取:提取该工具的全部参数,与描述完全一致,严格按标注类型赋值,规则如下:\n" +
  10 + " 2.1 数字无引号,为空时禁止赋值0;\n" +
  11 + " 2.2 如果有空格需要去掉空格后再提取。";
  12 +}
src/main/java/com/xly/agent/ErpAiAgent.java
@@ -10,18 +10,14 @@ import dev.langchain4j.service.V; @@ -10,18 +10,14 @@ import dev.langchain4j.service.V;
10 * 优化后:新增场景专属交互规则,大模型仅处理当前场景业务指令 10 * 优化后:新增场景专属交互规则,大模型仅处理当前场景业务指令
11 */ 11 */
12 public interface ErpAiAgent { 12 public interface ErpAiAgent {
13 - @SystemMessage("""  
14 - 1. 方法匹配:先精准拆解用户查询的核心业务意图,再自动匹配唯一符合用户问题的工具方法(MethodNo),禁止自创,规则如下;  
15 - 1.1 匹配方法时,无需考虑工具描述(@TOOL)中 1.必填参数,2.选填参数,示例,parameters内容 四个部分的内容;  
16 - 1.2 匹配方法时,只关注工具描述(@TOOL)中 “当用户” 和 “时,必须调用本工具”两个短语之间的内容;  
17 - 1.3 调用工具前,不需要询问用户提供缺失的参数  
18 - 2. 参数提取:提取该工具的全部参数,与描述完全一致,严格按标注类型赋值,规则如下:  
19 - 2.1 数字无引号,为空时禁止赋值0;  
20 - 2.2 如果有空格需要去掉空格后再提取。  
21 - """)  
22 - @UserMessage("用户输入:{{userInput}}")  
23 - String chat(@MemoryId String userId, @V("userInput") String userInput);  
24 13
  14 + @SystemMessage("{{sSystemPrompt}}")
  15 + @UserMessage("用户输入:{{userInput}}")
  16 + String chat(
  17 + @MemoryId String userId,
  18 + @V("userInput") String userInput,
  19 + @V("sSystemPrompt") String sSystemPrompt
  20 + );
25 /** 21 /**
26 * 动态表结构:自然语言解释SQL执行结果 22 * 动态表结构:自然语言解释SQL执行结果
27 * 入参:用户问题、执行的SQL、表结构、JSON格式结果 23 * 入参:用户问题、执行的SQL、表结构、JSON格式结果
src/main/java/com/xly/entity/UserSceneSession.java
@@ -75,6 +75,14 @@ public class UserSceneSession { @@ -75,6 +75,14 @@ public class UserSceneSession {
75 * 数据库类型 H: 缓存 D: 动态 75 * 数据库类型 H: 缓存 D: 动态
76 */ 76 */
77 private String dbCach; 77 private String dbCach;
  78 + /**
  79 + * @Author 钱豹
  80 + * @Date 22:55 2026/4/13
  81 + * @Param
  82 + * @return
  83 + * @Description AI 模板
  84 + **/
  85 + private String sSystemPrompt;
78 86
79 /** 87 /**
80 * 构建场景选择提示语:展示权限内场景,引导用户选择 88 * 构建场景选择提示语:展示权限内场景,引导用户选择
src/main/java/com/xly/service/XlyErpService.java
@@ -8,10 +8,7 @@ import cn.hutool.core.util.StrUtil; @@ -8,10 +8,7 @@ import cn.hutool.core.util.StrUtil;
8 import cn.hutool.json.JSONUtil; 8 import cn.hutool.json.JSONUtil;
9 import com.alibaba.fastjson2.JSON; 9 import com.alibaba.fastjson2.JSON;
10 import com.alibaba.fastjson2.JSONObject; 10 import com.alibaba.fastjson2.JSONObject;
11 -import com.xly.agent.ChatiAgent;  
12 -import com.xly.agent.DynamicTableNl2SqlAiAgent;  
13 -import com.xly.agent.ErpAiAgent;  
14 -import com.xly.agent.SceneSelectorAiAgent; 11 +import com.xly.agent.*;
15 import com.xly.config.OperableChatMemoryProvider; 12 import com.xly.config.OperableChatMemoryProvider;
16 import com.xly.constant.CommonConstant; 13 import com.xly.constant.CommonConstant;
17 import com.xly.constant.ReturnTypeCode; 14 import com.xly.constant.ReturnTypeCode;
@@ -140,7 +137,7 @@ public class XlyErpService { @@ -140,7 +137,7 @@ public class XlyErpService {
140 && ObjectUtil.isNotEmpty(session.getCurrentTool().getSInputTabelName()) 137 && ObjectUtil.isNotEmpty(session.getCurrentTool().getSInputTabelName())
141 && ObjectUtil.isNotEmpty(session.getCurrentTool().getSStructureMemo())) 138 && ObjectUtil.isNotEmpty(session.getCurrentTool().getSStructureMemo()))
142 ){ 139 ){
143 - sResponMessage = aiAgent.chat(userId, input); 140 + sResponMessage = aiAgent.chat(userId, input , AgentSystemPrompt.sSystemPrompt);
144 } 141 }
145 142
146 if(ObjectUtil.isNotEmpty(session.getCurrentTool()) 143 if(ObjectUtil.isNotEmpty(session.getCurrentTool())
@@ -351,7 +348,11 @@ public class XlyErpService { @@ -351,7 +348,11 @@ public class XlyErpService {
351 && ObjectUtil.isNotEmpty(session.getCurrentTool().getSInputTabelName()) 348 && ObjectUtil.isNotEmpty(session.getCurrentTool().getSInputTabelName())
352 && ObjectUtil.isNotEmpty(session.getCurrentTool().getSStructureMemo())) 349 && ObjectUtil.isNotEmpty(session.getCurrentTool().getSStructureMemo()))
353 ){ 350 ){
354 - sResponMessage = aiAgent.chat(userId, input); 351 + String sSystemPrompt = AgentSystemPrompt.sSystemPrompt;
  352 + if(ObjectUtil.isNotEmpty(session.getSSystemPrompt())){
  353 + sSystemPrompt = session.getSSystemPrompt();
  354 + }
  355 + sResponMessage = aiAgent.chat(userId, input,sSystemPrompt);
355 } 356 }
356 methodName = ObjectUtil.isNotEmpty(session.getCurrentTool())?session.getCurrentTool().getSMethodName():StrUtil.EMPTY; 357 methodName = ObjectUtil.isNotEmpty(session.getCurrentTool())?session.getCurrentTool().getSMethodName():StrUtil.EMPTY;
357 if(ObjectUtil.isNotEmpty(session.getCurrentTool()) 358 if(ObjectUtil.isNotEmpty(session.getCurrentTool())
@@ -947,7 +948,7 @@ public class XlyErpService { @@ -947,7 +948,7 @@ public class XlyErpService {
947 UserSceneSessionService.ERP_AGENT_CACHE.put(userId, aiAgent); 948 UserSceneSessionService.ERP_AGENT_CACHE.put(userId, aiAgent);
948 // 初始化AiService 以防止热加载太慢 找不到相应的方法 949 // 初始化AiService 以防止热加载太慢 找不到相应的方法
949 try{ 950 try{
950 - aiAgent.chat(userId, "initAiService"); 951 + aiAgent.chat(userId, "initAiService",AgentSystemPrompt.sSystemPrompt);
951 }catch (Exception e){ 952 }catch (Exception e){
952 e.printStackTrace(); 953 e.printStackTrace();
953 } 954 }
src/main/java/com/xly/tool/DynamicToolProvider.java
@@ -10,11 +10,9 @@ import com.fasterxml.jackson.core.type.TypeReference; @@ -10,11 +10,9 @@ import com.fasterxml.jackson.core.type.TypeReference;
10 10
11 import com.fasterxml.jackson.databind.ObjectMapper; 11 import com.fasterxml.jackson.databind.ObjectMapper;
12 12
  13 +import com.xly.agent.AgentSystemPrompt;
13 import com.xly.config.OperableChatMemoryProvider; 14 import com.xly.config.OperableChatMemoryProvider;
14 -import com.xly.constant.ErrorCode;  
15 -import com.xly.constant.ProcedureConstant;  
16 -import com.xly.constant.RuleCode;  
17 -import com.xly.constant.UrlErpConstant; 15 +import com.xly.constant.*;
18 import com.xly.entity.*; 16 import com.xly.entity.*;
19 import com.xly.exception.dto.BusinessException; 17 import com.xly.exception.dto.BusinessException;
20 import com.xly.mapper.ParamRuleMapper; 18 import com.xly.mapper.ParamRuleMapper;
@@ -245,80 +243,67 @@ public class DynamicToolProvider implements ToolProvider { @@ -245,80 +243,67 @@ public class DynamicToolProvider implements ToolProvider {
245 return ToolProviderResult.builder().addAll(executors).build(); 243 return ToolProviderResult.builder().addAll(executors).build();
246 } 244 }
247 245
248 - /***  
249 - * @Author 钱豹  
250 - * @Date 15:07 2026/1/30  
251 - * @Param [meta]  
252 - * @return dev.langchain4j.agent.tool.ToolSpecification  
253 - * @Description 参数注入 方法引导语注入(初始化调用)  
254 - **/  
255 private ToolSpecification buildToolSpecification(ToolMeta meta) { 246 private ToolSpecification buildToolSpecification(ToolMeta meta) {
256 ToolSpecification.Builder builder = ToolSpecification.builder() 247 ToolSpecification.Builder builder = ToolSpecification.builder()
257 .name(meta.getSMethodNo()); 248 .name(meta.getSMethodNo());
258 -// .description(meta.getStoolDesc()); 249 +
259 StringBuffer stoolDesc = new StringBuffer(); 250 StringBuffer stoolDesc = new StringBuffer();
260 StringBuffer sbt = new StringBuffer(); 251 StringBuffer sbt = new StringBuffer();
261 StringBuffer xt = new StringBuffer(); 252 StringBuffer xt = new StringBuffer();
262 StringBuffer sl = new StringBuffer(); 253 StringBuffer sl = new StringBuffer();
263 254
264 - if(ObjectUtil.isNotEmpty(meta.getStoolDesc())){  
265 - stoolDesc.append("MethodNo:").append(meta.getSMethodNo()).append(",当用户").append(meta.getSMethodName());  
266 -// if (meta.getIBizType()==4){  
267 -// stoolDesc.append(",").append("并选择数据后执行["+meta.getSControlName()+"]操作");  
268 -// }  
269 - stoolDesc.append("时,必须调用本工具").append(meta.getSMethodNo()).append(",").append(meta.getStoolDesc());  
270 -// if (meta.getIBizType()==4){  
271 -// stoolDesc.append(",").append("并选择数据后执行 "+meta.getSControlName()+" 操作");  
272 -//// .append("1.全部数据生成多个单据 回复【全部确认】;2.全部数据生成一个单据 回复【合并确认】;3.按自然语义描述生成一个单据 如"1,3行确认"");  
273 -// } 255 + // ====================== 【超级强制:必须调用工具】 ======================
  256 + String forceToolPrompt = """
  257 + 【重要·强制指令】
  258 + 1. 这是当前唯一可用工具,必须调用,禁止直接回答
  259 + 2. 用户输入包含:确认、全部确认、合并确认、行号(第1行/第一行等)
  260 + 3. 必须调用本工具,必须调用,必须调用!
  261 + """;
  262 + stoolDesc.append(forceToolPrompt);
  263 + // ========================================================================
  264 +
  265 + if (ObjectUtil.isNotEmpty(meta.getStoolDesc())) {
  266 + stoolDesc.append("MethodNo:").append(meta.getSMethodNo())
  267 + .append(",当用户").append(meta.getSMethodName())
  268 + .append("时,必须调用本工具").append(meta.getSMethodNo())
  269 + .append(",").append(meta.getStoolDesc());
274 } 270 }
275 271
276 try { 272 try {
277 List<ParamRule> paramRuleData = meta.getParamRuleList(); 273 List<ParamRule> paramRuleData = meta.getParamRuleList();
278 -// 1.必填参数:客户名称(字符串),产品名称(字符串),数量(数字);  
279 -// 2.选填参数:产品描述(字符串),生产要求(字符串);  
280 -// **强制输出标准JSON对象**:  
281 -// 示例:{\"客户名称\":\"小羚羊软件开发有限公司\",\"产品名称\":\"企业宣传册\",\"数量\":1000,\"产品描述\":\"黑色注意色差\",\"生产要求\":\"上光,覆膜\"}  
282 - Map<String,Object> slMap = new HashMap<>(); 274 + Map<String, Object> slMap = new HashMap<>();
  275 +
283 for (ParamRule paramRule : paramRuleData) { 276 for (ParamRule paramRule : paramRuleData) {
284 -// String paramName = ObjectUtil.isEmpty(paramRule.getSParamValue())?null:paramRule.getSParamValue();  
285 - String paramDesc = ObjectUtil.isEmpty(paramRule.getSParam())?null:paramRule.getSParam(); 277 + String paramDesc = ObjectUtil.isEmpty(paramRule.getSParam()) ? null : paramRule.getSParam();
286 String paramType = paramRule.getSType(); 278 String paramType = paramRule.getSType();
287 Boolean bEmpty = paramRule.getBEmpty(); 279 Boolean bEmpty = paramRule.getBEmpty();
288 String sExampleValue = paramRule.getSExampleValue(); 280 String sExampleValue = paramRule.getSExampleValue();
289 - //示例值,只有枚举放  
290 - if(ObjectUtil.isNotEmpty(sExampleValue) && "enum".equals(paramType.toLowerCase())){  
291 - //英文  
292 -// slMap.put(paramName,sExampleValue);  
293 - //中文  
294 - slMap.put(paramDesc,sExampleValue); 281 +
  282 + if (ObjectUtil.isNotEmpty(sExampleValue) && "enum".equals(paramType.toLowerCase())) {
  283 + slMap.put(paramDesc, sExampleValue);
295 } 284 }
296 if (paramDesc == null || paramDesc.trim().isEmpty()) { 285 if (paramDesc == null || paramDesc.trim().isEmpty()) {
297 continue; 286 continue;
298 } 287 }
299 - // 构建参数属性  
300 - //{"string":"字符","integer":"数字","double":"浮点","boolean":"布尔型","array":"数组","enum":"枚举"} 288 +
301 List<JsonSchemaProperty> properties = new ArrayList<>(); 289 List<JsonSchemaProperty> properties = new ArrayList<>();
302 - // 添加类型属性  
303 -// String ,仅允许【{}】多选一,严格匹配)  
304 - String sRuleCost = getConstMeg(paramRule.getSParamConfig(),paramRule);  
305 -// 2. 付款方式:字符串类型,互斥枚举值[90天、60天、现结],默认值[现结]  
306 -// 5. 生产要求:数组类型,可多选枚举值[上光、复膜、烫金],无默认值 290 + String sRuleCost = getConstMeg(paramRule.getSParamConfig(), paramRule);
  291 +
307 switch (paramType.toLowerCase()) { 292 switch (paramType.toLowerCase()) {
308 case "string": 293 case "string":
309 - if(bEmpty){  
310 - sbt.append(paramDesc).append("(字符串").append(sRuleCost).append(")").append("、");  
311 - }else{  
312 - xt.append(paramDesc).append("(字符串").append(sRuleCost).append(")").append("、"); 294 + if (bEmpty) {
  295 + sbt.append(paramDesc).append("(字符串").append(sRuleCost).append(")、");
  296 + } else {
  297 + xt.append(paramDesc).append("(字符串").append(sRuleCost).append(")、");
313 } 298 }
314 properties.add(JsonSchemaProperty.STRING); 299 properties.add(JsonSchemaProperty.STRING);
315 break; 300 break;
316 case "integer": 301 case "integer":
317 case "int": 302 case "int":
318 - if(bEmpty){  
319 - sbt.append(paramDesc).append("(数字").append(sRuleCost).append(")").append("、");  
320 - }else{  
321 - xt.append(paramDesc).append("(数字").append(sRuleCost).append(")").append("、"); 303 + if (bEmpty) {
  304 + sbt.append(paramDesc).append("(数字").append(sRuleCost).append(")、");
  305 + } else {
  306 + xt.append(paramDesc).append("(数字").append(sRuleCost).append(")、");
322 } 307 }
323 properties.add(JsonSchemaProperty.INTEGER); 308 properties.add(JsonSchemaProperty.INTEGER);
324 break; 309 break;
@@ -326,91 +311,84 @@ public class DynamicToolProvider implements ToolProvider { @@ -326,91 +311,84 @@ public class DynamicToolProvider implements ToolProvider {
326 case "double": 311 case "double":
327 case "float": 312 case "float":
328 properties.add(JsonSchemaProperty.NUMBER); 313 properties.add(JsonSchemaProperty.NUMBER);
329 - if(bEmpty){  
330 - sbt.append(paramDesc).append("(浮点").append(sRuleCost).append(")").append("、");  
331 - }else{  
332 - xt.append(paramDesc).append("(浮点").append(sRuleCost).append(")").append("、"); 314 + if (bEmpty) {
  315 + sbt.append(paramDesc).append("(浮点").append(sRuleCost).append(")、");
  316 + } else {
  317 + xt.append(paramDesc).append("(浮点").append(sRuleCost).append(")、");
333 } 318 }
334 break; 319 break;
335 case "boolean": 320 case "boolean":
336 case "bool": 321 case "bool":
337 - if(bEmpty){  
338 - sbt.append(paramDesc).append("(布尔型").append(sRuleCost).append(")").append("、");  
339 - }else{  
340 - xt.append(paramDesc).append("(布尔型").append(sRuleCost).append(")").append("、"); 322 + if (bEmpty) {
  323 + sbt.append(paramDesc).append("(布尔型").append(sRuleCost).append(")、");
  324 + } else {
  325 + xt.append(paramDesc).append("(布尔型").append(sRuleCost).append(")、");
341 } 326 }
342 properties.add(JsonSchemaProperty.BOOLEAN); 327 properties.add(JsonSchemaProperty.BOOLEAN);
343 break; 328 break;
344 case "array": 329 case "array":
345 String sRuleArray = getArrrayBySql(paramRule); 330 String sRuleArray = getArrrayBySql(paramRule);
346 - // 生产要求:数组类型,可多选枚举值[上光、复膜、烫金],无默认值  
347 if (ObjectUtil.isNotEmpty(sRuleArray)) { 331 if (ObjectUtil.isNotEmpty(sRuleArray)) {
348 - sRuleArray = StrUtil.replace(sRuleArray,",","/"); 332 + sRuleArray = StrUtil.replace(sRuleArray, ",", "/");
349 properties.add(JsonSchemaProperty.enums(sRuleArray.split("/"))); 333 properties.add(JsonSchemaProperty.enums(sRuleArray.split("/")));
350 } 334 }
351 - if(bEmpty){  
352 - //动态SQL 或者写死默认值的 动态SQL只存在一条数据 直接给默认值  
353 - sbt.append(paramDesc).append("(").append("数组类型") .append(",可多选枚举值 [").append(sRuleArray).append("]");  
354 - if(ObjectUtil.isNotEmpty(paramRule.getSDefaultValue()) || (ObjectUtil.isNotEmpty(sRuleArray) && sRuleArray.split("/").length==1)){  
355 - String sDefaultVal = (ObjectUtil.isNotEmpty(sRuleArray) && sRuleArray.split("/").length==1)?sRuleArray:paramRule.getSDefaultValue(); 335 + if (bEmpty) {
  336 + sbt.append(paramDesc).append("(数组类型,可多选枚举值 [").append(sRuleArray).append("]");
  337 + if (ObjectUtil.isNotEmpty(paramRule.getSDefaultValue()) || (ObjectUtil.isNotEmpty(sRuleArray) && sRuleArray.split("/").length == 1)) {
  338 + String sDefaultVal = (ObjectUtil.isNotEmpty(sRuleArray) && sRuleArray.split("/").length == 1) ? sRuleArray : paramRule.getSDefaultValue();
356 sbt.append(",默认值[").append(sDefaultVal).append("]"); 339 sbt.append(",默认值[").append(sDefaultVal).append("]");
357 - }else{ 340 + } else {
358 sbt.append(",无默认值"); 341 sbt.append(",无默认值");
359 } 342 }
360 sbt.append(")、"); 343 sbt.append(")、");
361 - }else{  
362 - xt.append(paramDesc).append("(").append("数组类型") .append(",可多选枚举值 [").append(sRuleArray).append("]");  
363 - if(ObjectUtil.isNotEmpty(paramRule.getSDefaultValue())){ 344 + } else {
  345 + xt.append(paramDesc).append("(数组类型,可多选枚举值 [").append(sRuleArray).append("]");
  346 + if (ObjectUtil.isNotEmpty(paramRule.getSDefaultValue())) {
364 xt.append(",默认值[").append(paramRule.getSDefaultValue()).append("]"); 347 xt.append(",默认值[").append(paramRule.getSDefaultValue()).append("]");
365 - }else{ 348 + } else {
366 xt.append(",无默认值"); 349 xt.append(",无默认值");
367 } 350 }
368 xt.append(")、"); 351 xt.append(")、");
369 } 352 }
370 properties.add(JsonSchemaProperty.ARRAY); 353 properties.add(JsonSchemaProperty.ARRAY);
371 - // 默认字符串数组  
372 properties.add(JsonSchemaProperty.items(JsonSchemaProperty.STRING)); 354 properties.add(JsonSchemaProperty.items(JsonSchemaProperty.STRING));
373 break; 355 break;
374 case "enum": 356 case "enum":
375 - // 处理枚举值  
376 if (ObjectUtil.isNotEmpty(sRuleCost)) { 357 if (ObjectUtil.isNotEmpty(sRuleCost)) {
377 properties.add(JsonSchemaProperty.enums(sRuleCost.split("/"))); 358 properties.add(JsonSchemaProperty.enums(sRuleCost.split("/")));
378 - }else{ 359 + } else {
379 sRuleCost = getArrrayBySql(paramRule); 360 sRuleCost = getArrrayBySql(paramRule);
380 } 361 }
381 - // eg: 付款方式(字符串,互斥枚举值[90天、60天、现结],默认值[现结])  
382 - if(bEmpty){  
383 - sbt.append(paramDesc).append("(").append("字符串") .append(",互斥枚举值 [").append(sRuleCost).append("]");  
384 - if(ObjectUtil.isNotEmpty(paramRule.getSDefaultValue())){ 362 + if (bEmpty) {
  363 + sbt.append(paramDesc).append("(字符串,互斥枚举值 [").append(sRuleCost).append("]");
  364 + if (ObjectUtil.isNotEmpty(paramRule.getSDefaultValue())) {
385 sbt.append(",默认值[").append(paramRule.getSDefaultValue()).append("]"); 365 sbt.append(",默认值[").append(paramRule.getSDefaultValue()).append("]");
386 - }else{ 366 + } else {
387 sbt.append(",无默认值"); 367 sbt.append(",无默认值");
388 } 368 }
389 sbt.append(")、"); 369 sbt.append(")、");
390 - }else{  
391 - xt.append(paramDesc).append("(").append("字符串") .append(",互斥枚举值 [").append(sRuleCost).append("]");  
392 - if(ObjectUtil.isNotEmpty(paramRule.getSDefaultValue())){ 370 + } else {
  371 + xt.append(paramDesc).append("(字符串,互斥枚举值 [").append(sRuleCost).append("]");
  372 + if (ObjectUtil.isNotEmpty(paramRule.getSDefaultValue())) {
393 xt.append(",默认值[").append(paramRule.getSDefaultValue()).append("]"); 373 xt.append(",默认值[").append(paramRule.getSDefaultValue()).append("]");
394 - }else{ 374 + } else {
395 xt.append(",无默认值"); 375 xt.append(",无默认值");
396 } 376 }
397 xt.append(")、"); 377 xt.append(")、");
398 } 378 }
399 properties.add(JsonSchemaProperty.ARRAY); 379 properties.add(JsonSchemaProperty.ARRAY);
400 - // 默认字符串数组  
401 properties.add(JsonSchemaProperty.items(JsonSchemaProperty.STRING)); 380 properties.add(JsonSchemaProperty.items(JsonSchemaProperty.STRING));
402 break; 381 break;
403 default: 382 default:
404 properties.add(JsonSchemaProperty.STRING); 383 properties.add(JsonSchemaProperty.STRING);
405 break; 384 break;
406 } 385 }
407 - // 添加描述 386 +
408 if (!paramDesc.isEmpty()) { 387 if (!paramDesc.isEmpty()) {
409 properties.add(JsonSchemaProperty.description(paramDesc)); 388 properties.add(JsonSchemaProperty.description(paramDesc));
410 } 389 }
411 - // 检查是否必填 390 +
412 boolean required = bEmpty; 391 boolean required = bEmpty;
413 - // 添加参数  
414 if (required) { 392 if (required) {
415 builder.addParameter(paramDesc, properties); 393 builder.addParameter(paramDesc, properties);
416 } else { 394 } else {
@@ -418,31 +396,33 @@ public class DynamicToolProvider implements ToolProvider { @@ -418,31 +396,33 @@ public class DynamicToolProvider implements ToolProvider {
418 } 396 }
419 } 397 }
420 398
421 - if(ObjectUtil.isNotEmpty(sbt)){  
422 - stoolDesc  
423 - .append(System.lineSeparator())  
424 - .append("1.必填参数:")  
425 - .append(sbt); 399 + if (ObjectUtil.isNotEmpty(sbt)) {
  400 + stoolDesc.append(System.lineSeparator()).append("1.必填参数:").append(sbt);
426 } 401 }
427 - if(ObjectUtil.isNotEmpty(xt)){  
428 - stoolDesc  
429 - .append(System.lineSeparator())  
430 - .append("2.选填参数:")  
431 - .append(xt); 402 + if (ObjectUtil.isNotEmpty(xt)) {
  403 + stoolDesc.append(System.lineSeparator()).append("2.选填参数:").append(xt);
432 } 404 }
433 - if(ObjectUtil.isNotEmpty(slMap)){  
434 - stoolDesc  
435 - .append(System.lineSeparator())  
436 - .append(sl)  
437 - .append("**强制输出标准JSON对象**:").append(System.lineSeparator())  
438 - .append("示例:").append(JSONUtil.toJsonStr(slMap)); 405 + if (ObjectUtil.isNotEmpty(slMap)) {
  406 + stoolDesc.append(System.lineSeparator()).append(sl).append("**强制输出标准JSON对象**:")
  407 + .append(System.lineSeparator()).append("示例:").append(JSONUtil.toJsonStr(slMap));
439 } 408 }
440 - log.info("方法描述========================{}",stoolDesc); 409 +
  410 + log.info("方法描述========================{}", stoolDesc);
441 } catch (Exception e) { 411 } catch (Exception e) {
442 e.printStackTrace(); 412 e.printStackTrace();
443 log.error("Failed to parse parameter rules: {}", meta.getSMethodName(), e); 413 log.error("Failed to parse parameter rules: {}", meta.getSMethodName(), e);
444 - // 参数解析失败时,创建无参数的工具规格  
445 } 414 }
  415 +
  416 + // ====================== 【关键修复:强制添加两个参数】 ======================
  417 + if (meta.getIBizType() == 4 || meta.getIBizType() == 5) {
  418 + // 强制添加 operateType(必填)
  419 + builder.addParameter("operateType",
  420 + JsonSchemaProperty.STRING,
  421 + JsonSchemaProperty.description("操作类型:全部确认/合并确认/单行确认")
  422 + );
  423 + }
  424 + // ============================================================================
  425 +
446 builder.description(stoolDesc.toString()); 426 builder.description(stoolDesc.toString());
447 return builder.build(); 427 return builder.build();
448 } 428 }
@@ -628,14 +608,14 @@ public class DynamicToolProvider implements ToolProvider { @@ -628,14 +608,14 @@ public class DynamicToolProvider implements ToolProvider {
628 String input = StrUtil.replace(userMessage.text(),"用户输入:",StrUtil.EMPTY); 608 String input = StrUtil.replace(userMessage.text(),"用户输入:",StrUtil.EMPTY);
629 // {"0":"查询","1":"执行"} 查询不需要确认 609 // {"0":"查询","1":"执行"} 查询不需要确认
630 Boolean isConfirmed = isConfirmed(input) || input.contains("生成") || input.contains("确认"); 610 Boolean isConfirmed = isConfirmed(input) || input.contains("生成") || input.contains("确认");
631 - //判断是否生成数据  
632 - List<Map<String,Object>> sRowData = new ArrayList<>();  
633 - String sHandleType = "merge";  
634 - if(4== meta.getIBizType() && ObjectUtil.isNotEmpty(session.getCurrentRowData())){  
635 - Map<String,Object> sRowDataMap = UserChoseIntentParser.getSelectedRows( input, session.getCurrentRowData());  
636 - sRowData = (List<Map<String, Object>>) sRowDataMap.get("sRowData");  
637 - sHandleType = sRowDataMap.get("sHandleType").toString();  
638 - } 611 +// //判断是否生成数据
  612 +// List<Map<String,Object>> sRowData = new ArrayList<>();
  613 +// String sHandleType = "merge";
  614 +// if(4== meta.getIBizType() && ObjectUtil.isNotEmpty(session.getCurrentRowData())){
  615 +// Map<String,Object> sRowDataMap = UserChoseIntentParser.getSelectedRows( input, session.getCurrentRowData());
  616 +// sRowData = (List<Map<String, Object>>) sRowDataMap.get("sRowData");
  617 +// sHandleType = sRowDataMap.get("sHandleType").toString();
  618 +// }
639 //{"1":"存储过程","2":"SQL查询","3":"第三方API","4":"ERP未清","5":"ERP列表(报表)","6":"ERP单据","7":"其它","8":"自然语言TEXT2SQL"} 619 //{"1":"存储过程","2":"SQL查询","3":"第三方API","4":"ERP未清","5":"ERP列表(报表)","6":"ERP单据","7":"其它","8":"自然语言TEXT2SQL"}
640 if((isConfirmed && (4== meta.getIBizType() ||1== meta.getIBizType())) 620 if((isConfirmed && (4== meta.getIBizType() ||1== meta.getIBizType()))
641 || 2== meta.getIBizType() 621 || 2== meta.getIBizType()
@@ -658,12 +638,12 @@ public class DynamicToolProvider implements ToolProvider { @@ -658,12 +638,12 @@ public class DynamicToolProvider implements ToolProvider {
658 String askconfirmMsg =StrUtil.EMPTY; 638 String askconfirmMsg =StrUtil.EMPTY;
659 if(4== meta.getIBizType() || meta.getIBizType()==5){ 639 if(4== meta.getIBizType() || meta.getIBizType()==5){
660 askconfirmMsg = doGetFromData( meta,args,session); 640 askconfirmMsg = doGetFromData( meta,args,session);
661 - return executeWithConfirmation(askconfirmMsg,operableChatMemoryProvider.get(session.getUserId()), session, meta); 641 + return executeWithConfirmation(askconfirmMsg, session, meta);
662 }else{ 642 }else{
663 askconfirmMsg =getDefMessage(args,meta.getSControlName(),meta); 643 askconfirmMsg =getDefMessage(args,meta.getSControlName(),meta);
664 } 644 }
665 // 返回需要确认的结果 645 // 返回需要确认的结果
666 - return executeWithConfirmation(askconfirmMsg,operableChatMemoryProvider.get(session.getUserId()), session, meta); 646 + return executeWithConfirmation(askconfirmMsg, session, meta);
667 } 647 }
668 648
669 /*** 649 /***
@@ -955,6 +935,8 @@ public class DynamicToolProvider implements ToolProvider { @@ -955,6 +935,8 @@ public class DynamicToolProvider implements ToolProvider {
955 sReturn = executeToolAfter(meta, args,paramRuleData,session); 935 sReturn = executeToolAfter(meta, args,paramRuleData,session);
956 }catch (BusinessException e) { 936 }catch (BusinessException e) {
957 return e.getMessage(); 937 return e.getMessage();
  938 + }finally {
  939 + session.setSSystemPrompt(StrUtil.EMPTY);
958 } 940 }
959 if(meta.getIActionType()==1){ 941 if(meta.getIActionType()==1){
960 session.setBCleanMemory(true); 942 session.setBCleanMemory(true);
@@ -1197,7 +1179,10 @@ public class DynamicToolProvider implements ToolProvider { @@ -1197,7 +1179,10 @@ public class DynamicToolProvider implements ToolProvider {
1197 rMap.put("sSlaveId",uniqueValue); 1179 rMap.put("sSlaveId",uniqueValue);
1198 rMap.put("唯一",uniqueValue); 1180 rMap.put("唯一",uniqueValue);
1199 // 在行末添加隐藏数据的特殊标记(AI可以解析) 1181 // 在行末添加隐藏数据的特殊标记(AI可以解析)
1200 - markdown.append(" <!-- HIDDEN_DATA:").append(JSONUtil.toJsonStr(rMap)).append("-->"); 1182 + markdown.append(" <span style=\"display:none;\">HIDDEN_DATA:")
  1183 + .append(JSONUtil.toJsonStr(rMap))
  1184 + .append("</span>");
  1185 +// markdown.append(" <!-- HIDDEN_DATA:").append(JSONUtil.toJsonStr(rMap)).append("-->");
1201 markdown.append("\n"); 1186 markdown.append("\n");
1202 machineData.add(rMap); 1187 machineData.add(rMap);
1203 currentRowData.put(i + 1,recordData.get(i)); 1188 currentRowData.put(i + 1,recordData.get(i));
@@ -1212,9 +1197,50 @@ public class DynamicToolProvider implements ToolProvider { @@ -1212,9 +1197,50 @@ public class DynamicToolProvider implements ToolProvider {
1212 appendConfirmAll(markdown,meta.getSControlName()); 1197 appendConfirmAll(markdown,meta.getSControlName());
1213 } 1198 }
1214 session.setCurrentRowData(currentRowData); 1199 session.setCurrentRowData(currentRowData);
  1200 + // ===================== 加入这一行 =====================
  1201 + String sSystemPrompt = buildDynamicSystemPrompt(session);
  1202 + session.setSSystemPrompt(sSystemPrompt);
  1203 + // ======================================================
1215 return markdown.toString(); 1204 return markdown.toString();
1216 } 1205 }
1217 1206
  1207 + public String buildDynamicSystemPrompt(UserSceneSession session) {
  1208 +
  1209 + // 1. 强制指令 + 固定方法编号
  1210 + String promptHead = """
  1211 + 【极强约束·必须执行】
  1212 + 1. 禁止说话!禁止解释!
  1213 + 2. 必须调用工具,固定方法编号:MethodNo = queryUnprocessQuote
  1214 + 3. 只输出JSON,无任何其他内容
  1215 +
  1216 + 以下是【行号 → sSlaveId】对应数据:
  1217 + """;
  1218 +
  1219 + // 2. 动态拼接行号 + 动态 sSlaveId
  1220 + Map<Integer, Map<String, Object>> rowDataMap = session.getCurrentRowData();
  1221 + StringBuilder rowDataSb = new StringBuilder();
  1222 + if (ObjectUtil.isNotEmpty(rowDataMap)) {
  1223 + for (Map.Entry<Integer, Map<String, Object>> entry : rowDataMap.entrySet()) {
  1224 + int rowNum = entry.getKey();
  1225 + String sSlaveId = StrUtil.toString(entry.getValue().get("sSlaveId"));
  1226 + rowDataSb.append("第").append(rowNum).append("行 → ").append(sSlaveId).append("\n");
  1227 + }
  1228 + }
  1229 +
  1230 + // 3. 输出格式约束
  1231 + String promptFoot = """
  1232 + 【输出JSON格式·严格遵守】
  1233 + 根据用户选择的行,自动填写 sSlaveId,多行用英文逗号拼接
  1234 + {
  1235 + "operateType": "全部确认/合并确认/单行确认",
  1236 + "sSlaveId": "填写对应的ID,多个用逗号分隔"
  1237 + }
  1238 + """;
  1239 +
  1240 + // 拼接最终返回
  1241 + return promptHead + rowDataSb + promptFoot;
  1242 + }
  1243 +
1218 // 辅助方法:根据中文名查找字段名(通过映射关系转换) 1244 // 辅助方法:根据中文名查找字段名(通过映射关系转换)
1219 private List<Map<String, Object>> findFieldNameByChinese(List<Map<String, Object>> sAIshowfieldShow,List<Map<String, Object>> rows){ 1245 private List<Map<String, Object>> findFieldNameByChinese(List<Map<String, Object>> sAIshowfieldShow,List<Map<String, Object>> rows){
1220 //获取映射关系 1246 //获取映射关系
@@ -1362,7 +1388,7 @@ public class DynamicToolProvider implements ToolProvider { @@ -1362,7 +1388,7 @@ public class DynamicToolProvider implements ToolProvider {
1362 /** 1388 /**
1363 * 执行方法后需要用户确认的扩展版本 1389 * 执行方法后需要用户确认的扩展版本
1364 */ 1390 */
1365 - private String executeWithConfirmation(String initialResult,ChatMemory chatMemory, UserSceneSession session,ToolMeta meta) { 1391 + private String executeWithConfirmation(String initialResult, UserSceneSession session,ToolMeta meta) {
1366 1392
1367 // 第一步:执行原始操作,返回初步结果 1393 // 第一步:执行原始操作,返回初步结果
1368 Map<String, Object> step1Result = new HashMap<>(); 1394 Map<String, Object> step1Result = new HashMap<>();
@@ -1377,6 +1403,7 @@ public class DynamicToolProvider implements ToolProvider { @@ -1377,6 +1403,7 @@ public class DynamicToolProvider implements ToolProvider {
1377 // session.setSFunPrompts(userMessage); 1403 // session.setSFunPrompts(userMessage);
1378 // 6. 返回确认请求 1404 // 6. 返回确认请求
1379 // return ToolExecutionResultMessage.from(request,userMessage); 1405 // return ToolExecutionResultMessage.from(request,userMessage);
  1406 +// ToolExecutionResultMessage.from(request, text);
1380 return userMessage; 1407 return userMessage;
1381 } 1408 }
1382 1409
src/main/resources/templates/chat.html
@@ -462,11 +462,11 @@ @@ -462,11 +462,11 @@
462 <script> 462 <script>
463 let sessionId =""; 463 let sessionId ="";
464 let userid= "17522967560005776104370282597000"; 464 let userid= "17522967560005776104370282597000";
465 - let username= "钱豹"; 465 + let username= "qianb";
466 let brandsid= "1111111111"; 466 let brandsid= "1111111111";
467 let subsidiaryid= "1111111111"; 467 let subsidiaryid= "1111111111";
468 let usertype= "sysadmin"; 468 let usertype= "sysadmin";
469 - let authorization="CE444885A9BCFDDE1FD793F8A0931301E9D7DE6CEDD9DE4B83ECE2219C7829A8F3419238942A93E9AD666629E18D159AF7FE144A6407DE745BA0AEC8B235FC1D4CAE6F9AC893752209A98011A981375391D4466816B7D3D1AF306E28B989121C538155B7ADAEE71E899235DC1122F426"; 469 + let authorization="CE444885A9BCFDDE1FD793F8A0931301E9D7DE6CEDD9DE4B83ECE2219C7829A8F3419238942A93E9AD666629E18D159AF7FE144A6407DE745BA0AEC8B235FC1DDD971C9BA1323777865C7C7C720A0F7D9C9A98822AD3F0E3100F8DBBB5963377538155B7ADAEE71E899235DC1122F426";
470 let hrefLock = window.location.origin+"/xlyAi"; 470 let hrefLock = window.location.origin+"/xlyAi";
471 471
472 const CONFIG = { 472 const CONFIG = {