工业级Text2SQL实战:半导体晶圆厂Agent系统

📅 2026/7/31 3:02:54 👁️ 阅读次数 📝 编程学习
工业级Text2SQL实战:半导体晶圆厂Agent系统

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


一、背景:为什么晶圆厂的Text2SQL这么难?

半导体晶圆厂(Fab)是Text2SQL最"地狱"的应用场景之一,没有之一。

想象一下这个画面:你走进一座12英寸晶圆厂的数据中心,面前是上千张表,每张表动辄上百个字段,命名风格百花齐放——

  • LOT_TRACKING(批次追踪,还算正常)
  • F001_WAFER_PARAM_RAW(某测试参数表,缩写F001)
  • TBL_EQP_CHMBR_LOG_2024(设备腔体日志,年份硬编码)
  • 中英文混杂:产品型号PRODUCTPRD_CD三个字段可能指同一件事
  • 字段值千奇百怪:STEP_NAME='PHOTO_01A'STATUS='RUNNING'vs'IN_PROCESS'(语义重复)

更致命的是,业务知识极度隐性

  • 用户说"查一下良率"——是YIELD_PERCENT?还是(GOOD_DIE/TOTAL_DIE)*100?不同产品类型公式不同
  • 用户说"看看SPC有没有异常"——是看MEAS_VALUE>UCL?还是MEAS_VALUE<LCL?还是连续7点同侧?
  • 用户说"WIP堆积"——哪个工序算堆积?>1000片?还是超过该工序平均WIP的2倍?

学术界Benchmark(Spider/BIRD)上的SOTA模型,丢到这种环境里,准确率直接腰斩。

这不是模型不够大,而是问题本身就不是纯生成问题——它是一个领域知识 + 确定性模板 + 灵活生成的混合问题。


二、核心创意:能用确定性的,就不麻烦LLM

这是整个项目最重要的设计哲学,一句话概括:

「80%的查询是可枚举的固定模式,把它们写成参数化模板,关键词命中即用,零LLM调用、零幻觉、零延迟。」

这听起来很朴素,甚至有点"不AI"。但正是这个朴素的思想,让我们在195条领域测试用例上达到了100%准确率——而同期直接用GPT-4o生成SQL的方案,在同样的数据上准确率只有60%~70%。

为什么不直接上LLM?

维度纯LLM方案Skill模板优先方案
准确率60%~70%(幻觉/方言错误)模板命中≈100%
延迟2~5秒/次(API调用)模板命中<10ms
成本每次消耗token模板命中零成本
可解释性黑盒生成模板可读、可审计
安全性可能生成DML/DDL模板白名单,天然安全
维护成本Prompt越写越长新增场景=新增模板文件

LLM不是万能锤子,Text2SQL也不是钉子。把它用在真正需要"理解+创造"的地方(Skill未命中时的兜底),才是正确姿势。


三、系统架构:四层九Agent

┌──────────────────────────────────────────────────────────────────────┐ │ 用户查询 (自然语言) │ │ "查询产品A3最近三个月的良率趋势,并按工序分组" │ └──────────────────────────────┬───────────────────────────────────────┘ │ ┌──────────────▼──────────────┐ │ Text2SQLAgent │ │ (think → act → observe) │ │ ReAct 循环 ≤10步 │ └──────────────┬──────────────┘ │ ┌────────────────────┼────────────────────┐ │ │ │ ▼ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ SkillMatch │ │ ToolRegistry │ │ LLM Fallback │ │ (优先, 免调用) │ │ (9 个工具) │ │ (Skill未命中时) │ │ 239个模板 │ │ execute/fix/ │ │ GPT-4o-mini │ │ 加权关键词匹配 │ │ search/safety │ │ 兜底生成 │ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │ │ │ ▼ ▼ │ ┌─────────────────┐ ┌─────────────────┐ │ │ semantic_layer │ │ oracle_mock │ │ │ 术语词典 │ │ (DuckDB 内存) │◀──────────┘ │ + Skill模板 │ │ + 方言翻译层 │ │ + 参数提取 │ │ Oracle→DuckDB │ └─────────────────┘ └─────────────────┘ │ ▼ ┌─────────────────┐ │ 结果 / SQL │ │ + 可视化图表 │ └─────────────────┘

