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 148 @V("n") String iErroCount,
149 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 178 \ No newline at end of file
... ...
src/main/java/com/xly/agent/ErpAiAgent.java
... ... @@ -21,4 +21,31 @@ public interface ErpAiAgent {
21 21 """)
22 22 @UserMessage("用户输入:{{userInput}}")
23 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 52 \ No newline at end of file
... ...
src/main/java/com/xly/config/ModelConfig.java
... ... @@ -75,7 +75,7 @@ public class ModelConfig {
75 75  
76 76 // SQL/代码专用模型 qwen2.5-coder:14b
77 77 @Bean("sqlChatModel") // 明确指定bean名称
78   - public ChatLanguageModel sqlChatModel() {
  78 + public OllamaChatModel sqlChatModel() {
79 79 return OllamaChatModel.builder()
80 80 .baseUrl(sqlModelUrl)
81 81 .modelName(sqlModelName) // 使用SQL模型名称
... ...
src/main/java/com/xly/service/XlyErpService.java
... ... @@ -31,7 +31,10 @@ import dev.langchain4j.model.ollama.OllamaChatModel;
31 31 import dev.langchain4j.service.AiServices;
32 32 import lombok.RequiredArgsConstructor;
33 33 import lombok.extern.slf4j.Slf4j;
  34 +import org.springframework.beans.factory.annotation.Value;
34 35 import org.springframework.stereotype.Service;
  36 +
  37 +import java.time.Duration;
35 38 import java.util.*;
36 39  
37 40 @Service
... ... @@ -41,20 +44,22 @@ public class XlyErpService {
41 44 //中文对话模型
42 45 private final OllamaChatModel chatModel;
43 46 private final ChatLanguageModel chatiModel;
44   - private final ChatLanguageModel sqlChatModel;
45 47 private final SceneSelectorAiAgent sceneSelectorAiAgent;
46 48 private final UserSceneSessionService userSceneSessionService;
47 49 private final DynamicToolProvider dynamicToolProvider;
48 50 private final OperableChatMemoryProvider operableChatMemoryProvider;
49 51 private final DynamicExeDbService dynamicExeDbService;
50   - private final ToolMetaMapper toolMetaMapper;
51 52  
52 53 //执行动态语句 执行异常的情况下 最多执行次数
53 54 private final Integer maxRetries = 5;
54 55 //没有找到对应方法重走一次补偿次数
55 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 64 * @Author 钱豹
60 65 * @Date 19:18 2026/1/27
... ... @@ -128,7 +133,7 @@ public class XlyErpService {
128 133 && ObjectUtil.isNotEmpty(session.getCurrentTool().getSInputTabelName())
129 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 139 if (ObjectUtil.isEmpty(sResponMessage)){
... ... @@ -173,13 +178,13 @@ public class XlyErpService {
173 178 * @return java.lang.String
174 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 182 String resultExplain = "信息模糊,请提供更具体的问题或指令";
178 183 try{
179 184 while (attempt < maxRetries) {
180 185 try{
181 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 188 }catch (SqlValidateException e){
184 189 return "本场景没有识别到您的意图<br/> 如果切换场景,点[回首页],如果在本场景下,转换意图,点[清除记忆]";
185 190 }catch (Exception e){
... ... @@ -198,14 +203,15 @@ public class XlyErpService {
198 203 if (attempt == maxRetries) {
199 204 return resultExplain +"<br/>查询的SQL语句:"+historySqlList;
200 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 210 }catch (Exception e){
206   - }finally {
207   - // doCleanUserMemory(session,userId);
208 211 }
  212 +// finally {
  213 +// doCleanUserMemory(session,userId);
  214 +// }
209 215 return resultExplain;
210 216 }
211 217  
... ... @@ -233,29 +239,29 @@ public class XlyErpService {
233 239 * @return java.lang.String
234 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 243 // 1. 构建自然语言转SQLAgent,
238 244 DynamicTableNl2SqlAiAgent aiDynamicTableNl2SqlAiAgent = createDynamicTableNl2SqlAiAgent(userId, input, session);
239 245 String tableNames = session.getCurrentTool().getSInputTabelName();
240 246 // "订单表:viw_salsalesorder,客户信息表:elecustomer,结算方式表:sispayment,产品表(无单价,无金额,无数量):viw_product_sort,销售人员表:viw_sissalesman_depart";
241 247 String tableStruct = session.getCurrentTool().getSStructureMemo();
242 248 String sDataNow = DateUtil.format(new Date(), DatePattern.CHINESE_DATE_TIME_FORMAT);
243   - log.info("当前时间:"+sDataNow);
244 249 String rawSql = StrUtil.EMPTY;
245 250 if(ObjectUtil.isEmpty(errorSql) && ObjectUtil.isEmpty(errorMessage)){
246 251 rawSql = aiDynamicTableNl2SqlAiAgent.generateMysqlSql(userId,tableNames,tableStruct,sDataNow,userInput);
247 252 }else{
248 253 rawSql = aiDynamicTableNl2SqlAiAgent.regenerateSqlWithError(userId, tableNames,tableStruct,sDataNow,userInput,errorSql,errorMessage,iErroCount,historySqlList);
249 254 }
  255 + log.info("rawSql:"+rawSql);
250 256 if (rawSql == null || rawSql.trim().isEmpty()) {
251 257 throw new SqlValidateException("SQL EMPTY");
252 258 }
253 259 // 2. 清理SQL多余符号 + 生产级强校验(核心安全保障,不可省略)
254 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 265 SqlValidateUtil.validateMysqlSql(cleanSql);
260 266 // 4. 执行SQL获取结构化结果
261 267 // Map<String,Object> params = new HashMap<>();
... ... @@ -267,13 +273,15 @@ public class XlyErpService {
267 273 }
268 274 // 5. 调用AI服务生成自然语言解释(传入表结构,让解释更贴合业务)
269 275 String resultJson = JSON.toJSONString(sqlResult);
270   - return aiDynamicTableNl2SqlAiAgent.explainSqlResult(
  276 + String sText = aiAgent.explainSqlResult(
271 277 userId,
272 278 userInput,
273 279 cleanSql,
274 280 tableStruct,
275 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 332 // 4. 获取/创建用DynamicTableNl2SqlAiAgent
325 333 DynamicTableNl2SqlAiAgent aiAgent = UserSceneSessionService.ERP_DynamicTableNl2SqlAiAgent_CACHE.get(userId);
326 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 345 aiAgent = AiServices.builder(DynamicTableNl2SqlAiAgent.class)
328   - .chatLanguageModel(sqlChatModel)
  346 + .chatLanguageModel(ol)
329 347 .chatMemoryProvider(operableChatMemoryProvider)
330 348 .toolProvider(dynamicToolProvider)
331 349 .build();
... ...
src/main/resources/application.yml
... ... @@ -75,7 +75,7 @@ langchain4j:
75 75 base-url: http://121.43.128.225:11434
76 76 chat-model-name: qwen2.5:7b-instruct
77 77 # SQL/代码模型配置(专门用于代码和SQL生成)
78   - sql-model-name: qwen2.5-coder:14b
  78 + sql-model-name: qwen2.5-coder:32b
79 79 # 或者如果两个模型在同一服务器,可以使用同一个URL
80 80  
81 81 mybatis:
... ...