Commit f57d2eee0f6e8fa29aeb14524b196f697dbdb688

Authored by qianbao
1 parent a37aafa7

AI 对于时间的处理

src/main/java/com/xly/agent/DynamicTableNl2SqlAiAgent.java
@@ -148,30 +148,30 @@ public interface DynamicTableNl2SqlAiAgent { @@ -148,30 +148,30 @@ public interface DynamicTableNl2SqlAiAgent {
148 @V("n") String iErroCount, 148 @V("n") String iErroCount,
149 @V("historySqlList") String historySqlList 149 @V("historySqlList") String historySqlList
150 ); 150 );
151 - /**  
152 - * 动态表结构:自然语言解释SQL执行结果  
153 - * 入参:用户问题、执行的SQL、表结构、JSON格式结果  
154 - */  
155 - @SystemMessage("""  
156 - 你是专业的业务数据分析师,严格遵循以下**通用规则**解释查询结果,适用于所有业务场景:  
157 - 1. 解释风格:贴合业务场景,无任何SQL专业术语,用口语化、简洁的商业语言说明,避免技术词汇;  
158 - 2. 数据准确:严格按照JSON执行结果解释,不夸大、不遗漏、不编造数据,数值与结果完全一致;  
159 - 3. 输出格式:仅返回解释内容,不要列出ID,无多余标题、换行、符号,结果为空时直接返回“未查询到相关数据”;  
160 - 4. 长度控制:单条解释不超过150字,条理清晰,重点突出核心数据/趋势;  
161 - 5. 禁止重复:不重复用户问题、不重复执行的SQL语句,仅针对结果做业务解读。  
162 - """)  
163 - @UserMessage("""  
164 - 【业务场景表结构信息】  
165 - 表结构详情:{{tableStruct}}  
166 - 【查询相关信息】  
167 - 用户原始查询:{{userInput}}  
168 - 执行的MySQL SQL:{{sql}}  
169 - SQL执行结果(JSON格式):{{result}}  
170 - 请根据上述信息+通用规则,对查询结果做业务解释:  
171 - """)  
172 - String explainSqlResult(@MemoryId String userId,  
173 - @V("userInput") String userInput,  
174 - @V("sql") String sql,  
175 - @V("tableStruct") String tableStruct,  
176 - @V("result") String result); 151 +// /**
  152 +// * 动态表结构:自然语言解释SQL执行结果
  153 +// * 入参:用户问题、执行的SQL、表结构、JSON格式结果
  154 +// */
  155 +// @SystemMessage("""
  156 +// 你是专业的业务数据分析师,严格遵循以下**通用规则**解释查询结果,适用于所有业务场景:
  157 +// 1. 解释风格:贴合业务场景,无任何SQL专业术语,用口语化、简洁的商业语言说明,避免技术词汇;
  158 +// 2. 数据准确:严格按照JSON执行结果解释,不夸大、不遗漏、不编造数据,数值与结果完全一致;
  159 +// 3. 输出格式:仅返回解释内容,不要列出ID,无多余标题、换行、符号,结果为空时直接返回“未查询到相关数据”;
  160 +// 4. 长度控制:单条解释不超过150字,条理清晰,重点突出核心数据/趋势;
  161 +// 5. 禁止重复:不重复用户问题、不重复执行的SQL语句,仅针对结果做业务解读。
  162 +// """)
  163 +// @UserMessage("""
  164 +// 【业务场景表结构信息】
  165 +// 表结构详情:{{tableStruct}}
  166 +// 【查询相关信息】
  167 +// 用户原始查询:{{userInput}}
  168 +// 执行的MySQL SQL:{{sql}}
  169 +// SQL执行结果(JSON格式):{{result}}
  170 +// 请根据上述信息+通用规则,对查询结果做业务解释:
  171 +// """)
  172 +// String explainSqlResult(@MemoryId String userId,
  173 +// @V("userInput") String userInput,
  174 +// @V("sql") String sql,
  175 +// @V("tableStruct") String tableStruct,
  176 +// @V("result") String result);
