feat: 模型获取支持 CLI 端点回退,同族优先 chat 后降级 cli

This commit is contained in:
fawney19
2026-02-09 11:37:41 +08:00
parent 8673ed1459
commit 8702786fa2
2 changed files with 55 additions and 16 deletions

View File

@@ -20,8 +20,12 @@ from src.utils.ssl_utils import get_ssl_context
# 并发请求限制
MAX_CONCURRENT_REQUESTS = 5
# 只对这些基础 endpoint signature 获取模型列表CLI 使用相同的上游 API
MODEL_FETCH_FORMATS = ["openai:chat", "claude:chat", "gemini:chat"]
# 模型获取格式优先级:同族内优先使用 chat 端点,若无则回退到 cli 端点
MODEL_FETCH_FORMAT_PRIORITY: list[tuple[str, ...]] = [
("openai:chat", "openai:cli"),
("claude:chat", "claude:cli"),
("gemini:chat", "gemini:cli"),
]
# Return tuple signature:
# (models, errors, has_success, upstream_metadata)
@@ -163,17 +167,21 @@ def build_all_format_configs(
if not format_to_endpoint:
return []
# 只对基础 API 格式获取模型CLI 格式使用相同的上游 API
return [
{
"api_key": api_key_value,
"base_url": ep.base_url,
"api_format": fmt,
"extra_headers": get_extra_headers_from_endpoint(ep),
}
for fmt in MODEL_FETCH_FORMATS
if (ep := format_to_endpoint.get(fmt)) is not None
]
# 同族内优先使用 chat 端点,若无则回退到 cli 端点
configs: list[dict] = []
for candidates in MODEL_FETCH_FORMAT_PRIORITY:
fmt = next((f for f in candidates if f in format_to_endpoint), None)
if fmt is not None:
ep = format_to_endpoint[fmt]
configs.append(
{
"api_key": api_key_value,
"base_url": ep.base_url,
"api_format": fmt,
"extra_headers": get_extra_headers_from_endpoint(ep),
}
)
return configs
async def fetch_models_from_endpoints(

View File

@@ -47,15 +47,46 @@ def test_only_configured_formats_are_included() -> None:
assert configs[0]["base_url"] == "https://betterclau.de/claude/api.freekey.site"
def test_cli_format_only_is_skipped() -> None:
"""如果只配置了 CLI 格式(不在 MODEL_FETCH_FORMATS 中),应返回空列表"""
def test_cli_format_only_fallback() -> None:
"""只配置了 CLI 格式时,应回退使用 CLI 端点获取模型"""
format_to_endpoint = {
"openai:cli": _make_endpoint("https://api.openai.example.com"),
}
configs = build_all_format_configs("sk-test-key", format_to_endpoint) # type: ignore[arg-type]
assert configs == []
assert len(configs) == 1
assert configs[0]["api_format"] == "openai:cli"
assert configs[0]["base_url"] == "https://api.openai.example.com"
def test_chat_takes_priority_over_cli() -> None:
"""同族同时存在 chat 和 cli 端点时,应优先使用 chat 端点。"""
format_to_endpoint = {
"openai:chat": _make_endpoint("https://chat.openai.example.com"),
"openai:cli": _make_endpoint("https://cli.openai.example.com"),
}
configs = build_all_format_configs("sk-test-key", format_to_endpoint) # type: ignore[arg-type]
assert len(configs) == 1
assert configs[0]["api_format"] == "openai:chat"
assert configs[0]["base_url"] == "https://chat.openai.example.com"
def test_mixed_chat_and_cli_fallback() -> None:
"""一个族有 chat、另一个族只有 cli应各自正确选择。"""
format_to_endpoint = {
"openai:chat": _make_endpoint("https://chat.openai.example.com"),
"claude:cli": _make_endpoint("https://cli.claude.example.com"),
}
configs = build_all_format_configs("sk-test-key", format_to_endpoint) # type: ignore[arg-type]
assert len(configs) == 2
by_fmt = {c["api_format"]: c for c in configs}
assert by_fmt["openai:chat"]["base_url"] == "https://chat.openai.example.com"
assert by_fmt["claude:cli"]["base_url"] == "https://cli.claude.example.com"
def test_empty_format_to_endpoint() -> None: