Initial qiwei secondary development handoff
This commit is contained in:
560
config/types.go
Normal file
560
config/types.go
Normal file
@@ -0,0 +1,560 @@
|
||||
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")
|
||||
}
|
||||
Reference in New Issue
Block a user