mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
- 删除全部 Python 源码 (src/) 及 Alembic 迁移脚本,归档至 _deprecated_py_src/ - 重构 Rust gateway ai_pipeline: 拆分 planner/finalize 模块,新增 contracts/adaptation 层 - 重组 handlers 模块为 admin/public/proxy/internal/shared 子模块结构 - 新增 executor 模块,引入 Rust 原生数据库迁移 (aether-data/migrations) - 简化 CI/Docker 构建流程,移除 base image 二级构建,统一为单一 app image - 移除 Python 相关基础设施文件 (entrypoint.sh, gunicorn_conf.py, Dockerfile.base)
168 lines
7.0 KiB
Python
168 lines
7.0 KiB
Python
"""
|
||
Provider Key 响应对象构建器。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
from dataclasses import asdict
|
||
from typing import Any
|
||
|
||
from src.core.crypto import crypto_service
|
||
from src.core.logger import logger
|
||
from src.core.provider_oauth_utils import normalize_oauth_organizations
|
||
from src.models.database import ProviderAPIKey
|
||
from src.models.endpoint_models import EndpointAPIKeyResponse
|
||
from src.services.provider_keys.auth_type import normalize_auth_type
|
||
from src.services.provider_keys.status_snapshot_store import (
|
||
normalize_oauth_expires_at,
|
||
resolve_provider_key_status_snapshot,
|
||
)
|
||
|
||
|
||
def build_key_response(
|
||
key: ProviderAPIKey,
|
||
api_key_plain: str | None = None,
|
||
*,
|
||
provider_type: str | None = None,
|
||
) -> EndpointAPIKeyResponse:
|
||
"""构建 Key 响应对象。"""
|
||
auth_type = normalize_auth_type(getattr(key, "auth_type", "api_key"))
|
||
encrypted_api_key = str(getattr(key, "api_key", "") or "")
|
||
request_count = int(getattr(key, "request_count", 0) or 0)
|
||
success_count = int(getattr(key, "success_count", 0) or 0)
|
||
total_response_time_ms = float(getattr(key, "total_response_time_ms", 0) or 0.0)
|
||
rpm_limit = getattr(key, "rpm_limit", None)
|
||
|
||
if auth_type in ("service_account", "vertex_ai"):
|
||
# Service Account 不显示占位符
|
||
masked_key = "[Service Account]"
|
||
elif auth_type == "oauth":
|
||
masked_key = "[OAuth Token]"
|
||
else:
|
||
try:
|
||
decrypted_key = crypto_service.decrypt(encrypted_api_key)
|
||
masked_key = f"{decrypted_key[:8]}***{decrypted_key[-4:]}"
|
||
except Exception:
|
||
masked_key = "***ERROR***"
|
||
|
||
success_rate = success_count / request_count if request_count > 0 else 0.0
|
||
avg_response_time_ms = total_response_time_ms / success_count if success_count > 0 else 0.0
|
||
|
||
is_adaptive = rpm_limit is None
|
||
key_dict: dict[str, Any] = dict(getattr(key, "__dict__", {}))
|
||
key_dict.pop("_sa_instance_state", None)
|
||
key_dict.pop("api_key", None) # 移除敏感字段,避免泄露
|
||
key_dict["auth_type"] = auth_type
|
||
|
||
# 提取 OAuth 元数据(如果是 OAuth 类型)
|
||
oauth_expires_at = None
|
||
oauth_email = None
|
||
oauth_plan_type = None
|
||
oauth_account_id = None
|
||
oauth_account_name = None
|
||
oauth_account_user_id = None
|
||
auth_config: dict[str, Any] | None = None
|
||
oauth_organizations: list[dict[str, object]] = []
|
||
encrypted_auth_config = key_dict.pop("auth_config", None) # 移除敏感字段,避免泄露
|
||
if auth_type == "oauth" and isinstance(encrypted_auth_config, str) and encrypted_auth_config:
|
||
try:
|
||
decrypted_config = crypto_service.decrypt(encrypted_auth_config)
|
||
auth_config = json.loads(decrypted_config)
|
||
oauth_expires_at = normalize_oauth_expires_at(auth_config.get("expires_at"))
|
||
oauth_email = auth_config.get("email")
|
||
oauth_plan_type = auth_config.get("plan_type") # Codex: plus/free/team/enterprise
|
||
# Antigravity 使用 "tier" 字段(如 "PAID"/"FREE"),做小写化 fallback
|
||
if not oauth_plan_type:
|
||
ag_tier = auth_config.get("tier")
|
||
if ag_tier and isinstance(ag_tier, str):
|
||
oauth_plan_type = ag_tier.lower()
|
||
oauth_account_id = auth_config.get("account_id") # Codex: chatgpt_account_id
|
||
oauth_account_name = auth_config.get("account_name")
|
||
oauth_account_user_id = auth_config.get("account_user_id")
|
||
oauth_organizations = normalize_oauth_organizations(auth_config.get("organizations"))
|
||
except Exception as e:
|
||
logger.error("Failed to decrypt auth_config for key {}: {}", key.id, e)
|
||
|
||
if not provider_type:
|
||
provider_rel = getattr(key, "provider", None)
|
||
provider_type = (
|
||
str(getattr(provider_rel, "provider_type", None) or "").strip()
|
||
or str(getattr(provider_rel, "type", None) or "").strip()
|
||
or None
|
||
)
|
||
|
||
status_snapshot = resolve_provider_key_status_snapshot(
|
||
key,
|
||
provider_type=provider_type,
|
||
auth_config=auth_config,
|
||
oauth_expires_at=oauth_expires_at,
|
||
)
|
||
|
||
# 从 health_by_format 计算汇总字段(便于列表展示)
|
||
raw_health_by_format = getattr(key, "health_by_format", None)
|
||
health_by_format = raw_health_by_format if isinstance(raw_health_by_format, dict) else {}
|
||
raw_circuit_by_format = getattr(key, "circuit_breaker_by_format", None)
|
||
circuit_by_format = raw_circuit_by_format if isinstance(raw_circuit_by_format, dict) else {}
|
||
|
||
# 计算整体健康度(取所有格式中的最低值)
|
||
if health_by_format:
|
||
health_scores = [float(h.get("health_score") or 1.0) for h in health_by_format.values()]
|
||
min_health_score = min(health_scores) if health_scores else 1.0
|
||
# 取最大的连续失败次数
|
||
max_consecutive = max(
|
||
(int(h.get("consecutive_failures") or 0) for h in health_by_format.values()),
|
||
default=0,
|
||
)
|
||
# 取最近的失败时间
|
||
failure_times = [
|
||
h.get("last_failure_at") for h in health_by_format.values() if h.get("last_failure_at")
|
||
]
|
||
last_failure = max(failure_times) if failure_times else None
|
||
else:
|
||
min_health_score = 1.0
|
||
max_consecutive = 0
|
||
last_failure = None
|
||
|
||
# 检查是否有任何格式的熔断器打开
|
||
any_circuit_open = any(c.get("open", False) for c in circuit_by_format.values())
|
||
|
||
key_dict.update(
|
||
{
|
||
"api_key_masked": masked_key,
|
||
"api_key_plain": api_key_plain,
|
||
"success_rate": success_rate,
|
||
"avg_response_time_ms": round(avg_response_time_ms, 2),
|
||
"is_adaptive": is_adaptive,
|
||
"effective_limit": (
|
||
getattr(
|
||
key, "learned_rpm_limit", None
|
||
) # 自适应模式:使用学习值,未学习时为 None(不限制)
|
||
if is_adaptive
|
||
else rpm_limit
|
||
),
|
||
# 汇总字段
|
||
"health_score": min_health_score,
|
||
"consecutive_failures": max_consecutive,
|
||
"last_failure_at": last_failure,
|
||
"circuit_breaker_open": any_circuit_open,
|
||
# OAuth 相关
|
||
"oauth_expires_at": oauth_expires_at,
|
||
"oauth_email": oauth_email,
|
||
"oauth_plan_type": oauth_plan_type,
|
||
"oauth_account_id": oauth_account_id,
|
||
"oauth_account_name": oauth_account_name,
|
||
"oauth_account_user_id": oauth_account_user_id,
|
||
"oauth_organizations": oauth_organizations,
|
||
"oauth_invalid_at": status_snapshot.oauth.invalid_at,
|
||
"oauth_invalid_reason": getattr(key, "oauth_invalid_reason", None),
|
||
"status_snapshot": asdict(status_snapshot),
|
||
}
|
||
)
|
||
|
||
# 防御性:确保 api_formats 存在(历史数据可能为空/缺失)
|
||
if "api_formats" not in key_dict or key_dict["api_formats"] is None:
|
||
key_dict["api_formats"] = []
|
||
|
||
return EndpointAPIKeyResponse(**key_dict)
|