Files
qiweimanager-master/config/types_test.go
yuanzhipeng 1517be2a25 feat(auto-reply): 接入万川平台模型配置 + 各模型独立网关回退
万川平台对接
- 新增 wanchuan_proxy.go:WanchuanLogin/WanchuanGetModel 代理登录与按 code 拉取模型,
  日志对 password/token/apiKey 打码(含 encryptedConfig 二次解析)
- 新增 PlatformConfig(baseUrl/username/password)及 Get/SavePlatformConfig 持久化
- 前端万川卡片:登录→拉取 chat/vision/embedding/rerank/voice→回填 form 并保存→必要时重建向量索引

各模型独立网关(url+key),留空回退聊天网关
- RetrievalConfig 新增 embeddingBaseUrl/embeddingApiKey、rerankBaseUrl/rerankApiKey
- embeddingRequestConfig/rerankRequestConfig:优先独立网关,未配置回退 AI.BaseURL/APIKey
- vision/audio 同模式:非 DashScope 网关下视觉/语音模型留空时不再锁死或强写 DashScope,
  运行期由 fallbackString(VisionModel, Model) 动态复用聊天模型

陈旧向量空间防护
- loadEmbeddingIndex 检测磁盘索引与当前 embedding 模型/维度不一致时清空向量、回退关键词检索,
  并提示重建(embeddingIndexStaleReason,兼容旧版无模型名索引)

UI 状态修复
- 登录拉模型期间统一置全局 busy,禁用闸门收敛为 busy(与刷新联系人等按钮同范式),
  platformBusy 仅保留用于按钮「处理中…」文案,杜绝并发读写 form 与反向可点洞

其他
- 删除遗留 helper/auto_reply_ai.go.bak
- 补充 config/helper 单元测试(视觉回退分支、陈旧索引判定)
2026-06-26 10:17:02 +08:00

148 lines
5.2 KiB
Go

