基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
AIHT
内容
精选全部 AI 动态热点榜AI 日报收藏
分类
模型行业编程产品技术观点开源
更多
Agent 接入
返回首页开发者导航
AIHT
精选 / 资讯详情
8.0
热点
AI SCORE
技术实践2026-10-29 11:33

内容审核实战:异步分流与人工兜底

dev.to · AI#内容审核#异步架构#人工复核
Editor brief · 编辑速览

文章拆解文本与视听内容两条异步审核管线,分别使用NestJS、BullMQ、OpenAI Moderation及AWS消息与识别服务。两条管线共享审核状态、保存模型证据,并将模糊案例交给人工处理。

文章思维导图
Knowledge map
拖拽缩放
AI deep read

AI 深度解读

上传成功,不代表内容可以立刻进入信息流。作者团队把 AI 内容审核设计成一套独立流程:文本和媒体分别异步处理,明确安全的放行,明确违规的拒绝,模糊地带交给人工,公开列表只接收审核通过的内容。

这套方案值得看的地方,是它把模型分数接到了队列、数据库状态、人工后台和线上监控上。但文中的阈值不能直接照搬,父子内容的状态聚合也有一处需要核实的矛盾。

先把「上传成功」和「允许展示」拆开

所有受审内容从 pending 开始,后台处理后进入 approved、under_review 或 rejected。只有 approved 可以公开展示;under_review 必须等待人工决定,拒绝内容则保持隐藏并通知作者。

异步处理可以缩短提交请求的等待时间,公开展示仍然必须等待审核结果。

原有的 active、trash、uploaded 继续表达业务状态,安全状态另存到源记录的 content_moderation_status。账号或帖子处于活跃状态,并不能证明内容适合展示,混用字段会让查询条件和业务判断越来越难维护。

作者还用通用 content 记录串起审核对象,在 moderation_results 保存供应商、risk_score、标签、ai_decision、final_decision,人工处理时记录 reviewed_by。保留机器判断和最终判断,才能知道审核员改了什么。

内容审核状态与公开展示的关系

公开列表在服务端查询时限定 content_moderation_status = approved,不能先返回全部内容再由前端隐藏。否则页面看似过滤了,数据已经离开服务端。

两条异步管线,分别适应文本和媒体

文本已经写进 PostgreSQL,适合从数据库事件启动。Hasura 调用 POST /v1/event/content-moderation,NestJS 创建审核记录,向 BullMQ 投递 MODERATE_TEXT,worker 再请求 OpenAI /v1/moderations。

HTTP 请求不等模型返回。处理器设置并发和速率限制,空文本直接通过,失败任务进入死信队列。Hasura webhook 配置为尝试 3 次、间隔 10 秒、超时 60 秒,用于吸收 NestJS 短暂不可用的情况。

媒体从 S3 的 users/uploads/ 进入,经 SNS、SQS 和 Lambda 调用 Amazon Rekognition。作者将其拆成接收队列和结果队列,分别由评分 Lambda、结果 worker 消费,避免两个函数争抢同一队列后再自行过滤消息。

这里的设计方向合理,但原文把 SQS 描述成每条消息恰好交给一个消费者,表述过强,不能据此省掉重复消息处理。

图片支持 .jpg、.jpeg、.png、.bmp、.webp,调用 DetectModerationLabels 和 RecognizeCelebrities。视频支持 .mp4、.mov,通过 StartContentModeration 启动作业,完成通知进入结果队列,worker 分页调用 GetContentModeration 取全标签。

S3 通知可能早于媒体记录落库,因此 worker 查询媒体记录时重试 3 次、间隔 3 秒。广告缩略图也要审核,未知扩展名则跳过,不能顺手当成安全内容。

部署流程体现了两个函数的职责差异,原文保留如下:

Push to develop
↓
OIDC to AWS
↓
npm ci --omit=dev
↓
Zip the function
↓
Create or update Lambda
↓
Wait + verify

