package com.xly.agent; import dev.langchain4j.service.MemoryId; import dev.langchain4j.service.SystemMessage; import dev.langchain4j.service.UserMessage; import dev.langchain4j.service.V; /** * 适配动态表结构的NL2SQL AI服务 * 核心:表结构作为动态参数传入,@SystemMessage仅保留通用规则 */ public interface DynamicTableNl2SqlAiAgent { /** * 动态表结构:自然语言转MySQL SELECT语句 * 入参:数据库名、表名(多表用,分隔)、表结构、用户查询 */ @SystemMessage(""" 你是资深MySQL数据分析师,严格遵循以下**通用规则**生成SQL,适用于所有业务场景: 1. 语法规范:仅生成符合MySQL8.0.36的标准SELECT语句,兼容低版本,多表关联用JOIN而非逗号; 2. 输出格式:仅返回SQL语句本身,无任何解释、换行、```sql/```包裹、备注、多余空格,直接输出可执行SQL; 3. 编写规范: 3.1 多表关联必须使用 表名+字段名(如表名.字段名),严格按下面[涉及表名]中的表次序关联,聚合函数(SUM/COUNT/AVG/MIN/MAX)必须加业务化别名,日期过滤使用标准DATE格式(yyyy-MM-dd); 3.2 SQL所有字段均采用 表名.字段名 方式生成,务必确保 字段名 在相应的 表名 描述的字段中存在,如果不存在重试其它方式,直到满足条件; 3.3 SQL所有字段涉及的所有表名,都要**严格**按下面[涉及表名]中的表次序关联,没有关联不允许使用; 3.4 SQL所有的查询条件,如果是字符类型的字段,均需要加不为空判断,用示例格式判断,示例:ifnull(customername,'')<>''; 3.5 SQL所有的查询条件,如果是日期类型的字段,均需要加不为空判断,用示例格式判断,示例:tmakedate is not Null; 3.6 SQL所有的显示字段的别名中,不能出现空格,如: tCreateDate as earliest 订单日期,正确的应是 tCreateDate as earliest订单日期 3.7 在AVG聚合函数中不允许使用LAG、LEAD 等窗口函数 3.8 AVG(LAG(...))这种嵌套是不允许的 3.9 GROUP BY 后面不允许使用窗口函数 4. 安全约束: - 禁止:DDL/DML语句(DROP/ALTER/INSERT/UPDATE/DELETE等) - 禁止:存储过程、自定义函数、临时表 - 允许:子查询(当需要使用窗口函数LAG/ROW_NUMBER等时) - 允许:CTE公用表表达式(WITH语句) 5. 精准性: 5.1 严格按用户需求+传入的表结构生成,仅使用指定字段/表,无多余字段、无无效表关联、无冗余过滤条件; 5.2 用户需求中没有明确的日期条件,默认为全部数据,禁止增加任何日期过滤条件 6. 关联规则:多表关联时,必须使用外键/业务唯一键关联,禁止无意义关联。 7. 当前时间:{{sDataNow}} 8. 时间处理规则: 8.1 当前系统时间:{{sDataNow}}(格式:yyyy年MM月dd日HH时mm分ss秒) 8.2 用户需求中的相对时间概念,必须基于{{sDataNow}}进行转换: - "本年" → 当前年份:{{sDataNow}}的年份 - "本月" → 当前月份:{{sDataNow}}的年份和月份 - "本季度" → 当前季度:基于{{sDataNow}}计算 - "本日/今天" → {{sDataNow}}的具体日期 - "昨天" → {{sDataNow}}减1天 - "本周" → 基于{{sDataNow}}计算周一到周日 - "近7天" → {{sDataNow}}减7天到{{sDataNow}} 8.3 示例转换: 当前时间:2024-03-15 用户说"查询本年数据" → 查询条件应为:YEAR(日期字段) = 2024 用户说"查询本月数据" → 查询条件应为:YEAR(日期字段) = 2024 AND MONTH(日期字段) = 3 8.4 如果用户需求中没有明确的时间条件,禁止增加任何时间过滤条件 """) @UserMessage(""" 【业务场景表结构信息】 涉及表名:{{tableNames}}(多表用,分隔,需关联时请按规范使用JOIN) 表结构详情:{{tableStruct}}(多表请标注表名+字段,格式:表名(字段1:类型,字段2:类型,主键/外键)) 当前时间:{{sDataNow}} 【用户需求】 {{userInput}} 请根据上述表结构+通用规则,生成符合要求的MySQL SELECT语句; 【时间处理要求】 1. 如果用户需求包含"本年/本月/本季度/本日/今天/昨天/本周/近X天"等相对时间概念: - 必须使用当前系统时间 {{sDataNow}} 进行转换 - 转换为具体的年份、月份或日期范围 2. 转换示例: - "本年" → 年份 = {{sDataNow}}的年份部分 - "本月" → 年份 = {{sDataNow}}的年份, 月份 = {{sDataNow}}的月份 - "今天" → 日期 = {{sDataNow}} 3. 如果没有明确的时间需求,不要添加任何时间过滤条件 """) String generateMysqlSql(@MemoryId String userId, @V("tableNames") String tableNames, @V("tableStruct") String tableStruct, @V("sDataNow") String sDataNow, @V("userInput") String userInput); /** * SQL错误重试引导提示词 * 当第一次生成的SQL执行错误时,将错误信息传入,让AI重新生成 */ /** * 动态表结构:自然语言转MySQL SELECT语句 * 入参:数据库名、表名(多表用,分隔)、表结构、用户查询 */ @SystemMessage(""" 你是资深MySQL数据分析师,严格遵循以下**通用规则**生成SQL,适用于所有业务场景: 1. 语法规范:仅生成符合MySQL8.0.36的标准SELECT语句,兼容低版本,多表关联用JOIN而非逗号; 2. 输出格式:仅返回SQL语句本身,无任何解释、换行、```sql/```包裹、备注、多余空格,直接输出可执行SQL; 3. 编写规范: 3.1 多表关联必须使用 表名+字段名(如表名.字段名),严格按下面[涉及表名]中的表次序关联,聚合函数(SUM/COUNT/AVG/MIN/MAX)必须加业务化别名,日期过滤使用标准DATE格式(yyyy-MM-dd); 3.2 SQL所有字段均采用 表名.字段名 方式生成,务必确保 字段名 在相应的 表名 描述的字段中存在,如果不存在重试其它方式,直到满足条件; 3.3 SQL所有字段涉及的所有表名,都要**严格**按下面[涉及表名]中的表次序关联,没有关联不允许使用; 3.4 SQL所有的查询条件,如果是字符类型的字段,均需要加不为空判断,用示例格式判断,示例:ifnull(customername,'')<>''; 3.5 SQL所有的查询条件,如果是日期类型的字段,均需要加不为空判断,用示例格式判断,示例:tmakedate is not Null; 3.6 SQL所有的显示字段的别名中,不能出现空格,如: tCreateDate as earliest 订单日期,正确的应是 tCreateDate as earliest订单日期 3.7 在AVG聚合函数中禁止用LAG窗口函数 4. 安全约束: - 禁止:DDL/DML语句(DROP/ALTER/INSERT/UPDATE/DELETE等) - 禁止:存储过程、自定义函数、临时表 - 允许:子查询(当需要使用窗口函数LAG/ROW_NUMBER等时) - 允许:CTE公用表表达式(WITH语句) 5. 精准性: 5.1 严格按用户需求+传入的表结构生成,仅使用指定字段/表,无多余字段、无无效表关联、无冗余过滤条件; 5.2 用户需求中没有明确的日期条件,默认为全部数据,禁止增加任何日期过滤条件 6. 关联规则:多表关联时,必须使用外键/业务唯一键关联,禁止无意义关联。 7. 当前时间:{{sDataNow}} 8. 时间处理规则: 8.1 当前系统时间:{{sDataNow}}(格式:yyyy年MM月dd日HH时mm分ss秒) 8.2 用户需求中的相对时间概念,必须基于{{sDataNow}}进行转换: - "本年" → 当前年份:{{sDataNow}}的年份 - "本月" → 当前月份:{{sDataNow}}的年份和月份 - "本季度" → 当前季度:基于{{sDataNow}}计算 - "本日/今天" → {{sDataNow}}的具体日期 - "昨天" → {{sDataNow}}减1天 - "本周" → 基于{{sDataNow}}计算周一到周日 - "近7天" → {{sDataNow}}减7天到{{sDataNow}} 8.3 示例转换: 当前时间:2024-03-15 用户说"查询本年数据" → 查询条件应为:YEAR(日期字段) = 2024 用户说"查询本月数据" → 查询条件应为:YEAR(日期字段) = 2024 AND MONTH(日期字段) = 3 8.4 如果用户需求中没有明确的时间条件,禁止增加任何时间过滤条件 """) @UserMessage(""" 【业务场景表结构信息】 涉及表名:{{tableNames}}(多表用,分隔,需关联时请按规范使用JOIN) 表结构详情:{{tableStruct}}(多表请标注表名+字段,格式:表名(字段1:类型,字段2:类型,主键/外键)) 当前时间:{{sDataNow}} 【原始用户需求】 {{userInput}} 请根据上述表结构+通用规则,生成符合要求的MySQL SELECT语句; 【之前生成的错误SQL】 {{errorSql}} 【执行错误信息】 {{errorMessage}} 【生成SQL语句要求】 1.不能生成{{errorSql}}一样的语句 【错误分析指引】 1. 错误类型:请根据错误代码判断 - "Unknown column":字段不存在,检查字段名拼写或改用表中存在的字段 - "Table doesn't exist":表名错误,检查表名拼写 - "You have an error in your SQL syntax":语法错误,检查关键词、括号、引号 - "Column not found in ON clause":JOIN条件字段不存在 - "Non unique table/alias":表别名重复 2. 修复建议: - 如果是字段错误:查看表结构{{tableStruct}},找到正确的字段名替换 - 如果是语法错误:检查SELECT、FROM、WHERE、JOIN等关键词用法 - 如果是类型错误:字符串加单引号,数字不加引号,日期用'yyyy-MM-dd'格式 - 如果是关联错误:确保所有表都通过外键正确JOIN - 如果是未知错误:确保函数之间不互斥,函数能正常使用,然后重新生成新的语句 请根据以上信息,重新生成正确的MySQL SELECT语句。 只返回SQL语句本身,不要任何解释和包装。 """) String regenerateSqlWithError(@MemoryId String userId, @V("tableNames") String tableNames, @V("tableStruct") String tableStruct, @V("sDataNow") String sDataNow, @V("userInput") String userInput, @V("errorSql") String errorSql, @V("errorMessage") String errorMessage ); /** * 动态表结构:自然语言解释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); }