# 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` - `decryptJson(ciphertext: string): Record` - `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` - `listAdminSafe(): Promise` - `getAuthorizedSource(name: string, agentId: number): Promise` - `saveConfig(input): Promise` - `testConnection(input): Promise<{ ok: boolean; error?: string }>` - 应用 `allowedAgentIds`、`allowedTables`、`blockedTables`、`maskedColumns` 和 `schemaVisibility`。 - 编辑已有数据源并测试连接时,如果没有传新密码,必须从数据库读取旧 `passwordEncrypted`,不能依赖接口回显密文。 新增 `MysqlPoolManager`: - `getPool(source): Promise` - 按 `dataSource.id` 缓存连接池。 - 默认 `connectionLimit=3`,由 `extra.poolConnectionLimit` 覆盖但不得超过 10。 - 保存、禁用或删除数据源时关闭对应 pool。 - 连接失败时不泄漏 host、username、password,只返回脱敏错误。 新增 `MysqlIntrospectionService`: - `listSchema(source, options): Promise` - 读取 `information_schema.columns`、`statistics`、`key_column_usage`。 - 只返回授权表。字段输出要应用 `maskedColumns` 标记,让 Agent 知道该列敏感;MVP 中查询或样例请求直接引用该列时由后端拒绝,后续具备可靠列血缘解析后再支持脱敏展示。 新增 `MysqlQueryService`: - `executeReadOnly(source, sql, options): Promise` - 使用 `mysql2/promise`。 - 每次查询设置超时、最大行数和结果截断。 - `SELECT` 查询若没有 `LIMIT`,外层包裹为 `SELECT * FROM () 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 导出。