基于 OKF + RAG 构建 Text2SQL 语义层:让 LLM 真正理解你的数据库

引言

Text2SQL(自然语言转 SQL)正在从学术研究走向生产应用。然而,工程实践中一个反复出现的问题是:LLM 拿到 schema 后仍然写不对 SQL

典型的失败场景包括:

  • event_value_in_usd 误解为"交易金额"(实际是事件参数的货币换算值)
  • 不知道两张表的 Join 条件,生成笛卡尔积
  • 对分片表(sharded table)使用错误的通配符语法
  • 生成全表扫描查询,在 TB 级表上产生高额费用

根本原因在于:INFORMATION_SCHEMA 提供的是物理结构(列名、类型),而 LLM 需要的是业务语义(这列是什么意思、怎么和其他表关联、查询时有什么注意事项)

本文介绍一种基于 Open Knowledge Format(OKF)的解决方案:将数据资产的业务上下文以结构化 Markdown 文档的形式组织为"语义层",通过 RAG 检索注入 LLM context,从而显著提升 Text2SQL 的准确率。


一、问题分析:为什么纯 Schema 不够

以 Google Analytics 4 的 BigQuery 导出表 events_* 为例。该表的 INFORMATION_SCHEMA.COLUMNS 返回约 80 个字段定义,其中包含大量嵌套 RECORD(如 event_paramsuser_propertiesitems)。