四层职责拆解

层级职责关键模块
用户交互层Streamlit Web UI + CLI + REST APIapp.py,cli.py,main.py
Agent编排层LangGraph状态图 + ReAct循环 + Human-in-the-loopworkflow.py,agent.py
语义理解层Skill匹配 + 术语词典 + 参数提取 + LLM兜底semantic_layer.py,llm_client.py
数据执行层DuckDB内存引擎 + Oracle方言翻译 + 安全拦截oracle_mock.py,tools.py

四、Skill模板机制:项目的灵魂

4.1 一个Skill长什么样?

{"name":"yield_trend","description":"产品良率趋势分析,按月聚合","keywords":["良率趋势","yield trend","良率变化","yield by month"],"extract_params":_extract_params_yield_trend,# 提取产品名、时间范围"template":""" SELECT TRUNC(START_TIME, 'MM') AS MONTH, PRODUCT, AVG(YIELD_PERCENT) AS AVG_YIELD FROM LOT_TRACKING WHERE PRODUCT = '{product}' AND START_TIME >= ADD_MONTHS(SYSDATE, -{months}) GROUP BY TRUNC(START_TIME, 'MM'), PRODUCT ORDER BY MONTH """}

用户输入"最近三个月A3产品的良率趋势" → 匹配到yield_trend→ 提取product=A3, months=3→ 渲染模板 → 得到可执行SQL。全程零LLM调用,耗时<10ms。

4.2 加权关键词匹配算法

这是Skill命中率的关键。不是简单的"包含即匹配",而是多因子加权评分

score=匹配关键词数量 ×2000# 命中越多越优先+Σ(匹配关键词长度)×10# 越长越具体("良率趋势">"良率")+Σ(位置奖励)×100# 关键词列表越靠前权重越高+描述命中数 ×500# 描述中也出现 = 强信号

为什么不用向量相似度?在239个模板的规模下,关键词匹配的精确率远高于向量检索——"良率趋势"和"良率分布"语义接近但SQL完全不同,向量检索容易混淆,关键词则精确可控。

4.3 分代注入:从20到239的演进

模板不是一次性写完的,而是跟着测试用例一起生长

模板数覆盖场景设计思路
Gen320基础查询(良率/SPC/设备/批次)先跑通核心链路
Gen4+25多表JOIN/聚合/子查询覆盖跨表关联
Gen5+50边界/压力(NULL/闰年/ROLLUP/CUBE)极端case驱动
Gen6+30多步业务流程/统计建模/数据血缘复杂分析场景

每一代都是"测试失败→分析根因→补模板→再测试"的迭代产物。这本质上是一种测试驱动的Skill工程

4.4 业务术语词典

TERM_DICTIONARY={"良率":{"field":"YIELD_PERCENT","table":"LOT_TRACKING"},"在制品":{"table":"LOT_TRACKING","condition":"STATUS='RUNNING'"},"WIP":{"table":"LOT_TRACKING","condition":"STATUS='RUNNING'"},"SPC异常":{"table":"SPC_RESULTS","condition":"MEAS_VALUE>UCL OR MEAS_VALUE<LCL"},"低库存":{"table":"MATERIAL_INVENTORY","condition":"QTY_ON_HAND<100"},"OEE":{"field":"DURATION_MIN","table":"EQUIPMENT_LOG","calc":"running_time/total_time"},}

LLM在生成SQL前,先把自然语言中的模糊词替换为确定性映射,相当于给模型配了一本领域字典


五、Agent执行循环:ReAct范式

当Skill未命中时,系统进入Agentic模式:

whilestep<max_stepsandnottask_completed:# Think:决定下一步用什么工具thought=_heuristic_decision(user_input,current_sql,observation)# Act:执行工具ifthought=="match_skill":result=SkillMatchTool.invoke(user_input)elifthought=="generate_sql":result=LLMFallbackSQLTool.invoke(user_input,schema_context)elifthought=="execute_sql":result=ExecuteSQLTool.invoke(current_sql)elifthought=="fix_sql":result=FixSQLTool.invoke(current_sql,error_msg)# Observe:观察结果,更新状态observation=result.outputifresult.success:task_completed=True

这是一个标准的ReAct(Reasoning + Acting)循环,最多10步。每一步的thought/action/observation都被记录,可以在Streamlit界面上实时展示——用户能看到Agent"在想什么"、“在做什么”。


六、LangGraph工作流:状态图编排

fromlanggraph.graphimportStateGraph,START,ENDfromlanggraph.checkpoint.memoryimportMemorySaver# 定义状态classGraphState(TypedDict):user_input:strschema_context:listlogical_plan:strgenerated_sql:strcritic_feedback:strhuman_edited_sql:strapproved:booliteration_count:interror_type:Optional[str]# 构建图builder=StateGraph(GraphState)builder.add_node("planner",planner_agent)# 意图理解+逻辑计划builder.add_node("generator",sql_generator)# SQL生成builder.add_node("critic",sql_critic)# 执行校验builder.add_node("safety",safety_auditor)# 安全审计builder.add_edge(START,"planner")builder.add_edge("planner","generator")builder.add_edge("generator","critic")builder.add_conditional_edges("critic",decide_next)# 错误→回generator,成功→safetybuilder.add_edge("safety",END)# 编译(带内存检查点,支持对话上下文)graph=builder.compile(checkpointer=MemorySaver())

为什么用LangGraph而不是直接调函数?三个理由:

  1. 状态持久化MemorySaver自动保存每步状态,多轮对话不丢上下文
  2. 条件分支:Critic校验失败自动回退到Generator,无需手写if-else
  3. 可观测性:每个节点的输入输出天然可追踪,方便调试和可视化

七、Oracle → DuckDB 方言适配:离线可跑的秘诀

真实晶圆厂用的是Oracle,但开发和测试不可能连生产库。解决方案:用DuckDB做内存模拟,加一层方言翻译

def_translate_oracle_sql(sql:str)->str:"""把Oracle语法翻译成DuckDB兼容语法"""sql=re.sub(r'TRUNC\s*\(([^,]+),\s*[\'"]MM[\'"]\)',r'DATE_TRUNC(\'month\', \1)',sql,flags=re.I)sql=re.sub(r'ADD_MONTHS\s*\(([^,]+),\s*([^)]+)\)',r'(\1 + INTERVAL \2 MONTH)',sql,flags=re.I)sql=re.sub(r'SYSDATE','CURRENT_TIMESTAMP',sql,flags=re.I)sql=re.sub(r'NVL\s*\(','COALESCE(',sql,flags=re.I)sql=re.sub(r'TO_DATE\s*\(','CAST(',sql,flags=re.I)# ... 更多规则returnsql

这意味着:所有Skill模板可以用Oracle原生语法编写(贴合真实场景),运行时自动翻译为DuckDB执行。开发和CI/CD完全离线,不依赖任何外部数据库。


八、Human-in-the-loop:让人和AI协作而非对抗

Streamlit界面设计遵循一个原则:AI生成,人类拍板

┌──────────────────────────┬──────────────────────────┐ │ 聊天区 (60%) │ Agent状态面板 (40%) │ ├──────────────────────────┼──────────────────────────┤ │ 🧑 用户:查A3良率趋势 │ 📍 当前节点: generator │ │ 🤖 Agent:已匹配Skill │ 📋 逻辑计划: 按月聚合... │ │ │ 🔍 Schema: LOT_TRACKING │ │ ┌──────────────────────┐ │ 💬 反馈: 无 │ │ │ SELECT ... │ │ 🔄 迭代: 1/3 │ │ │ (可编辑的SQL框) │ │ │ │ └──────────────────────┘ │ ┌──────────────────────┐ │ │ │ │ ▶ 执行 ❌ 不满意 │ │ │ ┌──────────────────────┐ │ └──────────────────────┘ │ │ │ 执行结果表格 │ │ │ │ │ MONTH | AVG_YIELD │ │ │ │ │ 2025-01| 92.3% │ │ │ │ │ 2025-02| 94.1% │ │ │ │ └──────────────────────┘ │ │ └──────────────────────────┴──────────────────────────┘
  • 编辑框:AI生成的SQL不是终点,用户可以修改后执行
  • 不满意按钮:点击后iteration_count++,Agent带着上次错误信息重新生成
  • 状态面板:实时展示Agent在想什么、用什么工具、当前进度

九、双层安全拦截

工业场景容不得半点闪失——绝不能让AI生成一条DELETE语句

# 第一层:ExecuteSQLTool 正则拦截DANGER_PATTERNS=[r'\bDROP\s+(TABLE|INDEX|VIEW|SCHEMA|DATABASE)',r'\bDELETE\s+FROM\b',r'\bINSERT\s+INTO\b',r'\bUPDATE\s+\w+\s+SET\b',r'\bALTER\s+(TABLE|INDEX|VIEW)',r'\bCREATE\s+(TABLE|INDEX|VIEW|DATABASE)',r'\bTRUNCATE\s+TABLE\b',r';\s*(DROP|DELETE|INSERT|UPDATE|ALTER)',# 多语句注入]# 第二层:SafetyCheckTool 独立审计节点# 在LangGraph工作流中,critic之后再次校验

两层防御的哲理:第一层是"防呆"(简单粗暴但有效),第二层是"审计"(独立节点,防止第一层被绕过)。即使LLM产生了危险SQL,也绝无执行可能。


十、评测体系:195条用例100%通过

📊 总览: 总用例: 195 通过: 195 失败: 0 总准确率: 100.0% Skills 总数: 239
用例数覆盖维度设计意图
Gen320基础单表查询验证核心链路可用
Gen425多表JOIN/聚合/子查询覆盖跨表关联
Gen5115NULL/闰年/ROLLUP/CUBE/安全拦截/中英混合压力测试,115条是主力
Gen630多步业务流/统计建模(CORR/STDDEV/PERCENTILE)/数据血缘复杂分析场景

Gen5的115条是用心之作——它包含了你能想到的几乎所有边界case:

  • 闰年2月29日的日期计算
  • 全NULL列的聚合处理
  • 空结果集的友好提示
  • 超长SQL(>100行)的渲染
  • 中英混合输入(“查A3 yield trend”)
  • SQL注入尝试的拦截

十一、关键数据:12张表、6.5万行

行数说明
LOT_TRACKING5,000批次追踪(核心表)
WAFER_TEST10,000晶圆测试参数
EQUIPMENT_LOG8,000设备运行日志
SPC_RESULTS20,000SPC统计过程控制
PRODUCTION_LOG15,000生产日志
DEFECT_ANALYSIS3,000缺陷分析
PROCESS_STEP50工艺步骤定义
CUSTOMER_ORDER1,500客户订单
+4张主数据表产品/设备/员工/物料

6.5万行数据全部在内存中运行,DuckDB的OLAP性能绰绰有余,单条查询毫秒级返回。


十二、项目结构

fab_text2sql/ ├── eval_dashboard.py # 评测入口(一键跑195条用例) ├── requirements.txt ├── .env # LLM API Key ├── README.md │ ├── src/ │ ├── core/ # 13个核心模块 │ │ ├── semantic_layer.py # ★ Skill模板库 + 术语词典 │ │ ├── tools.py # ★ 9个工具 + ToolRegistry │ │ ├── agent.py # ★ Text2SQLAgent (ReAct) │ │ ├── agents.py # LangGraph节点Agent │ │ ├── workflow.py # LangGraph状态图 │ │ ├── oracle_mock.py # DuckDB + 方言翻译 │ │ ├── schema_knowledge.py # 12张表Schema │ │ ├── data_generator.py # Mock数据生成 │ │ ├── vector_index.py # TF-IDF向量检索 │ │ ├── llm_client.py # LLM客户端封装 │ │ ├── config.py # 全局配置 │ │ └── utils.py # 通用工具 │ │ │ └── skills/ # 分代Skill注入 │ ├── install_gen3_skills.py │ ├── install_gen4_skills.py │ ├── install_gen5_skills.py │ └── install_gen6_skills.py │ ├── tests/ # 195条评测用例 │ ├── test_gen3.py │ ├── test_gen4.py │ ├── test_gen5_edge.py │ └── test_gen6.py │ ├── scripts/ # 应用入口 │ ├── main.py # CLI / API │ ├── cli.py # rich交互式命令行 │ └── app.py # ★ Streamlit Web界面 │ └── archive/ # 历史调试脚本

十三、运行方式

# 1. 安装依赖pipinstallduckdb langgraph rich streamlit scikit-learn pandas openai# 2. 评测(验证195/195)python eval_dashboard.py# 生成 eval_dashboard.html,浏览器打开查看可视化报告# 3. CLI交互模式python scripts/main.py# 4. Streamlit Web界面streamlit run scripts/app.py

界面展示:


十四、经验总结:做对了的5件事

✅ 1. Skill优先,LLM兜底

不是所有问题都需要大模型。确定性模板在领域场景的准确率和效率碾压LLM。

✅ 2. 测试驱动Skill工程

先写会失败的测试用例,再写模板让它通过。239个模板不是拍脑袋写的,是195条用例逼出来的。

✅ 3. 方言翻译层

Oracle→DuckDB的翻译让项目完全离线可跑,CI/CD无依赖,开发体验极佳。

✅ 4. 双层安全

正则拦截 + 独立审计节点,即使LLM"叛变"也执行不了危险操作。

✅ 5. Human-in-the-loop

AI不是取代人,是辅助人。编辑框 + 不满意按钮 + 状态可视化,让人在回路中始终掌控。


十五、局限与展望

当前局限

  • Skill模板需要人工编写,虽然分代注入降低了门槛,但仍是人力成本
  • 239个模板覆盖的是已知场景,长尾查询仍依赖LLM兜底(准确率约70%)
  • DuckDB模拟无法100%还原Oracle的优化器行为

未来方向

  1. 自动Skill挖掘:从SQL日志中自动提取高频模式,半自动生成模板
  2. RAG增强:将表注释、字段描述、历史SQL存入向量库,提升LLM兜底质量
  3. 多轮对话:支持"把刚才的查询按周聚合"这种上下文依赖的交互
  4. 真实Oracle适配:替换DuckDB为Oracle连接,方言翻译层反向工作
  5. ChatBI产品化:从Text2SQL进化为完整的对话式BI,支持图表自动推荐

写在最后

Text2SQL不是一个新课题,但工业级落地学术Benchmark刷分是两回事。

Spider 2.0上GPT-4o只有10%成功率,不是因为模型不够强,而是因为真实世界的数据库太脏、太复杂、太有领域特性

解法不是更大的模型,而是更好的架构。Skill模板、术语词典、方言适配、安全拦截、Human-in-the-loop——这些工程化的东西,才是让Text2SQL从"Demo惊艳"走向"生产可用"的关键。

代码不一定要优雅,但一定要对业务有用


参考资源

  • Spider 2.0: The Second Edition of the Spider Dataset — 企业级Text2SQL Benchmark
  • BIRD: Large-Scale Dataset for Text-to-SQL — 真实数据库+脏数据
  • LangGraph官方文档 — Agent编排框架
  • DuckDB官方文档 — 内存OLAP引擎
  • ReAct: Synergizing Reasoning and Acting — ReAct范式原论文
  • LinkedIn’s SQL Bot — 工业级Text2SQL实践参考

如果这篇文章对你有帮助,欢迎点赞、收藏、转发三连 🙏

有任何问题,评论区见!