# 本项目结构化数据 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`:管理数据源和凭据。