Commit 97ec2c2e3e8eafaf0b1ee4e7d6f280455b5c7066

Authored by qianbao
1 parent 25c2a140

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

src/main/java/com/xly/agent/AgentSystemPrompt.java
@@ -2,21 +2,22 @@ package com.xly.agent; @@ -2,21 +2,22 @@ package com.xly.agent;
2 2
3 public class AgentSystemPrompt { 3 public class AgentSystemPrompt {
4 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 - public static final String sSystemPrompt = "你是一个专业的智能助手,请根据以下规则精准匹配并调用工具:\n" +  
13 - "【核心决策逻辑】\n" +  
14 - "1. 单一工具场景:当系统仅提供一个可用工具时,除非用户是在进行无意义的闲聊(如打招呼),否则必须无条件调用该工具,禁止向用户索要额外信息或进行解释;\n" +  
15 - "2. 多工具场景:当系统提供多个工具时,必须先精准识别用户的核心业务意图,然后匹配唯一对应的工具进行调用;\n" +  
16 - "\n" +  
17 - "【参数提取与调用规范】\n" +  
18 - "3. 触发条件:匹配工具时,仅关注工具描述中“当用户”和“时,必须调用本工具”之间的语义关联,无需过度纠结参数的详细描述;\n" +  
19 - "4. 缺省处理:调用工具前,不需要询问用户提供缺失的参数。如果某个必填参数在用户输入中找不到,请直接省略该字段(不要填入\"\"、null或0);\n" +  
20 - "5. 格式要求:提取参数时严格按标注类型赋值,数字不带引号,字符串需去除首尾空格;\n" +  
21 - "6. 绝对禁止:在任何情况下,禁止向用户反问以确认参数或意图,你的输出只能是最终回复或合法的工具调用请求。";  
22 -} 5 +// public static final String sSystemPrompt = "你是一个专业的智能助手,请根据以下规则精准匹配并调用工具:\n" +
  6 +// "【核心决策逻辑】\n" +
  7 +// "1. 单一工具绝对调用:当系统仅提供一个可用工具时,必须无条件调用该工具。即使参数不全,也必须发起调用,严禁返回向用户索要信息或者不调用工具;\n" +
  8 +// "2. 多工具意图匹配:当系统提供多个工具时,必须先精准识别用户的核心业务意图,然后匹配唯一对应的工具进行调用;\n" +
  9 +// "\n" +
  10 +// "【参数提取与调用规范】\n" +
  11 +// "3. 触发条件:匹配工具时,仅关注工具描述中“当用户”和“时,必须调用本工具”之间的语义关联;\n" +
  12 +// "4. 参数补全与缺省处理:\n" +
  13 +// " - 如果当前是全新的独立请求,且必填参数缺失,请直接省略该字段发起调用(不要反问用户)。\n" +
  14 +// "5. 格式要求:提取参数时严格按标注类型赋值,数字不带引号,字符串需去除首尾空格;\n" +
  15 +// "6. 绝对禁止:在任何情况下,禁止向用户反问以确认参数或意图,你的输出只能是最终回复或合法的工具调用请求。";
  16 +
  17 + public static final String sSystemPrompt = "你是一个无情的参数提取器,不是聊天助手。\n" +
  18 + "【绝对指令】\n" +
  19 + "1. 你的唯一任务是提取用户输入中的参数,并立即调用工具。\n" +
  20 + "2. 严禁与用户进行任何对话!严禁输出“您好”、“请提供”、“需要补充”等任何纯文本回复!\n" +
  21 + "3. 无论用户输入多么简短(例如只输入了公司名),或者参数缺失多么严重,你都必须直接调用工具,将缺失的字段留空或填0。\n" +
  22 + "4. 不要试图去引导用户,引导用户是你的后端程序要做的事,你只需要负责把提取到的参数传出去。";
  23 +}
23 \ No newline at end of file 24 \ No newline at end of file
src/main/java/com/xly/service/XlyErpService.java
@@ -30,6 +30,7 @@ import dev.langchain4j.agent.tool.ToolSpecification; @@ -30,6 +30,7 @@ import dev.langchain4j.agent.tool.ToolSpecification;
30 import dev.langchain4j.data.message.AiMessage; 30 import dev.langchain4j.data.message.AiMessage;
31 import dev.langchain4j.data.message.ChatMessage; 31 import dev.langchain4j.data.message.ChatMessage;
32 32
  33 +import dev.langchain4j.memory.ChatMemory;
