mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-16 15:43:13 +08:00
176 lines
6.2 KiB
Go
176 lines
6.2 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
|
)
|
|
|
|
const (
|
|
ContentModerationEngineOpenAI = "openai"
|
|
ContentModerationEngineTypeSafe = "typesafe"
|
|
)
|
|
|
|
type ContentModerationEngineConfig struct {
|
|
BaseURL string `json:"base_url"`
|
|
Model string `json:"model"`
|
|
ProxyID *int64 `json:"proxy_id,omitempty"`
|
|
APIKeys []string `json:"api_keys,omitempty"`
|
|
TimeoutMS int `json:"timeout_ms"`
|
|
RetryCount int `json:"retry_count"`
|
|
Thresholds map[string]float64 `json:"thresholds"`
|
|
}
|
|
|
|
type UpdateContentModerationEngineInput struct {
|
|
BaseURL *string `json:"base_url"`
|
|
Model *string `json:"model"`
|
|
ProxyID *int64 `json:"proxy_id"`
|
|
APIKey *string `json:"api_key"`
|
|
APIKeys *[]string `json:"api_keys"`
|
|
APIKeysMode string `json:"api_keys_mode"`
|
|
DeleteAPIKeyHashes *[]string `json:"delete_api_key_hashes"`
|
|
ClearAPIKey bool `json:"clear_api_key"`
|
|
TimeoutMS *int `json:"timeout_ms"`
|
|
RetryCount *int `json:"retry_count"`
|
|
Thresholds *map[string]float64 `json:"thresholds"`
|
|
}
|
|
|
|
type ContentModerationEngineMeta struct {
|
|
Engine string `json:"engine"`
|
|
Model string `json:"model"`
|
|
RulesVersion string `json:"rules_version"`
|
|
SkippedImages int `json:"skipped_images"`
|
|
}
|
|
|
|
func moderationEngine(engine string) string {
|
|
if engine == "" {
|
|
return ContentModerationEngineOpenAI
|
|
}
|
|
return engine
|
|
}
|
|
|
|
func validModerationEngine(engine string) bool {
|
|
return engine == ContentModerationEngineOpenAI || engine == ContentModerationEngineTypeSafe
|
|
}
|
|
|
|
func moderationEngineDefaults(engine string) *ContentModerationEngineConfig {
|
|
p := &ContentModerationEngineConfig{BaseURL: defaultContentModerationBaseURL, Model: defaultContentModerationModel, TimeoutMS: 3000, RetryCount: 2, Thresholds: ContentModerationDefaultThresholds()}
|
|
if engine == ContentModerationEngineTypeSafe {
|
|
p.BaseURL, p.Model = "https://api.typesafe.ai", "jev-latest"
|
|
}
|
|
return p
|
|
}
|
|
|
|
func (cfg *ContentModerationConfig) engineProfile(engine string) *ContentModerationEngineConfig {
|
|
var p ContentModerationEngineConfig
|
|
if engine == ContentModerationEngineTypeSafe {
|
|
if cfg.TypeSafe == nil {
|
|
return moderationEngineDefaults(engine)
|
|
}
|
|
p = *cfg.TypeSafe
|
|
} else {
|
|
p = ContentModerationEngineConfig{BaseURL: cfg.BaseURL, Model: cfg.Model, ProxyID: cfg.ProxyID, APIKeys: cfg.apiKeys(), TimeoutMS: cfg.TimeoutMS, RetryCount: cfg.RetryCount, Thresholds: cfg.Thresholds}
|
|
}
|
|
p.ProxyID = cloneInt64Ptr(p.ProxyID)
|
|
p.APIKeys = append([]string(nil), p.APIKeys...)
|
|
p.Thresholds = cloneFloatMap(p.Thresholds)
|
|
return &p
|
|
}
|
|
|
|
func (cfg *ContentModerationConfig) applyEngineProfile(p *ContentModerationEngineConfig) {
|
|
cfg.BaseURL, cfg.Model, cfg.ProxyID = p.BaseURL, p.Model, cloneInt64Ptr(p.ProxyID)
|
|
cfg.APIKey, cfg.APIKeys = "", append([]string(nil), p.APIKeys...)
|
|
cfg.TimeoutMS, cfg.RetryCount = p.TimeoutMS, p.RetryCount
|
|
cfg.Thresholds = cloneFloatMap(p.Thresholds)
|
|
}
|
|
|
|
func (cfg *ContentModerationConfig) effectiveEngine(engine string) *ContentModerationConfig {
|
|
out := cloneContentModerationConfig(cfg)
|
|
out.Engine = moderationEngine(engine)
|
|
out.applyEngineProfile(cfg.engineProfile(out.Engine))
|
|
return out
|
|
}
|
|
|
|
func (s *ContentModerationService) updateEngineProfile(ctx context.Context, cfg *ContentModerationConfig, engine string, input UpdateContentModerationEngineInput) error {
|
|
if !validModerationEngine(engine) {
|
|
return infraerrors.BadRequest("INVALID_CONTENT_MODERATION_ENGINE", "内容审计引擎无效")
|
|
}
|
|
p := cfg.engineProfile(engine)
|
|
defaults := moderationEngineDefaults(engine)
|
|
if input.BaseURL != nil {
|
|
p.BaseURL = strings.TrimSpace(*input.BaseURL)
|
|
if p.BaseURL == "" {
|
|
p.BaseURL = defaults.BaseURL
|
|
}
|
|
}
|
|
if input.Model != nil {
|
|
p.Model = strings.TrimSpace(*input.Model)
|
|
if p.Model == "" {
|
|
p.Model = defaults.Model
|
|
}
|
|
}
|
|
if input.ProxyID != nil {
|
|
p.ProxyID = cloneInt64Ptr(input.ProxyID)
|
|
if *input.ProxyID <= 0 {
|
|
p.ProxyID = nil
|
|
}
|
|
}
|
|
if input.TimeoutMS != nil {
|
|
p.TimeoutMS = *input.TimeoutMS
|
|
}
|
|
if input.RetryCount != nil {
|
|
p.RetryCount = *input.RetryCount
|
|
}
|
|
if input.Thresholds != nil {
|
|
p.Thresholds = mergeContentModerationThresholds(defaults.Thresholds, *input.Thresholds)
|
|
}
|
|
if input.ClearAPIKey {
|
|
p.APIKeys = []string{}
|
|
} else {
|
|
mode := normalizeContentModerationAPIKeysMode(input.APIKeysMode)
|
|
if input.DeleteAPIKeyHashes != nil && mode != contentModerationAPIKeysModeReplace {
|
|
p.APIKeys = deleteModerationAPIKeysByHash(p.APIKeys, *input.DeleteAPIKeyHashes, engine)
|
|
}
|
|
if input.APIKeys != nil {
|
|
if mode == contentModerationAPIKeysModeReplace {
|
|
p.APIKeys = normalizeModerationAPIKeys(*input.APIKeys)
|
|
} else {
|
|
p.APIKeys = normalizeModerationAPIKeys(append(p.APIKeys, *input.APIKeys...))
|
|
}
|
|
}
|
|
if input.APIKey != nil {
|
|
p.APIKeys = normalizeModerationAPIKeys(append(p.APIKeys, *input.APIKey))
|
|
}
|
|
}
|
|
check := cloneContentModerationConfig(cfg)
|
|
check.Engine = engine
|
|
check.applyEngineProfile(p)
|
|
if err := s.validateConfig(ctx, check); err != nil {
|
|
return err
|
|
}
|
|
p = &ContentModerationEngineConfig{BaseURL: check.BaseURL, Model: check.Model, ProxyID: check.ProxyID, APIKeys: check.APIKeys, TimeoutMS: check.TimeoutMS, RetryCount: check.RetryCount, Thresholds: check.Thresholds}
|
|
if engine == ContentModerationEngineTypeSafe {
|
|
cfg.TypeSafe = p
|
|
} else {
|
|
cfg.applyEngineProfile(p)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *ContentModerationService) engineConfigView(cfg *ContentModerationConfig) *ContentModerationConfigView {
|
|
view := s.configView(cfg.effectiveEngine(cfg.Engine))
|
|
view.EngineConfigs = map[string]*ContentModerationConfigView{}
|
|
for _, engine := range []string{ContentModerationEngineOpenAI, ContentModerationEngineTypeSafe} {
|
|
view.EngineConfigs[engine] = s.configView(cfg.effectiveEngine(engine))
|
|
}
|
|
return view
|
|
}
|
|
|
|
func scopedModerationKeyHash(key string, engine ...string) string {
|
|
if len(engine) > 0 && engine[0] == ContentModerationEngineTypeSafe {
|
|
return moderationAPIKeyHash("typesafe\x00" + key)
|
|
}
|
|
return moderationAPIKeyHash(key)
|
|
}
|