Solon v4.0.2

talents - Text2SqlTalent 数据库自然语言查询

</> markdown
2026年6月10日 下午9:44:27

Text2SqlTalent 是一个面向 Agent 的结构化数据库对话组件,实现了从自然语言意图到安全 SQL 执行的完整闭环。它内置自适应方言引擎、分级元数据策略、语义关联引导和多层执行护栏,让 Agent 能够像数据分析师一样与数据库交互。

相关依赖包:solon-ai-skill-text2sql

1、核心特性

自适应方言引擎

  • 内置 6 种 SqlDialect 实现,覆盖主流数据库产品:
    • MySQL — 同时兼容 MariaDB、TiDB(通过 matched() 关键字匹配)
    • PostgreSQL — 兼容人大金仓(Kingbase)、瀚高(Highgo)
    • Oracle — 兼容达梦(Dameng)
    • SQLite — 针对缺失标准日期函数及弱类型特性进行优化
    • H2 — 支持兼容模式自动检测(如检测到 MySQL 模式会自动切换为 MySqlDialect)
    • Generic SQL — ANSI SQL 标准兜底方言
  • 通过 SqlDialectManager 统一注册与检索,支持用户自定义方言扩展(SqlDialectManager.register()
  • 自动处理标识符转义、分页语法差异(LIMIT / ROWNUM / FETCH FIRST)、系统函数差异

分级元数据策略SchemaMode):

  • FULL 模式(表 <= 20):在 onAttach 阶段自动注入全量表结构信息(字段名、类型、长度、是否可空、主键标记、备注)到 System Prompt,AI 拥有"全局视角",无需额外工具调用
  • DYNAMIC 模式(表 > 20 或手动指定):仅注入表清单与外键关系地图到 Instruction,引导 AI 在需要时通过 get_table_schema 工具按需探测具体字段,有效节省 Token 消耗

语义关联引导

  • 通过 JDBC DatabaseMetaData.getImportedKeys() 自动提取外键关系,格式化为 table.fk_col -> pk_table.pk_col 的关联描述
  • 同时提取表级别和列级别的 COMMENT 备注,为 AI 提供多表 Join 的"语义线索"

执行安全护栏(四层防护):

  • 只读校验readOnly(true) 时强制拦截非 SELECT 语句
  • 别名修复:通过正则 AS_PATTERN 自动为 AI 遗漏引号的别名添加方言转义(如 MySQL 加反引号、Oracle/PG 加双引号)
  • 分页注入:当 SQL 未包含 LIMIT/ROWNUM/FETCH FIRST/TOP 时,自动追加方言对应的分页语法(默认 maxRows=50)
  • 结果截断:查询结果超出 maxContextLength(默认 8000 字符)时自动截断,防止上下文溢出

2、配置参数

参数方法默认值说明
maxRows.maxRows(int)50单次查询返回最大行数,控制分页注入的 LIMIT 值
maxContextLength.maxContextLength(int)8000查询结果 JSON 的最大字符长度,超出时截断并追加 [Truncated] 标记
schemaMode.schemaMode(SchemaMode)自动判断(表<=20 为 FULL,>20 为 DYNAMIC)元数据注入策略
readOnly.readOnly(boolean)true是否强制只读模式(仅允许 SELECT)
dialect.dialect(SqlDialect)自动识别手动指定方言适配器,覆盖自动检测

构造函数支持两种数据源接入方式:

// 方式一:直接传入 DataSource
new Text2SqlTalent(dataSource, "users", "orders")

// 方式二:传入 SqlUtils 实例(适用于已有 SqlUtils 的场景)
new Text2SqlTalent(sqlUtils, "users", "orders")

3、应用示例

将数据库变成智能体的知识库,只需几行代码:

// 1. 初始化 Talent,指定数据源及允许 AI 访问的表范围
Text2SqlTalent sqlTalent = new Text2SqlTalent(dataSource, "users", "orders", "order_refunds")
        .maxRows(50)            // 限制单次返回最大行数,防止大表扫描
        .readOnly(true)         // 强制只读模式,拦截任何写操作
        .maxContextLength(8000) // 结果截断长度,保护 AI 上下文
        .schemaMode(SchemaMode.DYNAMIC); // 如果表非常多,建议手动开启动态模式

// 2. 构建 ReAct 智能体
ReActAgent agent = ReActAgent.of(chatModel)
        .role("财务分析专家")
        .instruction("你负责分析订单与退款数据。请结合表备注理解字段含义,金额单位均为元。")
        .defaultTalentAdd(sqlTalent)
        .build();

// 3. 执行任务
// Agent 会经历:Reasoning (分析表名) -> Action (生成/执行 SQL) -> Observation (结果分析)
String response = agent.prompt("张三在 2024 年一共有多少笔退款订单?总金额是多少?")
       .call()
       .getContent();

手动指定方言的示例(适用于自动识别不准确的场景):

Text2SqlTalent sqlTalent = new Text2SqlTalent(dataSource, "users", "orders")
        .dialect(new PostgreDialect())  // 强制使用 PostgreSQL 方言
        .readOnly(true);

