""" Gemini 格式转换器 提供 Gemini 与其他 API 格式(Claude、OpenAI)之间的转换 """ from typing import Any, Dict, List, Optional class ClaudeToGeminiConverter: """ Claude -> Gemini 请求转换器 将 Claude Messages API 格式转换为 Gemini generateContent 格式 """ def convert_request(self, claude_request: Dict[str, Any]) -> Dict[str, Any]: """ 将 Claude 请求转换为 Gemini 请求 Args: claude_request: Claude 格式的请求字典 Returns: Gemini 格式的请求字典 """ gemini_request: Dict[str, Any] = { "contents": self._convert_messages(claude_request.get("messages", [])), } # 转换 system prompt system = claude_request.get("system") if system: gemini_request["system_instruction"] = self._convert_system(system) # 转换生成配置 generation_config = self._build_generation_config(claude_request) if generation_config: gemini_request["generation_config"] = generation_config # 转换工具 tools = claude_request.get("tools") if tools: gemini_request["tools"] = self._convert_tools(tools) return gemini_request def _convert_messages(self, messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]: """转换消息列表""" contents = [] for msg in messages: role = msg.get("role", "user") # Gemini 使用 "model" 而不是 "assistant" gemini_role = "model" if role == "assistant" else "user" content = msg.get("content", "") parts = self._convert_content_to_parts(content) contents.append( { "role": gemini_role, "parts": parts, } ) return contents def _convert_content_to_parts(self, content: Any) -> List[Dict[str, Any]]: """将 Claude 内容转换为 Gemini parts""" if isinstance(content, str): return [{"text": content}] if isinstance(content, list): parts: List[Dict[str, Any]] = [] for block in content: if isinstance(block, str): parts.append({"text": block}) elif isinstance(block, dict): block_type = block.get("type") if block_type == "text": parts.append({"text": block.get("text", "")}) elif block_type == "image": # 转换图片 source = block.get("source", {}) if source.get("type") == "base64": parts.append( { "inline_data": { "mime_type": source.get("media_type", "image/png"), "data": source.get("data", ""), } } ) elif block_type == "tool_use": # 转换工具调用 parts.append( { "function_call": { "name": block.get("name", ""), "args": block.get("input", {}), } } ) elif block_type == "tool_result": # 转换工具结果 parts.append( { "function_response": { "name": block.get("tool_use_id", ""), "response": {"result": block.get("content", "")}, } } ) return parts return [{"text": str(content)}] def _convert_system(self, system: Any) -> Dict[str, Any]: """转换 system prompt""" if isinstance(system, str): return {"parts": [{"text": system}]} if isinstance(system, list): parts = [] for item in system: if isinstance(item, str): parts.append({"text": item}) elif isinstance(item, dict) and item.get("type") == "text": parts.append({"text": item.get("text", "")}) return {"parts": parts} return {"parts": [{"text": str(system)}]} def _build_generation_config(self, claude_request: Dict[str, Any]) -> Optional[Dict[str, Any]]: """构建生成配置""" config: Dict[str, Any] = {} if "max_tokens" in claude_request: config["max_output_tokens"] = claude_request["max_tokens"] if "temperature" in claude_request: config["temperature"] = claude_request["temperature"] if "top_p" in claude_request: config["top_p"] = claude_request["top_p"] if "top_k" in claude_request: config["top_k"] = claude_request["top_k"] if "stop_sequences" in claude_request: config["stop_sequences"] = claude_request["stop_sequences"] return config if config else None def _convert_tools(self, tools: List[Dict[str, Any]]) -> List[Dict[str, Any]]: """转换工具定义""" function_declarations = [] for tool in tools: func_decl = { "name": tool.get("name", ""), } if "description" in tool: func_decl["description"] = tool["description"] if "input_schema" in tool: func_decl["parameters"] = tool["input_schema"] function_declarations.append(func_decl) return [{"function_declarations": function_declarations}] class GeminiToClaudeConverter: """ Gemini -> Claude 响应转换器 将 Gemini generateContent 响应转换为 Claude Messages API 格式 """ def convert_response(self, gemini_response: Dict[str, Any]) -> Dict[str, Any]: """ 将 Gemini 响应转换为 Claude 响应 Args: gemini_response: Gemini 格式的响应字典 Returns: Claude 格式的响应字典 """ candidates = gemini_response.get("candidates", []) if not candidates: return self._create_empty_response() candidate = candidates[0] content = candidate.get("content", {}) parts = content.get("parts", []) # 转换内容块 claude_content = self._convert_parts_to_content(parts) # 转换使用量 usage = self._convert_usage(gemini_response.get("usageMetadata", {})) # 转换停止原因 stop_reason = self._convert_finish_reason(candidate.get("finishReason")) return { "id": f"msg_{gemini_response.get('modelVersion', 'gemini')}", "type": "message", "role": "assistant", "content": claude_content, "model": gemini_response.get("modelVersion", "gemini"), "stop_reason": stop_reason, "stop_sequence": None, "usage": usage, } def _convert_parts_to_content(self, parts: List[Dict[str, Any]]) -> List[Dict[str, Any]]: """将 Gemini parts 转换为 Claude content blocks""" content = [] for part in parts: if "text" in part: content.append( { "type": "text", "text": part["text"], } ) elif "functionCall" in part: func_call = part["functionCall"] content.append( { "type": "tool_use", "id": f"toolu_{func_call.get('name', '')}", "name": func_call.get("name", ""), "input": func_call.get("args", {}), } ) return content def _convert_usage(self, usage_metadata: Dict[str, Any]) -> Dict[str, int]: """转换使用量信息""" return { "input_tokens": usage_metadata.get("promptTokenCount", 0), "output_tokens": usage_metadata.get("candidatesTokenCount", 0), "cache_creation_input_tokens": 0, "cache_read_input_tokens": usage_metadata.get("cachedContentTokenCount", 0), } def _convert_finish_reason(self, finish_reason: Optional[str]) -> Optional[str]: """转换停止原因""" mapping = { "STOP": "end_turn", "MAX_TOKENS": "max_tokens", "SAFETY": "content_filtered", "RECITATION": "content_filtered", "OTHER": "stop_sequence", } if finish_reason is None: return "end_turn" return mapping.get(finish_reason, "end_turn") def _create_empty_response(self) -> Dict[str, Any]: """创建空响应""" return { "id": "msg_empty", "type": "message", "role": "assistant", "content": [], "model": "gemini", "stop_reason": "end_turn", "stop_sequence": None, "usage": { "input_tokens": 0, "output_tokens": 0, }, } class OpenAIToGeminiConverter: """ OpenAI -> Gemini 请求转换器 将 OpenAI Chat Completions API 格式转换为 Gemini generateContent 格式 """ def convert_request(self, openai_request: Dict[str, Any]) -> Dict[str, Any]: """ 将 OpenAI 请求转换为 Gemini 请求 Args: openai_request: OpenAI 格式的请求字典 Returns: Gemini 格式的请求字典 """ messages = openai_request.get("messages", []) # 分离 system 消息和其他消息 system_messages = [] other_messages = [] for msg in messages: if msg.get("role") == "system": system_messages.append(msg) else: other_messages.append(msg) gemini_request: Dict[str, Any] = { "contents": self._convert_messages(other_messages), } # 转换 system messages if system_messages: system_text = "\n".join(msg.get("content", "") for msg in system_messages) gemini_request["system_instruction"] = {"parts": [{"text": system_text}]} # 转换生成配置 generation_config = self._build_generation_config(openai_request) if generation_config: gemini_request["generation_config"] = generation_config # 转换工具 tools = openai_request.get("tools") if tools: gemini_request["tools"] = self._convert_tools(tools) return gemini_request def _convert_messages(self, messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]: """转换消息列表""" contents = [] for msg in messages: role = msg.get("role", "user") gemini_role = "model" if role == "assistant" else "user" content = msg.get("content", "") parts = self._convert_content_to_parts(content) # 处理工具调用 tool_calls = msg.get("tool_calls", []) for tc in tool_calls: if tc.get("type") == "function": func = tc.get("function", {}) import json try: args = json.loads(func.get("arguments", "{}")) except json.JSONDecodeError: args = {} parts.append( { "function_call": { "name": func.get("name", ""), "args": args, } } ) if parts: contents.append( { "role": gemini_role, "parts": parts, } ) return contents def _convert_content_to_parts(self, content: Any) -> List[Dict[str, Any]]: """将 OpenAI 内容转换为 Gemini parts""" if content is None: return [] if isinstance(content, str): return [{"text": content}] if isinstance(content, list): parts: List[Dict[str, Any]] = [] for item in content: if isinstance(item, str): parts.append({"text": item}) elif isinstance(item, dict): item_type = item.get("type") if item_type == "text": parts.append({"text": item.get("text", "")}) elif item_type == "image_url": # OpenAI 图片 URL 格式 image_url = item.get("image_url", {}) url = image_url.get("url", "") if url.startswith("data:"): # base64 数据 URL # 格式: data:image/png;base64,xxxxx try: header, data = url.split(",", 1) mime_type = header.split(":")[1].split(";")[0] parts.append( { "inline_data": { "mime_type": mime_type, "data": data, } } ) except (ValueError, IndexError): pass return parts return [{"text": str(content)}] def _build_generation_config(self, openai_request: Dict[str, Any]) -> Optional[Dict[str, Any]]: """构建生成配置""" config: Dict[str, Any] = {} if "max_tokens" in openai_request: config["max_output_tokens"] = openai_request["max_tokens"] if "temperature" in openai_request: config["temperature"] = openai_request["temperature"] if "top_p" in openai_request: config["top_p"] = openai_request["top_p"] if "stop" in openai_request: stop = openai_request["stop"] if isinstance(stop, str): config["stop_sequences"] = [stop] elif isinstance(stop, list): config["stop_sequences"] = stop if "n" in openai_request: config["candidate_count"] = openai_request["n"] return config if config else None def _convert_tools(self, tools: List[Dict[str, Any]]) -> List[Dict[str, Any]]: """转换工具定义""" function_declarations = [] for tool in tools: if tool.get("type") == "function": func = tool.get("function", {}) func_decl = { "name": func.get("name", ""), } if "description" in func: func_decl["description"] = func["description"] if "parameters" in func: func_decl["parameters"] = func["parameters"] function_declarations.append(func_decl) return [{"function_declarations": function_declarations}] class GeminiToOpenAIConverter: """ Gemini -> OpenAI 响应转换器 将 Gemini generateContent 响应转换为 OpenAI Chat Completions API 格式 """ def convert_response(self, gemini_response: Dict[str, Any]) -> Dict[str, Any]: """ 将 Gemini 响应转换为 OpenAI 响应 Args: gemini_response: Gemini 格式的响应字典 Returns: OpenAI 格式的响应字典 """ import time candidates = gemini_response.get("candidates", []) choices = [] for i, candidate in enumerate(candidates): content = candidate.get("content", {}) parts = content.get("parts", []) # 提取文本内容 text_parts = [] tool_calls = [] for part in parts: if "text" in part: text_parts.append(part["text"]) elif "functionCall" in part: func_call = part["functionCall"] import json tool_calls.append( { "id": f"call_{func_call.get('name', '')}_{i}", "type": "function", "function": { "name": func_call.get("name", ""), "arguments": json.dumps(func_call.get("args", {})), }, } ) message: Dict[str, Any] = { "role": "assistant", "content": "".join(text_parts) if text_parts else None, } if tool_calls: message["tool_calls"] = tool_calls finish_reason = self._convert_finish_reason(candidate.get("finishReason")) choices.append( { "index": i, "message": message, "finish_reason": finish_reason, } ) # 转换使用量 usage = self._convert_usage(gemini_response.get("usageMetadata", {})) return { "id": f"chatcmpl-{gemini_response.get('modelVersion', 'gemini')}", "object": "chat.completion", "created": int(time.time()), "model": gemini_response.get("modelVersion", "gemini"), "choices": choices, "usage": usage, } def _convert_usage(self, usage_metadata: Dict[str, Any]) -> Dict[str, int]: """转换使用量信息""" prompt_tokens = usage_metadata.get("promptTokenCount", 0) completion_tokens = usage_metadata.get("candidatesTokenCount", 0) return { "prompt_tokens": prompt_tokens, "completion_tokens": completion_tokens, "total_tokens": prompt_tokens + completion_tokens, } def _convert_finish_reason(self, finish_reason: Optional[str]) -> str: """转换停止原因""" mapping = { "STOP": "stop", "MAX_TOKENS": "length", "SAFETY": "content_filter", "RECITATION": "content_filter", "OTHER": "stop", } if finish_reason is None: return "stop" return mapping.get(finish_reason, "stop") __all__ = [ "ClaudeToGeminiConverter", "GeminiToClaudeConverter", "OpenAIToGeminiConverter", "GeminiToOpenAIConverter", ]