先说一个我自己经历过的场景:有段时间我在做内容合规方向的内部工具,需要把 AI 批量生成的稿件按风险等级分拣出来,再交给人工复核。最初我试过用另一个大模型当裁判,效果时好时坏,而且调一次要花不少 token,排队跑一批文档经常把人等毛了。后来同事半开玩笑地说:“既然你怀疑输出在骗人,那不如先让几行字符串规则去盘问一下。”我当时心里挺不屑的——正则什么时候能看穿语义了?但实在被成本逼得没办法,就写了几十条规则试着筛了一轮,结果让我很意外:大约三成有明显“自说自话”特征的稿子,根本不需要语义模型,用模式匹配就能抓出来。今天这篇就想聊聊,我后来怎么把这个思路做成了一道放在 LLM 输出侧的正则闸门。
我不是要跟你说正则能替代语义审核,那是吹牛。真正有价值的点在于:LLM 输出的不可信,往往不是整个句子、整个段落都不可信,而是藏在一些非常固定的“语气瑕疵”和“结构瑕疵”里。而这些东西,恰好是正则的甜区。
1. 先说清楚“骗自己”是怎么回事:自噬循环与幻觉的“合法化外衣”
1.1 模型输出正在变成下一代模型的训练原料
很多人讨论 LLM 幻觉时,默认场景是人喂给模型的数据有问题,或者模型没学过相关知识便强行作答。但你有没有想过一个问题:现在网上铺天盖地的文章,有多少已经是 AI 生成的了?如果这些 AI 生成的内容又被人抓取、整理成数据集,再拿去微调下一代模型,会发生什么?
技术圈管这叫“自噬循环”,也有人叫“模型坍缩”。一代模型在数据里留下一个错误说法,第二代模型不仅没纠正,还会基于这个错误说法继续“合理”推导,把错误包装得更圆润。我见过一个离谱的例子:一份 AI 写的行业综述里出现了“根据 CB Insights 2024 年 6 月的报告”,我花十分钟找到了原始出处,发现 CB Insights 根本没发过类似报告,那个标题是模型自己拼接的。但句子结构、称呼、引用格式都是对的,一眼看过去和真实引用几乎一样。
这就是我标题里说的“骗自己”。不是说模型有主观恶意,而是它从那些同样由 AI 制造的内容里学来了“引用应该长什么样”,却学不会“这个引用是否真实存在”。等到这类内容再回流,错误就变成了一种被复制的“记忆”。
1.2 幻觉在文本上留下的痕迹一点都不隐蔽
听上去很玄乎,好像模型在撒谎这件事难以捕捉。但你把几十篇有幻觉的文章摆在一起对比,会发现它们的输出有一种奇特的“紧绷感”。人写东西的时候会有犹豫、会有口语的毛边、会使用模糊程度不一的限定词;而模型在编造事实时,反而特别爱用斩钉截铁的绝对化表达。
举个具体的对比:
- 人写:“这个方向可能跟图数据库有点关系,但我还没验证。”
- 模型编造时爱写:“这一领域主要由Neo4j主导,其核心优势在于原生图存储与事务处理的深度整合。”
第二句话里没有一处语法错误,逻辑上也没有硬伤,但它预设了“Neo4j主导”这个未经论证的前提,并且用非常笃定的语气把它陈述出来。正则干的活,就是先把这种“语气上的过度自信”和“上下文里缺少支撑证据”的错位信号标记出来。
我后来把这类特征总结成四类,下面第二部分会展开讲。这四类信号单独出现任何一个,都不一定代表内容是错的;但多个信号叠加的时候,幻觉概率会明显上升。这道闸门的核心逻辑就是:不追求零误伤,只追求把高风险的稿件以高召回率拦下来,让真人去复核。
2. 这道闸门为什么用正则而不是大模型裁判
2.1 语义审核在批量场景中的三个现实痛点
不是说大模型裁判没用,它在深度分析方面确实更强。但把它放在“第一道闸门”的位置上会有几个绕不开的问题:
第一个是成本。一次语义判断如果按输入 2000 字、输出 200 字来算,在主流 API 定价下,批量跑一万篇稿子的开销是肉眼可见的。排在业务高峰期时,还会挤占真正需要模型智能的环节。
第二个是延迟。内容审核管道通常要求文档进来后几秒内给出返回,语义级裁判的响应时间往往不可控。你总不能因为裁判服务超时,就把所有待审稿件压在队列里。
第三个是黑盒风险。如果是给下游提供 API 能力,你需要向调用方解释“为什么这篇稿子被判高风险”。大模型给出来的理由经常是“根据上下文判断,该内容可能包含不实信息”。这种理由没法审计,也没法沉淀成规则。但正则规则是透明的:“你命中第 3 类信号 7 次,阈值超过了。”直接把命中的原文片段贴出来就行,谁看了都服气。
2.2 正则能抓到的是人类作者的“刻意”和机器作者的“惯性”
正则的局限大家都清楚:它不理解上下文,不掌握常识,甚至搞不定同义改写。那为什么在检测 AI 内容可信度时反而有奇效?
关键在于人和机器的犯错模式不一样。人类故意写假话时,会刻意规避明显的模式复用,把谎话说得很“活”;但 LLM 生成文本时的概率采样机制决定了,只要语义相似,它就更倾向于落入相同的表层句式。同一个意思,人的表达可以换一百种花样,模型的表层句法特征却往往收敛在有限的几类槽位上。
“根据XX报告显示”是一种槽位,“值得注意的是”是一种槽位,“综上所述,我们可以得出结论”也是一种槽位。槽位不是错误本身,但它可以帮助你锁定某个位置,然后去检查这个位置上的内容是否真的配得上这么硬的语气。这就是正则闸门的抓手。
2.3 闸门不是替代品,是前置于深度审核的粗筛层
想清楚定位后,我对这套规则的预期就变得很务实。它不负责告诉你“这篇文章是假的”,它只负责告诉你“这篇文章在语气和结构上呈现出若干高 hallucination 风险特征,建议进入下一步人工或语义模型复核”。如果一条规则命中了,系统会把原文切片高亮展示给审核员。
这种前置闸门的价值不亚于一个完整的语义审核器。因为大部分接入内容生成管线的团队,真正缺的并不是最终那一下判断,而是缺少一个能把每天几千篇稿子缩减到几十篇重点稿的低成本初筛器。把这道初筛放在 LLM 输出和业务展示之间,用户的体感是“多了道保险”,而不是“每次都要等半天审核结果”。
3. 我把危险信号拆成了四类,直接在文本模式上做标注
那具体怎么在文本模式上做文章呢?我在第一版里拆了四类正则信号。拆分的依据不是语言学理论,而是我肉眼标注了几百段可疑文本之后,总结出来的高频共性。你不需要跟我完全一致,可以按你自己的语料去调整,但这四类可以作为一个相对通用的切入点。
3.1 A类信号:无出处支撑的刚性论断
这是最重要的一类。它的特征是,句子在形式上被包装成了一个“有出处”的陈述,但整篇上下文里并没有真正给出可验证的来源。典型模式我用表格列一下:
| 规则意图 | 正则模式片段(示意) | 命中的文本示例 |
|---|---|---|
| 引用类动词后接机构或报告 | `(根据 | 据 |
| 机构名加年份的伪精确引用 | `[A-Z][A-Za-z0-9&\s]{1,20}?(研究院 | 研究中心 |
| 绝对化副词后接数字 | `(毫无疑问 | 毋庸置疑 |
这个思路值得展开说说。正则只抓“形式引用”,不判断引用是否真实存在,这个矛盾怎么解决?答案是把判断留给后续规则。比如一个名叫“某某研究院”的机构名出现了,正则先把它圈出来;接着另一条规则会检查全文是否出现过至少两条以上的独立来源名称。如果全篇只有一个“权威机构”死死撑着,来源多样性不足,这条规则组合就把该段落标记为“可疑引用”。
3.2 B类信号:变量置换式类比和“一本正经”的桥接句
模型在编造不存在的机制时,特别喜欢把“一种技术”和“另一种技术”在句子里强行做平滑过渡,读起来没有任何逻辑跳跃,但恰恰因为太顺了,才显得可疑。标志性的表达有:“其底层逻辑可以类比为”“本质上等同于”“从原理上看,这与……高度一致”。
我不是说这些表达一定出自幻觉文本,人类作者也常用。但如果你统计一段文字里这类桥接句的密度,会发现模型输出显著高于普通作者。原因是 LLM 生成时非常依赖 token 之间的高概率续写,而这些桥接句在语料里出现的频率极高,属于模型眼里的“安全路径”。
我在实际规则里没有去禁止这些词,而是对它们做了加权计分:出现一次扣一点信任分,出现三次以上才触发高亮。真正被我们标记成高风险的,往往是“桥接句 + 无引用 + 绝对化陈述”三个特征挤在同一段的情况。这种组合模式下,幻觉率极高。
3.3 C类信号:空转的宏观总结和陈词滥调堆叠
很多 AI 生成稿件被判“假大空”,不在于它说了假数据,而在于它输出了一堆不可证伪的宏观话术。比如“赋能”“抓手”“闭环”“生态化反”这类词的密集出现,往往说明模型在调用一套被过度训练的“官腔语料库”,而它的具体信息量已经枯竭了。正则在这里做不了语义判断,但可以统计“废话密度”。
具体做法是维护一个表达库,包含空泛名词和低频个性化表达。在模型输出之后,计算“空泛词密度”,公式是:空泛词命中次数除以总词数。阈值我设的比较宽,超过 15% 才提示,低于这个值不打扰。这样做是为了避免误伤那些明明内容扎实但风格上也有点爱用术语的真人作者。
3.4 D类信号:逻辑连接词的单调性和机械排比
这个特征平时很少有人提,但在我看来非常有意思。真人写作时,逻辑连接词的使用往往比较散——一会儿“但”,一会儿“不过”,一会儿直接不用连接词靠语序推进。模型却倾向于反复使用同一批低风险连接词,比如“此外”“同时”“然而”在几千字里反复出现,甚至隔两句就出现一次。
对这种现象,正则的抓取逻辑采用滑窗检测:设定窗口 500 字,如果同一个连接词出现三次以上,并且它出现的间隔特别均匀,就标记为“机械节奏”。
这种规则看着不强,实际用起来却能揪出一整类“AI 水货稿”。这类稿子内容也许没有错,但它表达出来的思维深度和编辑程度都偏低,不该让它直接流向用户侧。
4. 落地成闸门:正则规则怎么组织、怎么计分、怎么输出
4.1 规则引擎的层次划分
这块我建议直接把规则分成两层:信号层和聚合层。信号层的规则只管一件事——抓某个文本模式,返回命中位置和命中类型;聚合层的规则才去组合多个信号,判断最终要不要拦截以及拦截的级别。这样做的好处是,以后单条规则想调整、想加新特征,不用动核心流程。
下面是一个规则引擎的简化示意,用的 Python 风格伪码,但在任何语言里实现起来都不复杂:
# signal layer: 每条规则都是 (pattern, weight, signal_type, desc) SIGNAL_RULES = [ { "name": "assertive_citation", "weight": 2.0, "type": "A", "desc": "无出处的刚性引用", "pattern": r"(根据|据|援引|引自)[^。,]{0,30}?(报告|研究|数据|统计)" }, { "name": "absolute_number", "weight": 1.8, "type": "A", "desc": "绝对化副词后接数据", "pattern": r"(毫无疑问|毋庸置疑)[^。]{0,30}?\d+(%|万|亿)" }, { "name": "bridge_sentence", "weight": 1.2, "type": "B", "desc": "高概率类比桥接句", "pattern": r"(底层逻辑可以类比为|本质上等同于|从原理上看,这与[^。]{0,20}高度一致)" }, { "name": "empty_talk", "weight": 0.6, "type": "C", "desc": "空泛词命中", "pattern": r"(赋能|抓手|闭环|生态化反|全方位|多维度)" }, { "name": "monotone_connector", "weight": 0.4, "type": "D", "desc": "机械连接词", "pattern": r"(此外|与此同时|然而)" } ] # aggregation layer: 聚合信号 def evaluate(text): hits = [] for rule in SIGNAL_RULES: for m in re.finditer(rule["pattern"], text): hits.append({ "rule": rule["name"], "type": rule["type"], "weight": rule["weight"], "span": m.span(), "sample": m.group(0)[:80] }) score = score_hits(hits, text) return classify(score), hits4.2 计分模型:累加加权、密度惩罚和上下文抑制
计算最终风险分时,千万别只做简单加法,要注意三个细节:
第一是长度归一化。一篇两百字的短文命中三个“空泛词”,比一篇五千字的文章命中五个更扎眼。所以每一类信号的原始分先除以文本长度,得到密度分,再参与聚合。
第二是同类信号的重复惩罚。连续命中同一个规则名,第一次权重计入,后续命中按 0.7、0.5、0.3 的衰减系数折减。为什么这样处理?因为同一类模式连续出现,说明内容越写越“机械化的空转”,确实该扣分,但不至于一条命中的分无限累加,把整个文件打到不可看的程度。
第三是上下文抑制机制。也就是说,不是所有引用都该被扣分。如果检测到该段落前文存在类似“本文整理自XXX的访谈,成稿经过对方确认”这样明确的外部信息佐证,引用句的“无出处刚性引用”扣分权重降为原来的 30%。具体实现可以在命中后向上回溯 500 字,看看有没有佐证词。
4.3 三档输出策略
最终输出不要做得太两极分化,建议三档:
- 低风险:直接放行,不需要人工介入。
- 中风险:系统自动放行,但推送给作者一个提醒,让作者自己看一眼高亮片段决定是否修改。
- 高风险:自动拦截,进入人工复核队列,复核通过前不能对外展示。
这个策略的核心是:闸门并不是卡死了所有可疑内容,而是给不同风险等级安排了不同的处理路径。尤其是中风险档,能避免大量不必要的审核人力。
5. 真刀真枪跑一轮:验证集效果与调参注意事项
5.1 用什么验证集来测
正则有一个特别容易踩的坑:你在手头的一百篇稿子上调出来的规则,拿到新的领域语料上可能完全失灵。所以验证集的构建千万别只用自己生产的同领域数据。
我建议至少包含四个来源:一,自己管线里的人工复核稿件;二,合作伙伴提供的“疑似高风险”样本;三,从开源数据集里随机挑的“正常文本”作为负样本;四,纯人工写作的高质量博客文章作为负样本第二组。负样本里混合真人内容很重要,否则你调着调着就会把所有规则调到过拟合,误伤极高,闸门根本没法上线。
我当时第一版规则在正样本上召回率达到 0.82,但负样本误伤率高达 0.47,也就是说每两篇正常文章就有一篇被误判为高风险。后来我意识到,问题出在 D 类“机械连接词”规则上,真人比较长的文章里也会频繁使用那些连接词。后来通过滑窗检测、同类惩罚的衰减系数调整,误伤率才降到 12% 左右,召回率虽然也掉了一点,到 0.76,但整个系统变得能用了。
5.2 误伤案例分析:真人写作用词也有“高密度时刻”
有一次,一篇人工深度调查文章被闸门判成高风险,理由是 C 类空泛词密度超标。我一看原文标题就笑了——那是一篇正经的行业会议综述,里面大量引用了嘉宾发言的原话,嘉宾在台上就是爱讲“赋能”“抓手”这类词。正则分不清作者自己在说空话,还是在引用一个说空话的人。那之后我加了一条前置处理规则:如果检测到引号或冒号直接引语比例超过 30%,先把引语部分切走,只对非引语部分做密度分析。
另一个经典误伤是编辑在文章开头放了免责声明:“本文基于公开资料整理,部分观点引自行业报告……”结果 A 类规则的“无出处刚性引用”命中了大量正文内容。实际上文章处处有出处,只是出处都在文后的参考文献列表里。针对这种情况,我把引用列表单独切出来,并在正文计分时对“有对应参考文献编号的句子”做豁免处理。这个豁免的逻辑很粗糙,但把一个最严重的误伤源解决了。
5.3 不该省的小事:对命中的语境片段做保留
正则命中的只是几个词,但审核员和下游系统需要的是上下文。我强烈建议在输出命中记录时,不要只输出匹配到的二十个字,而是把命中位置前后各扩展 200 字符作为“语境窗口”,并且把窗口内的其他 B 类、C 类信号同时也标亮。这样人工复核时看到的就是一个完整句群,而不是碎片。
我曾经偷懒只保存命中片段,结果人工复核员不得不一次次回到原始文档里去翻上下文,效率折损大半。这也算是一个“机器可读不够,人还得流畅审核”的经验。
6. 更进一步:正则与其他可信度校验手段怎么组合
6.1 信号输出可以作为语义模型的先验特征
单独的正则再细致,也没法覆盖“事实性错误但语气相对克制”的那类内容。所以,我会在闸门把高风险稿件摘出来之后,在第二级引入一个小型语义模型,对高风险稿件做更具体的事实一致性判断。正则闸门的输出不是终点,而是给语义模型的“先验框”——告诉它重点检查哪几段。语义模型只针对这些高亮段落做 entailment 判断,比让它全文扫读要准得多、省得多。
具体来说,把闸门输出的命中片段拼成一句话,比如“本文在第 3 段出现绝对化断言,涉及数据无来源;第 5 段出现桥接类比,缺上下文支撑”,然后让语义模型去判断文案里的核心断言是否和已知知识库冲突。这种方式相当于把语义模型从一个“通读型阅读者”变成了“拿着线索查证的审计员”,效果和成本都有改善。
6.2 知识库正则:为行业专有实体建立动态白名单
除了对通用表达做检测,你还可以把业务知识库里的专有实体表拉出来,生成一组对照规则。比如在某个医学内容场景下,模型经常编造“某药物已在各国获批上市”。跑正则之前,先生成一张带正则化实体名的知识图谱表,对所有药物名称做白名单匹配。不在白名单上的陌生拼写一旦出现,直接进入高风险池。
这个思路听起来像知识库校验而不是正则,但落地时你会发现,它完全可以做成正则样式:(?:复方|单方)?[^。,]{1,30}?(胶囊|片剂|注射剂)[^。]{0,20}?(获批上市|上市许可),再用后面捕获到的名称去查业务库。正则在这里的价值不是理解知识,而是帮你完成从自然语言到结构化实体查询的“定位”工作。
6.3 要不要考虑多语种变体
如果你的内容管线覆盖英文、日文等语言,正则闸门不是不能做,但要小心字符编码和分词差异。中文在正则上有一个天然优势:字与字之间没有空格,词边界模式相对稳定。英文则要处理大小写、复数变形、时态变化等大量形态学问题,只靠正则很容易被击穿。
对于英文,我的建议是正则只用于标点级别或句式层面的稳定性特征(如引号使用一致性、引用格式),不再试图用正则去抓“过度自信表达”这种语义倾向特征。英文内容需要另配一套基于分词和词性标注的规则引擎,那又是另一套工程了。
6.4 长期维护机制:置信度基线与误报反馈闭环
任何规则系统上线后都会面临同一个问题:规则老化。模型的语言习惯会随着底座模型的更新而变化,新的模型版本可能不再使用旧版爱用的那些高频表达,或者学会了绕开你精心设计的规则。我见过不少团队做完一版就再也不动了,半年后规则集沦为摆设。
要避免这个问题,建议做两件事。第一,在系统里保留一段“规则灰度开关”,按流量百分比放量,每条规则单独看命中后的人工复核通过率。通过率低于 60% 的规则,自动降级为观察状态。第二,每周抽少量“高风险通过人工复核后被判定为正常”的案例,反向生成新的特征词加入规则库。
维护这套机制的工作量不大,单人每周半天到一天就能完成,但能让规则库保持生命力,始终贴合最新模型的输出模式。
7. 踩过的典型坑:我在生产环境里遇到的三个反直觉问题
7.1 规则冲突导致高风险稿件被降权放行
听起来很简单:一条规则加了 40 分,一条规则减了 30 分,本来应该拦截的稿子,因为同时命中了一条“信任加分类规则”而净分数低于阈值。这种冲突在刚开始时根本想象不到,直到一次审核员反馈“有一篇明显是模型编的稿件居然放行了”,我们查看日志才发现,它命中了“上下文抑制关键词”中的“经作者授权”这个片段,而这句话其实出现在稿件的广告合作免责段落里,和正文内容毫无关系。
后来我加了两个约束:一是“抑制词必须与被抑制的命中片段处于同一段”,二是“抑制效果不跨命中类叠加”。这两个约束修掉一整类因为全局抑制导致的误放问题。
7.2 正则与 LLM 输出格式的耦合问题
模型在输出时经常加 markdown 格式,常见的有加粗、列表、表格。你在纯文本上写的正则,如果没先剥掉 markdown 标记,会在匹配边界上出很多怪问题。比如“根据*报告显示”,星号把文本切得七零八落,规则可能匹配不到;或者因为路径里有大量 markdown 表格符号,导致一段长文本里多处命中重叠。
解决方案是在进入规则引擎前先做格式清洗:去掉 markdown 标记、去掉超链接保留链接文字、将无序列表符号替换为统一分隔符。清洗阶段和信标阶段分离之后,正则规则的编写难度会显著下降,因为不需要再考虑字符边界上的干扰。
7.3 LLM 会“绕开”规则,但绕开的样子也很明显
真正让我觉得有意思的是,部分较新的模型如果把温度调高,会减少使用被规则高频命中的“空泛词”,甚至整体降低绝对化陈述的密度。可一旦它们刻意绕开这些词,新的输出往往会显得很“端着”——大量使用被动语态,把原本是主语的机构名全部后置,一个判断句能被拆成三段。
我没有额外针对“被动语态”加规则,因为闸门的重心始终是召回高风险内容,它绕开了“空泛词密度”这类低权重信号并不代表它能绕过“无出处刚性引用”这种高权重结构信号。只要你的证据锚点足够硬,换皮不换核的生成策略依然会撞上口子。
8. 我不建议你照抄规则模板的几点想法
最后这段可能跟很多“技术教程”的调性不一样。网上能搜到很多现成的 AI 检测正则模板,包括一些检测 AI 内容的“风格指纹”清单。我也参考过一些,但最终没有直接照搬。
原因很简单:语言是活的,模型也是活的。一个月前的规则列表,放到新的模型版本上,去重率可能会掉 30% 以上。模板能给你的只是“从哪里开始看”的思路,真正能打的规则集,必须建立在你自己的语料和业务场景之上。
我建议你从自己的负样本和正样本里各抽二十篇,逐句过一遍,找到那些频繁出现且在你人工审核时愿意打上“有点可疑”标签的句子形状,再去结构化整理规则。你会发现这是一种比想象中更有效的训练方式——你用肉身当过一阵子规则引擎,才会真正理解哪些槽位值得用正则去找,哪些槽位需要交给更重的模型。
如果你只是偶尔需要判断一篇 AI 生成内容的可信度,那不需要搭完整的闸门服务,写一个十几行的 Python 脚本,把 A 类那几条规则放进去跑一遍就够了。多数时候,刚性的“伪引用”和“绝对化数字”已经能帮你拦住大量肉眼看似流畅、实际站不住脚的文章。等你的用量和数据量上来,需要把它做成带人工复核流程的生产组件,再回来参考我上面讲的工程化细节。
正则识别不了一句话背后的真假世界,但它在“文本正式入口”这个位置上,确实能帮你用极低成本拦住一批最危险的自动生成内容。它们或许不假到能被一眼看穿,但你看过足够多案例后会发现,它们的“假”从来不会毫无声音。