Skip to content

生产级 AI 查询需要 audit 和 provenance,而不只是一个答案

AI 问数最容易展示的是最终答案。

用户问:

text
上季度销售额同比增长多少?

系统回答:

text
上季度销售额同比增长 12.4%。

这个体验很直接,也很符合很多人对 AI 数据助手的期待。但在企业系统里,一个答案本身远远不够。

真正需要追问的是:

  • 这个答案来自哪个数据模型;
  • 用了哪些字段和指标;
  • 时间范围怎么解释;
  • 当前用户是谁;
  • 应用了哪些权限规则;
  • 查询了哪些公司或组织范围;
  • 底层执行了什么查询;
  • 返回了多少数据;
  • 是否发生过澄清或拒绝;
  • 用户如何复核这个结果。

如果这些问题回答不上来,AI 问数就很难进入生产系统。

生产级 AI 查询需要 audit 和 provenance。audit 关注谁在什么时候做了什么;provenance 关注结果从哪些数据、模型、转换和执行步骤产生。两者共同决定一个 AI 查询结果是否可追踪、可复核、可解释。

最终答案不是证据

AI 生成的自然语言答案不等于查询证据。

答案是面向用户阅读的摘要。它可能包含指标、趋势、解释和建议。但它通常不是完整事实链。

例如一句“销售额同比增长 12.4%”,至少依赖以下隐含条件:

  • 销售额的指标定义;
  • 当前季度和去年同期的时间边界;
  • 是否按订单确认日期、发票日期或收款日期统计;
  • 是否排除取消、草稿或内部交易;
  • 是否按当前公司范围过滤;
  • 是否包含税额、折扣和币种换算;
  • 当前用户是否有权查看该指标;
  • 查询结果是否经过聚合、过滤或截断。

如果系统只保存最终答案,事后几乎无法判断这个数字是否可信。

这在企业数据场景里是不可接受的。一个数据结论一旦用于经营复盘、财务分析、库存决策、客户风险评估或管理汇报,就必须能追溯来源。

AI 可以生成摘要,但摘要必须建立在可复核的查询证据之上。

audit 和 provenance 是两件事

audit 和 provenance 经常被混在一起,但它们解决的问题不同。

audit 更像操作日志。它回答:

  • 谁发起了请求;
  • 什么时间发起;
  • 从哪个客户端或应用发起;
  • 使用了哪个租户、公司和用户上下文;
  • 调用了哪个工具;
  • 请求是否成功、澄清、拒绝或失败;
  • 是否导出、下载或触发后续动作。

provenance 更像数据血缘和执行证据。它回答:

  • 结果来自哪个查询模型;
  • 使用了哪些字段、维度和指标;
  • 查询请求的结构化载荷是什么;
  • 引擎注入了哪些权限过滤;
  • 查询编译成了什么底层执行计划或 SQL;
  • 查询运行在哪个数据源和模型版本上;
  • 返回了多少行、哪些聚合层级、是否分页或截断;
  • 最终答案引用了哪些结果片段。

audit 让系统知道“这个人做过什么”。provenance 让系统知道“这个结果怎么来的”。

生产级 AI 查询需要两者同时存在。只有 audit,没有 provenance,事后只能知道用户问过问题,却不知道答案是否正确。只有 provenance,没有 audit,则无法把查询行为绑定到用户、权限和合规责任。

应该记录哪些审计字段

一个务实的 AI 查询审计记录,不需要一开始就做成复杂平台,但至少应该覆盖关键边界。

推荐记录:

  • requestId:一次 AI 查询的唯一标识;
  • conversationId:所属对话或任务链路;
  • timestamp:请求时间;
  • tenantId / databaseId:租户或数据源边界;
  • effectiveUser:实际授权用户;
  • client:来源客户端,例如 Web、MCP client、IDE 或内部系统;
  • toolName:调用的工具,例如 list_modelsdescribe_modelquery_model
  • queryModel:使用的查询模型;
  • status:成功、澄清、拒绝、无效请求、系统错误;
  • reasonCode:拒绝、澄清或失败原因;
  • companyContext:多公司或组织上下文;
  • fieldsRequested:请求字段、维度、指标;
  • rowCount:返回行数或聚合结果规模;
  • resultPolicy:是否分页、截断、脱敏、只返回聚合;
  • durationMs:执行耗时;
  • errorClass:异常类型,避免把敏感异常细节直接暴露给终端用户。