33 import dev.langchain4j.model.ollama.OllamaChatModel; 34 import dev.langchain4j.model.ollama.OllamaChatModel;
34 import dev.langchain4j.service.AiServices; 35 import dev.langchain4j.service.AiServices;
35 import dev.langchain4j.service.Result; 36 import dev.langchain4j.service.Result;
@@ -864,26 +865,6 @@ public class XlyErpService { @@ -864,26 +865,6 @@ public class XlyErpService {
864 } 865 }
865 866
866 867
867 -  
868 - /***  
869 - * @Author 钱豹  
870 - * @Date 11:22 2026/1/31  
871 - * @Param  
872 - * @return  
873 - * @Description 动态参数补齐处理  
874 - **/  
875 - private String dotoolExecutionRequests(AiMessage aiMessage){  
876 - String textTs = aiMessage.text();  
877 - if(aiMessage.hasToolExecutionRequests()){  
878 - List<ToolExecutionRequest> toolExecutionRequests = aiMessage.toolExecutionRequests();  
879 - toolExecutionRequests.forEach(toolRequests->{  
880 - String arguments = toolRequests.arguments();  
881 - log.info(arguments);  
882 - });  
883 - }  
884 - return textTs;  
885 - }  
886 -  
887 /*** 868 /***
888 * 存入全部场景 869 * 存入全部场景
889 * @Author 钱豹 870 * @Author 钱豹
@@ -974,9 +955,6 @@ public class XlyErpService { @@ -974,9 +955,6 @@ public class XlyErpService {
974 .chatModel(chatModel) 955 .chatModel(chatModel)
975 .chatMemoryProvider(operableChatMemoryProvider) 956 .chatMemoryProvider(operableChatMemoryProvider)
976 .tools(executors,immediateReturnToolNames) 957 .tools(executors,immediateReturnToolNames)
977 -// .toolProvider(dynamicToolProvider)  
978 -// .returnBehavior(ReturnBehavior.IMMEDIATE)  
979 -// .toolChoice(ChatCompletionToolChoice.ofRequired())  
980 .build(); 958 .build();
981 UserSceneSessionService.ERP_AGENT_CACHE.put(userId, aiAgent); 959 UserSceneSessionService.ERP_AGENT_CACHE.put(userId, aiAgent);
982 // log.info("用户{}Agent构建完成,已选场景:{},场景ID{}", userId, session.isSceneSelected() ? session.getCurrentScene().getSSceneName() : "未选(全场景匹配)", dynamicToolProvider.sSceneIdMap.get(userId)); 960 // log.info("用户{}Agent构建完成,已选场景:{},场景ID{}", userId, session.isSceneSelected() ? session.getCurrentScene().getSSceneName() : "未选(全场景匹配)", dynamicToolProvider.sSceneIdMap.get(userId));
@@ -995,8 +973,10 @@ public class XlyErpService { @@ -995,8 +973,10 @@ public class XlyErpService {
995 executors.put(one.getToolSpecification(),one.getToolExecutor()); 973 executors.put(one.getToolSpecification(),one.getToolExecutor());
996 }); 974 });
997 } 975 }
  976 + ChatMemory chatMemory = operableChatMemoryProvider.get(session.getUserId());
998 ErpAiAgent aiAgent = AiServices.builder(ErpAiAgent.class) 977 ErpAiAgent aiAgent = AiServices.builder(ErpAiAgent.class)
999 .chatModel(chatModel) 978 .chatModel(chatModel)
  979 + .chatMemory(chatMemory)
1000 .chatMemoryProvider(operableChatMemoryProvider) 980 .chatMemoryProvider(operableChatMemoryProvider)
1001 .tools(executors,immediateReturnToolNames) 981 .tools(executors,immediateReturnToolNames)
1002 .build(); 982 .build();
src/main/java/com/xly/tool/DynamicToolProvider.java
1 package com.xly.tool; 1 package com.xly.tool;
2 2
3 3
  4 +import cn.hutool.core.date.DateUtil;
4 import cn.hutool.core.util.BooleanUtil; 5 import cn.hutool.core.util.BooleanUtil;
5 import cn.hutool.core.util.ObjectUtil; 6 import cn.hutool.core.util.ObjectUtil;
6 import cn.hutool.core.util.StrUtil; 7 import cn.hutool.core.util.StrUtil;
@@ -23,11 +24,8 @@ import com.xly.service.UserSceneSessionService; @@ -23,11 +24,8 @@ import com.xly.service.UserSceneSessionService;
23 import com.xly.util.*; 24 import com.xly.util.*;
24 import dev.langchain4j.agent.tool.*; 25 import dev.langchain4j.agent.tool.*;
25 26
26 -import dev.langchain4j.data.message.ChatMessage;  
27 -import dev.langchain4j.data.message.ChatMessageType;  
28 -import dev.langchain4j.data.message.ToolExecutionResultMessage; 27 +import dev.langchain4j.data.message.*;
29 28
30 -import dev.langchain4j.data.message.UserMessage;  
31 import dev.langchain4j.memory.ChatMemory; 29 import dev.langchain4j.memory.ChatMemory;
32 30
33 import dev.langchain4j.model.chat.request.json.JsonObjectSchema; 31 import dev.langchain4j.model.chat.request.json.JsonObjectSchema;
@@ -208,6 +206,7 @@ public class DynamicToolProvider implements ToolProvider { @@ -208,6 +206,7 @@ public class DynamicToolProvider implements ToolProvider {
208 log.error("返回信息",e); 206 log.error("返回信息",e);
209 String askMsg = e.getMessage(); 207 String askMsg = e.getMessage();
210 session.setSFunPrompts(askMsg); 208 session.setSFunPrompts(askMsg);
  209 + addSystemMessage(session, askMsg);
211 // 需要提问用户 → 抛异常停止循环 210 // 需要提问用户 → 抛异常停止循环
212 throw new RuntimeException(askMsg); 211 throw new RuntimeException(askMsg);
213 } 212 }
@@ -221,6 +220,7 @@ public class DynamicToolProvider implements ToolProvider { @@ -221,6 +220,7 @@ public class DynamicToolProvider implements ToolProvider {
221 // 4.1 参数缺失,生成“提问”消息,直接返给客户 220 // 4.1 参数缺失,生成“提问”消息,直接返给客户
222 String askMsg = buildAskUserMessage(meta, missing,args); 221 String askMsg = buildAskUserMessage(meta, missing,args);
223 session.setSFunPrompts(askMsg); 222 session.setSFunPrompts(askMsg);
  223 + addSystemMessage(session, askMsg);
224 return String.valueOf(successResult(toolExecutionRequest, askMsg)); 224 return String.valueOf(successResult(toolExecutionRequest, askMsg));
225 } 225 }
226 // ====================== 返回时带终止指令 ====================== 226 // ====================== 返回时带终止指令 ======================
@@ -311,10 +311,16 @@ public class DynamicToolProvider implements ToolProvider { @@ -311,10 +311,16 @@ public class DynamicToolProvider implements ToolProvider {
311 【工具调用规则】 311 【工具调用规则】
312 1. 每个自定义方法最多只能调用 1 次 312 1. 每个自定义方法最多只能调用 1 次
313 2. 只要方法返回结果,必须停止调用 313 2. 只要方法返回结果,必须停止调用
314 - 3. 禁止重复调用同一个方法  
315 - 4. 禁止无意义循环调用  
316 - 5. 当用户输入包含【数据确认】、确认数据、确认、第*条数据确认时,必须调用本工具 314 + 3. 当用户输入包含【数据确认】、确认数据、确认、第*条数据确认时,必须调用本工具
317 """; 315 """;
  316 +// String forceToolPrompt = """
  317 +// 【工具调用规则】
  318 +// 1. 每个自定义方法最多只能调用 1 次
  319 +// 2. 只要方法返回结果,必须停止调用
  320 +// 3. 禁止重复调用同一个方法
  321 +// 4. 禁止无意义循环调用
  322 +// 5. 当用户输入包含【数据确认】、确认数据、确认、第*条数据确认时,必须调用本工具
  323 +// """;
318 stoolDesc.append(forceToolPrompt); 324 stoolDesc.append(forceToolPrompt);
319 if (ObjectUtil.isNotEmpty(meta.getStoolDesc())) { 325 if (ObjectUtil.isNotEmpty(meta.getStoolDesc())) {
320 stoolDesc.append("MethodNo:").append(meta.getSMethodNo()) 326 stoolDesc.append("MethodNo:").append(meta.getSMethodNo())
@@ -370,6 +376,15 @@ public class DynamicToolProvider implements ToolProvider { @@ -370,6 +376,15 @@ public class DynamicToolProvider implements ToolProvider {
370 schemaBuilder.addEnumProperty(paramDesc, constList, paramDesc); 376 schemaBuilder.addEnumProperty(paramDesc, constList, paramDesc);
371 } 377 }
372 break; 378 break;
  379 + case "date":
  380 + case "datetime":
  381 + // 核心技巧:在描述中强制要求 AI 进行格式转换
  382 + String dateDesc = paramDesc + "。【重要】请将用户输入的任意日期(如'明天'、'2026/5/1'、'下周三')自动转换为 'yyyy-MM-dd' 格式的字符串,当前日期:"+ DateUtil.now() +"。";
  383 + schemaBuilder.addStringProperty(paramDesc, dateDesc);
  384 + if (bEmpty) {
  385 + requiredParams.add(paramDesc);
  386 + }
  387 + break;
373 default: 388 default:
374 schemaBuilder.addStringProperty(paramDesc, paramDesc); 389 schemaBuilder.addStringProperty(paramDesc, paramDesc);
375 break; 390 break;
@@ -381,13 +396,17 @@ public class DynamicToolProvider implements ToolProvider { @@ -381,13 +396,17 @@ public class DynamicToolProvider implements ToolProvider {
381 } catch (Exception e) { 396 } catch (Exception e) {
382 e.printStackTrace(); 397 e.printStackTrace();
383 } 398 }
384 - 399 + List<String> requiredParamsNew = new ArrayList<>();
  400 + if(requiredParams!=null && requiredParams.size()>0){
  401 + requiredParamsNew.add(requiredParams.get(0));
  402 + }
385 if (meta.getIBizType() == 4 || meta.getIBizType() == 5) { 403 if (meta.getIBizType() == 4 || meta.getIBizType() == 5) {
386 schemaBuilder.addStringProperty("operateType", "操作类型:全部确认/合并确认/单行确认"); 404 schemaBuilder.addStringProperty("operateType", "操作类型:全部确认/合并确认/单行确认");
387 requiredParams.add("operateType"); 405 requiredParams.add("operateType");
  406 + requiredParamsNew.add("operateType");
388 } 407 }
389 if (!requiredParams.isEmpty()) { 408 if (!requiredParams.isEmpty()) {
390 - schemaBuilder.required(requiredParams); 409 + schemaBuilder.required(requiredParamsNew);
391 } 410 }
392 JsonObjectSchema parameters = schemaBuilder.build(); 411 JsonObjectSchema parameters = schemaBuilder.build();
393 return builder 412 return builder
@@ -473,6 +492,7 @@ public class DynamicToolProvider implements ToolProvider { @@ -473,6 +492,7 @@ public class DynamicToolProvider implements ToolProvider {
473 List<String> missing = checkRequiredParams(args, paramRuleDataCheck); 492 List<String> missing = checkRequiredParams(args, paramRuleDataCheck);
474 if (!missing.isEmpty()) { 493 if (!missing.isEmpty()) {
475 String askMsg = buildAskUserMessage(meta, missing,args); 494 String askMsg = buildAskUserMessage(meta, missing,args);
  495 + addSystemMessage(session, askMsg);
476 return askMsg; 496 return askMsg;
477 } 497 }
478 498
@@ -503,6 +523,7 @@ public class DynamicToolProvider implements ToolProvider { @@ -503,6 +523,7 @@ public class DynamicToolProvider implements ToolProvider {
503 List<ParamRule> paramRuleDataMiss = getMissParamRuleAfter(args, paramRuleData); 523 List<ParamRule> paramRuleDataMiss = getMissParamRuleAfter(args, paramRuleData);
504 String sSystemPrompt = buildMissParamPrompt(session,paramRuleDataMiss); 524 String sSystemPrompt = buildMissParamPrompt(session,paramRuleDataMiss);
505 session.setSSystemPrompt(sSystemPrompt); 525 session.setSSystemPrompt(sSystemPrompt);
  526 + addSystemMessage(session, askMsg);
506 return askMsg; 527 return askMsg;
507 } 528 }
508 return executeTool(meta, args, paramRuleData, session.getUserId(), session); 529 return executeTool(meta, args, paramRuleData, session.getUserId(), session);
@@ -801,12 +822,18 @@ public class DynamicToolProvider implements ToolProvider { @@ -801,12 +822,18 @@ public class DynamicToolProvider implements ToolProvider {
801 if(ObjectUtil.isEmpty(sBizContent) && iBizType == 4){ 822 if(ObjectUtil.isEmpty(sBizContent) && iBizType == 4){
802 sBizContent ="Sp_Ai_AddCommonAfterNew"; 823 sBizContent ="Sp_Ai_AddCommonAfterNew";
803 if(ObjectUtil.isEmpty(args.get("sSlaveId"))){ 824 if(ObjectUtil.isEmpty(args.get("sSlaveId"))){
804 - throw new BusinessException(-1,"请选择操作数据"); 825 + //插入AI记忆
  826 + String sMsg = "请选择操作数据";
  827 + addSystemMessage(session, sMsg);
  828 + throw new BusinessException(-1,sMsg);
805 } 829 }
806 List<Map<String,Object>> sRowData = doGetFromDataWq( meta, args, session); 830 List<Map<String,Object>> sRowData = doGetFromDataWq( meta, args, session);
807 if(ObjectUtil.isEmpty(sRowData)){ 831 if(ObjectUtil.isEmpty(sRowData)){
808 - session.setSFunPrompts("选择的数据ID:"+args.get("sSlaveId")+"不存在,请重新选择。");  
809 - throw new BusinessException(-1,"选择的数据ID:"+args.get("sSlaveId")+"不存在,请重新选择。"); 832 + String sMsg = "选择的数据ID:"+args.get("sSlaveId")+"不存在,请重新选择。";
  833 + session.setSFunPrompts(sMsg);
  834 + //插入AI记忆
  835 + addSystemMessage(session, sMsg);
  836 + throw new BusinessException(-1,sMsg);
810 } 837 }
811 Map<String,Object> dataOne = DeepCopyUtils.deepCopy(args); 838 Map<String,Object> dataOne = DeepCopyUtils.deepCopy(args);
812 dataOne.remove("sSlaveId"); 839 dataOne.remove("sSlaveId");
@@ -859,6 +886,7 @@ public class DynamicToolProvider implements ToolProvider { @@ -859,6 +886,7 @@ public class DynamicToolProvider implements ToolProvider {
859 } else { 886 } else {
860 String sMsgText = "未找到对应的数据"; 887 String sMsgText = "未找到对应的数据";
861 session.setSFunPrompts(sMsgText); 888 session.setSFunPrompts(sMsgText);
  889 + addSystemMessage(session, sMsgText);
862 throw new BusinessException(-1,sMsgText); 890 throw new BusinessException(-1,sMsgText);
863 } 891 }
864 } 892 }
@@ -869,6 +897,12 @@ public class DynamicToolProvider implements ToolProvider { @@ -869,6 +897,12 @@ public class DynamicToolProvider implements ToolProvider {
869 return "操作成功"; 897 return "操作成功";
870 } 898 }
871 899
  900 + private void addSystemMessage(UserSceneSession session,String sMsg){
  901 +// if(ObjectUtil.isNotEmpty(operableChatMemoryProvider) && ObjectUtil.isNotEmpty(operableChatMemoryProvider.get(session.getUserId()))){
  902 +// operableChatMemoryProvider.get(session.getUserId()).add(SystemMessage.from("上一次工具调用失败。原因:" + sMsg + "。请根据用户意图修正参数,并再次调用工具。"));
  903 +// }
  904 + }
  905 +
872 private List<Map<String,Object>> doGetFromDataWq(ToolMeta meta, Map<String, Object> args,UserSceneSession session){ 906 private List<Map<String,Object>> doGetFromDataWq(ToolMeta meta, Map<String, Object> args,UserSceneSession session){
873 String sUrl = meta.getSendUrl(); 907 String sUrl = meta.getSendUrl();
874 Map<String,Object> sBody = new HashMap<>(); 908 Map<String,Object> sBody = new HashMap<>();