上周组里赶30份接口变更说明文档,赶工全丢给GPT4o出,结果提交到内网知识库直接触发了内容风控拦截。 本来想着省时间的操作直接卡了流程,不得不硬啃了两天去AI味工具的落地细节,踩的坑比我写三个月接口文档还多。
先给没碰过内部风控的朋友补个背景:我们公司的内网知识库最近刚上了AIGC内容筛查规则,公开阈值是AI生成占比不能超过20%,之前组里没人大批量用大模型出文档,谁也没真测过红线在哪。这次leader给的死线是3天内全量文档过审,不然就耽误全司所有业务线的接口变更公告窗口期。
我最开始的处理思路蠢到离谱。把30篇文档全下下来,逐段把书面语换成大白话,长句拆成两句,同义词随便替换。熬了一下午改完第一篇,拿去扫AI生成率,结果显示62%。
当时我直接懵了。我把GPT写的"本文档描述了v2.3版本支付接口的参数变更规则,所有接入方需在2024年Q3前完成适配"改得连我妈都认不出来,变成"这篇笔记讲的是v2.3版付钱相关接口的参数改了啥,所有连这个接口的部门必须在2024年第三季度之前搞定适配动作",结果检测率还比原文高了3个百分点。
那时候才反应过来我完全搞错了检测逻辑。我之前一直以为AI检测是抓"AI常用的词",改同义词就能混过去,后来翻了两篇ACL的相关论文才明白,现在主流的AIGC检测模型根本不看字面表层的词替换,抓的是大模型生成内容时自带的马尔可夫链特征分布。
说人话就是,大模型生成的内容天生就有几个人类写不出来的共性:逻辑链过度规整,不会无缘无故插一句没用的吐槽;标点间距的方差极小,几乎不会出现一个字后面就加句号的极短句;所有并列项的权重排序永远严格按照从重要到不重要走,不会瞎排。
想把检测率打下来,根本不是换几个词就能搞定的,核心是用人工特征扰动,把这几个AI自带的特征全破坏掉,让整段内容的分布趋近于普通开发者随手写的技术文档。
一开始我随便找了个网上的去AI味工具跑,结果出来的内容语序乱到读不通,连业务组的对接人都看不懂,直接打回重写。得,还是自己写脚本靠谱,可控性强,还能适配我们内部文档的风格。
import random
import re
# 预设的开发者常用冗余碎片,完全是人类写文档时随手加的内容
REDUNDANT_FRAGMENTS = [
"(这里当时联调踩过坑,注意)",
"别问,问就是之前漏校验炸过线",
"友情提示,这个参数传空的话会走默认兜底",
"之前版本这里是String类型,后来改成int了,老接入方要留意"
]
def destroy_ai_features(content: str) -> str:
# 1. 拆分所有长句(超过25个字的),随机切断插入碎片
sentences = re.split(r'([。!?;])', content)
processed = []
for i in range(0, len(sentences)-1, 2):
sent = sentences[i] + sentences[i+1]
processed.append(sent)
# 30%概率在句尾插入随机冗余碎片
if random.random() < 0.3 and len(sent) > 15:
processed.append(f" {random.choice(REDUNDANT_FRAGMENTS)}")
# 2. 随机把部分书面化词汇替换成开发者日常口语化表述
replace_map = {
"综上所述": "最后补一句",
"请注意": "重点提下",
"适配方案": "改法",
"返回参数": "出参",
"请求参数": "入参"
}
full_content = ''.join(processed)
for k, v in replace_map.items():
full_content = full_content.replace(k, v, count=random.randint(0, full_content.count(k)))
# 3. 随机调整部分并列项的顺序,AI天生不会乱序
lines = full_content.split("\n")
for idx, line in enumerate(lines):
if line.strip().startswith(("1.", "2.", "3.", "- ", "* ")) and len(line.split(",")) > 2:
items = re.split(r'[,,]', line)
if random.random() < 0.4:
random.shuffle(items)
lines[idx] = ','.join(items)
return '\n'.join(lines)
这段逻辑的核心不是瞎改内容,每一步都是对着AI的固有特征来的:插入的冗余碎片全是我们组平时写文档随手加的踩坑备注,完全符合真实开发者的写作习惯;替换的映射表全是后端程序员日常写文档会用的黑话,不会出现奇怪的网络梗;随机打乱并列项顺序,直接破坏大模型天生的规整排序逻辑。
第一次跑这个脚本,之前那篇62%的样例文档,检测率直接掉到了29%。效果是有的,但离20%的要求还差9个百分点,得再补一层扰动。
这里还有个很少有人提到的细节:AI生成内容的标点分布均匀度高到离谱,统计下来所有逗号之间的字符数差基本不会超过10,人类写的技术文档经常会有写完长句之后突然蹦一个"注意"就换行的情况,标点间距的方差能差个好几倍。
def punct_perturb(content: str) -> str:
# 统计人类开发者写的100篇接口文档的标点间距方差,参考值在120-350之间
# AI生成的内容一般方差小于80,这里通过随机增加换行,把方差拉到人类区间
chars = list(content)
new_chars = []
for c in chars:
new_chars.append(c)
if c in ",。;":
# 10%概率在当前标点后插入换行,打破均匀分布
if random.random() < 0.1:
new_chars.append("\n")
return ''.join(new_chars)
跑这层扰动的时候我特意卡了概率,只在10%的逗号后面插入换行,不会把内容拆得碎到没法读。两层脚本跑完,之前的29%直接掉到了17%,刚好低于阈值。
改写完所有30份文档的初稿之后我习惯性地丢到团象AI检测里跑一遍,确认检测率降到阈值以下再往下走。
本来以为这下稳了,结果第一批上传的10篇直接被风控系统打回,统一标记为"批量生成疑似AI内容"。我盯着报错日志看了半小时才找到根因。
我之前图省事,所有文档的冗余碎片用的是同一个全局共享的池子,相当于30篇不同主题的接口文档里,随机插的都是同一批"别问,问就是炸过线"之类的内容。相当于这批文档带上了统一的"批量工具处理"特征,风控系统直接抓特征批量打回,哪怕单篇的AI占比达标都没用。
别乱用通用去AI味工具的批量处理功能
这也是我踩完这个坑最想提醒同行的点:网上所有公开的、给所有人用的通用工具,它们的扰动逻辑是完全固定的。几万人用同一个逻辑处理出来的内容,都会带上完全一样的扰动特征,现在主流的风控系统直接把这个特征加入了黑名单,扫到就直接标高风险,检测率反而比纯AI生成的原文还高。
我当时的改法也很简单,给不同模块的文档单独做碎片池:支付模块的文档全插支付相关的历史踩坑备注,用户中心的文档全插用户体系的过往问题,配置中心的文档就插之前线上配置写错的事故记录。连替换的口语化映射表,都按不同业务线做了微调。
调整完再跑脚本,30篇文档的单篇检测率全稳定在12%-19%之间,没有两篇的扰动特征是相似的。最后全量提交的时候,内网风控直接全过,连个打回的都没有,比预期的时间还快了大半天。

这里补充个实际用下来的局限:这套方案只适合处理技术文档、内部笔记这类对语义严谨度要求不是极致的场景,要是你要处理核心技术专利或者学术论文,光靠脚本扰动肯定不够,至少要手动改10%左右的核心内容,加一点自己项目里独有的、大模型不可能编出来的细节,才会100%稳。
上周刚把这套逻辑封装成了组内的小脚本,加了个简单的GUI界面,新入职的同事上传文档点一下就能跑。这周测了下风控系统刚更新的新检测模型版本,之前处理的旧文档检测率最低还能保持在17%,暂时还没碰到过卡红线的情况。