ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Text2Metrics:基于指标语义层的BI智能问答架构实践

Text2Metrics:基于指标语义层的BI智能问答架构实践

几乎每家做BI的团队,这几年都在跟“大模型+自然语言查数”较劲。我们也不例外。去年年中我们接了一个很典型的客户需求:业务人员不想再记报表路径,希望直接打字问系统“帮我看看华东区上个月的销售额,跟去年同期比怎么样”。按当时的惯性思维,第一反应是做个Text2SQL——把用户问题翻译成SQL,让模型去数据库里跑。但真把这个方案推到线上试跑,才发现这条路在成熟BI产品里根本走不通。

业务人员那句话里全是坑。“销售额”含不含税、包不包含已取消订单?“上个月”按自然月还是财务月?“华东区”到底是哪几个省?跟“去年同期”比,同期口径要不要排除当年新增渠道?这些答案全不在问题文本里,而在企业沉淀多年的指标口径定义里。SQL只是连接数据库的语言,不是连接业务的语言。所以当我们决定重构BI智能内核时,直接把方案从“文本翻译成SQL”换成了“文本先翻译成指标,再由指标引擎生成查询”。这套思路,我们内部叫Text2Metrics。这篇文章就是把这套架构从设计到落地的完整复盘,重点放在那些网上不太会写、但实际踩到才会懂的问题上。

1. Text2SQL在真实BI场景里为什么失灵

1.1 业务提问不是查询语句,而是口径描述

先别着急骂大模型生成SQL不准。真正让Text2SQL在BI场景失效的,根本不是模型的SQL生成能力,而是用户的自然语言和企业指标口径之间存在一道翻译鸿沟。

举个例子。业务方问“最近三个月毛利怎么样”,在同一个企业里可能对应两种完全不同的算法。财务部门定义的毛利是营业收入减营业成本,不含税金及附加;销售部门定义的毛利可能把运费也扣掉了。两种口径算出来,数字能差出几个点。即便是内部指标平台已经统一了某个口径,用户也不会把这一长串限定条件写进问题里。用户默认“你知道我说的是什么”。

SQL本身是没有“口径”概念的。它只认表、字段、聚合函数。大模型把“毛利”翻译成 SUM(profit) 很容易,但总和里包不包括一条已退款订单,决定了这张报表能不能过财务的审计。过去我们做传统BI报表时,口径是通过几十页数据字典和人肉沟通去对齐的;到了自然语言查数时代,大模型没有渠道读到这些隐性的业务约定,只能靠猜。靠猜,就一定有概率错。而且错的还不一定是SQL语法,是业务语义。语法错了报错能看出来,语义错了看起来一切正常,数字却不对,这种错误对BI产品是致命的。

说到底,用户提问的是一段带业务前提的自然语言,模型需要的是一套带明确口径定义的指标语义体系。如果这个体系不存在,所有Text2SQL的努力都是在沙滩上盖楼。

1.2 大模型不懂“查询规划”

即便我们把问题限定在口径清晰的场景,Text2SQL还有第二道坎:复杂查询规划。

真正的企业级分析查询很少是“查一张表、聚合一下”那么简单。典型的是“按月统计各品类销售额、环比、同比、贡献占比”,一次查询要join好几张表,涉及过滤、分组、去重、窗口函数、多维对比。大模型写这种SQL,经常出现三种情况:第一,逻辑残缺,比如同比的计算没有考虑到上年同月不存在数据的处理;第二,表关系用错,比如事实表和维表join时选错关联键,导致笛卡尔积膨胀;第三,过分依赖“在WHERE里硬筛”,把应该放子查询的逻辑全部压在一个超大查询里。

模型不是不会算,是它不会像资深数据分析师那样做查询规划。资深分析师会先想清楚:要回答这个问题,需要哪张事实表、哪些维表、先算哪个粒度、再怎么聚合、最后怎么呈现。大模型在做推理时是“一步步生成token”,它并没有一个显式的“先规划后执行”环节。你给它一个复杂问题,它看到的是文字序列,不是数据结构。

这也是我们后来设计Text2Metrics时一个非常重要的启示:与其让模型在一个开放式空间里自由发挥写SQL,不如把查询过程拆成模型擅长的“识别与选择”和引擎擅长的“计算与执行”。让模型去选择正确的指标、正确的维度和正确的时间范围,至于SQL的生成,交给基于指标定义的可控查询计划器。

1.3 性能与权限的黑盒风险

Text2SQL还有一个被严重低估的问题:不可控。

