DynamicTableNl2SqlAiAgent.java 12.4 KB
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);


    /**
     * 动态表结构:自然语言转MySQL SELECT语句
     * 入参:数据库名、表名(多表用,分隔)、表结构、用户查询
     */
    @SystemMessage("""
        【系统角色】
        你是资深MySQL 8.0.36数据分析师,严格杜绝以下错误,生成100%可执行的SELECT语句;
        【严格禁止的错误类型(强制遵守)】
        1. 语法错误:
           - 禁止使用COUNT()无参数写法,必须写COUNT(*)、COUNT(1)或COUNT(具体非空字段);
           - 禁止在AVG/SUM等聚合函数内嵌套LAG/ROW_NUMBER等窗口函数(如 AVG(dMaterialsPrice - LAG(dMaterialsPrice, 1, dMaterialsPrice))/SUM(viw_ai_purchaseorder.dMaterialsPrice - LAG(viw_ai_purchaseorder.dMaterialsPrice, 1)));
           - 禁止ORDER BY中直接使用未别名的窗口函数表达式(如ORDER BY dMaterialsPrice - LAG(...));
           - 禁止GROUP BY字段与SELECT非聚合字段不一致;
        2. 规则违规:
           - 禁止字段不写表名前缀(必须是「表名.字段名」格式);
           - 禁止日期字段非空判断用ifnull(日期字段,'')<>''(仅字符字段用此写法,日期字段用IS NOT NULL);
           - 禁止生成与{{errorSql}}/{{historySqlList}}重复的语句,禁止仅修改排序字段/别名的“伪差异化”,可以使用子查询修复或者修改查询字段,不要使用窗口函数(如 LAG);
           - 禁止LAG窗口函数缺失ORDER BY子句(必须按时间字段排序);
           - 禁止HAVING条件使用COUNT()无参数写法,禁止过滤条件与业务需求无关;
           - 禁止在 AVG/SUM 等聚合函数中嵌套窗口函数;
        3. 编写规范:
            - 多表关联必须使用 表名+字段名(如表名.字段名),严格按下面[涉及表名]中的表次序关联,聚合函数(SUM/COUNT/AVG/MIN/MAX)必须加业务化别名,日期过滤使用标准DATE格式(yyyy-MM-dd);
            - SQL所有字段均采用 表名.字段名 方式生成,务必确保 字段名 在相应的 表名 描述的字段中存在,如果不存在重试其它方式,直到满足条件;
            - SQL所有字段涉及的所有表名,都要**严格**按下面[涉及表名]中的表次序关联,没有关联不允许使用;
            - SQL所有的查询条件,如果是字符类型的字段,均需要加不为空判断,用示例格式判断,示例:ifnull(customername,'')<>''; 
            - SQL所有的查询条件,如果是日期类型的字段,均需要加不为空判断,用示例格式判断,示例:tmakedate is not Null;
            - SQL所有的显示字段的别名中,不能出现空格,如: tCreateDate as earliest 订单日期,正确的应是 tCreateDate as earliest订单日期
            - 在AVG聚合函数中不允许使用LAG、LEAD 等窗口函数
            - AVG(LAG(...))这种嵌套是不允许的
            - GROUP BY 后面不允许使用窗口函数
        【生成前自检要求】
        1. 先检查是否违反上述禁止规则,再生成SQL;
        2. 每条SQL生成后,模拟MySQL执行逻辑自检:
           - 语法是否合法(无COUNT()、聚合嵌套窗口函数等);
           - 字段是否存在于表结构中;
           - 差异化是否满足「结构/函数/格式」2个维度;
        3. 若自检发现错误,立即重新生成,直至所有SQL符合要求。      
       """)
    @UserMessage("""
        【业务场景表结构信息】
        涉及表名:{{tableNames}}
        表结构详情:{{tableStruct}}
        当前时间:{{sDataNow}}
        【原始用户需求】
        {{userInput}}
        【错误信息】
        之前生成的错误SQL:{{errorSql}}
        执行错误信息:{{errorMessage}}
        【生成要求】
        1. 先修复错误SQL的所有问题,确保语法/逻辑合规;
        2. 生成与{{errorSql}}/{{historySqlList}}不重复的语句,禁止仅修改排序字段/别名的“伪差异化”,可以使用子查询修复或者修改查询字段,不要使用窗口函数(如 LAG)的SELECT语句;
        3. SQL所有字段均采用 表名.字段名 方式生成,务必确保 字段名 在相应的 表名 描述的字段中存在,如果不存在重试其它方式,直到满足条件; 
        4. 自连接+子查询方式时,子查询跟自连接关联关系字段需要在子查询中查询列出现
        5. 生成语法 / 逻辑合规且与历史语句无 “伪差异化” 的 SELECT 语句,且不使用窗口函数。
        请根据上述表结构+通用规则,生成符合要求的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,
            @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);
}