返回正文目录

AI 查错了,先别急着改提示词

副标题:一个错误结果,可能来自用户表达、指标口径、权限、查询执行或底层数据

前文:业务系统有表格,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,也不保存由凭证派生的指纹;查询参数和业务数据同样应该遵循现有的脱敏规则。

如果系统没有保存当时的模型版本、权限摘要或结果引用,也应该明确记录“缺少什么”,不能在事后重新查询一次,就把它描述成当时的真实结果。

按层排查,而不是先改提示词

1. 用户到底问了什么

先看原始问法和上下文,而不是只看模型最后整理出的结构化问题。

用户:昨天的销售额是多少?

这句话至少缺少几个关键信息:

如果页面上下文已经确认了发票口径,可以直接回答并显式说明;如果上下文不足,就应该澄清,而不是让模型随机选择一个字段。

2. 指标和 QM 是否选择正确

在 WWI 发票明细模型中,一个可解释的链路可以写成:

QM: wwi_oltp_invoice_lines
  → measure: totalIncludingTax
  → date: invoiceDate
  → aggregation: SUM

如果用户实际要的是订单事实,却被映射到这个发票 QM,问题在指标选择;如果用户已经明确说“开票含税销售额”,但模型却选择了订单 QM,问题在自然语言路由或模型边界。

这类问题不能通过查看最终数字来判断,应该检查模型定义。dataset.explain_query 的 DEFINITION 模式适合回答:

这个字段是什么?
它属于哪个 QM / TM?
默认日期和聚合方式是什么?
物理来源有没有明确声明?

DEFINITION 读取当前定义,不编译查询,也不返回业务结果。

3. DSL 是否改变了原问题的边界

指标选择正确,不代表这一次查询就正确。还需要检查本次 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 证据。

4. 权限是否改变了结果范围

权限问题经常被误认为“AI 少算了”。同一个 DSL,在不同用户身份下,本来就可能得到不同的数据范围。

例如:

用户条件:invoiceDate 在 2016-01-01 到 2016-06-01
模型权限:customer.salesTerritory = Southeast

解释结果应该把两者标记为不同来源:

USER_SLICE
MODEL_PERMISSION

用户输入的筛选条件可以由用户修改,模型权限条件则由可信授权上下文决定。不能让用户在自然语言里说“我是管理员”,也不能因为一次回答结果看起来少,就直接移除权限条件。

如果错误只在某个用户或某个组织出现,排查顺序应该优先检查身份、组织、授权和权限模型,而不是先调模型提示词。

5. 物理来源、状态和刷新时间是否一致

在当前 WWI 发票模型里,totalIncludingTax 的语义链路可以继续向下解释为:

QM.totalIncludingTax
  → TM.totalIncludingTax
  → SUM(total_including_tax)
  → Sales.InvoiceLines.ExtendedPrice

其中,QM 到 TM 的关系来自模型结构,TM 的聚合来自度量定义,物理来源则应由 viewSql 和 sourceRefs 等声明共同说明。

如果查询使用了正确的字段,但来源数据尚未刷新,或者取消、退货、贷项的状态条件没有对齐,那么即使生成 SQL 与当前模型定义一致,也不能保证业务结果符合预期。

因此还要记录:

6. JOIN、聚合和执行路径是否造成重复

明细加总和汇总不一致时,常见原因不是 LLM,而是粒度发生了变化:

这时需要检查 TM / QM 的关联定义、DSL 的分组和过滤,以及查询引擎最终采用的规划。

如果只是使用 RECOMPILED,得到的是当前状态下的重新编译结果。它可以帮助定位当前模型会如何生成 SQL,但不能证明刚才那次请求当时实际走过了哪条 JDBC 或缓存路径。

7. 结果是否在展示层被改变

有时接口结果是正确的,用户看到的页面却不一样:

所以“生成 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,还是先查权限和指标口径?