mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 17:30:23 +08:00
feat: add Gemini Files API proxy with key capability support
- Add gemini_files_api capability for key-level Files API support - Implement Gemini Files API proxy endpoints (upload/list/get/delete) - Add file-to-key mapping cache for correct key routing - Support preferred_key_ids to prioritize keys that uploaded files - Enhance logging for file mapping diagnostics
This commit is contained in:
@@ -439,6 +439,14 @@ class BaseMessageHandler:
|
||||
adapter_detector=self.adapter_detector,
|
||||
)
|
||||
|
||||
async def _resolve_preferred_key_ids(
|
||||
self,
|
||||
model_name: str,
|
||||
request_body: Optional[Dict[str, Any]] = None,
|
||||
) -> Optional[list[str]]:
|
||||
"""可选的 Key 优先级解析钩子(默认不启用)。"""
|
||||
return None
|
||||
|
||||
def get_api_format(self, provider_type: Optional[str] = None) -> APIFormat:
|
||||
"""根据 provider_type 解析 API 格式,未知类型默认 OPENAI"""
|
||||
if provider_type:
|
||||
|
||||
@@ -511,6 +511,11 @@ class ChatHandlerBase(BaseMessageHandler, ABC):
|
||||
capability_requirements = self._resolve_capability_requirements(
|
||||
model_name=model,
|
||||
request_headers=original_headers,
|
||||
request_body=original_request_body,
|
||||
)
|
||||
preferred_key_ids = await self._resolve_preferred_key_ids(
|
||||
model_name=model,
|
||||
request_body=original_request_body,
|
||||
)
|
||||
|
||||
# 执行请求(通过 FallbackOrchestrator)
|
||||
@@ -529,6 +534,7 @@ class ChatHandlerBase(BaseMessageHandler, ABC):
|
||||
request_id=self.request_id,
|
||||
is_stream=True,
|
||||
capability_requirements=capability_requirements or None,
|
||||
preferred_key_ids=preferred_key_ids or None,
|
||||
request_body_ref=request_body_ref, # 传递容器引用
|
||||
)
|
||||
|
||||
@@ -1126,6 +1132,11 @@ class ChatHandlerBase(BaseMessageHandler, ABC):
|
||||
capability_requirements = self._resolve_capability_requirements(
|
||||
model_name=model,
|
||||
request_headers=original_headers,
|
||||
request_body=original_request_body,
|
||||
)
|
||||
preferred_key_ids = await self._resolve_preferred_key_ids(
|
||||
model_name=model,
|
||||
request_body=original_request_body,
|
||||
)
|
||||
|
||||
(
|
||||
@@ -1142,6 +1153,7 @@ class ChatHandlerBase(BaseMessageHandler, ABC):
|
||||
request_func=sync_request_func,
|
||||
request_id=self.request_id,
|
||||
capability_requirements=capability_requirements or None,
|
||||
preferred_key_ids=preferred_key_ids or None,
|
||||
request_body_ref=request_body_ref, # 传递容器引用
|
||||
)
|
||||
|
||||
|
||||
@@ -568,6 +568,11 @@ class CliMessageHandlerBase(BaseMessageHandler):
|
||||
capability_requirements = self._resolve_capability_requirements(
|
||||
model_name=ctx.model,
|
||||
request_headers=original_headers,
|
||||
request_body=original_request_body,
|
||||
)
|
||||
preferred_key_ids = await self._resolve_preferred_key_ids(
|
||||
model_name=ctx.model,
|
||||
request_body=original_request_body,
|
||||
)
|
||||
|
||||
# 执行请求(通过 FallbackOrchestrator)
|
||||
@@ -586,6 +591,7 @@ class CliMessageHandlerBase(BaseMessageHandler):
|
||||
request_id=self.request_id,
|
||||
is_stream=True,
|
||||
capability_requirements=capability_requirements or None,
|
||||
preferred_key_ids=preferred_key_ids or None,
|
||||
request_body_ref=request_body_ref, # 传递容器引用
|
||||
)
|
||||
|
||||
@@ -2324,6 +2330,11 @@ class CliMessageHandlerBase(BaseMessageHandler):
|
||||
capability_requirements = self._resolve_capability_requirements(
|
||||
model_name=model,
|
||||
request_headers=original_headers,
|
||||
request_body=original_request_body,
|
||||
)
|
||||
preferred_key_ids = await self._resolve_preferred_key_ids(
|
||||
model_name=model,
|
||||
request_body=original_request_body,
|
||||
)
|
||||
|
||||
(
|
||||
@@ -2340,6 +2351,7 @@ class CliMessageHandlerBase(BaseMessageHandler):
|
||||
request_func=sync_request_func,
|
||||
request_id=self.request_id,
|
||||
capability_requirements=capability_requirements or None,
|
||||
preferred_key_ids=preferred_key_ids or None,
|
||||
request_body_ref=request_body_ref, # 传递容器引用
|
||||
)
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ from src.api.handlers.base.chat_handler_base import ChatHandlerBase
|
||||
from src.core.api_format import extract_client_api_key_with_query
|
||||
from src.core.logger import logger
|
||||
from src.models.gemini import GeminiRequest
|
||||
from src.services.gemini_files_mapping import extract_file_names_from_request
|
||||
from src.services.provider.transport import redact_url_for_log
|
||||
|
||||
|
||||
@@ -56,6 +57,16 @@ class GeminiChatAdapter(ChatAdapterBase):
|
||||
self._get_api_format(),
|
||||
)
|
||||
|
||||
def detect_capability_requirements(
|
||||
self,
|
||||
headers: Dict[str, str], # noqa: ARG002 - 预留
|
||||
request_body: Optional[Dict[str, Any]] = None,
|
||||
) -> Dict[str, bool]:
|
||||
"""检测是否需要 Gemini Files API 能力"""
|
||||
if request_body and extract_file_names_from_request(request_body):
|
||||
return {"gemini_files_api": True}
|
||||
return {}
|
||||
|
||||
def _merge_path_params(
|
||||
self, original_request_body: Dict[str, Any], path_params: Dict[str, Any] # noqa: ARG002
|
||||
) -> Dict[str, Any]:
|
||||
|
||||
@@ -22,6 +22,58 @@ class GeminiChatHandler(ChatHandlerBase):
|
||||
|
||||
FORMAT_ID = "GEMINI"
|
||||
|
||||
async def _resolve_preferred_key_ids(
|
||||
self,
|
||||
model_name: str, # noqa: ARG002 - 仅做文件绑定
|
||||
request_body: Optional[Dict[str, Any]] = None,
|
||||
) -> Optional[list[str]]:
|
||||
"""
|
||||
从 files/xxx 绑定关系中解析优先 Key ID 列表。
|
||||
|
||||
Gemini 文件与上传它的 API Key 绑定,必须使用同一 Key 访问。
|
||||
此方法从缓存中查找文件→Key 映射,优先使用正确的 Key。
|
||||
|
||||
注意事项:
|
||||
- 如果映射缺失(缓存过期/重启),会记录警告,请求可能失败
|
||||
- 如果多个文件属于不同 Key,只能使用其中一个,其他文件可能无法访问
|
||||
"""
|
||||
from src.core.logger import logger
|
||||
from src.services.gemini_files_mapping import (
|
||||
extract_file_names_from_request,
|
||||
get_file_key_mapping,
|
||||
)
|
||||
|
||||
file_names = extract_file_names_from_request(request_body or {})
|
||||
if not file_names:
|
||||
return None
|
||||
|
||||
preferred_key_ids: list[str] = []
|
||||
unmapped_files: list[str] = [] # 记录找不到映射的文件
|
||||
|
||||
for file_name in file_names:
|
||||
key_id = await get_file_key_mapping(file_name)
|
||||
if key_id:
|
||||
if key_id not in preferred_key_ids:
|
||||
preferred_key_ids.append(key_id)
|
||||
else:
|
||||
unmapped_files.append(file_name)
|
||||
|
||||
# 警告:映射缺失
|
||||
if unmapped_files:
|
||||
logger.warning(
|
||||
f"[{self.request_id}] Gemini 文件→Key 映射缺失: {unmapped_files},"
|
||||
"请求可能失败(文件属于其他 Key 或映射已过期)"
|
||||
)
|
||||
|
||||
# 警告:多个文件属于不同 Key
|
||||
if len(preferred_key_ids) > 1:
|
||||
logger.warning(
|
||||
f"[{self.request_id}] 请求使用了多个文件,但它们属于不同的 Key: "
|
||||
f"{preferred_key_ids},只能使用第一个 Key,其他文件可能无法访问"
|
||||
)
|
||||
|
||||
return preferred_key_ids or None
|
||||
|
||||
def extract_model_from_request(
|
||||
self,
|
||||
request_body: Dict[str, Any],
|
||||
|
||||
Reference in New Issue
Block a user