这些字段的目标不是堆日志,而是让安全、运维、数据平台和业务团队能复盘关键行为。

例如,当某个用户频繁请求成本、薪资或跨公司数据但被拒绝时,审计系统应该能看到。反过来,如果大量合法问题都被拒绝,也说明查询模型或权限映射可能需要调整。

provenance 应该记录到什么粒度

provenance 的粒度需要控制。

记录太少,无法复核;记录太多,可能泄露敏感信息,也会增加存储和合规负担。

比较稳妥的粒度是记录“足够复核查询过程”的结构化证据:

  • 原始用户问题;
  • 规范化后的查询意图;
  • 选定的查询模型及模型版本;
  • 查询模型中被引用的字段、维度、指标;
  • 结构化查询载荷,例如 JSON DSL;
  • 引擎注入的权限切片摘要;
  • 底层 SQL 或执行计划的安全版本;
  • 数据源标识和执行时间;
  • 返回结果摘要,例如行数、列名、聚合层级;
  • 最终回答引用了哪些结果集。

这里有一个重要细节:底层 SQL 不一定适合原样展示给所有用户。

在某些系统中,SQL 可能包含物理表名、内部字段、租户标识、权限条件或实现细节。它适合进入安全审计日志,但不一定适合展示给业务用户。对终端用户可以展示更高层的查询证据,例如查询模型、字段、筛选条件、时间范围、聚合口径和结果摘要。

也就是说,provenance 可以分层:

  • 用户可见证据:业务模型、字段、指标、过滤条件、结果摘要;
  • 管理员证据:结构化 DSL、权限切片、执行状态;
  • 工程证据:底层 SQL、数据源、异常栈、性能信息。

不同角色看到不同层级,才更符合企业系统的安全边界。

澄清和拒绝也要进入审计

很多系统只记录成功查询。

这不够。

澄清和拒绝同样是重要事件。

当系统因为“销售额口径不明确”而澄清时,这说明当前问题存在语义分歧。记录这些澄清,可以帮助团队改进默认指标、字段描述和查询模型设计。

当系统因为“用户无权访问成本字段”而拒绝时,这说明一次敏感访问被阻止。记录这些拒绝,可以帮助安全团队发现异常访问模式,也能证明系统没有执行越权查询。

一个拒绝事件至少应该记录:

  • 用户原始问题;
  • 触发拒绝的模型或字段;
  • 拒绝原因代码;
  • 当前用户和权限上下文;
  • 是否向用户提供了替代查询建议;
  • 是否有后续用户确认或改问。

注意,拒绝日志也要控制敏感信息。日志里可以记录字段标识和原因代码,但终端用户不一定需要看到完整权限规则。

结果可复核,不等于结果保证正确

audit 和 provenance 的目标不是给 AI 结果盖章。

它们不能保证模型的自然语言解释一定正确,也不能保证用户一定理解结果。它们解决的是另一个问题:当结果重要时,系统必须能提供复核路径。

可复核意味着:

  • 能看到问题如何被映射为查询;
  • 能看到查询用了哪些模型和字段;
  • 能看到权限边界是否生效;
  • 能看到底层执行是否成功;
  • 能看到结果范围和数据规模;
  • 能在必要时重放或对照查询。

这和传统 BI、报表、数据仓库的原则是一致的。区别在于 AI 问数多了一层自然语言理解和工具调用,所以更需要记录中间过程。

如果 AI 只返回一个漂亮段落,用户很难判断它是基于真实数据、缓存结果、错误查询,还是模型自己的推断。

生产系统应该避免这种黑箱。

audit / provenance 应该在执行层,而不是只在提示词里

有些团队会让模型在回答末尾附上“数据来源”和“查询说明”。

这有一定帮助,但不应该作为唯一证据。

原因很简单:模型生成的说明仍然是模型输出。它可能遗漏字段、改写条件、简化过程,甚至把没有执行过的查询描述成已经执行。

真正的 audit 和 provenance 应该由执行层记录:

  • 工具层知道当前用户和调用上下文;
  • 查询引擎知道结构化查询载荷;
  • 权限模块知道注入了哪些过滤条件;
  • 编译器知道生成了什么 SQL;
  • 执行器知道返回了多少行和耗时;
  • 答案生成器知道引用了哪个结果集。

