Files

170 lines
5.9 KiB
Go

// Copyright 2022 E99p1ant. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package censor
// Strictness 内容审核严格度档位。
// 审核服务调不松、不可配白名单,可控的只有「命中后拦不拦」,故把拦截从 Pass 判定剥离、改由本档位裁决。
type Strictness string
const (
// StrictnessLax 宽松:仅拦底线维度,误伤最小、漏放最多。
StrictnessLax Strictness = "lax"
// StrictnessStandard 标准:底线 + 高置信度的辱骂/违禁。
StrictnessStandard Strictness = "standard"
// StrictnessStrict 严格:底线 + 全部非正常维度(不限置信度)+ 采信服务 block 建议。
// 严格档 ⊇ 改造前行为(改造前为 !Pass),「至少与改造前一样严」,并非完全等价。
StrictnessStrict Strictness = "strict"
)
// DefaultStrictness 缺省取 strict:部署不应悄悄放宽任何账号,「调松」必须是箱主显式动作;
// 想默认更少误伤可改 Standard。
const DefaultStrictness = StrictnessStrict
// Suggestion 审核服务自身给出的管控建议取值。
const (
SuggestionBlock = "block"
SuggestionReview = "review"
SuggestionPass = "pass"
)
// baselineBlockLabels 底线拦截维度:涉政/暴恐/色情/自定义词库。
// 合规底线:任何档位强制生效、不受置信度约束、不可关闭、不可绕过(见 Decide 最先判定)。
// Customized 归入底线:它是控制台显式配置的词库,要放开应改控制台词库而非调档位。
var baselineBlockLabels = map[ForbiddenType]struct{}{
ForbiddenTypePolitics: {},
ForbiddenTypeTerrorism: {},
ForbiddenTypePorn: {},
ForbiddenTypeCustomized: {},
}
// abuseScoreFloor 标准档下「辱骂」生效所需最低置信度。
// 辱骂误伤最集中(吐槽常被判辱骂),故标准档要求高置信度;严格档不限。阿里云 2.0 只给 riskLevel,下语义为「只拦 high」。
const abuseScoreFloor = 0.9
// levelPolicy 单一档位的拦截规则。
type levelPolicy struct {
// blockLabels 该档位额外拦截的维度 -> 生效最低置信度(0 表示不限)。底线维度不在此表,由 Decide 前置判定。
blockLabels map[ForbiddenType]float64
// blockOnSuggestion 是否采信服务自身的 block 建议:不携带维度、无法收敛置信度,误伤面最大,仅严格档启用。
blockOnSuggestion bool
}
var strictnessPolicies = map[Strictness]levelPolicy{
StrictnessLax: {
blockLabels: map[ForbiddenType]float64{},
blockOnSuggestion: false,
},
StrictnessStandard: {
blockLabels: map[ForbiddenType]float64{
ForbiddenTypeAbuse: abuseScoreFloor,
ForbiddenTypeContraband: 0,
},
blockOnSuggestion: false,
},
StrictnessStrict: {
blockLabels: map[ForbiddenType]float64{
ForbiddenTypeSpam: 0,
ForbiddenTypeAd: 0,
ForbiddenTypeAbuse: 0,
ForbiddenTypeFlood: 0,
ForbiddenTypeContraband: 0,
ForbiddenTypeMeaningless: 0,
ForbiddenTypeHarmful: 0,
},
blockOnSuggestion: true,
},
}
// Decision 对单条内容的最终裁决。
type Decision int
const (
// DecisionAllow 放行。
DecisionAllow Decision = iota
// DecisionBlock 拒绝提交。
DecisionBlock
)
// Blocked 是否应当拒绝提交。
func (d Decision) Blocked() bool { return d == DecisionBlock }
// IsValidStrictness 校验档位取值合法性,供后台保存前校验;
// 非法值一律拒绝写入,避免脏数据把账号静默切到非预期档位。
func IsValidStrictness(s string) bool {
switch Strictness(s) {
case StrictnessLax, StrictnessStandard, StrictnessStrict:
return true
default:
return false
}
}
// ParseStrictness 归一化严格度,未知/空值回退 DefaultStrictness(最严方向);
// 宁可沿用严格判定,也不要因识别失败静默放到最松档。
func ParseStrictness(s string) Strictness {
if !IsValidStrictness(s) {
return DefaultStrictness
}
return Strictness(s)
}
// Decide 按档位裁决一次审核结果,返回裁决与触发拦截的维度(无具体维度时 nil)。
// 判定顺序固定不可调换:底线(无条件)→ 档位维度(带置信度门槛)→ 服务 block 建议(仅严格档)。
// Details 为空时仅开启 blockOnSuggestion 的档位会拦截。
func Decide(strictness string, r *TextCensorResponse) (Decision, *LabelScore) {
if r == nil {
return DecisionAllow, nil
}
policy := strictnessPolicies[ParseStrictness(strictness)]
// 1. 底线:任何档位强制生效、不可关闭。
for i := range r.Details {
detail := &r.Details[i]
if _, ok := baselineBlockLabels[detail.Label]; ok {
return DecisionBlock, detail
}
}
// 2. 档位维度:命中维度且达到置信度门槛。
for i := range r.Details {
detail := &r.Details[i]
scoreFloor, ok := policy.blockLabels[detail.Label]
if !ok {
continue
}
if scoreFloor > 0 && detail.Score < scoreFloor {
continue
}
return DecisionBlock, detail
}
// 3. 服务 block 建议:仅严格档采信。
if policy.blockOnSuggestion && r.ProviderSuggestion == SuggestionBlock {
return DecisionBlock, nil
}
return DecisionAllow, nil
}
// BlockedLabels 返回指定档位最终拦截的全部维度(含底线)。
// 不变量由 policy_test.go 守住(底线维度在每个档位都出现且不重复);设置页文案仍是 i18n 静态文本,未消费本函数。
func BlockedLabels(strictness string) []ForbiddenType {
policy := strictnessPolicies[ParseStrictness(strictness)]
labels := make([]ForbiddenType, 0, len(baselineBlockLabels)+len(policy.blockLabels))
for label := range baselineBlockLabels {
labels = append(labels, label)
}
for label := range policy.blockLabels {
// 底线维度已在上面加入,避免重复展示。
if _, ok := baselineBlockLabels[label]; ok {
continue
}
labels = append(labels, label)
}
return labels
}