内容安全审核怎么做?

进阶安全场景题约 7 分钟读完

一句话回答

输入和输出都要审核:输入审核拦住违规请求、省下模型调用;输出审核兜住模型被诱导或者自己"说错话"的情况。实现上用规则(关键词、正则)+ 分类模型或审核接口组合(规则快、可解释,模型能理解语义和变体),命中后按风险分级处理:拦截、改写或给出安全回复、提示用户、仅记录。流式输出的难点是内容边生成边展示,要边生成边检测,命中后中断生成并撤回已显示的内容。阈值要在误杀和漏放之间权衡,同时满足所在地区对生成式 AI 服务的监管要求。

详细解析

审核链路

文本
用户输入 → 规则过滤 → 分类模型 / 审核接口 → 模型生成(流式)
                                                  ↓
用户 ← 前端展示 ← 输出审核(规则同步检查 + 模型异步检查,边生成边检测)

所有命中记录 → 审核后台:人工复核、处理申诉、迭代规则和模型
  • 规则:敏感词库用 AC 自动机这类多模式匹配算法,正则识别联系方式、网址等引流内容。匹配前先做归一化:全角半角、大小写、繁简体,去掉夹在中间的符号和空格,应对简单的变体
  • 分类模型或审核接口:识别语义层面的违规(换个说法、隐喻),输出各类别的风险分数。可以用云厂商的内容安全服务、模型厂商提供的审核接口,也可以自己部署开源的安全分类模型
  • 人工复核:模型拿不准的、用户申诉的、高风险的进入人工队列,复核结果反过来用于优化规则和模型
  • 越狱意图的检测也在这条链路上完成,为什么输出审核最关键见越狱

按风险分级处理

级别 例子 处理
高 明显违法、严重暴力或色情 直接拦截,不调用模型;保留证据;多次触发的账号限制使用
中 擦边内容、可能造成伤害的建议 给出安全回复,或引导到正规渠道(如建议咨询医生、律师)
低 轻微不当的用语 正常回答,必要时提示;记录
特殊 流露出自伤倾向 不简单拒绝,给出关怀和求助渠道

流式输出的难点

整段生成完再审核最安全,但用户要多等很久,流式就失去了意义。常见的折中:

  1. 关键词检查在每个片段到达时同步执行,检查范围要往前多取一段,因为敏感词可能被拆在两个片段里
  2. 模型审核按句子或固定长度异步执行,不阻塞输出
  3. 可以让展示比生成慢一点(缓冲最后几十个字),给审核留出时间,减少违规内容在屏幕上停留
  4. 命中后中断上游生成(做法见停止生成),通知前端撤回已显示的内容、替换成提示语;这条消息标记为已拦截,不再放进之后的对话上下文
  5. 生成结束后对全文再做一次审核,全部通过才把消息标记为正常

误杀、漏放和合规

  • 阈值低,漏放少、误杀多;阈值高则相反。不同类别、不同产品的阈值不同,面向未成年人的产品要更严格
  • 用标注好的样本同时评估两类错误率,每次调整规则和阈值都做回归测试
  • 给用户申诉入口,被误杀的样本是优化的重要来源
  • 面向公众提供生成式 AI 服务,要满足所在地区的监管要求。国内在备案、安全评估、生成内容标识等方面都有规定,上线前要和法务、合规团队确认具体义务

代码示例

边生成边审核:关键词同步检查,模型审核异步进行,命中后撤回。hitKeyword、moderate 分别代表敏感词匹配和审核接口,MAX_WORD_LENGTH 是词库中最长的词的长度:

TypeScript
async function* moderatedStream(upstream: AsyncIterable<string>, abort: () => void) {
  let text = ''
  let checkedUntil = 0
  let flagged = false
  const checks: Promise<void>[] = []

  for await (const delta of upstream) {
    text += delta
    // 往前多取一段再匹配,防止敏感词被拆在两个片段之间
    if (flagged || hitKeyword(text.slice(-(delta.length + MAX_WORD_LENGTH)))) {
      abort() // 停止上游生成,不再消耗 token
      yield { type: 'retract' } // 前端收到后把已显示的内容替换成提示语
      return
    }
    yield { type: 'delta', text: delta }

    // 每积累一段完整的句子,异步送审(带上前文作为语境),不阻塞输出
    if (text.length - checkedUntil >= 80 && /[。!?\n]$/.test(text)) {
      const segment = text.slice(Math.max(0, checkedUntil - 100))
      checkedUntil = text.length
      checks.push(
        moderate(segment).then(
          (r) => { if (r.flagged) flagged = true },
          () => { /* 审核服务出错:按产品策略决定放行还是拦截 */ },
        ),
      )
    }
  }

  await Promise.all(checks)
  // 全文终审
  if (flagged || (await moderate(text)).flagged) yield { type: 'retract' }
  else yield { type: 'done' }
}

面试官可能追问

用户的输入被拦截了,要告诉他具体原因吗?

只给出概括性的提示,比如"内容涉及不适宜讨论的话题",不要告诉对方命中了哪个词,否则等于帮攻击者试探规则的边界。后台记录命中的类别和规则,用户申诉时人工复核。

审核接口本身出故障了怎么办?

按风险决定"失败时放行"还是"失败时拦截":面向公众、风险高的场景宁可拦截或只返回预设回复;内部工具可以放行,但要记录下来事后补审。同时降级到只用本地规则,并告警。

除了文本,图片和文件怎么审核?

用户上传的图片走图片审核,图片里的文字要先识别出来再按文本审核;文档提取文本后审核。模型生成的图片同样要审核。知识库里的文档在入库时审核,而不是等到回答时才处理。

流式输出撤回的内容,用户已经看到了怎么办?

撤回只能尽量减少影响,做不到绝对。所以高风险场景可以加大展示缓冲,或者对特定类别的问题关闭流式、整段审核后再展示。撤回后,这条内容也要从前端缓存和会话历史里删除,避免刷新后又出现。

易错点

  • 只审核输入:模型可能被诱导,或者从检索到的资料里带出违规内容
  • 关键词只检查新到的片段,被拆开的敏感词就漏掉了
  • 撤回的消息还留在对话历史里,下一轮又被发给模型
  • 只靠关键词匹配:换个说法、谐音、拆字就能绕过,要和理解语义的模型结合

AI 模拟面试官

用自己的话回答,AI 对照参考答案打分、指出遗漏,再追问,最多 3 轮

登录后就可以和 AI 面试官对练,面试记录也会保存下来。登录

这道题你掌握了吗?

选一个最接近的状态,没掌握的题会出现在"我的进度 · 待复习"里。

学习记录暂存在本机浏览器。登录后自动同步到账号,换设备也能看到。