| 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()
|
|
|
| for attempt in range(self.max_retries):
|
|
|
| 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)
|
| 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
|
|
|