这些信息来自系统事实,而不是模型自述。

模型可以把证据摘要解释给用户,但证据本身应该由工具和引擎产生。

对 MCP 工具设计的影响

如果 AI 数据访问通过 MCP 暴露,工具返回结构应该为审计和证据预留空间。

一个查询工具不应该只返回 rows 或一段自然语言。它更应该返回结构化元信息,例如:

json
{
  "status": "ok",
  "requestId": "qry_20260530_001",
  "model": "sales_order_analysis",
  "fields": ["orderDate", "customerRegion", "confirmedSalesAmount"],
  "filters": [
    { "field": "orderDate", "op": "between", "value": ["2026-01-01", "2026-03-31"] }
  ],
  "policy": {
    "rowLimit": 1000,
    "masked": false,
    "truncated": false
  },
  "evidence": {
    "queryDslId": "dsl_abc123",
    "compiledQueryId": "sql_def456",
    "rowCount": 12,
    "durationMs": 238
  },
  "data": []
}

这只是示意,不是固定格式。关键是工具契约要把查询数据、执行状态和证据标识分开。

当状态是 rejected 时,也应该返回结构化原因:

json
{
  "status": "rejected",
  "requestId": "qry_20260530_002",
  "reasonCode": "FIELD_NOT_VISIBLE",
  "message": "当前用户无权访问请求字段。",
  "field": "grossMargin"
}

这样 AI client 可以停止继续尝试同一越权请求,审计系统也能记录发生过什么。

对 Odoo / ERP 场景的具体含义

在 Odoo 这样的 ERP 系统里,audit 和 provenance 不是附加功能。

Odoo 查询结果通常受到多层因素影响:

  • effective Odoo user;
  • user groups;
  • ir.model.access
  • ir.rule
  • multi-company context;
  • 字段级 group 可见性;
  • 模块安装状态;
  • 二开字段和自定义业务规则。

如果 AI 查询返回一个销售、库存或财务结论,系统应该能说明它是在什么 Odoo 用户和公司上下文下执行的,使用了哪个查询模型,是否注入了 record rules,是否访问了受限字段,返回结果是否被裁剪。

否则,用户看到的是一个答案,系统却无法证明它没有绕过 Odoo 原有权限。

对实施团队来说,查询证据还有另一个价值:排查问题。

当业务用户说“AI 算出来的销售额和 Odoo 报表不一致”时,团队需要知道:

  • AI 用的是哪个日期字段;
  • 是否排除了取消订单;
  • 是否按当前公司过滤;
  • 是否包含税额;
  • 是否使用了同一个用户权限;
  • 是否查询了同一个模型和字段;
  • 是否有分页、截断或缓存。

没有 provenance,这类问题只能靠猜。

日志本身也要有边界

记录 audit 和 provenance 不等于无限制记录所有数据。

审计日志本身可能包含敏感信息。设计时需要考虑:

  • 不在普通日志里记录完整结果集;
  • 对敏感字段值做脱敏或哈希;
  • 区分业务用户可见证据和管理员审计证据;
  • 控制日志保留周期;
  • 对审计日志访问做权限控制;
  • 避免在错误信息里泄露表名、字段名或规则细节;
  • 对导出、下载和大范围明细查询做单独记录。

换句话说,audit 和 provenance 也需要治理。它们不是越多越好,而是要在可复核、安全和成本之间取得平衡。

结论

企业 AI 查询不能只交付一个答案。

一个生产级系统至少要能说明:

  • 用户是谁;
  • 使用了哪个业务查询模型;
  • 问题如何被转成结构化查询;
  • 查询引用了哪些字段和指标;
  • 权限规则如何生效;
  • 底层执行是否成功;
  • 返回了多少数据;
  • 是否发生澄清、拒绝、截断或脱敏;
  • 结果如何复核。

audit 让查询行为可追踪。provenance 让查询结果可复核。两者共同决定 AI 问数能否从 demo 进入企业系统。

Foggy Data MCP / Foggy Odoo Bridge 的技术方向也是把查询执行放在受治理的模型和工具边界内:AI 可以发起查询意图,但字段校验、权限注入、查询编译、执行证据和拒绝记录应该由系统负责,而不是依赖模型在回答里自我说明。