Text-to-SQL的救星:我把RAG暴力拆成三路,准确率终于达标了
前四篇聊完了Graph工作流,今天开始拆RAG模块。
聊之前先说个让我头疼了很久的事:早期版本的RAG就一路检索,用户问什么,向量库就搜什么,然后把结果一股脑塞给LLM。结果呢?LLM要么瞎猜表名,要么把“活跃用户”理解成“所有注册用户”,气得人拍桌子。
后来我们做了个很“笨”但极其有效的决定——按知识类型拆成三路RAG:Schema、Example、Knowledge。每路的索引构建和检索策略都不一样。这篇不讲虚的,直接上代码和踩坑记录。
一、一路检索为什么搞不定SQL生成?
先捋清楚LLM写SQL到底缺什么。缺三类东西:
- Schema(表结构)
告诉LLM“数据库里有哪些表、字段叫什么、注释是什么”。没这个,LLM会瞎编。用户问“商品销量”,它不知道库里那张表叫bb_sales_record,敢给你编个product_sales出来。
- Example(NL-SQL示例)
给LLM看几个“问题→SQL”的样例,让它学会你们公司的查询习惯。比如我们把“GMV”硬编码成SUM(revenue),LLM看几个样例就懂了,不用每次在Prompt里啰嗦。
- Knowledge(业务知识)
术语定义、指标口径、查询规则。比如“活跃用户”在我们公司特指“最近7天登录过的用户”,这事儿LLM上哪儿知道去?只能喂。
这三类知识的特性完全不一样,硬塞进同一套检索管道里必然顾此失彼。我做了个对比表,差异一目了然:
用一套检索吃所有场景?纯向量会召回相似但错误的表,BM25又抓不住语义。所以——拆。
二、三路检索的调度骨架
SchemaRagRetrieveNode是Graph工作流的第一个节点,负责把三路结果并行捞回来,塞进state里供SqlGenerateNode渲染Prompt时使用。
下面是简化后的调度逻辑(真实代码在SchemaRagRetrieveNode.java):
三路结果分别写入state的三个key,互不干扰。SqlGenerateNode组装Prompt时各取所需。
三、Schema RAG:表结构怎么建向量索引?
Schema RAG最核心的问题:一张表在向量库里长什么样?
我们的做法是“一表一文档”。来看SchemaRagServiceImpl的索引构建逻辑(真实行号:111-136):
关键设计:双写。每个Schema文档同时写入pgvector(向量库)和Lucene(BM25索引),检索时走混合检索,召回率比单路高出一截。
文档内容格式长这样,对LLM非常友好:
表名:bb_sales_record 注释:销售记录表 列信息: - id BIGINT 主键 - product_id BIGINT 商品ID - revenue DECIMAL(10,2) 销售金额 - sale_date DATE 销售日期 ...元数据三件套(knowledgeType、dataSourceId、tableName)的用途:检索时精准过滤,避免跨类型、跨数据源污染。
四、检索过滤:数据源隔离是硬底线
检索时用filterExpression做过滤,类似SQL的WHERE子句。看一眼代码(51-54行):
两个过滤条件缺一不可:
knowledgeType == ‘schema’—— 只捞Schema,别把Example和Knowledge混进来
dataSourceId == ‘xxx’—— 只查当前数据源,严防跨库泄漏
为了保险起见,我在入口加了一道硬拦截(45-49行):
dataSourceId为空直接拒绝服务。宁可查不到结果,也不能把A数据源的表暴露给B数据源的查询。这条底线必须守住。
五、降级策略:挂了也不能让流程全崩
RAG检索可能翻车——向量库超时、API额度耗尽、网络抖动。SchemaRagServiceImpl里设计了三层降级,逐级兜底:
第一层:混合检索(向量 + BM25)正常走
第二层:混合检索抛异常时,降级为纯向量检索
第三层:向量也挂了?直接从JDBC元数据里拉取所有表结构,虽然没排序但至少能用
核心逻辑在retrieveSchemaContext方法里,用try-catch层层包裹,每层失败就降级并打印告警日志,不影响主流程。
六、Example RAG:自学习闭环
Example RAG的数据来源不是人工录入,而是审核通过的查询自动沉淀。
流程在QueryService的resumeWithReview方法里:
approved:沉淀LLM原始生成的SQL(用户认可)
modified:沉淀用户修改后的SQL(改对的比原始更有学习价值)
canceled:不沉淀(失败的案例没参考意义)
autoDepositExample会把“用户问题 + 成功SQL”写入example_entry表,同时索引到向量库。下次遇到相似问题,Example RAG就把这条捞出来当few-shot喂给LLM。
为什么Example用纯向量检索?因为示例本质是“自然语言→SQL”的映射,语义相似度比关键词匹配重要得多。用户问“上个月销售额”,能召回“上月营收”的示例,靠的就是向量的语义泛化能力。
七、Knowledge RAG:术语匹配为什么必须上BM25?
Knowledge RAG管的是术语、指标、规则。比如:
“GMV” =SUM(revenue)
“活跃用户” = “最近7天登录过的用户”
“订单金额”必须关联bb_order_item表
这类知识的特点是关键词精确匹配比语义理解更重要。用户问“GMV排名”,向量检索很可能召回“销售额”“营收”等语义相近但不对的东西,而用户要的就是GMV的精确定义。
所以我们把Knowledge RAG切成了纯BM25检索:
List<SearchResult> results = fullTextSearchService.search(query, topk, filterExpr);BM25对“GMV”这类精确术语的匹配碾压向量。切完之后的效果很明显——基于某零售客户3个月的查询日志统计,术语召回准确率从不到七成提升到了九成以上。
八、Prompt里的上下文顺序,我们A/B测试了三种方案
SqlGenerateNode把三路结果拼进Prompt模板,顺序长这样:
【Schema 表结构】 ...(schema_context) 【Example SQL 示例】 ...(few_shot_examples) 【业务知识】 ...(domain_knowledge) 【用户问题】 ...(原始query)这个顺序不是凭空拍的,我们当时闲得慌(不是),正经测了三种排列组合:
方案一:Knowledge放最前面 → LLM先入为主,拿着术语定义去套所有问题,无视Schema真实结构,效果最烂
方案二:Example放最前面 → LLM过度依赖示例,遇到没见过的问法直接懵
方案三:Schema → Example → Knowledge → 问题 →胜出
最终方案让LLM先建立“数据库有什么”的认知,再通过示例理解查询模式,最后用业务知识做修正,层层递进。
九、Top-K怎么定?写死成常量了
三路检索的Top-K不一样,直接上最终值:
十、几个让我熬夜的坑
坑一:filterExpression拼接的安全隐患
最初我图省事,直接字符串拼过滤条件:
String filterExpr = "knowledgeType == 'schema' && dataSourceId == '" + dataSourceId + "'";理论上dataSourceId是UUID,不会有单引号,不会触发注入。但代码审查时被同事点名了——不符合安全规约,万一哪天ID生成规则变了呢?
修复方案:在submitQuery入口就校验dataSourceId格式,必须是合法UUID,否则直接拒掉。防御性编程,不给自己留雷。
坑二:示例沉淀导致的循环依赖
最早期设计是QueryService直接调用ExampleRagService去沉淀示例,但ExampleRagService又被SchemaRagRetrieveNode间接引用——好家伙,Spring启动直接报循环依赖错误。
解决方案:把沉淀逻辑内聚到ExampleRagServiceImpl里,QueryService只通过接口调用。Spring用代理对象打破了循环,问题解决。
坑三:新增表搜不到,因为BM25索引没重建
用户在数据源里新增了几张表,但BM25索引没跟着刷新,新表死活检索不到。排查了半天才发现索引是旧的。
修复方案:在buildSchemaIndex里先主动失效旧缓存:
// 0. 失效旧缓存,确保拿到最新表结构 schemaService.evictSchemaCache(dataSourceId);强制每次重建索引时拉取最新的元数据,不给缓存留机会。
总结
这篇把三路RAG的差异化设计从头到尾捋了一遍。核心就几点:
- 分路是必须的——Schema混合检索、Example纯向量、Knowledge纯BM25,各取所长
- Schema双写——VectorStore + BM25,混合检索召回率才稳
- 数据源隔离是底线——dataSourceId为空直接拒,不存侥幸心理
- 三层降级——混合检索→纯向量→JDBC直取,挂了也不崩
- 示例自学习闭环——审核通过的查询自动沉淀,越用越聪明
- Prompt顺序有讲究——Schema在前,Example居中,Knowledge补充,问题最后
学AI大模型的正确顺序,千万不要搞错了
🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!
有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!
就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋
📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇
学习路线:
✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经
以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!
我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~
