Spaces:
Sleeping
Sleeping
IliaLarchenko
commited on
Commit
•
3667c7a
1
Parent(s):
4518e15
Huge refactoring
Browse files- api/audio.py +62 -0
- api/llm.py +88 -0
- app.py +30 -50
- audio.py +0 -17
- config.py +19 -25
- docs/instruction.py +29 -0
- llm.py +0 -135
- prompts.py +0 -37
- options.py → resources/data.py +0 -0
- resources/prompts.py +34 -0
- utils/ui.py +11 -0
api/audio.py
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import io
|
2 |
+
import wave
|
3 |
+
|
4 |
+
import requests
|
5 |
+
|
6 |
+
from openai import OpenAI
|
7 |
+
|
8 |
+
|
9 |
+
def numpy_audio_to_bytes(audio_data):
|
10 |
+
sample_rate = 44100
|
11 |
+
num_channels = 1
|
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 |
+
|
23 |
+
class STTManager:
|
24 |
+
def __init__(self, config):
|
25 |
+
self.config = config
|
26 |
+
|
27 |
+
def speech_to_text(self, audio, convert_to_bytes=True):
|
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 |
+
|
42 |
+
|
43 |
+
class TTSManager:
|
44 |
+
def __init__(self, config):
|
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
|
api/llm.py
ADDED
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from openai import OpenAI
|
2 |
+
|
3 |
+
|
4 |
+
class LLMManager:
|
5 |
+
def __init__(self, config, prompts):
|
6 |
+
self.config = config
|
7 |
+
self.client = OpenAI(base_url=config.llm.url, api_key=config.llm.key)
|
8 |
+
self.prompts = prompts
|
9 |
+
|
10 |
+
def test_connection(self):
|
11 |
+
response = self.client.chat.completions.create(
|
12 |
+
model=self.config.llm.name,
|
13 |
+
messages=[
|
14 |
+
{"role": "system", "content": "You just help me test the connection."},
|
15 |
+
{"role": "user", "content": "Hi!"},
|
16 |
+
{"role": "user", "content": "Ping!"},
|
17 |
+
],
|
18 |
+
)
|
19 |
+
return response.choices[0].message.content.strip()
|
20 |
+
|
21 |
+
def init_bot(self, problem=""):
|
22 |
+
chat_history = [
|
23 |
+
{"role": "system", "content": self.prompts["coding_interviewer_prompt"]},
|
24 |
+
{"role": "system", "content": f"The candidate is solving the following problem: {problem}"},
|
25 |
+
]
|
26 |
+
return chat_history
|
27 |
+
|
28 |
+
def get_problem(self, requirements, difficulty, topic):
|
29 |
+
full_prompt = (
|
30 |
+
f"Create a {difficulty} {topic} coding problem. "
|
31 |
+
f"Additional requirements: {requirements}. "
|
32 |
+
"The problem should be clearly stated, well-formatted, and solvable within 30 minutes. "
|
33 |
+
"Ensure the problem varies each time to provide a wide range of challenges."
|
34 |
+
)
|
35 |
+
response = self.client.chat.completions.create(
|
36 |
+
model=self.config.llm.name,
|
37 |
+
messages=[
|
38 |
+
{"role": "system", "content": self.prompts["problem_generation_prompt"]},
|
39 |
+
{"role": "user", "content": full_prompt},
|
40 |
+
],
|
41 |
+
temperature=1.0,
|
42 |
+
)
|
43 |
+
question = response.choices[0].message.content.strip()
|
44 |
+
chat_history = self.init_bot(question)
|
45 |
+
return question, chat_history
|
46 |
+
|
47 |
+
def send_request(self, code, previous_code, message, chat_history, chat_display):
|
48 |
+
# Update chat history if code has changed
|
49 |
+
if code != previous_code:
|
50 |
+
chat_history.append({"role": "user", "content": f"My latest code:\n{code}"})
|
51 |
+
chat_history.append({"role": "user", "content": message})
|
52 |
+
|
53 |
+
# Process the updated chat history with the language model
|
54 |
+
response = self.client.chat.completions.create(model=self.config.llm.name, messages=chat_history)
|
55 |
+
reply = response.choices[0].message.content.strip()
|
56 |
+
chat_history.append({"role": "assistant", "content": reply})
|
57 |
+
|
58 |
+
# Update chat display with the new reply
|
59 |
+
if chat_display:
|
60 |
+
chat_display[-1][1] = reply
|
61 |
+
else:
|
62 |
+
chat_display.append([message, reply])
|
63 |
+
|
64 |
+
# Return updated chat history, chat display, an empty string placeholder, and the unchanged code
|
65 |
+
return chat_history, chat_display, "", code
|
66 |
+
|
67 |
+
def end_interview(self, problem_description, chat_history):
|
68 |
+
if not chat_history or len(chat_history) <= 2:
|
69 |
+
return "No interview content available to review."
|
70 |
+
|
71 |
+
transcript = []
|
72 |
+
for message in chat_history[1:]:
|
73 |
+
role = message["role"]
|
74 |
+
content = f"{role.capitalize()}: {message['content']}"
|
75 |
+
transcript.append(content)
|
76 |
+
|
77 |
+
response = self.client.chat.completions.create(
|
78 |
+
model=self.config.llm.name,
|
79 |
+
messages=[
|
80 |
+
{"role": "system", "content": self.prompts["grading_feedback_prompt"]},
|
81 |
+
{"role": "user", "content": f"The original problem to solve: {problem_description}"},
|
82 |
+
{"role": "user", "content": "\n\n".join(transcript)},
|
83 |
+
{"role": "user", "content": "Grade the interview based on the transcript provided and give feedback."},
|
84 |
+
],
|
85 |
+
temperature=0.5,
|
86 |
+
)
|
87 |
+
feedback = response.choices[0].message.content.strip()
|
88 |
+
return feedback
|
app.py
CHANGED
@@ -2,9 +2,17 @@ import os
|
|
2 |
|
3 |
import gradio as gr
|
4 |
|
5 |
-
from
|
6 |
-
from llm import
|
7 |
-
from
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
|
9 |
default_audio_params = {
|
10 |
"label": "Record answer",
|
@@ -26,19 +34,6 @@ def hide_settings():
|
|
26 |
return init_acc, start_btn, solution_acc, end_btn, audio_input
|
27 |
|
28 |
|
29 |
-
def add_interviewer_message(message):
|
30 |
-
def f(chat):
|
31 |
-
chat.append((None, message))
|
32 |
-
return chat
|
33 |
-
|
34 |
-
return f
|
35 |
-
|
36 |
-
|
37 |
-
def add_candidate_message(message, chat):
|
38 |
-
chat.append((message, None))
|
39 |
-
return chat
|
40 |
-
|
41 |
-
|
42 |
def hide_solution():
|
43 |
solution_acc = gr.Accordion("Solution", open=False)
|
44 |
end_btn = gr.Button("Finish the interview", interactive=False)
|
@@ -52,51 +47,36 @@ with gr.Blocks() as demo:
|
|
52 |
with gr.Row():
|
53 |
with gr.Column(scale=10):
|
54 |
gr.Markdown("# Welcome to the AI Tech Interviewer Training!")
|
55 |
-
gr.Markdown(
|
56 |
-
"""
|
57 |
-
This project leverages the latest AI models to simulate a realistic tech interview experience,
|
58 |
-
allowing you to practice your coding interview skills in an environment that closely mimics the real thing.
|
59 |
-
While it's not designed to replace a human interviewer or the essential steps of interview preparation, such as studying algorithms and practicing coding,
|
60 |
-
it serves as a valuable addition to your preparation arsenal.
|
61 |
-
"""
|
62 |
-
)
|
63 |
|
64 |
if os.getenv("IS_DEMO"):
|
65 |
-
gr.Markdown(
|
66 |
-
"""
|
67 |
-
### Demo Version Notice
|
68 |
-
**This is a demo version running on limited resources, which may respond slower than usual.**
|
69 |
-
It's primarily for demonstration purposes.
|
70 |
-
For optimal performance, we recommend running this application on your local machine using your own OpenAI API_KEY or local models.
|
71 |
-
See the instructions below on how to set up and run this application locally for the best experience.
|
72 |
-
I also recommend to read this introduction page first.
|
73 |
-
If you proceed to the interview interface right now, just click on the 'Coding' tab.
|
74 |
-
"""
|
75 |
-
)
|
76 |
|
77 |
gr.Markdown("### Introduction")
|
78 |
gr.Markdown("### Setting Up Locally")
|
79 |
gr.Markdown("### Interview Interface Overview")
|
80 |
gr.Markdown("### Models Configuration")
|
|
|
|
|
81 |
|
82 |
with gr.Column(scale=1):
|
83 |
try:
|
84 |
-
audio_test = text_to_speech("Handshake")
|
85 |
-
gr.Markdown(f"TTS status: 🟢. Model: {
|
86 |
except:
|
87 |
-
gr.Markdown(f"TTS status: 🔴. Model: {
|
88 |
|
89 |
try:
|
90 |
-
text_test = speech_to_text(audio_test, False)
|
91 |
-
gr.Markdown(f"STT status: 🟢. Model: {
|
92 |
except:
|
93 |
-
gr.Markdown(f"STT status: 🔴. Model: {
|
94 |
|
95 |
try:
|
96 |
-
test_connection()
|
97 |
-
gr.Markdown(f"LLM status: 🟢. Model: {
|
98 |
except:
|
99 |
-
gr.Markdown(f"LLM status: 🔴. Model: {
|
100 |
|
101 |
with gr.Tab("Coding") as coding_tab:
|
102 |
chat_history = gr.State([])
|
@@ -148,14 +128,14 @@ with gr.Blocks() as demo:
|
|
148 |
coding_tab.select(fn=add_interviewer_message(fixed_messages["intro"]), inputs=[chat], outputs=[chat])
|
149 |
|
150 |
start_btn.click(fn=add_interviewer_message(fixed_messages["start"]), inputs=[chat], outputs=[chat]).then(
|
151 |
-
fn=get_problem,
|
152 |
inputs=[requirements, difficulty_select, topic_select],
|
153 |
outputs=[description, chat_history],
|
154 |
scroll_to_output=True,
|
155 |
).then(fn=hide_settings, inputs=None, outputs=[init_acc, start_btn, solution_acc, end_btn, audio_input])
|
156 |
|
157 |
message.submit(
|
158 |
-
fn=send_request,
|
159 |
inputs=[code, previous_code, message, chat_history, chat],
|
160 |
outputs=[chat_history, chat, message, previous_code],
|
161 |
)
|
@@ -165,18 +145,18 @@ with gr.Blocks() as demo:
|
|
165 |
inputs=[chat],
|
166 |
outputs=[chat],
|
167 |
).then(
|
168 |
-
fn=end_interview, inputs=[description, chat_history], outputs=feedback
|
169 |
).then(fn=hide_solution, inputs=None, outputs=[solution_acc, end_btn, problem_acc, audio_input])
|
170 |
|
171 |
-
audio_input.stop_recording(fn=speech_to_text, inputs=[audio_input], outputs=[message]).then(
|
172 |
fn=lambda: None, inputs=None, outputs=[audio_input]
|
173 |
).then(fn=add_candidate_message, inputs=[message, chat], outputs=[chat]).then(
|
174 |
-
fn=send_request,
|
175 |
inputs=[code, previous_code, message, chat_history, chat],
|
176 |
outputs=[chat_history, chat, message, previous_code],
|
177 |
)
|
178 |
|
179 |
-
chat.change(fn=read_last_message, inputs=[chat], outputs=[audio_output])
|
180 |
|
181 |
audio_output.stop(fn=lambda: None, inputs=None, outputs=[audio_output])
|
182 |
|
|
|
2 |
|
3 |
import gradio as gr
|
4 |
|
5 |
+
from api.audio import STTManager, TTSManager
|
6 |
+
from api.llm import LLMManager
|
7 |
+
from config import config
|
8 |
+
from docs.instruction import instruction
|
9 |
+
from resources.data import fixed_messages, topics_list
|
10 |
+
from resources.prompts import prompts
|
11 |
+
from utils.ui import add_candidate_message, add_interviewer_message
|
12 |
+
|
13 |
+
llm = LLMManager(config, prompts)
|
14 |
+
tts = TTSManager(config)
|
15 |
+
stt = STTManager(config)
|
16 |
|
17 |
default_audio_params = {
|
18 |
"label": "Record answer",
|
|
|
34 |
return init_acc, start_btn, solution_acc, end_btn, audio_input
|
35 |
|
36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
def hide_solution():
|
38 |
solution_acc = gr.Accordion("Solution", open=False)
|
39 |
end_btn = gr.Button("Finish the interview", interactive=False)
|
|
|
47 |
with gr.Row():
|
48 |
with gr.Column(scale=10):
|
49 |
gr.Markdown("# Welcome to the AI Tech Interviewer Training!")
|
50 |
+
gr.Markdown(instruction["intro"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
51 |
|
52 |
if os.getenv("IS_DEMO"):
|
53 |
+
gr.Markdown(instruction["demo"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
|
55 |
gr.Markdown("### Introduction")
|
56 |
gr.Markdown("### Setting Up Locally")
|
57 |
gr.Markdown("### Interview Interface Overview")
|
58 |
gr.Markdown("### Models Configuration")
|
59 |
+
gr.Markdown("### Acknowledgement")
|
60 |
+
gr.Markdown(instruction["acknowledgements"])
|
61 |
|
62 |
with gr.Column(scale=1):
|
63 |
try:
|
64 |
+
audio_test = tts.text_to_speech("Handshake")
|
65 |
+
gr.Markdown(f"TTS status: 🟢. Model: {config.tts.name}")
|
66 |
except:
|
67 |
+
gr.Markdown(f"TTS status: 🔴. Model: {config.tts.name}")
|
68 |
|
69 |
try:
|
70 |
+
text_test = stt.speech_to_text(audio_test, False)
|
71 |
+
gr.Markdown(f"STT status: 🟢. Model: {config.stt.name}")
|
72 |
except:
|
73 |
+
gr.Markdown(f"STT status: 🔴. Model: {config.stt.name}")
|
74 |
|
75 |
try:
|
76 |
+
llm.test_connection()
|
77 |
+
gr.Markdown(f"LLM status: 🟢. Model: {config.llm.name}")
|
78 |
except:
|
79 |
+
gr.Markdown(f"LLM status: 🔴. Model: {config.llm.name}")
|
80 |
|
81 |
with gr.Tab("Coding") as coding_tab:
|
82 |
chat_history = gr.State([])
|
|
|
128 |
coding_tab.select(fn=add_interviewer_message(fixed_messages["intro"]), inputs=[chat], outputs=[chat])
|
129 |
|
130 |
start_btn.click(fn=add_interviewer_message(fixed_messages["start"]), inputs=[chat], outputs=[chat]).then(
|
131 |
+
fn=llm.get_problem,
|
132 |
inputs=[requirements, difficulty_select, topic_select],
|
133 |
outputs=[description, chat_history],
|
134 |
scroll_to_output=True,
|
135 |
).then(fn=hide_settings, inputs=None, outputs=[init_acc, start_btn, solution_acc, end_btn, audio_input])
|
136 |
|
137 |
message.submit(
|
138 |
+
fn=llm.send_request,
|
139 |
inputs=[code, previous_code, message, chat_history, chat],
|
140 |
outputs=[chat_history, chat, message, previous_code],
|
141 |
)
|
|
|
145 |
inputs=[chat],
|
146 |
outputs=[chat],
|
147 |
).then(
|
148 |
+
fn=llm.end_interview, inputs=[description, chat_history], outputs=feedback
|
149 |
).then(fn=hide_solution, inputs=None, outputs=[solution_acc, end_btn, problem_acc, audio_input])
|
150 |
|
151 |
+
audio_input.stop_recording(fn=stt.speech_to_text, inputs=[audio_input], outputs=[message]).then(
|
152 |
fn=lambda: None, inputs=None, outputs=[audio_input]
|
153 |
).then(fn=add_candidate_message, inputs=[message, chat], outputs=[chat]).then(
|
154 |
+
fn=llm.send_request,
|
155 |
inputs=[code, previous_code, message, chat_history, chat],
|
156 |
outputs=[chat_history, chat, message, previous_code],
|
157 |
)
|
158 |
|
159 |
+
chat.change(fn=tts.read_last_message, inputs=[chat], outputs=[audio_output])
|
160 |
|
161 |
audio_output.stop(fn=lambda: None, inputs=None, outputs=[audio_output])
|
162 |
|
audio.py
DELETED
@@ -1,17 +0,0 @@
|
|
1 |
-
import io
|
2 |
-
import wave
|
3 |
-
|
4 |
-
|
5 |
-
def numpy_audio_to_bytes(audio_data):
|
6 |
-
sample_rate = 44100
|
7 |
-
num_channels = 1
|
8 |
-
sampwidth = 2
|
9 |
-
|
10 |
-
buffer = io.BytesIO()
|
11 |
-
with wave.open(buffer, "wb") as wf:
|
12 |
-
wf.setnchannels(num_channels)
|
13 |
-
wf.setsampwidth(sampwidth)
|
14 |
-
wf.setframerate(sample_rate)
|
15 |
-
wf.writeframes(audio_data.tobytes())
|
16 |
-
|
17 |
-
return buffer.getvalue()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
config.py
CHANGED
@@ -1,28 +1,22 @@
|
|
1 |
-
|
2 |
-
# X_TYPE - the type of the model, can be "OPENAI_API" or "HF_API"
|
3 |
-
# there should be an environment variable with the f"{}_KEY" name and the key as the value to authenticate the API
|
4 |
-
# X_NAME - the name of the model, used only for OpenAI API
|
5 |
|
6 |
-
|
7 |
-
LLM_TYPE = "OPENAI_API"
|
8 |
-
LLM_NAME = "gpt-3.5-turbo"
|
9 |
-
# "gpt-3.5-turbo" - ~3 seconds delay with decent quality
|
10 |
-
# "gpt-4-turbo","gpt-4", etc. 10+ seconds delay but higher quality
|
11 |
-
# For HuggingFace models, the Messages API is used, it if compatible with Open AI API
|
12 |
-
# Don't forget to add "/v1" to the end of the URL for HuggingFace LLM models
|
13 |
-
# https://huggingface.co/docs/text-generation-inference/en/messages_api
|
14 |
|
15 |
-
STT_URL = "https://api-inference.huggingface.co/models/openai/whisper-tiny.en"
|
16 |
-
STT_TYPE = "HF_API"
|
17 |
-
STT_NAME = "whisper-1"
|
18 |
-
# "whisper-1" is the only OpenAI STT model available for OpenAI API
|
19 |
-
# The whisper family with more models is available on HuggingFace:
|
20 |
-
# https://huggingface.co/collections/openai/whisper-release-6501bba2cf999715fd953013
|
21 |
-
# you can also use any other compatible model from HuggingFace
|
22 |
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
|
|
|
|
|
|
2 |
|
3 |
+
from dotenv import load_dotenv
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5 |
|
6 |
+
class ServiceConfig:
|
7 |
+
def __init__(self, url_var, type_var, name_var):
|
8 |
+
self.url = os.getenv(url_var)
|
9 |
+
self.type = os.getenv(type_var)
|
10 |
+
self.name = os.getenv(name_var)
|
11 |
+
self.key = os.getenv(f"{self.type}_KEY")
|
12 |
+
|
13 |
+
|
14 |
+
class Config:
|
15 |
+
def __init__(self):
|
16 |
+
load_dotenv()
|
17 |
+
self.llm = ServiceConfig("LLM_URL", "LLM_TYPE", "LLM_NAME")
|
18 |
+
self.stt = ServiceConfig("STT_URL", "STT_TYPE", "STT_NAME")
|
19 |
+
self.tts = ServiceConfig("TTS_URL", "TTS_TYPE", "TTS_NAME")
|
20 |
+
|
21 |
+
|
22 |
+
config = Config()
|
docs/instruction.py
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# I will change it into proper format later
|
2 |
+
|
3 |
+
instruction = {
|
4 |
+
"intro": """
|
5 |
+
This project leverages the latest AI models to simulate a realistic tech interview experience,
|
6 |
+
allowing you to practice your coding interview skills in an environment that closely mimics the real thing.
|
7 |
+
While it's not designed to replace a human interviewer or the essential steps of interview preparation, such as studying algorithms and practicing coding,
|
8 |
+
it serves as a valuable addition to your preparation arsenal.
|
9 |
+
""",
|
10 |
+
"demo": """
|
11 |
+
### Demo Version Notice
|
12 |
+
**This is a demo version running on limited resources, which may respond slower than usual.**
|
13 |
+
It's primarily for demonstration purposes.
|
14 |
+
For optimal performance, we recommend running this application on your local machine using your own OpenAI API_KEY or local models.
|
15 |
+
See the instructions below on how to set up and run this application locally for the best experience.
|
16 |
+
I also recommend to read this introduction page first.
|
17 |
+
If you proceed to the interview interface right now, just click on the 'Coding' tab.
|
18 |
+
""",
|
19 |
+
"acknowledgements": """
|
20 |
+
The library can be used with a wide variety of models and APIs depending on configuration.
|
21 |
+
But I would like to acknowledge the following models in particular:
|
22 |
+
* OpenAI: GPT-3.5, GPT-4, Whisper, TTS-1, etc.
|
23 |
+
* Meta: Llama family, especially Meta-Llama-3-70B-Instruct and Meta-Llama-3-8B-Instruct.
|
24 |
+
* HuggingFace: for providing a wide range of models and APIs that can be used with this library.
|
25 |
+
|
26 |
+
Please, make sure to check the documentation of the models and APIs you are using to understand their capabilities and limitations.
|
27 |
+
Also, make sure to comply with the terms of service and acceptable use policies of the models and APIs you are using.
|
28 |
+
""",
|
29 |
+
}
|
llm.py
DELETED
@@ -1,135 +0,0 @@
|
|
1 |
-
import json
|
2 |
-
import os
|
3 |
-
|
4 |
-
import requests
|
5 |
-
|
6 |
-
from dotenv import load_dotenv
|
7 |
-
from openai import OpenAI
|
8 |
-
|
9 |
-
from audio import numpy_audio_to_bytes
|
10 |
-
from config import LLM_NAME, LLM_TYPE, LLM_URL, STT_NAME, STT_TYPE, STT_URL, TTS_NAME, TTS_TYPE, TTS_URL
|
11 |
-
from prompts import coding_interviewer_prompt, grading_feedback_prompt, problem_generation_prompt
|
12 |
-
|
13 |
-
load_dotenv()
|
14 |
-
|
15 |
-
client_LLM = OpenAI(base_url=LLM_URL, api_key=os.getenv(f"{LLM_TYPE}_KEY"))
|
16 |
-
|
17 |
-
|
18 |
-
def test_connection():
|
19 |
-
response = client_LLM.chat.completions.create(
|
20 |
-
model=LLM_NAME,
|
21 |
-
messages=[
|
22 |
-
{"role": "system", "content": "You just help me test the connection."},
|
23 |
-
{"role": "user", "content": "Hi!"},
|
24 |
-
{"role": "user", "content": "Ping!"},
|
25 |
-
],
|
26 |
-
)
|
27 |
-
return response.choices[0].message.content.strip()
|
28 |
-
|
29 |
-
|
30 |
-
def init_bot(problem=""):
|
31 |
-
chat_history = [
|
32 |
-
{"role": "system", "content": coding_interviewer_prompt},
|
33 |
-
{"role": "system", "content": f"The candidate is solving the following problem: {problem}"},
|
34 |
-
]
|
35 |
-
return chat_history
|
36 |
-
|
37 |
-
|
38 |
-
def get_problem(requirements, difficulty, topic, client=client_LLM):
|
39 |
-
full_prompt = (
|
40 |
-
f"Create a {difficulty} {topic} coding problem. "
|
41 |
-
f"Additional requirements: {requirements}. "
|
42 |
-
"The problem should be clearly stated, well-formatted, and solvable within 30 minutes. "
|
43 |
-
"Ensure the problem varies each time to provide a wide range of challenges."
|
44 |
-
)
|
45 |
-
response = client.chat.completions.create(
|
46 |
-
model=LLM_NAME,
|
47 |
-
messages=[
|
48 |
-
{"role": "system", "content": problem_generation_prompt},
|
49 |
-
{"role": "user", "content": full_prompt},
|
50 |
-
],
|
51 |
-
temperature=1.0,
|
52 |
-
)
|
53 |
-
question = response.choices[0].message.content.strip()
|
54 |
-
chat_history = init_bot(question)
|
55 |
-
return question, chat_history
|
56 |
-
|
57 |
-
|
58 |
-
def end_interview(problem_description, chat_history, client=client_LLM):
|
59 |
-
if not chat_history or len(chat_history) <= 2:
|
60 |
-
return "No interview content available to review."
|
61 |
-
|
62 |
-
transcript = []
|
63 |
-
for message in chat_history[1:]:
|
64 |
-
role = message["role"]
|
65 |
-
content = f"{role.capitalize()}: {message['content']}"
|
66 |
-
transcript.append(content)
|
67 |
-
|
68 |
-
response = client.chat.completions.create(
|
69 |
-
model=LLM_NAME,
|
70 |
-
messages=[
|
71 |
-
{"role": "system", "content": grading_feedback_prompt},
|
72 |
-
{"role": "user", "content": f"The original problem to solve: {problem_description}"},
|
73 |
-
{"role": "user", "content": "\n\n".join(transcript)},
|
74 |
-
{"role": "user", "content": "Grade the interview based on the transcript provided and give feedback."},
|
75 |
-
],
|
76 |
-
temperature=0.5,
|
77 |
-
)
|
78 |
-
feedback = response.choices[0].message.content.strip()
|
79 |
-
return feedback
|
80 |
-
|
81 |
-
|
82 |
-
def send_request(code, previous_code, message, chat_history, chat_display, client=client_LLM):
|
83 |
-
if code != previous_code:
|
84 |
-
chat_history.append({"role": "user", "content": f"My latest code:\n{code}"})
|
85 |
-
chat_history.append({"role": "user", "content": message})
|
86 |
-
|
87 |
-
response = client.chat.completions.create(model=LLM_NAME, messages=chat_history)
|
88 |
-
|
89 |
-
reply = response.choices[0].message.content.strip()
|
90 |
-
|
91 |
-
chat_history.append({"role": "assistant", "content": reply})
|
92 |
-
chat_display[-1][1] = reply
|
93 |
-
|
94 |
-
return chat_history, chat_display, "", code
|
95 |
-
|
96 |
-
|
97 |
-
def speech_to_text(audio, convert_to_bytes=True):
|
98 |
-
assert STT_TYPE in ["OPENAI_API", "HF_API"]
|
99 |
-
|
100 |
-
if convert_to_bytes:
|
101 |
-
audio = numpy_audio_to_bytes(audio[1])
|
102 |
-
|
103 |
-
if STT_TYPE == "OPENAI_API":
|
104 |
-
data = ("temp.wav", audio, "audio/wav")
|
105 |
-
client = OpenAI(base_url=STT_URL, api_key=os.getenv(f"{STT_TYPE}_KEY"))
|
106 |
-
transcription = client.audio.transcriptions.create(model=STT_NAME, file=data, response_format="text")
|
107 |
-
elif STT_TYPE == "HF_API":
|
108 |
-
headers = {"Authorization": "Bearer " + os.getenv(f"{STT_TYPE}_KEY")}
|
109 |
-
transcription = requests.post(STT_URL, headers=headers, data=audio)
|
110 |
-
transcription = transcription.json()["text"]
|
111 |
-
|
112 |
-
return transcription
|
113 |
-
|
114 |
-
|
115 |
-
def text_to_speech(text):
|
116 |
-
assert TTS_TYPE in ["OPENAI_API", "HF_API"]
|
117 |
-
|
118 |
-
if TTS_TYPE == "OPENAI_API":
|
119 |
-
client = OpenAI(base_url=TTS_URL, api_key=os.getenv(f"{TTS_TYPE}_KEY"))
|
120 |
-
response = client.audio.speech.create(model=TTS_NAME, voice="alloy", response_format="opus", input=text)
|
121 |
-
elif TTS_TYPE == "HF_API":
|
122 |
-
headers = {"Authorization": "Bearer " + os.getenv(f"{STT_TYPE}_KEY")}
|
123 |
-
response = requests.post(TTS_URL, headers=headers)
|
124 |
-
|
125 |
-
audio = response.content
|
126 |
-
return audio
|
127 |
-
|
128 |
-
|
129 |
-
def read_last_message(chat_display):
|
130 |
-
if len(chat_display) > 0:
|
131 |
-
last_message = chat_display[-1][1]
|
132 |
-
if last_message is not None:
|
133 |
-
audio = text_to_speech(last_message)
|
134 |
-
return audio
|
135 |
-
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
prompts.py
DELETED
@@ -1,37 +0,0 @@
|
|
1 |
-
problem_generation_prompt = (
|
2 |
-
"You are AI acting as a coding round interviewer for a big-tech company. "
|
3 |
-
"Generate a problem that tests the candidate's ability to solve real-world coding challenges efficiently. "
|
4 |
-
"Ensure the problem tests for problem-solving skills, technical proficiency, code quality, and handling of edge cases. "
|
5 |
-
)
|
6 |
-
|
7 |
-
# Coding round interviewer instructions
|
8 |
-
coding_interviewer_prompt = (
|
9 |
-
"As an AI acting as a coding interviewer for a major tech company, you are to maintain a professional and analytical demeanor. "
|
10 |
-
"You must consistently ask about the time and space complexity of the candidate's solutions after each significant problem-solving step. "
|
11 |
-
"Prompt the candidate to explain how they compute these complexities, and guide them through the process if necessary, without providing the answers directly. "
|
12 |
-
"Encourage thorough exploration of solutions without revealing answers directly. Provide hints subtly only after observing the candidate struggle significantly or upon explicit request. "
|
13 |
-
"Probe the candidate with questions related to problem-solving approaches, algorithm choices, handling of edge cases, and error identification to assess technical proficiency comprehensively. "
|
14 |
-
"If the candidate deviates from the problem, gently guide them back to focus on the task at hand. "
|
15 |
-
"After multiple unsuccessful attempts by the candidate to identify or fix an error, provide more direct hints or rephrase the problem slightly to aid understanding. "
|
16 |
-
"Encourage the candidate to think about real-world applications and scalability of their solutions, asking how changes to the problem parameters might affect their approach. "
|
17 |
-
)
|
18 |
-
|
19 |
-
|
20 |
-
# Prompt for grading feedback
|
21 |
-
grading_feedback_prompt = (
|
22 |
-
"You are the AI grader for a coding interview at a major tech firm. "
|
23 |
-
"The following is the interview transcript with the candidate's responses. "
|
24 |
-
"Ignore minor transcription errors unless they impact comprehension. "
|
25 |
-
"If there are no real solution provide just say it. "
|
26 |
-
"Evaluate the candidate’s performance based on the following criteria: "
|
27 |
-
"\n- **Problem-Solving Skills**: Approach to solving problems, creativity, and handling of complex issues."
|
28 |
-
"\n- **Technical Proficiency**: Accuracy of the solution, usage of appropriate algorithms and data structures, consideration of edge cases, and error handling."
|
29 |
-
"\n- **Code Quality**: Readability, maintainability, scalability, and overall organization."
|
30 |
-
"\n- **Communication Skills**: Ability to explain their thought process clearly, interaction during the interview, and responsiveness to feedback."
|
31 |
-
"\n- **Debugging Skills**: Efficiency in identifying and resolving errors."
|
32 |
-
"\n- **Adaptability**: Ability to incorporate feedback and adjust solutions as needed."
|
33 |
-
"\n- **Handling Ambiguity**: Approach to dealing with uncertain or incomplete requirements."
|
34 |
-
"\nProvide comprehensive feedback, detailing overall performance, specific errors, areas for improvement, communication lapses, overlooked edge cases, and any other relevant observations. "
|
35 |
-
"Use code examples to illustrate points where necessary. Your feedback should be critical, aiming to fail candidates who do not meet high standards while providing detailed improvement areas. "
|
36 |
-
"Format all feedback in clear, structured markdown for readability."
|
37 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
options.py → resources/data.py
RENAMED
File without changes
|
resources/prompts.py
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
prompts = {
|
2 |
+
"problem_generation_prompt": (
|
3 |
+
"You are AI acting as a coding round interviewer for a big-tech company. "
|
4 |
+
"Generate a problem that tests the candidate's ability to solve real-world coding challenges efficiently. "
|
5 |
+
"Ensure the problem tests for problem-solving skills, technical proficiency, code quality, and handling of edge cases. "
|
6 |
+
),
|
7 |
+
"coding_interviewer_prompt": (
|
8 |
+
"As an AI acting as a coding interviewer for a major tech company, you are to maintain a professional and analytical demeanor. "
|
9 |
+
"You must consistently ask about the time and space complexity of the candidate's solutions after each significant problem-solving step. "
|
10 |
+
"Prompt the candidate to explain how they compute these complexities, and guide them through the process if necessary, without providing the answers directly. "
|
11 |
+
"Encourage thorough exploration of solutions without revealing answers directly. Provide hints subtly only after observing the candidate struggle significantly or upon explicit request. "
|
12 |
+
"Probe the candidate with questions related to problem-solving approaches, algorithm choices, handling of edge cases, and error identification to assess technical proficiency comprehensively. "
|
13 |
+
"If the candidate deviates from the problem, gently guide them back to focus on the task at hand. "
|
14 |
+
"After multiple unsuccessful attempts by the candidate to identify or fix an error, provide more direct hints or rephrase the problem slightly to aid understanding. "
|
15 |
+
"Encourage the candidate to think about real-world applications and scalability of their solutions, asking how changes to the problem parameters might affect their approach. "
|
16 |
+
),
|
17 |
+
"grading_feedback_prompt": (
|
18 |
+
"You are the AI grader for a coding interview at a major tech firm. "
|
19 |
+
"The following is the interview transcript with the candidate's responses. "
|
20 |
+
"Ignore minor transcription errors unless they impact comprehension. "
|
21 |
+
"If there are no real solution provide just say it. "
|
22 |
+
"Evaluate the candidate’s performance based on the following criteria: "
|
23 |
+
"\n- **Problem-Solving Skills**: Approach to solving problems, creativity, and handling of complex issues."
|
24 |
+
"\n- **Technical Proficiency**: Accuracy of the solution, usage of appropriate algorithms and data structures, consideration of edge cases, and error handling."
|
25 |
+
"\n- **Code Quality**: Readability, maintainability, scalability, and overall organization."
|
26 |
+
"\n- **Communication Skills**: Ability to explain their thought process clearly, interaction during the interview, and responsiveness to feedback."
|
27 |
+
"\n- **Debugging Skills**: Efficiency in identifying and resolving errors."
|
28 |
+
"\n- **Adaptability**: Ability to incorporate feedback and adjust solutions as needed."
|
29 |
+
"\n- **Handling Ambiguity**: Approach to dealing with uncertain or incomplete requirements."
|
30 |
+
"\nProvide comprehensive feedback, detailing overall performance, specific errors, areas for improvement, communication lapses, overlooked edge cases, and any other relevant observations. "
|
31 |
+
"Use code examples to illustrate points where necessary. Your feedback should be critical, aiming to fail candidates who do not meet high standards while providing detailed improvement areas. "
|
32 |
+
"Format all feedback in clear, structured markdown for readability."
|
33 |
+
),
|
34 |
+
}
|
utils/ui.py
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
def add_interviewer_message(message):
|
2 |
+
def f(chat):
|
3 |
+
chat.append((None, message))
|
4 |
+
return chat
|
5 |
+
|
6 |
+
return f
|
7 |
+
|
8 |
+
|
9 |
+
def add_candidate_message(message, chat):
|
10 |
+
chat.append((message, None))
|
11 |
+
return chat
|