IliaLarchenko commited on
Commit
44800eb
1 Parent(s): 1273e6d

Added errors handling

Browse files
Files changed (5) hide show
  1. .env.huggingface.example +1 -1
  2. api/audio.py +56 -20
  3. api/llm.py +58 -43
  4. docs/instruction.py +1 -1
  5. utils/errors.py +15 -0
.env.huggingface.example CHANGED
@@ -24,7 +24,7 @@ STT_NAME=whisper-tiny.en
24
 
25
  # You can use compatible TTS model from HuggingFace
26
  # For example you can try public Inference API endpoint for Facebook MMS-TTS model
27
- # Im my experience OS TTS models from HF sound much more robotic than OpenAI TTS models
28
  TTS_URL=https://api-inference.huggingface.co/models/facebook/mms-tts-eng
29
  TTS_TYPE=HF_API
30
  TTS_NAME=Facebook-mms-tts-eng
 
24
 
25
  # You can use compatible TTS model from HuggingFace
26
  # For example you can try public Inference API endpoint for Facebook MMS-TTS model
27
+ # In my experience OS TTS models from HF sound much more robotic than OpenAI TTS models
28
  TTS_URL=https://api-inference.huggingface.co/models/facebook/mms-tts-eng
29
  TTS_TYPE=HF_API
30
  TTS_NAME=Facebook-mms-tts-eng
api/audio.py CHANGED
@@ -5,6 +5,8 @@ import requests
5
 
6
  from openai import OpenAI
7
 
 
 
8
 
9
  def numpy_audio_to_bytes(audio_data):
10
  sample_rate = 44100
@@ -12,11 +14,14 @@ def numpy_audio_to_bytes(audio_data):
12
  sampwidth = 2
13
 
14
  buffer = io.BytesIO()
15
- with wave.open(buffer, "wb") as wf:
16
- wf.setnchannels(num_channels)
17
- wf.setsampwidth(sampwidth)
18
- wf.setframerate(sample_rate)
19
- wf.writeframes(audio_data.tobytes())
 
 
 
20
  return buffer.getvalue()
21
 
22
 
@@ -28,14 +33,31 @@ class STTManager:
28
  if convert_to_bytes:
29
  audio = numpy_audio_to_bytes(audio[1])
30
 
