Spaces:
Paused
Paused
| """ | |
| OpenAI Format Transformers - Handles conversion between OpenAI and Gemini API formats. | |
| This module contains all the logic for transforming requests and responses between the two formats. | |
| """ | |
| import json | |
| import time | |
| import uuid | |
| from typing import Dict, Any | |
| from .models import OpenAIChatCompletionRequest, OpenAIChatCompletionResponse | |
| from .config import DEFAULT_SAFETY_SETTINGS | |
| def openai_request_to_gemini(openai_request: OpenAIChatCompletionRequest) -> Dict[str, Any]: | |
| """ | |
| Transform an OpenAI chat completion request to Gemini format. | |
| Args: | |
| openai_request: OpenAI format request | |
| Returns: | |
| Dictionary in Gemini API format | |
| """ | |
| contents = [] | |
| # Process each message in the conversation | |
| for message in openai_request.messages: | |
| role = message.role | |
| # Map OpenAI roles to Gemini roles | |
| if role == "assistant": | |
| role = "model" | |
| elif role == "system": | |
| role = "user" # Gemini treats system messages as user messages | |
| # Handle different content types (string vs list of parts) | |
| if isinstance(message.content, list): | |
| parts = [] | |
| for part in message.content: | |
| if part.get("type") == "text": | |
| parts.append({"text": part.get("text", "")}) | |
| elif part.get("type") == "image_url": | |
| image_url = part.get("image_url", {}).get("url") | |
| if image_url: | |
| # Parse data URI: "data:image/jpeg;base64,{base64_image}" | |
| try: | |
| mime_type, base64_data = image_url.split(";") | |
| _, mime_type = mime_type.split(":") | |
| _, base64_data = base64_data.split(",") | |
| parts.append({ | |
| "inlineData": { | |
| "mimeType": mime_type, | |
| "data": base64_data | |
| } | |
| }) | |
| except ValueError: | |
| continue | |
| contents.append({"role": role, "parts": parts}) | |
| else: | |
| # Simple text content | |
| contents.append({"role": role, "parts": [{"text": message.content}]}) | |
| # Map OpenAI generation parameters to Gemini format | |
| generation_config = {} | |
| if openai_request.temperature is not None: | |
| generation_config["temperature"] = openai_request.temperature | |
| if openai_request.top_p is not None: | |
| generation_config["topP"] = openai_request.top_p | |
| if openai_request.max_tokens is not None: | |
| generation_config["maxOutputTokens"] = openai_request.max_tokens | |
| if openai_request.stop is not None: | |
| # Gemini supports stop sequences | |
| if isinstance(openai_request.stop, str): | |
| generation_config["stopSequences"] = [openai_request.stop] | |
| elif isinstance(openai_request.stop, list): | |
| generation_config["stopSequences"] = openai_request.stop | |
| if openai_request.frequency_penalty is not None: | |
| # Map frequency_penalty to Gemini's frequencyPenalty | |
| generation_config["frequencyPenalty"] = openai_request.frequency_penalty | |
| if openai_request.presence_penalty is not None: | |
| # Map presence_penalty to Gemini's presencePenalty | |
| generation_config["presencePenalty"] = openai_request.presence_penalty | |
| if openai_request.n is not None: | |
| # Map n (number of completions) to Gemini's candidateCount | |
| generation_config["candidateCount"] = openai_request.n | |
| if openai_request.seed is not None: | |
| # Gemini supports seed for reproducible outputs | |
| generation_config["seed"] = openai_request.seed | |
| if openai_request.response_format is not None: | |
| # Handle JSON mode if specified | |
| if openai_request.response_format.get("type") == "json_object": | |
| generation_config["responseMimeType"] = "application/json" | |
| return { | |
| "contents": contents, | |
| "generationConfig": generation_config, | |
| "safetySettings": DEFAULT_SAFETY_SETTINGS, | |
| "model": openai_request.model | |
| } | |
| def gemini_response_to_openai(gemini_response: Dict[str, Any], model: str) -> Dict[str, Any]: | |
| """ | |
| Transform a Gemini API response to OpenAI chat completion format. | |
| Args: | |
| gemini_response: Response from Gemini API | |
| model: Model name to include in response | |
| Returns: | |
| Dictionary in OpenAI chat completion format | |
| """ | |
| choices = [] | |
| for candidate in gemini_response.get("candidates", []): | |
| role = candidate.get("content", {}).get("role", "assistant") | |
| # Map Gemini roles back to OpenAI roles | |
| if role == "model": | |
| role = "assistant" | |
| # Extract text content from parts | |
| parts = candidate.get("content", {}).get("parts", []) | |
| content = "" | |
| if parts and len(parts) > 0: | |
| content = parts[0].get("text", "") | |
| choices.append({ | |
| "index": candidate.get("index", 0), | |
| "message": { | |
| "role": role, | |
| "content": content, | |
| }, | |
| "finish_reason": _map_finish_reason(candidate.get("finishReason")), | |
| }) | |
| return { | |
| "id": str(uuid.uuid4()), | |
| "object": "chat.completion", | |
| "created": int(time.time()), | |
| "model": model, | |
| "choices": choices, | |
| } | |
| def gemini_stream_chunk_to_openai(gemini_chunk: Dict[str, Any], model: str, response_id: str) -> Dict[str, Any]: | |
| """ | |
| Transform a Gemini streaming response chunk to OpenAI streaming format. | |
| Args: | |
| gemini_chunk: Single chunk from Gemini streaming response | |
| model: Model name to include in response | |
| response_id: Consistent ID for this streaming response | |
| Returns: | |
| Dictionary in OpenAI streaming format | |
| """ | |
| choices = [] | |
| for candidate in gemini_chunk.get("candidates", []): | |
| role = candidate.get("content", {}).get("role", "assistant") | |
| # Map Gemini roles back to OpenAI roles | |
| if role == "model": | |
| role = "assistant" | |
| # Extract text content from parts | |
| parts = candidate.get("content", {}).get("parts", []) | |
| content = "" | |
| if parts and len(parts) > 0: | |
| content = parts[0].get("text", "") | |
| choices.append({ | |
| "index": candidate.get("index", 0), | |
| "delta": { | |
| "content": content, | |
| }, | |
| "finish_reason": _map_finish_reason(candidate.get("finishReason")), | |
| }) | |
| return { | |
| "id": response_id, | |
| "object": "chat.completion.chunk", | |
| "created": int(time.time()), | |
| "model": model, | |
| "choices": choices, | |
| } | |
| def _map_finish_reason(gemini_reason: str) -> str: | |
| """ | |
| Map Gemini finish reasons to OpenAI finish reasons. | |
| Args: | |
| gemini_reason: Finish reason from Gemini API | |
| Returns: | |
| OpenAI-compatible finish reason | |
| """ | |
| if gemini_reason == "STOP": | |
| return "stop" | |
| elif gemini_reason == "MAX_TOKENS": | |
| return "length" | |
| elif gemini_reason in ["SAFETY", "RECITATION"]: | |
| return "content_filter" | |
| else: | |
| return None |