基于 OKF 知识图谱的 Text2SQL 领域知识注入实践——半导体晶圆厂数据资产知识库 MVP 剖析

基于 OKF 知识图谱的 Text2SQL 领域知识注入实践------半导体晶圆厂数据资产知识库 MVP 剖析

关键词:Text2SQL · OKF(Open Knowledge Format)· 知识图谱 · 隐性知识 · 半导体 Fab / 晶圆厂 · Streamlit · LLM · RAG · 数据资产知识库

GitHub:https://github.com/BumbleBee-ZDS/fabwiki

引言

Text2SQL 在演示环境中表现亮眼,但落地到真实生产数据仓库后,准确率往往急剧下降。本文的场景是:为半导体晶圆厂(Fab)的数据仓库构建 Text2SQL 查询能力。表面上看,这不过是「RAG + LLM 生成 SQL」的组合,但深入实践后会发现,Fab 数仓是一个典型的「知识封闭」环境:

  • 表结构来自 MES / EAP / YMS 系统,字段名多为 LOT_STSSTEP_GRPDISPOSITION 这类缩写,仅凭表名难以判断语义;
  • 状态字段充满魔法数字:LOT_STS='05' 表示什么?'99' 呢?
  • 良率、缺陷等指标的口径写死在存储过程中,真正的「隐性知识」散落在工程师的头脑里。

如果让 LLM 在没有领域知识的情况下直接生成 SQL,结果往往是「语法正确、语义错误」------查询结果与业务真实口径相去甚远。

本文将从理论和实践两个层面,完整复盘如何用 OKF 思想 + 知识图谱,将「让 LLM 看懂 Fab 数仓」落地为一个最小可运行 MVP。全文既包含方法论述,也提供可运行的代码与演示。


一、问题拆解:为什么朴素 Text2SQL 在 Fab 场景中失效

1.1 什么是 Text2SQL

Text2SQL 的任务是:给定一句自然语言,让模型生成对应的 SQL 并执行。

复制代码
"当前 Hold 中的批次有多少?"  →  SELECT COUNT(*) FROM WIP_LOT WHERE LOT_STS='05' ...

从语法层面看,这并非难事。但在 Fab 数仓中,真正的瓶颈不是「会写 SQL」,而是「理解业务语义」。

1.2 三类典型的「隐性知识」陷阱

陷阱一:魔法编码值

sql 复制代码
-- ❌ 将测试批次误计入 Hold 批次
SELECT COUNT(*) FROM WIP_LOT WHERE LOT_STS = '05';

-- ✅ 05=Hold(待 MRB 评审),99=测试批次,统计时须排除
SELECT COUNT(*) FROM WIP_LOT WHERE LOT_STS = '05' AND LOT_STS <> '99';

LOT_STS='99' 在 Fab 中表示测试批次(非真实产品)。统计良率、在制品数量时若不排除,报表数字将直接失真。而「99 需要排除」这一规则,数据字典中并不存在。

陷阱二:前缀编码与统计口径

sql 复制代码
-- ❌ DEFECT_CD 不应做精确匹配
WHERE DEFECT_CD = 'P12'

-- ✅ P*=Particle、S*=Scratch、M*=Metal 残留,应使用前缀匹配
WHERE DEFECT_CD LIKE 'P%'

-- 且 DISPOSITION='SC'(Scrap)的晶圆不计入产出

陷阱三:字段类型与时序约束

sql 复制代码
-- ❌ TECH_NODE 存储的是字符串 '28nm',与数字直接比较会出错
WHERE TECH_NODE = 28

-- ✅ 需先做类型转换
WHERE CAST(REPLACE(TECH_NODE,'nm','') AS INTEGER) = 28

而像 YLD_DAILY 这类由夜间 Job 生成的表,在当日 08:00 之前查询「昨天」的良率,结果可能为空。此类时序口径,仅靠 ER 图无法获知。

1.3 小结:Text2SQL 的瓶颈在于领域知识注入

