副标题:一个错误结果,可能来自用户表达、指标口径、权限、查询执行或底层数据
前文:业务系统有表格,BI 有看板,AI 问数还需要做什么?
AI 问数真正进入业务以后,迟早会遇到一句话:
这个结果不对。
很多团队的第一反应是修改提示词,或者再给模型补一段“销售额必须使用某某字段”的说明。
但一个结果“不对”,不一定是模型不会理解。它可能是用户想要订单金额,系统返回了开票金额;可能是权限条件生效后,用户只看到了自己有权访问的客户;也可能是数据还没有刷新,或者明细和汇总使用了不同的粒度。
如果没有先复现原问题,就直接改提示词,下一次即使数字变了,也不知道究竟修复了什么,更不知道会不会影响其他用户。
所以,AI 查错后的第一步不是“继续调提示词”,而是把错误结果拆成一条可以核对的生成链路:
用户表达与上下文
→ 指标和业务口径
→ QM / TM 模型
→ DSL 查询条件
→ 权限与数据范围
→ 物理来源与查询执行
→ 结果汇总与页面展示
这篇文章不试图证明 AI 永远不会答错,而是解决另一个更实际的问题:答错以后,团队能不能知道应该改哪一层。
一次合格的错误处理,至少应该留下四项结果:
这和“把提示词改得更长”不是一回事。提示词可以帮助模型理解用户表达,但不能替代指标决策、权限执行、数据质量和查询引擎。
可以先用下面的表格做第一轮分类:
| 表面现象 | 先检查什么 | 更可能由谁处理 |
|---|---|---|
| 用户说“销售额”,结果和预期不一样 | 订单、发票、回款等指标口径 | 业务负责人和指标建模人员 |
| 同一问题换一种问法,结果发生变化 | 用户表达、别名和上下文映射 | Agent 路由和语义描述 |
| 同一个问题,不同用户结果不同 | Authorization、组织和数据范围 | 权限与宿主系统 |
| 明细加总和汇总金额对不上 | 数据粒度、JOIN、重复聚合 | TM / QM、DSL 和数据实施 |
| 生成 SQL 与当前模型定义一致,但数据仍然不符合预期 | 来源表、状态、刷新时间和快照 | 数据实施和业务负责人 |
| 重新查询后金额改变 | 模型版本、数据变化或时间窗口 | 版本、数据和查询记录 |
| 接口结果正确,页面数字不一样 | 舍入、分页、前端汇总和格式化 | 前端组件和展示层 |
这张表的意义是提醒我们:同一句“AI 答错了”,可能对应完全不同的修复动作。
之前的文章已经讨论过,客户说“销售额”时,技术上可能对应不同的唯一指标:
orderTotalIncludingTax
invoiceTotalIncludingTax
在当前 WWI 样例基线中,统计 2016-01-01(含)至 2016-06-01(不含)的数据,订单和发票本身就不是同一个数字:
| 事实 | 含税金额 | 明细行数 | 数量 |
|---|---|---|---|
| 订单 | 26,848,038.92 | 29,902 | 1,291,028 |
| 发票 | 25,971,029.11 | 29,458 | 1,241,304 |
如果用户期待的是订单含税金额,而 AI 根据当前页面默认口径回答了开票含税金额,那么这个结果可能在计算上完全正确,在业务意图上却是错的。
这时应该修复的是指标映射或上下文,而不是简单告诉模型“销售额要算得更准”。需要继续确认:
如果两个口径都合法,就应该并列展示并标明名称,而不是让模型把两个数字相加,创造一个没有业务定义的新“销售额”。
错误处理不能只保存一句“用户觉得不对”。至少应该把当时能获得的上下文冻结下来:
{
"question": "昨天的销售额是多少?",
"actorContext": {
"user": "<opaque-user>",
"organization": "<opaque-organization>",
"page": "invoice-list",
"selectedFilters": ["<redacted>"]
},
"model": "wwi_oltp_invoice_lines",
"payload": "<normalized-query-payload>",
"permissionContextRef": "<controlled-audit-reference>",
"permissionScopeSummary": "<redacted-policy-scope>",
"modelRevision": "<revision>",
"dataCutoff": "<observed-cutoff>",
"resultReference": "<result-reference>"
}
这里保存的是复现所需的引用和摘要,不是把 Cookie、Authorization 或客户明细直接写进日志。权限部分只记录受控的内部审计引用或脱敏后的权限决策摘要,不保存 Token、Authorization,也不保存由凭证派生的指纹;查询参数和业务数据同样应该遵循现有的脱敏规则。
如果系统没有保存当时的模型版本、权限摘要或结果引用,也应该明确记录“缺少什么”,不能在事后重新查询一次,就把它描述成当时的真实结果。
先看原始问法和上下文,而不是只看模型最后整理出的结构化问题。
用户:昨天的销售额是多少?
这句话至少缺少几个关键信息:
如果页面上下文已经确认了发票口径,可以直接回答并显式说明;如果上下文不足,就应该澄清,而不是让模型随机选择一个字段。
在 WWI 发票明细模型中,一个可解释的链路可以写成:
QM: wwi_oltp_invoice_lines
→ measure: totalIncludingTax
→ date: invoiceDate
→ aggregation: SUM
如果用户实际要的是订单事实,却被映射到这个发票 QM,问题在指标选择;如果用户已经明确说“开票含税销售额”,但模型却选择了订单 QM,问题在自然语言路由或模型边界。
这类问题不能通过查看最终数字来判断,应该检查模型定义。dataset.explain_query 的 DEFINITION 模式适合回答:
这个字段是什么?
它属于哪个 QM / TM?
默认日期和聚合方式是什么?
物理来源有没有明确声明?
DEFINITION 读取当前定义,不编译查询,也不返回业务结果。
指标选择正确,不代表这一次查询就正确。还需要检查本次 DSL 的具体条件:
{
"columns": [
"invoiceDate$calendarMonthLabel",
"sum(totalIncludingTax) as totalIncludingTax"
],
"groupBy": [
"invoiceDate$calendarMonthLabel"
],
"slice": [
{
"field": "invoiceDate$id",
"op": "[)",
"value": ["2016-01-01", "2016-06-01"]
}
]
}
重点核对:
如果用户追问“这次查询现在会生成什么 SQL”,可以使用 RECOMPILED。它会在当前模型版本、当前请求和当前授权上下文下重新编译,并返回规范化条件、权限来源、查询规划和 SQL 证据。
权限问题经常被误认为“AI 少算了”。同一个 DSL,在不同用户身份下,本来就可能得到不同的数据范围。
例如:
用户条件:invoiceDate 在 2016-01-01 到 2016-06-01
模型权限:customer.salesTerritory = Southeast
解释结果应该把两者标记为不同来源:
USER_SLICE
MODEL_PERMISSION
用户输入的筛选条件可以由用户修改,模型权限条件则由可信授权上下文决定。不能让用户在自然语言里说“我是管理员”,也不能因为一次回答结果看起来少,就直接移除权限条件。
如果错误只在某个用户或某个组织出现,排查顺序应该优先检查身份、组织、授权和权限模型,而不是先调模型提示词。
在当前 WWI 发票模型里,totalIncludingTax 的语义链路可以继续向下解释为:
QM.totalIncludingTax
→ TM.totalIncludingTax
→ SUM(total_including_tax)
→ Sales.InvoiceLines.ExtendedPrice
其中,QM 到 TM 的关系来自模型结构,TM 的聚合来自度量定义,物理来源则应由 viewSql 和 sourceRefs 等声明共同说明。
如果查询使用了正确的字段,但来源数据尚未刷新,或者取消、退货、贷项的状态条件没有对齐,那么即使生成 SQL 与当前模型定义一致,也不能保证业务结果符合预期。
因此还要记录:
明细加总和汇总不一致时,常见原因不是 LLM,而是粒度发生了变化:
这时需要检查 TM / QM 的关联定义、DSL 的分组和过滤,以及查询引擎最终采用的规划。
如果只是使用 RECOMPILED,得到的是当前状态下的重新编译结果。它可以帮助定位当前模型会如何生成 SQL,但不能证明刚才那次请求当时实际走过了哪条 JDBC 或缓存路径。
有时接口结果是正确的,用户看到的页面却不一样:
所以“生成 SQL 与当前模型定义一致”只是查询链路的一部分。错误记录还应该保存页面筛选、请求参数、返回摘要和展示规则,避免把展示问题改成数据问题。
普通问数不需要每次都输出 QM、TM 和 SQL。用户问“上个月哪个客户销售额最高”时,首先需要的是结果;用户追问“为什么是这个数”时,才需要解释证据。
关于 QM、TM、过滤条件和 SQL 如何逐层解释,前文《当 AI 输出销售额时,如何让它解释这个数字是怎么算出来的?》已经做过完整说明。放到错误排查场景里,更重要的是明确调用边界:
可以把调用边界写成:
普通问题
→ query execute
→ 返回业务结果
解释性追问
→ dataset.explain_query
→ 返回生成路径和解释证据
需要复核当前请求时,可以通过 CLI 调用,也可以在 MCP 场景中调用同一个 dataset.explain_query:
foggy-runtime \
--base-url http://127.0.0.1:18066 \
--namespace wwi-oltp-sales \
query explain wwi_oltp_invoice_lines \
--payload <query-payload.json> \
--depth detailed \
--include-sql \
--include-physical-names
带 payload 后会进入 RECOMPILED 模式,按照当前模型 revision、当前请求和当前授权上下文重新推导查询路径。这个追加能力不需要修改已有的 query execute、models describe 工具协议,也不返回业务结果行,更不能成为绕过模型权限、namespace 隔离和查询限制的 SQL 入口。
排查时需要区分三类证据:
| 能力 | 可以说明什么 | 不能说明什么 |
|---|---|---|
DEFINITION |
当前字段、指标、QM → TM 与声明的物理来源 | 不编译本次查询,不返回业务结果 |
RECOMPILED |
当前 DSL、权限来源、查询规划和当前会生成的 SQL | 不能证明历史请求实际执行了这条路径 |
EXECUTED_TRACE |
历史请求当时使用的模型、权限、SQL、缓存或 JDBC 路径 | 当前版本暂不支持 |
因此,当前 RECOMPILED 响应会明确保留限制:
RECOMPILED_NOT_EXECUTED_TRACE
当用户问“刚才返回的结果为什么是这个数”时,如果系统没有保存当时的执行 trace,只能诚实地说:
我可以按当前模型和权限重新解释这条查询,
但不能证明它就是刚才那次请求实际执行过的全部过程。
解释工具提供的是当前定义和重新编译证据;历史运行轨迹则是另一种产品能力,二者不能混为一谈。
定位完成以后,再决定修复位置:
| 根因 | 正确的修复位置 | 不应该怎么做 |
|---|---|---|
| 用户表达不清 | 澄清规则、别名、上下文映射 | 让模型随机猜默认指标 |
| 指标口径错误 | 业务指标决策、QM / TM 定义 | 只在提示词里写一遍公式 |
| DSL 边界错误 | 查询模型和 DSL 生成 / 校验 | 直接拼接一条特殊 SQL |
| 权限范围错误 | 可信身份、组织和权限规则 | 允许用户用自然语言扩大范围 |
| 数据来源或刷新错误 | 数据实施、同步链路和状态规则 | 修改 AI 回答掩盖数据问题 |
| JOIN / 聚合错误 | TM、QM、查询规划和回归基线 | 让模型在结果上“修正数字” |
| 页面展示错误 | 前端组件、分页和格式化逻辑 | 把 UI 差异归因到底层数据 |
提示词只负责帮助模型理解用户表达和选择调用路径。它不应该承担权限执行、金额修正、数据清洗和历史结果伪造。
一次修复不能只重新问原问题。建议至少补齐几类反例:
1. 用户明确问订单含税销售额时,是否不会落到发票指标?
2. 用户明确问开票含税销售额时,日期是否使用 invoiceDate?
3. 两个不同授权上下文是否仍然只能看到各自范围?
4. 诊断记录是否只保存权限决策摘要或受控审计引用,而不保存 Authorization、Token 及其派生指纹?
5. 左闭右开的日期范围是否不会多算边界日期?
6. 客户、商品和明细三种粒度回加后是否保持一致?
7. 数据未刷新或状态不完整时,回答是否提示统计截止时间?
8. 用户追问“这个数怎么算”时,是否进入 explain,而不是修改普通查询协议?
如果这次问题来自 Agent 的周期任务,还应该保存任务定义的版本、统计窗口、指标口径、接收人,以及受控的权限审计引用或权限决策摘要。任务不能每天重新猜一遍“昨天的销售额”是什么意思,也不能把调用凭证写进任务记录。
AI 查错以后,最危险的动作不是承认系统有问题,而是在不知道问题在哪一层时就开始改提示词。
一个可交付的 AI 分析系统,应该能把“结果不对”转化为可复现、可归因、可修复和可回归的问题:
先冻结上下文
→ 再复现原查询
→ 区分口径、权限、数据、模型、执行和展示
→ 把修复放回正确责任层
→ 用反例和固定问题回归
语义层的价值不只是让 AI 找到字段,还在于让团队知道这个结果为什么被允许这样计算;查询解释工具的价值,也不只是返回一段 SQL,而是把 QM、TM、权限、规划和物理来源组织成可以核对的证据。
但证据有边界:当前 RECOMPILED 可以说明“现在会如何生成”,没有 EXECUTED_TRACE 时,就不能宣称“还原了刚才真实执行的全部过程”。
你们遇到 AI 结果不对时,第一反应通常是改提示词、查 SQL,还是先查权限和指标口径?