Nanbeige4.1-3B WebUI定制化改造指南添加历史记录/导出功能/多模型切换扩展如果你已经成功部署了Nanbeige4.1-3B的WebUI并且用了一段时间可能会发现一个“痛点”每次对话都是独立的聊完就没了想回顾一下之前的精彩对话或者想把对话内容保存下来都得手动复制粘贴非常麻烦。更别提如果你想试试其他模型还得关掉当前服务重新配置启动另一个来回切换很不方便。这篇文章就是来解决这些“痛点”的。我将手把手带你对Nanbeige4.1-3B的官方WebUI进行一番“魔改”给它加上三个非常实用的功能对话历史记录自动保存你和模型的每一次对话随时可以查看、继续。对话内容导出一键将对话保存为Markdown或TXT文件方便整理和分享。多模型热切换在同一个WebUI界面上无需重启服务就能快速切换使用不同的模型。改造完成后你的WebUI将从一个基础的对话工具升级为一个功能更完善、体验更流畅的个人AI工作台。下面我们就开始动手。1. 改造前的准备理解现有代码结构在动刀之前我们先得看看“病人”长什么样。根据你提供的项目结构核心文件是/root/nanbeige-webui/webui.py。我们先来简单分析一下一个典型的、基于Gradio的LLM WebUI可能的结构你需要根据你的实际文件内容进行调整# webui.py 可能的核心部分示意 import gradio as gr from transformers import AutoModelForCausalLM, AutoTokenizer import torch # 1. 模型和分词器加载通常全局只加载一次 model_path /root/ai-models/nanbeige/Nanbeige4___1-3B tokenizer AutoTokenizer.from_pretrained(model_path, trust_remote_codeTrue) model AutoModelForCausalLM.from_pretrained( model_path, torch_dtypetorch.bfloat16, device_mapauto, trust_remote_codeTrue ) # 2. 核心的对话生成函数 def predict(message, history, temperature, top_p, max_tokens): history 参数Gradio ChatInterface 会自动传入格式为 [[用户消息1, AI回复1], [用户消息2, AI回复2], ...] 但基础版可能没利用这个参数做持久化。 # 构建对话格式 messages [] if history: # 如果有历史将其构建到 messages 中 for human, assistant in history: messages.append({role: user, content: human}) messages.append({role: assistant, content: assistant}) messages.append({role: user, content: message}) # Tokenization 和生成 input_ids tokenizer.apply_chat_template(messages, return_tensorspt).to(model.device) with torch.no_grad(): outputs model.generate( input_ids, max_new_tokensmax_tokens, temperaturetemperature, top_ptop_p, do_sampleTrue ) response tokenizer.decode(outputs[0][len(input_ids[0]):], skip_special_tokensTrue) # 返回新的回复Gradio会自动更新界面上的history return response # 3. 创建Gradio界面 with gr.Blocks() as demo: gr.Markdown(# Nanbeige4.1-3B Chat WebUI) chatbot gr.Chatbot(label对话历史) # 显示对话的组件 msg gr.Textbox(label输入你的问题) # 输入框 with gr.Row(): submit gr.Button(发送) clear gr.Button(清空对话) # 参数调节 with gr.Accordion(生成参数, openFalse): temperature gr.Slider(0.0, 2.0, value0.6, labelTemperature) top_p gr.Slider(0.0, 1.0, value0.95, labelTop-P) max_tokens gr.Slider(128, 4096, value2048, step128, label最大生成长度) # 事件绑定 def respond(message, chat_history, temp, top_p_val, max_tok): bot_message predict(message, chat_history, temp, top_p_val, max_tok) chat_history.append((message, bot_message)) return , chat_history # 清空输入框更新聊天历史 msg.submit(respond, [msg, chatbot, temperature, top_p, max_tokens], [msg, chatbot]) submit.click(respond, [msg, chatbot, temperature, top_p, max_tokens], [msg, chatbot]) clear.click(lambda: None, None, chatbot, queueFalse) # 清空界面历史 demo.launch(server_name0.0.0.0, server_port7860)关键点history参数在predict函数和界面交互中流转但它只存在于内存中页面一刷新或服务重启就没了。我们的目标是将这个history持久化到本地文件并增加管理它的界面。2. 功能一实现对话历史记录与加载我们需要一个地方来存储历史对话。一个简单有效的方法是为每次对话会话创建一个独立的文件以时间戳或会话ID命名。2.1 修改webui.py添加历史记录功能我们将在文件开头导入必要的库并添加历史记录管理类。# webui.py 修改版 - 添加历史记录功能 import gradio as gr from transformers import AutoModelForCausalLM, AutoTokenizer import torch import json import os from datetime import datetime from typing import List, Tuple, Optional # --- 新增历史记录管理器 --- class ChatHistoryManager: 管理对话历史记录的保存与加载 HISTORY_DIR ./chat_histories # 历史记录保存目录 def __init__(self): os.makedirs(self.HISTORY_DIR, exist_okTrue) def _get_session_filename(self, session_id: str None) - str: 生成会话文件名。如果未提供session_id则用时间戳生成一个新的。 if session_id is None: session_id datetime.now().strftime(session_%Y%m%d_%H%M%S) return os.path.join(self.HISTORY_DIR, f{session_id}.json) def save_history(self, history: List[Tuple[str, str]], session_id: str None) - str: 保存对话历史到JSON文件。返回保存的文件名session_id。 if session_id is None: session_id datetime.now().strftime(session_%Y%m%d_%H%M%S) filename self._get_session_filename(session_id) # 将Gradio格式的history [(user, bot), ...] 转换为可序列化的列表 serializable_history [{user: h[0], assistant: h[1]} for h in history] with open(filename, w, encodingutf-8) as f: json.dump({ session_id: session_id, created_at: datetime.now().isoformat(), history: serializable_history }, f, ensure_asciiFalse, indent2) print(f历史记录已保存至: {filename}) return session_id def load_history(self, session_id: str) - List[Tuple[str, str]]: 从JSON文件加载对话历史并转换回Gradio格式。 filename self._get_session_filename(session_id) if not os.path.exists(filename): return [] with open(filename, r, encodingutf-8) as f: data json.load(f) # 转换回 [(user, bot), ...] 格式 history [(item[user], item[assistant]) for item in data[history]] return history def list_sessions(self) - List[dict]: 列出所有历史会话。 sessions [] for fname in os.listdir(self.HISTORY_DIR): if fname.endswith(.json): filepath os.path.join(self.HISTORY_DIR, fname) try: with open(filepath, r, encodingutf-8) as f: data json.load(f) sessions.append({ id: data.get(session_id, fname.replace(.json, )), created_at: data.get(created_at, ), message_count: len(data.get(history, [])) }) except: continue # 按创建时间倒序排列 sessions.sort(keylambda x: x.get(created_at, ), reverseTrue) return sessions def delete_session(self, session_id: str) - bool: 删除指定的历史会话。 filename self._get_session_filename(session_id) if os.path.exists(filename): os.remove(filename) print(f已删除会话: {session_id}) return True return False # 初始化历史记录管理器 history_manager ChatHistoryManager() # --- 原有模型加载部分暂时不变--- model_path /root/ai-models/nanbeige/Nanbeige4___1-3B tokenizer AutoTokenizer.from_pretrained(model_path, trust_remote_codeTrue) model AutoModelForCausalLM.from_pretrained( model_path, torch_dtypetorch.bfloat16, device_mapauto, trust_remote_codeTrue ) # --- 修改预测函数使其能接收并利用历史 --- def predict_with_history(message, history, temperature, top_p, max_tokens): 支持历史上下文的预测函数 # 构建messages messages [] for human, assistant in history: messages.append({role: user, content: human}) messages.append({role: assistant, content: assistant}) messages.append({role: user, content: message}) # Tokenization 和生成与之前相同 input_ids tokenizer.apply_chat_template(messages, return_tensorspt).to(model.device) with torch.no_grad(): outputs model.generate( input_ids, max_new_tokensmax_tokens, temperaturetemperature, top_ptop_p, do_sampleTrue ) response tokenizer.decode(outputs[0][len(input_ids[0]):], skip_special_tokensTrue) return response # --- 构建Gradio界面 --- with gr.Blocks(titleNanbeige4.1-3B 增强版 WebUI, themegr.themes.Soft()) as demo: # 使用一个变量来跟踪当前会话ID current_session_id gr.State(valueNone) gr.Markdown( # Nanbeige4.1-3B 增强版对话界面 **新增功能**历史记录 | 导出对话 | 多模型切换 ) with gr.Row(): with gr.Column(scale3): # 主聊天区域 chatbot gr.Chatbot(label对话历史, height500) msg gr.Textbox(label输入消息, placeholder输入您的问题..., lines2) with gr.Row(): submit_btn gr.Button(发送, variantprimary) clear_btn gr.Button(清空当前对话) save_btn gr.Button(保存当前对话) # 参数调节区域 with gr.Accordion(生成参数设置, openFalse): temperature gr.Slider(0.0, 2.0, value0.6, labelTemperature (创造性)) top_p gr.Slider(0.0, 1.0, value0.95, labelTop-P (多样性)) max_tokens gr.Slider(128, 8192, value2048, step128, label最大生成长度) with gr.Column(scale1): # --- 新增历史记录管理侧边栏 --- with gr.Group(): gr.Markdown(### 历史记录管理) with gr.Row(): session_dropdown gr.Dropdown( choices[], label选择历史会话, interactiveTrue, info选择要加载的对话 ) refresh_btn gr.Button(, sizesm) load_btn gr.Button(加载选中会话, variantsecondary) delete_btn gr.Button(删除选中会话, variantstop) # 会话信息显示 session_info gr.Markdown(当前会话新对话) # --- 新增导出功能区域 --- with gr.Group(): gr.Markdown(### 导出对话) export_format gr.Radio( choices[Markdown (.md), 纯文本 (.txt), JSON (.json)], valueMarkdown (.md), label导出格式 ) export_btn gr.Button(导出当前对话, variantsecondary) export_status gr.Markdown() # --- 事件处理函数 --- def respond(message, chat_history, session_id, temp, top_p_val, max_tok): 处理用户发送消息 if not message.strip(): return , chat_history, session_id bot_message predict_with_history(message, chat_history, temp, top_p_val, max_tok) chat_history.append((message, bot_message)) # 每次响应后如果还没有session_id则生成一个临时的实际保存时才固定 if session_id is None: session_id datetime.now().strftime(session_%Y%m%d_%H%M%S) return , chat_history, session_id def save_current_chat(chat_history, session_id): 保存当前对话到文件 if not chat_history: return 没有对话内容可保存, session_id # 如果session_id是临时的通过保存操作获得一个正式的id saved_id history_manager.save_history(chat_history, session_id) # 更新下拉列表 session_list history_manager.list_sessions() choices [s[id] for s in session_list] return f对话已保存为: {saved_id}, saved_id, gr.Dropdown.update(choiceschoices) def load_selected_session(session_id): 加载选中的历史会话 if not session_id: return [], 请先选择一个会话, None history history_manager.load_history(session_id) info_text f**已加载会话**: {session_id}\\n消息数: {len(history)} return history, info_text, session_id def delete_selected_session(session_id): 删除选中的历史会话 if not session_id: return 请先选择一个会话, gr.Dropdown.update() success history_manager.delete_session(session_id) if success: # 更新下拉列表 session_list history_manager.list_sessions() choices [s[id] for s in session_list] if session_list else [] return f已删除会话: {session_id}, gr.Dropdown.update(choiceschoices, valueNone), [] else: return 删除失败会话可能不存在, gr.Dropdown.update(), [] def refresh_session_list(): 刷新历史会话列表 session_list history_manager.list_sessions() choices [s[id] for s in session_list] if session_list else [] return gr.Dropdown.update(choiceschoices) def export_chat(chat_history, session_id, format_choice): 导出对话内容 if not chat_history: return 没有对话内容可导出, # 根据格式生成内容 if format_choice Markdown (.md): content # 对话记录\\n\\n for i, (user, assistant) in enumerate(chat_history, 1): content f## 第{i}轮\\n content f**用户**: {user}\\n\\n content f**AI助手**: {assistant}\\n\\n---\\n\\n filename f{session_id or chat_export}.md elif format_choice 纯文本 (.txt): content 对话记录\\n *30 \\n for i, (user, assistant) in enumerate(chat_history, 1): content f[第{i}轮]\\n content f用户: {user}\\n content fAI助手: {assistant}\\n content -*30 \\n filename f{session_id or chat_export}.txt else: # JSON import json export_data { session_id: session_id or unknown, export_time: datetime.now().isoformat(), history: [{user: u, assistant: a} for u, a in chat_history] } content json.dumps(export_data, ensure_asciiFalse, indent2) filename f{session_id or chat_export}.json # 保存文件 export_dir ./exports os.makedirs(export_dir, exist_okTrue) filepath os.path.join(export_dir, filename) with open(filepath, w, encodingutf-8) as f: f.write(content) return f✅ 对话已导出至: {filepath}, # --- 绑定事件 --- # 发送消息 msg.submit(respond, [msg, chatbot, current_session_id, temperature, top_p, max_tokens], [msg, chatbot, current_session_id]) submit_btn.click(respond, [msg, chatbot, current_session_id, temperature, top_p, max_tokens], [msg, chatbot, current_session_id]) # 清空对话 clear_btn.click(lambda: ([], None), None, [chatbot, current_session_id]) # 保存对话 save_btn.click(save_current_chat, [chatbot, current_session_id], [export_status, current_session_id, session_dropdown]) # 历史记录管理 refresh_btn.click(refresh_session_list, None, session_dropdown) load_btn.click(load_selected_session, session_dropdown, [chatbot, session_info, current_session_id]) delete_btn.click(delete_selected_session, session_dropdown, [export_status, session_dropdown, chatbot]) # 导出对话 export_btn.click(export_chat, [chatbot, current_session_id, export_format], [export_status, export_status]) # 初始化时刷新会话列表 demo.load(refresh_session_list, None, session_dropdown) # 启动应用 if __name__ __main__: demo.launch(server_name0.0.0.0, server_port7860, shareFalse)关键改动说明新增ChatHistoryManager类负责所有历史记录的保存、加载、列表和删除操作。数据以JSON格式保存在./chat_histories/目录下。界面布局重组使用gr.Row()和gr.Column()将界面分为主聊天区和侧边功能栏。新增UI组件session_dropdown下拉框显示所有历史会话。refresh_btn/load_btn/delete_btn历史记录的刷新、加载、删除按钮。export_format/export_btn导出格式选择和导出按钮。新增事件处理函数如save_current_chat,load_selected_session,delete_selected_session,export_chat等将UI操作与后端逻辑连接起来。状态管理使用gr.State()来跟踪current_session_id确保在对话和保存过程中会话ID的一致性。2.2 测试历史记录功能保存修改后的webui.py。重启WebUI服务如果之前已运行cd /root/nanbeige-webui ./stop.sh # 如果提供了停止脚本 # 或者使用 supervisorctl supervisorctl restart nanbeige-webui访问http://你的服务器IP:7860。进行几轮对话。点击“保存当前对话”按钮侧边栏的“选择历史会话”下拉框应该会更新。尝试从下拉框选择一个历史会话点击“加载选中会话”之前的对话应该会显示在聊天框中。点击“删除选中会话”可以删除不需要的历史记录。至此历史记录功能已经实现。接下来我们实现第二个功能多模型切换。3. 功能二实现多模型热切换我们希望在不重启WebUI服务的情况下动态切换使用不同的语言模型。这需要我们对模型加载部分进行改造使其支持动态加载和卸载。3.1 修改webui.py添加模型管理器我们将创建一个ModelManager类来管理多个模型并修改界面以支持切换。# 在 webui.py 的开头添加必要的导入 # ... 之前的导入 ... import threading from queue import Queue # --- 新增模型管理器 --- class ModelManager: 管理多个模型的加载、切换和推理 def __init__(self): self.current_model_name None self.current_model None self.current_tokenizer None self.model_cache {} # 缓存已加载的模型 {model_name: (model, tokenizer)} self.lock threading.Lock() # 防止并发加载/切换 # 预定义的模型配置你可以在这里添加更多模型 self.model_configs { Nanbeige4.1-3B: { path: /root/ai-models/nanbeige/Nanbeige4___1-3B, dtype: torch.bfloat16, description: 3B参数擅长推理与对话 }, # 示例添加另一个模型请确保路径正确 # Qwen2.5-3B: { # path: /root/ai-models/qwen/Qwen2.5-3B, # dtype: torch.bfloat16, # description: Qwen2.5 3B版本 # } } def load_model(self, model_name: str): 加载指定名称的模型 with self.lock: if model_name self.current_model_name and self.current_model is not None: print(f模型 {model_name} 已是当前模型无需重新加载。) return True # 检查配置是否存在 if model_name not in self.model_configs: print(f错误未找到模型 {model_name} 的配置。) return False config self.model_configs[model_name] model_path config[path] dtype config[dtype] try: print(f正在加载模型: {model_name} ...) # 如果模型已在缓存中直接使用 if model_name in self.model_cache: print(f从缓存中恢复模型: {model_name}) self.current_model, self.current_tokenizer self.model_cache[model_name] else: # 加载新的模型和分词器 tokenizer AutoTokenizer.from_pretrained( model_path, trust_remote_codeTrue ) model AutoModelForCausalLM.from_pretrained( model_path, torch_dtypedtype, device_mapauto, trust_remote_codeTrue ) # 放入缓存 self.model_cache[model_name] (model, tokenizer) self.current_model, self.current_tokenizer model, tokenizer self.current_model_name model_name print(f模型切换成功: {model_name}) return True except Exception as e: print(f加载模型 {model_name} 失败: {e}) return False def get_current_model_info(self): 获取当前模型信息 if self.current_model_name: config self.model_configs.get(self.current_model_name, {}) return { name: self.current_model_name, path: config.get(path, 未知), description: config.get(description, 无描述) } return {name: 未加载, path: 未知, description: 无} def unload_model(self, model_name: str): 从缓存中卸载模型以释放显存可选 if model_name in self.model_cache and model_name ! self.current_model_name: print(f正在从缓存中卸载模型: {model_name}) model, tokenizer self.model_cache.pop(model_name) del model del tokenizer torch.cuda.empty_cache() return True return False # 初始化模型管理器 model_manager ModelManager() # 默认加载第一个模型 default_model list(model_manager.model_configs.keys())[0] model_manager.load_model(default_model) # --- 修改预测函数使用模型管理器 --- def predict_with_history_and_model(message, history, temperature, top_p, max_tokens): 支持历史和动态模型的预测函数 # 从模型管理器获取当前模型和分词器 model model_manager.current_model tokenizer model_manager.current_tokenizer if model is None or tokenizer is None: return 错误模型未正确加载请检查模型配置或切换模型。 # 构建messages与之前相同 messages [] for human, assistant in history: messages.append({role: user, content: human}) messages.append({role: assistant, content: assistant}) messages.append({role: user, content: message}) # Tokenization 和生成 input_ids tokenizer.apply_chat_template(messages, return_tensorspt).to(model.device) with torch.no_grad(): outputs model.generate( input_ids, max_new_tokensmax_tokens, temperaturetemperature, top_ptop_p, do_sampleTrue ) response tokenizer.decode(outputs[0][len(input_ids[0]):], skip_special_tokensTrue) return response # --- 修改Gradio界面添加模型切换组件 --- with gr.Blocks(titleNanbeige4.1-3B 增强版 WebUI, themegr.themes.Soft()) as demo: current_session_id gr.State(valueNone) gr.Markdown( # 多功能AI模型对话平台 **功能**历史记录 | 导出对话 | **多模型热切换** ) with gr.Row(): with gr.Column(scale3): # --- 新增模型切换和信息显示行 --- with gr.Row(): model_dropdown gr.Dropdown( choiceslist(model_manager.model_configs.keys()), valuedefault_model, label选择AI模型, interactiveTrue ) model_switch_btn gr.Button(切换模型, variantprimary, sizesm) model_info_display gr.Markdown(f**当前模型**: {default_model}) # 主聊天区域保持不变 chatbot gr.Chatbot(label对话历史, height500) msg gr.Textbox(label输入消息, placeholder输入您的问题..., lines2) with gr.Row(): submit_btn gr.Button(发送, variantprimary) clear_btn gr.Button(清空当前对话) save_btn gr.Button(保存当前对话) # 参数调节区域保持不变 with gr.Accordion(生成参数设置, openFalse): temperature gr.Slider(0.0, 2.0, value0.6, labelTemperature (创造性)) top_p gr.Slider(0.0, 1.0, value0.95, labelTop-P (多样性)) max_tokens gr.Slider(128, 8192, value2048, step128, label最大生成长度) with gr.Column(scale1): # 历史记录管理侧边栏保持不变 with gr.Group(): gr.Markdown(### 历史记录管理) with gr.Row(): session_dropdown gr.Dropdown(choices[], label选择历史会话, interactiveTrue) refresh_btn gr.Button(, sizesm) load_btn gr.Button(加载选中会话, variantsecondary) delete_btn gr.Button(删除选中会话, variantstop) session_info gr.Markdown(当前会话新对话) # 导出功能区域保持不变 with gr.Group(): gr.Markdown(### 导出对话) export_format gr.Radio( choices[Markdown (.md), 纯文本 (.txt), JSON (.json)], valueMarkdown (.md), label导出格式 ) export_btn gr.Button(导出当前对话, variantsecondary) export_status gr.Markdown() # --- 新增模型切换事件处理函数 --- def switch_model(model_name): 切换模型 if not model_name: return 请选择一个模型, model_info_display.update() success model_manager.load_model(model_name) if success: info model_manager.get_current_model_info() info_text f**当前模型**: {info[name]}\\n**描述**: {info[description]} return f✅ 已切换到模型: {model_name}, gr.Markdown.update(valueinfo_text) else: return f❌ 切换模型失败: {model_name}, model_info_display.update() # --- 修改响应函数使用新的预测函数 --- def respond(message, chat_history, session_id, temp, top_p_val, max_tok): if not message.strip(): return , chat_history, session_id # 使用新的支持模型切换的预测函数 bot_message predict_with_history_and_model(message, chat_history, temp, top_p_val, max_tok) chat_history.append((message, bot_message)) if session_id is None: session_id datetime.now().strftime(session_%Y%m%d_%H%M%S) return , chat_history, session_id # --- 绑定所有事件 --- # 发送消息使用新的respond函数 msg.submit(respond, [msg, chatbot, current_session_id, temperature, top_p, max_tokens], [msg, chatbot, current_session_id]) submit_btn.click(respond, [msg, chatbot, current_session_id, temperature, top_p, max_tokens], [msg, chatbot, current_session_id]) # 清空对话 clear_btn.click(lambda: ([], None), None, [chatbot, current_session_id]) # 保存对话保持不变 save_btn.click(save_current_chat, [chatbot, current_session_id], [export_status, current_session_id, session_dropdown]) # 历史记录管理保持不变 refresh_btn.click(refresh_session_list, None, session_dropdown) load_btn.click(load_selected_session, session_dropdown, [chatbot, session_info, current_session_id]) delete_btn.click(delete_selected_session, session_dropdown, [export_status, session_dropdown, chatbot]) # 导出对话保持不变 export_btn.click(export_chat, [chatbot, current_session_id, export_format], [export_status, export_status]) # --- 绑定模型切换事件 --- model_switch_btn.click(switch_model, model_dropdown, [export_status, model_info_display]) # 初始化 demo.load(refresh_session_list, None, session_dropdown) # 初始化模型信息显示 initial_info model_manager.get_current_model_info() initial_info_text f**当前模型**: {initial_info[name]}\\n**描述**: {initial_info[description]} demo.load(lambda: gr.Markdown.update(valueinitial_info_text), None, model_info_display) # 启动应用 if __name__ __main__: demo.launch(server_name0.0.0.0, server_port7860, shareFalse)3.2 配置你自己的模型关键部分在ModelManager类的model_configs字典。你需要根据你服务器上实际存放的模型路径来修改它。self.model_configs { Nanbeige4.1-3B: { path: /root/ai-models/nanbeige/Nanbeige4___1-3B, # 你的模型路径 dtype: torch.bfloat16, description: 3B参数擅长推理与对话 }, 你的第二个模型名称: { path: /path/to/your/second/model, # 第二个模型的本地路径 dtype: torch.bfloat16, # 或 torch.float16 description: 模型的简要描述 }, # ... 可以继续添加更多模型 }注意事项显存占用同时加载多个大模型会占用大量显存。ModelManager使用了缓存机制但切换模型时如果新模型不在缓存中仍需加载时间并占用额外显存。请根据你的GPU显存量力而行。模型兼容性确保你添加的模型与transformers库兼容并且支持apply_chat_template方法或者你需要调整predict_with_history_and_model函数中的对话构建逻辑。首次加载首次切换到一个新模型时会因为下载/加载模型而有一定延迟。3.3 测试多模型切换功能按照上述说明修改model_configs添加你已有的其他模型路径。保存webui.py并重启服务。访问WebUI你应该能在顶部看到一个“选择AI模型”的下拉框。从下拉框选择另一个模型点击“切换模型”按钮。观察状态提示如果切换成功model_info_display区域会更新。切换后继续进行对话模型应该会使用新切换的模型来生成回复。4. 功能整合与最终优化现在我们已经将三个功能整合到了一个WebUI中。为了更好的体验我们还可以做一些优化4.1 添加模型卸载按钮可选在侧边栏增加一个按钮用于手动释放不用的模型缓存节省显存。# 在模型切换行旁边或侧边栏添加 with gr.Row(): model_dropdown gr.Dropdown(...) model_switch_btn gr.Button(切换模型, variantprimary, sizesm) model_unload_btn gr.Button(释放模型缓存, variantsecondary, sizesm) # 新增 model_info_display gr.Markdown(...) # 添加对应的事件处理函数 def unload_unused_models(): 卸载非当前模型以释放显存 current model_manager.current_model_name unloaded [] for model_name in list(model_manager.model_cache.keys()): if model_name ! current: if model_manager.unload_model(model_name): unloaded.append(model_name) if unloaded: return f已释放模型缓存: {, .join(unloaded)} else: return 没有可释放的模型缓存。 # 绑定事件 model_unload_btn.click(unload_unused_models, None, export_status)4.2 完善错误处理在实际的预测函数predict_with_history_and_model中添加更详细的错误处理避免因为模型加载或生成问题导致整个界面卡死。def predict_with_history_and_model(message, history, temperature, top_p, max_tokens): 支持历史和动态模型的预测函数带错误处理 model model_manager.current_model tokenizer model_manager.current_tokenizer if model is None or tokenizer is None: return 错误模型未正确加载。请尝试切换模型或检查控制台日志。 try: # 构建messages messages [] for human, assistant in history: messages.append({role: user, content: human}) messages.append({role: assistant, content: assistant}) messages.append({role: user, content: message}) # Tokenization input_ids tokenizer.apply_chat_template( messages, return_tensorspt, add_generation_promptTrue # 确保添加了生成提示 ).to(model.device) # 生成 with torch.no_grad(): outputs model.generate( input_ids, max_new_tokensmax_tokens, temperaturetemperature, top_ptop_p, do_sampleTrue, pad_token_idtokenizer.eos_token_id # 防止警告 ) # 解码 response tokenizer.decode( outputs[0][len(input_ids[0]):], skip_special_tokensTrue ) return response except torch.cuda.OutOfMemoryError: return 错误GPU显存不足。请尝试减少生成长度(max_tokens)或切换/释放其他模型。 except Exception as e: return f生成过程中出现错误: {str(e)}4.3 最终的项目结构完成所有修改后你的项目目录可能如下所示/root/nanbeige-webui/ ├── webui.py # 改造后的主程序文件 ├── start.sh # 启动脚本 ├── stop.sh # 停止脚本 ├── supervisord.conf # Supervisor配置 ├── requirements.txt # 依赖文件确保包含 gradio4.0 ├── chat_histories/ # 自动创建的存放历史会话JSON文件 │ ├── session_20250225_143022.json │ └── ... ├── exports/ # 自动创建的存放导出的对话文件 │ ├── session_20250225_143022.md │ └── ... └── README.md # 可选的更新说明文档5. 总结与使用建议通过以上步骤我们成功地将一个基础的Nanbeige4.1-3B WebUI改造成为一个具备历史记录、对话导出和多模型热切换三大增强功能的AI对话平台。5.1 核心改造回顾历史记录通过ChatHistoryManager类将内存中的对话持久化到本地JSON文件并提供了加载、删除、列表查看的完整界面。对话导出在历史记录的基础上增加了将对话内容一键导出为 Markdown、纯文本或 JSON 格式的功能便于知识整理和分享。多模型切换通过ModelManager类实现了模型的动态加载和缓存管理。你可以在一个WebUI中轻松切换使用不同的开源大模型无需重启服务。5.2 使用建议与后续扩展模型管理在model_configs字典中仔细配置每个模型的本地路径。建议从参数量小、显存占用低的模型开始测试。显存监控如果你的GPU显存有限注意不要同时配置太多大模型。可以善用“释放模型缓存”功能。功能扩展这个框架具有良好的扩展性。你还可以考虑添加以下功能参数预设保存几组常用的temperature、top_p等参数组合一键应用。对话重命名为历史会话提供自定义名称而不是只用时间戳。模型信息页为每个模型添加更详细的技术说明和示例。流式输出将生成过程改为流式streaming提升交互体验。5.3 如何部署与更新备份在修改webui.py前建议先备份原文件。安装依赖确保你的环境已安装gradio版本建议4.0以上。pip install gradio4.0.0重启服务每次修改webui.py后需要重启Gradio服务才能生效。# 使用你的管理脚本或Supervisor cd /root/nanbeige-webui ./stop.sh ./start.sh # 或 supervisorctl restart nanbeige-webui查看日志如果遇到问题查看日志是首要的排查手段。tail -f /var/log/supervisor/nanbeige-webui-stderr.log希望这份详细的改造指南能帮助你打造一个更加强大、便捷的个人AI对话环境。动手试试吧享受自定义和扩展开源工具带来的乐趣获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。