| 18 | |
| 19 | |
| 20 | class OpenRouterClient: |
| 21 | BASE_URL = "https://openrouter.ai/api/v1/chat/completions" |
| 22 | |
| 23 | # Default models - cheap and effective for testing |
| 24 | DEFAULT_MODEL = "deepseek/deepseek-chat" # ~$0.14/$0.28 per 1M tokens |
| 25 | CHEAP_MODEL = "qwen/qwen-2.5-7b-instruct" # ~$0.05/$0.10 per 1M tokens |
| 26 | FREE_MODEL = "deepseek/deepseek-chat:free" # Free with data opt-in |
| 27 | PREMIUM_MODEL = "openai/gpt-4o" # Best quality |
| 28 | |
| 29 | def __init__(self, api_key: str, model: Optional[str] = None): |
| 30 | self._api_key = api_key |
| 31 | self.model = model or self.DEFAULT_MODEL |
| 32 | self.headers = { |
| 33 | "Authorization": f"Bearer {api_key}", |
| 34 | "Content-Type": "application/json", |
| 35 | "HTTP-Referer": "https://github.com/RightNow-AI/rightnow-cli", |
| 36 | "X-Title": "RightNow CLI" |
| 37 | } |
| 38 | |
| 39 | @property |
| 40 | def api_key(self): |
| 41 | return self._api_key |
| 42 | |
| 43 | @api_key.setter |
| 44 | def api_key(self, value): |
| 45 | """Update API key and headers when API key changes.""" |
| 46 | self._api_key = value |
| 47 | # Update the Authorization header with the new API key |
| 48 | self.headers["Authorization"] = f"Bearer {value}" |
| 49 | |
| 50 | @backoff.on_exception( |
| 51 | backoff.expo, |
| 52 | (requests.exceptions.RequestException, requests.exceptions.HTTPError), |
| 53 | max_tries=3, |
| 54 | max_time=60 |
| 55 | ) |
| 56 | def _make_request(self, prompt: str, system_prompt: str, model: Optional[str] = None) -> str: |
| 57 | """Make a request to OpenRouter API with retry logic.""" |
| 58 | # Validate API key before making request |
| 59 | if not self.api_key or self.api_key == "sk-temp-placeholder": |
| 60 | raise ValueError("Invalid API key. Please check your OpenRouter API key.") |
| 61 | |
| 62 | model_to_use = model or self.model |
| 63 | payload = { |
| 64 | "model": model_to_use, |
| 65 | "messages": [ |
| 66 | {"role": "system", "content": system_prompt}, |
| 67 | {"role": "user", "content": prompt} |
| 68 | ], |
| 69 | "temperature": 0.2, |
| 70 | "max_tokens": 4000 |
| 71 | } |
| 72 | |
| 73 | response = requests.post( |
| 74 | self.BASE_URL, |
| 75 | headers=self.headers, |
| 76 | json=payload, |
| 77 | timeout=60 |
no outgoing calls