GPU_GUARD_MONOREPO/docs/superpowers/specs/2026-05-15-mysql-question-answering-design.md

453 lines
20 KiB
Markdown
Raw Normal View History

2026-05-20 21:39:12 +08:00
# MySQL 智能问数设计
> 日期2026-05-15
> 状态Draft v2
> Owner与 lixin 共识
> 范围MySQL 只读数据源配置、Agent Tool、跨表 JOIN、问数 Prompt Skill
## 0. v2 架构修订
v2 根据系统架构 review 收紧了安全边界:
- `mysql_query` MVP 只允许 `SELECT`,不再允许自由 `SHOW``DESCRIBE``EXPLAIN`
- schema、索引、外键和少量样例通过受控工具读取不通过自由 SQL 读取。
- 增加表级白名单、表级黑名单和列级脱敏配置。
- 增加 JOIN 成本保护:查询超时、最大 JOIN 表数、禁止无条件 JOIN、结果行数硬上限。
- 增加连接池生命周期设计。
- 增加查询审计表。
- 明确新增公共 `SecretCryptoService`,不复用当前 SkillSecretService 的明文写入行为。
- 区分后台安全投影和 Agent 工具投影,`mysql_list_sources` 不向 Agent 暴露 host、username、passwordEncrypted。
- 对显式 `LIMIT` 做硬上限校验,禁止用超大 LIMIT 绕过结果限制。
- MVP 对 `maskedColumns` 采取保守策略:自由 SQL 直接引用脱敏列时拒绝执行,避免 alias 绕过。
## 1. 背景与目标
Neta 当前已经有完整的 Agent Tool 治理、Skill Runtime 和 Agent 配置体系。智能问数能力需要同时满足两类需求:
- 平台能力:配置 MySQL 连接、加密保存密码、控制哪些 Agent 可用、执行只读 SQL、限制超时和返回行数。
- 问数流程:让 Agent 先理解 schema必要时澄清业务口径再生成 SQL最后解释结果、口径和限制。
因此本功能采用“Tool 做执行能力Skill 做问数方法论”的组合方案。MySQL 连接和 SQL 执行进入 NetaClaw Tool 系统;问数策略进入一个 prompt skill供需要问数的 Agent 绑定。
## 2. 非目标
第一版不实现以下能力:
- 不支持 PostgreSQL、SQLite、SQL Server、Oracle 等其他数据库。
- 不执行 INSERT、UPDATE、DELETE、CREATE、ALTER、DROP、TRUNCATE、CALL、事务语句或存储过程。
- 不做可视化 BI 图表、不新增复杂 dashboard。
- 不做自然语言到 SQL 专用模型微调。
- 不自动优化复杂 SQL只提供 schema、样例和执行反馈让 Agent 在只读约束内生成 SQL。
- 不跨多个数据源做 JOIN跨表 JOIN 仅限同一个 MySQL 数据源和当前连接可访问的库。
- 第一版不允许用户通过 `mysql_query` 自由执行 `SHOW``DESCRIBE``EXPLAIN`;这些元信息由 `mysql_schema` 等受控工具提供。
## 3. 用户体验
管理员在后台新增 MySQL 数据源,填写名称、连接地址、端口、数据库名、账号和密码。密码只保存加密值,不在接口回显明文。
管理员在 Agent 配置中启用 `mysql` toolset并按需绑定 `data-analyst-mysql` skill。用户在对话中问“上个月各门店销售额是多少”“按客户和订单表统计复购率”这类问题时Agent 会先读取 schema必要时读取表样例再执行只读 SQL。回答中需要包含
- 结论。
- 使用的 SQL。
- 关联口径,尤其是跨表 JOIN 的关联字段。
- 结果限制例如采样、LIMIT、字段缺失或关联字段为推断关系。
## 4. 架构
整体架构沿用 NetaClaw 当前工具链路:
```text
netaclaw_data_source + netaclaw_data_source_query_audit
-> SecretCryptoService + DataSourceService 授权过滤
-> MysqlPoolManager + MysqlQueryService / MysqlIntrospectionService
-> mysql_list_sources / mysql_schema / mysql_table_sample / mysql_query
-> Tool Catalog + Tool Governance + Tool Resolver
-> Agent Runtime
-> data-analyst-mysql Skill 约束问数流程
```
新增能力分为四层:
1. 数据源配置层:保存 MySQL 连接和 Agent 授权。
2. MySQL 执行层封装连接池、schema 查询、样例读取和只读 SQL 执行。
3. Tool 层:向 Agent 暴露稳定工具接口,并接入 catalog、resolver、runtime policy。
4. Skill 层定义问数流程、JOIN 推理规则和回答格式。
## 5. 数据模型
新增实体 `NetaClawDataSourceEntity`,表名 `netaclaw_data_source`
字段:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `name` | varchar(100), unique | 数据源唯一名,工具调用使用 |
| `label` | varchar(200) | 展示名 |
| `type` | varchar(20) | 固定为 `mysql` |
| `host` | varchar(255) | MySQL host |
| `port` | int | 默认 3306 |
| `database` | varchar(128) | 默认库名 |
| `username` | varchar(255) | 连接用户名 |
| `passwordEncrypted` | text | AES-256-GCM 加密密码 |
| `readonly` | int | 1 表示只读MVP 固定为 1 |
| `status` | int | 0 禁用1 启用 |
| `allowedAgentIds` | json | 为空表示不授权任何 Agent数组内 Agent 可用 |
| `extra` | json | SSL、connectTimeout、queryTimeout、maxRows、allowedTables、blockedTables、maskedColumns、maxJoinTables 等扩展 |
`extra` 权限字段:
| 字段 | 说明 |
| --- | --- |
| `allowedTables` | 可访问表白名单,空数组表示默认不开放任何业务表 |
| `blockedTables` | 表黑名单,优先级高于白名单 |
| `maskedColumns` | 需要脱敏的列,格式为 `{ "table.column": "hash|partial|redact" }` |
| `schemaVisibility` | `allowed-only``all-names-only`MVP 默认 `allowed-only` |
| `queryTimeoutMs` | 单次 SQL 超时,默认 10000硬上限 30000 |
| `maxRows` | 默认 200硬上限 1000 |
| `maxJoinTables` | 默认 4硬上限 6 |
| `poolConnectionLimit` | 默认 3硬上限 10 |
密码必须用新增公共 `SecretCryptoService` 加密保存。当前 `SkillSecretService.encrypt()` 新写入路径是 JSON 明文,不能作为数据源密码的加密实现直接复用。
服务端必须区分两种投影:
- 管理端安全投影:可返回 `host``port``database``username``hasPassword`、授权与权限配置,但不返回 `passwordEncrypted` 或明文密码。
- Agent 工具投影:`mysql_list_sources` 只返回 `name``label``database``status`,不返回 `host``username``passwordEncrypted`、权限细节或连接串。
新增实体 `NetaClawDataSourceQueryAuditEntity`,表名 `netaclaw_data_source_query_audit`
字段:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `dataSourceId` | int | 数据源 ID |
| `agentId` | int | 调用 Agent |
| `userId` | int | 当前用户,可为空 |
| `toolCallId` | varchar(100) | 工具调用 ID |
| `sqlHash` | varchar(64) | SQL SHA-256 |
| `sqlPreview` | text | 截断后的 SQL 摘要,不含结果集 |
| `status` | varchar(20) | success / rejected / failed |
| `rejectReason` | varchar(100) | 被 guard 拒绝原因 |
| `elapsedMs` | int | 耗时 |
| `rowCount` | int | 返回行数 |
| `errorCode` | varchar(50) | MySQL errno / SQLSTATE可为空 |
## 6. 后端服务
新增 `SecretCryptoService`
- `encryptJson(value: Record<string, string>): string`
- `decryptJson(ciphertext: string): Record<string, string>`
- `encryptText(value: string): string`
- `decryptText(ciphertext: string): string`
- 使用 AES-256-GCM密钥来自 `NETA_SECRET_KEY`,开发环境可回退到 `APP_SECRET`
- 密文必须带版本化 envelope例如 base64(JSON.stringify({ v: 1, alg: 'aes-256-gcm', iv, tag, ct })),便于后续密钥轮换、格式识别和 Skill secret 迁移。
- 新数据源密码只允许写入加密格式Skill secret 的现有兼容逻辑可后续迁移到该公共服务。
新增 `DataSourceService`
- `listForAgent(agentId: number): Promise<AgentDataSourceSummary[]>`
- `listAdminSafe(): Promise<AdminSafeDataSource[]>`
- `getAuthorizedSource(name: string, agentId: number): Promise<NetaClawDataSourceEntity>`
- `saveConfig(input): Promise<AdminSafeDataSource>`
- `testConnection(input): Promise<{ ok: boolean; error?: string }>`
- 应用 `allowedAgentIds``allowedTables``blockedTables``maskedColumns``schemaVisibility`
- 编辑已有数据源并测试连接时,如果没有传新密码,必须从数据库读取旧 `passwordEncrypted`,不能依赖接口回显密文。
新增 `MysqlPoolManager`
- `getPool(source): Promise<Pool>`
-`dataSource.id` 缓存连接池。
- 默认 `connectionLimit=3`,由 `extra.poolConnectionLimit` 覆盖但不得超过 10。
- 保存、禁用或删除数据源时关闭对应 pool。
- 连接失败时不泄漏 host、username、password只返回脱敏错误。
新增 `MysqlIntrospectionService`
- `listSchema(source, options): Promise<MysqlSchemaResult>`
- 读取 `information_schema.columns``statistics``key_column_usage`
- 只返回授权表。字段输出要应用 `maskedColumns` 标记,让 Agent 知道该列敏感MVP 中查询或样例请求直接引用该列时由后端拒绝,后续具备可靠列血缘解析后再支持脱敏展示。
新增 `MysqlQueryService`
- `executeReadOnly(source, sql, options): Promise<MysqlQueryResult>`
- 使用 `mysql2/promise`
- 每次查询设置超时、最大行数和结果截断。
- `SELECT` 查询若没有 `LIMIT`,外层包裹为 `SELECT * FROM (<sql>) AS neta_limited_query LIMIT ?`
- 显式 `LIMIT` 必须小于等于 `maxRows`;超过上限直接拒绝或重写为安全上限,不能先拉取大结果再在 Node 中 `slice`
- 执行前调用 SQL guard、授权表检查和 JOIN 成本检查。
- 自由 SQL 引用 `maskedColumns`MVP 直接拒绝执行,避免 `SELECT email AS e` 绕过脱敏;后续只有在引入 SQL parser 并能追踪 source column 到 output alias 后,才允许查询脱敏列并在返回前脱敏。
- 执行错误必须通过 `sanitizeMysqlError(err)` 脱敏,只返回 `code``sqlState``errno` 和固定短文案,不返回原始 `err.message`
- 执行后写 query audit不记录结果集。
## 7. Tool 设计
新增 toolset`mysql`
### mysql_list_sources
输入:
```json
{}
```
输出:
```json
{
"sources": [
{
"name": "sales_prod",
"label": "销售生产库",
"database": "sales",
"status": 1
}
]
}
```
只返回当前 Agent 授权的数据源摘要,不返回 host、username、passwordEncrypted、明文密码、连接串或表/列权限细节。
### mysql_schema
输入:
```json
{
"source": "sales_prod",
"tables": ["orders", "customers"]
}
```
`tables` 可选。为空时返回表摘要和字段摘要;指定时返回详细字段、索引和外键。
输出包含:
- `tables[].name`
- `tables[].comment`
- `tables[].columns[]`
- `tables[].primaryKey[]`
- `tables[].indexes[]`
- `tables[].foreignKeys[]`
### mysql_table_sample
输入:
```json
{
"source": "sales_prod",
"table": "orders",
"columns": ["id", "customer_id", "created_at"],
"limit": 5
}
```
用于辅助 Agent 判断字段含义和 JOIN key。`limit` 最大 20。表名和列名只能来自 introspection 结果,后端需要做 identifier 校验和反引号转义。
`mysql_table_sample` 不能通过拼接自由 SQL 绕过权限。后端必须先校验:
- `table``allowedTables` 内且不在 `blockedTables` 内。
- `columns` 均来自 `mysql_schema` 可见字段。
- 如果请求列命中 `maskedColumns`MVP 返回拒绝错误;后续可加入专门脱敏样例能力。
### mysql_query
输入:
```json
{
"source": "sales_prod",
"sql": "SELECT c.name, COUNT(*) AS order_count FROM customers c JOIN orders o ON o.customer_id = c.id GROUP BY c.name ORDER BY order_count DESC LIMIT 20"
}
```
允许:
- `SELECT`
- 单数据源内的跨表 JOIN
禁止:
- 多语句。
- SQL 注释,包括 `--``#``/* ... */` 和 MySQL 版本注释。
- DML、DDL、DCL、事务和存储过程。
- `SELECT ... INTO OUTFILE``LOAD_FILE``SLEEP``BENCHMARK`
- `SHOW``DESCRIBE``DESC``EXPLAIN` 自由 SQL。
- `UNION`、CTE、用户变量和临时表。
- 无限制返回大结果集。
输出:
```json
{
"columns": ["name", "order_count"],
"rows": [
{ "name": "A 客户", "order_count": 12 }
],
"rowCount": 1,
"truncated": false,
"elapsedMs": 38,
"sql": "SELECT ..."
}
```
## 8. SQL 安全策略
安全校验采用保守白名单规则。不要把 Skill prompt 当作安全边界,所有强制规则必须在 Tool/Service 层实现。
- 第一版优先采用 SQL parser 或词法 token guard如果没有稳定 parser就采用更严格的拒绝策略。
- 只允许单条 `SELECT`
- 拒绝 SQL 注释,避免版本注释和注释夹带绕过。
- 拒绝包含分号的多语句。
- 拒绝非白名单开头语句。
- 拒绝 `UNION`、CTE、`INTO`、用户变量、临时表、危险函数和文件访问能力。
- 查询涉及的表必须全部落在 `allowedTables` 内,并且不在 `blockedTables` 内。
- SQL guard 必须逐个检查 `JOIN` 片段:每个显式 `JOIN` 在下一个 `JOIN``WHERE``GROUP BY``ORDER BY``LIMIT` 或语句结束前都必须出现 `ON``USING`
- 自由 SQL 引用 `maskedColumns` 时直接拒绝,避免 alias 绕过。
- `SELECT` 必须限制行数;无 LIMIT 时后端包裹追加限制。
- 显式 LIMIT 必须不超过 `maxRows`
- 查询超时默认 10 秒。
- 最大返回行数默认 200硬上限 1000。
- 默认最大 JOIN 表数 4硬上限 6。
- 拒绝无 `ON` / `USING` 的显式 JOIN避免笛卡尔积。
- 错误信息只返回 SQLSTATE、errno/code 和固定短文案不返回原始连接错误、连接密码、host、username 或完整连接串。
- 每次查询无论成功、失败或被拒绝,都写入审计记录。
这层只保证工具层安全,不负责判断业务 SQL 是否“正确”。业务口径由 Skill 和 Agent 交互处理。
## 9. 跨表 JOIN 策略
跨表 JOIN 是 MVP 能力。Agent 可生成多表 `JOIN``LEFT JOIN` 和聚合查询,但必须遵循 Skill 约束:
- 先调用 `mysql_schema` 获取相关表字段、索引和外键。
- 有外键时优先使用外键关系。
- 无外键时,可根据字段名、字段类型、索引和样例值推断关联字段。
- 推断关联必须在回答中标明例如“orders.customer_id 与 customers.id 为字段名和样例推断关联”。
- 对不确定的关联关系或业务口径,必须先向用户澄清。
- JOIN 查询仍受 `mysql_query` 的只读、超时、LIMIT 和最大行数限制。
- JOIN 查询不得超过 `maxJoinTables`
- `JOIN` 必须带 `ON``USING`,不允许隐式逗号连接。
- 每个 `JOIN` 都必须有自己的 `ON``USING` 条件,不能只检查整条 SQL 是否出现过一次 `ON`
- 如果查询因超时或成本限制失败Agent 应缩小时间范围、减少表数量、添加过滤条件或向用户澄清。
## 10. Skill 设计
新增 prompt skill`packages/backend/skills/data-analyst-mysql/SKILL.md`
Frontmatter
```yaml
---
name: data-analyst-mysql
description: 使用 MySQL 工具进行只读智能问数,支持 schema 分析、跨表 JOIN、口径澄清和 SQL 结果解释。
version: 1.0.0
metadata:
skillType: llm
tags: [mysql, data-analysis, question-answering]
conditions:
requires_tools: ["mysql_list_sources", "mysql_schema", "mysql_query"]
---
```
Skill 指令要求:
- 先确认可用数据源。
- 先读取相关 schema再写 SQL。
- 需要 JOIN 时先找外键、索引和样例值。
- 跨表 JOIN 不确定时先解释拟采用的关联字段,必要时澄清。
- 不确定业务口径时使用 `clarify`
- 只执行只读 SQL。
- 回答必须包含结论、SQL、口径说明和限制。
- 不暴露连接地址、账号、密码。
- 不承诺“全量统计”除非 SQL 口径和过滤范围明确。
## 11. Tool Resolver 和 Runtime Policy
`tools/catalog.ts` 新增 `TOOLSET_MYSQL = 'mysql'`,并集中 import MySQL tool 文件。
MySQL 工具注册:
- `visibility: 'tool'`
- `capability: 'text'`
- `isCore: false`
- `canDisable: true`
主 Agent 可直接执行 MySQL 工具。子 Agent 默认不在 worker 本地执行 MySQL 工具runtime policy 应将这类工具路由为 `main-process-proxy``disabled`。推荐第一版走 `main-process-proxy`,避免子进程持有数据库密钥。
## 12. 管理接口
新增 admin controller`controller/admin/data_source.ts`
接口:
- `GET /admin/netaclaw/data-source/list`
- `POST /admin/netaclaw/data-source/save`
- `POST /admin/netaclaw/data-source/test`
- `POST /admin/netaclaw/data-source/delete`
保存接口接受明文密码,但只用于加密保存。若编辑时密码为空,则保留原密码。
Agent 编辑页后续可接入授权配置,但 MVP 后端先支持 `allowedAgentIds` 字段和接口。
## 13. 测试策略
测试采用 TDD。
优先新增单元测试:
- `mysql_sql_guard.test.ts`:验证只读 SQL 白名单、危险关键字、多语句、`SELECT INTO OUTFILE``SLEEP` 拒绝。
- `mysql_sql_guard.test.ts` 还要验证 `SHOW``DESCRIBE``EXPLAIN`、注释、`UNION`、CTE、用户变量、无条件 JOIN、部分 JOIN 缺少 ON/USING、显式 LIMIT 超过 maxRows、查询脱敏列、超过最大 JOIN 表数被拒绝。
- `mysql_schema_mapper.test.ts`:验证 information_schema 行映射为表、字段、索引和外键结构。
- `mysql_query_service.test.ts`:用 mock mysql pool 验证 LIMIT 包裹、超时参数、结果截断和错误脱敏。
- `mysql_query_service.test.ts` 还要验证表级授权、脱敏列拒绝、审计写入、pool 复用和 `mysql_table_sample` identifier 白名单。
- `secret_crypto.test.ts`:验证数据源密码使用 AES-GCM 加密,密文不包含明文密码。
- `data_source_projection.test.ts`验证管理端投影不返回密码Agent 投影不返回 host/username/passwordEncrypted。
- `tool_resolver.test.ts`:验证 `mysql` toolset 可以被 Agent 启用,并在子 Agent runtime profile 中不走 worker-local。
- `entity_exports.test.ts`:验证新增 entity 导出。
不在单元测试里连接真实 MySQL。真实连接测试通过 admin `test` 接口和后续手工环境验证完成。
## 14. 分阶段交付
### Phase 1后端最小闭环
- 新增数据源 entity、公共 secret crypto service、data source service。
- 新增 query audit entity。
- 新增公共 `SecretCryptoService`
- 新增 `MysqlPoolManager`
- 新增 MySQL SQL guard、schema mapper、query service。
- 新增四个工具:`mysql_list_sources``mysql_schema``mysql_table_sample``mysql_query`
- 接入 catalog、resolver、runtime policy。
- 新增 `data-analyst-mysql` prompt skill。
- 补充单元测试。
### Phase 2管理端配置
- 新增数据源管理接口。
- 前端工具管理或 Agent 编辑页接入数据源授权。
- 支持测试连接和编辑密码。
### Phase 3问数体验增强
- 根据真实问数记录优化 Skill。
- 增加 query result 前端结构化渲染。
- 增加审计日志查询页和慢查询提示。
## 15. 验收标准
- 管理员可以配置一个 MySQL 数据源并授权给指定 Agent。
- 数据源密码以 AES-GCM 密文保存,接口不回显密文或明文。
- 启用 `mysql` toolset 的 Agent 能列出授权数据源。
- Agent 只能读取授权表的 schema、字段、索引和外键。
- Agent 能读取授权表的少量样例。
- Agent 能执行只读 SELECT 查询。
- Agent 能执行同一数据源、授权表范围内的跨表 JOIN 查询。
- `SHOW``DESCRIBE``EXPLAIN`、多语句、注释、写入语句、无条件 JOIN 和超出 JOIN 表数限制的 SQL 被拒绝。
- 查询或样例请求命中 `maskedColumns`MVP 后端拒绝执行,避免 alias 绕过;后续如引入 SQL parser 再实现安全脱敏返回。
- 每次查询写入审计记录。
- 危险 SQL 被拒绝,错误原因可读且不泄漏连接秘密。
- Agent 绑定 `data-analyst-mysql` skill 后回答包含结论、SQL、口径和限制。
- 单元测试覆盖 SQL guard、schema mapper、query service、secret crypto、pool manager、tool resolver 和 entity 导出。