# 内容安全与本地敏感词过滤 > 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 范围 - 云内容安全真实厂商接入。 - 输出内容审核与输出命中退点/照扣策略。 - 图片内容审核。 - 脱敏、人工复审、灰度放行。 - 大规模开源词库入库;可提供导入命令,但真实词库数据不进仓库。