大模型热切换功能完整实现指南

如果文章对你有帮助,请点个"关注"

版本 :v1.0
日期 :2026-04-10
作者:阿财


目录

  1. 功能概述
  2. 核心原理
  3. 后端实现
  4. 前端实现
  5. 测试验证
  6. 故障排查

1. 功能概述

1.1 什么是热切换

Agent 配置热切换:在不重启服务的情况下,动态更新 Agent 的配置(如切换大模型供应商),新配置立即生效。

1.2 解决的问题

传统方式

复制代码
修改配置 → 保存 → 重启服务 → 等待启动完成 → 测试
  • 需要重启服务
  • 中断正在进行的对话
  • 用户体验差

热切换方式

复制代码
修改配置 → 保存 → 自动重新加载 → 立即生效
  • 无需重启服务
  • 不中断对话
  • 用户体验好

1.3 使用场景

场景 说明
切换供应商 火山方舟 → 阿里百炼
修改 API Key Key 过期或轮换
更换模型 qwen3.5-plus → deepseek-v3.2
测试对比 快速对比不同模型的效果

2. 核心原理

2.1 架构图

复制代码
┌─────────────────────────────────────────────────────────┐
│                   前端(PySide6)                        │
│                                                         │
│  ┌──────────────────────────────────────────────────┐  │
│  │  AgentDialog(Agent 编辑对话框)                    │  │
│  │                                                   │  │
│  │  1. 用户修改配置                                  │  │
│  │  2. 保存到数据库                                  │  │
│  │  3. 调用 POST /api/reload-agent                   │  │
│  │  4. 显示"已保存"提示                              │  │
│  └──────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────┘
                          ↓ HTTP POST
┌─────────────────────────────────────────────────────────┐
│                 后端(Node.js + Express)                │
│                 监听:3002 端口                           │
│                                                         │
│  ┌──────────────────────────────────────────────────┐  │
│  │  /api/reload-agent                                │  │
│  │                                                   │  │
│  │  1. 从数据库读取新配置                            │  │
│  │  2. 更新 agentConfigs Map                         │  │
│  │  3. 更新 assistant 实例配置                         │  │
│  │  4. 返回成功响应                                  │  │
│  └──────────────────────────────────────────────────┘  │
│                                                         │
│  ┌──────────────────────────────────────────────────┐  │
│  │  SimpleAIAssistant                              │  │
│  │                                                   │  │
│  │  apiKey, apiUrl, modelName                        │  │
│  │  ↓ 被 reloadAgent 更新                              │  │
│  │  下次对话使用新配置                               │  │
│  └──────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────┘
                          ↓ SQLite
┌─────────────────────────────────────────────────────────┐
│                  数据库(system.db)                     │
│                                                         │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐ │
│  │ agents       │  │ models       │  │ providers    │ │
│  │              │  │              │  │              │ │
│  │ name         │  │ name         │  │ base_url     │ │
│  │ model        │  │ provider_id  │  │ api_key      │ │
│  │ provider     │  │ contextWindow│  │              │ │
│  └──────────────┘  └──────────────┘  └──────────────┘ │
└─────────────────────────────────────────────────────────┘

2.2 数据流

复制代码
1. 用户修改 Agent 配置
   ↓
2. 点击"保存"按钮
   ↓
3. 前端保存到数据库
   ↓
4. 前端调用 POST /api/reload-agent
   ↓
5. 后端 reloadAgent 函数
   ├─ 从数据库读取新配置
   ├─ 更新 agentConfigs Map
   └─ 更新 assistant 实例配置  ← 关键!
   ↓
6. 返回成功响应
   ↓
7. 前端显示"已保存"提示
   ↓
8. 下次对话使用新配置

2.3 关键设计

设计 1:更新 assistant 实例配置
javascript 复制代码
// server.js
async function reloadAgent(agentName) {
  const agent = await configManager.getAgentByName(agentName);
  const model = await configManager.getModelByName(agent.model);
  
  //  如果是当前默认 Agent,同时更新 assistant 实例
  if (agentName === 'main') {
    assistant.apiKey = model.api_key;        // ← 更新 API Key
    assistant.apiUrl = model.apiUrl;         // ← 更新 API URL
    assistant.modelName = model.name;        // ← 更新模型名称
    assistant.contextWindow = model.contextWindow;
    assistant.maxTokens = model.maxTokens;
    console.log(`assistant 实例配置已更新`);
  }
}