package config
import (
"testing"
)
// 注意:这些测试函数测试的是未导出的私有函数
// 如果编译失败,说明函数未定义或包名不匹配
func TestIsRerankModelName(t *testing.T) {
tests := []struct {
name string
model string
expected bool
}{
{"gte-rerank-v2", "gte-rerank-v2", true},
{"qwen3-rerank", "qwen3-rerank", true},
{"bge-rerank-large", "bge-rerank-large", true},
{"text-embedding-v4", "text-embedding-v4", false},
{"text-embedding-v3", "text-embedding-v3", false},
{"qwen-turbo", "qwen-turbo", false},
{"empty string", "", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := isRerankModelName(tt.model)
if result != tt.expected {
t.Errorf("isRerankModelName(%q) = %v, want %v", tt.model, result, tt.expected)
}
})
}
}
func TestIsEmbeddingModelName(t *testing.T) {
tests := []struct {
name string
model string
expected bool
}{
{"text-embedding-v4", "text-embedding-v4", true},
{"text-embedding-v3", "text-embedding-v3", true},
{"bge-large-zh", "bge-large-zh", true},
{"gte-large", "gte-large", true},
{"gte-rerank-v2", "gte-rerank-v2", false},
{"qwen3-rerank", "qwen3-rerank", false},
{"qwen-turbo", "qwen-turbo", false},
{"empty string", "", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := isEmbeddingModelName(tt.model)
if result != tt.expected {
t.Errorf("isEmbeddingModelName(%q) = %v, want %v", tt.model, result, tt.expected)
}
})
}
}
func TestApplyDefaultsFixesWrongModelConfig(t *testing.T) {
// 测试错误配置会被自动修正
cfg := NewDefaultConfig()
// 模拟用户错误地将 Rerank 模型填到 Embedding 字段
cfg.AutoReplyConfig.Retrieval.EmbeddingModel = "gte-rerank-v2"
cfg.AutoReplyConfig.Retrieval.RerankModel = "qwen3-rerank"
cfg.ApplyDefaults()
// 验证错误的 Embedding 模型被修正为默认值
if cfg.AutoReplyConfig.Retrieval.EmbeddingModel != "text-embedding-v4" {
t.Errorf("Expected embedding model to be corrected to 'text-embedding-v4', got %q", cfg.AutoReplyConfig.Retrieval.EmbeddingModel)
}
// 验证 Rerank 模型保持不变
if cfg.AutoReplyConfig.Retrieval.RerankModel != "qwen3-rerank" {
t.Errorf("Expected rerank model to remain 'qwen3-rerank', got %q", cfg.AutoReplyConfig.Retrieval.RerankModel)
}
}
func TestApplyDefaultsFixesWrongRerankConfig(t *testing.T) {
// 测试错误配置会被自动修正
cfg := NewDefaultConfig()
// 模拟用户错误地将 Embedding 模型填到 Rerank 字段
cfg.AutoReplyConfig.Retrieval.EmbeddingModel = "text-embedding-v4"
cfg.AutoReplyConfig.Retrieval.RerankModel = "text-embedding-v3"
cfg.ApplyDefaults()
// 验证 Embedding 模型保持不变
if cfg.AutoReplyConfig.Retrieval.EmbeddingModel != "text-embedding-v4" {
t.Errorf("Expected embedding model to remain 'text-embedding-v4', got %q", cfg.AutoReplyConfig.Retrieval.EmbeddingModel)
}
// 验证错误的 Rerank 模型被修正为默认值
if cfg.AutoReplyConfig.Retrieval.RerankModel != "qwen3-rerank" {
t.Errorf("Expected rerank model to be corrected to 'qwen3-rerank', got %q", cfg.AutoReplyConfig.Retrieval.RerankModel)
}
}
// 非 DashScope 统一网关(如万川)且无独立视觉网关时:视觉模型 == 聊天模型应被清空,
// 以便运行期 fallbackString(VisionModel, Model) 动态跟随聊天模型,不锁死旧值。
func TestApplyDefaultsNonDashScopeVisionFollowsChat(t *testing.T) {
cfg := NewDefaultConfig()
cfg.AutoReplyConfig.AI.BaseURL = "https://wanchuan.example/v1"
cfg.AutoReplyConfig.AI.Model = "wanchuan-chat"
cfg.AutoReplyConfig.AI.VisionBaseURL = ""
cfg.AutoReplyConfig.AI.VisionModel = "wanchuan-chat" // 与聊天模型相同(之前回填留下的值)
cfg.ApplyDefaults()
if cfg.AutoReplyConfig.AI.VisionModel != "" {
t.Errorf("Expected vision model cleared to follow chat on non-DashScope unified gateway, got %q", cfg.AutoReplyConfig.AI.VisionModel)
}
}
// 非 DashScope 网关上用户在同一网关显式填了不同的视觉模型时,应保留其选择。
func TestApplyDefaultsNonDashScopeKeepsExplicitVision(t *testing.T) {
cfg := NewDefaultConfig()
cfg.AutoReplyConfig.AI.BaseURL = "https://wanchuan.example/v1"
cfg.AutoReplyConfig.AI.Model = "wanchuan-chat"
cfg.AutoReplyConfig.AI.VisionBaseURL = ""
cfg.AutoReplyConfig.AI.VisionModel = "wanchuan-vl" // 与聊天模型不同,属用户显式选择
cfg.ApplyDefaults()
if cfg.AutoReplyConfig.AI.VisionModel != "wanchuan-vl" {
t.Errorf("Expected explicit different vision model preserved, got %q", cfg.AutoReplyConfig.AI.VisionModel)
}
}
// DashScope 网关:视觉模型为空或文本模型时仍应回退到专用视觉模型,不受上面改动影响。
func TestApplyDefaultsDashScopeStillFallsBackToVisionModel(t *testing.T) {
cfg := NewDefaultConfig()
cfg.AutoReplyConfig.AI.BaseURL = "https://dashscope.aliyuncs.com/compatible-mode/v1"
cfg.AutoReplyConfig.AI.Model = "qwen-turbo"
cfg.AutoReplyConfig.AI.VisionBaseURL = ""
cfg.AutoReplyConfig.AI.VisionModel = ""
cfg.ApplyDefaults()
if cfg.AutoReplyConfig.AI.VisionModel != defaultVisionModel {
t.Errorf("Expected DashScope vision fallback to %q, got %q", defaultVisionModel, cfg.AutoReplyConfig.AI.VisionModel)
}
}