背景:内容安全不是一个 if flagged then block 问题
大模型应用上线后,内容安全很快会从”接一个 Moderation API”变成一个生产治理问题。原因并不复杂:用户输入可能包含违规请求、擦边表达、引用材料、投诉文本、新闻片段或教育语境;模型输出也可能在拒答、解释、总结、翻译、客服回复、工具结果复述时触发安全分类器。
如果系统只按 flagged=true 直接阻断,结果往往是两个极端:要么拦不住真正有风险的隐晦表达,要么误杀大量正常内容。OpenAI 的 Moderation 文档也明确提示,category_scores 应当被当作应用策略信号,而不是自动阻断的唯一依据——拒答或安全解释本身也可能因为讨论有害内容而触发标记。
生产级内容安全系统需要处理四件事:
- 在哪里检测:输入、输出、工具结果、上传文件和多模态内容。
- 如何解释分数:类别、置信度、阈值和策略版本。
- 如何降低误杀:灰度、人工复核、申诉和白名单。
- 如何审计演进:策略变更、样本回放、阈值校准和模型升级后的再评估。
核心原理:把 Moderation 拆成分类器、策略和执行动作
Moderation 分类器本质上不是业务规则引擎。它负责给输入或输出打出一组风险信号,例如是否命中某类风险、每个类别的置信度分数、适用的输入类型,以及可能的错误状态。主流服务的能力对比如下:
| 服务 | 覆盖模态 | 核心机制 | 特色能力 |
|---|---|---|---|
| OpenAI Moderation | 文本、图像 | category_scores (0-1) | 生成请求中同步返回输入输出结果 |
| Azure AI Content Safety | 文本、图像 | 多类 harm category + severity level | 可配置 severity threshold |
| Google Cloud Model Armor | 文本 | prompt/response 检查 + 阈值模板 | inspect-only / inspect-and-block 模式 |
| Meta Llama Guard 3 | 文本 | LLM-based 安全分类 | 开源可定制,支持多语言 |
生产系统应把链路拆成三层:
- 分类层:调用模型或安全服务,输出标准化结果。
- 策略层:把分类结果映射成动作——允许、提示改写、人工复核、阻断、账号限流、隐藏输出、仅记录审计。
- 执行层:真正改变业务流程,例如不展示输出、进入复核队列、返回替代提示、打审计日志或触发风控规则。
这种拆分的好处是,分类模型可以升级,业务策略可以灰度,执行动作可以按场景区分。比如同样命中 violence 类别,新闻总结、医学急救、游戏剧情和现实伤害指令的处理方式不应完全相同。
推荐架构:输入、输出、工具结果三段式检测
1. 输入侧:先做轻量准入,再进入模型
输入侧检测的目标不是理解所有上下文,而是尽早发现明显不应进入模型的请求。常见流程:
user_input → normalize / language detect / PII precheck
→ moderation_input → policy_decision
→ allow | rewrite hint | human review | block
→ llm_request
输入检测适合处理明确违规、恶意骚扰、自伤风险、成人内容、仇恨攻击、危险行为指导、Prompt Injection 和高风险敏感信息。对于业务客服、教育、医疗、法律、金融等场景,还需要增加业务自定义类别,例如”诱导退款欺诈""泄露保单隐私""绕过风控规则”。
但输入侧不能过度自信。很多请求单看输入是正常的,结合上下文才有风险;也有很多输入看起来敏感,但属于投诉、举报、新闻、历史、学术或安全教育场景。因此输入侧更适合做明显风险拦截和高风险复核分流。
2. 输出侧:展示前最后一道门
输出检测应放在模型生成之后、内容展示之前。OpenAI 文档中提到,可以在生成请求中同时拿到输入和输出的 moderation scores;Google Model Armor 也强调 prompt 和 response 都可被检查。输出侧检测可以防止模型在复杂对话、长上下文、工具结果、检索内容或多轮追问中生成不适合展示的结果。
输出策略不能简单地”命中就删除”。更合理的决策链:
| 风险等级 | 动作 | 说明 |
|---|---|---|
| 低风险 | 直接展示 | 分数远低于阈值 |
| 中风险 | 展示安全改写 / 追问用户意图 | 可能有擦边内容 |
| 高风险 | 阻断输出,返回拒答 | 明显违规 |
| 不确定 | 进入复核队列,暂时隐藏 | 需人工判断 |
尤其是拒答文本、风险解释、政策说明、新闻摘要、医学急救建议等内容,可能出现”内容本身讨论风险,但意图是安全”的情况。此时需要结合模型响应类型、用户意图、业务场景和阈值策略做二次判断。
3. 工具结果和上传内容:不要只检测用户原始输入
Agent 应用里,风险内容常常来自工具结果,而不是用户输入。例如:
- Web 搜索返回了不适合展示的文本
- 数据库查出了敏感字段
- 代码执行器输出了密钥片段
- 文件解析器读取了违规图文
OpenAI 文档说明,工具调用参数和工具输出进入 conversation content 时可以被 moderation 覆盖,但工具名称、工具描述、工具 schema 或 response-format schema 不在覆盖范围内。
生产系统应把工具结果也纳入内容安全链路:
tool_result → redact / truncate / classify
→ safe_summary_or_block → llm_context
→ output_moderation
这样可以避免模型把外部系统中的风险内容原样复述给用户,也可以给审计日志留下”风险来自哪个工具、哪次调用、哪个字段”的依据。
阈值治理:从 inspect-only 到 block,而不是一次性上线
阈值是内容安全系统的核心配置。OpenAI 的 category_scores 是 0 到 1 的置信度信号;Google Model Armor 的模板支持不同 confidence level 和 enforcement type;Azure AI Content Safety 则提供类别和 severity level 的分层表达。不同供应商的分数语义并不完全等价,因此不能把一个平台的阈值直接复制到另一个平台。
建议用四阶段上线:
阶段一:影子检测
所有请求照常处理,只记录分类结果,不改变用户体验。目标是看真实流量中的命中率、类别分布、语言分布、业务场景分布和高分样本。
阶段二:人工抽样复核
按类别和分数段抽样,例如 0.3-0.5、0.5-0.7、0.7-0.9、0.9+。每个分数段都要标注”应拦截、应放行、应改写、应复核”。这一步会暴露误杀集中在哪些类别和语境。
阶段三:低风险动作灰度
先上线低破坏动作——提示用户改写、隐藏部分内容、进入人工复核、对新账号限流——而不是直接永久封禁。对高置信度、高危类别再启用阻断。
阶段四:策略版本化
每一次阈值、类别映射、动作规则调整都要生成策略版本。日志里必须记录:
{
"policy_version": "safety-policy-2026-07-07-01",
"classifier": "omni-moderation-latest",
"category": "violence",
"score": 0.82,
"threshold": 0.75,
"decision": "human_review",
"surface": "output",
"request_id": "req_..."
}
没有策略版本,就无法解释”为什么昨天放行、今天阻断”,也无法在误杀率升高时快速回滚。
误杀治理:内容安全系统也需要 SLO
内容安全系统的指标不能只看”拦截了多少违规内容”。它至少要同时观察四类指标:
| 维度 | 关键指标 |
|---|---|
| 拦截质量 | 违规样本拦截率、漏放率、人工复核命中率 |
| 误杀成本 | 正常内容被阻断比例、申诉成功率、复核推翻率、重点客户误伤数 |
| 体验影响 | 被阻断后转化率、对话中断率、改写后继续率、客服转人工率 |
| 系统稳定性 | 分类器超时率、降级率、延迟 P95、策略服务错误率 |
安全分类器本身也可能出错。Llama Guard 3 的模型卡明确提醒,部署安全模型可能提升安全性,但也可能增加对良性提示的拒绝;并且 LLM 型 guard 模型会受到训练数据、政策覆盖、多语言能力和对抗攻击的限制。近年的 moderation API 审计研究也指出,商业内容审核 API 可能同时存在 over-moderation 和 under-moderation,尤其在群体相关表达、反击性言论、隐晦仇恨表达和语言变体上更容易出现偏差。
因此,生产系统需要一个误杀复核闭环:被拦截内容进入队列 → 审核员标注真实结果 → 标注结果回灌到阈值校准、规则例外、评测集和回归测试中。
工程落地:一个可运行的策略判断骨架
下面的 TypeScript 示例展示了如何把分类结果转换成业务动作。重点不是具体阈值,而是把分类、策略和执行动作拆开:
type SafetySurface = "input" | "output" | "tool_result";
type ModerationCategory =
| "hate" | "harassment" | "self_harm" | "sexual"
| "violence" | "illicit" | "prompt_injection" | "pii";
type ModerationSignal = {
category: ModerationCategory;
score: number;
flagged: boolean;
};
type SafetyDecision = {
action: "allow" | "rewrite" | "review" | "block";
reason: string;
policyVersion: string;
auditRequired: boolean;
};
const POLICY_VERSION = "safety-policy-2026-07-07-01";
const thresholds: Record<SafetySurface, Partial<Record<ModerationCategory, number>>> = {
input: {
self_harm: 0.55, violence: 0.75, harassment: 0.80,
prompt_injection: 0.65, pii: 0.60,
},
output: {
self_harm: 0.45, violence: 0.70, harassment: 0.75,
sexual: 0.70, illicit: 0.65,
},
tool_result: {
pii: 0.50, illicit: 0.70, prompt_injection: 0.60, violence: 0.75,
},
};
export function decideSafetyAction(
surface: SafetySurface,
signals: ModerationSignal[],
accountRisk: "low" | "normal" | "high"
): SafetyDecision {
const policy = thresholds[surface];
const hits = signals
.filter((s) => s.score >= (policy[s.category] ?? 0.95))
.sort((a, b) => b.score - a.score);
if (hits.length === 0) {
return { action: "allow", reason: "no_policy_threshold_hit",
policyVersion: POLICY_VERSION, auditRequired: false };
}
const top = hits[0];
if (top.category === "self_harm" && top.score >= 0.75) {
return { action: "review",
reason: "high_confidence_self_harm_needs_safe_response_or_review",
policyVersion: POLICY_VERSION, auditRequired: true };
}
if (surface === "output" && top.score >= 0.9) {
return { action: "block",
reason: `high_confidence_${top.category}_in_output`,
policyVersion: POLICY_VERSION, auditRequired: true };
}
if (accountRisk === "high" && top.score >= 0.7) {
return { action: "review",
reason: `risk_adjusted_review_for_${top.category}`,
policyVersion: POLICY_VERSION, auditRequired: true };
}
return { action: "rewrite",
reason: `medium_confidence_${top.category}_requires_safer_response`,
policyVersion: POLICY_VERSION, auditRequired: true };
}
这个骨架体现了三个原则:
- 不同检测面有不同阈值——输入、输出、工具结果各有侧重。
- 高风险类别不一定直接封禁——自伤类进入更谨慎的响应或复核。
- 策略版本始终跟随决策写入日志——可追溯、可回滚。
适用场景
内容安全 Moderation 体系适用于以下场景:
- C 端对话产品:面向公众的聊天机器人、客服机器人、社区问答、教育助手和内容生成工具。
- UGC / 外部内容:支持用户上传图片、文档、网页链接、聊天记录、客服工单等外部内容的应用。
- 高合规行业:涉及未成年人、医疗、金融、保险、法律、招聘、政务等强监管业务。
- Agent 复杂系统:支持工具调用、联网搜索、数据库查询、代码执行或知识库检索的智能体应用。
- 企业级 LLM 平台:需要给运营、合规、安全团队提供完整审计证据链的内部平台。
常见误区
误区一:把 flagged 当作唯一判断
flagged 适合快速发现风险,但不能表达业务语境、用户意图、历史行为和合规差异。生产策略应同时查看类别、分数、检测位置、输入类型、账号风险和场景标签。
误区二:一个阈值覆盖所有业务
同一内容在客服投诉、新闻摘要、学术研究、娱乐创作和未成年人产品中的处理方式不同。阈值应按业务线、用户年龄段、地区法规、内容类型和风险等级拆分。
误区三:只检测用户输入,不检测模型输出
模型可能在长上下文、工具结果、检索片段或多轮诱导中生成不适合展示的内容。只做输入检测会漏掉大量输出侧风险。
误区四:没有人工复核闭环
没有复核样本,阈值就只能靠感觉调。误杀和漏放都需要样本、标签、复盘和回归测试,否则策略升级很容易引入新的业务损伤。
误区五:忽略分类器失败和超时
安全服务本身也可能超时或返回错误。高风险场景可以 fail-closed,低风险场景可以 fail-open + 审计;关键是策略必须显式定义降级行为,而不是让调用异常随机影响用户体验。
上线检查清单
上线前至少检查以下项目:
- 明确了输入、输出、工具结果、上传文件和多模态内容的检测边界。
- 为每个业务场景定义了类别阈值、动作映射和策略版本。
- 支持 inspect-only 灰度,能够在不影响用户的情况下采集真实命中率。
- 有人工复核队列,并能把复核结果回灌到评测集。
- 日志记录
request_id、user_id、surface、category、score、threshold、decision、policy_version。 - 有误杀率、复核推翻率、申诉成功率、阻断率、分类器延迟和错误率看板。
- 为分类器升级、阈值调整和策略发布准备了回放测试。
- 定义了安全服务超时、失败、限流时的降级策略。
- 对敏感日志做了脱敏、最小化保留和访问控制。
总结
LLM 内容安全生产不是接一个 API 就完事的单点方案,而是一个需要分层检测、阈值治理、误杀闭环和持续审计的系统工程。将 Moderation 拆成分类层、策略层和执行层,配合从影子检测到策略版本化的四阶段灰度上线,再辅以人工复核和 SLO 监控,才能在安全与用户体验之间找到可持续的平衡点。