File size: 5,853 Bytes
d184447
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import asyncio
import random
import time
import aiohttp
import requests
from typing import Optional, Dict, Any, List

from . import config


class OpenRouterClient:
    def __init__(self):
        self.api_keys = config.get_api_keys()
        self.max_retries = config.get_max_retries()
        self.retry_delay = config.get_retry_delay()
        self.timeout = config.get_request_timeout()
        self.current_key_index = 0

    def get_random_key(self) -> str:
        return random.choice(self.api_keys)

    def get_next_key(self) -> str:
        key = self.api_keys[self.current_key_index]
        self.current_key_index = (self.current_key_index + 1) % len(self.api_keys)
        return key

    def _make_request(

        self,

        method: str,

        url: str,

        api_key: str,

        **kwargs

    ) -> requests.Response:
        headers = kwargs.pop("headers", {})
        headers["Authorization"] = f"Bearer {api_key}"
        headers["Content-Type"] = "application/json"

        return requests.request(
            method=method,
            url=url,
            headers=headers,
            timeout=self.timeout,
            **kwargs
        )

    def request_with_retry(

        self,

        method: str,

        url: str,

        **kwargs

    ) -> Optional[requests.Response]:
        last_error = None
        failed_keys: set = set()  # 记录 401 失败的 key,不再重复使用

        for attempt in range(self.max_retries):
            # 从尚未 401 失败的 key 中选择
            available_keys = [k for k in self.api_keys if k not in failed_keys]
            if not available_keys:
                print(f"All API keys returned 401, stopping retries")
                break
            api_key = random.choice(available_keys)

            try:
                response = self._make_request(method, url, api_key, **kwargs)

                if response.status_code == 200:
                    return response
                elif response.status_code == 429:
                    last_error = f"Rate limited (429)"
                    time.sleep(self.retry_delay * (attempt + 1))
                elif response.status_code == 401:
                    last_error = f"Unauthorized (401) — key excluded"
                    failed_keys.add(api_key)
                    time.sleep(self.retry_delay)  # 短暂等待后换 key 重试
                else:
                    last_error = f"HTTP {response.status_code}: {response.text[:100]}"
                    time.sleep(self.retry_delay)

            except requests.exceptions.Timeout:
                last_error = "Request timeout"
                time.sleep(self.retry_delay)
            except requests.exceptions.RequestException as e:
                last_error = f"Request error: {str(e)}"
                time.sleep(self.retry_delay)

        print(f"Request failed after {self.max_retries} attempts: {last_error}")
        return None

    def get_models(self) -> List[Dict[str, Any]]:
        url = "https://openrouter.ai/api/v1/models"
        response = self.request_with_retry("GET", url)

        if response and response.status_code == 200:
            data = response.json()
            return data.get("data", [])
        return []

    async def async_get_models(self) -> List[Dict[str, Any]]:
        """异步获取模型列表,使用 aiohttp"""
        url = "https://openrouter.ai/api/v1/models"
        api_key = self.get_random_key()
        headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        }

        for attempt in range(self.max_retries):
            try:
                async with aiohttp.ClientSession() as session:
                    async with session.get(url, headers=headers, timeout=aiohttp.ClientTimeout(total=self.timeout)) as response:
                        if response.status == 200:
                            data = await response.json()
                            return data.get("data", [])
                        elif response.status == 429:
                            print(f"[async_get_models] Rate limited (429), attempt {attempt + 1}")
                            await asyncio.sleep(self.retry_delay * (attempt + 1))
                            api_key = self.get_random_key()
                            headers["Authorization"] = f"Bearer {api_key}"
                        elif response.status == 401:
                            print(f"[async_get_models] Unauthorized (401), trying next key")
                            api_key = self.get_random_key()
                            headers["Authorization"] = f"Bearer {api_key}"
                            await asyncio.sleep(self.retry_delay)
                        else:
                            body = await response.text()
                            print(f"[async_get_models] HTTP {response.status}: {body[:100]}")
                            await asyncio.sleep(self.retry_delay)
            except (aiohttp.ClientError, asyncio.TimeoutError) as e:
                print(f"[async_get_models] Request error: {e}")
                await asyncio.sleep(self.retry_delay)

        print(f"[async_get_models] Failed after {self.max_retries} attempts")
        return []

    def test_model(self, model_id: str, prompt: str = "hi") -> bool:
        url = "https://openrouter.ai/api/v1/chat/completions"
        payload = {
            "model": model_id,
            "messages": [{"role": "user", "content": prompt}],
            "max_tokens": 10
        }

        response = self.request_with_retry("POST", url, json=payload)

        if response and response.status_code == 200:
            return True
        return False