Text2SQL 的难点从来不是 SQL 语法,而是如何将「数据字典中没有、只存在于资深工程师头脑中」的领域知识,准确地传递给模型。

这正是本项目引入 OKF 知识图谱的出发点。


二、理论基础:OKF(Open Knowledge Format)概述

2.1 OKF 的核心原则

OKF 是一种开源、可移植的知识组织形式,其核心理念可概括为三句话:

  • 一个知识包 = 一个文件夹
  • 一个知识点 = 一个 Markdown 文件(YAML frontmatter + 正文)
  • 文件之间通过相对路径链接构成一张知识图谱

Markdown 人人可写,YAML 易于解析,相对路径可表达有向链接。这三者结合,保证了知识体系既可读、可版本管理(Git),又可被程序索引为图结构。

2.2 知识点的三层结构

一个知识点文件示例如下(取自本项目真实的 WIP_LOT 表):

markdown 复制代码
---
title: "WIP_LOT 表知识"
type: Table                  # type 枚举:Table/Column/Metric/Spec
resource: WIP_LOT
tags: [wip, 在制品, fab]
confidence: seed              # 置信度:author/llm-inferred/verified/seed
related:                      # 用相对路径表达图的关系(血缘)
  - wiki/tables/WIP_LOT_HIST.md
  - wiki/tables/QCS_DEFECT.md
  - wiki/tables/PRD_PRODUCT.md
updated: 2026-09-13
---

# WIP_LOT 表
## 业务含义
批次主档,记录在制品的每一批......
## 关键字段(含隐性知识列)
| 字段 | 类型 | 含义 | 说明 |
|------|------|------|------|
| LOT_STS | TEXT | 批次状态 | 05=Hold(待MRB);99=测试批次,统计必须排除 |
## 血缘
下游:WIP_LOT_HIST、QCS_DEFECT;上游:PRD_PRODUCT
## Text2SQL 注意事项
统计在制品必须排除 LOT_STS='99'。
## 来源
raw/dictionary/WIP_LOT.json

注意:「隐性知识」被显式地写入正文,这正是普通数据字典无法做到的部分。

2.3 知识分层:从 raw 到 wiki

将知识分为两层,让「机器抽取」与「LLM 编译」各司其职:

层级 内容 生成方式 是否依赖 AI
L1 raw 数据字典 JSON(表/字段/类型/主外键/行数) 脚本自动抽取 否,纯机械
L2 wiki Markdown 知识(业务含义/隐性知识/SQL 注意事项) LLM(或 Mock/人工) 是,或人工

分层带来的收益:raw 层直接从数据库读取,永不失真;wiki 层负责知识增值,将工程师的经验沉淀为可检索内容。两层均可纳入 Git 管理,支持回滚与协同。


三、核心设计:知识导航式 Text2SQL 及其与朴素 RAG 的差异

3.1 为何不直接使用向量检索

常见的 Text2SQL 方案直接采用「向量数据库 + embedding 召回」。但在 Fab 这类强精确性的场景中,更推荐先做一层「确定性读取」:

  • 向量召回依赖分块与排序,可能遗漏关键字段,或将隐性知识切碎;
  • SQL 的正确性往往依赖「某个字段的确切取值」,而这恰恰是向量检索不擅长的「精确事实」。

3.2 方案:知识定位 → 确定性读全文 → 组装 → 执行 → 失败重试

复制代码
用户问题
   │  ① 关键词定位(从知识索引中挑选 2~5 张相关表)
   ▼
相关表知识点的【完整】Markdown(含隐性知识、SQL 注意事项)
   +   spec/text2sql-rules.md 规则文件(few-shot)
   │  ② 将全文与规则直接注入 LLM,不做向量分块
   ▼
LLM 生成 SQL
   │  ③ 在 SQLite 上执行
   ▼
返回 DataFrame + SQL + 引用的知识文件列表
   │  ④ 执行失败?将报错回填 LLM,重试一次

关键代码(core/text2sql.py):