4、工作流程:从意图到结果

阶段一:初始化与方言探测onAttachschemaInit

  • 采用双重检查锁(Double-Check Locking)保证线程安全的懒加载
  • 通过 Connection.getMetaData().getDatabaseProductName() 获取数据库产品名
  • SqlDialectManager.select() 遍历已注册方言,调用每个方言的 matched() 进行关键字匹配
  • 匹配成功后,调用 dialect.adaptDialect(conn) 进行动态适配(如 H2 检测兼容模式)
  • 加载所有指定表的备注(REMARKS)和外键关联关系(ImportedKeys)

阶段二:Schema 注入getInstruction

  • AI 在 System Prompt 中会看到当前数据库的方言类型(如 MySQL)和系统时间
  • FULL 模式:AI 直接获得所有表的完整字段结构(字段名、类型、长度、是否可空、主键标记、备注)
  • DYNAMIC 模式:AI 仅获得表名清单与外键关系线索,需先调用 get_table_schema 探测后再编写 SQL
  • 注入方言特定的执行指引(dialect.getCustomInstruction()),如 SQLite 的日期函数替代方案

阶段三:SQL 修复与执行execute_sql

  • 自动去除末尾分号
  • 正则修复别名(AS aliasAS "alias" / AS \alias``),处理 AI 容易遗漏的引号问题
  • 只读模式校验(仅允许 SELECT 开头的语句)
  • 分页补全:检测 SQL 是否已包含 LIMIT/ROWNUM/FETCH FIRST/TOP,未包含则自动注入
  • 结果转 JSON 返回,超出 maxContextLength 时截断

阶段四:纠错反馈

  • 如果执行报错,Talent 利用方言引擎的 getErrorHint() 将复杂的 SQL 错误转化为对 AI 友好的纠错建议
  • AI 可根据 Hint 调整 SQL 后重试

5、内置工具(Tool)

工具名称触发条件功能说明
execute_sql始终可用执行 SELECT 查询。自动进行别名修复、只读检查、分页补全和结果截断
get_table_schema仅 DYNAMIC 模式按需获取指定表的详细字段定义(字段名、类型、长度、是否可空、主键标记、备注),不在 FULL 模式下暴露以避免冗余

6、内置方言能力矩阵

方言匹配的数据库产品标识符转义分页语法特殊适配
MySqlDialectMySQL、MariaDB、TiDB反引号 `LIMIT nfindSchema 返回 null(MySQL 无 schema 层)
PostgreDialectPostgreSQL、Kingbase、Highgo双引号 ""LIMIT n区分大小写,别名必须加双引号
OracleDialectOracle、Dameng(达梦)双引号 ""(强制大写)ROWNUM <= n(子查询包装)GROUP BY 严格要求
SqliteDialectSQLite双引号 ""LIMIT n日期函数替代方案(strftime)、布尔值 0/1、findSchema 返回 null
H2DialectH2 Database双引号 ""(强制大写)LIMIT n兼容模式检测:若 MODE=MySQL 则自动切换为 MySqlDialect
GenericDialect其他(兜底)双引号 ""FETCH FIRST n ROWS ONLY遵循 ANSI SQL 标准

自定义方言扩展:

// 实现 SqlDialect 接口
public class MyDialect implements SqlDialect {
    // ... 实现各方法
}

// 注册到方言管理器(优先于默认方言匹配)
SqlDialectManager.register(new MyDialect());

7、Instruction 结构详解

当 Talent 被 Attach 到 Agent 时,getInstruction() 会生成结构化的提示词,包含三个章节:

## 1. 环境上下文
- 数据库方言: MySQL
- 系统时间: 2024-01-15 10:30:00

## 2. 数据库结构 (Schema)     ← FULL 模式:完整字段列表
   或 数据库目录与关系地图     ← DYNAMIC 模式:仅表名 + 外键线索

## 3. 执行准则
1. 权限: 只读模式,严禁写操作。
2. 方言特供指引: [dialect.getCustomInstruction()]
3. 结果截断: 默认必须分页,仅展示前 50 条。
4. 纠错逻辑: 若执行报错,请对比方言要求检查引号、函数名或保留字。

8、最佳实践建议

  • 完善注释:在数据库中为表和关键字段添加 COMMENT。Text2SqlTalent 会通过 DatabaseMetaData 自动提取这些备注,这是提升 AI 字段识别准确率的最有效手段
  • 权限隔离:虽然 Talent 有 readOnly(true) 护栏,但仍强烈建议在数据库层面为 AI 连接配置只读账号
  • 表范围控制:在构造函数中只传入当前任务相关的表名,减少干扰并显著降低 Token 消耗
  • 手动指定方言:对于使用兼容模式的数据库(如 H2 的 MySQL 模式),建议通过 .dialect() 手动指定,或依赖 adaptDialect() 的自动检测
  • DYNAMIC 模式适用场景:当表字段总量超过 200 列或表数量超过 20 张时,建议使用 SchemaMode.DYNAMIC,可显著减少首次 Instruction 的 Token 消耗