Files
cmhub/docs/moderation.md

106 lines
5.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 内容安全与本地敏感词过滤
> 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. 调上游,成功后写成功记录;失败走既有退点路径。
说明:T-604 已清理早期骨架里的 `moderate_output_*` 钩子和云厂商 provider stub,只保留 `moderate_prompt()` 本地关键词入口。输出审核若后续落地,必须重新定义「命中是否退点」的产品策略,并让对应配置真的生效;在 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 原文写入日志或数据库的新字段。
## 验收
当前实现状态:DONE。已落地 `apps.moderation`、`SensitiveWord` 模型/admin/迁移、`ahocorapy` keyword provider、归一化管线和共享 cache 版本号失效;生成接口已改为 serializer 后先审 prompt,命中时不会下载 `image_url`、不会扣点、不会写调用/流水、不会调上游。
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 范围
- 云内容安全真实厂商接入。
- 输出内容审核与输出命中退点/照扣策略。
- 图片内容审核。
- 脱敏、人工复审、灰度放行。
- 大规模开源词库入库;可提供导入命令,但真实词库数据不进仓库。