为什么需要更新 assistant 实例?

因为 assistant.js 在启动时加载配置:

javascript 复制代码
// assistant.js
async init() {
  const model = await configManager.getDefaultModel();
  this.apiKey = model.api_key;      // ← 启动时加载
  this.apiUrl = model.apiUrl;       // ← 启动时加载
  this.modelName = model.name;      // ← 启动时加载
}

如果不更新实例配置,下次对话仍然使用旧配置!


设计 2:下次对话重新构建 runtime
javascript 复制代码
// assistant.js
async chat(userMessage) {
  // 每次对话都会重新构建 runtime
  const runtimeText = getFullRuntimeInfo({
    workspace: this.workspace,
    model: this.modelName,  // ← 使用新模型名称
    default_model: this.modelName,  // ← 使用新模型名称
    ...
  });
  
  // 构建提示词
  const prompt = await buildSystemPrompt(memoryResults) + runtimeText;
  
  // 调用 API
  const response = await fetch(this.apiUrl, ...);  // ← 使用新 API URL
}

3. 后端实现

3.1 添加 configManager 导入

文件src/server.js
位置:第 13 行附近

javascript 复制代码
import db from './db.js';
import configManager from './core/config-manager.js';  // ← 添加这行

3.2 添加热切换函数

文件src/server.js
位置 :第 128 行(await assistant.init(); 之后)

javascript 复制代码
// 热切换功能
const agentConfigs = new Map();

/**
 * 重新加载 Agent 配置
 */
async function reloadAgent(agentName) {
  console.log(`重新加载 Agent: ${agentName}`);
  
  const agent = await configManager.getAgentByName(agentName);
  const model = await configManager.getModelByName(agent.model);
  
  agentConfigs.set(agentName, {
    name: agent.name,
    nickname: agent.nickname,
    apiKey: model.api_key,
    apiUrl: model.apiUrl,
    modelName: model.name
  });
  
  // 如果是当前默认 Agent,同时更新 assistant 实例
  if (agentName === 'main') {
    assistant.apiKey = model.api_key;
    assistant.apiUrl = model.apiUrl;
    assistant.modelName = model.name;
    assistant.contextWindow = model.contextWindow;
    assistant.maxTokens = model.maxTokens;
    console.log(`assistant 实例配置已更新`);
  }
  
  console.log(`Agent ${agentName} 配置已更新`);
  console.log(`   API URL: ${model.apiUrl}`);
  console.log(`   API Key: ${model.api_key.substring(0, 8)}...`);
}

/**
 * 刷新所有 Agent
 */
async function refreshAllAgents() {
  console.log('刷新所有 Agent 配置...');
  configManager.refreshCache();
  
  const agents = await configManager.getAgents();
  for (const agent of agents) {
    await reloadAgent(agent.name);
  }
  
  console.log('所有 Agent 配置已刷新');
}

// 初始化默认 Agent
await reloadAgent('main');

3.3 添加 API 接口

文件src/server.js
位置 :第 221 行左右(http.createServer 内部,// SSE 端点:/api/events 之前)

javascript 复制代码
// ===== 热切换 API 接口 =====

// 重新加载 Agent: POST /api/reload-agent
if (url.pathname === '/api/reload-agent' && req.method === 'POST') {
  try {
    const body = await new Promise((resolve) => {
      let data = '';
      req.on('data', chunk => { data += chunk; });
      req.on('end', () => resolve(JSON.parse(data)));
    });
    
    const { agentName } = body;
    
    if (!agentName) {
      res.writeHead(400, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ success: false, error: 'agentName 是必填参数' }));
      return;
    }
    
    await reloadAgent(agentName);
    
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ success: true, message: `Agent ${agentName} 配置已重新加载` }));
    return;
  } catch (e) {
    res.writeHead(500, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ success: false, error: e.message }));
    return;
  }
}

// 刷新所有 Agent: POST /api/refresh-all
if (url.pathname === '/api/refresh-all' && req.method === 'POST') {
  try {
    await refreshAllAgents();
    
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ success: true, message: '所有 Agent 配置已刷新' }));
    return;
  } catch (e) {
    res.writeHead(500, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ success: false, error: e.message }));
    return;
  }
}

