structured-data-rag-design.md 8.2 KB

本项目结构化数据 RAG 思路与实现

1. 定位

结构化数据 RAG 负责从 H2、MySQL 等关系数据库中获取精确数据证据,适合回答:

  • 当前有多少条记录;
  • 哪个对象数值最大;
  • 按时间、状态、区域统计;
  • 排名、汇总、趋势和明细查询;
  • 需要精确数字而不是语义相似内容的问题。

其核心不是向量检索,而是:

自然语言问题
→ 选择或生成 SQL
→ 安全执行
→ 将表格结果包装为 RAG 证据
→ 交给回答模型解释

2. 当前实际运行方式

当前知识库绑定中配置了 defaultSql,因此工作台实际执行的是显式 SQL

SELECT *
FROM rescue_forces
WHERE status = 'READY'
ORDER BY capacity DESC
LIMIT 10

虽然前端传入:

{
  "allowTextToSql": true
}

但后端优先级是:

显式 sql/defaultSql
>
Vanna 自动 Text-to-SQL

因此当前工作台查询并没有实际调用 Vanna。

3. 总体流程

flowchart TD
    Q["用户问题"] --> K["KnowledgeBaseRagRetriever"]
    K --> S["StructuredDataRagRetriever"]
    S --> E{"是否存在显式 SQL"}
    E -->|是| X["使用 sql/defaultSql"]
    E -->|否| A{"是否允许自动生成"}
    A -->|否| W["返回 warning"]
    A -->|是| V["VannaSqlGenerationService"]
    V --> SC["读取表和字段 Schema"]
    SC --> B["RAG AI Bridge /text2sql"]
    B --> L["Vanna + LLM 生成 SQL"]
    X --> G["SqlGuardService"]
    L --> G
    G --> DB["JDBC 只读执行"]
    DB --> R["columns + rows"]
    R --> EVIDENCE["SQL_RESULT 证据"]
    EVIDENCE --> ANSWER["多源证据融合回答"]

4. 查询请求

结构化检索使用统一的 RagQuery

{
  "query": "查询当前可用且载员能力最高的救援力量",
  "sourceIds": ["1"],
  "topK": 5,
  "filters": {
    "allowTextToSql": true
  }
}
字段 含义
query 用户自然语言问题
sourceIds 数据源 ID
topK SQL 最大返回行数控制
allowTextToSql 没有显式 SQL 时,是否允许 Vanna 自动生成

5. 显式 SQL 模式

显式 SQL 可以来自请求:

{
  "sql": "SELECT ..."
}

也可以来自知识库数据源绑定:

{
  "defaultSql": "SELECT ..."
}

执行过程:

读取显式 SQL
→ 跳过 Vanna 和大模型
→ SQL Guard
→ JDBC 执行
→ 转换为 SQL_RESULT

优点是稳定、快速、低成本;缺点是不能根据每次用户问题调整查询条件。

例如当前用户即使询问 BUSY 状态,固定 SQL 仍可能查询 READY

6. Vanna 自动 Text-to-SQL 模式

删除 defaultSql 后,如果满足以下任一条件:

allowTextToSql = true
retrievalMode = AUTO_GENERATE

系统会调用 VannaSqlGenerationService

6.1 收集数据源上下文

Java 根据数据源 ID 获取:

  • 数据库类型和方言;
  • Schema 名称;
  • 表名;
  • 字段名;
  • 字段类型;
  • 允许访问的表;
  • 最大返回行数。

并拼装类似 DDL:

TABLE rescue_forces (
    id BIGINT,
    name VARCHAR,
    type VARCHAR,
    status VARCHAR,
    capacity INTEGER
);

6.2 业务说明

当前救援演示加入了业务口径:

rescue_forces.status 使用 READY 表示当前可用,BUSY 表示忙碌。
结构化数据源只查询可用救援力量。
任务阶段由图数据源回答,不要在 SQL 中查询或拼接任务阶段。

业务说明的作用是将数据库字段转换成模型能理解的业务语义。

6.3 Few-shot 示例

问题:查询当前可用且载员能力最高的救援力量

SQL:
SELECT *
FROM rescue_forces
WHERE status = 'READY'
ORDER BY capacity DESC
LIMIT 1

6.4 调用 Vanna Bridge

Java 向 RAG AI Bridge 发送:

POST http://127.0.0.1:18733/text2sql

请求大致为:

{
  "query": "查询当前可用且载员能力最高的救援力量",
  "datasourceId": 1,
  "dialect": "mysql",
  "ddl": "TABLE rescue_forces (...)",
  "documentation": "READY 表示当前可用……",
  "examples": [
    {"question": "……", "sql": "SELECT ..."}
  ],
  "tableWhitelist": ["rescue_forces"],
  "maxRows": 5
}