当用户提问"上周有多少独立用户访问了网站?"时,LLM 面临以下决策:

  1. user_id 还是 user_pseudo_id 去重?(答案:user_pseudo_id,因为 user_id 大量为 null)
  2. 用什么字段过滤时间范围?(答案:event_date 是 STRING 格式 YYYYMMDD,不是 TIMESTAMP)
  3. 需要过滤特定的 event_name 吗?(答案:对于"访问"可能需要 page_viewsession_start

这些信息在 schema 中完全不存在。传统做法是在 system prompt 中硬编码表说明,但面对数十张表、数百个字段时,这种方式不可扩展。


二、OKF 作为 Text2SQL 语义层

2.1 OKF 提供了什么

Open Knowledge Format 是一种以 Markdown + YAML frontmatter 表示知识的开放格式。当其用于描述数据资产时,每个 Concept 文档可包含以下对 Text2SQL 有价值的信息:

信息类别 在 OKF 文档中的位置 Text2SQL 中的作用
表的业务含义 frontmatter description + 正文 帮助 LLM 判断该表是否与用户问题相关
字段语义 # Schema section 避免误解列名含义
Join 关系 # Joins section + 交叉链接 正确生成多表关联
SQL 示例 # Common query patterns 作为 few-shot 示例
指标定义 独立 reference concept 对齐业务术语与计算逻辑
注意事项 正文中的说明 避免性能陷阱和语义错误

2.2 实际 OKF 文档示例

以下是从 knowledge-catalog 项目中 GA4 bundle 的 tables/events_.md 中提取的关键片段:

markdown 复制代码
---
type: BigQuery Table
title: Events table (Google Analytics BigQuery Export)
description: Contains Google Analytics event export data from the
  `ga4_obfuscated_sample_ecommerce` dataset.
tags: [events, Google Analytics, BigQuery, ecommerce]
resource: https://bigquery.googleapis.com/.../events_*
---

# Overview
The `events_` table is a sharded BigQuery table containing Google
Analytics event export data...

# Schema
## event
- `event_date` (STRING): The date when the event was logged
  (YYYYMMDD format in the registered timezone of your app).
- `event_name` (STRING): The name of the event.
- `event_value_in_usd` (FLOAT): The currency-converted value (in USD)
  of the event's "value" parameter.

## user
- `user_pseudo_id` (STRING): The pseudonymous id (e.g., app instance ID)
  for the user. A unique identifier assigned when they first open the app
  or visit the site.

# Joins
- [Google Analytics Events to Google Ads Clicks](../references/joins/...)
  --- join on `collected_traffic_source.gclid` to attach Google Ads data.

相比 INFORMATION_SCHEMA 返回的 event_date STRING NULLABLE,OKF 文档多提供了格式说明(YYYYMMDD)、语义解释、以及关联关系------这些正是 LLM 生成正确 SQL 所需的关键上下文。


三、系统架构

3.1 整体流程

scss 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                         离线阶段                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│   数据源 (BigQuery)                                              │
│        │                                                          │
│        ▼                                                          │
│   OKF Enrichment Agent ──→ OKF Bundle (Markdown 文件)           │
│        │                        │                                 │
│        │                        ▼                                 │
│        │                   向量化 + 索引                          │
│        │                        │                                 │
│        ▼                        ▼                                 │
│   人工 Review (Git PR)    Vector Store                           │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                         在线阶段                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│   用户自然语言问题                                                │
│        │                                                          │
│        ▼                                                          │
│   ① 意图理解 + 关键词提取                                        │
│        │                                                          │
│        ▼                                                          │
│   ② RAG 检索 (语义搜索 + metadata filter)                       │
│        │                                                          │
│        ├──→ 命中: tables/orders.md                               │
│        ├──→ 命中: tables/customers.md                            │
│        └──→ 命中: references/metrics/revenue.md                  │
│                  │                                                │
│                  ▼                                                │
│   ③ 组装 Context (Schema + Joins + Examples)                    │
│                  │                                                │
│                  ▼                                                │
│   ④ LLM 生成 SQL                                                │
│                  │                                                │
│                  ▼                                                │
│   ⑤ 可选: SQL 验证 + 执行                                       │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

3.2 离线阶段:Bundle 生产与索引

Step 1: 生成 OKF Bundle

使用 knowledge-catalog 项目提供的 enrichment agent:

bash 复制代码
python -m enrichment_agent enrich \
    --source bq \
    --dataset myproject.analytics \
    --web-seed-file seeds.txt \
    --out ./bundles/analytics

Agent 将为每张表、每个数据集生成独立的 Concept 文档,包含 schema 描述、常见查询模式和引用来源。

Step 2: 人工审校与补充

通过 Git PR 流程补充 Agent 无法自动获取的信息:

  • 字段的业务含义澄清
  • 性能注意事项("务必使用 _TABLE_SUFFIX 限制分片范围")
  • 负面知识("不要用 user_id 做去重,null 率超过 60%")

Step 3: 向量化与索引

将 Bundle 中每个 Concept 文件作为一个 document 入库:

python 复制代码
for md_file in bundle_root.rglob("*.md"):
    if md_file.name == "index.md":
        continue
    doc = parse_okf_document(md_file)
    vector_store.add(
        text=doc.body,
        metadata={
            "type": doc.frontmatter["type"],
            "tags": doc.frontmatter.get("tags", []),
            "title": doc.frontmatter.get("title", ""),
            "concept_id": doc.concept_id,
        }
    )

frontmatter 字段作为 metadata,支持后续的结构化过滤。

3.3 在线阶段:检索与生成

Step 1: 意图理解

对用户问题进行初步分析,提取可能涉及的数据实体和指标:

makefile 复制代码
用户问题: "上季度购买转化率最高的流量来源是哪个?"
提取: [购买, 转化率, 流量来源, 上季度]

Step 2: RAG 检索

执行混合检索策略:

python 复制代码
# 语义搜索
results = vector_store.search(
    query="购买转化率 流量来源",
    filter={"type": {"$in": ["BigQuery Table", "Reference"]}},
    top_k=5
)

# 基于链接的图扩展
for result in results:
    linked_concepts = extract_links(result.body)
    results.extend(fetch_linked(linked_concepts))

Step 3: Context 组装

将检索到的 Concept 内容按优先级组装:

css 复制代码
[系统指令]
你是一个 SQL 生成助手。根据以下数据资产文档生成 BigQuery SQL。

[表文档 1: events_]
{events_.md 的完整内容或关键片段}

[表文档 2: ...]
{...}

[指标定义: 购买转化率]
{references/metrics/conversion_rate.md}

[用户问题]
上季度购买转化率最高的流量来源是哪个?

Step 4: SQL 生成

LLM 基于充分的业务上下文生成 SQL。由于 context 中已包含:

  • event_date 是 YYYYMMDD 格式的 STRING
  • 流量来源在 collected_traffic_source.manual_source 字段
  • 购买事件对应 event_name = 'purchase'
  • 转化率的定义公式

生成的 SQL 准确率显著高于仅提供 schema 的方案。


四、OKF 相比其他语义层方案的优势

4.1 vs 硬编码 System Prompt

维度 硬编码 OKF + RAG
可扩展性 表多了 prompt 超长 按需检索,只加载相关表
可维护性 修改需改代码 修改 .md 文件即可
协作 耦合在代码中 Git PR 流程
覆盖度 通常只覆盖核心表 Agent 可全量生成

4.2 vs 自定义 JSON/YAML 配置

一些团队选择自定义 JSON 格式来描述表的语义信息。OKF 的优势在于:

  • 正文可包含任意长度的自然语言:解释复杂的业务逻辑不受字段长度限制
  • SQL 示例直接嵌入:fenced code block 天然支持
  • 有社区规范:不是每个团队自造格式,互操作性更好
  • 消费工具已存在:Obsidian 可浏览、MkDocs 可发布、LLM 可直接消费

4.3 vs dbt docs / Metabase 数据字典

dbt 的 schema.yml 和 Metabase 的数据字典也提供语义信息,但:

  • dbt 绑定 dbt 工具链,非 dbt 项目无法使用
  • Metabase 的字典锁在其平台内,无法被外部 Agent 消费
  • 两者都不原生支持 SQL 示例、引用来源、交叉链接等富内容

OKF 作为独立于工具链的开放格式,可以从这些系统中导出,也可以被任何系统消费。


五、关键设计决策与权衡

5.1 粒度选择:一表一文件

OKF 推荐每个数据资产对应一个独立文件。对于 Text2SQL 场景:

  • 优势:检索粒度清晰,避免无关表的噪声
  • 代价:跨表关系信息分散,需通过链接图扩展补充

5.2 非结构化 vs 结构化的平衡

OKF 的 body 是自由格式 Markdown,不强制特定 section 结构。这意味着:

  • 优势:可以容纳"这个字段在每周一凌晨会有空值,因为上游 ETL 的时间窗口问题"这类难以结构化的知识
  • 代价:不同文件质量参差,需要人工或自动化质量检查

5.3 实时性问题

OKF Bundle 是静态文件。当源表 schema 发生变更时:

  • 短期方案:定时重新运行 enrichment agent + diff 检测
  • 长期方案:结合 schema change detection webhook 触发增量更新

六、实施建议

对于计划将 OKF 应用于 Text2SQL 场景的团队,建议按以下步骤推进:

第一阶段:核心表覆盖

  • 选择 10-20 张最常被查询的核心表
  • 使用 enrichment agent 生成初始 bundle
  • 人工补充 Join 关系和查询示例

第二阶段:指标与术语对齐

  • 将业务术语表转化为 reference concept
  • 建立"用户说的词"到"实际字段/计算逻辑"的映射

第三阶段:RAG 管道搭建

  • 将 bundle 入库向量数据库
  • 实现 metadata filter + 语义搜索的混合检索
  • 配置 context 组装模板

第四阶段:持续运营

  • 基于 SQL 执行失败的反馈修正文档
  • 定期重新 enrich 以同步 schema 变更
  • 通过 Git PR 流程收集领域专家的知识补充

七、局限性与未来方向

当前方案存在以下已知局限:

  1. 依赖检索质量:如果 RAG 未命中正确的 Concept,后续生成必然出错。需要持续优化检索策略(query rewrite、re-ranking)。

  2. 缺乏执行反馈闭环:当前流程中 OKF 文档是静态的,无法根据 SQL 执行结果自动修正。未来可引入"执行失败 → 自动标注文档不足 → 触发 re-enrich"的反馈循环。

  3. 多表复杂查询的 context 膨胀:涉及 5+ 张表的查询,即使只加载相关 Concept,context 仍可能过长。需要结合摘要策略或分层推理。

  4. 权限与数据安全:OKF 文档可能包含敏感的 schema 信息。部署时需考虑文档级的访问控制。


八、结语

Text2SQL 的核心挑战从来不是"LLM 不会写 SQL",而是"LLM 不理解你的数据"。OKF 提供了一种务实的方案:将人类和 Agent 对数据的理解以最朴素的形式(Markdown 文件)固化下来,再通过 RAG 在需要时精准注入 LLM 的上下文

这种方案的工程优雅性在于:它不需要引入新的基础设施组件------向量数据库和 LLM 调用链路在大多数 AI 应用中已然存在;它只需要一个新的"内容源",而 OKF bundle 恰好以最低的摩擦成本充当了这个角色。


参考资料

相关推荐
把所有砖敲烂1 小时前
MiniMax M3 深度实测:单卡部署、代码生成与性能全解析
人工智能
沉默王二1 小时前
老板:“请说出一个录用你的理由。”我脱口而出:“每个月 AI 支出都超过我的生活费了!”老板愣了一下,随即哈哈大笑:“好吧,你被录用了。”
人工智能·ai编程·claude
这token有力气2 小时前
ReAct 循环中陷入"工具调用死循环"
人工智能
Mr_愚人派2 小时前
当"Claude"不再是 Claude:一次第三方 API 代理引发的 AI 身份伪造排查实录
人工智能·安全
Lee川2 小时前
Memory 模块深度解析(面试向)
人工智能·面试
MacroZheng2 小时前
Claude Code官方桌面端正式发布,夯爆了!
java·人工智能·后端
IT_陈寒3 小时前
React的useEffect依赖数组把我坑惨了,真相其实很简单
前端·人工智能·后端
Kapaseker3 小时前
什么?Stack Overflow 给 AI 做了个 Stack Overflow
人工智能
aneasystone本尊3 小时前
让小龙虾自己写手册:Skill Workshop
人工智能