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: