CoolFace
Apppublic

Pamudu13/gemma-3-chat

sourceHugging Faceupdated 27d agoView on Hugging Face
0likes
ClaudeAsOpenAI.py206 linesDownload Raw Back to py
1import httpx2from typing import Optional, List, Dict, Any, AsyncIterator, Union3import functools4 5class AsyncClaudeAsOpenAI:6    """7    完全模拟 AsyncOpenAI 客户端,底层用 litellm.acompletion(懒加载)8    """9    10    def __init__(11        self, 12        api_key: str, 13        base_url: Optional[str] = None,14        default_model: Optional[str] = "claude-3-5-sonnet-20241022",15        http_client: Optional[httpx.AsyncClient] = None,16        timeout: Optional[float] = None,17        max_retries: Optional[int] = None,18        **kwargs19    ):20        self.api_key = api_key21        self.base_url = base_url  22        self.default_model = default_model23        self.http_client = http_client24        self.timeout = timeout25        self.max_retries = max_retries26        self._extra_kwargs = kwargs27        self._litellm_module = None  # 缓存 litellm 模块28 29    @property30    def _litellm(self):31        """懒加载 litellm,第一次调用时才 import"""32        if self._litellm_module is None:33            import litellm34            self._litellm_module = litellm35            # 可选:配置 litellm(如关闭日志)36            # litellm.set_verbose = False37            # litellm.suppress_debug_info = True38        return self._litellm_module39 40    @property41    def models(self):42        return self._ModelsResource(self)43 44    class _ModelsResource:45        def __init__(self, parent: "AsyncClaudeAsOpenAI"):46            self._parent = parent47 48        async def list(self):49            # 构造兼容 OpenAI 返回形式的对象50            class ModelItem:51                def __init__(self, model_id: str):52                    self.id = model_id53 54            class ModelList:55                def __init__(self, data: list):56                    self.data = data57 58            # 处理请求 URL,Anthropic 的获取模型接口通常是 /v1/models59            base_url = self._parent.base_url or "https://api.anthropic.com"60            if base_url.endswith("/v1") or base_url.endswith("/v1/"):61                url = f"{base_url.rstrip('/')}/models"62            else:63                url = f"{base_url.rstrip('/')}/v1/models"64 65            headers = {66                "x-api-key": self._parent.api_key,67                "anthropic-version": "2023-06-01"68            }69 70            try:71                # 优先复用全局 http_client 以走系统代理配置72                client = self._parent.http_client73                need_close = False74                if not client:75                    client = httpx.AsyncClient()76                    need_close = True77 78                response = await client.get(url, headers=headers)79                80                if need_close:81                    await client.aclose()82 83                # 如果 API 成功响应84                if response.status_code == 200:85                    data = response.json()86                    # 解析官方格式: {"type": "list", "data":[{"id": "claude-3-opus-...", ...}]}87                    models = [ModelItem(m["id"]) for m in data.get("data",[])]88                    if models:89                        return ModelList(models)90            except Exception as e:91                print(f"动态获取 Anthropic 模型列表失败 (可能代理/代理商不支持): {e}")92 93            # [静态兜底方案]:如果请求报错或代理商 API 未实现 /models 端点,返回常见的 Claude 模型94            fallback_models =[]95            return ModelList([ModelItem(m) for m in fallback_models])96 97    def _convert_tools(self, tools: Optional[List[Dict]]) -> Optional[List[Dict]]:98        """OpenAI Tools -> Claude Tools"""99        if not tools:100            return None101            102        claude_tools = []103        for tool in tools:104            tool_type = tool.get("type")105            106            if tool_type == "custom":107                continue  # Claude 不支持108            elif tool_type == "function":109                func = tool.get("function", {})110                claude_tools.append({111                    "name": func.get("name"),112                    "description": func.get("description", ""),113                    "input_schema": func.get("parameters", {"type": "object", "properties": {}})114                })115            elif tool_type in ["web_search_20250305", "web_search_20260209"]:116                claude_tools.append(tool)117                118        return claude_tools if claude_tools else None119    120    def _convert_tool_choice(self, tool_choice: Any) -> Any:121        """OpenAI tool_choice -> Claude tool_choice"""122        if tool_choice is None:123            return None124            125        if isinstance(tool_choice, str):126            return tool_choice127            128        if isinstance(tool_choice, dict) and tool_choice.get("type") == "function":129            func_name = tool_choice.get("function", {}).get("name")130            if func_name:131                return {"type": "tool", "name": func_name}132                    133        return tool_choice134 135    @property136    def chat(self):137        return self._ChatResource(self)138 139    class _ChatResource:140        def __init__(self, parent: "AsyncClaudeAsOpenAI"):141            self.completions = self._CompletionsResource(parent)142 143        class _CompletionsResource:144            def __init__(self, parent: "AsyncClaudeAsOpenAI"):145                self._parent = parent146 147            async def create(148                self,149                model: Optional[str] = None,150                messages: Optional[List[Dict[str, Any]]] = None,151                temperature: Optional[float] = None,152                max_tokens: Optional[int] = None,153                stream: bool = False,154                top_p: Optional[float] = None,155                stop: Optional[Union[str, List[str]]] = None,156                tools: Optional[List[Dict]] = None,157                tool_choice: Optional[Any] = None,158                **kwargs159            ):160                model = model or self._parent.default_model161                if not model:162                    raise ValueError("model is required")163 164                if not model.startswith("anthropic/"):165                    model = f"anthropic/{model}"166 167                # ===== 懒加载 litellm =====168                litellm = self._parent._litellm169                170                completion_kwargs = {171                    "model": model,172                    "messages": messages,173                    "api_key": self._parent.api_key,174                    "stream": stream,175                }176                177                # Tools 转换178                if tools:179                    converted_tools = self._parent._convert_tools(tools)180                    if converted_tools:181                        completion_kwargs["tools"] = converted_tools182                        183                if tool_choice:184                    completion_kwargs["tool_choice"] = self._parent._convert_tool_choice(tool_choice)185                186                # 其他参数187                if self._parent.base_url:188                    completion_kwargs["api_base"] = self._parent.base_url189                if temperature is not None:190                    completion_kwargs["temperature"] = temperature191                if max_tokens is not None:192                    completion_kwargs["max_tokens"] = max_tokens193                if top_p is not None:194                    completion_kwargs["top_p"] = top_p195                if stop is not None:196                    completion_kwargs["stop"] = stop197                if self._parent.timeout is not None:198                    completion_kwargs["timeout"] = self._parent.timeout199                if self._parent.http_client is not None:200                    completion_kwargs["client"] = self._parent.http_client201 202                # 过滤 OpenAI 特有参数203                safe_kwargs = {k: v for k, v in kwargs.items() 204                              if k not in ['logprobs', 'top_logprobs', 'response_format', 'n']}205                206                return await litellm.acompletion(**completion_kwargs, **safe_kwargs)