我们内部做过一轮压测,让大模型针对一个包含数亿行记录的事实表回答业务问题。模型生成的SQL里,有好几条是对全表做聚合后再join维表,扫描成本极高,到了生产环境会让BI系统直接超时。就算加了超时保护,资源也被浪费了。自然语言问题太开放,模型无法预估它对数据库的冲击。没有配额管理、没有扫描行数预测,一个“好问题”足以拖垮一套在线分析系统。

权限更麻烦。传统报表系统对每个用户可以访问的数据范围是有严格行级授权的。Text2SQL把SQL生成权给了模型,等于把权限的最终执行寄托在“模型不会生成越权SQL”的假设上。假设是靠不住的,特别是多轮对话里,用户可以通过上下文诱导模型拼出不同的过滤条件。有一次测试,一个只被授权查看A区域的运营问“帮我查一下B区域的库存”,模型生成的SQL里真的没有带任何区域过滤条件。所幸是我们内部测试,没造成数据泄露,但这已经说明:靠提示词来约束权限,本身就是定时炸弹。

Text2SQL不是不能用,它更适合那些表结构简单、口径清晰、权限管控弱的个人效率工具场景。一旦进入企业级BI,它就缺了三个东西:指标语义层、查询规划层、权限执行层。Text2Metrics要补的,正是这三块。

2. Text2Metrics的建模思路:把口径变成可执行资产

2.1 指标中心化:把口径固化,而不是让模型猜

我们团队内部对Text2Metrics的一个核心判断是:大模型不应该直接回答“数据是多少”,它应该回答“用户问的是哪个指标”。一旦指标确定了,数值计算就交还给了指标引擎。这个判断彻底改变了架构。

具体做法是把所有核心指标注册到一个指标资产中心,每条指标不只包含一个SQL表达式,而是包含完整的语义定义。我用一个简化的伪代码说明一下:

metric: gmv alias: [成交总额, 销售额, 销售金额] description: 已完成支付的订单金额合计,剔除全额退款订单 owned_by: 电商运营中心 measure: table: fact_order field: pay_amount aggregation: sum filters: - order_status = 'paid' - is_refunded = false dimensions: [order_date, channel, region, product_category] time_rules: - default_granularity: month - fiscal_offset: 0

当用户问“华东区5月成交总额是多少”时,系统做什么?不是让模型从零写SQL,而是让模型完成一个选择题:这句话里,“成交总额”命中 gmv 指标,“5月”命中订单日期维度的一个月份过滤,“华东区”命中区域维度的一个成员集合。模型需要产出的是一份结构化的查询意图,而不是一行SQL。

这个转变最大的价值在于,业务口径成了企业资产的一部分。口径变更时,只需要修改指标定义中心,所有基于该指标生成的问答结果自动修正。而Text2SQL方案里,口径散落在每一次生成的SQL中,改一处漏一处。

2.2 从“模型写SQL”变成“模型组装查询”

拆开看Text2Metrics的查询链路,大致是五层:

  1. 意图解析层:识别用户问题中的指标、维度、时间范围、统计粒度、对比需求。
  2. 指标匹配层:基于指标的别名体系、语义相似度,把文本中的业务词绑定到指标资产中心的唯一指标ID。
  3. 查询计划层:根据匹配到的指标定义和维度约束,由查询计划器生成标准查询语义。
  4. 权限注入层:注入当前用户的角色、数据空间、行级权限条件。
  5. 执行与解释层:交给底层OLAP引擎执行,并把SQL片段、口径描述、数据来源组装成可解释的分析结果返回给用户。

其中第3步值得多说几句。过去我们让大模型输出SQL,结果不可预期。现在查询计划器只接受“指标ID+维度列表+过滤条件+时间范围”这段结构化指令,再由它去查指标定义,反向生成SQL。大模型被移出了“直接写SQL”的关键路径。

这不是我们的独创,业内叫Semantic Compilation,但很少有人把它在产品里落实。我们落实之后发现,模型的幻觉率大幅下降。原因很简单:模型的自由发挥空间被压窄了,它不需要记忆复杂表结构,不需要生成JOIN条件,只需要做“识别文本中的业务对象并映射到指标资产”。这件事恰好是语言模型的强项。

2.3 为什么不直接微调大模型写SQL

有同行问过,与其绕这么大一圈,为什么不对大模型做领域微调,让它学会这家企业的口径?

我们的回答是:微调解决不了口径漂移的问题。企业指标口径隔几个月就可能变一次,比如今年销售部门决定把“新客”的定义从“首单时间在统计周期内”改成“首次注册时间在统计周期内”。如果是微调模型,你需要重新准备数据、重新训练、重新发布,这个周期对业务来说是灾难。

