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 MAX_CONCURRENT_REQUESTS = 5
# 只对这些基础 endpoint signature 获取模型列表CLI 使用相同的上游 API # 模型获取格式优先级:同族内优先使用 chat 端点,若无则回退到 cli 端点
MODEL_FETCH_FORMATS = ["openai:chat", "claude:chat", "gemini:chat"] MODEL_FETCH_FORMAT_PRIORITY: list[tuple[str, ...]] = [
("openai:chat", "openai:cli"),
("claude:chat", "claude:cli"),
("gemini:chat", "gemini:cli"),
]
# Return tuple signature: # Return tuple signature:
# (models, errors, has_success, upstream_metadata) # (models, errors, has_success, upstream_metadata)
@@ -163,17 +167,21 @@ def build_all_format_configs(
if not format_to_endpoint: if not format_to_endpoint:
return [] return []
# 只对基础 API 格式获取模型CLI 格式使用相同的上游 API # 同族内优先使用 chat 端点,若无则回退到 cli 端点
return [ configs: list[dict] = []
{ for candidates in MODEL_FETCH_FORMAT_PRIORITY:
"api_key": api_key_value, fmt = next((f for f in candidates if f in format_to_endpoint), None)
"base_url": ep.base_url, if fmt is not None:
"api_format": fmt, ep = format_to_endpoint[fmt]
"extra_headers": get_extra_headers_from_endpoint(ep), configs.append(
} {
for fmt in MODEL_FETCH_FORMATS "api_key": api_key_value,
if (ep := format_to_endpoint.get(fmt)) is not None "base_url": ep.base_url,
] "api_format": fmt,
"extra_headers": get_extra_headers_from_endpoint(ep),
}
)
return configs
async def fetch_models_from_endpoints( 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" assert configs[0]["base_url"] == "https://betterclau.de/claude/api.freekey.site"
def test_cli_format_only_is_skipped() -> None: def test_cli_format_only_fallback() -> None:
"""如果只配置了 CLI 格式(不在 MODEL_FETCH_FORMATS 中),应返回空列表""" """只配置了 CLI 格式时,应回退使用 CLI 端点获取模型"""
format_to_endpoint = { format_to_endpoint = {
"openai:cli": _make_endpoint("https://api.openai.example.com"), "openai:cli": _make_endpoint("https://api.openai.example.com"),
} }
configs = build_all_format_configs("sk-test-key", format_to_endpoint) # type: ignore[arg-type] 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: def test_empty_format_to_endpoint() -> None: