1. 智能问数为什么难核心不是写SQL而是找对表和找对列1.1 从传统BI到自然语言问数的需求变化做自然语言问数这几年我最大的体感是大家一开始都以为难点在让大模型写SQL真正上手才发现卡脖子的地方在从几百张表、上万个字段里找对那一个表和那几列。这篇实践总结就围绕大模型时代的智能问数重点聊聊我们在多策略召回与精排环节的设计思路、踩坑过程和最终沉淀下来的方案。先还原一下需求场景。早年做数据平台的时候业务方取数基本就是提工单、排期、等Excel熟练一点的同学自己写SQL但前提是你得知道数仓里有什么表、表里有什么字段、字段口径是什么。自然语言问数想干掉的就是这个门槛——业务同学用大白话问一句上个月华东区的退货率是多少系统自己完成指标识别、维度匹配、SQL生成、结果返回。传统BI时代不是没人试过这类方案关键词匹配、规则模板都做过效果一直勉强。大模型时代把这件事推到聚光灯下核心原因是大模型写SQL的能力真的够用了但一个完整的问数系统里SQL生成只是最后一公里。真正决定问答质量的是给SQL生成器喂哪张表、哪几个字段。这一步不准再强的生成模型也只能在一个错误的候选集上生成一段语法完全正确、结果完全错误的SQL。1.2 大模型把问数从能不能做推到做得好不好大模型出现后Text-to-SQL的门槛确实被拉低了一大截。简单场景下把几张表的建表语句拼成Prompt丢给大模型它就能输出基本可跑的SQL。但业务落地会立刻撞上几个现实问题。首先是库表规模。一个中型公司的数仓往往有数百张表、上万字段Prompt根本放不下硬塞进去模型反而会被无关信息干扰。其次是口径歧义销售额在订单表里可能是不含税口径在财务表里是含税口径还有门店级、经销商级、订单级的区别——问题不是库里没有这张表而是要从几十张相似表里选对那一张。再就是权限与语义纠缠同一个词对不同角色边界不同。最后还有SQL生成本身的幻觉表名、字段名偶发编造加了约束也会出问题。这些问题串起来指向一个结论智能问数系统的核心竞争点在从海量数据资产中准确锁定相关的那一小部分。这就是召回和精排的主战场。候选集做准了下游生成的成功率和结果准确率才有保障候选集里混了垃圾下游模型再聪明也是戴着镣铐跳舞。1.3 问数问题的本质拆解召回与精排的边界业界比较常见的智能问数链路拆成四段Query理解、召回、精排、SQL生成与结果呈现。大家习惯把注意力放在头和尾但中间两层在工程上最见功夫。Query理解负责把用户问题解析成语义结构提取指标、维度、条件、时间范围等要素。召回层目标是高召回宁可多给也不能漏把可能相关的表、字段、口径定义从海量资产里捞出来。精排层目标是高精度在召回结果里找出真正对应的那一个或几个候选给下游一个干净可信的输入。SQL生成拿到精排结果后再组装查询语句、执行并检验。召回和精排的边界可以用一句话概括召回决定可能性精排决定确定性。召回阶段用便宜、宽泛、多路互补的算法保证不漏精排阶段允许上更重的模型、更丰富的特征保证准。下面我分两大部分把两边的实践细节展开讲。2. 系统总览理解、召回、精排、生成四个环节的职责切分2.1 理解层需要做到什么程度要素抽取、问法分类与歧义标记理解层不需要做得很重但必须完成三个基本任务不然下游全乱。第一个是要素抽取。把自然语言里的时间范围上个月近30天、指标退货率GMV、维度华东区按门店、过滤条件只看线上渠道拆出来。这一步完全可以用规则加小模型组合搞定比如用日期解析库把上个月归一成具体区间用业务词表匹配维度值。第二个是问法分类查询指标数值对比两段时间看趋势查明细是几种典型类型它决定后续SQL组装模板的选择也影响召回阶段对不同类型候选的偏好。第三个是歧义识别同一个词在业务里可能指多个指标需要识别出这里可能有歧义。关于歧义我给一个明确的建议理解层只负责标记歧义不负责消除歧义。真正的消歧要结合候选集信息来做——比如候选字段注释里写了什么、哪些表近期被访问过——放到精排阶段解决更合适。脱离候选集做消歧信息不充分容易拍脑袋。2.2 召回层候选粒度的选择字段级召回加表级聚合召回层的输入是Query理解的输出输出是一组候选对象。这里的候选粒度很关键我踩过的最大的坑就是一开始把粒度定错了。第一版我们按表级别做召回下游拿到几张表就完事了。很快发现一张表有几十个字段用户问的往往是其中两三个的组合比如销售额和退货率放一起看。表级召回太粗精排根本没办法处理字段之间的相关性选择。反过来纯字段级召回又太碎SQL生成时不知道哪些字段同属一张表、join关系怎么走。最终我们采用的是字段级召回加表级聚合的两级结构召回阶段对每个字段连同它所属表、表间关联信息做候选打分精排阶段也直接处理字段级候选进入SQL生成前再把同一张表里的高分字段聚合成可用表组合。这个调整让系统准确率肉眼可见地涨了一截也是我们后来所有迭代的基础。2.3 精排层除了排序还要会拒绝精排层收到召回送来的候选集合一般控制在几十到一两百个要算出每个候选是不是用户真正想要的分数最终把Top1到Top3交给SQL生成模块。精排需要利用的信息远比召回丰富字段与Query的语义相关性、字段在历史问法里的表现、表的使用热度、数据新鲜度、权限可用性以及多个候选之间的互斥关系——选了指标A就不能选指标B。这些特征统一喂给排序模型做优化。精排层还必须有一个拒绝机制当所有候选分数都低于阈值时宁可告诉用户这个问题我暂时答不了也不能随便挑一个表硬生成SQL。这一点工程上极其容易被忽略但是上线之后能省掉大量客服争议和信任损耗。用户被错误答案坑过一次就很难再信任这个系统了。2.4 生成层必须遵守候选白名单约束精排完还不是结束。SQL生成模块会基于精排输出的表字段组装Schema、生成SQL并做校验。这里有一个容易被忽视的分工生成层不应该再自作主张选择候选之外的任何表。我们在这个问题上吃过实实在在的亏。早期把库里全部表结构塞进系统Prompt大模型生成SQL时偶尔会灵机一动挑一张没有被精排选中的表。结果SQL语法正确、逻辑自洽但答非所问。后来加了一个候选白名单约束只把精排输出的候选信息暴露给生成模型其他任何表对模型不可见。就是这么一条简单的约束把生成阶段的幻觉表名问题几乎清零。3. 多策略召回实践四路互补召回与结果融合3.1 单一路径的盲区决定了多路召回的必然性为什么必须多路召回而不是找一个完美的单一算法因为每一路都有明显的结构性的盲区。纯语义向量召回表注释写得潦草、业务黑话和向量空间距离远的时候相似度就不靠谱。纯规则词典召回新词、变体说法、口语化槽点覆盖不到。纯历史日志召回新表冷门表没有访问记录数据完全缺失。多路召回的核心价值不是几个方法加起来分更高而是不同策略的盲区互为补充。这也是信息检索领域几十年沉淀下来被验证过无数次的思路。对我们来说最终沉淀下来的是四路互补策略语义向量召回、词典改写召回、历史日志召回、口径血缘召回。每一路单独看都不完美组合起来才勉强够用。3.2 第一路基于表结构语义的向量召回这是最基础也最重要的一路。做法是把表名、字段名、字段注释、表注释、枚举值描述等信息拼成文本用Embedding模型编码成向量存进向量库用户Query解析结果同样编码做近邻检索取回TopN。几条实操经验值得单独说。拼接文本时一定要加权字段名本身的权重应该高于字段注释因为很多注释是历史遗留的废话备注字段的注释就叫备注。表名和关键字段名高度重合时向量相似度往往已经很有说服力。字段注释必须清洗已废弃暂不用这类标记词会污染向量我们直接剥离并单独维护一份废弃字段黑名单命中即过滤。向量召回还有个明显短板对同义不同形的稳定性差用户说月活库里字段叫MAU向量距离未必够近。这需要靠词典映射在召回前做一次Query改写把用户口语翻成标准词再检索。3.3 第二路基于Query改写与业务词典召回业务词典是问数系统里一份需要长期维护的资产。我们把常见的业务术语、简称、英文缩写、用户口语词整理成映射表比如月活、MAU、月活跃用户对应标准字段回款、到账金额对应另一套口径。用户口头说法标准词 / 目标字段备注月活 / MAU / 月活跃用户active_users_monthly词典命中时给予高先验分回款 / 到账金额received_amount注意含税口径区分毛利 / 毛利润gross_profit与净利互为互斥候选坪效销售额 / 门店面积复合指标需要额外拆解词典维护最忌讳一步到位、后续不管。我们的做法是复盘式迭代每次线上出现用户问的词没匹配上、但人工答案里有正确答案的情况就把差异补进词典。不整理新词条词典会快速腐化。词典召回的具体实现不难分词加归一化匹配把命中的标准字段加进候选集。要注意的是词典命中不只是加入候选还应该给候选一个非常高的先验分。词典是人工维护的高置信信息应该直接左右精排决策。我们在精排特征里专门加了一个词典命中强度特征效果比单纯把词典结果混进候选好得多。3.4 第三路基于历史Query与反馈日志召回系统上线一段时间后会积攒大量用户Query到最终选中字段的日志。把这些日志利用起来本质是搜索引擎里协同过滤的思路迁移到问数场景。做法是对历史日志做聚合得到Query到字段的转移概率表也可以把Query本身向量化存起来做相似检索。新Query进来先匹配历史相似Query字符串编辑距离或者向量相似都可以把相似Query对应的历史高概率字段捞出来。这一路的优势在于能捕捉语义说不清楚、但业务上就是该这么选的隐含规律比如某些用户习惯性地把客户数说成人头数语义向量不一定懂但历史日志里早就出现过这个规律。缺点也明显冷启动阶段没有日志长尾Query覆盖不足。历史日志里有坑必须做反馈过滤。早期系统质量差的时候日志里的垃圾答案会被当成正样本反复强化。我们加了一层过滤只有用户明确点击了结果、或后续没有继续追问重问的日志才进候选池被人为纠偏过的Query只进词典不进日志候选避免错误信号循环放大。3.5 第四路基于指标口径与数据血缘召回这一路是问数场景特有的。很多用户问的指标库里根本没有一张表直接对应而是需要多张表join、套公式计算。数据血缘在这里价值很大。比如退货率没有任何表叫退货率表但数仓血缘里能看到退货明细表join订单表聚合后算退货率。我们维护了一份指标口径知识库记录常见指标的定义、涉及物理表、join键、聚合口径。用户Query命中口径知识库时把相关的所有物理表、关联字段全部加进候选集。实现上不复杂口径知识库本质是结构化数据指标名、口径描述、关联表列表、常见维度字段。复杂的是持续维护——业务口径经常调整口径一变知识库必须同步更新。我们专门建立了口径变更同步流程数仓侧发变更通知问数侧确认后刷新缓存。不维护这路召回很快就会变成坑。3.6 合并、加权与截断候选集质量的第一道闸多路召回结果合并有四个关键动作。去重必须用唯一的字段ID做聚合不同策略可能召回同一个字段来源策略的标记要保留下来。加权上不同策略置信度天然不同词典命中先验分最高历史日志次之语义向量弱一点。合并不是简单相加而是为每路设计基础权重再用来源数这个计数特征做修正——被三路同时召回的字段大概率比只被一路召回的靠谱。截断也很关键。候选过多会拖慢精排也引入大量噪声候选过少又会漏掉长尾相关项。我们一般控制在50到150个字段级候选进精排。这个数字不是拍脑袋定的上线前做过一组实验50和100效果接近扩到200以后指标明显下降。原因也好理解候选多了之后精排模型被灌进大量难分难舍的硬负样本反而把打分尺度拉偏。4. 精排模块让排序模型真正懂业务4.1 它和搜索排序的本质区别很多人天然认为精排就是把相关度排个序。问数场景里精排的目标比这个微妙得多它不是在较好的几个候选里排序而是要找唯一正确的答案。为什么因为下游SQL生成只允许拿到一到三个候选。排第二往往等于没用——用户问华东区退货率你把退货明细表和订单表两个候选一起交给生成模型模型大概率会纠结甚至选错。这和搜索引擎用户自己从10条结果里挑有本质区别。这个定位决定了建模思路、评估指标都和搜索排序完全不同。4.2 四组特征相关性、权威性、时效性、成本精排特征我习惯分成四组来设计分别对应四个维度的判断依据。相关性特征包括Query与字段的语义相似度得分、字面重合度退货是否出现在退货表.退货原因里、词典命中强度、历史Query相似度与转移概率。权威性特征包括表的使用热度近30天查询次数、字段历史被选率、数据质量评分空值率、更新延迟、是否来自指标口径知识库。时效性特征包括表最近更新时间、数据覆盖日期范围、用户Query时间范围与表数据范围的匹配度——Query要今年但表数据只到去年要明确降权。成本特征包括表的数据量级大表join可能超时、join复杂程度。四组特征里最容易被低估的是权威性。我们出过一次线上事故一张废弃数月的老表因为字段名特别匹配被排到第一名SQL跑通但数据是三个月前的业务方差点拿着错误结论去开会。后来在特征里加入表最近更新时间和表活跃度这类问题才基本控制住。4.3 从公式排序到树模型再到交互式模型精排模型的选型取决于数据量和场景复杂度不必一步到位。冷启动阶段数据量小一个加权得分公式就能跑语义相似度0.5、词典命中0.3、表热度0.2加总排序。公式虽糙但胜在透明可解释方便排查问题。数据量到几千几万样本时用梯度提升树模型做Pointwise或Pairwise就够用手工特征直接喂。数据量再大、文本交互信息多的时候可以升级为基于Transformer的交互式精排模型把Query和候选字段描述直接拼起来输入让模型自己学交叉特征——效果通常比树模型好一截推理成本也高一些。问数场景候选就几十个计算量可控能上就上。训练目标上我建议优先用Pairwise。Pointwise让模型学每个候选得多少分Pairwise让模型学正样本分数高于负样本更贴近找对答案的本质。Listwise理论上更优但实现复杂、调参要求高我们试下来收益有限。4.4 训练样本的正负样本艺术排序模型的训练样本是灵魂重点在负样本怎么构造。第一版我们取历史日志里被采纳的候选做正样本、随机采样其他字段做负样本。跑完效果还行但上线后出现一个怪现象语义很像但并不是正确答案的字段分数经常很高。原因是随机负样本太容易区分了模型压根没见过看起来像、其实不对的难样本。后来我们引入了四类硬负样本同一张表里名称相近但口径不同的字段订单金额对订单净额不同表里字段名完全相同但口径不同的候选A表和B表的销售额词面相似但业务完全不同的字段门店数对门店销售额被召回策略高置信召回但历史里用户重问过的候选。硬负样本逼着模型学习细微差异加入后精排Top1准确率在我们内部评测集上提升了差不多8到10个百分点。正样本同样有坑。历史日志里的被采纳不等于正确用户可能是在只有一个候选、没得选的情况下点的采纳。我们构造正样本时做了多候选博弈验证只有当候选集合里确实有正确答案、用户在有干扰项的情况下仍然选中它才算高质量正样本。4.5 校准、兜底与拒绝回答模型上线后有两件事容易被忽略分数校准和兜底规则。分数校准方面精排模型输出分数的绝对值不可靠不同时期训练的模型分布都不一样。我们在线上加了一个相对优势判断如果Top1和Top2分数差距很小比如小于0.05就认为系统不够自信此时把两个候选都输给生成层让生成模型结合上下文做最终决断相当于给大模型留了一个裁量空间。兜底规则就是置信阈值。所有候选分数都低于阈值时拒绝回答返回一句友好的暂时无法找到相关数据并引导用户换个说法。这个设计反直觉很多人觉得拒绝回答代表系统能力不行。但实际运营下来强行给错误答案对业务信任的伤害远远大于诚实的拒绝。错误答案会立刻让用户放弃这个工具而拒绝回答只是多一次重试成本。5. 实战复盘我踩过的四个典型问题5.1 召回全对但精排排错特征冲突案例有次收到业务反馈用户问各门店的坪效系统答成了各门店的销售额。排查链路很有意思。语义向量召回阶段坪效和销售额距离很接近因为坪效字段的注释写着销售额除以门店面积。精排模型又是语义特征权重最大的一版词典里还没收录坪效于是模型看到语义相似度高顺手就把销售额排上去了。问题本质是模型只能靠见过的特征做决策而语义相似度高在某些场景下就是误导信号。两处修复一是词典补上坪效及其标准字段二是给精排模型加了一个口径差异惩罚特征——Query词面带率、比、效等复合指标后缀而候选字段是绝对量指标时显著降权。这类问题靠离线构造特征基本穷尽不了更加说明复盘式迭代的必要每一次线上错误都要回流成词典条目或特征规则。5.2 长尾问法上的失灵模型没见过就乱来精排模型在训练集覆盖的问法上表现不错长尾问题掉得很快。有个典型例子用户问哪个季节卖得好标准答案应该是按月份聚合销售额后对比季度但训练集里根本没有季节这种问法。召回阶段语义向量勉强能把季节和月份关联上但精排模型给月份字段打的分数不够高导致正确候选排在后面甚至被截断掉。后来我们改变了思路不再只依赖精排模型硬扛而是引入Query改写加多路径推理机制先用大模型把长尾Query做一次语义改写拆解哪个季节卖得好被改写成月份加销售额加排序再走召回精排链路。这个调整等于把理解层的复杂度前置让召回精排面对更规整的输入长尾问法的命中率提升非常明显。5.3 冷启动阶段没有日志先跑通最小闭环新业务新系统上线最尴尬的事情精排模型要日志可日志要系统先跑起来才有。我们当时的策略是三条腿同时走路。第一用规则公式做骨架语义相似度加词典命中加表热度加权排序。第二用静态标注数据做小样本训练找业务分析师人工标注几百条Query到正确字段的样本够梯度提升树训一个粗模型。第三系统跑起来之后每天用线上日志回流做增量训练。这里想强调一个认知冷启动阶段不要追求精排很强而是要确保链路完整可迭代。规则方案的错误样本恰恰是最有价值的第一批训练数据。不要因为规则方案效果一般就不上线上线拿真实反馈比关闭在实验室里憋大模型快得多。5.4 离线指标与线上体感的鸿沟我们在离线评测集上把精排Top1准确率做到了80%以上觉得可以上线了。业务方试用第一天第一句话就是这也不准啊。排查下来发现离线评测集是我们自己构造的问题和答案都用的是标准问法而真实用户的问题千奇百怪反问句难道我们上个月没做活动吗、待填词XX区的退货率是多少XX是东北、中英混排FY24 Q1的GMV咋样。这些在离线集里几乎完全没有覆盖。后来我们专门建立了线上真实问法评测集把线上无标注Query抽样人工标注答案每周往里补充新样例。离线指标从此只看真实分布的结果自造的标准问法评测集降级为辅助参考。这个调整生效后离线指标和线上体感才对得上。建立这个评测集花了大概三周的人工精力但此后所有模型升级、特征迭代的收益判断都有了可信的基准。6. 给后来者的工程建议6.1 先窄场景跑通闭环再谈复杂架构我们趟过最大的坑就是一开始把系统设计得过于完整。多路召回、精排模型、大模型生成全套上马结果两三个月过去系统还没在一个真实业务场景里跑起来。浪费的时间全花在搭理想架构上。后来调整节奏先选一个指标口径清晰、表结构简单、用户量有限的场景用规则召回加公式精排加大模型生成的最小闭环跑通拿到真实反馈后再逐步加策略、加模型。系统的复杂度应该跟随业务验证节奏走而不是一开始就按终态设计。这个节奏调整之后第一版系统在三周内就上线了虽然效果糙但数据回流和迭代循环从此建立起来。6.2 评测集要当成产品来维护很多团队做问数系统评测集随便弄几十条样例就完事。但问数系统的评测集本质上是一个活资产。每条错误Case必须回流到评测集评测集要覆盖不同问法、不同表结构、不同业务口径每次模型升级、特征迭代都要回归评测集看分数变化。我们把评测集编号管理每条样例记录来源线上线下、期望答案、失败原因、对应修复策略。修复一个Case之后整个历史评测集要过一遍防止修了东边、漏了西边的回归问题。评测集维护得越细每次迭代越有明确打靶目标而不是凭感觉优化。6.3 三个环节的协同复盘节奏最后提醒一句多策略召回与精排不是孤立的模块生成层更不是终点。精排要根据召回策略的变化调整特征权重生成层要根据精排输出质量反馈调整候选白名单策略召回词典要根据线上错误持续补充。我们每两周做一次三者协同的复盘把本周Top失败Case按环节归属分类看是召回漏了、精排排错还是生成阶段出了幻觉再决定下一个迭代周期的优化点在哪里。从实际结果看这种做法让系统的进步是可追踪、可预期的而不是东一榔头西一棒子。做智能问数这件事最大的幻觉是以为找到一个大模型就能一步到位真正可靠的进步来自一条设计得足够细、足够透明、并且能持续看到反馈的优化闭环。