170 lines
5.9 KiB
Go
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
|
|
}
|