1
1
import logging
2
2
import re
3
+ import time
3
4
from typing import Dict , List , Optional
4
5
5
6
import openai # version 1.5
@@ -53,6 +54,11 @@ def _init_gpt(self):
53
54
}
54
55
self ._api_key_pattern = re .compile (r"^sk-[A-Za-z0-9_-]{48,}$" )
55
56
57
+ @property
58
+ def is_openrouter (self ) -> bool :
59
+ """Check if the current endpoint is for OpenRouter."""
60
+ return "openrouter.ai" in self ._endpoint .lower () if self ._endpoint else False
61
+
56
62
@property
57
63
def api_key (self ) -> Optional [str ]:
58
64
"""Get the configured API key."""
@@ -186,11 +192,10 @@ def _chat_completion( # noqa: C901
186
192
model : str ,
187
193
temperature : float ,
188
194
max_tokens : Optional [int ],
189
- ) -> str :
195
+ max_retries : int = 3 ,
196
+ retry_delay : float = 1.0 ,
197
+ ) -> str : # sourcery skip: low-code-quality
190
198
"""Internal method to handle chat completion requests."""
191
- max_retries = 3
192
- retry_delay = 2 # seconds
193
-
194
199
for attempt in range (1 , max_retries + 1 ):
195
200
try :
196
201
# Convert messages to the expected type
@@ -210,12 +215,24 @@ def _chat_completion( # noqa: C901
210
215
)
211
216
# Add other roles as needed
212
217
213
- # Make the API call first since token counting might fail for unknown models
218
+ # Prepare headers for OpenRouter if needed
219
+ extra_headers = {}
220
+ if self .is_openrouter :
221
+ extra_headers ["HTTP-Referer" ] = "https://moodle-mate.app"
222
+ extra_headers ["X-Title" ] = "Moodle Mate"
223
+
224
+ # Prepare arguments for the API call
225
+ api_call_args = {
226
+ "model" : model ,
227
+ "messages" : typed_messages ,
228
+ "temperature" : temperature ,
229
+ "max_tokens" : max_tokens ,
230
+ }
231
+ if extra_headers : # Only include extra_headers if it's a non-empty dict
232
+ api_call_args ["extra_headers" ] = extra_headers
233
+
214
234
response : ChatCompletion = openai .chat .completions .create (
215
- model = model ,
216
- messages = typed_messages ,
217
- temperature = temperature ,
218
- max_tokens = max_tokens ,
235
+ ** api_call_args
219
236
)
220
237
221
238
# Extract and validate response
@@ -258,64 +275,58 @@ def _chat_completion( # noqa: C901
258
275
return output_text
259
276
260
277
except openai .RateLimitError as e :
261
- if attempt < max_retries :
262
- wait_time = retry_delay * (
263
- 2 ** (attempt - 1 )
264
- ) # Exponential backoff
265
- logging .warning (
266
- f"Rate limit exceeded. Retrying in { wait_time } seconds... (Attempt { attempt } /{ max_retries } )"
267
- )
268
- import time
269
-
270
- time .sleep (wait_time )
271
- else :
278
+ if attempt >= max_retries :
272
279
raise RateLimitExceededError (
273
280
f"Rate limit exceeded after { max_retries } attempts: { str (e )} "
274
281
) from e
275
282
283
+ wait_time = retry_delay * (2 ** (attempt - 1 )) # Exponential backoff
284
+ logging .warning (
285
+ f"Rate limit exceeded. Retrying in { wait_time } seconds... (Attempt { attempt } /{ max_retries } )"
286
+ )
287
+ time .sleep (wait_time )
276
288
except openai .APITimeoutError as e :
277
289
if attempt < max_retries :
278
290
wait_time = retry_delay * (2 ** (attempt - 1 ))
279
291
logging .warning (
280
- f"API timeout. Retrying in { wait_time } seconds... (Attempt { attempt } /{ max_retries } )"
292
+ f"API timeout on attempt { attempt } /{ max_retries } . "
293
+ f"Retrying in { wait_time } seconds..."
281
294
)
282
- import time
283
-
284
295
time .sleep (wait_time )
285
- else :
286
- raise APITimeoutError (
287
- f"API request timed out after { max_retries } attempts: { str ( e ) } "
288
- ) from e
296
+ continue
297
+ raise APITimeoutError (
298
+ f"Request timed out after { max_retries } attempts"
299
+ ) from e
289
300
290
301
except openai .APIConnectionError as e :
291
302
if attempt < max_retries :
292
303
wait_time = retry_delay * (2 ** (attempt - 1 ))
293
304
logging .warning (
294
- f"API connection error. Retrying in { wait_time } seconds... (Attempt { attempt } /{ max_retries } )"
305
+ f"API connection error on attempt { attempt } /{ max_retries } . "
306
+ f"Retrying in { wait_time } seconds..."
295
307
)
296
- import time
297
-
298
308
time .sleep (wait_time )
299
- else :
300
- raise APIConnectionError (
301
- f"API connection failed after { max_retries } attempts: { str ( e ) } "
302
- ) from e
309
+ continue
310
+ raise APIConnectionError (
311
+ f"Connection failed after { max_retries } attempts"
312
+ ) from e
303
313
304
314
except openai .APIError as e :
305
- if 500 <= getattr ( e , " status_code" , 0 ) < 600 :
315
+ if e . status_code and 500 <= e . status_code < 600 : # Server errors
306
316
if attempt < max_retries :
307
317
wait_time = retry_delay * (2 ** (attempt - 1 ))
308
318
logging .warning (
309
- f"Server error. Retrying in { wait_time } seconds... (Attempt { attempt } /{ max_retries } )"
319
+ f"Server error (status { e .status_code } ) on attempt { attempt } /{ max_retries } . "
320
+ f"Retrying in { wait_time } seconds..."
310
321
)
311
- import time
312
-
313
322
time .sleep (wait_time )
314
- else :
315
- raise ServerError (
316
- f"OpenAI server error after { max_retries } attempts: { str (e )} "
317
- ) from e
318
- elif 400 <= getattr (e , "status_code" , 0 ) < 500 :
323
+ continue
324
+ raise ServerError (
325
+ f"Server error after { max_retries } attempts: { str (e )} "
326
+ ) from e
327
+ elif e .status_code == 401 :
328
+ raise InvalidAPIKeyError ("Invalid API key provided" ) from e
329
+ elif e .status_code and 400 <= e .status_code < 500 :
319
330
raise ClientError (f"Client error: { str (e )} " ) from e
320
331
else :
321
332
raise ChatCompletionError (f"API error: { str (e )} " ) from e
0 commit comments