Spaces:
Paused
Paused
| """ | |
| 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"} |