另外,微调本质上是把知识藏进模型权重里,用户看不到,出错了也没法追责。而Text2Metrics把口径知识外置在指标资产中心,每一次回答都可以展示“我按这个口径算的”。业务人员对BI系统的信任,很大程度上依赖这种可复核性。黑盒AI在分析决策场景里是没有生存空间的。

当然,我们不排斥微调。我们甚至认为好的产品应该在意图识别阶段引入针对业务领域的调优模型,让“华东区”这种同义表达识别得更准。但微调只用来提升“理解”的准确率,绝不用来生成“计算逻辑”。计算逻辑永远来自指标资产中心。

3. 从Copilot到Agent:被动问答如何升级成主动分析

3.1 Copilot的本质:一个增强的翻译器

Text2Metrics解决了“单轮问答怎么答对”。但用户在真实场景里几乎不会只问一句话就结束。最常见的情况是:问完“华东区5月成交额”,马上跟一句“那苏州呢”“跟4月比呢”“环比上升主要是哪个品类”。这种连续追问,需要系统对上下文有记忆,还需要对“分析目标”有理解。这就是从Copilot走到Agent的原因。

Copilot本质上还是一个增强的翻译器:用户输入一句,系统答一句。它不对用户的长期目标负责,也不校验自己的回答是否真的回答了用户想知道的结论。很典型的场景是用户问“为什么这个月销售额下滑了”,如果是Copilot,它可能只返回一张销售额趋势图。但用户真正想知道的不是“下滑了多少”,而是“下滑的原因可能是什么”。这个差距,不是靠改进翻译效果能填平的,需要把产品形态从“问答工具”升级成“分析智能体”。

我们做的Agent,初版思路是把一个完整的分析任务拆成五个环节:理解意图、拆解问题、规划查询、执行计算、校验归因。用户的问题只是起点,Agent要自己判断“为了回答这个问题,我需要哪些数据,做哪几步计算”。

3.2 Agent的工作流水线:意图识别、取证、编排、校验、解释

具体落地时,我们定义了一条Agent流水线:

第一步是意图识别。先判断这是一个“查询型问题”还是一个“分析型问题”。查询型问题如“华东区5月成交额是多少”,分析型如“为什么华东区5月成交额环比下降”。两种问题的处理路径完全不同。分析型问题会触发Agent的拆解逻辑。

第二步是取证。Agent会把分析目标转成一组可执行的指标查询。以“为什么下滑”为例,它会自动规划出至少三类查询:一个是时间序列查询,确认下滑趋势;一个是同环比对比查询,确认下滑幅度;一个是维度拆解查询,把总量按区域、品类、渠道拆开,找出贡献度最高的下滑来源。这个过程中,Text2Metrics的指标匹配能力是地基。

第三步是编排。Agent需要决定先查什么、后查什么、哪些查询可以并行。我们内部设计了一个叫“分析任务图”的数据结构,把并行的子查询组织在一起,减少交互延迟。一个复杂的归因分析,如果串行查询可能要15秒,编排成并行后能压到5秒以内。

第四步是校验。这是我很看重的一步。Agent拿到查询结果后,不会直接输出,而是先自检:这个结果和用户问题对齐了吗?有没有可能因为权限过滤导致结果不完整?时间范围是不是跟问题说的一致?如果发现异常,Agent会主动标记而不是硬着头皮回答。

第五步是解释。最终返回的不只是一个数字或图表,还包括Agent的分析路径:这个问题我拆成了几个子问题,每个子问题我用了什么指标、什么维度、什么时间范围,最终结论是什么。这本质上是在建立用户对AI分析结果的信任感。

3.3 多轮记忆与“分析动作”的编排

Agent和Copilot的另一个关键差异,是能不能记住对话中的约定。用户说“以后我说的华东区都指江浙沪”,Agent会在会话上下文中记录这个约定,后续所有涉及华东区的查询都自动按“上海+江苏+浙江”展开。Copilot做不到这种程度,因为它没有“分析状态”的概念,每轮都是独立的。

更体现Agent价值的是“分析动作”的编排。用户问完“5月哪个品类销售额最高”,Agent给出答案后,会自动补一个后续分析动作建议:“是否要查看这个品类的价格带分布?”用户点一下,就进入下一个分析路径。这不是一个简单的关键词推荐,而是基于当前分析上下文生成的数据探索路径,本质上像是一个资深分析师在带着业务用户做分析。

