mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 01:40:21 +08:00
refactor(api_format): 重构为结构化的 (ApiFamily, EndpointKind) 标识体系
将扁平的 APIFormat 枚举 (CLAUDE, OPENAI, GEMINI, *_CLI) 重构为二维结构: - ApiFamily: 协议族 (openai, claude, gemini) - EndpointKind: 端点变体 (chat, cli, video, image) 新增 EndpointSignature 数据类和 `family:kind` 签名键工具函数, 统一 JSON dict/metrics/logs 中的字符串标识格式。
This commit is contained in:
@@ -15,9 +15,12 @@ from __future__ import annotations
|
||||
from collections.abc import Set as AbstractSet
|
||||
from typing import Any
|
||||
|
||||
from src.core.api_format.enums import APIFormat
|
||||
from src.core.api_format.metadata import get_auth_config, get_extra_headers, get_protected_keys
|
||||
|
||||
from src.core.api_format.metadata import (
|
||||
get_auth_config_for_endpoint,
|
||||
get_extra_headers_for_endpoint,
|
||||
get_protected_keys_for_endpoint,
|
||||
)
|
||||
from src.core.api_format.signature import EndpointSignature, parse_signature_key
|
||||
|
||||
# =============================================================================
|
||||
# 头部常量定义
|
||||
@@ -118,65 +121,61 @@ def get_header_value(headers: dict[str, str], key: str, default: str = "") -> st
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def extract_client_api_key(headers: dict[str, str], api_format: APIFormat) -> str | None:
|
||||
def extract_client_api_key_for_endpoint(
|
||||
headers: dict[str, str],
|
||||
endpoint: str | EndpointSignature | tuple,
|
||||
) -> str | None:
|
||||
"""
|
||||
从客户端请求头提取 API Key
|
||||
|
||||
自动处理大小写,根据 API 格式使用正确的认证头和类型。
|
||||
新模式:从客户端请求头提取 API Key。
|
||||
|
||||
Args:
|
||||
headers: 原始请求头(自动处理大小写)
|
||||
api_format: API 格式
|
||||
|
||||
Returns:
|
||||
提取的 API Key,未找到返回 None
|
||||
endpoint: endpoint signature(`family:kind` / EndpointSignature / (ApiFamily, EndpointKind))
|
||||
"""
|
||||
|
||||
auth_header, auth_type = get_auth_config(api_format)
|
||||
auth_header, auth_type = get_auth_config_for_endpoint(endpoint)
|
||||
value = get_header_value(headers, auth_header)
|
||||
if not value:
|
||||
return None
|
||||
|
||||
if auth_type == "bearer":
|
||||
# Bearer token 格式: "Bearer <token>"
|
||||
if value.lower().startswith("bearer "):
|
||||
return value[7:] # 移除 "Bearer " 前缀
|
||||
return value[7:]
|
||||
return None
|
||||
|
||||
# 直接 header 格式
|
||||
return value
|
||||
|
||||
|
||||
def extract_client_api_key_with_query(
|
||||
def extract_client_api_key_for_endpoint_with_query(
|
||||
headers: dict[str, str],
|
||||
query_params: dict[str, str] | None,
|
||||
api_format: APIFormat,
|
||||
endpoint: str | EndpointSignature | tuple,
|
||||
) -> str | None:
|
||||
"""
|
||||
从客户端请求头或 URL 参数提取 API Key
|
||||
新模式:从客户端请求头或 URL 参数提取 API Key。
|
||||
|
||||
Gemini 格式优先级(与 Google SDK 行为一致):
|
||||
Gemini family 优先级:
|
||||
1. URL 参数 ?key=
|
||||
2. x-goog-api-key 请求头
|
||||
|
||||
其他格式仅从请求头提取。
|
||||
|
||||
Args:
|
||||
headers: 原始请求头(自动处理大小写)
|
||||
query_params: URL 查询参数
|
||||
api_format: API 格式
|
||||
|
||||
Returns:
|
||||
提取的 API Key,未找到返回 None
|
||||
"""
|
||||
# Gemini 格式:query 参数优先
|
||||
if api_format in (APIFormat.GEMINI, APIFormat.GEMINI_CLI):
|
||||
try:
|
||||
sig = (
|
||||
endpoint
|
||||
if isinstance(endpoint, EndpointSignature)
|
||||
else (
|
||||
parse_signature_key(endpoint) # type: ignore[arg-type]
|
||||
if isinstance(endpoint, str)
|
||||
else EndpointSignature(api_family=endpoint[0], endpoint_kind=endpoint[1])
|
||||
) # type: ignore[index]
|
||||
)
|
||||
except Exception:
|
||||
sig = None
|
||||
|
||||
if sig and sig.api_family.value == "gemini":
|
||||
query_key = query_params.get("key") if query_params else None
|
||||
if query_key:
|
||||
return query_key
|
||||
|
||||
# 其他格式或 Gemini header 方式:使用现有逻辑
|
||||
return extract_client_api_key(headers, api_format)
|
||||
return extract_client_api_key_for_endpoint(headers, endpoint)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
@@ -184,29 +183,33 @@ def extract_client_api_key_with_query(
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def detect_capabilities(
|
||||
def detect_capabilities_for_endpoint(
|
||||
headers: dict[str, str],
|
||||
api_format: APIFormat,
|
||||
request_body: dict[str, Any] | None = None, # noqa: ARG001 - 预留给部分格式使用
|
||||
endpoint: str | EndpointSignature | tuple,
|
||||
request_body: dict[str, Any] | None = None, # noqa: ARG001 - 预留
|
||||
) -> dict[str, bool]:
|
||||
"""
|
||||
从请求头检测能力需求
|
||||
新模式:从请求头检测能力需求。
|
||||
|
||||
当前支持:
|
||||
- Claude/Claude CLI: anthropic-beta 头中的 context-1m
|
||||
|
||||
Args:
|
||||
headers: 原始请求头(自动处理大小写)
|
||||
api_format: API 格式
|
||||
request_body: 请求体(部分格式可能需要)
|
||||
|
||||
Returns:
|
||||
能力需求字典,如 {"context_1m": True}
|
||||
当前支持:
|
||||
- Claude family: anthropic-beta 头中的 context-1m
|
||||
"""
|
||||
|
||||
requirements: dict[str, bool] = {}
|
||||
|
||||
if api_format in (APIFormat.CLAUDE, APIFormat.CLAUDE_CLI):
|
||||
try:
|
||||
sig = (
|
||||
endpoint
|
||||
if isinstance(endpoint, EndpointSignature)
|
||||
else (
|
||||
parse_signature_key(endpoint) # type: ignore[arg-type]
|
||||
if isinstance(endpoint, str)
|
||||
else EndpointSignature(api_family=endpoint[0], endpoint_kind=endpoint[1])
|
||||
) # type: ignore[index]
|
||||
)
|
||||
except Exception:
|
||||
sig = None
|
||||
|
||||
if sig and sig.api_family.value == "claude":
|
||||
beta_header = get_header_value(headers, "anthropic-beta")
|
||||
if "context-1m" in beta_header.lower():
|
||||
requirements["context_1m"] = True
|
||||
@@ -242,7 +245,9 @@ class HeaderBuilder:
|
||||
self.add(k, v)
|
||||
return self
|
||||
|
||||
def add_protected(self, headers: dict[str, str], protected_keys: AbstractSet[str]) -> HeaderBuilder:
|
||||
def add_protected(
|
||||
self, headers: dict[str, str], protected_keys: AbstractSet[str]
|
||||
) -> HeaderBuilder:
|
||||
"""
|
||||
添加头部但保护指定的 key 不被覆盖
|
||||
|
||||
@@ -310,7 +315,10 @@ class HeaderBuilder:
|
||||
to_key = rule.get("to", "")
|
||||
if from_key and to_key:
|
||||
# 两个 key 都不能是受保护的
|
||||
if from_key.lower() not in protected_lower and to_key.lower() not in protected_lower:
|
||||
if (
|
||||
from_key.lower() not in protected_lower
|
||||
and to_key.lower() not in protected_lower
|
||||
):
|
||||
self.rename(from_key, to_key)
|
||||
|
||||
return self
|
||||
@@ -320,9 +328,9 @@ class HeaderBuilder:
|
||||
return {original_key: value for original_key, value in self._headers.values()}
|
||||
|
||||
|
||||
def build_upstream_headers(
|
||||
def build_upstream_headers_for_endpoint(
|
||||
original_headers: dict[str, str],
|
||||
api_format: APIFormat,
|
||||
endpoint: str | EndpointSignature | tuple,
|
||||
provider_api_key: str,
|
||||
*,
|
||||
endpoint_headers: dict[str, str] | None = None,
|
||||
@@ -330,55 +338,36 @@ def build_upstream_headers(
|
||||
drop_headers: frozenset[str] | None = None,
|
||||
) -> dict[str, str]:
|
||||
"""
|
||||
构建发送给上游 Provider 的请求头
|
||||
新模式:构建发送给上游 Provider 的请求头(基于 endpoint signature)。
|
||||
|
||||
优先级(后者覆盖前者):
|
||||
1. 原始头部(排除 drop_headers)
|
||||
2. endpoint 配置头部
|
||||
3. extra_headers
|
||||
4. 认证头(最高优先级,始终设置)
|
||||
|
||||
Args:
|
||||
original_headers: 客户端原始请求头
|
||||
api_format: API 格式
|
||||
provider_api_key: Provider 的 API Key(已解密)
|
||||
endpoint_headers: Endpoint 配置的额外头部
|
||||
extra_headers: 调用方传入的额外头部
|
||||
drop_headers: 需要剔除的头部集合(None 使用默认值,空集合表示不剔除)
|
||||
|
||||
Returns:
|
||||
构建好的请求头字典
|
||||
"""
|
||||
|
||||
# 使用 is None 判断,允许显式传空集合
|
||||
if drop_headers is None:
|
||||
drop_headers = UPSTREAM_DROP_HEADERS
|
||||
|
||||
auth_header, auth_type = get_auth_config(api_format)
|
||||
auth_header, auth_type = get_auth_config_for_endpoint(endpoint)
|
||||
auth_value = f"Bearer {provider_api_key}" if auth_type == "bearer" else provider_api_key
|
||||
|
||||
# 认证头是受保护的,不能被 endpoint_headers 覆盖
|
||||
protected_keys = {auth_header.lower(), "content-type"}
|
||||
|
||||
builder = HeaderBuilder()
|
||||
|
||||
# 1. 添加原始头部(排除 drop_headers)
|
||||
for k, v in original_headers.items():
|
||||
if k.lower() not in drop_headers:
|
||||
builder.add(k, v)
|
||||
|
||||
# 2. 添加 endpoint 头部(保护认证头)
|
||||
if endpoint_headers:
|
||||
builder.add_protected(endpoint_headers, protected_keys)
|
||||
|
||||
# 3. 添加 extra_headers
|
||||
if extra_headers:
|
||||
builder.add_many(extra_headers)
|
||||
|
||||
# 4. 设置认证头(最高优先级,上游始终使用 header 认证)
|
||||
builder.add(auth_header, auth_value)
|
||||
|
||||
# 5. 确保 Content-Type
|
||||
result = builder.build()
|
||||
if not any(k.lower() == "content-type" for k in result):
|
||||
result["Content-Type"] = "application/json"
|
||||
@@ -470,39 +459,21 @@ def redact_headers_for_log(
|
||||
return {k: "***" if k.lower() in redact_keys else v for k, v in headers.items()}
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 兼容层(向后兼容,逐步废弃)
|
||||
# =============================================================================
|
||||
|
||||
# 兼容 request_builder.py 的 SENSITIVE_HEADERS
|
||||
SENSITIVE_HEADERS = UPSTREAM_DROP_HEADERS
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Adapter 统一接口
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def build_adapter_base_headers(
|
||||
api_format: APIFormat,
|
||||
def build_adapter_base_headers_for_endpoint(
|
||||
endpoint: str | EndpointSignature | tuple,
|
||||
api_key: str,
|
||||
*,
|
||||
include_extra: bool = True,
|
||||
) -> dict[str, str]:
|
||||
"""
|
||||
根据 API 格式构建基础请求头
|
||||
|
||||
包含:认证头 + Content-Type + 格式特定的额外头部(如 anthropic-version)
|
||||
|
||||
Args:
|
||||
api_format: API 格式
|
||||
api_key: API Key(已解密)
|
||||
include_extra: 是否包含格式特定的额外头部(默认 True)
|
||||
|
||||
Returns:
|
||||
基础请求头字典
|
||||
新模式:根据 endpoint signature 构建基础请求头。
|
||||
"""
|
||||
auth_header, auth_type = get_auth_config(api_format)
|
||||
auth_header, auth_type = get_auth_config_for_endpoint(endpoint)
|
||||
auth_value = f"Bearer {api_key}" if auth_type == "bearer" else api_key
|
||||
|
||||
headers: dict[str, str] = {
|
||||
@@ -511,53 +482,33 @@ def build_adapter_base_headers(
|
||||
}
|
||||
|
||||
if include_extra:
|
||||
extra = get_extra_headers(api_format)
|
||||
extra = get_extra_headers_for_endpoint(endpoint)
|
||||
if extra:
|
||||
headers.update(extra)
|
||||
|
||||
return headers
|
||||
|
||||
|
||||
def build_adapter_headers(
|
||||
api_format: APIFormat,
|
||||
def build_adapter_headers_for_endpoint(
|
||||
endpoint: str | EndpointSignature | tuple,
|
||||
api_key: str,
|
||||
extra_headers: dict[str, str] | None = None,
|
||||
) -> dict[str, str]:
|
||||
"""
|
||||
构建完整的 Adapter 请求头
|
||||
|
||||
在基础头部上合并 extra_headers,同时保护关键头部不被覆盖。
|
||||
|
||||
Args:
|
||||
api_format: API 格式
|
||||
api_key: API Key(已解密)
|
||||
extra_headers: 调用方传入的额外头部
|
||||
|
||||
Returns:
|
||||
完整的请求头字典
|
||||
新模式:构建完整的 Adapter 请求头(包含 extra_headers)。
|
||||
"""
|
||||
base = build_adapter_base_headers(api_format, api_key)
|
||||
|
||||
base = build_adapter_base_headers_for_endpoint(endpoint, api_key)
|
||||
if not extra_headers:
|
||||
return base
|
||||
|
||||
protected = get_protected_keys(api_format)
|
||||
protected = get_protected_keys_for_endpoint(endpoint)
|
||||
return merge_headers_with_protection(base, extra_headers, protected)
|
||||
|
||||
|
||||
def get_adapter_protected_keys(api_format: APIFormat) -> tuple[str, ...]:
|
||||
"""
|
||||
获取 Adapter 的受保护头部 key
|
||||
|
||||
用于 get_protected_header_keys() 方法返回值。
|
||||
|
||||
Args:
|
||||
api_format: API 格式
|
||||
|
||||
Returns:
|
||||
受保护的头部 key 元组
|
||||
"""
|
||||
return tuple(get_protected_keys(api_format))
|
||||
def get_adapter_protected_keys_for_endpoint(
|
||||
endpoint: str | EndpointSignature | tuple,
|
||||
) -> tuple[str, ...]:
|
||||
"""新模式:获取 Adapter 的受保护头部 key。"""
|
||||
return tuple(get_protected_keys_for_endpoint(endpoint))
|
||||
|
||||
|
||||
# =============================================================================
|
||||
@@ -608,4 +559,3 @@ def get_extra_headers_from_endpoint(endpoint: Any) -> dict[str, str] | None:
|
||||
"""
|
||||
header_rules = getattr(endpoint, "header_rules", None)
|
||||
return extract_set_headers_from_rules(header_rules)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user