perf: 启动输出瘦身 + FC 能力探测缓存,修复测试套件失效
自用体验(同时以人类和被接入 AI 的视角)暴露出三个问题,一并处理:
1. 启动太吵:117 行 / 16983 字符的开屏,人还没开始用就先刷屏
- 完整 ASCII 开机动画改为 --logo / --banner 按需召唤,默认两行紧凑标识
- 21 行命令说明折叠成一行「/help 全部命令 · /quit 退出 · ...」,
完整速查表挪进 /help(新增 show_quick_commands)
- 插件/引擎/工具的逐条加载日志改走 vprint,由 --verbose / -v /
XCLI_VERBOSE=1 召唤;工具加载汇总成一行
- 日志控制台 handler 降到 WARNING(文件仍留全量 INFO),并静音
httpx/httpcore/urllib3 等第三方心跳日志
=> 默认启动 10 行 / 475 字符,--verbose 仍可看到全部 36 行细节
2. 不支持 FC 的模型每次会话都白撞一发 500
讯飞 xophunyuan7bmt 不支持 Function Calling,请求带 tools 字段必报
500 Invalid Params,虽有降级重试兜底,但每个新会话都要先错一次。
- 新增 xcli_core/fc_tools 能力缓存(按 base_url::model 落盘 .fc_support.json)
- 降级重试成功后记住该模型不支持 FC,后续直接不发 tools
- 降级也失败时不写缓存,避免把别的故障误判成 FC 问题
- 新增 /fc [reset] 查看与清除缓存
3. diff 弹窗模式每次启动都要重新问一遍
- 首次选择后经 config.json 持久化,新增 /diff [popup|inline] 随时切换
测试:
- 新增 tests/test_startup_and_fc.py(27 项,含假 HTTP 验证降级与缓存链路)
- 修复 tests/test_plugins.py 长期失效:插件入口类早已由 Plugin 改名 Liugin,
8 处 fixture 未跟进导致 65 个用例直接 error;断言也未适配 ToolResult 返回值
与「❌」错误前缀。改为 plugin_class() / as_text() / is_error() 语义判断。
- 全量:217 passed(此前 152 passed + 65 error + 27 failed)
This commit is contained in:
1 parent
d2ac6896ad
commit
3a86da7444
12 files changed
+728
-167
No files matched your search
@@ -2,7 +2,9 @@
|
||||
Function Calling 工具格式转换
|
||||
将 MCP 插件定义转为 OpenAI FC 格式,收集所有可用工具
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
import threading
|
||||
from typing import List, Dict, Optional
|
||||
|
||||
|
||||
@@ -248,3 +250,81 @@ def _get_builtin_tools() -> List[Dict]:
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
# FC 能力探测缓存
|
||||
#
|
||||
# 部分渠道/模型(如讯飞星火经 one-api 转发的 xophunyuan* 系列)
|
||||
# 不支持 function calling,请求体里只要带上非空 tools 字段就会直接
|
||||
# 返回 500 RequestParamsError:Invalid Params。
|
||||
# 引擎捕获到这种错误后会去掉 tools 降级重试;本缓存把"该模型不支持 FC"
|
||||
# 这件事记下来,后续请求直接跳过 tools,避免每次冷启动都白撞一次 500。
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
|
||||
_FC_CACHE_FILE = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__))), ".fc_support.json"
|
||||
)
|
||||
_fc_cache = None
|
||||
_fc_lock = threading.Lock()
|
||||
|
||||
|
||||
def _fc_key(base_url, model) -> str:
|
||||
"""缓存键:base_url + model(同一渠道下不同模型能力可能不同)"""
|
||||
return f"{(base_url or '').rstrip('/')}::{model or ''}"
|
||||
|
||||
|
||||
def _load_fc_cache() -> dict:
|
||||
global _fc_cache
|
||||
if _fc_cache is not None:
|
||||
return _fc_cache
|
||||
try:
|
||||
with open(_FC_CACHE_FILE, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
_fc_cache = data if isinstance(data, dict) else {}
|
||||
except (FileNotFoundError, json.JSONDecodeError, OSError, ValueError):
|
||||
_fc_cache = {}
|
||||
return _fc_cache
|
||||
|
||||
|
||||
def _save_fc_cache(cache: dict):
|
||||
try:
|
||||
with open(_FC_CACHE_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(cache, f, ensure_ascii=False, indent=2)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def supports_fc(base_url, model) -> bool:
|
||||
"""该 (base_url, model) 是否支持 FC。未探测过时默认 True(乐观,先试一次)"""
|
||||
return _load_fc_cache().get(_fc_key(base_url, model)) is not False
|
||||
|
||||
|
||||
def mark_fc_unsupported(base_url, model) -> bool:
|
||||
"""标记该 (base_url, model) 不支持 FC 并持久化。首次标记返回 True"""
|
||||
with _fc_lock:
|
||||
cache = _load_fc_cache()
|
||||
key = _fc_key(base_url, model)
|
||||
if cache.get(key) is False:
|
||||
return False
|
||||
cache[key] = False
|
||||
_save_fc_cache(cache)
|
||||
return True
|
||||
|
||||
|
||||
def reset_fc_cache(base_url=None, model=None) -> int:
|
||||
"""清除 FC 探测缓存。不传参数则全清,返回清除的条目数"""
|
||||
with _fc_lock:
|
||||
cache = _load_fc_cache()
|
||||
if base_url is None and model is None:
|
||||
count = len(cache)
|
||||
cache.clear()
|
||||
else:
|
||||
count = 1 if cache.pop(_fc_key(base_url, model), None) is not None else 0
|
||||
_save_fc_cache(cache)
|
||||
return count
|
||||
|
||||
|
||||
def get_fc_cache() -> dict:
|
||||
"""返回 FC 探测缓存副本(供 /fc 命令展示)"""
|
||||
return dict(_load_fc_cache())
|
||||
Reference in new issue
Block a user