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)
172 lines
6.1 KiB
Python
172 lines
6.1 KiB
Python
"""
|
||
Gemini CLI Adapter - 基于通用 CLI Adapter 基类的实现
|
||
|
||
继承 CliAdapterBase,处理 Gemini CLI 格式的请求。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import Any
|
||
|
||
from fastapi import Request
|
||
|
||
from src.api.handlers.base.cli_adapter_base import CliAdapterBase, register_cli_adapter
|
||
from src.api.handlers.base.cli_handler_base import CliMessageHandlerBase
|
||
from src.api.handlers.gemini.adapter import GeminiCapabilityDetector
|
||
from src.config.settings import config
|
||
from src.core.api_format import ApiFamily, get_auth_handler
|
||
from src.core.api_format.enums import AuthMethod
|
||
|
||
|
||
@register_cli_adapter
|
||
class GeminiCliAdapter(CliAdapterBase):
|
||
"""
|
||
Gemini CLI API 适配器
|
||
|
||
处理 Gemini CLI 格式的请求(透传模式,最小验证)。
|
||
"""
|
||
|
||
FORMAT_ID = "gemini:cli"
|
||
API_FAMILY = ApiFamily.GEMINI
|
||
name = "gemini.cli"
|
||
|
||
@property
|
||
def HANDLER_CLASS(self) -> type[CliMessageHandlerBase]:
|
||
"""延迟导入 Handler 类避免循环依赖"""
|
||
from src.api.handlers.gemini_cli.handler import GeminiCliMessageHandler
|
||
|
||
return GeminiCliMessageHandler
|
||
|
||
def __init__(self, allowed_api_formats: list[str] | None = None):
|
||
super().__init__(allowed_api_formats)
|
||
|
||
def extract_api_key(self, request: Request) -> str | None:
|
||
"""
|
||
从请求中提取 API 密钥 - Gemini CLI 支持 header 和 query 两种方式
|
||
|
||
优先级(与 Google SDK 行为一致):
|
||
1. URL 参数 ?key=
|
||
2. x-goog-api-key 请求头
|
||
"""
|
||
handler = get_auth_handler(AuthMethod.GOOG_API_KEY)
|
||
return handler.extract_credentials(request)
|
||
|
||
def detect_capability_requirements(
|
||
self,
|
||
headers: dict[str, str],
|
||
request_body: dict[str, Any] | None = None,
|
||
) -> dict[str, bool]:
|
||
"""从请求体检测 Gemini 能力需求(fileData.fileUri -> gemini_files)"""
|
||
return GeminiCapabilityDetector.detect_from_request(headers, request_body)
|
||
|
||
def _merge_path_params(
|
||
self, original_request_body: dict[str, Any], path_params: dict[str, Any] # noqa: ARG002
|
||
) -> dict[str, Any]:
|
||
"""
|
||
合并 URL 路径参数到请求体 - Gemini CLI 特化版本
|
||
|
||
Gemini API 特点:
|
||
- model 不合并到请求体(Gemini 原生请求体不含 model,通过 URL 路径传递)
|
||
- stream 不合并到请求体(Gemini API 通过 URL 端点区分流式/非流式)
|
||
|
||
基类已经从 path_params 获取 model 和 stream 用于日志和路由判断。
|
||
|
||
Args:
|
||
original_request_body: 原始请求体字典
|
||
path_params: URL 路径参数字典(包含 model、stream 等)
|
||
|
||
Returns:
|
||
原始请求体(不合并任何 path_params)
|
||
"""
|
||
# Gemini: 不合并任何 path_params 到请求体
|
||
return original_request_body.copy()
|
||
|
||
def _extract_message_count(self, payload: dict[str, Any]) -> int:
|
||
"""Gemini CLI 使用 contents 字段"""
|
||
contents = payload.get("contents", [])
|
||
return len(contents) if isinstance(contents, list) else 0
|
||
|
||
def _build_audit_metadata(
|
||
self,
|
||
payload: dict[str, Any],
|
||
path_params: dict[str, Any] | None = None,
|
||
) -> dict[str, Any]:
|
||
"""Gemini CLI 特定的审计元数据"""
|
||
# 从 path_params 获取 model(Gemini 请求体不含 model)
|
||
model = path_params.get("model", "unknown") if path_params else "unknown"
|
||
contents = payload.get("contents", [])
|
||
generation_config = payload.get("generation_config", {}) or {}
|
||
|
||
role_counts: dict[str, int] = {}
|
||
for content in contents:
|
||
role = content.get("role", "unknown") if isinstance(content, dict) else "unknown"
|
||
role_counts[role] = role_counts.get(role, 0) + 1
|
||
|
||
return {
|
||
"action": "gemini_cli_request",
|
||
"model": model,
|
||
"stream": bool(payload.get("stream", False)),
|
||
"max_output_tokens": generation_config.get("max_output_tokens"),
|
||
"contents_count": len(contents),
|
||
"content_roles": role_counts,
|
||
"temperature": generation_config.get("temperature"),
|
||
"top_p": generation_config.get("top_p"),
|
||
"top_k": generation_config.get("top_k"),
|
||
"tools_count": len(payload.get("tools") or []),
|
||
"system_instruction_present": bool(payload.get("system_instruction")),
|
||
"safety_settings_count": len(payload.get("safety_settings") or []),
|
||
}
|
||
|
||
@classmethod
|
||
def build_endpoint_url(
|
||
cls,
|
||
base_url: str,
|
||
request_data: dict[str, Any],
|
||
model_name: str | None = None,
|
||
*,
|
||
provider_type: str | None = None,
|
||
) -> str:
|
||
"""构建Gemini CLI API端点URL"""
|
||
effective_model_name = model_name or request_data.get("model", "")
|
||
if not effective_model_name:
|
||
raise ValueError("Model name is required for Gemini API")
|
||
|
||
base_url = base_url.rstrip("/")
|
||
if base_url.endswith("/v1beta"):
|
||
prefix = base_url
|
||
else:
|
||
prefix = f"{base_url}/v1beta"
|
||
return f"{prefix}/models/{effective_model_name}:generateContent"
|
||
|
||
# build_request_body 使用基类实现,通过 format_conversion_registry 自动转换 OPENAI -> GEMINI_CLI
|
||
|
||
@classmethod
|
||
def get_cli_user_agent(cls) -> str | None:
|
||
"""获取Gemini CLI User-Agent"""
|
||
return config.internal_user_agent_gemini_cli
|
||
|
||
@classmethod
|
||
def get_cli_extra_headers(
|
||
cls, *, base_url: str | None = None, provider_type: str | None = None
|
||
) -> dict[str, str]:
|
||
"""获取Gemini CLI额外请求头,包含 x-app: cli 标识"""
|
||
headers = super().get_cli_extra_headers(base_url=base_url, provider_type=provider_type)
|
||
headers["x-app"] = "cli" # 标识 CLI 模式,让上游使用正确的 adapter
|
||
return headers
|
||
|
||
|
||
def build_gemini_cli_adapter(x_app_header: str = "") -> GeminiCliAdapter:
|
||
"""
|
||
构建 Gemini CLI 适配器
|
||
|
||
Args:
|
||
x_app_header: X-App 请求头值(预留扩展)
|
||
|
||
Returns:
|
||
GeminiCliAdapter 实例
|
||
"""
|
||
return GeminiCliAdapter()
|
||
|
||
|
||
__all__ = ["GeminiCliAdapter", "build_gemini_cli_adapter"]
|