我们后来把这类能力做成了“分析动作模板库”,沉淀了一批常见分析路径,比如“先看趋势,再看构成,再看异常点”。Agent会在合适的节点自动判断是否触发下一个动作。这个功能上线后,业务人员的使用深度比原来报表搜索提升了不止一个量级。

4. 指标治理与权限:Text2Metrics要靠工程兜底

4.1 指标分层与口径统一

Text2Metrics听上去是个技术架构,但真正决定它能跑多远的,是指标治理的基础打得牢不牢。我们内部把指标分成三层。

第一层是原子指标,比如订单金额、订单数量、客户数量。它们是直接从事实表聚合就能得到的,定义最简单。第二层是派生指标,比如GMV、净销售额,是在原子指标基础上叠加过滤口径得到的。第三层是复合指标,比如毛利率、客单价、同比增长率,需要多个指标之间做四则运算或时间对比。

这个分层为什么重要?因为大模型在匹配用户问题时,最容易搞混的是复合指标。用户说“毛利率怎么样”,如果Agent直接去匹配“毛利”,再除以“营收”,它需要知道分子分母分别对应哪个原子指标。我们把复合指标显式建模成“指标表达式”,例如:

metric: gross_margin type: composite expression: gross_profit / revenue

这样一来,Agent就不需要自己推理“毛利率怎么算”,它只需要识别“毛利率”这个指标ID,表达式的求值交给指标引擎。口径的统一性就此保住。

4.2 行级权限必须下推到指标执行器

前文提到,Text2SQL方案里的权限风险非常大。Text2Metrics在这方面的优势不只是语义识别,更是因为我们把权限控制从提示词约束改成了执行器强制。

具体做法是:所有指标查询在执行前,都会经过一个权限策略注入器。系统先获取当前用户的角色和授权数据空间集合,然后把它编译成过滤条件,注入到查询计划中。用户想查B区域,但权限只开放了A区域,最终执行的查询会被强制截断到A区域。不管Agent是否理解用户的请求,权限执行都不会被绕过。

这里有个细节:行级权限下推不能只加一层WHERE条件,还要考虑聚合结果是否会泄露。比如用户被限制只能看A区域,但他问“全国销售额”,系统如果返回全国汇总数,即便他看不到明细,也存在数据泄露。我们在权限注入器里加了一个“粒度和权限一致性校验”,如果查询粒度高过用户权限粒度,执行器会直接拒绝。这条规则写成了一条硬约束,Agent没有权限修改它。

4.3 血缘、审计与冷启动

指标资产中心不仅是口径字典,它还是数据血缘和审计的载体。用户问完“毛利率”,系统返回结果时附带一张指标卡片,上面标注了“毛利率=毛利/营收”“毛利口径来自财务部门”“营收含税且剔除退款”。这些信息对业务人员来说,比查询结果本身更有说服力。

审计方面,我们保留每一次指标查询的完整日志:谁在什么时间问了什么口径的指标,系统返回了什么数据。这在传统BI时代是可选项,在AI问答时代变成了必选项。因为一旦出现口径纠纷,AI系统必须能自证“我问的就是这个定义”。

冷启动是很多团队会忽略的问题。我们的经验是:不要在第一天就追求指标体系的完整覆盖,而是先梳理出高频核心指标,通常几十个就够覆盖六成以上日常问题。然后通过新版本问答的“未命中分析”日志,持续扩展指标字典。有一次我们就是通过日志发现很多人在问“退货率”,原来业务部门最近在重点盯售后质量。这个指标我们没有注册,导致连续三周命中率下滑。后来补上定义之后,命中率立刻回升。所以,Text2Metrics的指标资产,不是一次性建完的,而是跟着业务走得跑起来的活资产。

5. 线上跑问答的错题集:50多条真实案例的复盘

5.1 同义词和指代歧义:毛利与毛利率

任何系统上线前都觉得自己做得差不多了,上线后才知道自己哪里痛。我们拿内部几十个业务人员的真实问答做了试跑,整理了厚厚一本错题集。第一个高发问题就是同义词和指代歧义。

最典型的例子是“毛利”。有人问“我们毛利怎么样”,本意是毛利总额,系统却默认匹配了毛利率。因为“毛利率”在指标资产里的别名设置更宽泛,导致文本匹配时被拉偏。还有“新客”这个词,有人指“新注册用户”,有人指“新成交客户”,系统如果没有上下文提示,很容易选错。