177 } 177 }
178 \ No newline at end of file 178 \ No newline at end of file
src/main/java/com/xly/agent/ErpAiAgent.java
@@ -21,4 +21,31 @@ public interface ErpAiAgent { @@ -21,4 +21,31 @@ public interface ErpAiAgent {
21 """) 21 """)
22 @UserMessage("用户输入:{{userInput}}") 22 @UserMessage("用户输入:{{userInput}}")
23 String chat(@MemoryId String userId, @V("userInput") String userInput); 23 String chat(@MemoryId String userId, @V("userInput") String userInput);
  24 +
  25 + /**
  26 + * 动态表结构:自然语言解释SQL执行结果
  27 + * 入参:用户问题、执行的SQL、表结构、JSON格式结果
  28 + */
  29 + @SystemMessage("""
  30 + 你是专业的业务数据分析师,严格遵循以下**通用规则**解释查询结果,适用于所有业务场景:
  31 + 1. 解释风格:贴合业务场景,无任何SQL专业术语,用口语化、简洁的商业语言说明,避免技术词汇;
  32 + 2. 数据准确:严格按照JSON执行结果解释,不夸大、不遗漏、不编造数据,数值与结果完全一致;
  33 + 3. 输出格式:仅返回解释内容,不要列出ID,无多余标题、换行、符号,结果为空时直接返回“未查询到相关数据”;
  34 + 4. 长度控制:单条解释不超过150字,条理清晰,重点突出核心数据/趋势;
  35 + 5. 禁止重复:不重复用户问题、不重复执行的SQL语句,仅针对结果做业务解读。
  36 + """)
  37 + @UserMessage("""
  38 + 【业务场景表结构信息】
  39 + 表结构详情:{{tableStruct}}
  40 + 【查询相关信息】
  41 + 用户原始查询:{{userInput}}
  42 + 执行的MySQL SQL:{{sql}}
  43 + SQL执行结果(JSON格式):{{result}}
  44 + 请根据上述信息+通用规则,对查询结果做业务解释:
  45 + """)
  46 + String explainSqlResult(@MemoryId String userId,
  47 + @V("userInput") String userInput,
  48 + @V("sql") String sql,
  49 + @V("tableStruct") String tableStruct,
  50 + @V("result") String result);
24 } 51 }
25 \ No newline at end of file 52 \ No newline at end of file
src/main/java/com/xly/config/ModelConfig.java
@@ -75,7 +75,7 @@ public class ModelConfig { @@ -75,7 +75,7 @@ public class ModelConfig {
75 75
76 // SQL/代码专用模型 qwen2.5-coder:14b 76 // SQL/代码专用模型 qwen2.5-coder:14b
77 @Bean("sqlChatModel") // 明确指定bean名称 77 @Bean("sqlChatModel") // 明确指定bean名称
78 - public ChatLanguageModel sqlChatModel() { 78 + public OllamaChatModel sqlChatModel() {
79 return OllamaChatModel.builder() 79 return OllamaChatModel.builder()
80 .baseUrl(sqlModelUrl) 80 .baseUrl(sqlModelUrl)
81 .modelName(sqlModelName) // 使用SQL模型名称 81 .modelName(sqlModelName) // 使用SQL模型名称
src/main/java/com/xly/service/XlyErpService.java
@@ -31,7 +31,10 @@ import dev.langchain4j.model.ollama.OllamaChatModel; @@ -31,7 +31,10 @@ import dev.langchain4j.model.ollama.OllamaChatModel;
31 import dev.langchain4j.service.AiServices; 31 import dev.langchain4j.service.AiServices;
32 import lombok.RequiredArgsConstructor; 32 import lombok.RequiredArgsConstructor;
33 import lombok.extern.slf4j.Slf4j; 33 import lombok.extern.slf4j.Slf4j;
  34 +import org.springframework.beans.factory.annotation.Value;
34 import org.springframework.stereotype.Service; 35 import org.springframework.stereotype.Service;
  36 +
  37 +import java.time.Duration;
35 import java.util.*; 38 import java.util.*;
36 39
37 @Service 40 @Service
@@ -41,20 +44,22 @@ public class XlyErpService { @@ -41,20 +44,22 @@ public class XlyErpService {
41 //中文对话模型 44 //中文对话模型
42 private final OllamaChatModel chatModel; 45 private final OllamaChatModel chatModel;
43 private final ChatLanguageModel chatiModel; 46 private final ChatLanguageModel chatiModel;
44 - private final ChatLanguageModel sqlChatModel;  
45 private final SceneSelectorAiAgent sceneSelectorAiAgent; 47 private final SceneSelectorAiAgent sceneSelectorAiAgent;
46 private final UserSceneSessionService userSceneSessionService; 48 private final UserSceneSessionService userSceneSessionService;
47 private final DynamicToolProvider dynamicToolProvider; 49 private final DynamicToolProvider dynamicToolProvider;
48 private final OperableChatMemoryProvider operableChatMemoryProvider; 50 private final OperableChatMemoryProvider operableChatMemoryProvider;
49 private final DynamicExeDbService dynamicExeDbService; 51 private final DynamicExeDbService dynamicExeDbService;
50 - private final ToolMetaMapper toolMetaMapper;  
51 52
52 //执行动态语句 执行异常的情况下 最多执行次数 53 //执行动态语句 执行异常的情况下 最多执行次数
53 private final Integer maxRetries = 5; 54 private final Integer maxRetries = 5;
54 //没有找到对应方法重走一次补偿次数 55 //没有找到对应方法重走一次补偿次数
55 public final static Integer maxTollRetries = 1; 56 public final static Integer maxTollRetries = 1;
56 57
  58 + @Value("${langchain4j.ollama.base-url}")
  59 + private String sqlModelUrl;
57 60
  61 + @Value("${langchain4j.ollama.sql-model-name}")
  62 + private String sqlModelName;
58 /*** 63 /***
59 * @Author 钱豹 64 * @Author 钱豹
60 * @Date 19:18 2026/1/27 65 * @Date 19:18 2026/1/27
@@ -128,7 +133,7 @@ public class XlyErpService { @@ -128,7 +133,7 @@ public class XlyErpService {
128 && ObjectUtil.isNotEmpty(session.getCurrentTool().getSInputTabelName()) 133 && ObjectUtil.isNotEmpty(session.getCurrentTool().getSInputTabelName())
129 && ObjectUtil.isNotEmpty(session.getCurrentTool().getSStructureMemo())) 134 && ObjectUtil.isNotEmpty(session.getCurrentTool().getSStructureMemo()))
130 ){ 135 ){
131 - sResponMessage = getDynamicTableSql(session, input, userId, userInput,0,StrUtil.EMPTY,StrUtil.EMPTY,"0",StrUtil.EMPTY); 136 + sResponMessage = getDynamicTableSql(session, input, userId, userInput,0,StrUtil.EMPTY,StrUtil.EMPTY,"0",StrUtil.EMPTY, aiAgent);
132 } 137 }
133 //如果返回空的进入闲聊模式 138 //如果返回空的进入闲聊模式
134 if (ObjectUtil.isEmpty(sResponMessage)){ 139 if (ObjectUtil.isEmpty(sResponMessage)){
@@ -173,13 +178,13 @@ public class XlyErpService { @@ -173,13 +178,13 @@ public class XlyErpService {
173 * @return java.lang.String 178 * @return java.lang.String
174 * @Description 获取执行动态SQL 179 * @Description 获取执行动态SQL
175 **/ 180 **/
176 - private String getDynamicTableSql(UserSceneSession session,String input,String userId,String userInput,Integer attempt,String errorSql,String errorMessage,String iErroCount,String historySqlList ){ 181 + private String getDynamicTableSql(UserSceneSession session,String input,String userId,String userInput,Integer attempt,String errorSql,String errorMessage,String iErroCount,String historySqlList,ErpAiAgent aiAgent){
177 String resultExplain = "信息模糊,请提供更具体的问题或指令"; 182 String resultExplain = "信息模糊,请提供更具体的问题或指令";
178 try{ 183 try{
179 while (attempt < maxRetries) { 184 while (attempt < maxRetries) {
180 try{ 185 try{
181 attempt = attempt+1; 186 attempt = attempt+1;
182 - return getDynamicTableSqlExec(session, input, userId, userInput,errorSql,errorMessage,iErroCount,historySqlList); 187 + return getDynamicTableSqlExec(session, input, userId, userInput,errorSql,errorMessage,iErroCount,historySqlList, aiAgent);
183 }catch (SqlValidateException e){ 188 }catch (SqlValidateException e){
184 return "本场景没有识别到您的意图<br/> 如果切换场景,点[回首页],如果在本场景下,转换意图,点[清除记忆]"; 189 return "本场景没有识别到您的意图<br/> 如果切换场景,点[回首页],如果在本场景下,转换意图,点[清除记忆]";
185 }catch (Exception e){ 190 }catch (Exception e){
@@ -198,14 +203,15 @@ public class XlyErpService { @@ -198,14 +203,15 @@ public class XlyErpService {
198 if (attempt == maxRetries) { 203 if (attempt == maxRetries) {
199 return resultExplain +"<br/>查询的SQL语句:"+historySqlList; 204 return resultExplain +"<br/>查询的SQL语句:"+historySqlList;
200 } else { 205 } else {
201 - return getDynamicTableSql( session, input, userId, userInput, attempt,errorSqlOld,errorMessageOld,attempt.toString(),historySqlList); 206 + return getDynamicTableSql( session, input, userId, userInput, attempt,errorSqlOld,errorMessageOld,attempt.toString(),historySqlList, aiAgent);
202 } 207 }
203 } 208 }
204 } 209 }
205 }catch (Exception e){ 210 }catch (Exception e){
206 - }finally {  
207 - // doCleanUserMemory(session,userId);  
208 } 211 }
  212 +// finally {
  213 +// doCleanUserMemory(session,userId);
  214 +// }
209 return resultExplain; 215 return resultExplain;
210 } 216 }
211 217
@@ -233,29 +239,29 @@ public class XlyErpService { @@ -233,29 +239,29 @@ public class XlyErpService {
233 * @return java.lang.String 239 * @return java.lang.String
234 * @Description 执行动态sSql 240 * @Description 执行动态sSql
235 **/ 241 **/
236 - private String getDynamicTableSqlExec(UserSceneSession session,String input,String userId,String userInput,String errorSql,String errorMessage,String iErroCount,String historySqlList){ 242 + private String getDynamicTableSqlExec(UserSceneSession session,String input,String userId,String userInput,String errorSql,String errorMessage,String iErroCount,String historySqlList,ErpAiAgent aiAgent){
237 // 1. 构建自然语言转SQLAgent, 243 // 1. 构建自然语言转SQLAgent,
238 DynamicTableNl2SqlAiAgent aiDynamicTableNl2SqlAiAgent = createDynamicTableNl2SqlAiAgent(userId, input, session); 244 DynamicTableNl2SqlAiAgent aiDynamicTableNl2SqlAiAgent = createDynamicTableNl2SqlAiAgent(userId, input, session);
239 String tableNames = session.getCurrentTool().getSInputTabelName(); 245 String tableNames = session.getCurrentTool().getSInputTabelName();
240 // "订单表:viw_salsalesorder,客户信息表:elecustomer,结算方式表:sispayment,产品表(无单价,无金额,无数量):viw_product_sort,销售人员表:viw_sissalesman_depart"; 246 // "订单表:viw_salsalesorder,客户信息表:elecustomer,结算方式表:sispayment,产品表(无单价,无金额,无数量):viw_product_sort,销售人员表:viw_sissalesman_depart";
241 String tableStruct = session.getCurrentTool().getSStructureMemo(); 247 String tableStruct = session.getCurrentTool().getSStructureMemo();
242 String sDataNow = DateUtil.format(new Date(), DatePattern.CHINESE_DATE_TIME_FORMAT); 248 String sDataNow = DateUtil.format(new Date(), DatePattern.CHINESE_DATE_TIME_FORMAT);
243 - log.info("当前时间:"+sDataNow);  
244 String rawSql = StrUtil.EMPTY; 249 String rawSql = StrUtil.EMPTY;
245 if(ObjectUtil.isEmpty(errorSql) && ObjectUtil.isEmpty(errorMessage)){ 250 if(ObjectUtil.isEmpty(errorSql) && ObjectUtil.isEmpty(errorMessage)){
246 rawSql = aiDynamicTableNl2SqlAiAgent.generateMysqlSql(userId,tableNames,tableStruct,sDataNow,userInput); 251 rawSql = aiDynamicTableNl2SqlAiAgent.generateMysqlSql(userId,tableNames,tableStruct,sDataNow,userInput);
247 }else{ 252 }else{
248 rawSql = aiDynamicTableNl2SqlAiAgent.regenerateSqlWithError(userId, tableNames,tableStruct,sDataNow,userInput,errorSql,errorMessage,iErroCount,historySqlList); 253 rawSql = aiDynamicTableNl2SqlAiAgent.regenerateSqlWithError(userId, tableNames,tableStruct,sDataNow,userInput,errorSql,errorMessage,iErroCount,historySqlList);
249 } 254 }
  255 + log.info("rawSql:"+rawSql);
250 if (rawSql == null || rawSql.trim().isEmpty()) { 256 if (rawSql == null || rawSql.trim().isEmpty()) {
251 throw new SqlValidateException("SQL EMPTY"); 257 throw new SqlValidateException("SQL EMPTY");
252 } 258 }
253 // 2. 清理SQL多余符号 + 生产级强校验(核心安全保障,不可省略) 259 // 2. 清理SQL多余符号 + 生产级强校验(核心安全保障,不可省略)
254 String cleanSql = SqlValidateUtil.cleanSqlSymbol(rawSql); 260 String cleanSql = SqlValidateUtil.cleanSqlSymbol(rawSql);
255 - String[] cleanSqlA = rawSql.split(";");  
256 - if(cleanSqlA.length>1){  
257 - cleanSql = cleanSqlA[cleanSqlA.length-1];  
258 - } 261 +// String[] cleanSqlA = rawSql.split(";");
  262 +// if(cleanSqlA.length>1){
  263 +// cleanSql = cleanSqlA[cleanSqlA.length-1];
  264 +// }
259 SqlValidateUtil.validateMysqlSql(cleanSql); 265 SqlValidateUtil.validateMysqlSql(cleanSql);
260 // 4. 执行SQL获取结构化结果 266 // 4. 执行SQL获取结构化结果
261 // Map<String,Object> params = new HashMap<>(); 267 // Map<String,Object> params = new HashMap<>();
@@ -267,13 +273,15 @@ public class XlyErpService { @@ -267,13 +273,15 @@ public class XlyErpService {
267 } 273 }
268 // 5. 调用AI服务生成自然语言解释(传入表结构,让解释更贴合业务) 274 // 5. 调用AI服务生成自然语言解释(传入表结构,让解释更贴合业务)
269 String resultJson = JSON.toJSONString(sqlResult); 275 String resultJson = JSON.toJSONString(sqlResult);
270 - return aiDynamicTableNl2SqlAiAgent.explainSqlResult( 276 + String sText = aiAgent.explainSqlResult(
271 userId, 277 userId,
272 userInput, 278 userInput,
273 cleanSql, 279 cleanSql,
274 tableStruct, 280 tableStruct,
275 resultJson 281 resultJson
276 ); 282 );
  283 + log.info("sText:"+sText);
  284 + return sText;
277 } 285 }
278 286
279 /*** 287 /***
@@ -324,8 +332,18 @@ public class XlyErpService { @@ -324,8 +332,18 @@ public class XlyErpService {
324 // 4. 获取/创建用DynamicTableNl2SqlAiAgent 332 // 4. 获取/创建用DynamicTableNl2SqlAiAgent
325 DynamicTableNl2SqlAiAgent aiAgent = UserSceneSessionService.ERP_DynamicTableNl2SqlAiAgent_CACHE.get(userId); 333 DynamicTableNl2SqlAiAgent aiAgent = UserSceneSessionService.ERP_DynamicTableNl2SqlAiAgent_CACHE.get(userId);
326 if(ObjectUtil.isEmpty(aiAgent)){ 334 if(ObjectUtil.isEmpty(aiAgent)){
  335 + OllamaChatModel ol = OllamaChatModel.builder()
  336 + .baseUrl(sqlModelUrl)
  337 + .modelName(sqlModelName) // 使用SQL模型名称
  338 + .temperature(0.0)
  339 + .topP(0.95)
  340 + .numPredict(4096) // 代码生成需要更长
  341 + .timeout(Duration.ofSeconds(120))
  342 + .maxRetries(3)
  343 +// .repeatPenalty(1.1) // 减少重复
  344 + .build();
327 aiAgent = AiServices.builder(DynamicTableNl2SqlAiAgent.class) 345 aiAgent = AiServices.builder(DynamicTableNl2SqlAiAgent.class)
328 - .chatLanguageModel(sqlChatModel) 346 + .chatLanguageModel(ol)
329 .chatMemoryProvider(operableChatMemoryProvider) 347 .chatMemoryProvider(operableChatMemoryProvider)
330 .toolProvider(dynamicToolProvider) 348 .toolProvider(dynamicToolProvider)
331 .build(); 349 .build();
src/main/resources/application.yml
@@ -75,7 +75,7 @@ langchain4j: @@ -75,7 +75,7 @@ langchain4j:
75 base-url: http://121.43.128.225:11434 75 base-url: http://121.43.128.225:11434
76 chat-model-name: qwen2.5:7b-instruct 76 chat-model-name: qwen2.5:7b-instruct
77 # SQL/代码模型配置(专门用于代码和SQL生成) 77 # SQL/代码模型配置(专门用于代码和SQL生成)
78 - sql-model-name: qwen2.5-coder:14b 78 + sql-model-name: qwen2.5-coder:32b
79 # 或者如果两个模型在同一服务器,可以使用同一个URL 79 # 或者如果两个模型在同一服务器,可以使用同一个URL
80 80
81 mybatis: 81 mybatis: