做社区类平台,评论审核和回复这块我前后折腾过好几个版本。最开始是人工一条条在后台过,稍微有点量就顶不住;后来上了关键词过滤,感觉能喘口气了;再后来发现有用户用拼音、拆字、表情符号绕过词库,还有那种表面正常、实际带攻击性的评论,规则引擎完全招架不住。于是我换了个思路:把 SpringBoot 服务和大模型接口结合起来,做了一套“AI 智能评论审核 + 自动回复”的系统,一条评论从提交到上线,中间经过前置过滤、AI 语义判定、分级处置、自动回复几个环节。这篇文章把整个方案的架构设计、核心代码实现、部署后踩过的坑和复盘过程都写出来。适合正在做内容社区、博客、电商评价这类场景,且想用 AI 替代部分人工审核工作的开发者参考。我会把关键代码和配置直接贴出来,也会说明为什么这么设计,而不是只给结论。
1. 关键词过滤之外:评论审核真正要解决的三件事
1.1 语义级风险是规则引擎的盲区
先聊一个最常见的误区:很多人觉得审核等于敏感词拦截。词库这东西确实便宜、快、可控,但它的盲区也非常明显。我见过一个真实案例:一条评论把某个负面词汇拆成“尸 米 青”,中间插了空格,词库没命中;还有人用同音字、拼音首字母、谐音梗来绕过,这些都还是低级的。更高一级的是语义伪装,比如“这篇文章写得真好,作者怕不是用脚写的吧”,这句话单独抽出来每个词都正常,但整体是讽刺。关键词引擎面对这种句子,只能放行。
更麻烦的是,很多攻击是上下文相关的。同一句话出现在不同文章里,意思完全不一样。比如“这本书我两天就看完了,作者水平也就那样”,如果在书评区出现,可能是一条中性的体验反馈;如果配合了前面几十条争吵,它可能就是在引战。这种判断依赖对话上下文,依赖评论者历史行为,甚至依赖文章本身的立场,纯规则系统无法建模。
所以我把审核目标拆成三层:第一层是底线安全,比如广告、联系方式、违法信息,这种可以用词库快速拦截;第二层是语义风险,包括辱骂、引战、歧视性表达,这种需要 AI 理解语境;第三层是价值判断,比如评论是否跑题、是否包含未经验证的事实陈述,这种通常不需要直接拦,但会影响是否触发人工复审。想清楚这三层,后面的技术选型才有依据。
1.2 审核标准不能是一刀切
审核最容易犯的第二个错误,是试图用一个统一阈值处理所有评论。实际上“什么该拦”“什么该放”高度依赖平台定位。我做的是一个技术内容社区,用户可以接受比较直接的批评,但不能接受人身攻击;而电商评价场景下,带强烈情绪但信息真实的差评,恰恰是不能拦的,因为拦了等于掩盖真实用户反馈。如果让一版 prompt 去适应所有业务,结果一定是误杀率飙升,要么垃圾评论漏成筛子。
我的做法是把审核策略做成可配置的,按业务线拉出不同的“审核档位”。比如:
| 业务场景 | 底线拦截 | 语义拦截标准 | 自动回复 |
|---|---|---|---|
| 技术博客评论区 | 广告、外链、联系方式 | 仅拦截人身攻击和引战 | 对提问类评论自动回复 |
| 商品评价区 | 广告、虚假交易暗示 | 拦截明显辱骂,保留负面评价 | 不自动回复,只归档分析 |
| 社区讨论版 | 广告、违法信息 | 拦截歧视、骚扰 | 仅回复礼貌性支持类评论 |
这套配置在代码里就是一个枚举加一个策略类,AI 审核时把这些档位信息拼进 prompt。好处是同一个模型接口可以服务多条业务线,换业务线时只需要改配置,不用重新调模型。实际运行下来我发现,档位越明确,模型的输出越稳定,因为大模型对“边界”的理解依赖你给它的约束条件,约束越模糊输出越飘。
1.3 为什么要用大模型而不是自训练分类器
可能有人会问,这种语义分类任务,用传统机器学习的文本分类不也能做吗?我早期也试过训练一个轻量分类器,效果能用但代价不小:标注数据要持续维护,评论区用语变化快,今天新出的梗、新形态的广告变体,分类器需要周期训练才能跟上,维护成本比想象中高得多。大模型的优势在于它的常识推理能力,能处理没见过的表达方式,这是规则和传统模型做不到的。
不过大模型也有明显的短板:贵、慢、结果不稳定。所以我的结论不是“全量交给大模型”,而是“让大模型做它擅长的事,让规则做它擅长的事”。这个思路贯穿了整个架构设计,前置过滤用规则把明显垃圾挡掉,大模型只处理需要语义判断的部分,最后再用规则兜底校验大模型的输出。这句话请先记住,后面所有设计都是在为这句话服务。
2. 评论从提交到上线的完整路径:审核架构设计
2.1 同步拦截还是异步审核:延迟与体验的取舍
第一版我做得比较天真,评论提交接口里直接同步调用 AI 接口,等审核结果出来再决定是落库还是拒绝。单条评论如果只是接个外部接口,看起来也就一秒多,但一旦并发上来,整个写入链路全被拖住。有一段时间评论接口的 P95 延迟到了 6 秒以上,用户提交一条评论转半天,体验非常糟糕,数据库连接也被拖垮。
后来我把审核改成了异步流程。用户提交评论后,先做一层非常快的前置检查(长度、频率、敏感词、黑白名单),通过后立刻把评论状态置为“审核中”,同时返回给前端一个“提交成功,等待审核”的结果。真正的 AI 审核在另一个线程池里异步执行,审核结束后通过状态更新通知前端结果,前端轮询或者靠 WebSocket 推送都行。
异步化之后,写接口的耗时从几秒降到了几十毫秒,用户体验基本没变化,而且 AI 审核失败也不影响评论的提交。副作用是引入了状态管理,因为评论不再一步直接落成“已通过”,而是有中间态。但这个代价值得,后面第四节会聊到分级状态机的具体设计。
2.2 分级处置:直接通过、人工复审、直接拦截
AI 审核的结果不能只有“过/不过”两种,那样太粗暴。我按风险等级把处置动作分成了三档:
- 通过:语义正常,直接上线展示。
- 拦截:属于广告、辱骂、违法信息等硬风险,直接丢弃,不展示给其他用户,但需要记录日志。
- 复审:有潜在争议,比如疑似引战、疑似阴阳怪气、模型置信度不高,这时候交给人工后台处理。
为什么必须留“复审”这个中间档?因为大模型的判断它不是百分百可靠的,有的评论确实在打擦边球,你和它之间的边界特别模糊。如果系统把它直接拦截了,真实用户会觉得平台乱删评论;如果直接放了,又会有人说平台不管控。人工复审就是这两个极端之间的缓冲带。
实际运行数据里,我会定期抽样复审结果反哺 prompt 和阈值,比如某段时间“复审”率过高,说明提示词里的标准太严,需要把边界描述得更具体。这里提醒一点:复审不是为了追着 AI 改错,而是让系统能意识到自己“拿不准”,能主动把不确定的样本抛给人,这是审核系统成熟度的一个关键标志。
2.3 数据表与状态机的设计
异步审核和分级处置意味着评论数据有一组明确的状态流转,我用一个状态字段表示:
PENDING (待审核) -> APPROVED (通过) / BLOCKED (拦截) / REVIEW (人工复审) APPROVED -> BLOCKED (人工追加拦截) REVIEW -> APPROVED / BLOCKED (人工决定)表结构里除了评论本身的内容,至少要有这几个关键字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| comment_id | bigint | 评论主键 |
| content | text | 评论原文 |
| status | varchar | PENDING / APPROVED / BLOCKED / REVIEW |
| audit_source | varchar | 审核来源:RULE / AI / MANUAL |
| audit_score | decimal | AI 给的风险分,方便排序复审队列 |
| audit_reason | varchar | AI 返回的判定理由,方便人工快速定位 |
| audit_time | datetime | 审核完成时间 |
| reply_status | varchar | 是否需要自动回复 / 已回复 / 免回复 |
设计这个表的时候最容易忽略的是 audit_reason。AI 返回的不只是一个标签,还要有一句话说明判定依据,否则人工复审时面对几百条待审队列,根本不知道模型为什么拦了这条评论,只能逐条重新读,效率极低。加了判定理由字段后,人工复审速度能提升好几倍。
3. 核心实现:SpringBoot 调用 AI 审核的工程细节
3.1 HTTP 客户端选型与超时、重试策略
SpringBoot 里调外部 AI 接口,客户端我用的是 RestClient,Spring 6.1 之后官方推荐的方式,写起来比 RestTemplate 干净,又比 WebClient 少一层响应式的心智负担。核心配置就几个点:连接超时、读取超时、重试次数、连接池大小。
这里重点说说超时和重试。AI 接口的响应时间很不稳定,高峰期可能超过 30 秒,但你绝对不能把读取超时设成 30 秒,否则线程池直接被占满。我实测下来,正常场景 P95 大概 2 到 5 秒,所以把读取超时配置成 15 秒已经足够宽容了,超过这个时间就当失败处理。重试要非常克制,因为 AI 接口的失败往往不是瞬时抖动,而是上游过载,你重试三次等于往火上浇油。我的策略是只重试一次,而且只重试连接失败和 5xx,不重试超时,因为超时场景下即使重试成功,前面那 15 秒已经让线程阻塞太久了。
@Configuration public class AiClientConfig { @Bean public RestClient aiRestClient() { // 简单的连接池,避免每次创建新连接 var requestFactory = ClientHttpRequestFactorySettings.DEFAULTS .withConnectTimeout(Duration.ofSeconds(5)) .withReadTimeout(Duration.ofSeconds(15)); return RestClient.builder() .requestFactory(new JdkClientHttpRequestFactory(requestFactory)) .defaultHeader("Content-Type", "application/json") .build(); } }3.2 Prompt 设计与结构化输出解析
AI 审核最关键的一环是 prompt。我一开始是让模型返回一段自然语言结论,比如“这条评论存在人身攻击,建议拦截”,然后让系统去解析这段文字。这个方案非常脆弱,模型稍微换个表达方式,解析逻辑就崩了。后来改成结构化输出,让模型严格按照 JSON 格式返回,字段固定为风险等级、风险分、判定理由、触发标签。
为了让模型稳定输出 JSON,prompt 里要写清楚输出格式的例子,也就是 few-shot。我贴一下当时用的核心 prompt 模板(做了脱敏处理,只保留结构):
你是一名社区评论审核员。请判断下面用户评论的风险等级。 业务规则: 1. 广告推广、留联系方式、外链引流,属于硬风险,直接拦截。 2. 对他人进行人身攻击、辱骂、骚扰,属于硬风险,直接拦截。 3. 内容阴阳怪气、挑拨引战但未直接辱骂,属于软风险,建议人工复审。 4. 正常讨论、批评、建议,属于安全,直接通过。 5. 无法确定时,给"REVIEW"。 输出格式(严格JSON): { "risk_level": "PASS | BLOCK | REVIEW", "risk_score": 0到100之间的整数, "reason": "不超过20字的判定依据", "tags": ["引战", "广告"] } 评论内容:{commentContent}解析这块我不用太复杂的工具,Jackson 直接读成一个 record 就行。但有个坑要提醒:模型偶尔会返回带 Markdown 代码块包裹的 JSON,或者 JSON 里夹着多余文字。我写了一个简单的清洗函数,先把代码块包裹和首尾乱字符去掉,再尝试解析,解析失败就把这条评论丢进 REVIEW 队列而不是直接放行。宁可让人判,也不能让解析失败变成“审核通过”的逃逸通道。
3.3 成本控制:三层漏斗减少 API 调用
大模型接口按 token 计费,评论量大的时候,费用控不住是最现实的问题。我的做法是做三层漏斗,让绝大多数评论根本走不到大模型那一步:
第一层是本地规则前置过滤。命中硬性敏感词、包含明显广告模式(比如连续数字、链接、微信号字样)的,直接 BLOCK,不调用大模型。这一层能挡掉大概 40% 的垃圾流量。
第二层是低风险直接放行。对长度短、无敏感词、无链接、用户历史信用好的评论,直接 PASS,也不需要大模型判断。比如“写得不错”“学习了”“沙发”,这些高频正常评论完全没必要消耗 token。我维护了一个白名单库,命中直接通过。
第三层才是大模型。只有前两层都没法确定的评论才送到模型那里,量大概占全部评论的 30% 到 40%。这样一来,AI 部分的费用能控制在可接受范围内,而且因为送进来的都是“有点嫌疑但规则说不清”的样本,模型的判断价值也更高。
还有一个省钱细节:prompt 里的业务规则部分是不变的,可以作为固定前缀缓存,但需要算 token 的时候注意,模型计费是按输入总 token 算的,固定前缀太长也费钱,所以规则要尽量精炼,不要写一堆废话。
3.4 降级策略:AI 不可用时怎么办
外部接口总有挂的时候,审核系统必须考虑降级路径。我的降级顺序是这样的:AI 服务不可用 → 先尝试重试一次 → 仍然失败 → 降级为纯规则审核,只有命中硬敏感词的评论会被拦截,其余直接放行并标记为“低置信度审核”,同时记录一条降级告警。
有人可能担心,降级期间垃圾评论不就漏了吗?确实会漏,但你要想清楚一个取舍:审核服务挂了导致评论接口不可用,和审核服务降级导致部分垃圾评论暂时漏进来,哪个后果更严重?对大多数社区产品来说,评论接口的可用性优先级远高于审核的完备性,你要保证用户随时能发评论,而不是因为审核挂了就集体禁言。
降级期间的评论会打上一个 special 标记,后续 AI 恢复后再开一个补偿任务,对标记过的存疑评论补审一遍。这套“先放行、后补审”的思路,在处理外部依赖不稳定时非常实用。
4. 自动回复:让机器回复得像真人但不越界
4.1 该不该回复:意图分类决定了回复边界
自动回复如果写不好,会比不回复更糟糕。那种“感谢您的宝贵评论,我们会继续努力”的模板回复,用户一眼就能看出来,反而引发反感。我的做法是先把评论的意图分类出来,只有特定的意图才触发自动回复,其他情况宁可沉默。
我设定的意图分类有:提问类、支持类、批评类、无效类(纯表情、纯打卡)。触发自动回复的只有两种:提问类和支持类。提问类回复的是针对文章内容的具体疑问,支持类回复的是感谢和认可。批评类一律不自动回复,最多标记给运营同事看。无效类直接跳过,不浪费一次模型调用。
这个策略看起来保守,实际收益很好。自动回复的评论区里,用户回怼机器人的比例明显低了,因为那些会被用户反感的评论,机器人根本不会去接话。自动回复的目的本来就不是“每条都要应”,而是“该应的能应上”,少而精是底线。
4.2 文章上下文注入与事实约束
自动回复最容易翻车的点,是大模型一本正经地胡说八道。用户问“这篇文章里第三段提到的那个算法,为什么不用另一种方式实现”,如果模型没有文章上下文,它会自己编一个理由,看起来头头是道,实际根本不是文章里的内容。让机器人替作者回复,却说出了作者没说过的话,这是社区运营事故。
我的解决办法是注入文章上下文。调用生成回复的接口时,除了用户评论,还把文章标题、摘要、正文前几段拼进 prompt。同时加上硬性约束:如果评论内容超出已提供信息的范围,模型必须回答“这个问题我暂时回答不了,已把问题转给作者”之类的话,不能靠自己的知识库编造。
你是这篇文章的作者助手。以下是文章摘要和开头部分: {articleContext} 用户评论:{commentContent} 回复要求: 1. 语气与文章风格保持一致,简洁自然,不要客套。 2. 只依据上面提供的文章内容回答,不要编造未提供的信息。 3. 如果问题在文章内容中找不到答案,回复:“这个问题超出文章范围,我帮你转给作者看看。” 4. 回复控制在50字以内。这个 prompt 里最关键的是第 2 条和第 3 条,相当于给模型划定了一个“知识边界”。加了这个约束之后,自动回复里的事实性错误率大幅下降。我建议所有做自动回复的人都把这条当作默认配置,别让模型自由发挥。
4.3 防死循环与同一评论去重
自动回复上线后遇到过一个尴尬问题:机器人回复用户的评论,用户觉得有意思又回复了一句,机器人又接话,你一言我一语,评论区变成了两个机器人对话现场。要解决死循环,最直接的办法是限制机器人的回复层级:自动回复状态在表里存一份,回复链路只允许机器人在第一层回复,用户回复机器人之后,系统就退出,不再对话。
还有一个问题更隐蔽:同一用户对多篇文章发了一模一样的评论,他的意图可能只是刷存在感,而不是真在提问。对这种重复内容,我在生成回复之前加一步去重检查,按用户和内容做哈希,查到历史相同评论就不触发自动回复。否则用户会看到一个现象:发十条同样的“博主,你这个方案能给我一份吗”,机器人回十遍一模一样的话,非常傻。
去重逻辑也顺带解决了另一个边界问题:AI 接口返回的内容如果不稳定,同一条评论重试生成时可能得到不同回复,去重表也能帮我们把回复结果固定下来,重复调用时直接读缓存,不重复消耗 token。
5. 实测环节:并发、延迟、误判的教训
5.1 第一版同步调用的性能崩塌
前面提到第一版是同步阻塞调用,我拿压测数据再说明一下严重程度。当时用 50 个并发线程模拟用户提交评论,AI 接口平均响应 3 秒,结果评论接口的吞吐直接从每秒两千掉到每秒不到两百,线程池被打满,数据库连接也被堆积的请求耗尽。同步调用的本质问题在于,你在用户请求的关键路径上挂了一个外部慢依赖,而且这个依赖的延迟不可控。
改成异步之后,我同样压了一遍:提交接口的 P95 从 6 秒降到 80 毫秒,吞吐恢复到了同步版本的十倍以上。AI 审核线程池单独监控的话,高峰期会堆积,但堆积不会拖垮主接口,只会让审核结果晚几十秒出来,用户完全能接受。这个对比让我真正理解了那句话:外部 AI 调用,永远不要放在请求的关键路径上。
5.2 限流与队列压力的平衡
异步化之后,新的问题变成了:AI 线程池里的任务积压。有一段时间运营活动引流,评论量暴涨,AI 审核队列积压了几万个任务,审核延迟从几秒恶化了到十几分钟。积压带来一个连锁反应:很多用户发布的评论迟迟没有状态更新,前端一直显示“审核中”,他们以为平台瘫了,开始重复提交,又增大了队列压力。
解决办法是三层配合。第一,AI 调用方按模型接口的 QPS 上限做限流,多余的请求直接返回“忙”,任务进入本地队列等待;第二,本地队列用有界队列,满了之后触发快速降级,新评论走纯规则审核,避免无限积压;第三,给 AI 审核任务设置最大排队时间,超过两分钟还没排上,自动降级处理并记录标记,后面补审。
这个平衡的核心是:审核任务的积压是有边界的,系统在任何情况下都不能无限堆任务。宁可让一部分评论先放行再补审,也不能让整个审核链路被拖到不可用。这和水坝泄洪是一个道理,你得保留溢洪道。
5.3 误判案例分析:被拦错的和被漏掉的
上线一段时间后,我发现误判才是审核系统最需要关注的问题。举两个真实案例。第一个是被误拦的:“这篇教程里的配置我照着做了,跑不起来,真是服了。”这条评论里有“真是服了”这种看似阴阳怪气的表达,AI 给了 REVIEW,人工看完发现其实用户在认真反馈问题,最后放行了。这种用户体验非常差,他明明在提意见,结果提示“审核中”,运营还要多花人工去看。
第二个是漏掉的:“有没有好心人私我一下,懂的都懂。”这句话没有任何敏感词,AI 判断为正常评论,但结合上下文,这是典型的绕过平台外链监管的引流话术。后来我在规则层加了一条:评论中出现“私我”“加我”“懂的都懂”等引路边坡词,直接降级为人工复审,不再依赖 AI 判断。
这两个案例给我的教训是:AI 审核的输出不能直接当结论用,它必须和规则层的结果合并。我的最终判定逻辑是“规则层 BLOCK 优先,规则层 PASS 且 AI 层无异议才 PASS,二者冲突取更严的一档”。这个设计可能牺牲了一点通过率,但保证了底线不破。
6. 监控指标与复盘:审核系统上线后看什么
6.1 核心指标定义
系统上线之后,必须用数据说话,而不是凭感觉调阈值。我维护的核心指标有这样几个:
| 指标 | 定义 | 用途 |
|---|---|---|
| 审核通过率 | PASS 数 / 总评论数 | 过低说明策略过严,过高说明可能漏判 |
| AI 调用成功率 | 成功响应 / 总调用次数 | 反映外部接口稳定性 |
| P95 审核延迟 | 从提交到状态更新完成 | 用户端“审核中”的体验 |
| 误拦率 | 人工复审中最终放行的比例 | 反映 AI 和规则层的过度拦截情况 |
| 漏放率 | 上线后被举报且确属违规的比例 | 底线安全指标 |
| 自动回复触发率 | 触发回复数 / 可回复评论数 | 反映自动回复的覆盖面 |
这些指标每周复盘一次。我发现最容易被忽略的是误拦率,因为它看起来是“安全的错误”——反正拦错了也不会出安全事故,但它真实伤害了用户发言意愿。有一次我把某条规则放宽之后,误拦率从 8% 降到了 3%,但漏放率只上升了 0.2 个百分点,这个交换非常值得。
6.2 人工复审通道的兜底价值
最后说说人工复审这个兜底通道。很多开发者在设计系统时会天然倾向于追求“全自动”,希望 AI 搞定一切,不要人工参与。但我的真实体会是,评论审核这件事,永远需要保留一个人工介入的出口,原因有三:第一,AI 判断错误的边界情况必然存在,平台要能纠正;第二,用户投诉“我的评论被误删”时,你需要一个机制快速复核并给出结论,这个机制不能完全依赖模型自证;第三,规则更新和阈值调节都需要人工定期检查效果,否则系统会慢慢退化。
我建的是一个轻量复审后台:只展示标记 REVIEW 的评论,显示 AI 风险分和判定理由,操作按钮就两个:放行、拦截。运营同事每天花半小时处理一批即可。后台数据会回流到审核日志表,定期用来评估 AI 的判断质量,发现某种类型的误判集中出现,就去调整对应轮规则或者改进 prompt。
我个人的真切体会是,审核系统做到最后,拼的不是模型多厉害,而是工程上如何把外部模型能力和本地规则、人工兜底这三层揉在一起,并且让每一层都只做自己擅长的事。这个架构跑了大半年,审核通过率稳定在 93% 左右,AI 调用成本可控,评论区整体质量明显比其他同期平台要好。如果你也正准备做类似功能,建议从异步化、分级处置、降级策略这三件事入手,先把骨架搭稳,再逐步让 AI 参与更多环节。