副标题:从业务指标、查询条件到数据库 SQL,逆向回放一个结果
前文:客户说"销售额",系统怎样落到订单含税金额或开票含税销售额?
上一篇解决的是"销售额到底指哪个事实"。但用户继续追问时,问题会变成另一种:
text
这个数字是怎么来的?
为什么用了这个模型?
能不能看到它对应的 SQL?
权限条件有没有被带进去?
普通问数只要返回一个结果,解释型问数还要回答结果的生成路径。企业不一定一开始就要建设完整的数据血缘平台,但至少应该能把一次查询回放到它实际使用的语义模型、查询 DSL、权限条件、物理来源和生成 SQL。
这次补上的能力就是按需解释查询:用户追问,或者明确要求解释结果来源时,Agent 才调用解释工具;普通查询仍然走原来的执行入口。
先看最后交付了什么
当前 Foggy 已发布:
- CLI
foggy-runtime query explain,CLI 版本为v0.1.22。 - MCP 工具
dataset.explain_query,Launcher 版本为foggy-runtime-launcher-v0.1.17。 DEFINITION:解释模型和字段定义。RECOMPILED:按当前请求、当前模型版本和当前权限上下文重新编译,并返回查询证据。
用户可以这样追问:
text
请解释刚才的开票销售额是如何生成的,列出使用的 QM、TM、过滤条件和 SQL。
Agent 不需要每次都把底层过程塞进普通回答,而是在出现"为什么""从哪来""对应哪张表""SQL 是什么"这类请求时调用 dataset.explain_query。返回结果再由 Agent 翻译成业务人员能读懂的说明。
第一层:QM 说明用户问的是什么
在 WWI 的发票明细模型中,用户看到的是一个销售事实,技术上它有稳定的唯一名称:
text
QM: wwi_oltp_invoice_lines
measure: totalIncludingTax
caption: 含税销售额
aggregation: SUM
QM 负责把用户可用的查询字段组织起来。它说明这个问题可以按哪些维度筛选、按哪些字段汇总,以及"含税销售额"这个度量允许怎样聚合。
这里的关键不是把 QM 当成数据库表的别名,而是让 Agent 能回答:这次结果选择的是发票事实,还是订单事实;选择的是含税金额,还是不含税金额;日期过滤落在发票日期,还是订单日期。
第二层:TM 说明模型怎样落到底层事实
QM 继续向下引用 TM。当前链路可以简化为:
text
用户问题
→ QM: wwi_oltp_invoice_lines
→ TM: WwiOltpInvoiceLineModel
→ measure: totalIncludingTax
→ SUM(total_including_tax)
→ Sales.InvoiceLines.ExtendedPrice
TM 负责描述表级事实、字段、维度、度量和必要的物理映射。WWI 这条链中,TM 的 viewSql 将发票明细里的 ExtendedPrice 映射为 total_including_tax,并声明物理来源是 Sales.InvoiceLines 的 ExtendedPrice。
因此,解释结果时不能只说"查了发票表"。至少要说清楚:使用了哪个事实模型、哪个度量、经过了什么字段映射,以及最终的聚合方式是什么。
第三层:DSL 说明这一次具体查了什么
模型定义只能说明"能查什么",不能代替一次查询的具体条件。一次实际的 DSL 还会记录本次问题中的列、时间范围、分组和排序,例如:
json
{
"columns": [
{"field": "customer", "agg": "GROUP"},
{"field": "totalIncludingTax", "agg": "SUM"}
],
"slices": [
{"field": "invoiceDate", "op": "BETWEEN", "values": ["2016-01-01", "2016-06-30"]}
]
}
解释器会把这部分整理成可读信息:按客户分组,统计 2016 年上半年的发票含税销售额,并按用户要求排序。这样用户看到的不是一条脱离上下文的 SQL,而是从业务问题到结构化查询的中间证据。
权限和预聚合也属于结果生成路径
查询解释不能只展示用户主动输入的筛选条件。模型权限可能自动加入条件,而且必须和用户的筛选区分开。
例如,在一个带有区域权限的查询中:
text
用户条件:invoiceDate 在 2016-01-01 到 2016-06-30
模型权限:customer.salesTerritory = Southeast
前者来自 USER_SLICE,后者来自 MODEL_PERMISSION。两者都影响最终结果,但来源不同。解释结果应明确告诉用户,哪些条件是他提出的,哪些条件是系统根据可信身份注入的;不能把权限条件伪装成用户筛选,也不能让模型根据自然语言猜测用户身份。
如果模型配置了预聚合,解释结果还需要说明本次重新编译选择了原始表、预聚合直读、预聚合汇总还是混合路径。这属于查询规划证据,不等于把所有底层物理细节都暴露给业务用户,但对开发和审计很有价值。
RECOMPILED 不等于历史执行轨迹
这是当前能力最需要说清楚的边界。
RECOMPILED 会在当前模型版本、当前请求和当前授权上下文下重新编译查询。它可以返回:
- 规范化后的列、分组、排序和筛选条件。
- 用户条件与模型权限条件的来源。
- QM、TM、物理来源和度量公式。
- 预聚合路由信息。
- 在允许返回 SQL 时的生成 SQL;敏感参数会做脱敏处理。
但它不是历史查询的真实执行录像。当前版本暂不支持真实的 EXECUTED_TRACE,执行轨迹会标记为未评估。也就是说,如果模型定义、权限配置或数据已经变化,重新编译得到的是"现在按同样请求会怎样生成",不是"当时数据库实际执行的每一个细节"。
这两个概念不能混用:
text
RECOMPILED 当前状态下重新推导查询路径
EXECUTED_TRACE 历史请求实际执行过的路径和运行证据
前者足以帮助定位指标映射、DSL 条件、权限注入和 SQL 生成问题;后者还需要查询日志、执行 ID、模型版本、运行时参数和结果快照等持久化能力。
为什么要按需调用解释工具
普通业务用户问"上个月哪个客户销售额最高",首先需要的是答案和可继续筛选的结果。每次都返回 QM、TM、SQL,会增加阅读成本,也会把内部实现细节暴露在不必要的场景里。
但当用户追问"为什么是这个数"时,系统必须有一条受控的证据路径。按需解释的边界比较清楚:
text
普通问题 → query execute → 返回业务结果
解释性追问 → dataset.explain_query → 返回生成路径和解释证据
它不会修改现有 query execute、models describe 等工具协议,也不把解释工具变成一个可以任意执行 SQL 的入口。解释工具的价值,是把已经允许的语义查询讲清楚,而不是绕过模型、权限和查询引擎。
这一步之后,语义层还要连接哪些入口
当前这篇文章解决的是"AI 给出的结果如何解释"。下一步更值得验证的是:同一个 QM 能不能同时服务业务系统表格、BI 和 AI 问数。
理想的用户路径应该是:
text
AI 问数得到客户销售额
→ 打开同一 QM 的可筛选业务表格
→ 继续增加筛选、分页和排序
→ 点击客户或单据跳回业务系统详情
→ BI 使用同一指标和同一权限边界做趋势分析
这里的重点不是让三个入口长得一样,而是让它们共享事实、指标、维度、权限和解释规则。下一篇将从这个交付闭环开始,验证业务系统查询表格、BI 和 AI 问数共用语义层时,哪些内容可以复用,哪些仍然需要入口侧处理。