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

## 1. 定位

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

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

其核心不是向量检索，而是：

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

## 2. 当前实际运行方式

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

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

虽然前端传入：

```json
{
  "allowTextToSql": true
}
```

但后端优先级是：

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

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

## 3. 总体流程

```mermaid
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`：

```json
{
  "query": "查询当前可用且载员能力最高的救援力量",
  "sourceIds": ["1"],
  "topK": 5,
  "filters": {
    "allowTextToSql": true
  }
}
```

| 字段 | 含义 |
|---|---|
| `query` | 用户自然语言问题 |
| `sourceIds` | 数据源 ID |
| `topK` | SQL 最大返回行数控制 |
| `allowTextToSql` | 没有显式 SQL 时，是否允许 Vanna 自动生成 |

## 5. 显式 SQL 模式

显式 SQL 可以来自请求：

```json
{
  "sql": "SELECT ..."
}
```

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

```json
{
  "defaultSql": "SELECT ..."
}
```

执行过程：

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

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

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

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

删除 `defaultSql` 后，如果满足以下任一条件：

```text
allowTextToSql = true
retrievalMode = AUTO_GENERATE
```

系统会调用 `VannaSqlGenerationService`。

### 6.1 收集数据源上下文

Java 根据数据源 ID 获取：

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

并拼装类似 DDL：

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

### 6.2 业务说明

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

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

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

### 6.3 Few-shot 示例

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

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

### 6.4 调用 Vanna Bridge

Java 向 RAG AI Bridge 发送：

```text
POST http://127.0.0.1:18733/text2sql
```

请求大致为：

```json
{
  "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；
- 去掉末尾分号；
- 只允许 `SELECT` 或 `WITH`；
- 禁止 DML、DDL 和危险操作；
- 要求结果不超过 `maxRows`。

### 8.2 Java Guard

`SqlGuardService` 再次检查：

- 只允许 `SELECT`、`WITH`、`EXPLAIN`；
- 禁止分号和多语句；
- 禁止 SQL 注释；
- 禁止 `INSERT`、`UPDATE`、`DELETE`、`DROP`、`ALTER` 等；
- 禁止访问配置中的系统 Schema。

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

## 9. JDBC 执行

校验通过后，系统：

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

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

## 10. 结构化数据证据

执行结果封装为：

```json
{
  "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`：管理数据源和凭据。
