From f57d2eee0f6e8fa29aeb14524b196f697dbdb688 Mon Sep 17 00:00:00 2001 From: qianbao Date: Thu, 12 Mar 2026 21:20:40 +0800 Subject: [PATCH] AI 对于时间的处理 --- src/main/java/com/xly/agent/DynamicTableNl2SqlAiAgent.java | 52 ++++++++++++++++++++++++++-------------------------- src/main/java/com/xly/agent/ErpAiAgent.java | 27 +++++++++++++++++++++++++++ src/main/java/com/xly/config/ModelConfig.java | 2 +- src/main/java/com/xly/service/XlyErpService.java | 50 ++++++++++++++++++++++++++++++++++---------------- src/main/resources/application.yml | 2 +- 5 files changed, 89 insertions(+), 44 deletions(-) diff --git a/src/main/java/com/xly/agent/DynamicTableNl2SqlAiAgent.java b/src/main/java/com/xly/agent/DynamicTableNl2SqlAiAgent.java index 3552cf0..af78df4 100644 --- a/src/main/java/com/xly/agent/DynamicTableNl2SqlAiAgent.java +++ b/src/main/java/com/xly/agent/DynamicTableNl2SqlAiAgent.java @@ -148,30 +148,30 @@ public interface DynamicTableNl2SqlAiAgent { @V("n") String iErroCount, @V("historySqlList") String historySqlList ); - /** - * 动态表结构:自然语言解释SQL执行结果 - * 入参:用户问题、执行的SQL、表结构、JSON格式结果 - */ - @SystemMessage(""" - 你是专业的业务数据分析师,严格遵循以下**通用规则**解释查询结果,适用于所有业务场景: - 1. 解释风格:贴合业务场景,无任何SQL专业术语,用口语化、简洁的商业语言说明,避免技术词汇; - 2. 数据准确:严格按照JSON执行结果解释,不夸大、不遗漏、不编造数据,数值与结果完全一致; - 3. 输出格式:仅返回解释内容,不要列出ID,无多余标题、换行、符号,结果为空时直接返回“未查询到相关数据”; - 4. 长度控制:单条解释不超过150字,条理清晰,重点突出核心数据/趋势; - 5. 禁止重复:不重复用户问题、不重复执行的SQL语句,仅针对结果做业务解读。 - """) - @UserMessage(""" - 【业务场景表结构信息】 - 表结构详情:{{tableStruct}} - 【查询相关信息】 - 用户原始查询:{{userInput}} - 执行的MySQL SQL:{{sql}} - SQL执行结果(JSON格式):{{result}} - 请根据上述信息+通用规则,对查询结果做业务解释: - """) - String explainSqlResult(@MemoryId String userId, - @V("userInput") String userInput, - @V("sql") String sql, - @V("tableStruct") String tableStruct, - @V("result") String result); +// /** +// * 动态表结构:自然语言解释SQL执行结果 +// * 入参:用户问题、执行的SQL、表结构、JSON格式结果 +// */ +// @SystemMessage(""" +// 你是专业的业务数据分析师,严格遵循以下**通用规则**解释查询结果,适用于所有业务场景: +// 1. 解释风格:贴合业务场景,无任何SQL专业术语,用口语化、简洁的商业语言说明,避免技术词汇; +// 2. 数据准确:严格按照JSON执行结果解释,不夸大、不遗漏、不编造数据,数值与结果完全一致; +// 3. 输出格式:仅返回解释内容,不要列出ID,无多余标题、换行、符号,结果为空时直接返回“未查询到相关数据”; +// 4. 长度控制:单条解释不超过150字,条理清晰,重点突出核心数据/趋势; +// 5. 禁止重复:不重复用户问题、不重复执行的SQL语句,仅针对结果做业务解读。 +// """) +// @UserMessage(""" +// 【业务场景表结构信息】 +// 表结构详情:{{tableStruct}} +// 【查询相关信息】 +// 用户原始查询:{{userInput}} +// 执行的MySQL SQL:{{sql}} +// SQL执行结果(JSON格式):{{result}} +// 请根据上述信息+通用规则,对查询结果做业务解释: +// """) +// String explainSqlResult(@MemoryId String userId, +// @V("userInput") String userInput, +// @V("sql") String sql, +// @V("tableStruct") String tableStruct, +// @V("result") String result); } \ No newline at end of file diff --git a/src/main/java/com/xly/agent/ErpAiAgent.java b/src/main/java/com/xly/agent/ErpAiAgent.java index 89e3387..aab7f71 100644 --- a/src/main/java/com/xly/agent/ErpAiAgent.java +++ b/src/main/java/com/xly/agent/ErpAiAgent.java @@ -21,4 +21,31 @@ public interface ErpAiAgent { """) @UserMessage("用户输入:{{userInput}}") String chat(@MemoryId String userId, @V("userInput") String userInput); + + /** + * 动态表结构:自然语言解释SQL执行结果 + * 入参:用户问题、执行的SQL、表结构、JSON格式结果 + */ + @SystemMessage(""" + 你是专业的业务数据分析师,严格遵循以下**通用规则**解释查询结果,适用于所有业务场景: + 1. 解释风格:贴合业务场景,无任何SQL专业术语,用口语化、简洁的商业语言说明,避免技术词汇; + 2. 数据准确:严格按照JSON执行结果解释,不夸大、不遗漏、不编造数据,数值与结果完全一致; + 3. 输出格式:仅返回解释内容,不要列出ID,无多余标题、换行、符号,结果为空时直接返回“未查询到相关数据”; + 4. 长度控制:单条解释不超过150字,条理清晰,重点突出核心数据/趋势; + 5. 禁止重复:不重复用户问题、不重复执行的SQL语句,仅针对结果做业务解读。 + """) + @UserMessage(""" + 【业务场景表结构信息】 + 表结构详情:{{tableStruct}} + 【查询相关信息】 + 用户原始查询:{{userInput}} + 执行的MySQL SQL:{{sql}} + SQL执行结果(JSON格式):{{result}} + 请根据上述信息+通用规则,对查询结果做业务解释: + """) + String explainSqlResult(@MemoryId String userId, + @V("userInput") String userInput, + @V("sql") String sql, + @V("tableStruct") String tableStruct, + @V("result") String result); } \ No newline at end of file diff --git a/src/main/java/com/xly/config/ModelConfig.java b/src/main/java/com/xly/config/ModelConfig.java index 1ce2c81..60612c5 100644 --- a/src/main/java/com/xly/config/ModelConfig.java +++ b/src/main/java/com/xly/config/ModelConfig.java @@ -75,7 +75,7 @@ public class ModelConfig { // SQL/代码专用模型 qwen2.5-coder:14b @Bean("sqlChatModel") // 明确指定bean名称 - public ChatLanguageModel sqlChatModel() { + public OllamaChatModel sqlChatModel() { return OllamaChatModel.builder() .baseUrl(sqlModelUrl) .modelName(sqlModelName) // 使用SQL模型名称 diff --git a/src/main/java/com/xly/service/XlyErpService.java b/src/main/java/com/xly/service/XlyErpService.java index c56ffea..83b4b4c 100644 --- a/src/main/java/com/xly/service/XlyErpService.java +++ b/src/main/java/com/xly/service/XlyErpService.java @@ -31,7 +31,10 @@ import dev.langchain4j.model.ollama.OllamaChatModel; import dev.langchain4j.service.AiServices; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; + +import java.time.Duration; import java.util.*; @Service @@ -41,20 +44,22 @@ public class XlyErpService { //中文对话模型 private final OllamaChatModel chatModel; private final ChatLanguageModel chatiModel; - private final ChatLanguageModel sqlChatModel; private final SceneSelectorAiAgent sceneSelectorAiAgent; private final UserSceneSessionService userSceneSessionService; private final DynamicToolProvider dynamicToolProvider; private final OperableChatMemoryProvider operableChatMemoryProvider; private final DynamicExeDbService dynamicExeDbService; - private final ToolMetaMapper toolMetaMapper; //执行动态语句 执行异常的情况下 最多执行次数 private final Integer maxRetries = 5; //没有找到对应方法重走一次补偿次数 public final static Integer maxTollRetries = 1; + @Value("${langchain4j.ollama.base-url}") + private String sqlModelUrl; + @Value("${langchain4j.ollama.sql-model-name}") + private String sqlModelName; /*** * @Author 钱豹 * @Date 19:18 2026/1/27 @@ -128,7 +133,7 @@ public class XlyErpService { && ObjectUtil.isNotEmpty(session.getCurrentTool().getSInputTabelName()) && ObjectUtil.isNotEmpty(session.getCurrentTool().getSStructureMemo())) ){ - sResponMessage = getDynamicTableSql(session, input, userId, userInput,0,StrUtil.EMPTY,StrUtil.EMPTY,"0",StrUtil.EMPTY); + sResponMessage = getDynamicTableSql(session, input, userId, userInput,0,StrUtil.EMPTY,StrUtil.EMPTY,"0",StrUtil.EMPTY, aiAgent); } //如果返回空的进入闲聊模式 if (ObjectUtil.isEmpty(sResponMessage)){ @@ -173,13 +178,13 @@ public class XlyErpService { * @return java.lang.String * @Description 获取执行动态SQL **/ - private String getDynamicTableSql(UserSceneSession session,String input,String userId,String userInput,Integer attempt,String errorSql,String errorMessage,String iErroCount,String historySqlList ){ + private String getDynamicTableSql(UserSceneSession session,String input,String userId,String userInput,Integer attempt,String errorSql,String errorMessage,String iErroCount,String historySqlList,ErpAiAgent aiAgent){ String resultExplain = "信息模糊,请提供更具体的问题或指令"; try{ while (attempt < maxRetries) { try{ attempt = attempt+1; - return getDynamicTableSqlExec(session, input, userId, userInput,errorSql,errorMessage,iErroCount,historySqlList); + return getDynamicTableSqlExec(session, input, userId, userInput,errorSql,errorMessage,iErroCount,historySqlList, aiAgent); }catch (SqlValidateException e){ return "本场景没有识别到您的意图
如果切换场景,点[回首页],如果在本场景下,转换意图,点[清除记忆]"; }catch (Exception e){ @@ -198,14 +203,15 @@ public class XlyErpService { if (attempt == maxRetries) { return resultExplain +"
查询的SQL语句:"+historySqlList; } else { - return getDynamicTableSql( session, input, userId, userInput, attempt,errorSqlOld,errorMessageOld,attempt.toString(),historySqlList); + return getDynamicTableSql( session, input, userId, userInput, attempt,errorSqlOld,errorMessageOld,attempt.toString(),historySqlList, aiAgent); } } } }catch (Exception e){ - }finally { - // doCleanUserMemory(session,userId); } +// finally { +// doCleanUserMemory(session,userId); +// } return resultExplain; } @@ -233,29 +239,29 @@ public class XlyErpService { * @return java.lang.String * @Description 执行动态sSql **/ - private String getDynamicTableSqlExec(UserSceneSession session,String input,String userId,String userInput,String errorSql,String errorMessage,String iErroCount,String historySqlList){ + private String getDynamicTableSqlExec(UserSceneSession session,String input,String userId,String userInput,String errorSql,String errorMessage,String iErroCount,String historySqlList,ErpAiAgent aiAgent){ // 1. 构建自然语言转SQLAgent, DynamicTableNl2SqlAiAgent aiDynamicTableNl2SqlAiAgent = createDynamicTableNl2SqlAiAgent(userId, input, session); String tableNames = session.getCurrentTool().getSInputTabelName(); // "订单表:viw_salsalesorder,客户信息表:elecustomer,结算方式表:sispayment,产品表(无单价,无金额,无数量):viw_product_sort,销售人员表:viw_sissalesman_depart"; String tableStruct = session.getCurrentTool().getSStructureMemo(); String sDataNow = DateUtil.format(new Date(), DatePattern.CHINESE_DATE_TIME_FORMAT); - log.info("当前时间:"+sDataNow); String rawSql = StrUtil.EMPTY; if(ObjectUtil.isEmpty(errorSql) && ObjectUtil.isEmpty(errorMessage)){ rawSql = aiDynamicTableNl2SqlAiAgent.generateMysqlSql(userId,tableNames,tableStruct,sDataNow,userInput); }else{ rawSql = aiDynamicTableNl2SqlAiAgent.regenerateSqlWithError(userId, tableNames,tableStruct,sDataNow,userInput,errorSql,errorMessage,iErroCount,historySqlList); } + log.info("rawSql:"+rawSql); if (rawSql == null || rawSql.trim().isEmpty()) { throw new SqlValidateException("SQL EMPTY"); } // 2. 清理SQL多余符号 + 生产级强校验(核心安全保障,不可省略) String cleanSql = SqlValidateUtil.cleanSqlSymbol(rawSql); - String[] cleanSqlA = rawSql.split(";"); - if(cleanSqlA.length>1){ - cleanSql = cleanSqlA[cleanSqlA.length-1]; - } +// String[] cleanSqlA = rawSql.split(";"); +// if(cleanSqlA.length>1){ +// cleanSql = cleanSqlA[cleanSqlA.length-1]; +// } SqlValidateUtil.validateMysqlSql(cleanSql); // 4. 执行SQL获取结构化结果 // Map params = new HashMap<>(); @@ -267,13 +273,15 @@ public class XlyErpService { } // 5. 调用AI服务生成自然语言解释(传入表结构,让解释更贴合业务) String resultJson = JSON.toJSONString(sqlResult); - return aiDynamicTableNl2SqlAiAgent.explainSqlResult( + String sText = aiAgent.explainSqlResult( userId, userInput, cleanSql, tableStruct, resultJson ); + log.info("sText:"+sText); + return sText; } /*** @@ -324,8 +332,18 @@ public class XlyErpService { // 4. 获取/创建用DynamicTableNl2SqlAiAgent DynamicTableNl2SqlAiAgent aiAgent = UserSceneSessionService.ERP_DynamicTableNl2SqlAiAgent_CACHE.get(userId); if(ObjectUtil.isEmpty(aiAgent)){ + OllamaChatModel ol = OllamaChatModel.builder() + .baseUrl(sqlModelUrl) + .modelName(sqlModelName) // 使用SQL模型名称 + .temperature(0.0) + .topP(0.95) + .numPredict(4096) // 代码生成需要更长 + .timeout(Duration.ofSeconds(120)) + .maxRetries(3) +// .repeatPenalty(1.1) // 减少重复 + .build(); aiAgent = AiServices.builder(DynamicTableNl2SqlAiAgent.class) - .chatLanguageModel(sqlChatModel) + .chatLanguageModel(ol) .chatMemoryProvider(operableChatMemoryProvider) .toolProvider(dynamicToolProvider) .build(); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 2b9c968..4cc4d26 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -75,7 +75,7 @@ langchain4j: base-url: http://121.43.128.225:11434 chat-model-name: qwen2.5:7b-instruct # SQL/代码模型配置(专门用于代码和SQL生成) - sql-model-name: qwen2.5-coder:14b + sql-model-name: qwen2.5-coder:32b # 或者如果两个模型在同一服务器,可以使用同一个URL mybatis: -- libgit2 0.22.2