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
@@ -19,6 +19,12 @@ except Exception:
|
||||
def get_system_config(key, default=None):
|
||||
return default
|
||||
|
||||
try:
|
||||
from xcli_core.verbose import vprint
|
||||
except Exception:
|
||||
def vprint(*args, **kwargs):
|
||||
pass
|
||||
|
||||
try:
|
||||
from xcli_core.tool_result import normalize_tool_text
|
||||
except Exception:
|
||||
@@ -147,7 +153,7 @@ OpenAI 兼容格式引擎帮助信息
|
||||
if not self.base_url:
|
||||
print(f"{Fore.YELLOW}警告: openai 引擎的 base_url 未设置{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{Fore.GREEN}OpenAI 兼容引擎已启用 (base_url: {self.base_url}){Style.RESET_ALL}")
|
||||
vprint(f"{Fore.GREEN}OpenAI 兼容引擎已启用 (base_url: {self.base_url}){Style.RESET_ALL}")
|
||||
|
||||
def _get_headers(self):
|
||||
"""构建请求头"""
|
||||
@@ -167,6 +173,26 @@ OpenAI 兼容格式引擎帮助信息
|
||||
url = url.rstrip('/') + '/chat/completions'
|
||||
return url
|
||||
|
||||
# ── FC 能力探测缓存 ──
|
||||
|
||||
def _fc_supported(self):
|
||||
"""当前 (base_url, model) 是否支持 function calling(未探测过默认支持)"""
|
||||
try:
|
||||
from xcli_core.fc_tools import supports_fc
|
||||
return supports_fc(self.base_url, self.model)
|
||||
except Exception:
|
||||
return True
|
||||
|
||||
def _mark_fc_unsupported(self):
|
||||
"""降级成功后记住该模型不支持 FC,后续请求不再带 tools"""
|
||||
try:
|
||||
from xcli_core.fc_tools import mark_fc_unsupported
|
||||
if mark_fc_unsupported(self.base_url, self.model):
|
||||
print(f"{Fore.CYAN}已记住 {self.model} 不支持 FC,后续请求将跳过工具字段"
|
||||
f"(/fc reset 可清除){Style.RESET_ALL}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def generate_response(self, user_input, tool_results=None, system_prompt=None, tools=None):
|
||||
"""
|
||||
生成 AI 响应
|
||||
@@ -196,7 +222,8 @@ OpenAI 兼容格式引擎帮助信息
|
||||
}
|
||||
|
||||
# ── Function Calling: 注入工具定义(统一纯官方格式)──
|
||||
if tools:
|
||||
# 已探测过不支持 FC 的模型直接跳过 tools,省掉每次白撞一次 500
|
||||
if tools and self._fc_supported():
|
||||
pure_tools = []
|
||||
for t in tools:
|
||||
if not isinstance(t, dict):
|
||||
@@ -260,12 +287,13 @@ OpenAI 兼容格式引擎帮助信息
|
||||
return f"API 端点不存在,请检查 base_url 是否正确: {api_url}"
|
||||
elif response.status_code == 400:
|
||||
# 工具定义可能不兼容,降级为无工具重试
|
||||
if tools and "tools" in str(response.text).lower():
|
||||
if body.get("tools") and "tools" in str(response.text).lower():
|
||||
print(f"{Fore.YELLOW}FC 不支持,降级为普通模式{Style.RESET_ALL}")
|
||||
body.pop("tools", None)
|
||||
body.pop("tool_choice", None)
|
||||
retry = requests.post(url=api_url, json=body, headers=self._get_headers(), timeout=None)
|
||||
if retry.status_code == 200:
|
||||
self._mark_fc_unsupported()
|
||||
rd = retry.json()
|
||||
if "choices" in rd and rd["choices"]:
|
||||
ai_response = rd["choices"][0].get("message", {}).get("content", "")
|
||||
@@ -275,12 +303,13 @@ OpenAI 兼容格式引擎帮助信息
|
||||
# 500 等错误:部分服务(如讯飞星火经 one-api 转发)不支持 FC 工具,
|
||||
# 会直接报 500 Invalid Params —— 尝试去掉 tools 降级重试一次
|
||||
lower = str(response.text).lower()
|
||||
if tools and ("xunfei" in lower or "invalid params" in lower or "requestparamserror" in lower):
|
||||
if body.get("tools") and ("xunfei" in lower or "invalid params" in lower or "requestparamserror" in lower):
|
||||
print(f"{Fore.YELLOW}该接口可能不支持 FC 工具,降级为普通模式重试...{Style.RESET_ALL}")
|
||||
body.pop("tools", None)
|
||||
body.pop("tool_choice", None)
|
||||
retry = requests.post(url=api_url, json=body, headers=self._get_headers(), timeout=None)
|
||||
if retry.status_code == 200:
|
||||
self._mark_fc_unsupported()
|
||||
rd = retry.json()
|
||||
retry_usage = rd.get("usage", {})
|
||||
if isinstance(retry_usage, dict) and retry_usage.get("prompt_tokens"):
|
||||
|
||||
Reference in new issue
Block a user