其中要留意 npm ci --omit=dev:部署包安装的是生产依赖。两个 workflow 分别是 deploy-image-moderation.yml、deploy-image-moderation-worker.yml,监听 lambda/image-moderation/**、lambda/image-moderation-worker/**。运行时为 Node.js 22,评分函数约 60 秒、256 MB,结果 worker 约 5 分钟、512 MB。

分数怎样变成决定,才是审核策略的核心

文本没有直接采用 OpenAI 的布尔标记,而是取 category_scores 最大值,转换成 0–100 的风险分数:

OpenAI category_scores
→
Take the maximum score
→
riskScore = round(max × 100)
→
Compare to thresholds
→
approved / under_review / rejected

要看清 riskScore = round(max × 100):这是作者定义的决策输入,不能自动解释为内容违规的概率。

文本低于 40 自动通过,40 至不足 75 进入人工队列,75 及以上拒绝。配置项为 MODERATION_APPROVED_THRESHOLD、MODERATION_REJECTED_THRESHOLD,只保存达到通过边界的类别标签,减少后台噪声。

图片取 Rekognition 标签的最高置信度并四舍五入:低于 75 通过,75 至不足 90 人工审核,90 及以上拒绝,对应 MODERATION_REVIEW_THRESHOLD = 75、MODERATION_REJECT_THRESHOLD = 90。图片保留全部标签及其置信度。

视频还增加时间维度:标签置信度至少 75% 的帧算违规帧;达到 5 帧,或形成持续至少 3 秒的聚集,就满足帧策略。任何标签达到 90% 都拒绝;只有审核区间命中时,满足帧策略也拒绝,否则交给人工;没有符合条件的标签才通过。

这些数字来自作者声称做过的标注样本校准:收集明确安全、模糊、明确违规三类内容,遍历候选阈值,比较误放、误拒和人工工作量。文本样本中,约 40 以下几乎没有人工确认的违规,约 75 以上几乎都有,中间区间集中了多数人工改判。

但原文没有给样本量、具体错误率、队列规模或对照结果。「几乎」究竟是多少,没有说清。视频的采样间隔、重复标签是否重复计帧、3 秒聚集如何计算,也缺少定义。

人工后台因此是必要组成:显示原文或媒体、供应商、分数、标签、人脸名称和视频帧摘要,提供通过与拒绝操作。拒绝后由 POST /v1/event/content-rejected-notify 触发 SendGrid 邮件及 Firebase 站内或推送通知。

上线后还要分别观察文本、图片、视频的 p50/p95 审核延迟、人工队列年龄与 SLA、供应商错误和限流、重试与死信量,以及改判、申诉和误放举报。作者建议每周或每两周检查漂移,必要时重新校准。

我的判断

我认同这套方案把审核做成产品生命周期。与同步等待模型相比,异步方案改善提交体验;与单一阈值直接二选一相比,三段式决策给人工留下了处理歧义的空间。代价是必须维护队列、后台和运营能力,不能只估算 API 调用费。

40/75 和 75/90 是他们团队的配置结果,不是通用安全标准。

OpenAI 类别分数与 Rekognition 置信度并非同一把尺子,不能因为媒体的数字更高,就直接证明策略更严格。原文虽描述了校准过程,但没有提供足够数据让外部团队复核效果,仍需把结论视为该团队的经验。

更需要核实的是父状态聚合。作者要求任一子项仍在 pending 时,父内容不能通过;随后却给出 pending < approved < under_review < rejected,并按最严重状态聚合。照此取最大值,pending + approved 会得到 approved,与前述要求冲突。数据库函数 apply_aggregate_moderation_status 的实际实现没有展示,不能认定它已经处理好了。

另外,「已有结果只允许升级到更严重状态」可以防止迟到消息覆盖结论,却还不足以说明完整幂等性。内容修改后的重新审核、旧结果与新版本的关系、重复拒绝通知如何避免,原文都没有讲清。接入现有产品时,这些比直接复制 Lambda 配置更值得先查。

能拿来做什么

  • 给现有 UGC 产品增加独立审核字段,让公开列表在数据库查询阶段只返回 approved;前端分别展示上传完成、审核中和审核拒绝。
  • 在 NestJS 或其他 Node 服务中,把模型调用移到队列 worker,并保存原始证据、机器决定和人工决定,为后续调参留下依据。
  • 用自己的安全、模糊、违规样本验证候选阈值,同时观察误放、误拒和人工等待时间,不直接继承作者的默认数字。
  • 上线前重点核对多图帖子包含待审附件、内容修改后收到旧结果、消息重复投递三类情况,确保公开展示和通知行为符合预期。
Original source

本文由 AI 通读原文后重新编写,观点部分为站内判断,细节以原文为准,来源 dev.to · AI,原文版权归原作者所有。

阅读原文
上一篇
语音 Agent 如何测试答录机误判
下一篇
Claude 文档问答如何选长上下文与检索

资讯速览

dev.to · AI · 2026-10-29 11:33

AI 评分 8.0 / 10

相关标签

#内容审核#异步架构#人工复核

继续阅读

  • 用本地模型生成可追溯的群聊周报
  • 语音 Agent 如何测试答录机误判
  • Claude 文档问答如何选长上下文与检索
  • 用本地模型解读Java旧代码并生成重构差异
  • 为iOS端侧转写增加模型回退,扩展语言覆盖