Spaces:
Running
Running
""" | |
Gemini API Routes - Handles native Gemini API endpoints. | |
This module provides native Gemini API endpoints that proxy directly to Google's API | |
without any format transformations. | |
""" | |
import json | |
import logging | |
from fastapi import APIRouter, Request, Response, Depends | |
from .auth import authenticate_user | |
from .google_api_client import send_gemini_request, build_gemini_payload_from_native | |
from .config import SUPPORTED_MODELS | |
router = APIRouter() | |
async def gemini_list_models(request: Request, username: str = Depends(authenticate_user)): | |
""" | |
Native Gemini models endpoint. | |
Returns available models in Gemini format, matching the official Gemini API. | |
""" | |
try: | |
logging.info("Gemini models list requested") | |
models_response = { | |
"models": SUPPORTED_MODELS | |
} | |
logging.info(f"Returning {len(SUPPORTED_MODELS)} Gemini models") | |
return Response( | |
content=json.dumps(models_response), | |
status_code=200, | |
media_type="application/json; charset=utf-8" | |
) | |
except Exception as e: | |
logging.error(f"Failed to list Gemini models: {str(e)}") | |
return Response( | |
content=json.dumps({ | |
"error": { | |
"message": f"Failed to list models: {str(e)}", | |
"code": 500 | |
} | |
}), | |
status_code=500, | |
media_type="application/json" | |
) | |
async def gemini_proxy(request: Request, full_path: str, username: str = Depends(authenticate_user)): | |
""" | |
Native Gemini API proxy endpoint. | |
Handles all native Gemini API calls by proxying them directly to Google's API. | |
This endpoint handles paths like: | |
- /v1beta/models/{model}/generateContent | |
- /v1beta/models/{model}/streamGenerateContent | |
- /v1/models/{model}/generateContent | |
- etc. | |
""" | |
try: | |
# Get the request body | |
post_data = await request.body() | |
# Determine if this is a streaming request | |
is_streaming = "stream" in full_path.lower() | |
# Extract model name from the path | |
# Paths typically look like: v1beta/models/gemini-1.5-pro/generateContent | |
model_name = _extract_model_from_path(full_path) | |
logging.info(f"Gemini proxy request: path={full_path}, model={model_name}, stream={is_streaming}") | |
if not model_name: | |
logging.error(f"Could not extract model name from path: {full_path}") | |
return Response( | |
content=json.dumps({ | |
"error": { | |
"message": f"Could not extract model name from path: {full_path}", | |
"code": 400 | |
} | |
}), | |
status_code=400, | |
media_type="application/json" | |
) | |
# Parse the incoming request | |
try: | |
if post_data: | |
incoming_request = json.loads(post_data) | |
else: | |
incoming_request = {} | |
except json.JSONDecodeError as e: | |
logging.error(f"Invalid JSON in request body: {str(e)}") | |
return Response( | |
content=json.dumps({ | |
"error": { | |
"message": "Invalid JSON in request body", | |
"code": 400 | |
} | |
}), | |
status_code=400, | |
media_type="application/json" | |
) | |
# Build the payload for Google API | |
gemini_payload = build_gemini_payload_from_native(incoming_request, model_name) | |
# Send the request to Google API | |
response = send_gemini_request(gemini_payload, is_streaming=is_streaming) | |
# Log the response status | |
if hasattr(response, 'status_code'): | |
if response.status_code != 200: | |
logging.error(f"Gemini API returned error: status={response.status_code}") | |
else: | |
logging.info(f"Successfully processed Gemini request for model: {model_name}") | |
return response | |
except Exception as e: | |
logging.error(f"Gemini proxy error: {str(e)}") | |
return Response( | |
content=json.dumps({ | |
"error": { | |
"message": f"Proxy error: {str(e)}", | |
"code": 500 | |
} | |
}), | |
status_code=500, | |
media_type="application/json" | |
) | |
def _extract_model_from_path(path: str) -> str: | |
""" | |
Extract the model name from a Gemini API path. | |
Examples: | |
- "v1beta/models/gemini-1.5-pro/generateContent" -> "gemini-1.5-pro" | |
- "v1/models/gemini-2.0-flash/streamGenerateContent" -> "gemini-2.0-flash" | |
Args: | |
path: The API path | |
Returns: | |
Model name (just the model name, not prefixed with "models/") or None if not found | |
""" | |
parts = path.split('/') | |
# Look for the pattern: .../models/{model_name}/... | |
try: | |
models_index = parts.index('models') | |
if models_index + 1 < len(parts): | |
model_name = parts[models_index + 1] | |
# Remove any action suffix like ":streamGenerateContent" or ":generateContent" | |
if ':' in model_name: | |
model_name = model_name.split(':')[0] | |
# Return just the model name without "models/" prefix | |
return model_name | |
except ValueError: | |
pass | |
# If we can't find the pattern, return None | |
return None | |
async def gemini_list_models_v1(request: Request, username: str = Depends(authenticate_user)): | |
""" | |
Alternative models endpoint for v1 API version. | |
Some clients might use /v1/models instead of /v1beta/models. | |
""" | |
return await gemini_list_models(request, username) | |
# Health check endpoint | |
async def health_check(): | |
""" | |
Simple health check endpoint. | |
""" | |
return {"status": "healthy", "service": "geminicli2api"} |