我们的解法是在指标匹配层加了“主动澄清”机制。当候选指标有两个以上且置信度非常接近、无法用上下文消歧时,Agent不会硬选,而是会反问一句:“您指的毛利是毛利总额,还是毛利率?”虽然多了一轮交互,但准确率提升非常明显。业务人员并不会觉得烦,反而觉得系统挺懂行。

然后还需要一个事后反馈回路。用户如果对回答不满意,点一下“结果不对”,系统会把这句话和候选指标列表存下来,作为后续消歧模型的训练样本。三个月的反馈积累下来,同义匹配的准确率高了不少。

5.2 财月、自然月与“去年同期”的时间陷阱

时间口径是第二个重灾区。很多企业不是自然年,而是有自己的财务周期,比如从每年4月1日开始算新财年。如果Text2Metrics没有把时间规则显式建模,用户问“今年第一季度”很可能被系统理解成自然年季度,而不是财年季度。数字错得离谱,用户一看就知道不对,但系统自己检查不出来。

“去年同期”也一样。我们遇到过好几类情况:有的指标按发货日期统计,有的按订单日期统计,那么“去年同期”对应的日期区间就不同。即便约定俗成统一用订单日期,碰到闰年、调休、节假日造成的周期性偏移,简单的日期相减也会出错。

最后我们在时间维度建模里加了两个规则:一是每个指标可以声明自己默认的时间字段和粒度;二是系统提供“财年偏移量”配置,例如 offset=-3 表示财年从4月开始。查询计划器在生成时间条件时会自动带入这些规则。更重要的是,Agent在回答带时间对比的问题时,会像这样输出解释:“同比期间为2024年5月,按自然月计算,同时期新渠道已纳入统计口径。”让用户知道系统没搞错。

5.3 模型的过度自信:没命中指标也要硬答

第三个坑是模型的过度自信。测试初期经常出现这种情况:用户的问题其实没有命中任何已注册指标,但Agent为了不冷场,拼凑了一个答案出来。比如问“服务工单的一次性解决率”,指标资产中心根本没有这个指标,Agent却从工单表里拉了个近似数据硬答。数字是算出来了,但口径完全不成立,用户要是拿去做周报,会出大事。

我们后来给Agent加了一道“置信度门槛”:如果问题未能映射到任何高置信度指标,Agent必须承认“这个指标我还没有纳入体系”,然后引导用户查看相近指标或提出新建指标的需求。这不是能力上的退步,反而让用户更信任系统。因为企业分析场景里,宁可说不知道,也不能给一个看似精确的伪答案。

技术实现上,我们为每次指标匹配产出一个综合置信度分数,包含文本相似度、上下文命中度、口径完整度三项。只有分数高于阈值才会进入查询计划阶段,否则走澄清路由。实测这条规则把无效答案率降低了六七成,业务投诉大幅减少。

5.4 性能兜底与线上回归

最后说性能。Text2Metrics把SQL生成交给了查询计划器,SQL本身可控了,但自然语言问题对数据扫描范围的冲击依然存在。用户问“过去十年的月度销售额趋势”,虽然查询计划很标准,但如果事实表按天存储,一次要扫三千多天数据,还是会拖垮引擎。

我们的兜底策略有三层:第一,在执行器层面设了最大扫描时间范围,超过直接拒绝;第二,设了结果集上限,防止一次性返回百万行明细;第三,给重要指标单独配置缓存策略,高频问题直接走缓存,大幅降低资源占用。

同时,我们把线上真实用户的高频问题沉淀成一套自动化回归集,每次指标定义或Agent更新之后,先跑一遍回归集,确保老问题不会因为新改动而答错。这套回归集帮我们挡过好几次事故。有一次我们调整了GMV的退款过滤逻辑,本意是修一个边界case,结果回归集立刻发现“近30日GMV”历史口径全变了。如果没有回归,这种问题上线后业务肯定会炸。

最后想说的

从Copilot到Agent,从Text2SQL到Text2Metrics,这套重构做下来,我最大的体会是:AI真正嵌入BI产品,卡点从来不在模型智商,而在企业对数据资产的梳理程度。Text2Metrics表面上是一层技术中间件,但做它的过程,逼着我们重新梳理了指标体系、权限边界和数据血缘。这些活儿又笨又细,却是AI能安全地跑在业务线上的前提。

如果你也在做类似的事情,我的建议是:别急着上多复杂的Agent,先花精力把20个最核心指标的语义定义做到无懈可击,再让Agent在这20个指标上跑出一次让业务方眼前一亮的体验。体验有了,后面推广指标治理就顺利得多。AI是放大器,数据秩序好,它放大的是效率;数据秩序乱,它放大的就是错误。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进