c22669c31d
- 新增迁移 000031-000034(登录日志/角色/模型配额/记忆) - 新增包: license/memory/modelquota/assistant,扫描引擎 - 全部功能后端+前端+端到端验证通过(25 包单测)
85 lines
2.1 KiB
Go
85 lines
2.1 KiB
Go
package contentpolicy
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"strings"
|
|
)
|
|
|
|
// OutputRedact 对一条模型响应 JSON(完整响应或 SSE data 载荷)应用全部
|
|
// redact 策略的规则:提取 choices[].message.content / choices[].delta.content
|
|
// 文本并替换敏感信息。与输入侧不同,输出 JSON 是给客户端消费的展示数据,
|
|
// 重编码可接受。返回替换后的字节与是否发生替换。
|
|
func (e *Engine) OutputRedact(payload []byte) ([]byte, bool) {
|
|
if e == nil || len(payload) == 0 {
|
|
return payload, false
|
|
}
|
|
rules := e.outputRules()
|
|
if len(rules) == 0 {
|
|
return payload, false
|
|
}
|
|
var document any
|
|
decoder := json.NewDecoder(bytes.NewReader(payload))
|
|
decoder.UseNumber()
|
|
if decoder.Decode(&document) != nil {
|
|
return payload, false
|
|
}
|
|
changed := false
|
|
redactTree(&document, &changed, rules)
|
|
if !changed {
|
|
return payload, false
|
|
}
|
|
encoded, err := json.Marshal(document)
|
|
if err != nil {
|
|
return payload, false
|
|
}
|
|
return encoded, true
|
|
}
|
|
|
|
// outputRules 返回全部启用策略的 redact 规则(输出侧不区分端点)。
|
|
func (e *Engine) outputRules() []compiledRule {
|
|
current := e.current.Load()
|
|
if current == nil {
|
|
return nil
|
|
}
|
|
var rules []compiledRule
|
|
for _, policy := range current.policies {
|
|
if policy.Action == "redact" {
|
|
rules = append(rules, policy.rules...)
|
|
}
|
|
}
|
|
return rules
|
|
}
|
|
|
|
// redactTree 递归遍历 JSON,仅对 content 文本应用规则。
|
|
func redactTree(value *any, changed *bool, rules []compiledRule) {
|
|
switch current := (*value).(type) {
|
|
case map[string]any:
|
|
for key, child := range current {
|
|
local := child
|
|
if strings.ToLower(key) == "content" {
|
|
if text, ok := local.(string); ok {
|
|
next := text
|
|
for _, rule := range rules {
|
|
if rule.expression.MatchString(next) {
|
|
next = rule.expression.ReplaceAllString(next, rule.replacement)
|
|
}
|
|
}
|
|
if next != text {
|
|
*changed = true
|
|
local = next
|
|
}
|
|
}
|
|
}
|
|
redactTree(&local, changed, rules)
|
|
current[key] = local
|
|
}
|
|
case []any:
|
|
for index, child := range current {
|
|
local := child
|
|
redactTree(&local, changed, rules)
|
|
current[index] = local
|
|
}
|
|
}
|
|
}
|