Spaces:
Sleeping
Sleeping
| """ | |
| 协议转换器 - OpenAI 格式 <-> Gemini 格式 | |
| """ | |
| from typing import Dict, Any, List | |
| from models import OpenAIChatRequest, MODEL_MAPPING | |
| def convert_openai_to_gemini(request: OpenAIChatRequest) -> Dict[str, Any]: | |
| """ | |
| 将 OpenAI Chat Completion 请求转换为 Gemini 格式 | |
| OpenAI 格式: | |
| { | |
| "model": "gpt-4", | |
| "messages": [ | |
| {"role": "system", "content": "You are..."}, | |
| {"role": "user", "content": "Hello"} | |
| ] | |
| } | |
| Gemini 格式: | |
| { | |
| "contents": [{"role": "user", "parts": [{"text": "Hello"}]}], | |
| "systemInstruction": {"role": "user", "parts": [{"text": "You are..."}]}, | |
| "generationConfig": {...} | |
| } | |
| """ | |
| contents = [] | |
| system_instruction = None | |
| for msg in request.messages: | |
| if msg.role == "system": | |
| system_instruction = { | |
| "role": "user", | |
| "parts": [{"text": msg.content}] | |
| } | |
| elif msg.role == "user": | |
| contents.append({ | |
| "role": "user", | |
| "parts": [{"text": msg.content}] | |
| }) | |
| elif msg.role == "assistant": | |
| contents.append({ | |
| "role": "model", | |
| "parts": [{"text": msg.content}] | |
| }) | |
| # 如果没有 system instruction,使用空字符串 | |
| if system_instruction is None: | |
| system_instruction = {"role": "user", "parts": [{"text": ""}]} | |
| # Generation Config | |
| generation_config = { | |
| "temperature": request.temperature or 1.0, | |
| "topP": request.top_p or 0.95, | |
| "maxOutputTokens": request.max_tokens or 8192, | |
| "candidateCount": 1, | |
| } | |
| # 检查是否需要启用思维链(thinking) | |
| model_lower = request.model.lower() | |
| if "thinking" in model_lower or "sonnet-3-7" in model_lower: | |
| generation_config["thinkingConfig"] = { | |
| "includeThoughts": True, | |
| "thinkingBudget": 8191, # Google Protocol Limit < 8192 | |
| } | |
| return { | |
| "contents": contents, | |
| "systemInstruction": system_instruction, | |
| "generationConfig": generation_config, | |
| } | |
| def map_model_name(model: str) -> str: | |
| """ | |
| 映射模型名称到 Gemini API 支持的名称 | |
| 支持灵活匹配: | |
| - 精确匹配: claude-sonnet-4-5 -> gemini-2.5-flash-preview | |
| - 模糊匹配: 包含 opus -> gemini-2.5-pro-preview | |
| """ | |
| # 先尝试精确匹配 | |
| if model in MODEL_MAPPING: | |
| return MODEL_MAPPING[model] | |
| # 模糊匹配 | |
| model_lower = model.lower() | |
| # Gemini 模型直通(添加 -preview 后缀如需要) | |
| if model_lower.startswith("gemini-"): | |
| if not model_lower.endswith("-preview"): | |
| # 某些模型需要 -preview 后缀 | |
| if model_lower in ["gemini-3-flash", "gemini-3-pro", "gemini-2.5-pro", "gemini-2.5-flash"]: | |
| return model + "-preview" | |
| return model | |
| # Claude 模型映射 | |
| if "opus" in model_lower: | |
| return "gemini-2.5-pro-preview" | |
| if "sonnet" in model_lower: | |
| if "thinking" in model_lower: | |
| return "gemini-2.5-pro-preview" | |
| return "gemini-2.5-flash-preview" | |
| if "haiku" in model_lower: | |
| return "gemini-2.5-flash-lite-preview" | |
| # 默认返回原模型名 | |
| return model | |
| def convert_gemini_to_openai_chunk(gemini_data: Dict[str, Any], model: str) -> Dict[str, Any]: | |
| """ | |
| 将 Gemini 流式响应转换为 OpenAI chunk 格式 | |
| Gemini 格式: | |
| { | |
| "candidates": [{ | |
| "content": {"parts": [{"text": "Hello"}]}, | |
| "finishReason": "STOP" | |
| }] | |
| } | |
| OpenAI 格式: | |
| { | |
| "id": "chatcmpl-xxx", | |
| "object": "chat.completion.chunk", | |
| "choices": [{ | |
| "index": 0, | |
| "delta": {"content": "Hello"}, | |
| "finish_reason": null | |
| }] | |
| } | |
| """ | |
| import uuid | |
| from datetime import datetime | |
| # 解析 Gemini 响应 | |
| candidates = gemini_data.get("candidates", []) | |
| if not candidates: | |
| # 可能是嵌套在 response 中 | |
| response = gemini_data.get("response", {}) | |
| candidates = response.get("candidates", []) | |
| text = "" | |
| finish_reason = None | |
| is_thought = False | |
| thought_signature = None | |
| if candidates: | |
| candidate = candidates[0] | |
| content = candidate.get("content", {}) | |
| parts = content.get("parts", []) | |
| if parts: | |
| part = parts[0] | |
| text = part.get("text", "") | |
| is_thought = part.get("thought", False) | |
| thought_signature = part.get("thoughtSignature") | |
| # 转换结束原因 | |
| gemini_reason = candidate.get("finishReason") | |
| if gemini_reason == "STOP": | |
| finish_reason = "stop" | |
| elif gemini_reason == "MAX_TOKENS": | |
| finish_reason = "length" | |
| elif gemini_reason == "SAFETY": | |
| finish_reason = "content_filter" | |
| # 构建 OpenAI chunk | |
| delta = {"content": text} | |
| if is_thought: | |
| delta["thought"] = True | |
| if thought_signature: | |
| delta["thoughtSignature"] = thought_signature | |
| return { | |
| "id": gemini_data.get("responseId", f"chatcmpl-{uuid.uuid4().hex[:8]}"), | |
| "object": "chat.completion.chunk", | |
| "created": int(datetime.now().timestamp()), | |
| "model": model, | |
| "choices": [{ | |
| "index": 0, | |
| "delta": delta, | |
| "finish_reason": finish_reason | |
| }] | |
| } | |
| def convert_gemini_to_openai_response(gemini_data: Dict[str, Any], model: str) -> Dict[str, Any]: | |
| """ | |
| 将 Gemini 非流式响应转换为 OpenAI 格式 | |
| """ | |
| import uuid | |
| from datetime import datetime | |
| candidates = gemini_data.get("candidates", []) | |
| if not candidates: | |
| response = gemini_data.get("response", {}) | |
| candidates = response.get("candidates", []) | |
| text = "" | |
| finish_reason = "stop" | |
| if candidates: | |
| candidate = candidates[0] | |
| content = candidate.get("content", {}) | |
| parts = content.get("parts", []) | |
| if parts: | |
| text = parts[0].get("text", "") | |
| gemini_reason = candidate.get("finishReason") | |
| if gemini_reason == "MAX_TOKENS": | |
| finish_reason = "length" | |
| elif gemini_reason == "SAFETY": | |
| finish_reason = "content_filter" | |
| return { | |
| "id": f"chatcmpl-{uuid.uuid4().hex[:8]}", | |
| "object": "chat.completion", | |
| "created": int(datetime.now().timestamp()), | |
| "model": model, | |
| "choices": [{ | |
| "index": 0, | |
| "message": { | |
| "role": "assistant", | |
| "content": text | |
| }, | |
| "finish_reason": finish_reason | |
| }], | |
| "usage": { | |
| "prompt_tokens": 0, | |
| "completion_tokens": 0, | |
| "total_tokens": 0 | |
| } | |
| } | |