如果文章对你有帮助请点个“关注”版本v1.0日期2026-04-10作者阿财目录功能概述核心原理后端实现前端实现测试验证故障排查1. 功能概述1.1 什么是热切换Agent 配置热切换在不重启服务的情况下动态更新 Agent 的配置如切换大模型供应商新配置立即生效。1.2 解决的问题传统方式修改配置 → 保存 → 重启服务 → 等待启动完成 → 测试需要重启服务中断正在进行的对话用户体验差热切换方式修改配置 → 保存 → 自动重新加载 → 立即生效无需重启服务不中断对话用户体验好1.3 使用场景场景说明切换供应商火山方舟 → 阿里百炼修改 API KeyKey 过期或轮换更换模型qwen3.5-plus → deepseek-v3.2测试对比快速对比不同模型的效果2. 核心原理2.1 架构图┌─────────────────────────────────────────────────────────┐ │ 前端PySide6 │ │ │ │ ┌──────────────────────────────────────────────────┐ │ │ │ AgentDialogAgent 编辑对话框 │ │ │ │ │ │ │ │ 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 实例配置// server.jsasyncfunctionreloadAgent(agentName){constagentawaitconfigManager.getAgentByName(agentName);constmodelawaitconfigManager.getModelByName(agent.model);// 如果是当前默认 Agent同时更新 assistant 实例if(agentNamemain){assistant.apiKeymodel.api_key;// ← 更新 API Keyassistant.apiUrlmodel.apiUrl;// ← 更新 API URLassistant.modelNamemodel.name;// ← 更新模型名称assistant.contextWindowmodel.contextWindow;assistant.maxTokensmodel.maxTokens;console.log(assistant 实例配置已更新);}}为什么需要更新 assistant 实例因为assistant.js在启动时加载配置// assistant.jsasyncinit(){constmodelawaitconfigManager.getDefaultModel();this.apiKeymodel.api_key;// ← 启动时加载this.apiUrlmodel.apiUrl;// ← 启动时加载this.modelNamemodel.name;// ← 启动时加载}如果不更新实例配置下次对话仍然使用旧配置设计 2下次对话重新构建 runtime// assistant.jsasyncchat(userMessage){// 每次对话都会重新构建 runtimeconstruntimeTextgetFullRuntimeInfo({workspace:this.workspace,model:this.modelName,// ← 使用新模型名称default_model:this.modelName,// ← 使用新模型名称...});// 构建提示词constpromptawaitbuildSystemPrompt(memoryResults)runtimeText;// 调用 APIconstresponseawaitfetch(this.apiUrl,...);// ← 使用新 API URL}3. 后端实现3.1 添加 configManager 导入文件src/server.js位置第 13 行附近importdbfrom./db.js;importconfigManagerfrom./core/config-manager.js;// ← 添加这行3.2 添加热切换函数文件src/server.js位置第 128 行await assistant.init();之后// 热切换功能constagentConfigsnewMap();/** * 重新加载 Agent 配置 */asyncfunctionreloadAgent(agentName){console.log(重新加载 Agent:${agentName});constagentawaitconfigManager.getAgentByName(agentName);constmodelawaitconfigManager.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(agentNamemain){assistant.apiKeymodel.api_key;assistant.apiUrlmodel.apiUrl;assistant.modelNamemodel.name;assistant.contextWindowmodel.contextWindow;assistant.maxTokensmodel.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 */asyncfunctionrefreshAllAgents(){console.log(刷新所有 Agent 配置...);configManager.refreshCache();constagentsawaitconfigManager.getAgents();for(constagentofagents){awaitreloadAgent(agent.name);}console.log(所有 Agent 配置已刷新);}// 初始化默认 AgentawaitreloadAgent(main);3.3 添加 API 接口文件src/server.js位置第 221 行左右http.createServer内部// SSE 端点/api/events之前// 热切换 API 接口 // 重新加载 Agent: POST /api/reload-agentif(url.pathname/api/reload-agentreq.methodPOST){try{constbodyawaitnewPromise((resolve){letdata;req.on(data,chunk{datachunk;});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;}awaitreloadAgent(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-allif(url.pathname/api/refresh-allreq.methodPOST){try{awaitrefreshAllAgents();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/agentsif(url.pathname/api/agentsreq.methodGET){try{constagentsawaitconfigManager.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/events4. 前端实现4.1 修改 agent_dialog.py文件desktop_client/agent_dialog.py位置save_agent方法defsave_agent(self):保存 Agent 并应用更改nameself.name_input.text().strip()# ... 其他代码 ...try:cursorconfig_manager.cursor connconfig_manager.connifself.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:importrequestsprint(f[save_agent] POST http://localhost:3002/api/reload-agent)print(f[save_agent] JSON: {{agentName: {name}}})responserequests.post(http://localhost:3002/api/reload-agent,json{agentName:name},timeout5)print(f[save_agent] 响应状态码{response.status_code})print(f[save_agent] 响应内容{response.text})ifresponse.json().get(success):print(f[save_agent] 热切换成功)else:print(f[save_agent] 热切换失败{response.json().get(error)})QMessageBox.warning(self,警告,热切换失败response.json().get(error,未知错误))exceptExceptionase:print(f[save_agent] API 调用失败{e})QMessageBox.warning(self,提示,配置已保存但热切换失败\n需要重启服务才能生效)# 关闭对话框无论成功还是失败都关闭self.accept()# 创建工作区目录仅新建 Agent 时ifnotself.agent_name:base_diros.path.dirname(os.path.dirname(os.path.abspath(__file__)))workspace_pathos.path.join(base_dir,workspace,name)os.makedirs(workspace_path,exist_okTrue)print(f[save_agent] 已创建工作区{workspace_path})exceptExceptionase:print(f[save_agent] 错误{e})ifUNIQUE constraint failedinstr(e):QMessageBox.critical(self,错误,fAgent{name}已存在)else:QMessageBox.critical(self,错误,f保存失败{e})4.2 修改 config_dialog.py文件desktop_client/config_dialog.py位置save_agent方法defsave_agent(self):保存 Agent 并应用更改nameself.agent_name.text().strip()providerself.agent_provider.currentText()modelself.agent_model.currentData()ifnotnameornotprovider:QMessageBox.warning(self,警告,名称和供应商必填)returntry:cursorconfig_manager.cursor cursor.execute(SELECT id FROM agents WHERE name ?,(name,))ifcursor.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:importrequestsprint(f[save_agent] POST http://localhost:3002/api/reload-agent)print(f[save_agent] JSON: {{agentName: {name}}})responserequests.post(http://localhost:3002/api/reload-agent,json{agentName:name},timeout5)print(f[save_agent] 响应状态码{response.status_code})print(f[save_agent] 响应内容{response.text})ifresponse.json().get(success):print(f[save_agent] 热切换成功)# 不显示成功提示else:print(f[save_agent] 热切换失败{response.json().get(error)})QMessageBox.warning(self,警告,热切换失败response.json().get(error,未知错误))exceptExceptionase:print(f[save_agent] API 调用失败{e})# 不阻止保存只显示警告QMessageBox.warning(self,提示,配置已保存但热切换失败\n需要重启服务才能生效)config_manager.refresh_cache()self.load_data()self.config_changed.emit()exceptExceptionase:QMessageBox.critical(self,错误,str(e))5. 测试验证5.1 重启后端服务# 停止当前服务CtrlC# 重新启动nodesrc/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 列表curlhttp://localhost:3002/api/agents预期输出{success:true,agents:[{id:1,name:main,nickname:阿财,...},...]}测试 2重新加载 Agentcurl-XPOST http://localhost:3002/api/reload-agent\-HContent-Type: application/json\-d{\agentName\:\main\}预期输出{success:true,message:Agent main 配置已重新加载}测试 3刷新所有 Agentcurl-XPOST http://localhost:3002/api/refresh-all预期输出{success:true,message:所有 Agent 配置已刷新}5.3 测试前端热切换步骤打开桌面客户端配置管理 → Agent 管理双击 main Agent修改大模型如火山方舟 → 阿里百炼点击保存等待 2-3 秒对话框自动关闭查看日志客户端日志[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: 会下次对话时重新构建 runtimeQ4: 可以批量切换所有 Agent 吗A: 可以调用/api/refresh-all接口Q5: 切换失败会影响保存吗A: 不会配置已保存到数据库热切换失败不影响保存如果文章对你有帮助请点个“关注”