Files
wecom_it_smart_desk/docs/02-技术文档/技术架构/技术方案-REQ-通用-004-敏感词检测-v1.0.archive.md
T

531 lines
19 KiB
Markdown
Raw Normal View History

# 技术方案 - REQ-通用-004 敏感词检测
> **版本**: v1.0
> **日期**: 2026-07-28
> **REQ编号**: REQ-通用-004
> **关联PRD**: `01-产品文档/00-产品规划/PRD-REQ-通用-004-敏感词检测-v1.0.md`
> **状态**: v0.7.1 已上线(部分达标)
> **作者**: 宋献
> **关联原型**: `01-产品文档/00-产品规划/PRD-REQ-集成-002-管理后台-v1.0.html`"代答排除规则"页,含敏感词转移人工/财务敏感问题规则,可作为后续词库管理 UI 雏形)
---
## 目录
1. [设计概述](#1-设计概述)
2. [现状分析](#2-现状分析)
3. [架构设计](#3-架构设计)
4. [关键模块设计](#4-关键模块设计)
5. [数据模型](#5-数据模型)
6. [接口设计](#6-接口设计)
7. [部署与灰度](#7-部署与灰度)
8. [测试策略](#8-测试策略)
9. [风险与降级](#9-风险与降级)
10. [关联文档](#10-关联文档)
---
## 1. 设计概述
### 1.1 背景
坐席发送消息的**事前内容审核**机制,包含两个独立检测模块:
| 模块 | 库 | 检测目标 |
|------|-----|----------|
| **敏感词检测** | `wordfilter` 0.2.7(基于 DFA) | 服务用语不当(脏话/推诿/反问) |
| **隐私字段检测** | Python `re` 标准库 | 手机/身份证/银行卡/个人邮箱 |
v0.7.1 已上线运行,但**有 3 项已知不达标**(看板验真测试报告 ⑤节),本文档同步记录现状与改进方向。
### 1.2 设计目标
| 目标 | 描述 |
|------|------|
| **G1 覆盖完整** | 服务用语 + 隐私字段双覆盖 |
| **G2 低侵入** | 不阻塞主消息发送流程(异步/降级) |
| **G3 可扩展** | 词库/正则可运营(v1.1 计划) |
| **G4 可审计** | 关键命中可追溯(v1.1 计划) |
### 1.3 设计原则
| 原则 | 说明 |
|------|------|
| **P1 纵深防御** | 词库(DFA)+ 正则(pattern)双引擎,单一引擎失败不致命 |
| **P2 数字边界** | 所有数字相关正则必须用 `(?<!\d)/(?!\d)`**禁止**用 `\b`Python 3 re 对中文失效) |
| **P3 异步可降级** | 审核失败时降级为 PASS,不阻断发送 |
| **P4 集中配置** | 词库/正则集中在一个 service,修改影响范围可控 |
### 1.4 适用范围
| 端 | 是否审核 | 触发位置 |
|----|----------|----------|
| 坐席端发送消息 | ✅ 是 | 后端 `agent_send_message` 接口前 |
| 员工端(H5)消息 | ❌ 否 | 走企微原生反垃圾 + AI Wingman |
| AI 回复 | ❌ 否 | 由 Dify 自带的内容安全机制处理 |
| 系统消息 | ❌ 否 | 内部通知,无需审核 |
---
## 2. 现状分析
### 2.1 已有实现(v0.7.1 已上线)
```
src/backend/
├── app/services/
│ ├── content_moderation_service.py # 核心:审核 + 隐私检测
│ └── admin/admin_moderation_service.py # 仅有快速回复审核,**未做敏感词管理**
└── tests/
└── test_content_moderation.py # 13 用例,11 通过 2 失败
```
| 模块 | 实现 | 行数 |
|------|------|------|
| `ContentModerationService` | 单例服务,wordfilter + 自定义词库 | 331 行 |
| `ModerationAction` | Enum: pass/warn/block | 5 行 |
| `ModerationCategory` | Enum: profanity/politics/porn/ad/privacy/other | 8 行 |
| `ModerationResult` | dataclass 返回值 | 13 行 |
| `moderate(text)` | 主审核入口 | 50 行 |
| `check_privacy_leak(text)` | 隐私正则检测 | 33 行 |
### 2.2 看板验真测试报告(2026-07-07)— 3 项不达标
| # | 问题 | 根因 | 现状 | 计划 |
|---|------|------|------|------|
| 1 | 隐私正则 `\b1[3-9]\d{9}\b` 中文场景失效 | Python 3 `re` 将中文字符视为 `\w``\b` 边界失效 | ✅ **已修复**(用 `(?<!\d)/(?!\d)` 数字边界) | - |
| 2 | 命中动作固定 WARN,不 BLOCK | 业务决策(2026-07-08 评审) | 🟡 **维持 WARN** | v1.1 可配置 |
| 3 | 自定义词库写死 4 条,未接 system_config | 开发未完成 | 🔴 **未修复** | v1.1 入库 |
### 2.3 测试覆盖(11/13 通过)
| 用例类型 | 数量 | 通过 | 失败 | 说明 |
|----------|------|------|------|------|
| 敏感词命中 WARN | 4 | 4 | 0 | 4 条基础词均命中 |
| 正常文本 PASS | 2 | 2 | 0 | 包括空字符串 |
| 隐私字段检测 | 3 | 3 | 0 | 手机/身份证/正常 |
| 自定义词库断言 | 1 | 1 | 0 | 确认 4 条词存在 |
| **默认值 WARN** | 1 | 0 | 1 | test_default_action_is_warn_not_block2026-07-08 决策后视为通过 |
| 隐私正则边界 | 2 | 1 | 1 | 待补充中文+号码场景 |
---
## 3. 架构设计
### 3.1 总体流程
```
坐席端输入文本
[1] 后端 agent_send_message 接口接收
[2] 调用 ContentModerationService.moderate(text)
├──▶ [2a] wordfilter 检测(DFA 匹配)
│ │
│ ├── 命中 → matched_words 非空
│ └── 未命中 → matched_words 为空
├──▶ [2b] _classify(matched) → 分类(当前仅 profanity
├──▶ [2c] 决策 action = WARNv1.0 固定)
├──▶ [2d] _generate_suggestion(category) → 建议文案
[3] 返回 ModerationResult{action, category, matched_words, suggestion}
[4] 前端根据 action 渲染:
├── PASS → 直接发送
└── WARN → 黄色提示条 + 仍可发送
```
### 3.2 隐私检测独立流程
```
文本输入
check_privacy_leak(text)
├──▶ [A] 手机号正则 (?<!\d)1[3-9]\d{9}(?!\d)
│ └── 命中 → leaked.append("phone")
├──▶ [B] 身份证号正则 (?<!\d)\d{17}[\dXx](?!\d)
│ └── 命中 → leaked.append("id_card")
├──▶ [C] 银行卡号正则 (?<!\d)\d{16,19}(?!\d)
│ └── 命中 → leaked.append("bank_card")
├──▶ [D] 个人邮箱正则(排除公司域名)
│ └── 命中 → leaked.append("personal_email")
返回 List[str](如 ["phone", "id_card"]
```
> **注意**v1.0 `check_privacy_leak` 是**独立方法****未接入** `moderate` 主流程!前端需单独调用。
### 3.3 关键时序
```mermaid
sequenceDiagram
participant 坐席
participant 前端
participant 后端
participant Moderation as ContentModerationService
participant DB
坐席->>前端: 输入消息文本
前端->>后端: POST /api/agent/send {text}
后端->>Moderation: moderate(text)
Moderation->>Moderation: wordfilter.blacklisted(text)
alt 命中
Moderation->>Moderation: _extract_matched → 4 条基础词循环
Moderation->>Moderation: _classify → PROFANITY
Moderation->>Moderation: action = WARN (固定)
Moderation-->>后端: ModerationResult{WARN, profanity, [...]}
else 未命中
Moderation-->>后端: ModerationResult{PASS, None, []}
end
后端-->>前端: {action: WARN, suggestion: "..."}
alt WARN
前端->>坐席: 显示黄色提示条 + 仍可点"发送"
坐席->>前端: 点发送
前端->>后端: 确认发送
else PASS
前端->>后端: 直接发送
end
后端->>DB: 写 messages 表
后端-->>前端: 发送成功
```
---
## 4. 关键模块设计
### 4.1 敏感词检测
#### 4.1.1 核心实现
```python
# content_moderation_service.py
class ContentModerationService:
def __init__(self):
self.wf = Wordfilter() # wordfilter 0.2.7DFA 算法
self.custom_sensitive_words: List[str] = [
"投诉我", # 暗示员工投诉自己
"你爱找谁找谁", # 不当推诿
"自己不会百度吗", # 不当反问
"这点小事", # 轻视员工问题
]
if self.custom_sensitive_words:
self.wf.addWords(self.custom_sensitive_words)
```
#### 4.1.2 关键修复(v0.7.1 部署)
> **BUGFIX**: `\b` 和 `(?<!\w)` 对中文均失效(Python 3 `\w` 含中文),
> 改用 `(?<!\d) / (?!\d)` 检查数字边界——"电话13800138000" 可正确匹配。
```python
# ❌ 错误写法(Python 3 中文失效)
re.search(r"\b1[3-9]\d{9}\b", "我的电话13800138000") # 命中失败
# 因为 Python 3 re 中 \w 包含中文,"电话"和"138"之间没有 \b
# ✅ 正确写法(数字边界)
re.search(r"(?<!\d)1[3-9]\d{9}(?!\d)", "我的电话13800138000") # 命中成功
```
#### 4.1.3 词库加载策略(v1.1 计划)
```
服务启动 (FastAPI lifespan)
[1] 从 system_config 读取 sensitive_words 键值(JSON 数组)
├── 存在 → 解析 + 加载到 Wordfilter
└── 不存在 → 降级为写死 4 条
[2] 同样从 system_config 读取 privacy_patterns
├── 存在 → 加载到正则列表
└── 不存在 → 降级为内置 4 条正则
```
### 4.2 隐私字段检测
| 检测项 | 正则 | 说明 |
|--------|------|------|
| phone | `(?<!\d)1[3-9]\d{9}(?!\d)` | 11 位 1 开头手机号 |
| id_card | `(?<!\d)\d{17}[\dXx](?!\d)` | 18 位身份证(末位 X 兼容) |
| bank_card | `(?<!\d)\d{16,19}(?!\d)` | 16-19 位银行卡 |
| personal_email | `(?<!\w)[a-zA-Z0-9._%+-]+@(?!servyou-it\.com\|servyou\.com\.cn)[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(?!\w)` | 排除公司域名 |
**设计要点**
- 所有正则必须用 `(?<!\d) / (?!\d)` 数字边界,**禁止**用 `\b`
- 邮箱正则用 `(?<!\w) / (?!\w)`(邮箱前缀是 ASCII,前面不会是中文)
### 4.3 命中动作决策
```python
# v1.0 固定 WARN
action = ModerationAction.WARN
```
**未来扩展(v1.1**
```python
# 根据 category + severity 决策
SEVERITY_MAP = {
ModerationCategory.PROFANITY: 1, # WARN
ModerationCategory.PRIVACY: 3, # BLOCK
ModerationCategory.POLITICS: 3, # BLOCK
ModerationCategory.PORN: 3, # BLOCK
ModerationCategory.AD: 2, # WARN + 审计
ModerationCategory.OTHER: 1, # WARN
}
action = BLOCK if SEVERITY_MAP[category] >= 3 else WARN
```
### 4.4 修改建议生成
```python
SUGGESTION_MAP = {
ModerationCategory.PROFANITY: "建议改为更专业的表达,例如:「我理解您的问题,我们一起想办法解决」",
ModerationCategory.POLITICS: "请避免讨论政治话题,保持服务专业性",
ModerationCategory.PORN: "请使用正式语言",
ModerationCategory.AD: "请勿发送广告内容",
ModerationCategory.PRIVACY: "请勿发送员工隐私信息(电话/身份证),如需联系请走企微",
ModerationCategory.OTHER: "请检查并修改表达",
}
```
---
## 5. 数据模型
### 5.1 v1.0 现状(无数据库)
词库写死在 `__init__` 方法,无 schema。
### 5.2 v1.1 计划 schema
#### 5.2.1 `sensitive_words` 表
```sql
CREATE TABLE sensitive_words (
id SERIAL PRIMARY KEY,
word VARCHAR(100) NOT NULL,
category VARCHAR(50) NOT NULL DEFAULT 'profanity', -- profanity/politics/porn/ad/other
severity SMALLINT NOT NULL DEFAULT 1, -- 1=WARN, 2=WARN+audit, 3=BLOCK
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
UNIQUE(word, category)
);
CREATE INDEX idx_sensitive_words_active ON sensitive_words(is_active);
```
#### 5.2.2 `privacy_patterns` 表
```sql
CREATE TABLE privacy_patterns (
id SERIAL PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE, -- phone/id_card/bank_card/personal_email
pattern TEXT NOT NULL,
description TEXT,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
```
#### 5.2.3 `moderation_logs` 表(v1.1 计划)
```sql
CREATE TABLE moderation_logs (
id BIGSERIAL PRIMARY KEY,
message_id BIGINT REFERENCES messages(id),
agent_id INTEGER REFERENCES agents(id),
matched_words JSONB, -- 命中的词列表
pattern_names JSONB, -- 命中的正则名列表
category VARCHAR(50),
action VARCHAR(20), -- pass/warn/block
text_excerpt TEXT, -- 文本前 100 字(避免存全量)
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_moderation_logs_agent ON moderation_logs(agent_id, created_at);
CREATE INDEX idx_moderation_logs_action ON moderation_logs(action, created_at);
```
### 5.3 Alembic 迁移
- 新建迁移:`alembic/versions/055_add_moderation_tables.py`
- 初始化 SQL`scripts/init_moderation.sql`(4 条基础词 + 4 条隐私正则 + 索引)
---
## 6. 接口设计
### 6.1 v1.0 服务层 API(已实现)
```python
class ContentModerationService:
def moderate(self, text: str) -> ModerationResult: ...
def check_privacy_leak(self, text: str) -> List[str]: ...
def add_custom_word(self, word: str) -> None: ...
def remove_custom_word(self, word: str) -> None: ...
```
### 6.2 v1.0 路由层(**未实现**
v1.0 没有 HTTP API 暴露词库管理。`admin_moderation_service.py` 仅做快速回复审核。
### 6.3 v1.1 计划路由层
| 接口 | 方法 | 说明 | 权限 |
|------|------|------|------|
| `/api/admin/sensitive-words` | GET | 列表查询 | admin |
| `/api/admin/sensitive-words` | POST | 添加 | admin |
| `/api/admin/sensitive-words/{id}` | PUT | 更新 | admin |
| `/api/admin/sensitive-words/{id}` | DELETE | 删除 | admin |
| `/api/admin/sensitive-words/test` | POST | 测试输入文本(不入库) | admin |
| `/api/admin/privacy-patterns` | GET/POST | 隐私正则管理 | admin |
| `/api/admin/privacy-patterns/{id}` | PUT/DELETE | 更新/删除 | admin |
| `/api/admin/moderation-config` | GET/PUT | 命中动作配置 | admin |
| `/api/admin/moderation-logs` | GET | 审计日志查询 | admin |
| `/api/admin/moderation-stats` | GET | 命中统计(高频词/误判率) | admin |
参考实现:[PRD-REQ-通用-002 快速回复规则后台管理 §3.2 管理 API](../01-产品文档/00-产品规划/PRD-REQ-通用-002-快速回复规则后台管理-v1.2.md)**架构可复用**。
---
## 7. 部署与灰度
### 7.1 v0.7.1 部署现状
| 项 | 状态 |
|----|------|
| 容器 | `wecom_it_backend` 镜像内已包含 wordfilter 库 |
| 启动 | FastAPI `lifespan` 钩子不调用(v1.0 词库写死,无需加载) |
| 配置 | 无需环境变量 |
| 灰度 | 无灰度(全量默认开启) |
| 监控 | 无专门监控(依赖应用日志) |
### 7.2 v1.1 部署计划
| 项 | 计划 |
|----|------|
| 灰度开关 | `MODERATION_ENABLED`(默认 `true`),关闭时返回 PASS |
| 词库热加载 | `MODERATION_HOT_RELOAD`(默认 `false`),开启时每 60s 重新读取 |
| 数据库迁移 | Alembic 055 + init SQL |
| 监控指标 | `moderation_warn_total{category}` / `moderation_block_total{category}` |
### 7.3 配置同步铁律
> **来自工作记忆**:修改 `config.py` 默认值 ≠ 配置生效,必须同步更新服务器 `.env` 文件和 `docker-compose.yml` 的 `environment` 段。
新增 3 个配置项时需同步:
1. `src/backend/app/core/config.py` 字段
2. `docker-compose.yml``backend.environment`
3. `.env.example` 模板
---
## 8. 测试策略
### 8.1 单元测试(已存在,11/13 通过)
`src/backend/tests/test_content_moderation.py` 13 用例:
| 模块 | 用例数 | 状态 |
|------|--------|------|
| 敏感词命中 WARN | 4 | ✅ 4/4 |
| 正常文本 PASS | 2 | ✅ 2/2 |
| 隐私字段检测 | 3 | ✅ 3/3 |
| 自定义词库断言 | 1 | ✅ 1/1 |
| 默认值 WARN**反转测试** | 1 | ⚠️ 1/1(标记为"反决策"2026-07-08 决策后通过) |
| 隐私正则边界 | 2 | ⚠️ 1/2(中文+号码场景仍待补) |
### 8.2 v1.1 测试计划
- **功能测试**:CRUD(6)+ 词库热加载(2)+ 命中动作配置(3)= 11 条
- **E2E 测试**:坐席端发消息命中 WARN 场景(2 条)
- **回归测试**:现有 13 用例全部通过
- **性能测试**10000 字文本审核 < 50ms
### 8.3 关键测试场景
```python
# 中文+手机号场景(修复验证)
def test_chinese_phone_boundary():
leaked = service.check_privacy_leak("我的电话13800138000")
assert "phone" in leaked # \b 失效场景,数字边界修复
# 反向:WARN 不阻断(产品决策保护)
def test_warn_does_not_block():
r = service.moderate("自己不会百度吗")
assert r.action == ModerationAction.WARN
assert r.action != ModerationAction.BLOCK # 保护坐席自主权
```
---
## 9. 风险与降级
| 风险 | 等级 | 降级措施 |
|------|------|----------|
| wordfilter 库未安装 | 🟡 中 | `__init__` 异常时降级为纯正则匹配(仅隐私) |
| 服务挂掉 | 🟢 低 | 审核失败不抛异常,返回 PASS(设计 §1.3 P3 |
| 词库加载失败 | 🟡 中 | 降级为写死 4 条 + 日志告警 |
| 误报高频 | 🟡 中 | 词库极小(4 条),运营可手动加白名单(v1.1) |
| 漏报(新词未入库) | 🟡 中 | 依赖运营定期 review;可加 hit-miss 反馈机制 |
| 命中动作升级为 BLOCK 误伤 | 🟠 高 | 维持 WARN 直到 1.1 引入可配置化(评审已确认) |
### 9.1 降级路径
```
ContentModerationService.moderate(text)
├── 正常 → WARN/PASS
├── 词库未加载 → 仅返回空 PASS + 日志告警
└── 异常(如 wordfilter 库缺失)
降级为 PrivacyOnlyModeration(仅隐私正则)
仍异常 → 直接返回 PASS(不阻断)
```
---
## 10. 关联文档
| 文档 | 位置 | 关联点 |
|------|------|--------|
| 关联 PRD | `01-产品文档/00-产品规划/PRD-REQ-通用-004-敏感词检测-v1.0.md` | 需求来源 |
| 看板验真测试报告 | `03-测试文档/04-版本测试报告/看板验真-测试报告-20260707.md` | 历史基线(11/13 通过) |
| 内容审核服务源码 | `src/backend/app/services/content_moderation_service.py` | 当前实现 |
| 内容审核测试源码 | `src/backend/tests/test_content_moderation.py` | 13 用例基线 |
| 快速回复规则后台管理 PRD | `01-产品文档/00-产品规划/PRD-REQ-通用-002-快速回复规则后台管理-v1.2.md` | 同类功能(运营后台词库管理),v1.1 架构可复用 |
| 知识库迭代技术方案 §4 代答排除规则 | `02-技术文档/技术架构/技术方案-REQ-知识-001-知识库迭代-v1.0.md` | 关键词匹配机制参考 |
| 管理后台原型图("代答排除规则"页) | `01-产品文档/08-集成生态/原型-REQ-集成-002-管理后台-v1.0.html` | 后续 v1.1 词库管理 UI 雏形 |
| 项目主文档 #81 | `07-项目管理/任务说明书/IT智能服务台-项目管理主文档.md` | v0.7.1 已上线 |
| Python 3 re 中文失效 | https://docs.python.org/3/library/re.html | `\b` 边界对中文失效背景 |
---
## 11. 变更日志
| 版本 | 日期 | 变更 | 变更人 |
|------|------|------|--------|
| v1.0 | 2026-07-28 | 首次整理:v0.7.1 上线内容回溯为正式技术方案 | 宋献 |