生产级 AI 查询需要 audit 和 provenance,而不只是一个答案
AI 问数最容易展示的是最终答案。
用户问:
上季度销售额同比增长多少?系统回答:
上季度销售额同比增长 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_models、describe_model、query_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 或一段自然语言。它更应该返回结构化元信息,例如:
{
"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 时,也应该返回结构化原因:
{
"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 可以发起查询意图,但字段校验、权限注入、查询编译、执行证据和拒绝记录应该由系统负责,而不是依赖模型在回答里自我说明。