Files
cmhub/docs/moderation.md
T

104 lines
4.8 KiB
Markdown
Raw Normal View History

2026-07-06 10:44:13 +08:00
# 内容安全与本地敏感词过滤
> T-604 的实施口径。目标是先做「中文 prompt 本地敏感词快筛」,命中在扣点和调上游之前拦截;云内容安全与输出审核保留接口,不在本任务做实。
## 定位
- 本地敏感词过滤是免费快筛和运营自定义黑名单,不替代合规内容审核。
- T-604 只处理输入 prompt 的关键词拦截;图片审核、输出文本审核、输出图片审核、云厂商内容安全属于后续任务。
- 默认关闭:`MODERATION_ENABLED=false` 时必须 no-op,现有生成接口行为不变。
- 打开后使用 `MODERATION_PROVIDER=keyword`;未配置或配置错误时按 fail-closed 处理。
## 请求时序
生成接口必须按这个顺序执行:
1. DRF serializer 校验基础字段。
2. **先审核 prompt 文本**,不得先下载 `image_url` 或预扣点。
3. prompt 命中敏感词:返回 `400 content_blocked`,不下载图片、不解析别名、不计费、不写 `CallRecord` / `PointsLedger`、不调上游。
4. prompt 通过后,才允许读取 `image_base64` 或下载公网 `image_url`,并继续 SSRF 防护。
5. 别名解析、能力校验、计费规则查询。
6. 预扣点、写 pending 调用和 consume 流水。
7. 调上游,成功后写成功记录;失败走既有退点路径。
说明:当前 `apps/moderation` 骨架里有 `moderate_output_*` 钩子,但 T-604 不启用输出审核。输出审核若后续落地,必须重新定义「命中是否退点」的产品策略,并让 `MODERATION_REFUND_ON_OUTPUT_BLOCK` 真的生效;在 T-604 中不要暴露一个未实现的配置。
## 数据模型
新增 `SensitiveWord` 模型:
| 字段 | 说明 |
| --- | --- |
| `word` | 原始敏感词,唯一或按 category 唯一;不能为空 |
| `normalized_word` | 归一化后的词,用于构建 matcher;保存时生成,便于排查和唯一约束 |
| `category` | 分类,如 `politics` / `violence` / `porn` / `custom` |
| `action` | MVP 只支持 `block`;后续再扩展 `review` / `flag` |
| `is_active` | 是否启用 |
| `created_at` / `updated_at` | 时间戳 |
admin 要求:
- 支持按 `word` / `normalized_word` / `category` 搜索。
- 支持按 `category` / `action` / `is_active` 过滤。
- 修改词库后触发 matcher 版本失效。
## 归一化
匹配前对 prompt 与词库统一归一化:
- Unicode NFKC,把全角转半角。
- 去零宽字符。
- 去空白和常见分隔标点,用于拦截「敏 感 词」「敏-感-词」这类低级绕过。
- 英文字母小写。
- 繁简转换可选;如果 `opencc` 安装或运行失败,跳过繁简,不影响其他归一化。
风险:过度归一化会提高误伤概率。测试必须包含至少一条例外/普通文本样例,避免明显正常内容被误拦。
## 匹配与缓存
- 使用 Aho-Corasick 自动机做多词匹配;`ahocorapy` 作为候选依赖,但实现前必须验证 PyPI 包可用性和 API 形状。
- 禁止每请求重建 matcher。
- 每个 worker 进程内缓存 matcher;生产多 worker 下不能只依赖 Django `post_save` / `post_delete` signal,因为 signal 只影响当前进程。
- 采用共享版本号失效:词库变更后把 `moderation:sensitive_words:version` 写入共享 cache;每次请求只做轻量版本检查,版本不一致时当前进程重建 matcher。
- 生产必须使用共享 cache(DatabaseCache / Redis / Memcached),不能依赖 `LocMemCache` 完成跨 worker 失效。
## 错误与留痕
命中后对外响应:
```json
{
"error": {
"code": "content_blocked",
"message": "输入内容未通过安全审核"
}
}
```
留痕原则:
- 不保存违规 prompt 原文。
- 可记录分类、命中词 ID、请求用户、API Key 前缀、时间、阶段、处理结果。
- MVP 若不建 `ModerationRecord`,至少测试中确认不会把 prompt 原文写入日志或数据库的新字段。
## 验收
T-604 完成前至少覆盖:
- `MODERATION_ENABLED=false` 时生成接口 no-op,既有用例不变。
- `MODERATION_PROVIDER=keyword` 且命中 prompt 时返回 `400 content_blocked`。
- 命中时不扣点、不创建 `CallRecord`、不写 `PointsLedger`、不调用上游 provider。
- 命中时不会下载 `image_url`。
- 归一化命中:空格/分隔符/全半角/零宽字符。
- 繁简归一化若启用则有测试;未安装 `opencc` 时测试跳过或断言 graceful fallback。
- 词库变更后 matcher 版本号变化,当前进程下一次请求能重建 matcher。
- 多 worker 口径在文档和测试中说明:跨进程刷新依赖共享 cache 版本号,不依赖本地 signal。
## 不在 T-604 范围
- 云内容安全真实厂商接入。
- 输出内容审核与输出命中退点/照扣策略。
- 图片内容审核。
- 脱敏、人工复审、灰度放行。
- 大规模开源词库入库;可提供导入命令,但真实词库数据不进仓库。