GPU_GUARD_MONOREPO/docs/superpowers/specs/2026-05-15-mysql-question-answering-design.md
2026-05-20 21:39:12 +08:00

20 KiB
Raw Permalink Blame History

MySQL 智能问数设计

日期2026-05-15 状态Draft v2 Owner与 lixin 共识 范围MySQL 只读数据源配置、Agent Tool、跨表 JOIN、问数 Prompt Skill

0. v2 架构修订

v2 根据系统架构 review 收紧了安全边界:

  • mysql_query MVP 只允许 SELECT,不再允许自由 SHOWDESCRIBEEXPLAIN
  • 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 自由执行 SHOWDESCRIBEEXPLAIN;这些元信息由 mysql_schema 等受控工具提供。

3. 用户体验

管理员在后台新增 MySQL 数据源,填写名称、连接地址、端口、数据库名、账号和密码。密码只保存加密值,不在接口回显明文。

管理员在 Agent 配置中启用 mysql toolset并按需绑定 data-analyst-mysql skill。用户在对话中问“上个月各门店销售额是多少”“按客户和订单表统计复购率”这类问题时Agent 会先读取 schema必要时读取表样例再执行只读 SQL。回答中需要包含

  • 结论。
  • 使用的 SQL。
  • 关联口径,尤其是跨表 JOIN 的关联字段。
  • 结果限制例如采样、LIMIT、字段缺失或关联字段为推断关系。

4. 架构

整体架构沿用 NetaClaw 当前工具链路:

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
schemaVisibility allowed-onlyall-names-onlyMVP 默认 allowed-only
queryTimeoutMs 单次 SQL 超时,默认 10000硬上限 30000
maxRows 默认 200硬上限 1000
maxJoinTables 默认 4硬上限 6
poolConnectionLimit 默认 3硬上限 10

密码必须用新增公共 SecretCryptoService 加密保存。当前 SkillSecretService.encrypt() 新写入路径是 JSON 明文,不能作为数据源密码的加密实现直接复用。

服务端必须区分两种投影:

  • 管理端安全投影:可返回 hostportdatabaseusernamehasPassword、授权与权限配置,但不返回 passwordEncrypted 或明文密码。
  • Agent 工具投影:mysql_list_sources 只返回 namelabeldatabasestatus,不返回 hostusernamepasswordEncrypted、权限细节或连接串。

新增实体 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 }>
  • 应用 allowedAgentIdsallowedTablesblockedTablesmaskedColumnsschemaVisibility
  • 编辑已有数据源并测试连接时,如果没有传新密码,必须从数据库读取旧 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.columnsstatisticskey_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 引用 maskedColumnsMVP 直接拒绝执行,避免 SELECT email AS e 绕过脱敏;后续只有在引入 SQL parser 并能追踪 source column 到 output alias 后,才允许查询脱敏列并在返回前脱敏。
  • 执行错误必须通过 sanitizeMysqlError(err) 脱敏,只返回 codesqlStateerrno 和固定短文案,不返回原始 err.message
  • 执行后写 query audit不记录结果集。

7. Tool 设计

新增 toolsetmysql

mysql_list_sources

输入:

{}

输出:

{
  "sources": [
    {
      "name": "sales_prod",
      "label": "销售生产库",
      "database": "sales",
      "status": 1
    }
  ]
}

只返回当前 Agent 授权的数据源摘要,不返回 host、username、passwordEncrypted、明文密码、连接串或表/列权限细节。

mysql_schema

输入:

{
  "source": "sales_prod",
  "tables": ["orders", "customers"]
}

tables 可选。为空时返回表摘要和字段摘要;指定时返回详细字段、索引和外键。

输出包含:

  • tables[].name
  • tables[].comment
  • tables[].columns[]
  • tables[].primaryKey[]
  • tables[].indexes[]
  • tables[].foreignKeys[]

mysql_table_sample

输入:

{
  "source": "sales_prod",
  "table": "orders",
  "columns": ["id", "customer_id", "created_at"],
  "limit": 5
}

用于辅助 Agent 判断字段含义和 JOIN key。limit 最大 20。表名和列名只能来自 introspection 结果,后端需要做 identifier 校验和反引号转义。

mysql_table_sample 不能通过拼接自由 SQL 绕过权限。后端必须先校验:

  • tableallowedTables 内且不在 blockedTables 内。
  • columns 均来自 mysql_schema 可见字段。
  • 如果请求列命中 maskedColumnsMVP 返回拒绝错误;后续可加入专门脱敏样例能力。

mysql_query

输入:

{
  "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 OUTFILELOAD_FILESLEEPBENCHMARK
  • SHOWDESCRIBEDESCEXPLAIN 自由 SQL。
  • UNION、CTE、用户变量和临时表。
  • 无限制返回大结果集。

输出:

{
  "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 在下一个 JOINWHEREGROUP BYORDER BYLIMIT 或语句结束前都必须出现 ONUSING
  • 自由 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 可生成多表 JOINLEFT JOIN 和聚合查询,但必须遵循 Skill 约束:

  • 先调用 mysql_schema 获取相关表字段、索引和外键。
  • 有外键时优先使用外键关系。
  • 无外键时,可根据字段名、字段类型、索引和样例值推断关联字段。
  • 推断关联必须在回答中标明例如“orders.customer_id 与 customers.id 为字段名和样例推断关联”。
  • 对不确定的关联关系或业务口径,必须先向用户澄清。
  • JOIN 查询仍受 mysql_query 的只读、超时、LIMIT 和最大行数限制。
  • JOIN 查询不得超过 maxJoinTables
  • JOIN 必须带 ONUSING,不允许隐式逗号连接。
  • 每个 JOIN 都必须有自己的 ONUSING 条件,不能只检查整条 SQL 是否出现过一次 ON
  • 如果查询因超时或成本限制失败Agent 应缩小时间范围、减少表数量、添加过滤条件或向用户澄清。

10. Skill 设计

新增 prompt skillpackages/backend/skills/data-analyst-mysql/SKILL.md

Frontmatter

---
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-proxydisabled。推荐第一版走 main-process-proxy,避免子进程持有数据库密钥。

12. 管理接口

新增 admin controllercontroller/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 OUTFILESLEEP 拒绝。
  • mysql_sql_guard.test.ts 还要验证 SHOWDESCRIBEEXPLAIN、注释、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_sourcesmysql_schemamysql_table_samplemysql_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 查询。
  • SHOWDESCRIBEEXPLAIN、多语句、注释、写入语句、无条件 JOIN 和超出 JOIN 表数限制的 SQL 被拒绝。
  • 查询或样例请求命中 maskedColumnsMVP 后端拒绝执行,避免 alias 绕过;后续如引入 SQL parser 再实现安全脱敏返回。
  • 每次查询写入审计记录。
  • 危险 SQL 被拒绝,错误原因可读且不泄漏连接秘密。
  • Agent 绑定 data-analyst-mysql skill 后回答包含结论、SQL、口径和限制。
  • 单元测试覆盖 SQL guard、schema mapper、query service、secret crypto、pool manager、tool resolver 和 entity 导出。