diff --git a/src/main/java/com/xly/agent/AgentSystemPrompt.java b/src/main/java/com/xly/agent/AgentSystemPrompt.java index f9eb507..de3c099 100644 --- a/src/main/java/com/xly/agent/AgentSystemPrompt.java +++ b/src/main/java/com/xly/agent/AgentSystemPrompt.java @@ -2,21 +2,22 @@ package com.xly.agent; public class AgentSystemPrompt { -// public static final String sSystemPrompt= "1. 方法匹配:先精准拆解用户查询的核心业务意图,再自动匹配唯一符合用户问题的工具方法(MethodNo),禁止自创,规则如下;\n" + -// " 1.1 匹配方法时,无需考虑工具描述(@TOOL)中 1.必填参数,2.选填参数,示例,parameters内容 四个部分的内容;\n" + -// " 1.2 匹配方法时,只关注工具描述(@TOOL)中 “当用户” 和 “时,必须调用本工具”两个短语之间的内容;\n" + -// " 1.3 调用工具前,不需要询问用户提供缺失的参数\n" + -// " 2. 参数提取:提取该工具的全部参数,与描述完全一致,严格按标注类型赋值,规则如下:\n" + -// " 2.1 数字无引号,为空时禁止赋值0;\n" + -// " 2.2 如果有空格需要去掉空格后再提取。"; - public static final String sSystemPrompt = "你是一个专业的智能助手,请根据以下规则精准匹配并调用工具:\n" + - "【核心决策逻辑】\n" + - "1. 单一工具场景:当系统仅提供一个可用工具时,除非用户是在进行无意义的闲聊(如打招呼),否则必须无条件调用该工具,禁止向用户索要额外信息或进行解释;\n" + - "2. 多工具场景:当系统提供多个工具时,必须先精准识别用户的核心业务意图,然后匹配唯一对应的工具进行调用;\n" + - "\n" + - "【参数提取与调用规范】\n" + - "3. 触发条件:匹配工具时,仅关注工具描述中“当用户”和“时,必须调用本工具”之间的语义关联,无需过度纠结参数的详细描述;\n" + - "4. 缺省处理:调用工具前,不需要询问用户提供缺失的参数。如果某个必填参数在用户输入中找不到,请直接省略该字段(不要填入\"\"、null或0);\n" + - "5. 格式要求:提取参数时严格按标注类型赋值,数字不带引号,字符串需去除首尾空格;\n" + - "6. 绝对禁止:在任何情况下,禁止向用户反问以确认参数或意图,你的输出只能是最终回复或合法的工具调用请求。"; -} +// public static final String sSystemPrompt = "你是一个专业的智能助手,请根据以下规则精准匹配并调用工具:\n" + +// "【核心决策逻辑】\n" + +// "1. 单一工具绝对调用:当系统仅提供一个可用工具时,必须无条件调用该工具。即使参数不全,也必须发起调用,严禁返回向用户索要信息或者不调用工具;\n" + +// "2. 多工具意图匹配:当系统提供多个工具时,必须先精准识别用户的核心业务意图,然后匹配唯一对应的工具进行调用;\n" + +// "\n" + +// "【参数提取与调用规范】\n" + +// "3. 触发条件:匹配工具时,仅关注工具描述中“当用户”和“时,必须调用本工具”之间的语义关联;\n" + +// "4. 参数补全与缺省处理:\n" + +// " - 如果当前是全新的独立请求,且必填参数缺失,请直接省略该字段发起调用(不要反问用户)。\n" + +// "5. 格式要求:提取参数时严格按标注类型赋值,数字不带引号,字符串需去除首尾空格;\n" + +// "6. 绝对禁止:在任何情况下,禁止向用户反问以确认参数或意图,你的输出只能是最终回复或合法的工具调用请求。"; + + public static final String sSystemPrompt = "你是一个无情的参数提取器,不是聊天助手。\n" + + "【绝对指令】\n" + + "1. 你的唯一任务是提取用户输入中的参数,并立即调用工具。\n" + + "2. 严禁与用户进行任何对话!严禁输出“您好”、“请提供”、“需要补充”等任何纯文本回复!\n" + + "3. 无论用户输入多么简短(例如只输入了公司名),或者参数缺失多么严重,你都必须直接调用工具,将缺失的字段留空或填0。\n" + + "4. 不要试图去引导用户,引导用户是你的后端程序要做的事,你只需要负责把提取到的参数传出去。"; +} \ No newline at end of file diff --git a/src/main/java/com/xly/service/XlyErpService.java b/src/main/java/com/xly/service/XlyErpService.java index b6afaa2..aa512e9 100644 --- a/src/main/java/com/xly/service/XlyErpService.java +++ b/src/main/java/com/xly/service/XlyErpService.java @@ -30,6 +30,7 @@ import dev.langchain4j.agent.tool.ToolSpecification; import dev.langchain4j.data.message.AiMessage; import dev.langchain4j.data.message.ChatMessage; +import dev.langchain4j.memory.ChatMemory; import dev.langchain4j.model.ollama.OllamaChatModel; import dev.langchain4j.service.AiServices; import dev.langchain4j.service.Result; @@ -864,26 +865,6 @@ public class XlyErpService { } - - /*** - * @Author 钱豹 - * @Date 11:22 2026/1/31 - * @Param - * @return - * @Description 动态参数补齐处理 - **/ - private String dotoolExecutionRequests(AiMessage aiMessage){ - String textTs = aiMessage.text(); - if(aiMessage.hasToolExecutionRequests()){ - List toolExecutionRequests = aiMessage.toolExecutionRequests(); - toolExecutionRequests.forEach(toolRequests->{ - String arguments = toolRequests.arguments(); - log.info(arguments); - }); - } - return textTs; - } - /*** * 存入全部场景 * @Author 钱豹 @@ -974,9 +955,6 @@ public class XlyErpService { .chatModel(chatModel) .chatMemoryProvider(operableChatMemoryProvider) .tools(executors,immediateReturnToolNames) -// .toolProvider(dynamicToolProvider) -// .returnBehavior(ReturnBehavior.IMMEDIATE) -// .toolChoice(ChatCompletionToolChoice.ofRequired()) .build(); UserSceneSessionService.ERP_AGENT_CACHE.put(userId, aiAgent); // log.info("用户{}Agent构建完成,已选场景:{},场景ID{}", userId, session.isSceneSelected() ? session.getCurrentScene().getSSceneName() : "未选(全场景匹配)", dynamicToolProvider.sSceneIdMap.get(userId)); @@ -995,8 +973,10 @@ public class XlyErpService { executors.put(one.getToolSpecification(),one.getToolExecutor()); }); } + ChatMemory chatMemory = operableChatMemoryProvider.get(session.getUserId()); ErpAiAgent aiAgent = AiServices.builder(ErpAiAgent.class) .chatModel(chatModel) + .chatMemory(chatMemory) .chatMemoryProvider(operableChatMemoryProvider) .tools(executors,immediateReturnToolNames) .build(); diff --git a/src/main/java/com/xly/tool/DynamicToolProvider.java b/src/main/java/com/xly/tool/DynamicToolProvider.java index ca0e254..7f1e8be 100644 --- a/src/main/java/com/xly/tool/DynamicToolProvider.java +++ b/src/main/java/com/xly/tool/DynamicToolProvider.java @@ -1,6 +1,7 @@ package com.xly.tool; +import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; @@ -23,11 +24,8 @@ import com.xly.service.UserSceneSessionService; import com.xly.util.*; import dev.langchain4j.agent.tool.*; -import dev.langchain4j.data.message.ChatMessage; -import dev.langchain4j.data.message.ChatMessageType; -import dev.langchain4j.data.message.ToolExecutionResultMessage; +import dev.langchain4j.data.message.*; -import dev.langchain4j.data.message.UserMessage; import dev.langchain4j.memory.ChatMemory; import dev.langchain4j.model.chat.request.json.JsonObjectSchema; @@ -208,6 +206,7 @@ public class DynamicToolProvider implements ToolProvider { log.error("返回信息",e); String askMsg = e.getMessage(); session.setSFunPrompts(askMsg); + addSystemMessage(session, askMsg); // 需要提问用户 → 抛异常停止循环 throw new RuntimeException(askMsg); } @@ -221,6 +220,7 @@ public class DynamicToolProvider implements ToolProvider { // 4.1 参数缺失,生成“提问”消息,直接返给客户 String askMsg = buildAskUserMessage(meta, missing,args); session.setSFunPrompts(askMsg); + addSystemMessage(session, askMsg); return String.valueOf(successResult(toolExecutionRequest, askMsg)); } // ====================== 返回时带终止指令 ====================== @@ -311,10 +311,16 @@ public class DynamicToolProvider implements ToolProvider { 【工具调用规则】 1. 每个自定义方法最多只能调用 1 次 2. 只要方法返回结果,必须停止调用 - 3. 禁止重复调用同一个方法 - 4. 禁止无意义循环调用 - 5. 当用户输入包含【数据确认】、确认数据、确认、第*条数据确认时,必须调用本工具 + 3. 当用户输入包含【数据确认】、确认数据、确认、第*条数据确认时,必须调用本工具 """; +// String forceToolPrompt = """ +// 【工具调用规则】 +// 1. 每个自定义方法最多只能调用 1 次 +// 2. 只要方法返回结果,必须停止调用 +// 3. 禁止重复调用同一个方法 +// 4. 禁止无意义循环调用 +// 5. 当用户输入包含【数据确认】、确认数据、确认、第*条数据确认时,必须调用本工具 +// """; stoolDesc.append(forceToolPrompt); if (ObjectUtil.isNotEmpty(meta.getStoolDesc())) { stoolDesc.append("MethodNo:").append(meta.getSMethodNo()) @@ -370,6 +376,15 @@ public class DynamicToolProvider implements ToolProvider { schemaBuilder.addEnumProperty(paramDesc, constList, paramDesc); } break; + case "date": + case "datetime": + // 核心技巧:在描述中强制要求 AI 进行格式转换 + String dateDesc = paramDesc + "。【重要】请将用户输入的任意日期(如'明天'、'2026/5/1'、'下周三')自动转换为 'yyyy-MM-dd' 格式的字符串,当前日期:"+ DateUtil.now() +"。"; + schemaBuilder.addStringProperty(paramDesc, dateDesc); + if (bEmpty) { + requiredParams.add(paramDesc); + } + break; default: schemaBuilder.addStringProperty(paramDesc, paramDesc); break; @@ -381,13 +396,17 @@ public class DynamicToolProvider implements ToolProvider { } catch (Exception e) { e.printStackTrace(); } - + List requiredParamsNew = new ArrayList<>(); + if(requiredParams!=null && requiredParams.size()>0){ + requiredParamsNew.add(requiredParams.get(0)); + } if (meta.getIBizType() == 4 || meta.getIBizType() == 5) { schemaBuilder.addStringProperty("operateType", "操作类型:全部确认/合并确认/单行确认"); requiredParams.add("operateType"); + requiredParamsNew.add("operateType"); } if (!requiredParams.isEmpty()) { - schemaBuilder.required(requiredParams); + schemaBuilder.required(requiredParamsNew); } JsonObjectSchema parameters = schemaBuilder.build(); return builder @@ -473,6 +492,7 @@ public class DynamicToolProvider implements ToolProvider { List missing = checkRequiredParams(args, paramRuleDataCheck); if (!missing.isEmpty()) { String askMsg = buildAskUserMessage(meta, missing,args); + addSystemMessage(session, askMsg); return askMsg; } @@ -503,6 +523,7 @@ public class DynamicToolProvider implements ToolProvider { List paramRuleDataMiss = getMissParamRuleAfter(args, paramRuleData); String sSystemPrompt = buildMissParamPrompt(session,paramRuleDataMiss); session.setSSystemPrompt(sSystemPrompt); + addSystemMessage(session, askMsg); return askMsg; } return executeTool(meta, args, paramRuleData, session.getUserId(), session); @@ -801,12 +822,18 @@ public class DynamicToolProvider implements ToolProvider { if(ObjectUtil.isEmpty(sBizContent) && iBizType == 4){ sBizContent ="Sp_Ai_AddCommonAfterNew"; if(ObjectUtil.isEmpty(args.get("sSlaveId"))){ - throw new BusinessException(-1,"请选择操作数据"); + //插入AI记忆 + String sMsg = "请选择操作数据"; + addSystemMessage(session, sMsg); + throw new BusinessException(-1,sMsg); } List> sRowData = doGetFromDataWq( meta, args, session); if(ObjectUtil.isEmpty(sRowData)){ - session.setSFunPrompts("选择的数据ID:"+args.get("sSlaveId")+"不存在,请重新选择。"); - throw new BusinessException(-1,"选择的数据ID:"+args.get("sSlaveId")+"不存在,请重新选择。"); + String sMsg = "选择的数据ID:"+args.get("sSlaveId")+"不存在,请重新选择。"; + session.setSFunPrompts(sMsg); + //插入AI记忆 + addSystemMessage(session, sMsg); + throw new BusinessException(-1,sMsg); } Map dataOne = DeepCopyUtils.deepCopy(args); dataOne.remove("sSlaveId"); @@ -859,6 +886,7 @@ public class DynamicToolProvider implements ToolProvider { } else { String sMsgText = "未找到对应的数据"; session.setSFunPrompts(sMsgText); + addSystemMessage(session, sMsgText); throw new BusinessException(-1,sMsgText); } } @@ -869,6 +897,12 @@ public class DynamicToolProvider implements ToolProvider { return "操作成功"; } + private void addSystemMessage(UserSceneSession session,String sMsg){ +// if(ObjectUtil.isNotEmpty(operableChatMemoryProvider) && ObjectUtil.isNotEmpty(operableChatMemoryProvider.get(session.getUserId()))){ +// operableChatMemoryProvider.get(session.getUserId()).add(SystemMessage.from("上一次工具调用失败。原因:" + sMsg + "。请根据用户意图修正参数,并再次调用工具。")); +// } + } + private List> doGetFromDataWq(ToolMeta meta, Map args,UserSceneSession session){ String sUrl = meta.getSendUrl(); Map sBody = new HashMap<>();