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:
fawney19
2026-02-01 17:25:28 +08:00
parent 2101a957ce
commit c246ccfc91
51 changed files with 1078 additions and 869 deletions

View File

@@ -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)