31
- if self.config.stt.type == "OPENAI_API":
32
- data = ("temp.wav", audio, "audio/wav")
33
- client = OpenAI(base_url=self.config.stt.url, api_key=self.config.stt.key)
34
- transcription = client.audio.transcriptions.create(model=self.config.stt.name, file=data, response_format="text")
35
- elif self.config.stt.type == "HF_API":
36
- headers = {"Authorization": "Bearer " + self.config.stt.key}
37
- transcription = requests.post(self.config.stt.url, headers=headers, data=audio)
38
- transcription = transcription.json()["text"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
  return transcription
41
 
@@ -45,18 +67,32 @@ class TTSManager:
45
  self.config = config
46
 
47
  def text_to_speech(self, text):
48
- if self.config.tts.type == "OPENAI_API":
49
- client = OpenAI(base_url=self.config.tts.url, api_key=self.config.tts.key)
50
- response = client.audio.speech.create(model=self.config.tts.name, voice="alloy", response_format="opus", input=text)
51
- elif self.config.tts.type == "HF_API":
52
- headers = {"Authorization": "Bearer " + self.config.tts.key}
53
- response = requests.post(self.config.tts.url, headers=headers)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
  return response.content
56
 
57
  def read_last_message(self, chat_display):
58
  if chat_display:
59
- last_message = chat_display[-1][1] # Assuming the message is stored at index 1 of the last tuple/list in chat_display
60
  if last_message is not None:
61
  return self.text_to_speech(last_message)
62
  return None
 
5
 
6
  from openai import OpenAI
7
 
8
+ from utils.errors import APIError, AudioConversionError
9
+
10
 
11
  def numpy_audio_to_bytes(audio_data):
12
  sample_rate = 44100
 
14
  sampwidth = 2
15
 
16
  buffer = io.BytesIO()
17
+ try:
18
+ with wave.open(buffer, "wb") as wf:
19
+ wf.setnchannels(num_channels)
20
+ wf.setsampwidth(sampwidth)
21
+ wf.setframerate(sample_rate)
22
+ wf.writeframes(audio_data.tobytes())
23
+ except Exception as e:
24
+ raise AudioConversionError(f"Error converting numpy array to audio bytes: {e}")
25
  return buffer.getvalue()
26
 
27
 
 
33
  if convert_to_bytes:
34
  audio = numpy_audio_to_bytes(audio[1])
35
 
36
+ try:
37
+ if self.config.stt.type == "OPENAI_API":
38
+ data = ("temp.wav", audio, "audio/wav")
39
+ client = OpenAI(base_url=self.config.stt.url, api_key=self.config.stt.key)
40
+ response = client.audio.transcriptions.create(model=self.config.stt.name, file=data, response_format="text")
41
+ if not response.success:
42
+ raise APIError(
43
+ "STT Error: OpenAI API error",
44
+ status_code=response.status_code,
45
+ details=response.error.get("message", "No error message provided"),
46
+ )
47
+ transcription = response.data
48
+ elif self.config.stt.type == "HF_API":
49
+ headers = {"Authorization": "Bearer " + self.config.stt.key}
50
+ response = requests.post(self.config.stt.url, headers=headers, data=audio)
51
+ if response.status_code != 200:
52
+ error_details = response.json().get("error", "No error message provided")
53
+ raise APIError("STT Error: HF API error", status_code=response.status_code, details=error_details)
54
+ transcription = response.json().get("text", None)
55
+ if transcription is None:
56
+ raise APIError("STT Error: No transcription returned by HF API")
57
+ except APIError as e:
58
+ raise
59
+ except Exception as e:
60
+ raise APIError(f"STT Error: Unexpected error: {e}")
61
 
62
  return transcription
63
 
 
67
  self.config = config
68
 
69
  def text_to_speech(self, text):
70
+ try:
71
+ if self.config.tts.type == "OPENAI_API":
72
+ client = OpenAI(base_url=self.config.tts.url, api_key=self.config.tts.key)
73
+ response = client.audio.speech.create(model=self.config.tts.name, voice="alloy", response_format="opus", input=text)
74
+ if not response.success:
75
+ raise APIError(
76
+ "TTS Error: OpenAI API error",
77
+ status_code=response.status_code,
78
+ details=response.error.get("message", "No error message provided"),
79
+ )
80
+ elif self.config.tts.type == "HF_API":
81
+ headers = {"Authorization": "Bearer " + self.config.tts.key}
82
+ response = requests.post(self.config.tts.url, headers=headers, json={"inputs": text})
83
+ if response.status_code != 200:
84
+ error_details = response.json().get("error", "No error message provided")
85
+ raise APIError("TTS Error: HF API error", status_code=response.status_code, details=error_details)
86
+ except APIError as e:
87
+ raise
88
+ except Exception as e:
89
+ raise APIError(f"TTS Error: Unexpected error: {e}")
90
 
91
  return response.content
92
 
93
  def read_last_message(self, chat_display):
94
  if chat_display:
95
+ last_message = chat_display[-1][1]
96
  if last_message is not None:
97
  return self.text_to_speech(last_message)
98
  return None
api/llm.py CHANGED
@@ -2,6 +2,8 @@ import os
2
 
3
  from openai import OpenAI
4
 
 
 
5
 
6
  class LLMManager:
7
  def __init__(self, config, prompts):
@@ -10,27 +12,30 @@ class LLMManager:
10
  self.prompts = prompts
11
 
12
  def test_connection(self):
13
- response = self.client.chat.completions.create(
14
- model=self.config.llm.name,
15
- messages=[
16
- {"role": "system", "content": "You just help me test the connection."},
17
- {"role": "user", "content": "Hi!"},
18
- {"role": "user", "content": "Ping!"},
19
- ],
20
- )
21
- return response.choices[0].message.content.strip()
 
 
 
 
 
22
 
23
  def init_bot(self, problem=""):
24
-
25
  system_prompt = self.prompts["coding_interviewer_prompt"]
26
  if os.getenv("IS_DEMO"):
27
  system_prompt += " Keep your responses very short and simple, no more than 100 words."
28
 
29
- chat_history = [
30
  {"role": "system", "content": system_prompt},
31
  {"role": "system", "content": f"The candidate is solving the following problem: {problem}"},
32
  ]
33
- return chat_history
34
 
35
  def get_problem(self, requirements, difficulty, topic):
36
  full_prompt = (
@@ -43,61 +48,71 @@ class LLMManager:
43
  if os.getenv("IS_DEMO"):
44
  full_prompt += " Keep your response very short and simple, no more than 200 words."
45
 
46
- response = self.client.chat.completions.create(
47
- model=self.config.llm.name,
48
- messages=[
49
- {"role": "system", "content": self.prompts["problem_generation_prompt"]},
50
- {"role": "user", "content": full_prompt},
51
- ],
52
- temperature=1.0,
53
- )
54
- question = response.choices[0].message.content.strip()
 
 
 
 
 
 
55
  chat_history = self.init_bot(question)
56
  return question, chat_history
57
 
58
  def send_request(self, code, previous_code, message, chat_history, chat_display):
59
- # Update chat history if code has changed
60
  if code != previous_code:
61
  chat_history.append({"role": "user", "content": f"My latest code:\n{code}"})
62
  chat_history.append({"role": "user", "content": message})
63
 
64
- # Process the updated chat history with the language model
65
- response = self.client.chat.completions.create(model=self.config.llm.name, messages=chat_history)
66
- reply = response.choices[0].message.content.strip()
 
 
 
 
 
67
  chat_history.append({"role": "assistant", "content": reply})
68
 
69
- # Update chat display with the new reply
70
  if chat_display:
71
  chat_display[-1][1] = reply
72
  else:
73
  chat_display.append([message, reply])
74
 
75
- # Return updated chat history, chat display, an empty string placeholder, and the unchanged code
76
  return chat_history, chat_display, "", code
77
 
78
  def end_interview(self, problem_description, chat_history):
79
  if not chat_history or len(chat_history) <= 2:
80
  return "No interview content available to review."
81
 
82
- transcript = []
83
- for message in chat_history[1:]:
84
- role = message["role"]
85
- content = f"{role.capitalize()}: {message['content']}"
86
- transcript.append(content)
87
 
88
  system_prompt = self.prompts["grading_feedback_prompt"]
89
  if os.getenv("IS_DEMO"):
90
  system_prompt += " Keep your response very short and simple, no more than 200 words."
91
 
92
- response = self.client.chat.completions.create(
93
- model=self.config.llm.name,
94
- messages=[
95
- {"role": "system", "content": system_prompt},
96
- {"role": "user", "content": f"The original problem to solve: {problem_description}"},
97
- {"role": "user", "content": "\n\n".join(transcript)},
98
- {"role": "user", "content": "Grade the interview based on the transcript provided and give feedback."},
99
- ],
100
- temperature=0.5,
101
- )
102
- feedback = response.choices[0].message.content.strip()
 
 
 
 
 
 
103
  return feedback
 
2
 
3
  from openai import OpenAI
4
 
5
+ from utils.errors import APIError
6
+
7
 
8
  class LLMManager:
9
  def __init__(self, config, prompts):
 
12
  self.prompts = prompts
13
 
14
  def test_connection(self):
15
+ try:
16
+ response = self.client.chat.completions.create(
17
+ model=self.config.llm.name,
18
+ messages=[
19
+ {"role": "system", "content": "You just help me test the connection."},
20
+ {"role": "user", "content": "Hi!"},
21
+ {"role": "user", "content": "Ping!"},
22
+ ],
23
+ )
24
+ if not response.choices:
25
+ raise APIError("LLM Test Connection Error", details="No choices in response")
26
+ return response.choices[0].message.content.strip()
27
+ except Exception as e:
28
+ raise APIError(f"LLM Test Connection Error: Unexpected error: {e}")
29
 
30
  def init_bot(self, problem=""):
 
31
  system_prompt = self.prompts["coding_interviewer_prompt"]
32
  if os.getenv("IS_DEMO"):
33
  system_prompt += " Keep your responses very short and simple, no more than 100 words."
34
 
35
+ return [
36
  {"role": "system", "content": system_prompt},
37
  {"role": "system", "content": f"The candidate is solving the following problem: {problem}"},
38
  ]
 
39
 
40
  def get_problem(self, requirements, difficulty, topic):
41
  full_prompt = (
 
48
  if os.getenv("IS_DEMO"):
49
  full_prompt += " Keep your response very short and simple, no more than 200 words."
50
 
51
+ try:
52
+ response = self.client.chat.completions.create(
53
+ model=self.config.llm.name,
54
+ messages=[
55
+ {"role": "system", "content": self.prompts["problem_generation_prompt"]},
56
+ {"role": "user", "content": full_prompt},
57
+ ],
58
+ temperature=1.0,
59
+ )
60
+ if not response.choices:
61
+ raise APIError("LLM Problem Generation Error", details="No choices in response")
62
+ question = response.choices[0].message.content.strip()
63
+ except Exception as e:
64
+ raise APIError(f"LLM Problem Generation Error: Unexpected error: {e}")
65
+
66
  chat_history = self.init_bot(question)
67
  return question, chat_history
68
 
69
  def send_request(self, code, previous_code, message, chat_history, chat_display):
 
70
  if code != previous_code:
71
  chat_history.append({"role": "user", "content": f"My latest code:\n{code}"})
72
  chat_history.append({"role": "user", "content": message})
73
 
74
+ try:
75
+ response = self.client.chat.completions.create(model=self.config.llm.name, messages=chat_history)
76
+ if not response.choices:
77
+ raise APIError("LLM Send Request Error", details="No choices in response")
78
+ reply = response.choices[0].message.content.strip()
79
+ except Exception as e:
80
+ raise APIError(f"LLM Send Request Error: Unexpected error: {e}")
81
+
82
  chat_history.append({"role": "assistant", "content": reply})
83
 
 
84
  if chat_display:
85
  chat_display[-1][1] = reply
86
  else:
87
  chat_display.append([message, reply])
88
 
 
89
  return chat_history, chat_display, "", code
90
 
91
  def end_interview(self, problem_description, chat_history):
92
  if not chat_history or len(chat_history) <= 2:
93
  return "No interview content available to review."
94
 
95
+ transcript = [f"{message['role'].capitalize()}: {message['content']}" for message in chat_history[1:]]
 
 
 
 
96
 
97
  system_prompt = self.prompts["grading_feedback_prompt"]
98
  if os.getenv("IS_DEMO"):
99
  system_prompt += " Keep your response very short and simple, no more than 200 words."
100
 
101
+ try:
102
+ response = self.client.chat.completions.create(
103
+ model=self.config.llm.name,
104
+ messages=[
105
+ {"role": "system", "content": system_prompt},
106
+ {"role": "user", "content": f"The original problem to solve: {problem_description}"},
107
+ {"role": "user", "content": "\n\n".join(transcript)},
108
+ {"role": "user", "content": "Grade the interview based on the transcript provided and give feedback."},
109
+ ],
110
+ temperature=0.5,
111
+ )
112
+ if not response.choices:
113
+ raise APIError("LLM End Interview Error", details="No choices in response")
114
+ feedback = response.choices[0].message.content.strip()
115
+ except Exception as e:
116
+ raise APIError(f"LLM End Interview Error: Unexpected error: {e}")
117
+
118
  return feedback
docs/instruction.py CHANGED
@@ -3,7 +3,7 @@
3
  instruction = {
4
  "demo": """
5
  <span style="color: red;">
6
- This is a demo version utilizing free API access with strict request limits. As a result, the experience may be slow, occasionally buggy, and not of the highest quality. If a model is unavailable, please wait for a minute before retrying. Persistent unavailability may indicate that the request limit has been reached, making the demo temporarily inaccessible.
7
  For a significantly better experience, please run the service locally and use your own OpenAI key or HuggingFace models.
8
  </span>
9
 
 
3
  instruction = {
4
  "demo": """
5
  <span style="color: red;">
6
+ This is a demo version utilizing free API access with strict request limits. As a result, the experience may be slow, occasionally buggy, and not of the highest quality (e.g. robotic voice and very short problem and feedback). If a model is unavailable, please wait for a minute before retrying. Persistent unavailability may indicate that the request limit has been reached, making the demo temporarily inaccessible.
7
  For a significantly better experience, please run the service locally and use your own OpenAI key or HuggingFace models.
8
  </span>
9
 
utils/errors.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class AudioConversionError(Exception):
2
+ """Exception raised for errors in the audio conversion process."""
3
+
4
+ pass
5
+
6
+
7
+ class APIError(Exception):
8
+ """Custom exception for API error handling."""
9
+
10
+ def __init__(self, message, status_code=None, details=None):
11
+ if details:
12
+ super().__init__(f"{message} - Details: {details}")
13
+ else:
14
+ super().__init__(message)
15
+ self.status_code = status_code