feat: Antigravity 上游模型按 tier 排序逐个获取与前端单 Key 查询

- 后端 Antigravity Provider 获取上游模型改为按 tier/可用性排序后逐个尝试,成功即停止
- 新增 Provider 级别缓存避免重复请求
- 前端编辑 Key 允许模型时改为传入当前 Key ID 查询对应可用模型
This commit is contained in:
fawney19
2026-02-10 11:24:22 +08:00
parent 91cf93d133
commit fc1ad44346
2 changed files with 174 additions and 4 deletions

View File

@@ -16,6 +16,7 @@ from sqlalchemy.orm import Session, joinedload
from src.config.constants import TimeoutDefaults
from src.core.api_format import get_extra_headers_from_endpoint
from src.core.cache_service import CacheService
from src.core.crypto import crypto_service
from src.core.logger import logger
from src.core.provider_types import ProviderType
@@ -23,6 +24,7 @@ from src.database.database import get_db
from src.models.database import Provider, ProviderEndpoint, User
from src.services.model.fetch_scheduler import (
MODEL_FETCH_HTTP_TIMEOUT,
UPSTREAM_MODELS_CACHE_TTL_SECONDS,
get_upstream_models_from_cache,
set_upstream_models_to_cache,
)
@@ -40,6 +42,58 @@ from src.utils.ssl_utils import get_ssl_context
router = APIRouter(prefix="/api/admin/provider-query", tags=["Provider Query"])
# ---------------------------------------------------------------------------
# Provider-level upstream models cache (for multi-key ordered fetch)
# ---------------------------------------------------------------------------
async def _get_provider_upstream_models_cache(provider_id: str) -> list[dict] | None:
cache_key = f"upstream_models_provider:{provider_id}"
cached = await CacheService.get(cache_key)
return cached # type: ignore[return-value]
async def _set_provider_upstream_models_cache(provider_id: str, models: list[dict]) -> None:
cache_key = f"upstream_models_provider:{provider_id}"
await CacheService.set(cache_key, models, UPSTREAM_MODELS_CACHE_TTL_SECONDS)
# ---------------------------------------------------------------------------
# Antigravity: tier / availability sorting for upstream model fetching
# ---------------------------------------------------------------------------
# tier 排序权重(数值越大越优先)
_ANTIGRAVITY_TIER_PRIORITY: dict[str, int] = {"ultra": 3, "pro": 2, "free": 1}
def _antigravity_sort_keys(api_keys: list[Any]) -> list[Any]:
"""按 tier/可用性对 Antigravity Key 降序排列。
预计算排序键避免排序过程中重复解密。
排序维度(优先级从高到低):
1. 可用性: oauth_invalid_at 为空 = 1优先, 非空 = 0
2. 付费级别: Ultra=3 > Pro=2 > Free=1 > 未知=0
"""
sort_keys: list[tuple[tuple[int, int], Any]] = []
for api_key in api_keys:
availability = 0 if getattr(api_key, "oauth_invalid_at", None) else 1
tier_weight = 0
encrypted_auth_config = getattr(api_key, "auth_config", None)
if encrypted_auth_config:
try:
decrypted = crypto_service.decrypt(encrypted_auth_config)
auth_config = json.loads(decrypted)
tier = (auth_config.get("tier") or "").lower()
tier_weight = _ANTIGRAVITY_TIER_PRIORITY.get(tier, 0)
except Exception:
pass
sort_keys.append(((availability, tier_weight), api_key))
sort_keys.sort(key=lambda x: x[0], reverse=True)
return [item[1] for item in sort_keys]
# ---------------------------------------------------------------------------
# Key Auth Resolution (shared by multi-key and single-key paths)
# ---------------------------------------------------------------------------
@@ -193,7 +247,17 @@ async def query_available_models(
if not active_keys:
raise HTTPException(status_code=400, detail="No active API Key found for this provider")
# 并发获取所有 Key 的模型
# Antigravity: 按 tier/可用性排序后逐个尝试,成功即停止
provider_type = str(getattr(provider, "provider_type", "") or "").lower()
if provider_type == ProviderType.ANTIGRAVITY:
return await _fetch_models_antigravity_ordered(
provider=provider,
active_keys=active_keys,
format_to_endpoint=format_to_endpoint,
force_refresh=request.force_refresh,
)
# 其他类型: 并发获取所有 Key 的模型
async def fetch_for_key(api_key: Any) -> Any:
# 非强制刷新时,先检查缓存
if not request.force_refresh:
@@ -323,6 +387,113 @@ def _aggregate_models_by_id(models: list[dict]) -> list[dict]:
return result
async def _fetch_models_antigravity_ordered(
provider: Provider,
active_keys: list[Any],
format_to_endpoint: dict[str, EndpointFetchConfig],
force_refresh: bool,
) -> Any:
"""Antigravity: 按账号 tier/可用性排序后逐个尝试获取上游模型,成功即停止。
排序规则(降序):
1. 可用性: 无 oauth_invalid_at 的账号优先
2. 付费级别: Ultra > Pro > Free
"""
sorted_keys = _antigravity_sort_keys(active_keys)
# 非强制刷新时,先检查 Provider 级别缓存
if not force_refresh:
cached_models = await _get_provider_upstream_models_cache(provider.id)
if cached_models is not None:
safe_models = [m for m in cached_models if isinstance(m, dict)]
unique_models = _aggregate_models_by_id(safe_models)
if unique_models:
logger.info(
"Antigravity 上游模型命中 Provider 缓存: provider={}, models={}",
provider.name,
len(unique_models),
)
return {
"success": True,
"data": {
"models": unique_models,
"error": None,
"from_cache": True,
"keys_total": len(active_keys),
"keys_cached": 1,
"keys_fetched": 0,
},
"provider": {"id": provider.id, "name": provider.name},
}
all_errors: list[str] = []
for api_key in sorted_keys:
key_label = api_key.name or api_key.id
# 实时获取
try:
api_key_value, auth_config = await _resolve_key_auth(api_key, provider)
except _KeyAuthError as e:
all_errors.append(f"Key {key_label}: {e.message}")
continue
fetch_ctx = UpstreamModelsFetchContext(
provider_type=str(getattr(provider, "provider_type", "") or ""),
api_key_value=str(api_key_value or ""),
format_to_endpoint=format_to_endpoint,
proxy_config=getattr(provider, "proxy", None),
auth_config=auth_config,
)
models, errors, has_success, _meta = await fetch_models_for_key(
fetch_ctx, timeout_seconds=MODEL_FETCH_HTTP_TIMEOUT
)
if not has_success:
err = f"Key {key_label}: {'; '.join(errors)}" if errors else f"Key {key_label}: failed"
all_errors.append(err)
logger.info("Antigravity 上游模型获取失败, 尝试下一个账号: {}", err)
continue
# 成功: 聚合并写入 Provider 级别缓存
unique_models = _aggregate_models_by_id([m for m in models if isinstance(m, dict)])
if unique_models:
await _set_provider_upstream_models_cache(provider.id, unique_models)
logger.info(
"Antigravity 上游模型获取成功: key={}, models={}",
key_label,
len(unique_models),
)
return {
"success": len(unique_models) > 0,
"data": {
"models": unique_models,
"error": None,
"from_cache": False,
"keys_total": len(active_keys),
"keys_cached": 0,
"keys_fetched": 1,
},
"provider": {"id": provider.id, "name": provider.name},
}
# 所有 Key 均失败
error = "; ".join(all_errors) if all_errors else "All keys failed"
return {
"success": False,
"data": {
"models": [],
"error": error,
"from_cache": False,
"keys_total": len(active_keys),
"keys_cached": 0,
"keys_fetched": len(all_errors),
},
"provider": {"id": provider.id, "name": provider.name},
}
async def _fetch_models_for_single_key(
provider: Provider,
api_key_id: str,