feat: 优化模型配置管理 - 动态使用优先级最高的Provider
## 主要改进 ### 1. 数据库驱动的模型配置 - 新增 GetActiveProviderWithModel() 方法,从数据库获取优先级最高的 Provider 及其默认模型 - 支持多 Provider 配置,通过 priority 字段控制优先级 - 实现 5 分钟缓存机制,减少数据库查询 ### 2. 应用模型配置优化 - 移除应用层硬编码模型配置 - 应用自动使用优先级最高的 Provider 的默认模型 - 支持应用级模型覆盖(可选) ### 3. 工具脚本 - check-providers: 查询数据库中的 Provider 和应用配置 - fix-providers: 修复 Provider 配置(补全 config 和 models 字段) - clear-app-models: 清空应用硬编码模型配置 ### 4. 代码质量 - 删除未使用的 getProvider() 方法 - 修复 fmt.Println 冗余换行警告 - 统一代码格式 ## 技术细节 **降级策略**: 数据库 Provider (优先级) → 环境变量 Provider → 应用配置模型 **当前配置**: - 优先级 110: 本地LLM (qwen2.5-7b-instruct) - 优先级 100: 阿里云百炼 (qwen-plus) 所有 41 个应用现在自动使用本地模型,无需手动配置。
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 加载环境变量
|
||||
_ = godotenv.Load("../../.env")
|
||||
|
||||
dbURL := os.Getenv("DATABASE_URL")
|
||||
if dbURL == "" {
|
||||
dbURL = "postgres://freedak:@localhost:5432/govai_portal?sslmode=disable"
|
||||
}
|
||||
|
||||
pool, err := pgxpool.New(context.Background(), dbURL)
|
||||
if err != nil {
|
||||
log.Fatalf("无法连接数据库: %v", err)
|
||||
}
|
||||
defer pool.Close()
|
||||
|
||||
fmt.Println("=== 数据库中的模型提供商配置 ===")
|
||||
|
||||
rows, err := pool.Query(context.Background(), `
|
||||
SELECT id, name, base_url,
|
||||
SUBSTRING(api_key_encrypted, 1, 20) as api_key_prefix,
|
||||
models, is_active, priority, config
|
||||
FROM model_providers
|
||||
ORDER BY priority DESC, created_at
|
||||
`)
|
||||
if err != nil {
|
||||
log.Fatalf("查询失败: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
count := 0
|
||||
for rows.Next() {
|
||||
var id, name, baseURL, apiKeyPrefix string
|
||||
var modelsJSON, configJSON []byte
|
||||
var isActive bool
|
||||
var priority int
|
||||
|
||||
err := rows.Scan(&id, &name, &baseURL, &apiKeyPrefix, &modelsJSON, &isActive, &priority, &configJSON)
|
||||
if err != nil {
|
||||
log.Printf("扫描行失败: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
count++
|
||||
fmt.Printf("【提供商 %d】\n", count)
|
||||
fmt.Printf("ID: %s\n", id)
|
||||
fmt.Printf("名称: %s\n", name)
|
||||
fmt.Printf("Base URL: %s\n", baseURL)
|
||||
fmt.Printf("API Key: %s...\n", apiKeyPrefix)
|
||||
fmt.Printf("激活状态: %v\n", isActive)
|
||||
fmt.Printf("优先级: %d\n", priority)
|
||||
|
||||
// 解析 models
|
||||
var models []map[string]interface{}
|
||||
if err := json.Unmarshal(modelsJSON, &models); err == nil {
|
||||
fmt.Printf("可用模型: ")
|
||||
for i, m := range models {
|
||||
if i > 0 {
|
||||
fmt.Print(", ")
|
||||
}
|
||||
fmt.Printf("%v", m["id"])
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// 解析 config
|
||||
var config map[string]interface{}
|
||||
if err := json.Unmarshal(configJSON, &config); err == nil {
|
||||
fmt.Printf("Provider 类型: %v\n", config["provider"])
|
||||
fmt.Printf("默认模型: %v\n", config["default_model"])
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
fmt.Println("❌ 数据库中没有任何模型提供商配置")
|
||||
fmt.Println("提示: 运行种子数据脚本来初始化配置")
|
||||
} else {
|
||||
fmt.Printf("✅ 共找到 %d 个提供商配置\n", count)
|
||||
}
|
||||
|
||||
// 查询应用配置中的模型使用情况
|
||||
fmt.Println("\n=== 应用配置中的模型使用情况 ===")
|
||||
|
||||
appRows, err := pool.Query(context.Background(), `
|
||||
SELECT name,
|
||||
COALESCE(app_config->>'model', '未配置') as model,
|
||||
status
|
||||
FROM applications
|
||||
WHERE status = 'approved'
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 10
|
||||
`)
|
||||
if err != nil {
|
||||
log.Printf("查询应用失败: %v", err)
|
||||
return
|
||||
}
|
||||
defer appRows.Close()
|
||||
|
||||
appCount := 0
|
||||
modelStats := make(map[string]int)
|
||||
|
||||
for appRows.Next() {
|
||||
var name, model, status string
|
||||
if err := appRows.Scan(&name, &model, &status); err != nil {
|
||||
continue
|
||||
}
|
||||
appCount++
|
||||
modelStats[model]++
|
||||
fmt.Printf("应用: %-30s | 模型: %-15s | 状态: %s\n", name, model, status)
|
||||
}
|
||||
|
||||
if appCount > 0 {
|
||||
fmt.Printf("\n✅ 共查询到 %d 个已批准的应用\n\n", appCount)
|
||||
fmt.Println("模型使用统计:")
|
||||
for model, count := range modelStats {
|
||||
fmt.Printf(" - %-15s: %d 个应用\n", model, count)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 加载环境变量
|
||||
_ = godotenv.Load("../../.env")
|
||||
|
||||
dbURL := os.Getenv("DATABASE_URL")
|
||||
if dbURL == "" {
|
||||
dbURL = "postgres://freedak:@localhost:5432/govai_portal?sslmode=disable"
|
||||
}
|
||||
|
||||
pool, err := pgxpool.New(context.Background(), dbURL)
|
||||
if err != nil {
|
||||
log.Fatalf("无法连接数据库: %v", err)
|
||||
}
|
||||
defer pool.Close()
|
||||
|
||||
fmt.Println("=== 清空应用硬编码模型配置 ===")
|
||||
fmt.Println("目标:让所有应用自动使用优先级最高的 Provider 的默认模型")
|
||||
|
||||
// 1. 查看当前状态
|
||||
fmt.Println("【步骤1】查看当前应用模型配置...")
|
||||
var totalApps, configuredApps int
|
||||
err = pool.QueryRow(context.Background(), `
|
||||
SELECT
|
||||
COUNT(*) as total,
|
||||
COUNT(CASE WHEN app_config->>'model' IS NOT NULL AND app_config->>'model' != '' THEN 1 END) as configured
|
||||
FROM applications
|
||||
WHERE status = 'approved'
|
||||
`).Scan(&totalApps, &configuredApps)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("查询失败: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf(" - 已批准应用总数: %d\n", totalApps)
|
||||
fmt.Printf(" - 配置了模型的应用: %d\n", configuredApps)
|
||||
fmt.Printf(" - 未配置模型的应用: %d\n\n", totalApps-configuredApps)
|
||||
|
||||
// 2. 清空所有应用的模型配置
|
||||
fmt.Println("【步骤2】清空所有应用的模型配置...")
|
||||
result, err := pool.Exec(context.Background(), `
|
||||
UPDATE applications
|
||||
SET app_config = app_config - 'model'
|
||||
WHERE status = 'approved'
|
||||
AND app_config ? 'model'
|
||||
`)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("更新失败: %v", err)
|
||||
}
|
||||
|
||||
rowsAffected := result.RowsAffected()
|
||||
fmt.Printf("✅ 已清空 %d 个应用的模型配置\n\n", rowsAffected)
|
||||
|
||||
// 3. 验证结果
|
||||
fmt.Println("【步骤3】验证结果...")
|
||||
var remainingConfigured int
|
||||
err = pool.QueryRow(context.Background(), `
|
||||
SELECT COUNT(*)
|
||||
FROM applications
|
||||
WHERE status = 'approved'
|
||||
AND app_config->>'model' IS NOT NULL
|
||||
AND app_config->>'model' != ''
|
||||
`).Scan(&remainingConfigured)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("验证查询失败: %v", err)
|
||||
}
|
||||
|
||||
if remainingConfigured == 0 {
|
||||
fmt.Println("✅ 所有应用的模型配置已清空")
|
||||
} else {
|
||||
fmt.Printf("⚠️ 还有 %d 个应用仍有模型配置\n", remainingConfigured)
|
||||
}
|
||||
|
||||
// 4. 显示当前优先级最高的 Provider
|
||||
fmt.Println("\n【步骤4】当前优先级最高的 Provider...")
|
||||
var providerName, defaultModel string
|
||||
var priority int
|
||||
err = pool.QueryRow(context.Background(), `
|
||||
SELECT name, COALESCE(config->>'default_model', '未配置'), priority
|
||||
FROM model_providers
|
||||
WHERE is_active = true
|
||||
ORDER BY priority DESC, created_at
|
||||
LIMIT 1
|
||||
`).Scan(&providerName, &defaultModel, &priority)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("查询 Provider 失败: %v", err)
|
||||
} else {
|
||||
fmt.Printf(" Provider: %s\n", providerName)
|
||||
fmt.Printf(" 默认模型: %s\n", defaultModel)
|
||||
fmt.Printf(" 优先级: %d\n", priority)
|
||||
}
|
||||
|
||||
fmt.Println("\n=== 配置清理完成 ===")
|
||||
fmt.Println("\n说明:")
|
||||
fmt.Println(" - 现在所有应用将自动使用优先级最高的 Provider 的默认模型")
|
||||
fmt.Println(" - 当前会使用: " + providerName + " / " + defaultModel)
|
||||
fmt.Println(" - 如需特殊应用使用指定模型,可在应用配置中单独设置")
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 加载环境变量
|
||||
_ = godotenv.Load("../../.env")
|
||||
|
||||
dbURL := os.Getenv("DATABASE_URL")
|
||||
if dbURL == "" {
|
||||
dbURL = "postgres://freedak:@localhost:5432/govai_portal?sslmode=disable"
|
||||
}
|
||||
|
||||
pool, err := pgxpool.New(context.Background(), dbURL)
|
||||
if err != nil {
|
||||
log.Fatalf("无法连接数据库: %v", err)
|
||||
}
|
||||
defer pool.Close()
|
||||
|
||||
fmt.Println("=== 开始修复数据库配置 ===")
|
||||
|
||||
// 1. 修复本地模型配置
|
||||
fmt.Println("【任务1】修复本地模型配置...")
|
||||
result, err := pool.Exec(context.Background(), `
|
||||
UPDATE model_providers
|
||||
SET config = '{
|
||||
"provider": "openai",
|
||||
"default_model": "qwen2.5-7b-instruct",
|
||||
"supports_streaming": true,
|
||||
"supports_function_calling": false
|
||||
}'::jsonb,
|
||||
models = '[
|
||||
{"id": "qwen2.5-7b-instruct", "name": "Qwen2.5-7B-Instruct", "type": "chat"}
|
||||
]'::jsonb
|
||||
WHERE name = '本地LLM (Qwen2.5-7B)'
|
||||
`)
|
||||
if err != nil {
|
||||
log.Printf("❌ 修复本地模型失败: %v", err)
|
||||
} else {
|
||||
rowsAffected := result.RowsAffected()
|
||||
if rowsAffected > 0 {
|
||||
fmt.Printf("✅ 成功修复本地模型配置 (影响 %d 行)\n\n", rowsAffected)
|
||||
} else {
|
||||
fmt.Println("⚠️ 未找到本地模型记录")
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 为未配置模型的应用设置默认模型
|
||||
fmt.Println("【任务2】为未配置模型的应用设置默认模型...")
|
||||
result, err = pool.Exec(context.Background(), `
|
||||
UPDATE applications
|
||||
SET app_config = jsonb_set(
|
||||
COALESCE(app_config, '{}'::jsonb),
|
||||
'{model}',
|
||||
'"qwen2.5-7b-instruct"'
|
||||
)
|
||||
WHERE (app_config->>'model' IS NULL OR app_config->>'model' = '')
|
||||
AND status = 'approved'
|
||||
`)
|
||||
if err != nil {
|
||||
log.Printf("❌ 更新应用配置失败: %v", err)
|
||||
} else {
|
||||
rowsAffected := result.RowsAffected()
|
||||
if rowsAffected > 0 {
|
||||
fmt.Printf("✅ 成功为 %d 个应用设置默认模型\n\n", rowsAffected)
|
||||
} else {
|
||||
fmt.Println("ℹ️ 所有应用已配置模型")
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 验证修复结果
|
||||
fmt.Println("【验证】检查修复结果...")
|
||||
|
||||
var localConfig, localModels string
|
||||
err = pool.QueryRow(context.Background(), `
|
||||
SELECT
|
||||
COALESCE(config::text, 'null') as config,
|
||||
COALESCE(models::text, 'null') as models
|
||||
FROM model_providers
|
||||
WHERE name = '本地LLM (Qwen2.5-7B)'
|
||||
`).Scan(&localConfig, &localModels)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("查询验证失败: %v", err)
|
||||
} else {
|
||||
fmt.Printf("本地模型 config: %s\n", localConfig)
|
||||
fmt.Printf("本地模型 models: %s\n\n", localModels)
|
||||
}
|
||||
|
||||
var unconfiguredCount int
|
||||
err = pool.QueryRow(context.Background(), `
|
||||
SELECT COUNT(*)
|
||||
FROM applications
|
||||
WHERE (app_config->>'model' IS NULL OR app_config->>'model' = '')
|
||||
AND status = 'approved'
|
||||
`).Scan(&unconfiguredCount)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("查询未配置应用数量失败: %v", err)
|
||||
} else {
|
||||
fmt.Printf("未配置模型的应用数量: %d\n", unconfiguredCount)
|
||||
}
|
||||
|
||||
fmt.Println("\n=== 修复完成 ===")
|
||||
}
|
||||
@@ -44,7 +44,7 @@ func newRouter(cfg *config.Config, pool *pgxpool.Pool, rdb *redis.Client) http.H
|
||||
// LLM Manager — direct model calls replacing Dify for chat
|
||||
llmMgr := llm.NewManager()
|
||||
llmMgr.SetPool(pool)
|
||||
|
||||
|
||||
// 优先从数据库加载 providers(如果失败,回退到环境变量配置)
|
||||
if err := llmMgr.LoadProvidersFromDB(context.Background()); err != nil {
|
||||
// 回退:使用环境变量配置
|
||||
|
||||
Reference in New Issue
Block a user