日期:2026-07-24
本文为工程实践复盘,客户信息已脱敏;所述数据均为示意值,不对应任何具体合同或审计数字。
我们服务的一家内容平台,开始把大模型批量生成的外发文案直接下发给商家和渠道。很快出了岔子:一篇 AI 生成的营销文案被竞品截图转发、改头换面当成自家原创,平台想维权却拿不出"这是我家模型生成的"证据;另有一次,一段外发内容被断章取义传播,引发舆情,但谁也说不清是哪次调用、哪个模型、哪个部门生成的。我们还发现,有些运营同学把模型生成稿当作"纯人工创作"对外署名,出了合规问题追责时链路全断。我们意识到:AI 生成内容一旦离开我们的系统,就脱离了掌控,出事时"说不清出处"本身就是风险。于是我们在网关输出侧加了模型水印与输出溯源,让每一段外发内容都可被证明来源、可被追溯责任。
我们在网关的输出侧做了两层能力。一层是"不可感知水印":在文本生成时通过带偏置的词表采样,把可验证的隐式水印嵌入文本,正常阅读无感,但用配套检测器能在高置信度下判定"是否由本平台模型生成",且对轻度改写有一定鲁棒性。另一层是"显式溯源标识":每次外发生成都分配一个溯源 ID,和网关注册的模型、调用方、时间戳、部门绑定,写入审计日志;外发内容可附带结构化溯源元数据(如 HTML 注释或响应头)。我们还提供了一个校验入口,外部或内部拿到内容后可回查该溯源 ID 对应的生成记录,形成"可验证"的闭环。水印与溯源都在网关统一实施,业务侧无感知,不需要每个应用自己接一套。
第一个难点是水印的"不可感知"与"鲁棒"的平衡。水印太强会伤文风、太弱一改就掉。我们用基于绿名单(green-list)的采样:对每个 token,按上一个 token 哈希出一个候选子集,在该子集上轻微提升采样概率,整体分布几乎不变,但统计特征可被检测器识别;对 paraphrase 这类轻度改写,靠检测长文本的统计累积信号来保持判定力。第二个难点是溯源 ID 与留痕。网关在输出侧统一注入溯源 ID,并把"模型+调用方+时间+部门"写入审计日志,保证外发内容能回链到具体调用,而不是只留一句"系统生成"。第三个难点是性能与误报。水印采样对齐推理服务化层的解码路径,开销几乎可忽略;检测器设置置信阈值,宁可少报也不误伤正常人工内容。第四个难点是外发可控:溯源元数据默认随响应下发,但对外暴露字段做了最小化,避免泄露内部模型细节与调用拓扑。
案例片段(已脱敏):文本水印采样的简化逻辑(伪代码):
python def watermark_sample(logits, prev_token): green = hash_to_subset(prev_token, k=len(logits)//5) # 绿名单子集 logits[green] += bias # 轻微偏置 return sample(logits)
以脱敏示意口径看:外发文本的水印覆盖率(成功嵌入且可被检测)达到约 99%,对轻度改写(同义替换、语序调整)的仍可识别率约 85%。溯源 ID 的外发留痕率达到 100%,即每一段离开网关的 AI 内容都能回查到"哪个模型、哪个部门、什么时间"。上线后,因"内容归属争议"引发的维权与舆情核查平均耗时,从需要人工翻日志约半天,缩短到调用校验入口约 1 分钟。水印带来的文风质量主观评分下降在约 2% 以内,用户侧基本无感;误报率(把人工内容判为 AI)控制在约 0.1%。所有数值均为示意值,不对具体合同负责。
水印要先于外发——内容一旦离开系统就不可控,溯源是可问责的前提,别等出了舆情才想起来"谁生成的"。水印设计要兼顾"不可感知"与"鲁棒",用统计特征而非明文标记,对轻度改写保持判定力,但绝不以牺牲文风为代价。溯源 ID 必须和审计日志绑定,外发内容能回链到具体模型与部门,否则水印只是装饰。检测器要设置信阈值,宁可漏报不可误伤正常人工内容,误报比漏报更伤信任。外发元数据做最小化暴露,溯源但不泄露内部实现与调用拓扑。
灰度上我们采取“先内后外”:先对内部运营生成的内容全量打水印与溯源,跑一周验证检测器稳定、误报可接受,再对外部下发内容开启。我们还处理了“人工二次编辑的 AI 内容”这一灰色地带——运营在 AI 稿基础上大改后重新提交,网关会重新打标并刷新溯源 ID,保证外发版本对应的还是最新一次生成记录,而不是停留在初稿。对内我们把它定位成“信任信号”而非“监控枷锁”:对外展示“本内容由 AI 生成”的标识,反而提升了平台透明度与用户信任,舆情风险不升反降。值得强调,水印与溯源都只在网关输出侧统一实施,业务应用零改造;当新增模型接入时,只需在网关注册即自动获得水印与溯源能力,能力随网关扩展而复用。
案例片段(已脱敏):溯源留痕与校验配置片段:
yaml watermark: enabled: true bias: 0.5 detect_threshold: 0.9 provenance: inject_id: true log_fields: [model, caller, dept, ts] verify_endpoint: /v1/verify external_meta_minimal: true