561 lines
23 KiB
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")
|
|
}
|