你的聊天 UI 在每个响应下方都展示了一个“AI 生成”的小徽章。这很好。但现在,一个合作伙伴团队通过批处理作业调用了你的 /summarize 接口,将输出写入数据库,并将其渲染在面向客户的报告中。此时,你的徽章没有帮到任何人。
这正是 AI 披露一直以来容易被忽视的鸿沟。披露往往被设计为一种 UI 层面的决策,因此它止步于你自己前端的边缘。机器消费者什么也得不到,而它们恰恰是最需要这些信息的主体,因为它们无法通过观察响应来判断其是否来自模型。
自 2026 年 8 月 2 日起,《欧盟人工智能法案》第 50 条 使得许多团队的这一需求变得具体化。像 Anthropic 这样的模型提供商在模型级别标记其输出,但向人们明示其正在与 AI 交互的义务在于系统的部署者。如果你的 API 介于两者之间,那么你的调用者能否合规就全看你了。
以下是如何将披露信息写入契约(API 定义)而非仅停留在界面上的方法:返回什么、放在哪里、如何编写文档,以及如何进行测试以使其在下一次重构中幸存。Apifox 将这其中的设计、文档和测试环节融为一体。
响应中应该包含什么
包含三样东西,它们分别回答了三个不同的问题。
这是生成的吗? 一个布尔值,或者更好的是,一个枚举值。以 ai_generated: true 开头不错,但 generation: "synthetic" | "assisted" | "human" 更实用。因为“Claude 润色了人类的草稿”与“Claude 撰写了全文”有本质区别,且第 50 条的豁免条款对它们的处理方式也不同。
由什么生成的? 厂商和模型 ID。你的调用者可能拥有自己的模型策略,而切换模型的后备方案(fallback)会改变他们被允许对输出执行的操作。
我们实际知道什么? 来源(Provenance)状态(如果你进行过检查)。请将此与生成标志分开,因为“我们生成了此内容”是你掌握的事实,而“我们验证了 C2PA 清单”只是你所做的一项观察。
一个经得起推敲的数据结构:
{
"id": "sum_4f81a2",
"content": "The incident affected two regions for 41 minutes...",
"ai": {
"generation": "synthetic",
"vendor": "anthropic",
"model": "claude-opus-5",
"human_review": false,
"generated_at": "2026-08-11T09:14:22Z"
},
"provenance": {
"status": "unchecked",
"standard": null
}
}
两个值得坚持的细节。
human_review 存在的原因是《第 50(4) 条》的规定依赖于它。发布旨在向公众通报公共利益相关事项的 AI 生成文本必须进行披露,除非该文本经过了人工审查或具有编辑责任人员的编辑控制。如果你的平台记录了人工批准了草稿这一事实,那么该事实就应该包含在响应中,因为这决定了你的调用者是否需要打上标签。
provenance.status 拥有两个以上的状态。verified、absent、invalid 和 unchecked 都代表不同的含义,如果将它们折叠为一个布尔值,就会丢失这些有用的信息。验证服务的中断不应该与正常结果看起来完全一样。
header 还是 body?
针对不同的消费者,两者都需要。
body 承载真实数据。 它是被存储、记录、重放并传递给下游的数据。如果调用方只保留解析后的 JSON,那么披露信息就需要包含在其中。
header 在边缘层很有用。 代理、网关或日志记录层即使从不解析 body,也仍然可以根据 header 进行路由或记录。对于非 JSON 的响应(例如纯文本或二进制接口),header 也是放置披露信息的唯一合理位置。
HTTP/1.1 200 OK
Content-Type: application/json
X-AI-Generated: synthetic
X-AI-Model: anthropic/claude-opus-5
关于 header 有两条规则。首先,要在所有接口中保持一致,因为仅在某些路由上出现的 header 比完全没有更糟糕。其次,如果 body 与 header 不一致,切勿将 header 视为权威数据;选择其中一个作为权威源,在文档中进行说明,并通过测试来强制执行这一规范。
对于流式响应,请将披露信息放在第一个事件或响应 header 中。开始渲染第一个 token 的调用方不应该为了知道自己正在渲染什么而不得不等待 trailer(尾部数据)。如果您对 header 设计还不熟悉,what are HTTP headers 介绍了基本概念。
写入规范中
未包含在 OpenAPI 定义中的披露信息字段只是一种约定,而约定是会腐化的。将其定义为可复用的数据模型,以便每个基于 AI 的接口都使用相同的格式:
components:
schemas:
AiDisclosure:
type: object
required: [generation]
properties:
generation:
type: string
enum: [synthetic, assisted, human]
description: >
synthetic = produced by a model with no human authoring.
assisted = a human authored the content and a model edited,
translated, or summarised it.
human = no model involvement.
vendor:
type: string
example: anthropic
model:
type: string
example: claude-opus-5
human_review:
type: boolean
description: >
True when a person reviewed the output before it was returned
and an identifiable party holds editorial responsibility.
generated_at:
type: string
format: date-time
然后在各处引用它,并在任何可能包含模型输出的响应中将 ai 设为必填属性。必填非常重要:如果是可选字段,调用方就必须编写防御性代码,而大多数人并不会这么做。
两个连锁收益:您生成的文档现在可以向每个使用方解释该字段,而无需任何人编写 Wiki 页面;同时,规范校验会在该字段消失的那天及时捕获这一情况,这正是实际中会发生的失效模式。《如何校验 OpenAPI 规范》涵盖了校验方面的内容,而《在 CI 中使用 OpenAPI diff 阻止破坏性变更》则能捕获到有人悄悄将其设为可选字段的情况。
人们遗忘的路径
披露字段经常在没人注意到的路由上丢失。以下是需要明确检查的四个方面。
缓存的响应。 如果缓存层在附加披露信息之前就存储了 body,那么在 TTL(生存时间)到期前,它将一直提供未标记的输出。请缓存完整的响应,而不是仅缓存模型输出外加一个您重新构建的包装器。
错误和部分响应。 返回部分摘要的超时依然在返回模型输出。如果您的错误包装结构(envelope)具有不同的形状,它同样需要该字段。
批量和 Webhook 负载。 异步传递通常使用由不同代码构建的更精简的数据模型。这是该字段最容易缺失的地方。
降级路径(Fallback paths)。 当主模型失败且发生降级时,model 的值必须随之改变。在披露信息块中硬编码模型字符串迟早会出问题。
针对这四种情况的解决方案都是一样的:在模型输出进入响应对象的那一刻附加披露信息,而不是在您序列化正常路径的时候。
像对待保障一样测试它
披露字段是对调用方的承诺。未经测试的承诺仅仅是文档。
以下五个断言涵盖了大部分情况,而且它们只是普通的 API 测试。
1. 该字段存在于每个由 AI 支持的路由中。
const body = pm.response.json();
pm.test("response carries AI disclosure", function () {
pm.expect(body).to.have.property("ai");
pm.expect(body.ai.generation).to.be.oneOf(["synthetic", "assisted", "human"]);
});
2. header 与 body 一致。
pm.test("header and body agree", function () {
pm.expect(pm.response.headers.get("X-AI-Generated")).to.eql(body.ai.generation);
});
3. 报告的模型与您实际调用的模型一致。 这是捕获静默降级的关键。输出是否在上游被添加水印取决于模型 ID,这就是为什么 Claude 的 API 水印功能使固定模型版本成为一个合规性细节,而不仅仅是一个性能细节。
4. 缓存路径依然包含披露信息。 调用两次,断言来自缓存的第二次响应与第一次响应具有相同的披露信息。
5. 错误路径依然包含披露信息。 强制制造超时或下游故障,并断言包装结构依然携带该字段。
将这些内容组合到一个测试场景中,添加针对 OpenAPI 定义的数据模型校验,并在 CI 中通过 apifox-cli 运行它:
```bash apifox run --access-token "$APIFOXACCESSTOKEN" \ -t "$DISCLOSURESCENARIOID" -e "$APIFOXENVID" -r cli,html
当断言失败时,它会以非零状态码退出,因此若合并时删除了该字段,将会导致构建失败,而不是照常发布。完整的流水线配置请参见在 GitHub Actions 中自动执行 API 测试,常规断言模式请参见 API 断言。下载 Apifox 针对你自己的接口构建测试场景。
在调用方关注的地方进行文档化
针对两类受众,提供两个位置。
在参考文档中。 如果你编写得当,数据模型描述能起到关键作用。说明 assisted 在你的产品中代表什么,而不是进行抽象的定义。调用方在决定是否需要标签时,会通过阅读该句描述来做出合规性决定。
在一个简短的政策页面中。 用一个页面来说明哪些接口会返回模型输出、使用了哪些模型、是否包含人工审核以及审核的含义,以及你承诺与不承诺的事项。从参考文档中链接到该页面,并对其进行版本控制。
明确说明限制条件。如果你直接透传 Claude 的输出,文本中会带有你无法自行验证的嵌入水印,且 Anthropic 尚未开放检测手段。如实说明这一点,好过暗示自己拥有并不具备的验证能力。具体原因请参考如何检测 Claude 的水印。
交互式文档在此处比以往更有帮助,因为调用方可以在实时响应中看到披露字段,而不是仅信任表格数据。托管带有 try-it 控制台的交互式 API 文档可以满足这一配置。
FAQ
X-AI-Generated header 是一种标准吗? 不是。目前还没有针对 AI 披露的批准标准 header。选择一个名称,将其写入文档,保持一致,并将其视为契约的一部分。
披露信息应该放在 header 还是 body 中? 两者都应该。body 是被存储和传递的内容。header 则服务于代理、网关、日志和非 JSON 响应。如果两者出现不一致,请在文档中注明以哪一个为准。
我在法律上有义务这样做吗? 这取决于你的角色和内容。第 50 条规定的义务对提供商和部署者有不同的要求,且第 50(4) 条适用于深度伪造和符合公共利益的文本,并对人工编辑控制进行了免除。面向 API 开发者的《欧盟 AI 法案》第 50 条对此进行了详细拆解。法律层面的决策由你的法律顾问决定,而技术底座的搭建则由你负责。
我的服务商已经对其输出进行了水印处理。这还不够吗? 不够。对于文本而言,水印是一种机器可读的信号,你的调用方目前无法读取,而且它并不能免除你作为部署者自身的披露义务。它只是一个补充,而不是替代品。
流式响应该如何处理? 将披露信息放入响应 header 或第一个事件(event)中。调用方会在 token 到达时进行渲染,不应该让他们等到最后。
如何处理生成后经过人工编辑的内容? 这正是 assisted 和 human_review 的作用所在。第 50(4) 条对于在人工审核且承担编辑责任下的内容有豁免条款,因此准确记录这些信息比仅使用一个布尔值更有价值。
我应该对该字段进行版本控制吗? 它是你的响应数据模型的一部分,因此要像其他部分一样对其进行版本控制。添加枚举值是你的调用方需要获悉的变更,而 CI 中的规范差异会告知他们。
核心要点
AI 声明作为 UI 功能并不够用,但作为契约却很有效。在响应中添加一个必填字段,在 header 中对其进行镜像同步,在你的 OpenAPI 规范中定义一次,并在最容易遗漏它的缓存、错误、批量和回退路径上进行断言。
这大概只需要一下午的工作量,但它能将你营销中的宣传转化为调用方可以信赖的基础,以及你的测试可以强制执行的规范。
开发必备:API 全流程管理神器 Apifox
介绍完上文的内容,我想额外介绍一个对开发者同样重要的效率工具 —— Apifox。作为一个集 API 文档、调试、设计、测试、Mock、自动化测试于一体的工具,Apifox 是目前提升研发效率的首选。
如果你正在开发项目,不妨试试其极其友好的界面设计,它完全兼容 Postman 和 Swagger 数据格式,导入数据非常方便,,即使是新手也能很快上手,点击这里即可注册使用。

值得一提的是,除了个人和常规团队使用,针对有高安全合规要求、或需要在内网环境协作的企业,Apifox 还提供了深度定制的私有化部署方案。
获取专属报价与部署方案
详细的私有化部署系统架构与安全白皮书
针对您公司规模的专属报价单
免费的 1v1 专属产品演示 (Demo) 机会