7. 当前 Vanna 的使用程度

当前使用的是请求级 Vanna 上下文

  • 每次请求临时传入 DDL;
  • 临时传入业务说明;
  • 临时传入问题/SQL 示例;
  • 使用 Vanna 的提示构造和 SQL 生成能力。

当前没有建立持久化的 Vanna 训练知识库,也没有通过向量检索动态选择相关 DDL、文档和历史 SQL。

因此当前自动模式可以准确描述为:

Java 构造当前数据源上下文,Vanna 组织上下文并调用 LLM 生成 SQL。

8. 两层安全控制

8.1 Python Bridge

Bridge 负责:

  • 从 Markdown 或模型解释中提取 SQL;
  • 去掉末尾分号;
  • 只允许 SELECTWITH
  • 禁止 DML、DDL 和危险操作;
  • 要求结果不超过 maxRows

8.2 Java Guard

SqlGuardService 再次检查:

  • 只允许 SELECTWITHEXPLAIN
  • 禁止分号和多语句;
  • 禁止 SQL 注释;
  • 禁止 INSERTUPDATEDELETEDROPALTER 等;
  • 禁止访问配置中的系统 Schema。

数据库账号还应配置为只读账号,作为最终安全边界。

9. JDBC 执行

校验通过后,系统:

  1. 根据数据源 ID 读取连接配置;
  2. 解密数据库密码;
  3. 建立 JDBC 连接;
  4. 设置查询超时;
  5. 限制结果行数;
  6. 执行 SQL;
  7. 返回列名、数据行和耗时。

当前数据源名称为 MaritimeRescue,固定查询返回 READY 状态的救援力量,并按 capacity 降序排列。

10. 结构化数据证据

执行结果封装为:

{
  "sourceType": "STRUCTURED_DATA",
  "evidenceType": "SQL_RESULT",
  "title": "MaritimeRescue",
  "content": "查询返回5行,列:id, name, type, capacity, status",
  "score": 1.0,
  "payload": {
    "columns": ["id", "name", "type", "capacity", "status"],
    "rows": [],
    "rowCount": 5
  },
  "metadata": {
    "sql": "SELECT ...",
    "explicitSql": "SELECT ...",
    "executionTimeMs": 0,
    "warnings": []
  }
}

判断 SQL 来源:

  • explicitSql:固定 SQL;
  • generatedSql:Vanna 自动生成 SQL。

score=1.0 不是相似度,而是数据库实际执行得到的确定性证据标记。

11. 与文档 RAG、Graph RAG 的分工

以问题“东海海上目标救援任务中,可用救援力量和任务阶段分别是什么?”为例:

子问题 数据源 查询方式
当前可用救援力量 关系数据库 显式 SQL 或 Vanna Text-to-SQL
任务包含哪些阶段 Neo4j 显式 Cypher 或 Text-to-Cypher
制度、背景和方案说明 Milvus BGE-M3 + BM25

结构化数据库负责精确数据,不应让 SQL 查询承担图关系或文档语义问题。

12. 当前局限

  1. 当前 defaultSql 使 Vanna 无法实际参与工作台查询;
  2. 自动模式会把数据源中的所有表拼入 DDL,表多时上下文过大;
  3. 业务说明和示例是救援演示专用的硬编码;
  4. SQL 安全主要依靠正则规则,没有使用 SQL AST;
  5. 没有 EXPLAIN 预执行和扫描量控制;
  6. 没有持久化积累已验证的问题/SQL;
  7. 没有自动区分指标口径、时间字段和业务同义词。

13. 后续演进方向

  1. 删除演示知识库中的固定 defaultSql,实际启用 Vanna;
  2. 对表、字段、业务文档和历史 SQL 建立语义索引;
  3. 先检索相关 Schema,再提交给 Vanna;
  4. 持久化审核通过的问题/SQL 示例;
  5. 使用 SQL AST 校验表、字段和操作类型;
  6. 增加 EXPLAIN、超时、扫描量和结果量限制;
  7. SQL 失败时将数据库错误反馈给生成模型,最多修复一次;
  8. 将 SQL 结果转换为更紧凑的事实摘要,减少最终回答模型的上下文消耗。

14. 关键代码

  • KnowledgeBaseRagRetriever:多源知识库调度;
  • StructuredDataRagRetriever:结构化检索主入口;
  • ExplicitSqlGenerationService:显式 SQL;
  • VannaSqlGenerationService:自动 Text-to-SQL;
  • RagAiBridgeClient:调用 /text2sql
  • SqlGuardService:Java 只读安全校验;
  • SchemaExplorerServiceImpl:读取 Schema 并执行 SQL;
  • DataSourceService:管理数据源和凭据。