// 获取 Agent 列表:GET /api/agents
if (url.pathname === '/api/agents' && req.method === 'GET') {
  try {
    const agents = await configManager.getAgents();
    
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ success: true, agents }));
    return;
  } catch (e) {
    res.writeHead(500, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ success: false, error: e.message }));
    return;
  }
}

// SSE 端点:/api/events

4. 前端实现

4.1 修改 agent_dialog.py

文件desktop_client/agent_dialog.py
位置save_agent 方法

python 复制代码
def save_agent(self):
    """保存 Agent 并应用更改"""
    name = self.name_input.text().strip()
    # ... 其他代码 ...
    
    try:
        cursor = config_manager.cursor
        conn = config_manager.conn
        
        if self.agent_name:
            cursor.execute('''UPDATE agents SET ... WHERE name=?''', (...))
        else:
            cursor.execute('''INSERT INTO agents (...) VALUES (...)''', (...))
        conn.commit()
        
        # 通知后端重新加载配置(热切换)
        print(f'[save_agent] 调用后端 API: /api/reload-agent')
        
        try:
            import requests
            print(f'[save_agent] POST http://localhost:3002/api/reload-agent')
            print(f'[save_agent] JSON: {{"agentName": "{name}"}}')
            
            response = requests.post(
                'http://localhost:3002/api/reload-agent',
                json={'agentName': name},
                timeout=5
            )
            
            print(f'[save_agent] 响应状态码:{response.status_code}')
            print(f'[save_agent] 响应内容:{response.text}')
            
            if response.json().get('success'):
                print(f'[save_agent] 热切换成功')
            else:
                print(f'[save_agent] 热切换失败:{response.json().get("error")}')
                QMessageBox.warning(
                    self, '警告',
                    '热切换失败:' + response.json().get('error', '未知错误')
                )
        except Exception as e:
            print(f'[save_agent] API 调用失败:{e}')
            QMessageBox.warning(
                self, '提示',
                '配置已保存,但热切换失败\n需要重启服务才能生效'
            )
        
        # 关闭对话框(无论成功还是失败都关闭)
        self.accept()
        
        # 创建工作区目录(仅新建 Agent 时)
        if not self.agent_name:
            base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
            workspace_path = os.path.join(base_dir, 'workspace', name)
            os.makedirs(workspace_path, exist_ok=True)
            print(f'[save_agent] 已创建工作区:{workspace_path}')
            
    except Exception as e:
        print(f'[save_agent] 错误:{e}')
        if 'UNIQUE constraint failed' in str(e):
            QMessageBox.critical(self, '错误', f'Agent {name} 已存在')
        else:
            QMessageBox.critical(self, '错误', f'保存失败:{e}')

4.2 修改 config_dialog.py

文件desktop_client/config_dialog.py
位置save_agent 方法

python 复制代码
def save_agent(self):
    """保存 Agent 并应用更改"""
    name = self.agent_name.text().strip()
    provider = self.agent_provider.currentText()
    model = self.agent_model.currentData()
    
    if not name or not provider:
        QMessageBox.warning(self, '警告', '名称和供应商必填')
        return
    
    try:
        cursor = config_manager.cursor
        cursor.execute('SELECT id FROM agents WHERE name = ?', (name,))
        if cursor.fetchone():
            cursor.execute('''UPDATE agents SET ... WHERE name=?''', (...))
        else:
            cursor.execute('''INSERT INTO agents (...) VALUES (...)''', (...))
        config_manager.conn.commit()
        
        # 通知后端重新加载配置(热切换)
        print(f'[save_agent] 调用后端 API: /api/reload-agent')
        try:
            import requests
            print(f'[save_agent] POST http://localhost:3002/api/reload-agent')
            print(f'[save_agent] JSON: {{"agentName": "{name}"}}')
            
            response = requests.post(
                'http://localhost:3002/api/reload-agent',
                json={'agentName': name},
                timeout=5
            )
            
            print(f'[save_agent] 响应状态码:{response.status_code}')
            print(f'[save_agent] 响应内容:{response.text}')
            
            if response.json().get('success'):
                print(f'[save_agent] 热切换成功')
                # 不显示成功提示
            else:
                print(f'[save_agent] 热切换失败:{response.json().get("error")}')
                QMessageBox.warning(
                    self, '警告',
                    '热切换失败:' + response.json().get('error', '未知错误')
                )
        except Exception as e:
            print(f'[save_agent] API 调用失败:{e}')
            # 不阻止保存,只显示警告
            QMessageBox.warning(
                self, '提示',
                '配置已保存,但热切换失败\n需要重启服务才能生效'
            )
        
        config_manager.refresh_cache()
        self.load_data()
        self.config_changed.emit()
    except Exception as e:
        QMessageBox.critical(self, '错误', str(e))

