talents - Text2SqlTalent 数据库自然语言查询
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 标准兜底方言
- MySQL — 同时兼容 MariaDB、TiDB(通过
- 通过
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、工作流程:从意图到结果
阶段一:初始化与方言探测(onAttach → schemaInit)
- 采用双重检查锁(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 alias→AS "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、内置方言能力矩阵
| 方言 | 匹配的数据库产品 | 标识符转义 | 分页语法 | 特殊适配 |
|---|---|---|---|---|
| MySqlDialect | MySQL、MariaDB、TiDB | 反引号 ` | LIMIT n | findSchema 返回 null(MySQL 无 schema 层) |
| PostgreDialect | PostgreSQL、Kingbase、Highgo | 双引号 "" | LIMIT n | 区分大小写,别名必须加双引号 |
| OracleDialect | Oracle、Dameng(达梦) | 双引号 ""(强制大写) | ROWNUM <= n(子查询包装) | GROUP BY 严格要求 |
| SqliteDialect | SQLite | 双引号 "" | LIMIT n | 日期函数替代方案(strftime)、布尔值 0/1、findSchema 返回 null |
| H2Dialect | H2 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 消耗