Files
Aether/_deprecated_py_src/services/provider_ops/architectures/base.py
fawney19 1d9c77522a refactor: 移除 Python 后端源码,全面迁移至 Rust gateway 架构
- 删除全部 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)
2026-04-03 16:26:16 +08:00

551 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Provider 架构抽象基类
"""
from abc import ABC, abstractmethod
from collections.abc import AsyncIterator, Callable
from contextlib import asynccontextmanager
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Any
import httpx
from src.services.provider_ops.actions.base import ProviderAction
from src.services.provider_ops.types import (
ConnectorAuthType,
ConnectorState,
ConnectorStatus,
ProviderActionType,
)
from src.utils.ssl_utils import get_ssl_context
# ==================== 连接器基类 ====================
class ProviderConnector(ABC):
"""
提供商连接器基类
负责建立与提供商的认证连接,管理凭据状态。
每个架构应在自己的文件中实现对应的连接器子类。
"""
# 子类需要定义的类属性
auth_type: ConnectorAuthType = ConnectorAuthType.NONE
display_name: str = "Base Connector"
def __init__(self, base_url: str, config: dict[str, Any] | None = None):
"""
初始化连接器
Args:
base_url: 提供商 API 基础 URL
config: 连接器配置
"""
self.base_url = base_url.rstrip("/")
self.config = config or {}
self._status = ConnectorStatus.DISCONNECTED
self._connected_at: datetime | None = None
self._expires_at: datetime | None = None
self._last_error: str | None = None
# 代理配置(支持 proxy_node_id、tunnel 和旧的 proxy URL
from src.services.proxy_node.resolver import resolve_ops_proxy_config
self._proxy: str | httpx.Proxy | None
self._tunnel_node_id: str | None
self._proxy, self._tunnel_node_id = resolve_ops_proxy_config(self.config)
# HTTP 客户端配置
self._timeout = self.config.get("timeout", 30)
self._headers: dict[str, str] = {}
# 凭据更新回调Token Rotation 等场景需要持久化新凭据)
self._on_credentials_updated: Callable[[dict[str, Any]], None] | None = None
@abstractmethod
async def connect(self, credentials: dict[str, Any]) -> bool:
"""
建立认证连接
Args:
credentials: 凭据信息如用户名密码、API Key 等)
Returns:
是否连接成功
"""
pass
@abstractmethod
async def disconnect(self) -> None:
"""断开连接,清理状态"""
pass
@abstractmethod
async def is_authenticated(self) -> bool:
"""检查当前是否已认证"""
pass
@abstractmethod
def _apply_auth(self, request: httpx.Request) -> httpx.Request:
"""
为请求应用认证信息
Args:
request: 原始请求
Returns:
添加认证信息后的请求
"""
pass
async def refresh_auth(self, credentials: dict[str, Any]) -> bool:
"""
刷新认证(如 Token 过期)
默认实现:重新连接
Args:
credentials: 凭据信息
Returns:
是否刷新成功
"""
return await self.connect(credentials)
@asynccontextmanager
async def get_client(self) -> AsyncIterator[httpx.AsyncClient]:
"""
获取已认证的 HTTP 客户端
使用 context manager 确保资源正确释放。
tunnel 模式下使用 TunnelTransport 替代 proxy transport。
Yields:
已配置认证信息的 AsyncClient
"""
transport = None
if self._tunnel_node_id:
from src.services.proxy_node.tunnel_transport import create_tunnel_transport
transport = create_tunnel_transport(self._tunnel_node_id, timeout=self._timeout)
elif self._proxy:
transport = httpx.AsyncHTTPTransport(proxy=self._proxy)
async with httpx.AsyncClient(
base_url=self.base_url,
timeout=self._timeout,
transport=transport,
event_hooks={"request": [self._auth_hook]},
verify=get_ssl_context(),
) as client:
yield client
async def _auth_hook(self, request: httpx.Request) -> None:
"""请求钩子:应用认证信息"""
self._apply_auth(request)
def get_state(self) -> ConnectorState:
"""获取连接器当前状态"""
return ConnectorState(
status=self._status,
auth_type=self.auth_type,
connected_at=self._connected_at,
expires_at=self._expires_at,
last_error=self._last_error,
)
def _set_connected(self, expires_at: datetime | None = None) -> None:
"""设置为已连接状态"""
self._status = ConnectorStatus.CONNECTED
self._connected_at = datetime.now(timezone.utc)
self._expires_at = expires_at
self._last_error = None
def _set_error(self, error: str) -> None:
"""设置错误状态"""
self._status = ConnectorStatus.ERROR
self._last_error = error
def _set_disconnected(self) -> None:
"""设置为断开状态"""
self._status = ConnectorStatus.DISCONNECTED
self._connected_at = None
self._expires_at = None
@classmethod
def get_credentials_schema(cls) -> dict[str, Any]:
"""
获取凭据配置 JSON Schema用于前端表单生成
子类应重写此方法
"""
return {"type": "object", "properties": {}, "required": []}
# ==================== 验证结果 ====================
@dataclass
class VerifyResult:
"""认证验证结果"""
success: bool
message: str | None = None
username: str | None = None
display_name: str | None = None
email: str | None = None
quota: float | None = None
used_quota: float | None = None
request_count: int | None = None
extra: dict[str, Any] | None = None
def to_dict(self) -> dict[str, Any]:
"""转换为字典"""
if not self.success:
return {"success": False, "message": self.message}
return {
"success": True,
"data": {
"username": self.username,
"display_name": self.display_name or self.username,
"email": self.email,
"quota": self.quota,
"used_quota": self.used_quota,
"request_count": self.request_count,
"extra": self.extra or {},
},
}
# ==================== 架构基类 ====================
class ProviderArchitecture(ABC):
"""
提供商架构抽象基类
架构 = Connector鉴权方式 + Actions支持的操作
一个架构可以被多个 Provider 复用。
例如generic_api 架构可用于各种中转站。
## 添加新认证模板的步骤
1. 在 architectures/ 目录创建新文件
2. 继承 ProviderArchitecture 和 ProviderConnector
3. 定义类属性architecture_id, display_name, description
4. 实现连接器子类和架构类
5. 实现认证相关的抽象方法:
- get_credentials_schema(): 返回凭据字段定义
- get_verify_endpoint(): 返回验证端点
- build_verify_headers(): 构建验证请求 headers
- parse_verify_response(): 解析验证响应
6. 在 registry.py 的 _register_builtin_architectures() 中注册
"""
# 子类需要定义的类属性
architecture_id: str = ""
display_name: str = ""
description: str = ""
# 设为 True 时不在架构列表 API 中返回(内部使用的架构)
hidden: bool = False
# 支持的 Connector 类型列表(按优先级排序)
supported_connectors: list[type[ProviderConnector]] = []
# 支持的 Action 类型列表
supported_actions: list[type[ProviderAction]] = []
# 默认操作配置
default_action_configs: dict[ProviderActionType, dict[str, Any]] = {}
def __init__(self, config: dict[str, Any] | None = None):
"""
初始化架构
Args:
config: 架构配置
"""
self.config = config or {}
# ==================== 认证验证相关方法 ====================
@abstractmethod
def get_credentials_schema(self) -> dict[str, Any]:
"""
获取凭据字段定义JSON Schema 格式)
子类必须实现此方法定义需要的凭据字段。
Returns:
JSON Schema 格式的字段定义
"""
pass
@abstractmethod
def get_verify_endpoint(self) -> str:
"""
获取认证验证端点
子类必须实现此方法返回验证端点。
Returns:
验证端点路径(如 /api/user/self, /api/v1/auth/profile
"""
pass
@abstractmethod
def build_verify_headers(
self,
config: dict[str, Any],
credentials: dict[str, Any],
) -> dict[str, str]:
"""
构建认证验证请求的 Headers
子类必须实现此方法构建认证 Headers。
Args:
config: 连接器配置(可能包含 prepare_verify_config 返回的额外配置)
credentials: 凭据信息
Returns:
Headers 字典
"""
pass
def parse_verify_response(
self,
status_code: int,
data: dict[str, Any],
) -> VerifyResult:
"""
解析认证验证响应
默认实现处理通用的 {"success": bool, "data": {...}} 格式。
子类可重写 _auth_fail_message() 和 _build_verify_result() 进行自定义。
Args:
status_code: HTTP 状态码
data: 响应 JSON 数据
Returns:
验证结果
"""
if status_code == 401:
return VerifyResult(success=False, message=self._auth_fail_message(401))
if status_code == 403:
return VerifyResult(success=False, message=self._auth_fail_message(403))
if status_code != 200:
return VerifyResult(success=False, message=f"验证失败HTTP {status_code}")
# 解析通用响应格式
if data.get("success") is True and "data" in data:
user_data = data["data"]
elif data.get("success") is False:
message = data.get("message", "验证失败")
return VerifyResult(success=False, message=message)
else:
user_data = data
return self._build_verify_result(user_data, data)
def _auth_fail_message(self, status_code: int) -> str:
"""
获取认证失败消息
子类可重写以提供自定义消息(如 Cookie 认证场景)。
"""
if status_code == 401:
return "认证失败:无效的凭据"
return "认证失败:权限不足"
def _build_verify_result(
self, user_data: dict[str, Any], raw_data: dict[str, Any] | None = None
) -> VerifyResult:
"""
从用户数据构建验证结果
默认实现提取 username, display_name, email, quota, used_quota, request_count。
子类可重写以自定义字段提取。
"""
known_fields = (
"username",
"display_name",
"email",
"quota",
"used_quota",
"request_count",
)
return VerifyResult(
success=True,
username=user_data.get("username"),
display_name=user_data.get("display_name") or user_data.get("username"),
email=user_data.get("email"),
quota=user_data.get("quota"),
used_quota=user_data.get("used_quota"),
request_count=user_data.get("request_count"),
extra={k: v for k, v in user_data.items() if k not in known_fields},
)
# ==================== 可选的钩子方法 ====================
async def prepare_verify_config(
self,
base_url: str,
config: dict[str, Any],
credentials: dict[str, Any],
) -> dict[str, Any] | tuple[dict[str, Any], dict[str, Any]]:
"""
验证前的异步预处理(可选)
子类可重写以执行异步操作(如获取动态 Cookie、登录获取 Token
Args:
base_url: API 基础地址
config: 连接器配置
credentials: 凭据信息
Returns:
- dict: 额外配置(会与原 config 合并传递给 build_verify_headers
- tuple[dict, dict]: (额外配置, 需持久化的凭据更新)
当预处理过程中凭据发生变更时(如 Token Rotation
通过第二个 dict 显式返回需要持久化的字段。
"""
return {}
# ==================== 连接器和操作相关方法 ====================
def get_connector(
self,
base_url: str,
auth_type: ConnectorAuthType | None = None,
config: dict[str, Any] | None = None,
) -> ProviderConnector:
"""
获取连接器实例
Args:
base_url: 提供商 API 基础 URL
auth_type: 指定的认证类型None 则使用默认
config: 连接器配置
Returns:
连接器实例
Raises:
ValueError: 不支持的认证类型
"""
if not self.supported_connectors:
raise ValueError(f"架构 {self.architecture_id} 未配置支持的连接器")
# 查找匹配的连接器
connector_cls: type[ProviderConnector] | None = None
if auth_type:
for cls in self.supported_connectors:
if cls.auth_type == auth_type:
connector_cls = cls
break
if not connector_cls:
supported = [c.auth_type.value for c in self.supported_connectors]
raise ValueError(
f"架构 {self.architecture_id} 不支持 {auth_type.value} 认证,"
f"支持的类型: {supported}"
)
else:
# 使用第一个(默认)连接器
connector_cls = self.supported_connectors[0]
return connector_cls(base_url, config)
def get_action(
self,
action_type: ProviderActionType,
config: dict[str, Any] | None = None,
) -> ProviderAction:
"""
获取操作实例
Args:
action_type: 操作类型
config: 操作配置(会与默认配置合并)
Returns:
操作实例
Raises:
ValueError: 不支持的操作类型
"""
action_cls: type[ProviderAction] | None = None
for cls in self.supported_actions:
if cls.action_type == action_type:
action_cls = cls
break
if not action_cls:
supported = [a.action_type.value for a in self.supported_actions]
raise ValueError(
f"架构 {self.architecture_id} 不支持 {action_type.value} 操作,"
f"支持的操作: {supported}"
)
# 合并默认配置和用户配置
merged_config = dict(self.default_action_configs.get(action_type, {}))
if config:
merged_config.update(config)
return action_cls(merged_config)
def supports_action(self, action_type: ProviderActionType) -> bool:
"""检查是否支持指定操作"""
return any(a.action_type == action_type for a in self.supported_actions)
def supports_auth_type(self, auth_type: ConnectorAuthType) -> bool:
"""检查是否支持指定认证类型"""
return any(c.auth_type == auth_type for c in self.supported_connectors)
def get_supported_auth_types(self) -> list[ConnectorAuthType]:
"""获取支持的认证类型列表"""
return [c.auth_type for c in self.supported_connectors]
def get_supported_action_types(self) -> list[ProviderActionType]:
"""获取支持的操作类型列表"""
return [a.action_type for a in self.supported_actions]
def to_dict(self) -> dict[str, Any]:
"""转换为字典(用于 API 响应)"""
return {
"architecture_id": self.architecture_id,
"display_name": self.display_name,
"description": self.description,
"credentials_schema": self.get_credentials_schema(),
"verify_endpoint": self.get_verify_endpoint(),
"supported_auth_types": [
{
"type": c.auth_type.value,
"display_name": c.display_name,
"credentials_schema": c.get_credentials_schema(),
}
for c in self.supported_connectors
],
"supported_actions": [
{
"type": a.action_type.value,
"display_name": a.display_name,
"description": a.description,
"config_schema": a.get_config_schema(),
}
for a in self.supported_actions
],
"default_connector": (
self.supported_connectors[0].auth_type.value if self.supported_connectors else None
),
}