5. 测试验证

5.1 重启后端服务

bash 复制代码
# 停止当前服务(Ctrl+C)

# 重新启动
node src/server.js

预期输出

复制代码
重新加载 Agent: main
Agent main 配置已更新
   API URL: https://ark.cn-beijing.volces.com/api/coding/v3/chat/completions
   API Key: c867c7bb...
assistant 实例配置已更新

5.2 测试 API 接口

测试 1:获取 Agent 列表

bash 复制代码
curl http://localhost:3002/api/agents

预期输出

json 复制代码
{
  "success": true,
  "agents": [
    {"id": 1, "name": "main", "nickname": "阿财", ...},
    ...
  ]
}

测试 2:重新加载 Agent

bash 复制代码
curl -X POST http://localhost:3002/api/reload-agent \
  -H "Content-Type: application/json" \
  -d "{\"agentName\":\"main\"}"

预期输出

json 复制代码
{
  "success": true,
  "message": "Agent main 配置已重新加载"
}

测试 3:刷新所有 Agent

bash 复制代码
curl -X POST http://localhost:3002/api/refresh-all

预期输出

json 复制代码
{
  "success": true,
  "message": "所有 Agent 配置已刷新"
}

5.3 测试前端热切换

步骤

  1. 打开桌面客户端
  2. 配置管理 → Agent 管理
  3. 双击 main Agent
  4. 修改大模型(如火山方舟 → 阿里百炼)
  5. 点击"保存"
  6. 等待 2-3 秒
  7. 对话框自动关闭

查看日志

客户端日志

复制代码
[save_agent] 调用后端 API: /api/reload-agent
[save_agent] POST http://localhost:3002/api/reload-agent
[save_agent] JSON: {"agentName": "main"}
[save_agent] 响应状态码:200
[save_agent] 响应内容:{"success":true,"message":"..."}
[save_agent] 热切换成功

服务器日志

复制代码
重新加载 Agent: main
Agent main 配置已更新
   API URL: https://dashscope.aliyuncs.com/...
   API Key: sk-...
assistant 实例配置已更新

测试 4:验证新配置生效

发送一条消息,查看日志:

复制代码
apiurl: https://dashscope.aliyuncs.com/...  ← 新 URL
apikey: sk-...                              ← 新 Key
model: qwen3.5-plus                         ← 新模型

C. 常见问题

Q1: 热切换会影响正在进行的对话吗?

A: 不会,只影响下次对话

Q2: 需要重启服务吗?

A: 不需要,热切换的核心就是无需重启

Q3: 切换后 runtime 会更新吗?

A: 会,下次对话时重新构建 runtime

Q4: 可以批量切换所有 Agent 吗?

A: 可以,调用 /api/refresh-all 接口

Q5: 切换失败会影响保存吗?

A: 不会,配置已保存到数据库,热切换失败不影响保存


如果文章对你有帮助,请点个"关注"

相关推荐
小村儿2 小时前
连载05-Claude Skill 不是抄模板:真正管用的 Skill,都是从实战里提炼出来的
前端·后端·ai编程
爱码小白2 小时前
数据库多表命名的通用规范
数据库·python·mysql
互联网江湖2 小时前
千问闯关AI混沌期:阿里画靶,吴嘉张弓,马云射箭?
人工智能
AI品信智慧数智人2 小时前
景区AI伴游革新!山东品信数字人智能语音交互系统,重构文旅智慧体验✨
人工智能
大喵桑丶2 小时前
ZABBIX7二次开发AI监控数据调取杂记
大数据·人工智能·python
IT 行者2 小时前
Claude Code 源码解读 06:权限系统与 Hooks——安全与自动化的基石
ai编程·源码解读·claude code
小虎AI生活2 小时前
养个 AI 合伙人:Hermes Agent 保姆级部署教程
ai编程
龙文浩_2 小时前
AI中NLP的注意力机制的计算公式解析
人工智能·pytorch·深度学习·神经网络·自然语言处理
WangJunXiang62 小时前
Python网络编程
开发语言·网络·python