feat: OpenAI 多模型注册表 + /model 命令,告别手改配置文件

痛点:很多模型都是 OpenAI 格式,但换模型只能手改 config.json 的单槽位,
且只能存一个。新增模型注册表与 /model 命令族:

- 新增 xcli_core/model_registry.py:在 api.engines.openai 下维护
  models 列表(name + base_url + api_key + model)与 current 指针,全部落盘。
  向后兼容:无 models 时把扁平 api_key/base_url/model 当作单条隐式模型。
- OpenaiAI 接入:__init__ 应用注册表 current 模型;新增
  set_registry_model / list_registry_models / add_model / remove_model /
  current_model_name。注意保留引擎原有的轻量 set_model(只改 model 名)
  与 list_models(查 API /models 端点),新方法与它们不重名、互不干扰。
- 新增 /model 命令族(仅 openai 引擎):
    /model            列出已配置模型(* 标记当前)
    /model <名称>     切换当前模型并持久化
    /model add        交互式录入(名称/base_url/api_key/model),回车即存并切换
    /model rm <名称>  删除(删当前则回退到列表第一个)
- 启动横幅引擎名后显示当前模型;紧凑提示与 /help 速查表均补 /model 入口。
- 测试:新增 tests/test_model_registry.py(13 项,覆盖兼容/增删/切换/落盘/
  引擎接入/CLI 路由,全程临时配置不污染真实 config.json)。
  全量 230 passed(原 217 passed)。

交互实测:/model add → 已添加并切换到 gpt4o;/model gpt4o → 已切换;
/model rm gpt4o → 回退到 xophunyuan7bmt,配置结构与内容完好。
This commit is contained in:
shuiyu67 committed 2026-08-05 16:12:35 +08:00
1 parent 3a86da7444
commit 1383d32f8b
7 files changed
+493 -2

No files matched your search

+65
View File
@@ -19,6 +19,11 @@ except Exception:
def get_system_config(key, default=None):
return default
try:
from xcli_core import model_registry
except Exception:
model_registry = None
try:
from xcli_core.verbose import vprint
except Exception:
@@ -140,6 +145,9 @@ OpenAI 兼容格式引擎帮助信息
self.conversation_history = []
self.shared_conversation_history = None
# 应用注册表中的当前模型(覆盖扁平默认配置,向后兼容)
self._apply_registry_model()
# 最近一次请求的 token 用量(供 token 感知压缩使用)
self.last_prompt_tokens = None
@@ -155,6 +163,63 @@ OpenAI 兼容格式引擎帮助信息
else:
vprint(f"{Fore.GREEN}OpenAI 兼容引擎已启用 (base_url: {self.base_url}){Style.RESET_ALL}")
# ── 模型注册表(多模型在线切换)──
def _apply_registry_model(self):
"""若注册表存在 current 模型,用它覆盖 __init__ 读到的扁平默认配置。"""
if not model_registry:
return
try:
m = model_registry.get_model()
if m:
self.api_key = m.get("api_key", self.api_key)
self.base_url = (m.get("base_url") or self.base_url).rstrip('/')
self.model = m.get("model", self.model)
except Exception:
pass
def list_registry_models(self):
"""返回 (models, current_name),供 CLI 列出已配置模型。"""
if not model_registry:
return [], None
return model_registry.list_models()
def current_model_name(self):
"""当前激活模型在注册表中的名称(兼容单槽位时返回 model 值)。"""
if not model_registry:
return self.model
try:
models, current = model_registry.list_models()
return current or self.model
except Exception:
return self.model
def set_registry_model(self, name):
"""切换到注册表中指定模型(含 base_url/api_key/model)并持久化 current。
成功返回 True。"""
if not model_registry:
return False
m = model_registry.get_model(name)
if not m:
return False
self.api_key = m.get("api_key", "")
self.base_url = (m.get("base_url") or "").rstrip('/')
self.model = m.get("model", "")
model_registry.set_current(name)
return True
def add_model(self, entry):
"""新增/更新模型并持久化。entry: {name, base_url, api_key, model}。"""
if not model_registry:
return None
return model_registry.add_model(entry)
def remove_model(self, name):
"""删除模型并持久化。成功返回 True。"""
if not model_registry:
return False
return model_registry.remove_model(name)
def _get_headers(self):
"""构建请求头"""
headers = {