python 复制代码
def ask(self, question: str) -> Text2SQLResult:
    nodes = self.locate(question)            # ① 定位相关表
    ctx = []
    for n in nodes:
        # ② 确定性读取整篇知识 + 其 frontmatter
        ctx.append(f"===== {n.path} =====\n{self._table_full_text(n)}")
    ctx_text = "\n\n".join(ctx)

    if not config.LLM_AVAILABLE:
        # Mock 模式:内置示例问题 → SQL 映射(离线可演示)
        sql, desc, missed = self._match_mock(question)
        ...
        return self._execute(question, sql, referenced, logs, mock=True)

    # 真实 LLM:问题 + 知识全文 + 规则 + few-shot
    sql = self._llm_generate(question, ctx_text, referenced, logs)
    return self._execute(question, sql, referenced, logs, mock=False)

「规则 + few-shot」统一收口在 spec/text2sql-rules.md,其中将必读的隐性知识整理为一张查表与纠错对照表:

表/字段 陷阱 正确写法
LOT_STS 05=Hold,99=测试批次 LOT_STS='05' AND LOT_STS<>'99'
DEFECT_CD 前缀编码 DEFECT_CD LIKE 'P%'
TECH_NODE 字符串 '28nm' CAST(REPLACE(...,'nm','') AS INTEGER)=28
YLD_DAILY 夜间 Job 生成 当日 08:00 前查询昨日可能为空

这就是「知识驱动的 Text2SQL」,而非「碰运气的 Text2SQL」。

3.3 血缘图谱:让知识本身可被导航

借助 related 字段与 Markdown 中的相对路径链接,使用 networkx 将整个知识包构建为一张有向图(节点=知识点,边=链接),再用 pyvis 进行可视化。

在「血缘图谱」页面,可以直观看到这样一条链路:

复制代码
WIP_LOT(在制批次)
   ↓ WIP_LOT_HIST(过站历史)
   ↓ YLD_DAILY(日良率汇总)

这张图不仅是「看的」,更能服务于 Text2SQL:需要良率时,沿 WIP_LOT_HIST 向下游找到 YLD_DAILY;需要产品主档时,向上游找到 PRD_PRODUCT知识本身可被程序导航,这正是采用 OKF 构建知识体系的直接收益。


四、工程实践:从 0 到 1 搭建可运行 MVP

4.1 技术选型

选型 理由
前端/UI Streamlit 几分钟搭建 4 个页面,适合演示
数据库 SQLite(模拟 Oracle) 零配置,SQL 按标准书写
知识解析 python-frontmatter Markdown + YAML 一体化解析
图谱 networkx + pyvis 构建与可视化血缘图
LLM OpenAI 兼容 SDK base_url 可切换至 DeepSeek 等
数据处理 pandas + python-dotenv 数据处理 + 配置管理

关键设计:Mock 模式是一等公民config.LLM_AVAILABLE = bool(os.getenv("LLM_API_KEY")),无 Key 时自动降级至内置模板,离线亦可完整演示------这对撰写博客、搭建 Demo 至关重要。

4.2 目录结构

复制代码
fabwiki/
├── app.py                 # Streamlit 入口(四页面)
├── config.py              # 路径 + LLM 配置 + Mock 开关
├── core/
│   ├── db.py              # SQLite 建库 + seed + 外键元数据
│   ├── extractor.py       # L1:抽取数据字典 → raw/JSON
│   ├── compiler.py        # L2:Mock/LLM 编译知识 + 血缘 + index
│   ├── knowledge.py       # 知识加载解析 + networkx 血缘图
│   ├── llm.py             # LLM 统一封装(超时/重试/降级)
│   └── text2sql.py        # 知识导航式 Text2SQL
├── knowledge/             # OKF 知识包
├── scripts/init_db.py     # 一键建库 + seed
└── tests/test_smoke.py    # 全链路冒烟测试

4.3 准备模拟数据:将「隐性知识」植入进去

