🎯 本章核心问题
LLM 如何理解你的数据库结构?
这是所有 NL→SQL 系统的核心挑战:
- ❌ LLM 不知道你的
orders表里有哪些字段 - ❌ LLM 不知道
user_id是外键关联到users.id - ❌ LLM 不知道
status字段的值是"pending"/"paid"/"shipped"还是0/1/2
解决方案:构建”语义模型”(Semantic Model) —— 将数据库的物理结构翻译成 LLM 能理解的业务语言。
📐 架构总览
元数据管理与语义模型构建流程
三阶段流水线
结构导入 → LLM 分析 → 语义模型实时组装
🔄 一、阶段一:结构导入(全自动化)
1.1 从 MySQL 提取元数据
当用户配置数据源并点击”导入表结构”时,系统自动执行:
1 | |
📊 导入的数据示例
1 | |
1.2 存储到 SQLite(应用元数据库)
app/services/metadata.py
1 | |
💡 设计亮点:增量更新 vs 全量替换
我们选择 全量替换策略(先删后插)的原因:
| 策略 | 优点 | 缺点 |
|---|---|---|
| 全量替换(当前) | 实现简单;避免脏数据 | 大表时稍慢(但元数据量小可忽略) |
| 增量更新 | 保留人工修改 | 需要复杂的 diff 逻辑 |
理由:元数据通常只有几十张表、几百个字段,全量替换耗时 <100ms。
🤖 二、阶段二:LLM 智能分析(核心亮点)
2.1 为什么需要 LLM 分析?
原始元数据的问题:
1 | |
LLM 的价值:
- 添加中文业务名称:
user_id→用户ID - 补充业务描述:
orders表 → “存储用户的所有订单记录” - 推断表关系:发现
orders.user_id关联users.id - 识别枚举含义:
status=0/1/2→"待支付/已发货/已签收"
2.2 Prompt 工程设计
app/services/llm_gateway.py
1 | |
💡 Prompt 设计技巧
| 技巧 | 示例 | 效果 |
|---|---|---|
| 角色设定 | “你是一位资深的数据库分析师” | 让 LLM 以专业视角分析 |
| 结构化输出约束 | 强制 JSON 格式 + 给出模板 | 减少格式错误率从 30% → <5% |
| 规则明确化 | 列出 6 条关系推断规则 | 提高关系识别准确率 |
| 置信度分级 | high/medium/three-level | 方便下游过滤低置信度结果 |
| 负面约束 | “只输出确定存在的关系” | 减少误报(false positive) |
2.3 LLM 响应示例
输入(50 张表的简化版):
1 | |
输出(LLM 返回的 JSON):
1 | |
完美! 🎉 LLM 自动完成了:
- ✅ 中英文映射(
orders→订单表) - ✅ 业务描述生成
- ✅ 外键关系推断(
orders.user_id→users.id) - ✅ 置信度标记(
high)
2.4 解析响应并写回数据库
app/services/metadata.py
1 | |
🔗 三、关系推断算法详解
3.1 规则引擎(在 Prompt 中定义)
我们在 System Prompt 中明确告诉 LLM 推断规则:
1 | |
3.2 实际效果统计
在我们的测试集(电商数据库,15 张表)上:
| 指标 | 数值 | 说明 |
|---|---|---|
| 表命名准确率 | 100% (15/15) | 所有表都得到合理的中文命名 |
| 字段命名准确率 | 95% (142/150) | 少数缩写(如 sku)需要人工修正 |
| 关系识别召回率 | 90% (9/10) | 发现了 9 个真实的外键关系 |
| 关系识别准确率 | 88.9% (8/9) | 1 个误报(将 category_id 误判为关联) |
| 平均耗时 | 4.2 秒 | 15 张表,~200 个字段 |
💡 典型误报案例
1 | |
🏗️ 四、阶段三:语义模型组装
4.1 get_semantic_model() —— 核心查询函数
app/services/metadata.py
1 | |
4.2 语义模型的使用场景
场景 1:注入 SQL Generator 的 Prompt
1 | |
LLM 收到的上下文(部分截取):
1 | |
效果:LLM 知道要查 orders.total_amount 并按 created_at 过滤!✅
场景 2:注入 Chat Engine 的 System Prompt
1 | |
🎨 五、前端管理界面
5.1 元数据概览页(MetadataOverview.vue)
功能:
- 📋 展示所有表及其字段数量
- 🏷️ 显示 LLM 标注的业务名称
- 🔄 一键触发 LLM 重新分析
- ⚠️ 标记未分析的表(灰色高亮)
5.2 表详情页(TableDetail.vue)
功能:
- ✏️ 编辑表/字段的中文业务名称和描述
- 📝 补充 LLM 未识别的信息(如枚举值含义)
- 👁️ 查看样例数据(sample_values)
- 🗑️ 删除不需要的字段(隐藏而非物理删除)
5.3 关系编辑器(RelationEditor.vue)
功能:
- 🔗 图形化展示表之间的关联关系(节点+连线)
- ✅ 确认/拒绝 LLM 建议的关系
- ➕ 手动添加遗漏的关系
- ✏️ 修正关系类型(one_to_many ↔ many_to_many)
📊 六、性能优化策略
6.1 缓存语义模型
由于语义模型相对稳定(不会频繁变更),可以缓存:
1 | |
6.2 增量分析
对于大库(100+ 表),可以只分析新增/修改的表:
1 | |
6.3 异步分析
LLM 分析耗时较长(3-8s),应该异步执行:
1 | |
🔟 七、总结与最佳实践
✅ 我们做到了什么
- 全自动元数据导入:连接 MySQL 即可提取表结构(无需手工录入)
- LLM 智能标注:自动添加中文名、业务描述、推断关系(节省 90% 人工成本)
- 高质量语义模型:为 NL→SQL 转换提供精准的上下文
- 人机协同:LLM 建议结果可人工审核修正
📚 最佳实践清单
- 使用
SHOW TABLES+DESCRIBE自动导入元数据 - 存储样例数据帮助 LLM 理解字段含义
- 设计结构化的 Prompt 强制 JSON 输出
- 定义明确的关系推断规则和置信度等级
- 实现
get_semantic_model()统一查询接口 - 提供前端管理界面支持人工修正
- 缓存语义模型减少重复计算
- 支持异步分析避免阻塞请求
🚀 下一步
下一篇文章我们将深入 NL→SQL 转换引擎 —— 如何利用语义模型生成准确的 SQL?如何防止 SQL 注入?如何处理复杂的多表 JOIN?
敬请期待!🚀
相关代码文件:
app/services/metadata.py(metadata.py) — 元数据 CRUD 和 LLM 分析app/services/llm_gateway.py— LLM 分析 Promptapp/models/metadata.py(models/metadata.py) — 数据模型定义app/api/metadata.py(api/metadata.py) — API 接口层