Files
qiweimanager-master/config/types.go

561 lines
23 KiB
Go

package config
import (
"strings"
"time"
)
const defaultVisionModel = "qwen3-vl-plus"
const defaultAISystemPrompt = "你是一名企业微信智能客服。"
// CallbackConfig stores local callback and helper API settings.
type CallbackConfig struct {
CallbackURL string `json:"callbackUrl"`
CallbackToken string `json:"callbackToken"`
HTTPPort string `json:"httpPort"`
EnableCallback bool `json:"enableCallback"`
EnableCloudAuth bool `json:"enableCloudAuth"`
FileUploadUrl string `json:"fileUploadUrl"`
DeviceCode string `json:"deviceCode"`
}
// AutoReplyConfig stores the local automatic customer-service settings.
type AutoReplyConfig struct {
Enabled bool `json:"enabled"`
Listen ListenConfig `json:"listen"`
Knowledge KnowledgeConfig `json:"knowledge"`
Retrieval RetrievalConfig `json:"retrieval"`
AI AIConfig `json:"ai"`
Materials MaterialsConfig `json:"materials"`
HumanAssist HumanAssistConfig `json:"humanAssist"`
Collaboration CollaborationConfig `json:"collaboration"`
Handoff HandoffConfig `json:"handoff"`
Identity IdentityConfig `json:"identity"`
ReplyPolicy ReplyPolicyConfig `json:"replyPolicy"`
ReplyStyle string `json:"replyStyle"`
}
type ListenConfig struct {
EnablePrivateChat bool `json:"enablePrivateChat"`
EnableGroupChat bool `json:"enableGroupChat"`
GroupTriggerMode string `json:"groupTriggerMode"`
IgnoreSelfMessage bool `json:"ignoreSelfMessage"`
DeduplicateSeconds int `json:"deduplicateSeconds"`
}
type KnowledgeConfig struct {
Directory string `json:"directory"`
IndexPath string `json:"indexPath"`
SupportedExtensions []string `json:"supportedExtensions"`
TopK int `json:"topK"`
MinScore float64 `json:"minScore"`
AutoRebuildOnStart bool `json:"autoRebuildOnStart"`
}
type RetrievalConfig struct {
RetrievalMode string `json:"retrievalMode"`
EmbeddingIndexPath string `json:"embeddingIndexPath"`
EmbeddingModel string `json:"embeddingModel"`
EmbeddingDimensions int `json:"embeddingDimensions"`
RerankModel string `json:"rerankModel"`
RecallTopK int `json:"recallTopK"`
RerankTopK int `json:"rerankTopK"`
FinalTopK int `json:"finalTopK"`
}
type MaterialsConfig struct {
Directory string `json:"directory"`
IndexPath string `json:"indexPath"`
AutoSendEnabled bool `json:"autoSendEnabled"`
MaxPerReply int `json:"maxPerReply"`
}
type HumanAssistConfig struct {
Enabled bool `json:"enabled"`
WaitSeconds int `json:"waitSeconds"`
AfterHumanReplyDelaySeconds int `json:"afterHumanReplyDelaySeconds"`
SupplementMode string `json:"supplementMode"`
IgnoreLikelyAutoSentEcho bool `json:"ignoreLikelyAutoSentEcho"`
MinimumHumanReplyLengthRunes int `json:"minimumHumanReplyLengthRunes"`
}
type CollaborationConfig struct {
Enabled bool `json:"enabled"`
HumanWaitSeconds int `json:"humanWaitSeconds"`
AfterHumanReplyDelaySeconds int `json:"afterHumanReplyDelaySeconds"`
TakeoverIdleExitSeconds int `json:"takeoverIdleExitSeconds"`
SupplementTarget string `json:"supplementTarget"`
EngineerReturnPolicy string `json:"engineerReturnPolicy"`
}
type AIConfig struct {
Provider string `json:"provider"`
BaseURL string `json:"baseUrl"`
APIKey string `json:"apiKey"`
Model string `json:"model"`
SystemPrompt string `json:"systemPrompt"`
VisionModel string `json:"visionModel"`
VisionBaseURL string `json:"visionBaseUrl"`
VisionAPIKey string `json:"visionApiKey"`
AudioProvider string `json:"audioProvider"`
AudioMode string `json:"audioMode"`
AudioModel string `json:"audioModel"`
AudioBaseURL string `json:"audioBaseUrl"`
AudioAPIKey string `json:"audioApiKey"`
TimeoutSeconds int `json:"timeoutSeconds"`
EnableThinking bool `json:"enableThinking"`
ReplyDetail string `json:"replyDetail"`
Temperature float64 `json:"temperature"`
MaxTokens int `json:"maxTokens"`
}
type HandoffConfig struct {
HumanUserID string `json:"humanUserId"`
HumanConversationID string `json:"humanConversationId"`
MessageTemplate string `json:"messageTemplate"`
CustomerHandoffNotice string `json:"customerHandoffNotice"`
IncludeKnowledgeHits bool `json:"includeKnowledgeHits"`
SendHumanCardToCustomer bool `json:"sendHumanCardToCustomer"`
SendCustomerCardToHuman bool `json:"sendCustomerCardToHuman"`
CardTriggerMode string `json:"cardTriggerMode"`
ManualTriggerKeywords []string `json:"manualTriggerKeywords"`
CardKeywords []string `json:"cardKeywords"`
}
type IdentityConfig struct {
UnknownPolicy string `json:"unknownPolicy"`
UnknownHandoffPolicy string `json:"unknownHandoffPolicy"`
RefreshOnStart bool `json:"refreshOnStart"`
RefreshIntervalMinutes int `json:"refreshIntervalMinutes"`
PageSize int `json:"pageSize"`
InternalNoHandoffReply string `json:"internalNoHandoffReply"`
UnknownNoHandoffReply string `json:"unknownNoHandoffReply"`
InternalUserIDs []string `json:"internalUserIds"`
ExternalUserIDs []string `json:"externalUserIds"`
InternalGroupConversationIDs []string `json:"internalGroupConversationIds"`
InternalGroupIDsByScope map[string][]string `json:"internalGroupConversationIdsByScope"`
InternalUserLabels map[string]string `json:"internalUserLabels"`
ExternalUserLabels map[string]string `json:"externalUserLabels"`
}
type ReplyPolicyConfig struct {
UnknownAnswerToken string `json:"unknownAnswerToken"`
MaxQuestionLength int `json:"maxQuestionLength"`
CooldownSeconds int `json:"cooldownSeconds"`
SensitiveKeywords []string `json:"sensitiveKeywords"`
}
// Config stores the application configuration.
type Config struct {
CallbackConfig CallbackConfig `json:"callbackConfig"`
AutoReplyConfig AutoReplyConfig `json:"autoReplyConfig"`
LastUpdated int64 `json:"lastUpdated"`
}
// NewDefaultConfig creates a local-only default configuration.
func NewDefaultConfig() *Config {
return &Config{
CallbackConfig: CallbackConfig{
CallbackURL: "",
CallbackToken: "",
HTTPPort: "10001",
EnableCallback: false,
EnableCloudAuth: false,
FileUploadUrl: "",
DeviceCode: "",
},
AutoReplyConfig: NewDefaultAutoReplyConfig(),
LastUpdated: time.Now().Unix(),
}
}
// NewDefaultAutoReplyConfig creates disabled-but-ready automatic reply settings.
func NewDefaultAutoReplyConfig() AutoReplyConfig {
cfg := AutoReplyConfig{
Enabled: false,
Listen: ListenConfig{
EnablePrivateChat: true,
EnableGroupChat: true,
GroupTriggerMode: "mention_only",
IgnoreSelfMessage: true,
DeduplicateSeconds: 300,
},
Knowledge: KnowledgeConfig{
Directory: "config/knowledge",
IndexPath: "config/knowledge/index.json",
SupportedExtensions: []string{".md", ".txt", ".csv", ".xlsx", ".docx", ".pdf"},
TopK: 8,
MinScore: 0.40,
AutoRebuildOnStart: false,
},
Retrieval: RetrievalConfig{
RetrievalMode: "hybrid_rerank",
EmbeddingIndexPath: "config/knowledge/embedding_index.json",
EmbeddingModel: "text-embedding-v4",
EmbeddingDimensions: 512,
RerankModel: "qwen3-rerank",
RecallTopK: 50,
RerankTopK: 30,
FinalTopK: 8,
},
Materials: MaterialsConfig{
Directory: "config/materials",
IndexPath: "config/materials/materials.json",
AutoSendEnabled: true,
MaxPerReply: 2,
},
HumanAssist: HumanAssistConfig{
Enabled: false,
WaitSeconds: 15,
AfterHumanReplyDelaySeconds: 3,
SupplementMode: "supplement",
IgnoreLikelyAutoSentEcho: true,
MinimumHumanReplyLengthRunes: 4,
},
Collaboration: CollaborationConfig{
Enabled: false,
HumanWaitSeconds: 180,
AfterHumanReplyDelaySeconds: 3,
TakeoverIdleExitSeconds: 300,
SupplementTarget: "customer",
EngineerReturnPolicy: "review",
},
AI: AIConfig{
Provider: "openai_compatible",
BaseURL: "",
APIKey: "",
Model: "qwen-turbo",
SystemPrompt: defaultAISystemPrompt,
VisionModel: defaultVisionModel,
VisionBaseURL: "",
VisionAPIKey: "",
AudioProvider: "auto",
AudioMode: "openai_audio_chat",
AudioModel: "qwen3-asr-flash",
AudioBaseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
AudioAPIKey: "",
TimeoutSeconds: 20,
EnableThinking: false,
ReplyDetail: "detailed",
Temperature: 0,
MaxTokens: 700,
},
Handoff: HandoffConfig{
HumanUserID: "",
HumanConversationID: "",
MessageTemplate: "",
CustomerHandoffNotice: "已为您通知人工客服添加您的好友,请稍等。若 2 分钟内仍未收到好友申请,请点击上方名片主动添加人工客服。",
IncludeKnowledgeHits: true,
SendHumanCardToCustomer: true,
SendCustomerCardToHuman: true,
CardTriggerMode: "manual_keywords",
ManualTriggerKeywords: []string{"人工", "客服", "转人工", "人工客服", "真人", "真人客服"},
CardKeywords: []string{"人工", "客服", "转人工", "人工客服", "真人", "真人客服"},
},
Identity: IdentityConfig{
UnknownPolicy: "customer",
UnknownHandoffPolicy: "hold",
RefreshOnStart: true,
RefreshIntervalMinutes: 30,
PageSize: 200,
InternalNoHandoffReply: "内部员工消息不触发转人工,如需协助请直接联系对应同事。",
UnknownNoHandoffReply: "正在核验联系人身份,暂不触发转人工。如需协助请直接联系对应同事。",
InternalUserIDs: []string{},
ExternalUserIDs: []string{},
InternalGroupConversationIDs: []string{},
InternalGroupIDsByScope: map[string][]string{},
InternalUserLabels: map[string]string{},
ExternalUserLabels: map[string]string{},
},
ReplyPolicy: ReplyPolicyConfig{
UnknownAnswerToken: "NO_ANSWER",
MaxQuestionLength: 1000,
CooldownSeconds: 3,
SensitiveKeywords: []string{"人工", "转人工", "人工客服", "真人客服", "投诉", "退款", "退货", "合同", "发票", "赔偿", "价格审批"},
},
}
cfg.ReplyStyle = "natural_professional"
return cfg
}
// ApplyDefaults fills missing values for configs loaded from older files.
func (c *Config) ApplyDefaults() {
if c == nil {
return
}
defaultConfig := NewDefaultConfig()
if c.CallbackConfig.HTTPPort == "" {
c.CallbackConfig.HTTPPort = defaultConfig.CallbackConfig.HTTPPort
}
defaultAuto := NewDefaultAutoReplyConfig()
if c.AutoReplyConfig.Listen.GroupTriggerMode == "" {
c.AutoReplyConfig.Listen.GroupTriggerMode = defaultAuto.Listen.GroupTriggerMode
}
if !c.AutoReplyConfig.Listen.EnablePrivateChat && !c.AutoReplyConfig.Listen.EnableGroupChat {
c.AutoReplyConfig.Listen.EnablePrivateChat = defaultAuto.Listen.EnablePrivateChat
c.AutoReplyConfig.Listen.EnableGroupChat = defaultAuto.Listen.EnableGroupChat
}
if c.AutoReplyConfig.Listen.DeduplicateSeconds <= 0 {
c.AutoReplyConfig.Listen.DeduplicateSeconds = defaultAuto.Listen.DeduplicateSeconds
}
if c.AutoReplyConfig.Knowledge.Directory == "" {
c.AutoReplyConfig.Knowledge.Directory = defaultAuto.Knowledge.Directory
}
if c.AutoReplyConfig.Knowledge.IndexPath == "" {
c.AutoReplyConfig.Knowledge.IndexPath = defaultAuto.Knowledge.IndexPath
}
if len(c.AutoReplyConfig.Knowledge.SupportedExtensions) == 0 {
c.AutoReplyConfig.Knowledge.SupportedExtensions = defaultAuto.Knowledge.SupportedExtensions
}
if c.AutoReplyConfig.Knowledge.TopK <= 0 {
c.AutoReplyConfig.Knowledge.TopK = defaultAuto.Knowledge.TopK
} else if c.AutoReplyConfig.Knowledge.TopK < defaultAuto.Knowledge.TopK {
c.AutoReplyConfig.Knowledge.TopK = defaultAuto.Knowledge.TopK
}
if c.AutoReplyConfig.Knowledge.MinScore <= 0 {
c.AutoReplyConfig.Knowledge.MinScore = defaultAuto.Knowledge.MinScore
}
if c.AutoReplyConfig.Retrieval.RetrievalMode == "" {
c.AutoReplyConfig.Retrieval.RetrievalMode = defaultAuto.Retrieval.RetrievalMode
}
if c.AutoReplyConfig.Retrieval.EmbeddingIndexPath == "" {
c.AutoReplyConfig.Retrieval.EmbeddingIndexPath = defaultAuto.Retrieval.EmbeddingIndexPath
}
if c.AutoReplyConfig.Retrieval.EmbeddingModel == "" {
c.AutoReplyConfig.Retrieval.EmbeddingModel = defaultAuto.Retrieval.EmbeddingModel
}
// 检测用户是否错误地将 Rerank 模型填到了 Embedding 模型字段
if isRerankModelName(c.AutoReplyConfig.Retrieval.EmbeddingModel) {
c.AutoReplyConfig.Retrieval.EmbeddingModel = defaultAuto.Retrieval.EmbeddingModel
}
if c.AutoReplyConfig.Retrieval.EmbeddingDimensions <= 0 {
c.AutoReplyConfig.Retrieval.EmbeddingDimensions = defaultAuto.Retrieval.EmbeddingDimensions
}
if c.AutoReplyConfig.Retrieval.RerankModel == "" {
c.AutoReplyConfig.Retrieval.RerankModel = defaultAuto.Retrieval.RerankModel
}
// 检测用户是否错误地将 Embedding 模型填到了 Rerank 模型字段
if isEmbeddingModelName(c.AutoReplyConfig.Retrieval.RerankModel) {
c.AutoReplyConfig.Retrieval.RerankModel = defaultAuto.Retrieval.RerankModel
}
if c.AutoReplyConfig.Retrieval.RecallTopK <= 0 {
c.AutoReplyConfig.Retrieval.RecallTopK = defaultAuto.Retrieval.RecallTopK
} else if c.AutoReplyConfig.Retrieval.RecallTopK < defaultAuto.Retrieval.RecallTopK {
c.AutoReplyConfig.Retrieval.RecallTopK = defaultAuto.Retrieval.RecallTopK
}
if c.AutoReplyConfig.Retrieval.RerankTopK <= 0 {
c.AutoReplyConfig.Retrieval.RerankTopK = defaultAuto.Retrieval.RerankTopK
} else if c.AutoReplyConfig.Retrieval.RerankTopK < defaultAuto.Retrieval.RerankTopK {
c.AutoReplyConfig.Retrieval.RerankTopK = defaultAuto.Retrieval.RerankTopK
}
if c.AutoReplyConfig.Retrieval.FinalTopK <= 0 {
c.AutoReplyConfig.Retrieval.FinalTopK = defaultAuto.Retrieval.FinalTopK
} else if c.AutoReplyConfig.Retrieval.FinalTopK < defaultAuto.Retrieval.FinalTopK {
c.AutoReplyConfig.Retrieval.FinalTopK = defaultAuto.Retrieval.FinalTopK
}
if c.AutoReplyConfig.Materials.Directory == "" {
c.AutoReplyConfig.Materials.Directory = defaultAuto.Materials.Directory
}
if c.AutoReplyConfig.Materials.IndexPath == "" {
c.AutoReplyConfig.Materials.IndexPath = defaultAuto.Materials.IndexPath
}
if c.AutoReplyConfig.Materials.MaxPerReply <= 0 {
c.AutoReplyConfig.Materials.MaxPerReply = defaultAuto.Materials.MaxPerReply
}
if c.AutoReplyConfig.HumanAssist.WaitSeconds <= 0 {
c.AutoReplyConfig.HumanAssist.WaitSeconds = defaultAuto.HumanAssist.WaitSeconds
}
if c.AutoReplyConfig.HumanAssist.AfterHumanReplyDelaySeconds < 0 {
c.AutoReplyConfig.HumanAssist.AfterHumanReplyDelaySeconds = defaultAuto.HumanAssist.AfterHumanReplyDelaySeconds
}
if c.AutoReplyConfig.HumanAssist.SupplementMode == "" {
c.AutoReplyConfig.HumanAssist.SupplementMode = defaultAuto.HumanAssist.SupplementMode
}
if c.AutoReplyConfig.HumanAssist.MinimumHumanReplyLengthRunes <= 0 {
c.AutoReplyConfig.HumanAssist.MinimumHumanReplyLengthRunes = defaultAuto.HumanAssist.MinimumHumanReplyLengthRunes
}
if c.AutoReplyConfig.Collaboration.HumanWaitSeconds <= 0 {
c.AutoReplyConfig.Collaboration.HumanWaitSeconds = defaultAuto.Collaboration.HumanWaitSeconds
}
if c.AutoReplyConfig.Collaboration.AfterHumanReplyDelaySeconds < 0 {
c.AutoReplyConfig.Collaboration.AfterHumanReplyDelaySeconds = defaultAuto.Collaboration.AfterHumanReplyDelaySeconds
}
if c.AutoReplyConfig.Collaboration.TakeoverIdleExitSeconds <= 0 {
c.AutoReplyConfig.Collaboration.TakeoverIdleExitSeconds = defaultAuto.Collaboration.TakeoverIdleExitSeconds
}
if strings.TrimSpace(c.AutoReplyConfig.Collaboration.SupplementTarget) == "" {
c.AutoReplyConfig.Collaboration.SupplementTarget = defaultAuto.Collaboration.SupplementTarget
}
if strings.TrimSpace(c.AutoReplyConfig.Collaboration.EngineerReturnPolicy) == "" {
c.AutoReplyConfig.Collaboration.EngineerReturnPolicy = defaultAuto.Collaboration.EngineerReturnPolicy
}
if c.AutoReplyConfig.AI.Provider == "" {
c.AutoReplyConfig.AI.Provider = defaultAuto.AI.Provider
}
if c.AutoReplyConfig.AI.Model == "" {
c.AutoReplyConfig.AI.Model = defaultAuto.AI.Model
}
if strings.TrimSpace(c.AutoReplyConfig.AI.SystemPrompt) == "" {
c.AutoReplyConfig.AI.SystemPrompt = defaultAuto.AI.SystemPrompt
}
if c.AutoReplyConfig.AI.VisionModel == "" ||
(strings.EqualFold(c.AutoReplyConfig.AI.VisionModel, c.AutoReplyConfig.AI.Model) &&
!isVisionCapableModelName(c.AutoReplyConfig.AI.VisionModel)) ||
isLikelyTextOnlyQwenModel(c.AutoReplyConfig.AI.VisionModel) {
c.AutoReplyConfig.AI.VisionModel = defaultAuto.AI.VisionModel
}
if c.AutoReplyConfig.AI.AudioProvider == "" {
c.AutoReplyConfig.AI.AudioProvider = defaultAuto.AI.AudioProvider
}
if c.AutoReplyConfig.AI.AudioMode == "" {
c.AutoReplyConfig.AI.AudioMode = defaultAuto.AI.AudioMode
}
if c.AutoReplyConfig.AI.AudioModel == "" {
c.AutoReplyConfig.AI.AudioModel = defaultAuto.AI.AudioModel
}
if c.AutoReplyConfig.AI.TimeoutSeconds <= 0 {
c.AutoReplyConfig.AI.TimeoutSeconds = defaultAuto.AI.TimeoutSeconds
}
if c.AutoReplyConfig.AI.MaxTokens <= 0 {
c.AutoReplyConfig.AI.MaxTokens = defaultAuto.AI.MaxTokens
}
if c.AutoReplyConfig.AI.ReplyDetail == "" {
c.AutoReplyConfig.AI.ReplyDetail = defaultAuto.AI.ReplyDetail
}
if c.AutoReplyConfig.Handoff.MessageTemplate == "" {
c.AutoReplyConfig.Handoff.MessageTemplate = defaultAuto.Handoff.MessageTemplate
}
if c.AutoReplyConfig.Handoff.CustomerHandoffNotice == "" {
c.AutoReplyConfig.Handoff.CustomerHandoffNotice = defaultAuto.Handoff.CustomerHandoffNotice
}
if c.AutoReplyConfig.Handoff.CardTriggerMode == "" {
c.AutoReplyConfig.Handoff.SendHumanCardToCustomer = defaultAuto.Handoff.SendHumanCardToCustomer
c.AutoReplyConfig.Handoff.SendCustomerCardToHuman = defaultAuto.Handoff.SendCustomerCardToHuman
c.AutoReplyConfig.Handoff.CardTriggerMode = defaultAuto.Handoff.CardTriggerMode
}
if len(c.AutoReplyConfig.Handoff.ManualTriggerKeywords) == 0 {
c.AutoReplyConfig.Handoff.ManualTriggerKeywords = defaultAuto.Handoff.ManualTriggerKeywords
}
c.AutoReplyConfig.Handoff.ManualTriggerKeywords = dedupeStrings(append(
append([]string{}, c.AutoReplyConfig.Handoff.ManualTriggerKeywords...),
c.AutoReplyConfig.Handoff.CardKeywords...,
))
c.AutoReplyConfig.Handoff.CardKeywords = c.AutoReplyConfig.Handoff.ManualTriggerKeywords
if c.AutoReplyConfig.Identity.UnknownPolicy == "" {
c.AutoReplyConfig.Identity = defaultAuto.Identity
}
if c.AutoReplyConfig.Identity.UnknownHandoffPolicy == "" {
c.AutoReplyConfig.Identity.UnknownHandoffPolicy = defaultAuto.Identity.UnknownHandoffPolicy
}
if c.AutoReplyConfig.Identity.RefreshIntervalMinutes <= 0 {
c.AutoReplyConfig.Identity.RefreshIntervalMinutes = defaultAuto.Identity.RefreshIntervalMinutes
}
if c.AutoReplyConfig.Identity.PageSize <= 0 {
c.AutoReplyConfig.Identity.PageSize = defaultAuto.Identity.PageSize
}
if c.AutoReplyConfig.Identity.InternalNoHandoffReply == "" {
c.AutoReplyConfig.Identity.InternalNoHandoffReply = defaultAuto.Identity.InternalNoHandoffReply
}
if c.AutoReplyConfig.Identity.UnknownNoHandoffReply == "" {
c.AutoReplyConfig.Identity.UnknownNoHandoffReply = defaultAuto.Identity.UnknownNoHandoffReply
}
if c.AutoReplyConfig.Identity.InternalUserIDs == nil {
c.AutoReplyConfig.Identity.InternalUserIDs = defaultAuto.Identity.InternalUserIDs
}
if c.AutoReplyConfig.Identity.ExternalUserIDs == nil {
c.AutoReplyConfig.Identity.ExternalUserIDs = defaultAuto.Identity.ExternalUserIDs
}
if c.AutoReplyConfig.Identity.InternalGroupConversationIDs == nil {
c.AutoReplyConfig.Identity.InternalGroupConversationIDs = defaultAuto.Identity.InternalGroupConversationIDs
}
if c.AutoReplyConfig.Identity.InternalGroupIDsByScope == nil {
c.AutoReplyConfig.Identity.InternalGroupIDsByScope = defaultAuto.Identity.InternalGroupIDsByScope
}
if c.AutoReplyConfig.Identity.InternalUserLabels == nil {
c.AutoReplyConfig.Identity.InternalUserLabels = defaultAuto.Identity.InternalUserLabels
}
if c.AutoReplyConfig.Identity.ExternalUserLabels == nil {
c.AutoReplyConfig.Identity.ExternalUserLabels = defaultAuto.Identity.ExternalUserLabels
}
if c.AutoReplyConfig.ReplyPolicy.UnknownAnswerToken == "" {
c.AutoReplyConfig.ReplyPolicy.UnknownAnswerToken = defaultAuto.ReplyPolicy.UnknownAnswerToken
}
if c.AutoReplyConfig.ReplyPolicy.MaxQuestionLength <= 0 {
c.AutoReplyConfig.ReplyPolicy.MaxQuestionLength = defaultAuto.ReplyPolicy.MaxQuestionLength
}
if c.AutoReplyConfig.ReplyPolicy.CooldownSeconds <= 0 {
c.AutoReplyConfig.ReplyPolicy.CooldownSeconds = defaultAuto.ReplyPolicy.CooldownSeconds
}
if len(c.AutoReplyConfig.ReplyPolicy.SensitiveKeywords) == 0 {
c.AutoReplyConfig.ReplyPolicy.SensitiveKeywords = defaultAuto.ReplyPolicy.SensitiveKeywords
}
if strings.TrimSpace(c.AutoReplyConfig.ReplyStyle) == "" {
c.AutoReplyConfig.ReplyStyle = "natural_professional"
}
}
func dedupeStrings(items []string) []string {
seen := make(map[string]bool, len(items))
result := make([]string, 0, len(items))
for _, item := range items {
item = strings.TrimSpace(item)
if item == "" || seen[item] {
continue
}
seen[item] = true
result = append(result, item)
}
return result
}
func isVisionCapableModelName(model string) bool {
name := strings.ToLower(strings.TrimSpace(model))
return strings.Contains(name, "vl") ||
strings.Contains(name, "vision") ||
strings.Contains(name, "qvq") ||
strings.Contains(name, "omni")
}
func isLikelyTextOnlyQwenModel(model string) bool {
name := strings.ToLower(strings.TrimSpace(model))
if name == "" || isVisionCapableModelName(name) {
return false
}
switch name {
case "qwen-turbo", "qwen-plus", "qwen-max", "qwen-long":
return true
}
return strings.HasPrefix(name, "qwen") &&
(strings.Contains(name, "turbo") ||
strings.Contains(name, "plus") ||
strings.Contains(name, "max") ||
strings.Contains(name, "long") ||
strings.Contains(name, "coder") ||
strings.Contains(name, "math") ||
strings.Contains(name, "instruct"))
}
// isRerankModelName 检测模型名是否是 Rerank 模型
func isRerankModelName(model string) bool {
name := strings.ToLower(strings.TrimSpace(model))
if name == "" {
return false
}
return strings.Contains(name, "rerank") ||
strings.Contains(name, "gte-rerank") ||
strings.Contains(name, "bge-rerank")
}
// isEmbeddingModelName 检测模型名是否是 Embedding 模型
func isEmbeddingModelName(model string) bool {
name := strings.ToLower(strings.TrimSpace(model))
if name == "" {
return false
}
return strings.Contains(name, "embedding") ||
strings.Contains(name, "text-embedding") ||
strings.Contains(name, "bge-") ||
strings.Contains(name, "gte-") && !strings.Contains(name, "rerank")
}