为便于演示,构造了 8 张 Fab 风格的表(仿 Oracle 命名),并将隐性知识真实地编码进 seed 数据,例如:

python 复制代码
LOT_STS_VALUES = ["01", "03", "05", "07", "99"]   # 99 = 测试批次
EQP_STATUS    = ["R", "I", "D", "M"]              # M = PM 保养中
DEFECT_PREFIX = ["P", "S", "M"]                   # Particle/Scratch/Metal
DISPOSITIONS  = ["SC", "RS", "OK"]                # SC = Scrap

同时使用一张 meta_fk 表记录外键关系,使 extractor 能据此自动生成血缘。

4.4 L1 抽取:数据字典自动化

extractor.py 读取 sqlite_master / PRAGMA table_info + meta_fk,将每张表导出为 knowledge/raw/dictionary/{TABLE}.json,再通过 extract_all() 一键批量处理。

python 复制代码
def _dump_dict(conn, table) -> dict:
    cols = [{"name": r["name"], "type": r["type"], "pk": bool(r["pk"])}
            for r in conn.execute(f"PRAGMA table_info({table})")]
    fks  = [{"column": fc, "references": f"{tgt}.{tc}"}
            for fc, src, tgt, tc in db.get_meta_fk(conn) if src == table]
    return {"table": table, "row_count": row_count(conn, table),
            "primary_key": ..., "foreign_keys": fks, "columns": cols}

4.5 L2 编译:两种模式并存

compiler.py 是核心模块,提供两条产出路径:

  • 真实 LLM :将 raw JSON + 隐性知识提示拼成 prompt,调用 chat_complete
  • Mock :内置高质量模板字符串直接落盘------Mock 内容即为「LLM 应当生成的样子」,因此无 Key 时演示效果不减。

每张表还会附带生成血缘文件 {TABLE}__lineage.md,最后刷新全局 index.md

python 复制代码
def compile_all() -> dict:
    for table in db.table_names():
        md = _table_json_to_md(table) if llm_ok else None
        md = md or _table_md(table, row_count)      # 失败/Mock → 使用内置模板
        (config.WIKI_TABLES_DIR / f"{table}.md").write_text(md, ...)
        _gen_lineage(table, conn)                    # 生成血缘
    _gen_index(tables, METRICS)                      # 刷新导航

4.6 LLM 统一封装(重试 + 降级)

所有真实 LLM 调用收敛至 core/llm.py:超时、最多重试 1 次、异常时回调告警并返回 None,由上层降级至 Mock。

python 复制代码
for attempt in range(config.LLM_MAX_RETRIES + 1):
    try:
        resp = client.chat.completions.create(...)
        text = resp.choices[0].message.content.strip()
        if text: return text
    except Exception as e:
        last_err = str(e)
return None   # 上层捕获后降级至 Mock

4.7 四页面 UI

Streamlit 单入口 app.py,侧边栏导航切四个页面:

  1. 🏠 首页 :项目介绍、OKF 理念、知识包统计(节点/边/confidence 分布)、一键初始化;

  2. 📚 知识浏览 :按 type/tag/关键词过滤,渲染 frontmatter + 正文 Markdown,关联知识点可点击跳转;

  3. 🕸️ 血缘图谱 :pyvis 全库知识链接图(节点按 type 着色),并支持选某张表看 N 跳血缘;

  4. 💬 Text2SQL :对话式,边栏展示「本次引用的知识文件」,结果区三个 Tab

    (数据表格 / 生成 SQL / 执行日志),内置示例问题一键点。


五、运行验证:三步启动与效果验证

5.1 三步启动

bash 复制代码
cd fabwiki
pip install -r requirements.txt
python -m streamlit run app.py --server.port 8506

无 API Key 时自动进入 Mock 模式 ;配置 LLM_API_KEY 后切换至 真实 LLM,Mock 内容会被真实生成结果覆盖。

5.2 演示:一条「隐性知识」被正确生成

在 Text2SQL 页面提出一个天然带坑的问题:

「当前 Hold 中的批次数量?」

