gcli2api / src /gemini_routes.py
bibibi12345's picture
added error logging and handling
8061397
"""
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()
@router.get("/v1beta/models")
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"
)
@router.api_route("/{full_path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH"])
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
@router.get("/v1/models")
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
@router.get("/health")
async def health_check():
"""
Simple health check endpoint.
"""
return {"status": "healthy", "service": "geminicli2api"}