Skip to content

Commit 4830fee

Browse files
authored
Merge pull request #39 from EvickaStudio/dev
Dev
2 parents 081849a + afcaad4 commit 4830fee

File tree

2 files changed

+55
-44
lines changed

2 files changed

+55
-44
lines changed

src/core/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Version information for MoodleMate."""
22

3-
__version__ = "2.1.0"
3+
__version__ = "2.1.1"

src/services/ai/chat.py

Lines changed: 54 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import logging
22
import re
3+
import time
34
from typing import Dict, List, Optional
45

56
import openai # version 1.5
@@ -53,6 +54,11 @@ def _init_gpt(self):
5354
}
5455
self._api_key_pattern = re.compile(r"^sk-[A-Za-z0-9_-]{48,}$")
5556

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+
5662
@property
5763
def api_key(self) -> Optional[str]:
5864
"""Get the configured API key."""
@@ -186,11 +192,10 @@ def _chat_completion( # noqa: C901
186192
model: str,
187193
temperature: float,
188194
max_tokens: Optional[int],
189-
) -> str:
195+
max_retries: int = 3,
196+
retry_delay: float = 1.0,
197+
) -> str: # sourcery skip: low-code-quality
190198
"""Internal method to handle chat completion requests."""
191-
max_retries = 3
192-
retry_delay = 2 # seconds
193-
194199
for attempt in range(1, max_retries + 1):
195200
try:
196201
# Convert messages to the expected type
@@ -210,12 +215,24 @@ def _chat_completion( # noqa: C901
210215
)
211216
# Add other roles as needed
212217

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+
214234
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
219236
)
220237

221238
# Extract and validate response
@@ -258,64 +275,58 @@ def _chat_completion( # noqa: C901
258275
return output_text
259276

260277
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:
272279
raise RateLimitExceededError(
273280
f"Rate limit exceeded after {max_retries} attempts: {str(e)}"
274281
) from e
275282

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)
276288
except openai.APITimeoutError as e:
277289
if attempt < max_retries:
278290
wait_time = retry_delay * (2 ** (attempt - 1))
279291
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..."
281294
)
282-
import time
283-
284295
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
289300

290301
except openai.APIConnectionError as e:
291302
if attempt < max_retries:
292303
wait_time = retry_delay * (2 ** (attempt - 1))
293304
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..."
295307
)
296-
import time
297-
298308
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
303313

304314
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
306316
if attempt < max_retries:
307317
wait_time = retry_delay * (2 ** (attempt - 1))
308318
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..."
310321
)
311-
import time
312-
313322
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:
319330
raise ClientError(f"Client error: {str(e)}") from e
320331
else:
321332
raise ChatCompletionError(f"API error: {str(e)}") from e

0 commit comments

Comments
 (0)