经知识导航后,模型(真实 LLM 实测)生成的 SQL 如下:

sql 复制代码
SELECT COUNT(*) AS HOLD_LOT_CNT
FROM WIP_LOT
WHERE LOT_STS = '05' AND LOT_STS <> '99'

可以看到------模型不仅知道 05=Hold还自动补上了 <> '99' 以排除测试批次 。这正是知识文件中「隐性知识 + Text2SQL 注意事项」被确定性读取后的效果,而非 LLM 凭空猜测。

5.3 更多演示问题(内置 Mock 示例)

  • 最近一个月各产品的良率趋势?
  • PHOTO 工序缺陷 Top10?
  • 设备当前可用的各类型数量?
  • 各产品在制品的批次数量?

5.4 冒烟测试保底

tests/test_smoke.py 覆盖全链路:建库 → 抽取 → Mock 编译 → 知识加载 → Mock Text2SQL,并专门断言「Hold 查询必须含 '05' 且排除 '99'」。

bash 复制代码
python tests/test_smoke.py
# 全部 6 项冒烟测试通过

六、总结:这套方法可迁移的部分

  1. 将「隐性知识」显式化,是 Text2SQL 落地的第一步。 数据字典解决不了的问题,要靠业务知识文档解决。OKF 用「文件夹 + Markdown + 相对路径链接」将知识组织得可维护、可版本化。

  2. 知识导航优于盲目向量检索。 对「SQL 正确性依赖精确值」的场景,确定性读取相关表全文比 embedding 召回更稳定。向量检索适合「模糊概括」,不适合「精确写 SQL」这类任务。

  3. 分层知识(raw / wiki)+ Mock/LLM 双模式,使系统在线、离线均可运行、可演示、可测试。

  4. 血缘图谱不只是可视化工具,它本身就是 Text2SQL 的知识导航入口------顺着上下游,才能确定以何种粒度汇总。

  5. 给 LLM 的 prompt 需要持续「养」。 规则文件 + few-shot 应不断迭代,将踩过的 LOT_STS='99'TECH_NODE CAST 这类坑,逐一沉淀为查表修正规则。


七、延伸思考

最深刻的体会在于:技术难点往往不在模型本身,而在「领域知识的工程化」

让 LLM 写出语法正确的 SQL 并不困难,真正的挑战是让它在特定业务规则下「写对」。Fab 只是其中一个例子------医疗、金融、政务,凡是「数据里藏有魔法值、口径写死在存储过程中」的行业,这套「OKF 知识资产 + 确定性知识导航」的思路都同样适用。

如果你也在做企业级 Text2SQL,不妨尝试:先别急着调大模型参数,先把领域知识用 OKF 整理成一张可导航的图谱。 很可能,几个真实案例就能显著挽回准确率。

相关推荐
宣宣猪的小花园.1 小时前
【机器学习】损失函数与梯度下降:机器如何通过“犯错”不断变好
人工智能·算法·机器学习
jsl_jsl_jsl1 小时前
《Agent 是怎么“思考”的:ReAct 循环与流式 SSE 的完整实现》
人工智能
sarasuki1 小时前
失败重试:Agent 中指数退避的正确姿势
人工智能·设计模式·agent
sxwuyanzu1 小时前
DeepSeek冲IPO-谁在为它付钱-公众号发布稿
大数据·人工智能·科技
晚安日记wanna1 小时前
批量请求失败只弹一个 Toast面试官想听五层
前端·面试·架构
一切皆是因缘际会1 小时前
科技的赋能
人工智能
甲维斯1 小时前
RSI“真“”来了,OpenAI和Anthropic把人类逼到墙角!
人工智能
大模型码小白1 小时前
告别造假数据,直接连数据库查真实时序数据喂给 TimechoAI 大模型
java·数据库·人工智能·microsoft·架构
晚安日记wanna1 小时前
大表 DDL 面试翻车现场Online DDL 为什么还会锁死业务
数据库·面试·架构