Spaces:
Running
Running
First commit
Browse files- .gitignore +9 -0
- ai_config.py +80 -0
- app-v1.py +214 -0
- app.py +551 -0
- generator.py +134 -0
- generatorgr.py +121 -0
- gpt-general.py +96 -0
- gpt.py +109 -0
- gptgr-manager.py +185 -0
- gptgr.py +145 -0
- grad.py +104 -0
- interview.py +61 -0
- interview.txt +0 -0
- knowledge_retrieval.py +135 -0
- m6.py +245 -0
- professional_machine_learning_engineer_exam_guide_english.pdf +0 -0
- prompt_instructions.py +181 -0
- questions.py +147 -0
- questionsgr.py +38 -0
- requirements.txt +23 -0
- requirements_dev.txt +178 -0
- response.py +120 -0
- settings.py +3 -0
- split.py +88 -0
- splitgpt.py +331 -0
- utils.py +147 -0
.gitignore
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
.env
|
3 |
+
/__pycache__
|
4 |
+
/.gradio
|
5 |
+
/hr_interviewer
|
6 |
+
/knowledge
|
7 |
+
/reports
|
8 |
+
*.json
|
9 |
+
*.json
|
ai_config.py
ADDED
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from io import BytesIO
|
2 |
+
|
3 |
+
from langchain_openai import ChatOpenAI
|
4 |
+
from openai import OpenAI
|
5 |
+
import tiktoken
|
6 |
+
import os
|
7 |
+
from dotenv import load_dotenv
|
8 |
+
import os
|
9 |
+
from langchain.text_splitter import MarkdownHeaderTextSplitter
|
10 |
+
|
11 |
+
# Load environment variables from .env file
|
12 |
+
load_dotenv()
|
13 |
+
|
14 |
+
# IBM Connection Parameters (using loaded env variables)
|
15 |
+
openai_api_key = os.getenv("OPENAI_API_KEY")
|
16 |
+
|
17 |
+
def n_of_questions():
|
18 |
+
n_of_questions = 5
|
19 |
+
return n_of_questions
|
20 |
+
|
21 |
+
#openai_api_key = os.environ.get("openai_api_key")
|
22 |
+
|
23 |
+
model = "gpt-3.5-turbo-1106"
|
24 |
+
|
25 |
+
def load_model(openai_api_key):
|
26 |
+
return ChatOpenAI(
|
27 |
+
model_name=model,
|
28 |
+
openai_api_key=openai_api_key,
|
29 |
+
temperature=0.5
|
30 |
+
)
|
31 |
+
|
32 |
+
# Initialize the OpenAI client with the API key
|
33 |
+
client = OpenAI(api_key=openai_api_key)
|
34 |
+
|
35 |
+
|
36 |
+
def convert_text_to_speech(text, output, voice):
|
37 |
+
try:
|
38 |
+
# Convert the final text to speech
|
39 |
+
response = client.audio.speech.create(model="tts-1-hd", voice=voice, input=text)
|
40 |
+
|
41 |
+
if isinstance(output, BytesIO):
|
42 |
+
# If output is a BytesIO object, write directly to it
|
43 |
+
for chunk in response.iter_bytes():
|
44 |
+
output.write(chunk)
|
45 |
+
else:
|
46 |
+
# If output is a file path, open and write to it
|
47 |
+
with open(output, 'wb') as f:
|
48 |
+
for chunk in response.iter_bytes():
|
49 |
+
f.write(chunk)
|
50 |
+
|
51 |
+
except Exception as e:
|
52 |
+
print(f"An error occurred: {e}")
|
53 |
+
# Fallback in case of error
|
54 |
+
response = client.audio.speech.create(model="tts-1-hd", voice=voice, input='Here is my Report.')
|
55 |
+
|
56 |
+
if isinstance(output, BytesIO):
|
57 |
+
for chunk in response.iter_bytes():
|
58 |
+
output.write(chunk)
|
59 |
+
else:
|
60 |
+
with open(output, 'wb') as f:
|
61 |
+
for chunk in response.iter_bytes():
|
62 |
+
f.write(chunk)
|
63 |
+
|
64 |
+
|
65 |
+
def transcribe_audio(audio):
|
66 |
+
try:
|
67 |
+
audio_file = open(audio, "rb")
|
68 |
+
transcription = client.audio.transcriptions.create(
|
69 |
+
model="whisper-1",
|
70 |
+
file=audio_file
|
71 |
+
)
|
72 |
+
return transcription.text
|
73 |
+
except Exception as e:
|
74 |
+
return "Audio transcription failed. Please try again."
|
75 |
+
|
76 |
+
|
77 |
+
def split_text_with_langchain(text, headers_to_split_on):
|
78 |
+
markdown_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
|
79 |
+
docs = markdown_splitter.create_documents([text])
|
80 |
+
return docs
|
app-v1.py
ADDED
@@ -0,0 +1,214 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import grad as gr
|
2 |
+
import tempfile
|
3 |
+
import os
|
4 |
+
import json
|
5 |
+
from io import BytesIO
|
6 |
+
from gpt import read_questions_from_json, conduct_interview_with_user_input # Import from gpt.py
|
7 |
+
from ai_config import convert_text_to_speech, load_model
|
8 |
+
from knowledge_retrieval import setup_knowledge_retrieval, generate_report
|
9 |
+
from prompt_instructions import get_interview_initial_message_hr, get_default_hr_questions
|
10 |
+
from settings import language
|
11 |
+
from utils import save_interview_history
|
12 |
+
from questions import generate_and_save_questions_from_pdf
|
13 |
+
|
14 |
+
CONFIG_PATH = "config.json"
|
15 |
+
QUESTIONS_PATH = "questions.json"
|
16 |
+
|
17 |
+
class InterviewState:
|
18 |
+
def __init__(self):
|
19 |
+
self.reset()
|
20 |
+
|
21 |
+
def reset(self, voice="alloy"):
|
22 |
+
self.question_count = 0
|
23 |
+
self.interview_history = []
|
24 |
+
self.selected_interviewer = voice
|
25 |
+
self.interview_finished = False
|
26 |
+
self.audio_enabled = True
|
27 |
+
self.temp_audio_files = []
|
28 |
+
self.admin_authenticated = False
|
29 |
+
self.config = load_config()
|
30 |
+
self.technical_questions = []
|
31 |
+
|
32 |
+
def load_config():
|
33 |
+
if os.path.exists(CONFIG_PATH):
|
34 |
+
with open(CONFIG_PATH, "r") as f:
|
35 |
+
return json.load(f)
|
36 |
+
else:
|
37 |
+
return {"n_of_questions": 5, "type_of_interview": "Standard"}
|
38 |
+
|
39 |
+
def save_config(config):
|
40 |
+
with open(CONFIG_PATH, "w") as f:
|
41 |
+
json.dump(config, f, indent=4)
|
42 |
+
|
43 |
+
def save_questions(questions):
|
44 |
+
with open(QUESTIONS_PATH, "w") as f:
|
45 |
+
json.dump(questions, f, indent=4)
|
46 |
+
|
47 |
+
def load_questions():
|
48 |
+
if os.path.exists(QUESTIONS_PATH):
|
49 |
+
with open(QUESTIONS_PATH, "r") as f:
|
50 |
+
return json.load(f)
|
51 |
+
return []
|
52 |
+
|
53 |
+
interview_state = InterviewState()
|
54 |
+
|
55 |
+
# Load knowledge base and generate technical questions
|
56 |
+
def load_knowledge_base(file_input, n_questions_to_generate):
|
57 |
+
if not file_input:
|
58 |
+
return "❌ Error: No document uploaded."
|
59 |
+
|
60 |
+
llm = load_model(os.getenv("OPENAI_API_KEY"))
|
61 |
+
try:
|
62 |
+
_, _, retriever = setup_knowledge_retrieval(llm, language=language, file_path=file_input)
|
63 |
+
technical_questions = generate_and_save_questions_from_pdf(file_input, n_questions_to_generate)
|
64 |
+
save_questions(technical_questions)
|
65 |
+
|
66 |
+
return f"✅ {len(technical_questions)} technical questions generated and saved."
|
67 |
+
except Exception as e:
|
68 |
+
return f"❌ Error: {e}"
|
69 |
+
|
70 |
+
def reset_interview_action(voice):
|
71 |
+
interview_state.reset(voice)
|
72 |
+
config = interview_state.config
|
73 |
+
n_of_questions = config.get("n_of_questions", 5)
|
74 |
+
initial_message = {
|
75 |
+
"role": "assistant",
|
76 |
+
"content": get_interview_initial_message_hr(n_of_questions)
|
77 |
+
}
|
78 |
+
|
79 |
+
if config["type_of_interview"] == "Technical":
|
80 |
+
technical_questions = load_questions()
|
81 |
+
|
82 |
+
if not technical_questions:
|
83 |
+
return [{"role": "assistant", "content": "No technical questions available. Please contact the admin."}], None, gr.Textbox(interactive=False)
|
84 |
+
|
85 |
+
# Prepare for displaying questions one at a time
|
86 |
+
interview_state.technical_questions = technical_questions
|
87 |
+
interview_state.question_count = 0
|
88 |
+
return (
|
89 |
+
[initial_message],
|
90 |
+
None,
|
91 |
+
gr.Textbox(interactive=True, placeholder="Technical interview started. Answer the questions below...")
|
92 |
+
)
|
93 |
+
else:
|
94 |
+
initial_audio_buffer = BytesIO()
|
95 |
+
convert_text_to_speech(initial_message["content"], initial_audio_buffer, voice)
|
96 |
+
initial_audio_buffer.seek(0)
|
97 |
+
|
98 |
+
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as temp_file:
|
99 |
+
temp_audio_path = temp_file.name
|
100 |
+
temp_file.write(initial_audio_buffer.getvalue())
|
101 |
+
|
102 |
+
interview_state.temp_audio_files.append(temp_audio_path)
|
103 |
+
return (
|
104 |
+
[initial_message],
|
105 |
+
gr.Audio(value=temp_audio_path, autoplay=True),
|
106 |
+
gr.Textbox(interactive=True, placeholder="Type your answer here...")
|
107 |
+
)
|
108 |
+
|
109 |
+
def start_interview():
|
110 |
+
interview_config = load_config()
|
111 |
+
interview_state.config = interview_config
|
112 |
+
return reset_interview_action(interview_state.selected_interviewer)
|
113 |
+
|
114 |
+
def update_config(n_of_questions, interview_type):
|
115 |
+
config = {
|
116 |
+
"n_of_questions": int(n_of_questions),
|
117 |
+
"type_of_interview": interview_type
|
118 |
+
}
|
119 |
+
save_config(config)
|
120 |
+
return "✅ Configuration updated successfully."
|
121 |
+
|
122 |
+
def update_knowledge_base_and_generate_questions(file_input, n_questions_to_generate):
|
123 |
+
return load_knowledge_base(file_input, n_questions_to_generate)
|
124 |
+
|
125 |
+
def bot_response(chatbot, message):
|
126 |
+
config = interview_state.config
|
127 |
+
|
128 |
+
if config["type_of_interview"] == "Standard":
|
129 |
+
response = get_default_hr_questions(interview_state.question_count + 1)
|
130 |
+
chatbot.append({"role": "assistant", "content": response})
|
131 |
+
interview_state.question_count += 1
|
132 |
+
else:
|
133 |
+
if interview_state.question_count < len(interview_state.technical_questions):
|
134 |
+
question = interview_state.technical_questions[interview_state.question_count]
|
135 |
+
chatbot.append({"role": "assistant", "content": f"Q{interview_state.question_count + 1}: {question}"})
|
136 |
+
interview_state.question_count += 1
|
137 |
+
chatbot.append({"role": "user", "content": message}) # Append user response after the question
|
138 |
+
else:
|
139 |
+
chatbot.append({"role": "assistant", "content": "All questions completed."})
|
140 |
+
interview_state.interview_finished = True
|
141 |
+
|
142 |
+
if interview_state.interview_finished:
|
143 |
+
report_content = generate_report(interview_state.report_chain, [msg["content"] for msg in chatbot if msg["role"] == "user"], language)
|
144 |
+
txt_path = save_interview_history([msg["content"] for msg in chatbot], language)
|
145 |
+
return chatbot, gr.File(visible=True, value=txt_path)
|
146 |
+
|
147 |
+
return chatbot, None
|
148 |
+
|
149 |
+
def create_app():
|
150 |
+
with gr.Blocks(title="AI HR Interviewer") as demo:
|
151 |
+
gr.Markdown("## 🧑💼 HR Interviewer Application")
|
152 |
+
|
153 |
+
with gr.Row():
|
154 |
+
user_role = gr.Dropdown(choices=["Admin", "Candidate"], label="Select User Role", value="Candidate")
|
155 |
+
password_input = gr.Textbox(label="Enter Admin Password", type="password", visible=False)
|
156 |
+
login_button = gr.Button("Login", visible=False)
|
157 |
+
password_status = gr.Markdown("", visible=False)
|
158 |
+
|
159 |
+
admin_tab = gr.Tab("Admin Settings", visible=False)
|
160 |
+
interview_tab = gr.Tab("Interview", visible=True)
|
161 |
+
|
162 |
+
user_role.change(lambda role: (gr.update(visible=role == "Admin"),) * 2, inputs=[user_role], outputs=[password_input, login_button])
|
163 |
+
|
164 |
+
def authenticate_admin(password):
|
165 |
+
if password == "password1":
|
166 |
+
interview_state.admin_authenticated = True
|
167 |
+
return "✅ Password correct", gr.update(visible=False), gr.update(visible=True)
|
168 |
+
else:
|
169 |
+
return "❌ Incorrect password.", gr.update(visible=True), gr.update(visible=False)
|
170 |
+
|
171 |
+
login_button.click(authenticate_admin, inputs=[password_input], outputs=[password_status, password_input, admin_tab])
|
172 |
+
|
173 |
+
with admin_tab:
|
174 |
+
file_input = gr.File(label="Upload Knowledge Base Document", type="filepath")
|
175 |
+
n_questions_input = gr.Number(label="Number of Questions", value=10)
|
176 |
+
update_button = gr.Button("Update Knowledge Base")
|
177 |
+
update_status = gr.Markdown("")
|
178 |
+
update_button.click(update_knowledge_base_and_generate_questions, inputs=[file_input, n_questions_input], outputs=[update_status])
|
179 |
+
|
180 |
+
n_questions_interview_input = gr.Number(label="Number of Questions for Interview", value=5)
|
181 |
+
interview_type_input = gr.Dropdown(choices=["Standard", "Technical"], label="Type of Interview", value="Standard")
|
182 |
+
save_config_button = gr.Button("Save Configuration")
|
183 |
+
config_status = gr.Markdown("")
|
184 |
+
save_config_button.click(update_config, inputs=[n_questions_interview_input, interview_type_input], outputs=[config_status])
|
185 |
+
|
186 |
+
with interview_tab:
|
187 |
+
reset_button = gr.Button("Start Interview")
|
188 |
+
chatbot = gr.Chatbot(label="Chat Session", type="messages")
|
189 |
+
msg_input = gr.Textbox(label="💬 Type your message here...", interactive=True)
|
190 |
+
send_button = gr.Button("Send")
|
191 |
+
|
192 |
+
reset_button.click(start_interview, inputs=[], outputs=[chatbot])
|
193 |
+
|
194 |
+
msg_input.submit(lambda msg, hist: ("", hist + [{"role": "user", "content": msg}]), inputs=[msg_input, chatbot], outputs=[msg_input, chatbot]).then(
|
195 |
+
bot_response, [chatbot, msg_input], [chatbot]
|
196 |
+
)
|
197 |
+
|
198 |
+
send_button.click(lambda msg, hist: ("", hist + [{"role": "user", "content": msg}]), inputs=[msg_input, chatbot], outputs=[msg_input, chatbot]).then(
|
199 |
+
bot_response, [chatbot, msg_input], [chatbot]
|
200 |
+
)
|
201 |
+
|
202 |
+
return demo
|
203 |
+
|
204 |
+
def cleanup():
|
205 |
+
for audio_file in interview_state.temp_audio_files:
|
206 |
+
if os.path.exists(audio_file):
|
207 |
+
os.unlink(audio_file)
|
208 |
+
|
209 |
+
if __name__ == "__main__":
|
210 |
+
app = create_app()
|
211 |
+
try:
|
212 |
+
app.launch(server_name="0.0.0.0", server_port=7860, debug=True)
|
213 |
+
finally:
|
214 |
+
cleanup()
|
app.py
ADDED
@@ -0,0 +1,551 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import tempfile
|
3 |
+
import os
|
4 |
+
import json
|
5 |
+
from io import BytesIO
|
6 |
+
import subprocess
|
7 |
+
from collections import deque
|
8 |
+
from dotenv import load_dotenv
|
9 |
+
from langchain_openai import ChatOpenAI
|
10 |
+
from langchain.schema import HumanMessage, SystemMessage
|
11 |
+
|
12 |
+
# Imports from other modules
|
13 |
+
from generatorgr import (
|
14 |
+
generate_and_save_questions as generate_questions_manager,
|
15 |
+
update_max_questions,
|
16 |
+
)
|
17 |
+
from generator import (
|
18 |
+
PROFESSIONS_FILE,
|
19 |
+
TYPES_FILE,
|
20 |
+
OUTPUT_FILE,
|
21 |
+
load_json_data,
|
22 |
+
generate_questions,
|
23 |
+
)
|
24 |
+
from splitgpt import (
|
25 |
+
generate_and_save_questions_from_pdf3
|
26 |
+
)
|
27 |
+
|
28 |
+
# Placeholder imports for the manager application
|
29 |
+
# Ensure these modules and functions are correctly implemented in their respective files
|
30 |
+
from ai_config import convert_text_to_speech, load_model # Placeholder, needs implementation
|
31 |
+
from knowledge_retrieval import (
|
32 |
+
setup_knowledge_retrieval,
|
33 |
+
get_next_response,
|
34 |
+
generate_report,
|
35 |
+
get_initial_question,
|
36 |
+
) # Placeholder, needs implementation
|
37 |
+
from prompt_instructions import (
|
38 |
+
get_interview_initial_message_hr,
|
39 |
+
get_default_hr_questions,
|
40 |
+
) # Placeholder, needs implementation
|
41 |
+
from settings import language # Placeholder, needs implementation
|
42 |
+
from utils import save_interview_history # Placeholder, needs implementation
|
43 |
+
|
44 |
+
|
45 |
+
class InterviewState:
|
46 |
+
def __init__(self):
|
47 |
+
self.reset()
|
48 |
+
|
49 |
+
def reset(self, voice="alloy"):
|
50 |
+
self.question_count = 0
|
51 |
+
self.interview_history = []
|
52 |
+
self.selected_interviewer = voice
|
53 |
+
self.interview_finished = False
|
54 |
+
self.audio_enabled = True
|
55 |
+
self.temp_audio_files = []
|
56 |
+
self.initial_audio_path = None
|
57 |
+
self.admin_authenticated = False
|
58 |
+
self.document_loaded = False
|
59 |
+
self.knowledge_retrieval_setup = False
|
60 |
+
self.interview_chain = None
|
61 |
+
self.report_chain = None
|
62 |
+
self.current_questions = [] # Store the current set of questions
|
63 |
+
|
64 |
+
def get_voice_setting(self):
|
65 |
+
return self.selected_interviewer
|
66 |
+
|
67 |
+
|
68 |
+
interview_state = InterviewState()
|
69 |
+
|
70 |
+
|
71 |
+
def reset_interview_action(voice):
|
72 |
+
interview_state.reset(voice)
|
73 |
+
n_of_questions = 5 # Default questions
|
74 |
+
print(f"[DEBUG] Interview reset. Voice: {voice}")
|
75 |
+
|
76 |
+
initial_message = {
|
77 |
+
"role": "assistant",
|
78 |
+
"content": get_interview_initial_message_hr(n_of_questions),
|
79 |
+
}
|
80 |
+
print(f"[DEBUG] Interview reset. Voice: {voice}")
|
81 |
+
# Convert the initial message to speech
|
82 |
+
initial_audio_buffer = BytesIO()
|
83 |
+
convert_text_to_speech(initial_message["content"], initial_audio_buffer, voice)
|
84 |
+
initial_audio_buffer.seek(0)
|
85 |
+
|
86 |
+
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as temp_file:
|
87 |
+
temp_audio_path = temp_file.name
|
88 |
+
temp_file.write(initial_audio_buffer.getvalue())
|
89 |
+
|
90 |
+
interview_state.temp_audio_files.append(temp_audio_path)
|
91 |
+
print(f"[DEBUG] Audio file saved at {temp_audio_path}")
|
92 |
+
|
93 |
+
return (
|
94 |
+
[initial_message],
|
95 |
+
gr.Audio(value=temp_audio_path, autoplay=True),
|
96 |
+
gr.Textbox(interactive=True),
|
97 |
+
)
|
98 |
+
|
99 |
+
|
100 |
+
def start_interview():
|
101 |
+
|
102 |
+
return reset_interview_action(interview_state.selected_interviewer)
|
103 |
+
|
104 |
+
|
105 |
+
import os
|
106 |
+
from datetime import datetime
|
107 |
+
|
108 |
+
def store_interview_report(report_content, folder_path="reports"):
|
109 |
+
"""
|
110 |
+
Stores the interview report in a specified reports folder.
|
111 |
+
|
112 |
+
Args:
|
113 |
+
report_content (str): The content of the report to store.
|
114 |
+
folder_path (str): The directory where the report will be saved.
|
115 |
+
|
116 |
+
Returns:
|
117 |
+
str: The file path of the saved report.
|
118 |
+
"""
|
119 |
+
os.makedirs(folder_path, exist_ok=True)
|
120 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
121 |
+
file_path = os.path.join(folder_path, f"interview_report_{timestamp}.txt")
|
122 |
+
|
123 |
+
try:
|
124 |
+
with open(file_path, "w", encoding="utf-8") as file:
|
125 |
+
file.write(report_content)
|
126 |
+
print(f"[DEBUG] Interview report saved at {file_path}")
|
127 |
+
return file_path
|
128 |
+
except Exception as e:
|
129 |
+
print(f"[ERROR] Failed to save interview report: {e}")
|
130 |
+
return None
|
131 |
+
|
132 |
+
|
133 |
+
def bot_response(chatbot, message):
|
134 |
+
n_of_questions = 5 # Default value
|
135 |
+
interview_state.question_count += 1
|
136 |
+
voice = interview_state.get_voice_setting()
|
137 |
+
|
138 |
+
if interview_state.question_count == 1:
|
139 |
+
response = get_initial_question(interview_state.interview_chain)
|
140 |
+
else:
|
141 |
+
response = get_next_response(
|
142 |
+
interview_state.interview_chain,
|
143 |
+
message["content"],
|
144 |
+
[msg["content"] for msg in chatbot if msg.get("role") == "user"],
|
145 |
+
interview_state.question_count,
|
146 |
+
)
|
147 |
+
|
148 |
+
# Generate and save the bot's audio response
|
149 |
+
audio_buffer = BytesIO()
|
150 |
+
convert_text_to_speech(response, audio_buffer, voice)
|
151 |
+
audio_buffer.seek(0)
|
152 |
+
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as temp_file:
|
153 |
+
temp_audio_path = temp_file.name
|
154 |
+
temp_file.write(audio_buffer.getvalue())
|
155 |
+
|
156 |
+
interview_state.temp_audio_files.append(temp_audio_path)
|
157 |
+
chatbot.append({"role": "assistant", "content": response})
|
158 |
+
|
159 |
+
# Check if the interview is finished
|
160 |
+
if interview_state.question_count >= n_of_questions:
|
161 |
+
interview_state.interview_finished = True
|
162 |
+
conclusion_message = (
|
163 |
+
"Thank you for your time. The interview is complete. Please review your report."
|
164 |
+
)
|
165 |
+
|
166 |
+
# Generate conclusion audio message
|
167 |
+
conclusion_audio_buffer = BytesIO()
|
168 |
+
convert_text_to_speech(conclusion_message, conclusion_audio_buffer, voice)
|
169 |
+
conclusion_audio_buffer.seek(0)
|
170 |
+
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as temp_conclusion_file:
|
171 |
+
temp_conclusion_audio_path = temp_conclusion_file.name
|
172 |
+
temp_conclusion_file.write(conclusion_audio_buffer.getvalue())
|
173 |
+
interview_state.temp_audio_files.append(temp_conclusion_audio_path)
|
174 |
+
|
175 |
+
# Append conclusion message to chatbot history
|
176 |
+
chatbot.append({"role": "system", "content": conclusion_message})
|
177 |
+
|
178 |
+
# Generate the HR report content
|
179 |
+
report_content = generate_report(
|
180 |
+
interview_state.report_chain,
|
181 |
+
[msg["content"] for msg in chatbot],
|
182 |
+
language,
|
183 |
+
)
|
184 |
+
|
185 |
+
# Save the interview history
|
186 |
+
txt_path = save_interview_history(
|
187 |
+
[msg["content"] for msg in chatbot], language
|
188 |
+
)
|
189 |
+
print(f"[DEBUG] Interview history saved at: {txt_path}")
|
190 |
+
|
191 |
+
# Save the report to the reports folder
|
192 |
+
report_file_path = store_interview_report(report_content)
|
193 |
+
print(f"[DEBUG] Interview report saved at: {report_file_path}")
|
194 |
+
|
195 |
+
return chatbot, gr.File(visible=True, value=txt_path), gr.Audio(value=temp_conclusion_audio_path, autoplay=True)
|
196 |
+
|
197 |
+
return chatbot, gr.Audio(value=temp_audio_path, autoplay=True)
|
198 |
+
|
199 |
+
|
200 |
+
# --- Candidate Interview Implementation ---
|
201 |
+
load_dotenv()
|
202 |
+
|
203 |
+
# Function to read questions from JSON
|
204 |
+
def read_questions_from_json(file_path):
|
205 |
+
if not os.path.exists(file_path):
|
206 |
+
raise FileNotFoundError(f"The file '{file_path}' does not exist.")
|
207 |
+
|
208 |
+
with open(file_path, 'r') as f:
|
209 |
+
questions_list = json.load(f)
|
210 |
+
|
211 |
+
if not questions_list:
|
212 |
+
raise ValueError("The JSON file is empty or has invalid content.")
|
213 |
+
|
214 |
+
return questions_list
|
215 |
+
|
216 |
+
# Conduct interview and handle user input
|
217 |
+
|
218 |
+
import os
|
219 |
+
import json
|
220 |
+
from io import BytesIO
|
221 |
+
import tempfile
|
222 |
+
from collections import deque
|
223 |
+
from langchain_openai import ChatOpenAI
|
224 |
+
from langchain.schema import HumanMessage, SystemMessage
|
225 |
+
|
226 |
+
# Placeholder imports (ensure these are correctly implemented)
|
227 |
+
from ai_config import convert_text_to_speech # For text-to-speech
|
228 |
+
from knowledge_retrieval import generate_report # For report generation
|
229 |
+
from utils import save_interview_history # For saving interview history
|
230 |
+
from settings import language # Placeholder, needs implementation
|
231 |
+
|
232 |
+
# Assuming you have interview_state defined elsewhere and accessible here
|
233 |
+
# interview_state = InterviewState() # You might need to initialize this or pass it as a parameter
|
234 |
+
|
235 |
+
def conduct_interview(questions, language="English", history_limit=5):
|
236 |
+
openai_api_key = os.getenv("OPENAI_API_KEY")
|
237 |
+
if not openai_api_key:
|
238 |
+
raise RuntimeError(
|
239 |
+
"OpenAI API key not found. Please add it to your .env file as OPENAI_API_KEY."
|
240 |
+
)
|
241 |
+
|
242 |
+
chat = ChatOpenAI(
|
243 |
+
openai_api_key=openai_api_key, model="gpt-4", temperature=0.7, max_tokens=750
|
244 |
+
)
|
245 |
+
|
246 |
+
conversation_history = deque(maxlen=history_limit)
|
247 |
+
system_prompt = (
|
248 |
+
f"You are Sarah, an empathetic HR interviewer conducting a technical interview in {language}. "
|
249 |
+
"Respond to user follow-up questions politely and concisely. If the user is confused, provide clear clarification."
|
250 |
+
)
|
251 |
+
|
252 |
+
interview_data = []
|
253 |
+
current_question_index = [0]
|
254 |
+
|
255 |
+
initial_message = (
|
256 |
+
"👋 Hi there, I'm Sarah, your friendly AI HR assistant! "
|
257 |
+
"I'll guide you through a series of interview questions to learn more about you. "
|
258 |
+
"Take your time and answer each question thoughtfully."
|
259 |
+
)
|
260 |
+
|
261 |
+
def interview_step(user_input, history):
|
262 |
+
|
263 |
+
if user_input.lower() in ["exit", "quit"]:
|
264 |
+
history.append(
|
265 |
+
{
|
266 |
+
"role": "assistant",
|
267 |
+
"content": "The interview has ended at your request. Thank you for your time!",
|
268 |
+
}
|
269 |
+
)
|
270 |
+
return history, ""
|
271 |
+
|
272 |
+
question_text = questions[current_question_index[0]]
|
273 |
+
history_content = "\n".join(
|
274 |
+
[
|
275 |
+
f"Q: {entry['question']}\nA: {entry['answer']}"
|
276 |
+
for entry in conversation_history
|
277 |
+
]
|
278 |
+
)
|
279 |
+
combined_prompt = (
|
280 |
+
f"{system_prompt}\n\nPrevious conversation history:\n{history_content}\n\n"
|
281 |
+
f"Current question: {question_text}\nUser's input: {user_input}\n\n"
|
282 |
+
"Respond in a warm and conversational way, offering natural follow-ups if needed."
|
283 |
+
)
|
284 |
+
|
285 |
+
messages = [
|
286 |
+
SystemMessage(content=system_prompt),
|
287 |
+
HumanMessage(content=combined_prompt),
|
288 |
+
]
|
289 |
+
|
290 |
+
response = chat.invoke(messages)
|
291 |
+
response_content = response.content.strip()
|
292 |
+
|
293 |
+
# --- Integrated bot_response functionality starts here ---
|
294 |
+
|
295 |
+
interview_state.question_count += 1
|
296 |
+
voice = interview_state.get_voice_setting() # Get voice setting
|
297 |
+
|
298 |
+
# Generate and save the bot's audio response
|
299 |
+
audio_buffer = BytesIO()
|
300 |
+
convert_text_to_speech(response_content, audio_buffer, voice)
|
301 |
+
audio_buffer.seek(0)
|
302 |
+
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as temp_file:
|
303 |
+
temp_audio_path = temp_file.name
|
304 |
+
temp_file.write(audio_buffer.getvalue())
|
305 |
+
|
306 |
+
interview_state.temp_audio_files.append(temp_audio_path)
|
307 |
+
|
308 |
+
# --- Integrated bot_response functionality ends here ---
|
309 |
+
|
310 |
+
conversation_history.append({"question": question_text, "answer": user_input})
|
311 |
+
interview_data.append({"question": question_text, "answer": user_input})
|
312 |
+
history.append({"role": "user", "content": user_input})
|
313 |
+
history.append({"role": "assistant", "content": response_content, "audio": temp_audio_path}) # Store audio path
|
314 |
+
|
315 |
+
if current_question_index[0] + 1 < len(questions):
|
316 |
+
current_question_index[0] += 1
|
317 |
+
next_question = f"Alright, let's move on. {questions[current_question_index[0]]}"
|
318 |
+
history.append({"role": "assistant", "content": next_question})
|
319 |
+
|
320 |
+
else:
|
321 |
+
conclusion_message = "That wraps up our interview. Thank you so much for your responses—it's been great learning more about you!"
|
322 |
+
history.append(
|
323 |
+
{"role": "assistant", "content": conclusion_message}
|
324 |
+
)
|
325 |
+
|
326 |
+
# --- Generate report and save history (only at the end) ---
|
327 |
+
interview_state.interview_finished = True
|
328 |
+
|
329 |
+
# Generate the HR report content
|
330 |
+
report_content = generate_report(
|
331 |
+
interview_state.report_chain,
|
332 |
+
[msg["content"] for msg in history if msg["role"] != "system"], # Consider only user/assistant messages
|
333 |
+
language,
|
334 |
+
)
|
335 |
+
|
336 |
+
# Save the interview history
|
337 |
+
txt_path = save_interview_history(
|
338 |
+
[msg["content"] for msg in history if msg["role"] != "system"], language # Consider only user/assistant messages
|
339 |
+
)
|
340 |
+
print(f"[DEBUG] Interview history saved at: {txt_path}")
|
341 |
+
|
342 |
+
# Save the report to the reports folder
|
343 |
+
report_file_path = store_interview_report(report_content)
|
344 |
+
print(f"[DEBUG] Interview report saved at: {report_file_path}")
|
345 |
+
|
346 |
+
return history, ""
|
347 |
+
|
348 |
+
return interview_step, initial_message
|
349 |
+
|
350 |
+
|
351 |
+
|
352 |
+
def launch_candidate_app():
|
353 |
+
QUESTIONS_FILE_PATH = "questions.json"
|
354 |
+
|
355 |
+
def start_interview_ui():
|
356 |
+
# Reload questions every time the interview starts
|
357 |
+
interview_state.current_questions = read_questions_from_json(QUESTIONS_FILE_PATH)
|
358 |
+
interview_func, initial_message = conduct_interview(interview_state.current_questions)
|
359 |
+
interview_state.interview_func = interview_func
|
360 |
+
|
361 |
+
history = [{"role": "assistant", "content": initial_message}]
|
362 |
+
history.append({"role": "assistant", "content": "Let's begin! Here's your first question: " + interview_state.current_questions[0]})
|
363 |
+
return history, ""
|
364 |
+
|
365 |
+
def clear_interview_ui():
|
366 |
+
# Reset state when clearing the interview
|
367 |
+
interview_state.reset()
|
368 |
+
return [], ""
|
369 |
+
|
370 |
+
def on_enter_submit_ui(history, user_response):
|
371 |
+
if not user_response.strip():
|
372 |
+
return history, ""
|
373 |
+
history, _ = interview_state.interview_func(user_response, history)
|
374 |
+
return history, ""
|
375 |
+
|
376 |
+
with gr.Blocks(title="AI HR Interview Assistant") as candidate_app:
|
377 |
+
gr.Markdown("<h1 style='text-align: center;'>👋 Welcome to Your AI HR Interview Assistant</h1>")
|
378 |
+
start_btn = gr.Button("Start Interview", variant="primary")
|
379 |
+
chatbot = gr.Chatbot(label="Interview Chat", height=650, type="messages")
|
380 |
+
user_input = gr.Textbox(label="Your Response", placeholder="Type your answer here...", lines=1)
|
381 |
+
with gr.Row():
|
382 |
+
submit_btn = gr.Button("Submit")
|
383 |
+
clear_btn = gr.Button("Clear Chat")
|
384 |
+
|
385 |
+
start_btn.click(start_interview_ui, inputs=[], outputs=[chatbot, user_input])
|
386 |
+
submit_btn.click(on_enter_submit_ui, inputs=[chatbot, user_input], outputs=[chatbot, user_input])
|
387 |
+
user_input.submit(on_enter_submit_ui, inputs=[chatbot, user_input], outputs=[chatbot, user_input])
|
388 |
+
clear_btn.click(clear_interview_ui, inputs=[], outputs=[chatbot, user_input])
|
389 |
+
|
390 |
+
return candidate_app
|
391 |
+
|
392 |
+
|
393 |
+
def create_manager_app():
|
394 |
+
with gr.Blocks(
|
395 |
+
title="AI HR Interviewer Manager",
|
396 |
+
css="""
|
397 |
+
.tab-button {
|
398 |
+
background-color: #f0f0f0;
|
399 |
+
color: #333;
|
400 |
+
padding: 10px 20px;
|
401 |
+
border: none;
|
402 |
+
cursor: pointer;
|
403 |
+
font-size: 16px;
|
404 |
+
transition: background-color 0.3s ease;
|
405 |
+
}
|
406 |
+
.tab-button:hover {
|
407 |
+
background-color: #d0d0d0;
|
408 |
+
}
|
409 |
+
.tab-button.selected {
|
410 |
+
background-color: #666;
|
411 |
+
color: white;
|
412 |
+
}
|
413 |
+
""",
|
414 |
+
) as manager_app:
|
415 |
+
gr.HTML(
|
416 |
+
"""
|
417 |
+
<div style='text-align: center; margin-bottom: 20px;'>
|
418 |
+
<h1 style='font-size: 36px; color: #333;'>AI HR Interviewer Manager</h1>
|
419 |
+
<p style='font-size: 18px; color: #666;'>Select your role to start the interview process.</p>
|
420 |
+
</div>
|
421 |
+
"""
|
422 |
+
)
|
423 |
+
|
424 |
+
with gr.Row():
|
425 |
+
user_role = gr.Dropdown(
|
426 |
+
choices=["Admin", "Candidate"],
|
427 |
+
label="Select User Role",
|
428 |
+
value="Candidate",
|
429 |
+
)
|
430 |
+
proceed_button = gr.Button("👉 Proceed")
|
431 |
+
|
432 |
+
candidate_ui = gr.Column(visible=False)
|
433 |
+
admin_ui = gr.Column(visible=False)
|
434 |
+
|
435 |
+
with candidate_ui:
|
436 |
+
gr.Markdown("## 🚀 Candidate Interview")
|
437 |
+
candidate_app = launch_candidate_app()
|
438 |
+
|
439 |
+
with admin_ui:
|
440 |
+
gr.Markdown("## 🔒 Admin Panel")
|
441 |
+
with gr.Tab("Generate Questions"):
|
442 |
+
try:
|
443 |
+
professions_data = load_json_data(PROFESSIONS_FILE)
|
444 |
+
types_data = load_json_data(TYPES_FILE)
|
445 |
+
except (FileNotFoundError, json.JSONDecodeError) as e:
|
446 |
+
print(f"Error loading data from JSON files: {e}")
|
447 |
+
professions_data = []
|
448 |
+
types_data = []
|
449 |
+
|
450 |
+
profession_names = [
|
451 |
+
item["profession"] for item in professions_data
|
452 |
+
]
|
453 |
+
interview_types = [item["type"] for item in types_data]
|
454 |
+
|
455 |
+
with gr.Row():
|
456 |
+
profession_input = gr.Dropdown(
|
457 |
+
label="Select Profession", choices=profession_names
|
458 |
+
)
|
459 |
+
interview_type_input = gr.Dropdown(
|
460 |
+
label="Select Interview Type", choices=interview_types
|
461 |
+
)
|
462 |
+
|
463 |
+
num_questions_input = gr.Number(
|
464 |
+
label="Number of Questions (1-20)",
|
465 |
+
value=5,
|
466 |
+
precision=0,
|
467 |
+
minimum=1,
|
468 |
+
maximum=20,
|
469 |
+
)
|
470 |
+
overwrite_input = gr.Checkbox(
|
471 |
+
label="Overwrite all_questions.json?", value=True
|
472 |
+
)
|
473 |
+
# Update num_questions_input when interview_type_input changes
|
474 |
+
interview_type_input.change(
|
475 |
+
fn=update_max_questions,
|
476 |
+
inputs=interview_type_input,
|
477 |
+
outputs=num_questions_input,
|
478 |
+
)
|
479 |
+
generate_button = gr.Button("Generate Questions")
|
480 |
+
|
481 |
+
output_text = gr.Textbox(label="Output")
|
482 |
+
question_output = gr.JSON(label="Generated Questions")
|
483 |
+
|
484 |
+
generate_button.click(
|
485 |
+
generate_questions_manager,
|
486 |
+
inputs=[
|
487 |
+
profession_input,
|
488 |
+
interview_type_input,
|
489 |
+
num_questions_input,
|
490 |
+
overwrite_input,
|
491 |
+
],
|
492 |
+
outputs=[output_text, question_output],
|
493 |
+
)
|
494 |
+
|
495 |
+
|
496 |
+
with gr.Tab("Generate from PDF"):
|
497 |
+
gr.Markdown("### 📄 Upload PDF for Question Generation")
|
498 |
+
pdf_file_input = gr.File(label="Upload PDF File", type="filepath")
|
499 |
+
num_questions_pdf_input = gr.Number(label="Number of Questions", value=5, precision=0)
|
500 |
+
|
501 |
+
pdf_status_output = gr.Textbox(label="Status", lines=3)
|
502 |
+
pdf_question_output = gr.JSON(label="Generated Questions")
|
503 |
+
|
504 |
+
generate_pdf_button = gr.Button("Generate Questions from PDF")
|
505 |
+
|
506 |
+
def update_pdf_ui(pdf_path, num_questions):
|
507 |
+
for status, questions in generate_and_save_questions_from_pdf3(pdf_path, num_questions):
|
508 |
+
yield gr.update(value=status), gr.update(value=questions)
|
509 |
+
|
510 |
+
generate_pdf_button.click(
|
511 |
+
update_pdf_ui,
|
512 |
+
inputs=[pdf_file_input, num_questions_pdf_input],
|
513 |
+
outputs=[pdf_status_output, pdf_question_output],
|
514 |
+
)
|
515 |
+
|
516 |
+
|
517 |
+
|
518 |
+
|
519 |
+
def show_selected_ui(role):
|
520 |
+
if role == "Candidate":
|
521 |
+
return {candidate_ui: gr.Column(visible=True), admin_ui: gr.Column(visible=False)}
|
522 |
+
|
523 |
+
elif role == "Admin":
|
524 |
+
return {candidate_ui: gr.Column(visible=False), admin_ui: gr.Column(visible=True)}
|
525 |
+
else:
|
526 |
+
return {candidate_ui: gr.Column(visible=False), admin_ui: gr.Column(visible=False)}
|
527 |
+
|
528 |
+
|
529 |
+
proceed_button.click(
|
530 |
+
show_selected_ui,
|
531 |
+
inputs=[user_role],
|
532 |
+
outputs=[candidate_ui, admin_ui],
|
533 |
+
)
|
534 |
+
|
535 |
+
return manager_app
|
536 |
+
|
537 |
+
def cleanup():
|
538 |
+
for audio_file in interview_state.temp_audio_files:
|
539 |
+
try:
|
540 |
+
if os.path.exists(audio_file):
|
541 |
+
os.unlink(audio_file)
|
542 |
+
except Exception as e:
|
543 |
+
print(f"Error deleting file {audio_file}: {e}")
|
544 |
+
|
545 |
+
|
546 |
+
if __name__ == "__main__":
|
547 |
+
manager_app = create_manager_app()
|
548 |
+
try:
|
549 |
+
manager_app.launch(server_name="0.0.0.0", server_port=7860, debug=True)
|
550 |
+
finally:
|
551 |
+
cleanup()
|
generator.py
ADDED
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import json
|
3 |
+
from dotenv import load_dotenv
|
4 |
+
from langchain_openai import ChatOpenAI
|
5 |
+
from langchain.schema import HumanMessage, SystemMessage
|
6 |
+
|
7 |
+
# Load environment variables
|
8 |
+
load_dotenv()
|
9 |
+
|
10 |
+
# File paths
|
11 |
+
PROFESSIONS_FILE = "professions.json"
|
12 |
+
TYPES_FILE = "types.json"
|
13 |
+
OUTPUT_FILE = "all_questions.json"
|
14 |
+
|
15 |
+
def generate_questions(profession, interview_type, description, max_questions):
|
16 |
+
"""
|
17 |
+
Generates interview questions using the OpenAI API based on profession, type, and description.
|
18 |
+
"""
|
19 |
+
openai_api_key = os.getenv("OPENAI_API_KEY")
|
20 |
+
|
21 |
+
if not openai_api_key:
|
22 |
+
raise RuntimeError(
|
23 |
+
"OpenAI API key not found. Please add it to your .env file as OPENAI_API_KEY."
|
24 |
+
)
|
25 |
+
|
26 |
+
chat = ChatOpenAI(
|
27 |
+
openai_api_key=openai_api_key, model="gpt-4", temperature=0.7, max_tokens=750
|
28 |
+
)
|
29 |
+
|
30 |
+
messages = [
|
31 |
+
SystemMessage(
|
32 |
+
content="You are an expert interviewer who generates concise technical interview questions for HR interviews. "
|
33 |
+
"Answer only with questions. Do not number the questions. Each question should be a separate string. "
|
34 |
+
"The questions should be appropriate for "
|
35 |
+
f"the {interview_type} stage of the interview process and relevant to the {profession} profession."
|
36 |
+
f" Generate no more than {max_questions} questions."
|
37 |
+
|
38 |
+
),
|
39 |
+
HumanMessage(
|
40 |
+
content=f"Generate interview questions for the role of '{profession}'. "
|
41 |
+
f"Interview Type: '{interview_type}'. "
|
42 |
+
f"Description of the role: '{description}'. "
|
43 |
+
|
44 |
+
),
|
45 |
+
]
|
46 |
+
|
47 |
+
try:
|
48 |
+
print(f"[DEBUG] Sending request to OpenAI for {profession} - {interview_type}")
|
49 |
+
response = chat.invoke(messages)
|
50 |
+
# Directly split the response into individual questions without numbering
|
51 |
+
questions = [q.strip() for q in response.content.split("\n") if q.strip()]
|
52 |
+
|
53 |
+
except Exception as e:
|
54 |
+
print(f"[ERROR] Failed to generate questions: {e}")
|
55 |
+
questions = ["An error occurred while generating questions."]
|
56 |
+
|
57 |
+
return questions
|
58 |
+
|
59 |
+
|
60 |
+
def load_json_data(filepath):
|
61 |
+
"""Loads data from a JSON file."""
|
62 |
+
with open(filepath, "r") as f:
|
63 |
+
return json.load(f)
|
64 |
+
|
65 |
+
|
66 |
+
def save_questions_to_file(output_file, all_questions, overwrite=True):
|
67 |
+
"""
|
68 |
+
Saves the questions to the specified JSON file.
|
69 |
+
|
70 |
+
Args:
|
71 |
+
output_file: The path to the output JSON file.
|
72 |
+
all_questions: The list of question dictionaries to save.
|
73 |
+
overwrite: If True, overwrites the file if it exists. If False, appends to the file.
|
74 |
+
"""
|
75 |
+
if overwrite:
|
76 |
+
with open(output_file, "w") as outfile:
|
77 |
+
json.dump(all_questions, outfile, indent=4)
|
78 |
+
else:
|
79 |
+
try:
|
80 |
+
existing_questions = load_json_data(output_file)
|
81 |
+
except (FileNotFoundError, json.JSONDecodeError):
|
82 |
+
existing_questions = []
|
83 |
+
|
84 |
+
existing_questions.extend(all_questions)
|
85 |
+
|
86 |
+
with open(output_file, "w") as outfile:
|
87 |
+
json.dump(existing_questions, outfile, indent=4)
|
88 |
+
|
89 |
+
|
90 |
+
def main(overwrite_output=True):
|
91 |
+
"""
|
92 |
+
Main function to generate and save interview questions.
|
93 |
+
"""
|
94 |
+
try:
|
95 |
+
professions_data = load_json_data(PROFESSIONS_FILE)
|
96 |
+
types_data = load_json_data(TYPES_FILE)
|
97 |
+
except FileNotFoundError as e:
|
98 |
+
print(f"Error: File not found - {e}")
|
99 |
+
return
|
100 |
+
except json.JSONDecodeError as e:
|
101 |
+
print(f"Error: Invalid JSON format in file - {e}")
|
102 |
+
return
|
103 |
+
|
104 |
+
all_questions = []
|
105 |
+
|
106 |
+
for profession_info in professions_data:
|
107 |
+
profession = profession_info["profession"]
|
108 |
+
description = profession_info["description"]
|
109 |
+
|
110 |
+
for interview_type_info in types_data:
|
111 |
+
interview_type = interview_type_info["type"]
|
112 |
+
max_questions = interview_type_info.get("max_questions", 5)
|
113 |
+
|
114 |
+
questions = generate_questions(
|
115 |
+
profession, interview_type, description, max_questions
|
116 |
+
)
|
117 |
+
|
118 |
+
all_questions.append(
|
119 |
+
{
|
120 |
+
"profession": profession,
|
121 |
+
"interview_type": interview_type,
|
122 |
+
"description": description,
|
123 |
+
"max_questions": max_questions,
|
124 |
+
"questions": questions,
|
125 |
+
}
|
126 |
+
)
|
127 |
+
# Save the questions, either overwriting or appending based on the parameter
|
128 |
+
save_questions_to_file(OUTPUT_FILE, all_questions, overwrite=overwrite_output)
|
129 |
+
print(f"[INFO] Questions saved to {OUTPUT_FILE}")
|
130 |
+
|
131 |
+
|
132 |
+
if __name__ == "__main__":
|
133 |
+
# Set overwrite_output to True to overwrite the existing file, False to append
|
134 |
+
main(overwrite_output=True)
|
generatorgr.py
ADDED
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import json
|
3 |
+
import time
|
4 |
+
import os
|
5 |
+
|
6 |
+
from generator import PROFESSIONS_FILE, TYPES_FILE, OUTPUT_FILE
|
7 |
+
from generator import generate_questions, load_json_data, save_questions_to_file
|
8 |
+
|
9 |
+
# Load professions and interview types from JSON files
|
10 |
+
try:
|
11 |
+
professions_data = load_json_data(PROFESSIONS_FILE)
|
12 |
+
types_data = load_json_data(TYPES_FILE)
|
13 |
+
except (FileNotFoundError, json.JSONDecodeError) as e:
|
14 |
+
print(f"Error loading data from JSON files: {e}")
|
15 |
+
professions_data = []
|
16 |
+
types_data = []
|
17 |
+
|
18 |
+
# Extract profession names and interview types for the dropdown menus
|
19 |
+
profession_names = [item["profession"] for item in professions_data]
|
20 |
+
interview_types = [item["type"] for item in types_data]
|
21 |
+
|
22 |
+
# Define path for the questions.json file
|
23 |
+
QUESTIONS_FILE = "questions.json"
|
24 |
+
|
25 |
+
|
26 |
+
def generate_and_save_questions(profession, interview_type, num_questions, overwrite=True, progress=gr.Progress()):
|
27 |
+
"""
|
28 |
+
Generates questions using the generate_questions function and saves them to JSON files.
|
29 |
+
Provides progress updates.
|
30 |
+
"""
|
31 |
+
profession_info = next(
|
32 |
+
(item for item in professions_data if item["profession"] == profession), None
|
33 |
+
)
|
34 |
+
interview_type_info = next(
|
35 |
+
(item for item in types_data if item["type"] == interview_type), None
|
36 |
+
)
|
37 |
+
|
38 |
+
if profession_info is None or interview_type_info is None:
|
39 |
+
return "Error: Invalid profession or interview type selected.", None
|
40 |
+
|
41 |
+
description = profession_info["description"]
|
42 |
+
max_questions = min(int(num_questions), 20) # Ensure max is 20
|
43 |
+
|
44 |
+
progress(0, desc="Starting question generation...")
|
45 |
+
|
46 |
+
questions = generate_questions(
|
47 |
+
profession, interview_type, description, max_questions
|
48 |
+
)
|
49 |
+
|
50 |
+
progress(0.5, desc=f"Generated {len(questions)} questions. Saving...")
|
51 |
+
|
52 |
+
# Save the generated questions to the all_questions.json file
|
53 |
+
|
54 |
+
all_questions_entry = {
|
55 |
+
"profession": profession,
|
56 |
+
"interview_type": interview_type,
|
57 |
+
"description": description,
|
58 |
+
"max_questions": max_questions,
|
59 |
+
"questions": questions,
|
60 |
+
}
|
61 |
+
|
62 |
+
|
63 |
+
save_questions_to_file(OUTPUT_FILE, [all_questions_entry], overwrite=overwrite)
|
64 |
+
|
65 |
+
# Save the generated questions to the new questions.json file
|
66 |
+
with open(QUESTIONS_FILE, "w") as outfile:
|
67 |
+
json.dump(questions, outfile, indent=4)
|
68 |
+
|
69 |
+
progress(1, desc="Questions saved.")
|
70 |
+
|
71 |
+
return (
|
72 |
+
f"✅ Questions generated and saved for {profession} ({interview_type}). Max questions: {max_questions}",
|
73 |
+
questions,
|
74 |
+
)
|
75 |
+
|
76 |
+
|
77 |
+
|
78 |
+
def update_max_questions(interview_type):
|
79 |
+
"""
|
80 |
+
Updates the default value of the number input based on the selected interview type.
|
81 |
+
"""
|
82 |
+
interview_type_info = next(
|
83 |
+
(item for item in types_data if item["type"] == interview_type), None
|
84 |
+
)
|
85 |
+
if interview_type_info:
|
86 |
+
default_max_questions = interview_type_info.get("max_questions", 5)
|
87 |
+
return gr.update(value=default_max_questions, minimum=1, maximum=20)
|
88 |
+
else:
|
89 |
+
return gr.update(value=5, minimum=1, maximum=20)
|
90 |
+
|
91 |
+
|
92 |
+
with gr.Blocks() as demo:
|
93 |
+
gr.Markdown("## 📄 Interview Question Generator for IBM CIC")
|
94 |
+
with gr.Row():
|
95 |
+
profession_input = gr.Dropdown(label="Select Profession", choices=profession_names)
|
96 |
+
interview_type_input = gr.Dropdown(label="Select Interview Type", choices=interview_types)
|
97 |
+
|
98 |
+
num_questions_input = gr.Number(
|
99 |
+
label="Number of Questions (1-20)", value=5, precision=0, minimum=1, maximum=20
|
100 |
+
)
|
101 |
+
|
102 |
+
generate_button = gr.Button("Generate Questions")
|
103 |
+
|
104 |
+
output_text = gr.Textbox(label="Output")
|
105 |
+
question_output = gr.JSON(label="Generated Questions")
|
106 |
+
|
107 |
+
# Update num_questions_input when interview_type_input changes
|
108 |
+
interview_type_input.change(
|
109 |
+
fn=update_max_questions,
|
110 |
+
inputs=interview_type_input,
|
111 |
+
outputs=num_questions_input,
|
112 |
+
)
|
113 |
+
|
114 |
+
generate_button.click(
|
115 |
+
generate_and_save_questions,
|
116 |
+
inputs=[profession_input, interview_type_input, num_questions_input],
|
117 |
+
outputs=[output_text, question_output],
|
118 |
+
)
|
119 |
+
|
120 |
+
if __name__ == "__main__":
|
121 |
+
demo.queue().launch()
|
gpt-general.py
ADDED
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import json
|
3 |
+
from dotenv import load_dotenv
|
4 |
+
from langchain_openai import ChatOpenAI # Correct import from langchain-openai
|
5 |
+
from langchain.schema import HumanMessage, SystemMessage # For creating structured chat messages
|
6 |
+
|
7 |
+
# Load environment variables
|
8 |
+
load_dotenv()
|
9 |
+
|
10 |
+
# Function to read questions from JSON
|
11 |
+
# The JSON is expected to contain a list of dictionaries or strings.
|
12 |
+
def read_questions_from_json(file_path):
|
13 |
+
"""
|
14 |
+
Reads questions from a JSON file.
|
15 |
+
"""
|
16 |
+
if not os.path.exists(file_path):
|
17 |
+
raise FileNotFoundError(f"The file '{file_path}' does not exist.")
|
18 |
+
|
19 |
+
with open(file_path, 'r') as f:
|
20 |
+
questions_list = json.load(f)
|
21 |
+
|
22 |
+
if not questions_list:
|
23 |
+
raise ValueError("The JSON file is empty or has invalid content.")
|
24 |
+
|
25 |
+
return questions_list
|
26 |
+
|
27 |
+
# Function to generate interview questions using LLM and collect user answers
|
28 |
+
def conduct_interview_with_llm(questions, language="English"):
|
29 |
+
"""
|
30 |
+
Generates interview questions using the LLM and collects user responses.
|
31 |
+
"""
|
32 |
+
openai_api_key = os.getenv("OPENAI_API_KEY")
|
33 |
+
if not openai_api_key:
|
34 |
+
raise RuntimeError("OpenAI API key not found. Please add it to your .env file as OPENAI_API_KEY.")
|
35 |
+
|
36 |
+
chat = ChatOpenAI(
|
37 |
+
openai_api_key=openai_api_key, model="gpt-4", temperature=0.7, max_tokens=750
|
38 |
+
)
|
39 |
+
|
40 |
+
interview_data = []
|
41 |
+
print("\n--- Technical Interview Started ---\n")
|
42 |
+
|
43 |
+
for index, question_text in enumerate(questions):
|
44 |
+
# Create the system and user prompts
|
45 |
+
system_prompt = f"You are Sarah, a compassionate and empathetic HR professional conducting a technical interview in {language}."
|
46 |
+
messages = [
|
47 |
+
SystemMessage(content=system_prompt),
|
48 |
+
HumanMessage(content=f"Generate the next interview question based on the context and previous history. Current question number: {index + 1}/{len(questions)}.")
|
49 |
+
]
|
50 |
+
|
51 |
+
try:
|
52 |
+
# Generate a question from the LLM
|
53 |
+
print(f"Generating question {index + 1}...")
|
54 |
+
response = chat.invoke(messages)
|
55 |
+
llm_generated_question = response.content.strip()
|
56 |
+
print(f"Q{index + 1}: {llm_generated_question}")
|
57 |
+
|
58 |
+
# Collect the user’s answer
|
59 |
+
user_answer = input("Your answer: ").strip()
|
60 |
+
interview_data.append({"question": llm_generated_question, "answer": user_answer})
|
61 |
+
|
62 |
+
except Exception as e:
|
63 |
+
print(f"Error with OpenAI API: {e}")
|
64 |
+
interview_data.append({"question": "An error occurred while generating the question.", "answer": "No answer recorded."})
|
65 |
+
|
66 |
+
print("\n--- Technical Interview Completed ---\n")
|
67 |
+
return interview_data
|
68 |
+
|
69 |
+
# Function to save interview to a text file
|
70 |
+
def save_interview_to_file(interview_data, file_path):
|
71 |
+
"""
|
72 |
+
Saves the questions and answers to a text file.
|
73 |
+
"""
|
74 |
+
with open(file_path, 'w') as f:
|
75 |
+
for entry in interview_data:
|
76 |
+
f.write(f"Q: {entry['question']}\n")
|
77 |
+
f.write(f"A: {entry['answer']}\n\n")
|
78 |
+
|
79 |
+
print(f"Interview saved to {file_path}")
|
80 |
+
|
81 |
+
if __name__ == "__main__":
|
82 |
+
QUESTIONS_FILE_PATH = "questions.json"
|
83 |
+
INTERVIEW_FILE_PATH = "interview.txt"
|
84 |
+
|
85 |
+
try:
|
86 |
+
# Read questions from JSON file
|
87 |
+
questions = read_questions_from_json(QUESTIONS_FILE_PATH)
|
88 |
+
|
89 |
+
# Conduct the interview
|
90 |
+
interview_results = conduct_interview_with_llm(questions, language="English")
|
91 |
+
|
92 |
+
# Save the interview to a text file
|
93 |
+
save_interview_to_file(interview_results, INTERVIEW_FILE_PATH)
|
94 |
+
|
95 |
+
except Exception as e:
|
96 |
+
print(f"Error: {e}")
|
gpt.py
ADDED
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import json
|
3 |
+
from collections import deque
|
4 |
+
from dotenv import load_dotenv
|
5 |
+
from langchain_openai import ChatOpenAI
|
6 |
+
from langchain.schema import HumanMessage, SystemMessage
|
7 |
+
|
8 |
+
# Load environment variables
|
9 |
+
load_dotenv()
|
10 |
+
|
11 |
+
# Function to read questions from JSON
|
12 |
+
def read_questions_from_json(file_path):
|
13 |
+
if not os.path.exists(file_path):
|
14 |
+
raise FileNotFoundError(f"The file '{file_path}' does not exist.")
|
15 |
+
|
16 |
+
with open(file_path, 'r') as f:
|
17 |
+
questions_list = json.load(f)
|
18 |
+
|
19 |
+
if not questions_list:
|
20 |
+
raise ValueError("The JSON file is empty or has invalid content.")
|
21 |
+
|
22 |
+
return questions_list
|
23 |
+
|
24 |
+
# Function to handle user input and responses from the LLM
|
25 |
+
def handle_user_input(chat, system_prompt, conversation_history, question_text):
|
26 |
+
while True:
|
27 |
+
user_input = input(f"Your response: ").strip()
|
28 |
+
if user_input.lower() in ["exit", "quit"]:
|
29 |
+
print("Interview terminated as requested.")
|
30 |
+
return "[Interview Terminated by User]", True
|
31 |
+
|
32 |
+
history_content = "\n".join([f"Q: {entry['question']}\nA: {entry['answer']}" for entry in conversation_history])
|
33 |
+
combined_prompt = (f"{system_prompt}\n\nPrevious conversation history:\n{history_content}\n\n"
|
34 |
+
f"Current question: {question_text}\nUser's input: {user_input}\n\n"
|
35 |
+
"Respond naturally to any follow-up questions or requests for clarification, and let the user know when you're ready to proceed.")
|
36 |
+
|
37 |
+
messages = [
|
38 |
+
SystemMessage(content=system_prompt),
|
39 |
+
HumanMessage(content=combined_prompt)
|
40 |
+
]
|
41 |
+
|
42 |
+
response = chat.invoke(messages)
|
43 |
+
response_content = response.content.strip()
|
44 |
+
|
45 |
+
if "proceed" in response_content.lower() or "continue" in response_content.lower():
|
46 |
+
print("Understood. Let’s continue to the next question.")
|
47 |
+
return user_input, False
|
48 |
+
else:
|
49 |
+
print(f"LLM's Response: {response_content}")
|
50 |
+
#print("Whenever you're ready, just let me know if you want to move forward or discuss further.")
|
51 |
+
print("Whenever you're ready, just let me know, if you want to move to the next question tell me proceed, or provide further clarifications.")
|
52 |
+
|
53 |
+
# Function to conduct the interview
|
54 |
+
def conduct_interview_with_user_input(questions, language="English", history_limit=5):
|
55 |
+
openai_api_key = os.getenv("OPENAI_API_KEY")
|
56 |
+
if not openai_api_key:
|
57 |
+
raise RuntimeError("OpenAI API key not found. Please add it to your .env file as OPENAI_API_KEY.")
|
58 |
+
|
59 |
+
chat = ChatOpenAI(
|
60 |
+
openai_api_key=openai_api_key, model="gpt-4", temperature=0.7, max_tokens=750
|
61 |
+
)
|
62 |
+
|
63 |
+
interview_data = []
|
64 |
+
conversation_history = deque(maxlen=history_limit)
|
65 |
+
system_prompt = (f"You are Sarah, an empathetic HR interviewer conducting a technical interview in {language}. "
|
66 |
+
"Respond to user follow-up questions politely and concisely. If the user is confused, provide clear clarification.")
|
67 |
+
|
68 |
+
print("\n--- Technical Interview Started ---\n")
|
69 |
+
|
70 |
+
for index, question_text in enumerate(questions):
|
71 |
+
print(f"{index + 1}/{len(questions)}: {question_text}")
|
72 |
+
try:
|
73 |
+
user_answer, terminate = handle_user_input(chat, system_prompt, conversation_history, question_text)
|
74 |
+
if terminate:
|
75 |
+
break
|
76 |
+
|
77 |
+
conversation_history.append({"question": question_text, "answer": user_answer})
|
78 |
+
interview_data.append({"question": question_text, "answer": user_answer})
|
79 |
+
|
80 |
+
if index + 1 == len(questions):
|
81 |
+
print("Thank you for your time. This concludes the interview. We will prepare a report based on the gathered information.")
|
82 |
+
|
83 |
+
except Exception as e:
|
84 |
+
print(f"Error during the interview process: {e}")
|
85 |
+
interview_data.append({"question": question_text, "answer": "No answer recorded due to an error."})
|
86 |
+
|
87 |
+
print("\n--- Technical Interview Completed ---\n")
|
88 |
+
return interview_data
|
89 |
+
|
90 |
+
# Function to save interview to a text file
|
91 |
+
def save_interview_to_file(interview_data, file_path):
|
92 |
+
with open(file_path, 'w') as f:
|
93 |
+
for entry in interview_data:
|
94 |
+
f.write(f"Q: {entry['question']}\n")
|
95 |
+
f.write(f"A: {entry['answer']}\n\n")
|
96 |
+
|
97 |
+
print(f"Interview saved to {file_path}")
|
98 |
+
|
99 |
+
if __name__ == "__main__":
|
100 |
+
QUESTIONS_FILE_PATH = "questions.json"
|
101 |
+
INTERVIEW_FILE_PATH = "interview.txt"
|
102 |
+
|
103 |
+
try:
|
104 |
+
questions = read_questions_from_json(QUESTIONS_FILE_PATH)
|
105 |
+
interview_results = conduct_interview_with_user_input(questions, language="English")
|
106 |
+
save_interview_to_file(interview_results, INTERVIEW_FILE_PATH)
|
107 |
+
|
108 |
+
except Exception as e:
|
109 |
+
print(f"Error: {e}")
|
gptgr-manager.py
ADDED
@@ -0,0 +1,185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import tempfile
|
3 |
+
import os
|
4 |
+
import json
|
5 |
+
from io import BytesIO
|
6 |
+
from collections import deque
|
7 |
+
from dotenv import load_dotenv
|
8 |
+
from langchain_openai import ChatOpenAI
|
9 |
+
from langchain.schema import HumanMessage, SystemMessage
|
10 |
+
|
11 |
+
# Load environment variables
|
12 |
+
load_dotenv()
|
13 |
+
|
14 |
+
class InterviewState:
|
15 |
+
def __init__(self):
|
16 |
+
self.reset()
|
17 |
+
|
18 |
+
def reset(self, voice="alloy"):
|
19 |
+
self.question_count = 0
|
20 |
+
self.interview_history = []
|
21 |
+
self.selected_interviewer = voice
|
22 |
+
self.interview_finished = False
|
23 |
+
self.audio_enabled = True
|
24 |
+
self.temp_audio_files = []
|
25 |
+
self.initial_audio_path = None
|
26 |
+
self.admin_authenticated = False
|
27 |
+
self.document_loaded = False
|
28 |
+
self.knowledge_retrieval_setup = False
|
29 |
+
self.interview_chain = None
|
30 |
+
self.report_chain = None
|
31 |
+
|
32 |
+
def get_voice_setting(self):
|
33 |
+
return self.selected_interviewer
|
34 |
+
|
35 |
+
interview_state = InterviewState()
|
36 |
+
|
37 |
+
# Function to read questions from JSON
|
38 |
+
def read_questions_from_json(file_path):
|
39 |
+
if not os.path.exists(file_path):
|
40 |
+
raise FileNotFoundError(f"The file '{file_path}' does not exist.")
|
41 |
+
|
42 |
+
with open(file_path, 'r') as f:
|
43 |
+
questions_list = json.load(f)
|
44 |
+
|
45 |
+
if not questions_list:
|
46 |
+
raise ValueError("The JSON file is empty or has invalid content.")
|
47 |
+
|
48 |
+
return questions_list
|
49 |
+
|
50 |
+
# Conduct interview and handle user input
|
51 |
+
def conduct_interview(questions, language="English", history_limit=5):
|
52 |
+
openai_api_key = os.getenv("OPENAI_API_KEY")
|
53 |
+
if not openai_api_key:
|
54 |
+
raise RuntimeError("OpenAI API key not found. Please add it to your .env file as OPENAI_API_KEY.")
|
55 |
+
|
56 |
+
chat = ChatOpenAI(
|
57 |
+
openai_api_key=openai_api_key, model="gpt-4", temperature=0.7, max_tokens=750
|
58 |
+
)
|
59 |
+
|
60 |
+
conversation_history = deque(maxlen=history_limit)
|
61 |
+
system_prompt = (f"You are Sarah, an empathetic HR interviewer conducting a technical interview in {language}. "
|
62 |
+
"Respond to user follow-up questions politely and concisely. If the user is confused, provide clear clarification.")
|
63 |
+
|
64 |
+
interview_data = []
|
65 |
+
current_question_index = [0]
|
66 |
+
|
67 |
+
initial_message = ("👋 Hi there, I'm Sarah, your friendly AI HR assistant! "
|
68 |
+
"I'll guide you through a series of interview questions to learn more about you. "
|
69 |
+
"Take your time and answer each question thoughtfully.")
|
70 |
+
|
71 |
+
def interview_step(user_input, history):
|
72 |
+
if user_input.lower() in ["exit", "quit"]:
|
73 |
+
history.append({"role": "assistant", "content": "The interview has ended at your request. Thank you for your time!"})
|
74 |
+
return history, ""
|
75 |
+
|
76 |
+
question_text = questions[current_question_index[0]]
|
77 |
+
history_content = "\n".join([f"Q: {entry['question']}\nA: {entry['answer']}" for entry in conversation_history])
|
78 |
+
combined_prompt = (f"{system_prompt}\n\nPrevious conversation history:\n{history_content}\n\n"
|
79 |
+
f"Current question: {question_text}\nUser's input: {user_input}\n\n"
|
80 |
+
"Respond in a warm and conversational way, offering natural follow-ups if needed.")
|
81 |
+
|
82 |
+
messages = [
|
83 |
+
SystemMessage(content=system_prompt),
|
84 |
+
HumanMessage(content=combined_prompt)
|
85 |
+
]
|
86 |
+
|
87 |
+
response = chat.invoke(messages)
|
88 |
+
response_content = response.content.strip()
|
89 |
+
|
90 |
+
conversation_history.append({"question": question_text, "answer": user_input})
|
91 |
+
interview_data.append({"question": question_text, "answer": user_input})
|
92 |
+
history.append({"role": "user", "content": user_input})
|
93 |
+
history.append({"role": "assistant", "content": response_content})
|
94 |
+
|
95 |
+
if current_question_index[0] + 1 < len(questions):
|
96 |
+
current_question_index[0] += 1
|
97 |
+
next_question = f"Alright, let's move on. {questions[current_question_index[0]]}"
|
98 |
+
history.append({"role": "assistant", "content": next_question})
|
99 |
+
return history, ""
|
100 |
+
else:
|
101 |
+
history.append({"role": "assistant", "content": "That wraps up our interview. Thank you so much for your responses—it's been great learning more about you!"})
|
102 |
+
return history, ""
|
103 |
+
|
104 |
+
return interview_step, initial_message
|
105 |
+
|
106 |
+
def launch_candidate_app():
|
107 |
+
QUESTIONS_FILE_PATH = "questions.json"
|
108 |
+
try:
|
109 |
+
questions = read_questions_from_json(QUESTIONS_FILE_PATH)
|
110 |
+
interview_func, initial_message = conduct_interview(questions)
|
111 |
+
|
112 |
+
def start_interview_ui():
|
113 |
+
history = [{"role": "assistant", "content": initial_message}]
|
114 |
+
history.append({"role": "assistant", "content": "Let's begin! Here's your first question: " + questions[0]})
|
115 |
+
return history, ""
|
116 |
+
|
117 |
+
def clear_interview_ui():
|
118 |
+
return [], ""
|
119 |
+
|
120 |
+
def on_enter_submit_ui(history, user_response):
|
121 |
+
if not user_response.strip():
|
122 |
+
return history, ""
|
123 |
+
history, _ = interview_func(user_response, history)
|
124 |
+
return history, ""
|
125 |
+
|
126 |
+
with gr.Blocks(title="AI HR Interview Assistant") as candidate_app:
|
127 |
+
gr.Markdown("<h1 style='text-align: center;'>👋 Welcome to Your AI HR Interview Assistant</h1>")
|
128 |
+
start_btn = gr.Button("Start Interview", variant="primary")
|
129 |
+
chatbot = gr.Chatbot(label="Interview Chat", height=650, type="messages")
|
130 |
+
user_input = gr.Textbox(label="Your Response", placeholder="Type your answer here...", lines=1)
|
131 |
+
with gr.Row():
|
132 |
+
submit_btn = gr.Button("Submit")
|
133 |
+
clear_btn = gr.Button("Clear Chat")
|
134 |
+
|
135 |
+
start_btn.click(start_interview_ui, inputs=[], outputs=[chatbot, user_input])
|
136 |
+
submit_btn.click(on_enter_submit_ui, inputs=[chatbot, user_input], outputs=[chatbot, user_input])
|
137 |
+
user_input.submit(on_enter_submit_ui, inputs=[chatbot, user_input], outputs=[chatbot, user_input])
|
138 |
+
clear_btn.click(clear_interview_ui, inputs=[], outputs=[chatbot, user_input])
|
139 |
+
|
140 |
+
return candidate_app
|
141 |
+
|
142 |
+
except Exception as e:
|
143 |
+
print(f"Error: {e}")
|
144 |
+
return None
|
145 |
+
|
146 |
+
def create_manager_app():
|
147 |
+
with gr.Blocks(title="AI HR Interviewer Manager") as manager_app:
|
148 |
+
gr.HTML("<h1 style='text-align: center;'>AI HR Interviewer Manager</h1>")
|
149 |
+
user_role = gr.Dropdown(choices=["Admin", "Candidate"], label="Select User Role", value="Candidate")
|
150 |
+
proceed_button = gr.Button("👉 Proceed")
|
151 |
+
|
152 |
+
candidate_ui = gr.Column(visible=False)
|
153 |
+
admin_ui = gr.Column(visible=False)
|
154 |
+
|
155 |
+
with candidate_ui:
|
156 |
+
gr.Markdown("## 🚀 Candidate Interview")
|
157 |
+
candidate_app = launch_candidate_app()
|
158 |
+
|
159 |
+
with admin_ui:
|
160 |
+
gr.Markdown("## 🔒 Admin Panel")
|
161 |
+
gr.Markdown("Admin operations and question generation will go here.")
|
162 |
+
|
163 |
+
def show_selected_ui(role):
|
164 |
+
if role == "Candidate":
|
165 |
+
return gr.update(visible=True), gr.update(visible=False)
|
166 |
+
elif role == "Admin":
|
167 |
+
return gr.update(visible=False), gr.update(visible=True)
|
168 |
+
else:
|
169 |
+
return gr.update(visible=False), gr.update(visible=False)
|
170 |
+
|
171 |
+
proceed_button.click(show_selected_ui, inputs=[user_role], outputs=[candidate_ui, admin_ui])
|
172 |
+
|
173 |
+
return manager_app
|
174 |
+
|
175 |
+
def cleanup():
|
176 |
+
for audio_file in interview_state.temp_audio_files:
|
177 |
+
if os.path.exists(audio_file):
|
178 |
+
os.unlink(audio_file)
|
179 |
+
|
180 |
+
if __name__ == "__main__":
|
181 |
+
manager_app = create_manager_app()
|
182 |
+
try:
|
183 |
+
manager_app.launch(server_name="0.0.0.0", server_port=7860, debug=True)
|
184 |
+
finally:
|
185 |
+
cleanup()
|
gptgr.py
ADDED
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import json
|
3 |
+
from collections import deque
|
4 |
+
from dotenv import load_dotenv
|
5 |
+
import gradio as gr
|
6 |
+
from langchain_openai import ChatOpenAI
|
7 |
+
from langchain.schema import HumanMessage, SystemMessage
|
8 |
+
|
9 |
+
# Load environment variables
|
10 |
+
load_dotenv()
|
11 |
+
|
12 |
+
# Function to read questions from JSON
|
13 |
+
def read_questions_from_json(file_path):
|
14 |
+
if not os.path.exists(file_path):
|
15 |
+
raise FileNotFoundError(f"The file '{file_path}' does not exist.")
|
16 |
+
|
17 |
+
with open(file_path, 'r') as f:
|
18 |
+
questions_list = json.load(f)
|
19 |
+
|
20 |
+
if not questions_list:
|
21 |
+
raise ValueError("The JSON file is empty or has invalid content.")
|
22 |
+
|
23 |
+
return questions_list
|
24 |
+
|
25 |
+
# Conduct interview and handle user input
|
26 |
+
def conduct_interview(questions, language="English", history_limit=5):
|
27 |
+
openai_api_key = os.getenv("OPENAI_API_KEY")
|
28 |
+
if not openai_api_key:
|
29 |
+
raise RuntimeError("OpenAI API key not found. Please add it to your .env file as OPENAI_API_KEY.")
|
30 |
+
|
31 |
+
chat = ChatOpenAI(
|
32 |
+
openai_api_key=openai_api_key, model="gpt-4", temperature=0.7, max_tokens=750
|
33 |
+
)
|
34 |
+
|
35 |
+
conversation_history = deque(maxlen=history_limit)
|
36 |
+
system_prompt = (f"You are Sarah, an empathetic HR interviewer conducting a technical interview in {language}. "
|
37 |
+
"Respond to user follow-up questions politely and concisely. If the user is confused, provide clear clarification.")
|
38 |
+
|
39 |
+
interview_data = []
|
40 |
+
current_question_index = [0] # Use a list to hold the index
|
41 |
+
|
42 |
+
initial_message = ("👋 Hi there, I'm Sarah, your friendly AI HR assistant! "
|
43 |
+
"I'll guide you through a series of interview questions to learn more about you. "
|
44 |
+
"Take your time and answer each question thoughtfully.")
|
45 |
+
|
46 |
+
def interview_step(user_input, history):
|
47 |
+
if user_input.lower() in ["exit", "quit"]:
|
48 |
+
history.append((None, "The interview has ended at your request. Thank you for your time!"))
|
49 |
+
return history, ""
|
50 |
+
|
51 |
+
question_text = questions[current_question_index[0]]
|
52 |
+
history_content = "\n".join([f"Q: {entry['question']}\nA: {entry['answer']}" for entry in conversation_history])
|
53 |
+
combined_prompt = (f"{system_prompt}\n\nPrevious conversation history:\n{history_content}\n\n"
|
54 |
+
f"Current question: {question_text}\nUser's input: {user_input}\n\n"
|
55 |
+
"Respond in a warm and conversational way, offering natural follow-ups if needed.")
|
56 |
+
|
57 |
+
messages = [
|
58 |
+
SystemMessage(content=system_prompt),
|
59 |
+
HumanMessage(content=combined_prompt)
|
60 |
+
]
|
61 |
+
|
62 |
+
response = chat.invoke(messages)
|
63 |
+
response_content = response.content.strip()
|
64 |
+
|
65 |
+
conversation_history.append({"question": question_text, "answer": user_input})
|
66 |
+
interview_data.append({"question": question_text, "answer": user_input})
|
67 |
+
history.append((user_input, None))
|
68 |
+
history.append((None, response_content))
|
69 |
+
|
70 |
+
if current_question_index[0] + 1 < len(questions):
|
71 |
+
current_question_index[0] += 1
|
72 |
+
next_question = f"Alright, let's move on. {questions[current_question_index[0]]}"
|
73 |
+
history.append((None, next_question))
|
74 |
+
return history, ""
|
75 |
+
else:
|
76 |
+
history.append((None, "That wraps up our interview. Thank you so much for your responses—it's been great learning more about you!"))
|
77 |
+
return history, ""
|
78 |
+
|
79 |
+
return interview_step, initial_message
|
80 |
+
|
81 |
+
# Gradio interface
|
82 |
+
def main():
|
83 |
+
QUESTIONS_FILE_PATH = "questions.json" # Ensure you have a questions.json file with your interview questions
|
84 |
+
|
85 |
+
try:
|
86 |
+
questions = read_questions_from_json(QUESTIONS_FILE_PATH)
|
87 |
+
interview_func, initial_message = conduct_interview(questions)
|
88 |
+
|
89 |
+
css = """
|
90 |
+
.contain { display: flex; flex-direction: column; }
|
91 |
+
.gradio-container { height: 100vh !important; }
|
92 |
+
#component-0 { height: 100%; }
|
93 |
+
.chatbot { flex-grow: 1; overflow: auto; height: 100px; }
|
94 |
+
.chatbot .wrap.svelte-1275q59.wrap.svelte-1275q59 {flex-wrap : nowrap !important}
|
95 |
+
.user > div > .message {background-color : #dcf8c6 !important}
|
96 |
+
.bot > div > .message {background-color : #f7f7f8 !important}
|
97 |
+
"""
|
98 |
+
|
99 |
+
with gr.Blocks(css=css) as demo:
|
100 |
+
gr.Markdown("""
|
101 |
+
<h1 style='text-align: center; margin-bottom: 1rem'>👋 Welcome to Your AI HR Interview Assistant</h1>
|
102 |
+
""")
|
103 |
+
|
104 |
+
start_btn = gr.Button("Start Interview", variant="primary")
|
105 |
+
|
106 |
+
gr.Markdown("""
|
107 |
+
<p style='text-align: center; margin-bottom: 1rem'>I will ask you a series of questions. Please answer honestly and thoughtfully. When you are ready, click "Start Interview" to begin.</p>
|
108 |
+
""")
|
109 |
+
|
110 |
+
chatbot = gr.Chatbot(label="Interview Chat", elem_id="chatbot", height=650)
|
111 |
+
user_input = gr.Textbox(label="Your Response", placeholder="Type your answer here...", lines=1)
|
112 |
+
|
113 |
+
with gr.Row():
|
114 |
+
submit_btn = gr.Button("Submit", variant="primary")
|
115 |
+
clear_btn = gr.Button("Clear Chat")
|
116 |
+
|
117 |
+
def start_interview():
|
118 |
+
history = []
|
119 |
+
history.append((None, initial_message))
|
120 |
+
history.append((None, "Let's begin! Here's your first question: " + questions[0]))
|
121 |
+
return history, ""
|
122 |
+
|
123 |
+
def clear_interview():
|
124 |
+
return [], ""
|
125 |
+
|
126 |
+
def interview_step(user_response, history):
|
127 |
+
return interview_func(user_response, history)
|
128 |
+
|
129 |
+
def on_enter_submit(history, user_response):
|
130 |
+
if not user_response.strip():
|
131 |
+
return history, ""
|
132 |
+
return interview_step(user_response, history)
|
133 |
+
|
134 |
+
start_btn.click(start_interview, inputs=[], outputs=[chatbot, user_input])
|
135 |
+
submit_btn.click(interview_step, inputs=[user_input, chatbot], outputs=[chatbot, user_input])
|
136 |
+
user_input.submit(on_enter_submit, inputs=[chatbot, user_input], outputs=[chatbot, user_input])
|
137 |
+
clear_btn.click(clear_interview, inputs=[], outputs=[chatbot, user_input])
|
138 |
+
|
139 |
+
demo.launch()
|
140 |
+
|
141 |
+
except Exception as e:
|
142 |
+
print(f"Error: {e}")
|
143 |
+
|
144 |
+
if __name__ == "__main__":
|
145 |
+
main()
|
grad.py
ADDED
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import json
|
3 |
+
import gradio as gr
|
4 |
+
from collections import deque
|
5 |
+
from dotenv import load_dotenv
|
6 |
+
from langchain_openai import ChatOpenAI
|
7 |
+
from langchain.schema import HumanMessage, SystemMessage
|
8 |
+
|
9 |
+
# Load environment variables
|
10 |
+
load_dotenv()
|
11 |
+
|
12 |
+
# Function to read questions from JSON
|
13 |
+
def read_questions_from_json(file_path):
|
14 |
+
if not os.path.exists(file_path):
|
15 |
+
raise FileNotFoundError(f"The file '{file_path}' does not exist.")
|
16 |
+
with open(file_path, 'r') as f:
|
17 |
+
questions_list = json.load(f)
|
18 |
+
if not questions_list:
|
19 |
+
raise ValueError("The JSON file is empty or has invalid content.")
|
20 |
+
return questions_list
|
21 |
+
|
22 |
+
# Function to handle user input and LLM's response
|
23 |
+
def handle_user_input(chat, system_prompt, conversation_history, question_text, user_input):
|
24 |
+
history_content = "\n".join([f"Q: {entry['question']}\nA: {entry['answer']}" for entry in conversation_history])
|
25 |
+
combined_prompt = (f"{system_prompt}\n\nPrevious conversation history:\n{history_content}\n\n"
|
26 |
+
f"Current question: {question_text}\nUser's input: {user_input}\n\n"
|
27 |
+
"Respond naturally to any follow-up questions or requests for clarification."
|
28 |
+
" Provide the next question or end the interview when appropriate.")
|
29 |
+
|
30 |
+
messages = [SystemMessage(content=system_prompt), HumanMessage(content=combined_prompt)]
|
31 |
+
response = chat.invoke(messages)
|
32 |
+
return response.content.strip()
|
33 |
+
|
34 |
+
# Function to conduct the interview dynamically
|
35 |
+
def conduct_interview(questions, language="English", history_limit=5):
|
36 |
+
openai_api_key = os.getenv("OPENAI_API_KEY")
|
37 |
+
if not openai_api_key:
|
38 |
+
raise RuntimeError("OpenAI API key not found. Please add it to your .env file as OPENAI_API_KEY.")
|
39 |
+
|
40 |
+
chat = ChatOpenAI(openai_api_key=openai_api_key, model="gpt-4", temperature=0.7, max_tokens=750)
|
41 |
+
conversation_history = deque(maxlen=history_limit)
|
42 |
+
system_prompt = f"You are Sarah, an empathetic HR interviewer conducting an interview in {language}."
|
43 |
+
|
44 |
+
def gradio_interview(user_input, history):
|
45 |
+
if not history:
|
46 |
+
# Initial greeting and first question
|
47 |
+
initial_message = (f"👋 Hello, I'm your AI HR assistant!\n"
|
48 |
+
f"I will ask you {len(questions)} questions.\n"
|
49 |
+
"Please answer honestly and to the best of your ability.")
|
50 |
+
history = [{"role": "assistant", "content": initial_message}]
|
51 |
+
current_question = questions[0]
|
52 |
+
history.append({"role": "assistant", "content": f"First question: {current_question}"})
|
53 |
+
return history, ""
|
54 |
+
|
55 |
+
current_question_index = (len(history) - 2) // 2 # Adjust for assistant's intro
|
56 |
+
if current_question_index < len(questions):
|
57 |
+
current_question = questions[current_question_index]
|
58 |
+
response = handle_user_input(chat, system_prompt, conversation_history, current_question, user_input)
|
59 |
+
conversation_history.append({"question": current_question, "answer": user_input})
|
60 |
+
history.append({"role": "user", "content": user_input})
|
61 |
+
history.append({"role": "assistant", "content": response})
|
62 |
+
|
63 |
+
if current_question_index + 1 < len(questions):
|
64 |
+
next_question = questions[current_question_index + 1]
|
65 |
+
history.append({"role": "assistant", "content": f"Next question: {next_question}"})
|
66 |
+
else:
|
67 |
+
history.append({"role": "assistant", "content": "Thank you for your time. This concludes the interview."})
|
68 |
+
|
69 |
+
return history, ""
|
70 |
+
|
71 |
+
return gradio_interview
|
72 |
+
|
73 |
+
# Load questions and start Gradio app
|
74 |
+
def start_hr_chatbot():
|
75 |
+
QUESTIONS_FILE_PATH = "questions.json"
|
76 |
+
try:
|
77 |
+
questions = read_questions_from_json(QUESTIONS_FILE_PATH)
|
78 |
+
except Exception as e:
|
79 |
+
print(f"Error: {e}")
|
80 |
+
return
|
81 |
+
|
82 |
+
interview_fn = conduct_interview(questions)
|
83 |
+
|
84 |
+
with gr.Blocks(css=".gradio-container { font-family: Arial, sans-serif; max-width: 700px; margin: auto; }") as demo:
|
85 |
+
gr.Markdown("## 🤖 HR Interview Chatbot")
|
86 |
+
chatbot = gr.Chatbot(label="HR Chatbot", type="messages")
|
87 |
+
user_input = gr.Textbox(label="💬 Your answer:", placeholder="Type your answer here and press Enter...", interactive=True)
|
88 |
+
start_button = gr.Button("Start Interview")
|
89 |
+
state = gr.State([])
|
90 |
+
|
91 |
+
def on_start(history):
|
92 |
+
return interview_fn("", history)
|
93 |
+
|
94 |
+
def on_submit(user_input, history):
|
95 |
+
history, new_input = interview_fn(user_input, history)
|
96 |
+
return history, ""
|
97 |
+
|
98 |
+
start_button.click(fn=on_start, inputs=[state], outputs=[chatbot, state])
|
99 |
+
user_input.submit(fn=on_submit, inputs=[user_input, state], outputs=[chatbot, state])
|
100 |
+
|
101 |
+
demo.launch(server_name="0.0.0.0", server_port=7860, debug=True)
|
102 |
+
|
103 |
+
if __name__ == "__main__":
|
104 |
+
start_hr_chatbot()
|
interview.py
ADDED
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
import os
|
3 |
+
|
4 |
+
def read_questions_from_json(file_path):
|
5 |
+
"""
|
6 |
+
Reads questions from a JSON file.
|
7 |
+
"""
|
8 |
+
if not os.path.exists(file_path):
|
9 |
+
raise FileNotFoundError(f"The file '{file_path}' does not exist.")
|
10 |
+
|
11 |
+
with open(file_path, 'r') as f:
|
12 |
+
questions = json.load(f)
|
13 |
+
|
14 |
+
if not questions:
|
15 |
+
raise ValueError("The JSON file is empty or has invalid content.")
|
16 |
+
|
17 |
+
return questions
|
18 |
+
|
19 |
+
def conduct_interview(questions):
|
20 |
+
"""
|
21 |
+
Conducts an interview by printing each question, taking input for the answer,
|
22 |
+
and storing the questions and answers in a list.
|
23 |
+
"""
|
24 |
+
interview_data = []
|
25 |
+
print("\n--- Interview Started ---\n")
|
26 |
+
|
27 |
+
for question in questions:
|
28 |
+
print(f"{question}")
|
29 |
+
answer = input("Your answer: ").strip()
|
30 |
+
interview_data.append({"question": question, "answer": answer})
|
31 |
+
|
32 |
+
print("\n--- Interview Completed ---\n")
|
33 |
+
return interview_data
|
34 |
+
|
35 |
+
def save_interview_to_file(interview_data, file_path):
|
36 |
+
"""
|
37 |
+
Saves the questions and answers to a text file.
|
38 |
+
"""
|
39 |
+
with open(file_path, 'w') as f:
|
40 |
+
for entry in interview_data:
|
41 |
+
f.write(f"Q: {entry['question']}\n")
|
42 |
+
f.write(f"A: {entry['answer']}\n\n")
|
43 |
+
|
44 |
+
print(f"Interview saved to {file_path}")
|
45 |
+
|
46 |
+
if __name__ == "__main__":
|
47 |
+
QUESTIONS_FILE_PATH = "questions.json"
|
48 |
+
INTERVIEW_FILE_PATH = "interview.txt"
|
49 |
+
|
50 |
+
try:
|
51 |
+
# Read questions from JSON file
|
52 |
+
questions = read_questions_from_json(QUESTIONS_FILE_PATH)
|
53 |
+
|
54 |
+
# Conduct the interview
|
55 |
+
interview_data = conduct_interview(questions)
|
56 |
+
|
57 |
+
# Save the interview to a text file
|
58 |
+
save_interview_to_file(interview_data, INTERVIEW_FILE_PATH)
|
59 |
+
|
60 |
+
except Exception as e:
|
61 |
+
print(f"Error: {e}")
|
interview.txt
ADDED
File without changes
|
knowledge_retrieval.py
ADDED
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import fitz # PyMuPDF for PDF handling
|
3 |
+
from langchain_community.vectorstores import FAISS
|
4 |
+
from langchain_openai import OpenAIEmbeddings
|
5 |
+
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
6 |
+
from langchain.prompts import ChatPromptTemplate, PromptTemplate
|
7 |
+
from langchain.schema import Document, StrOutputParser
|
8 |
+
from langchain.chains.combine_documents.stuff import StuffDocumentsChain
|
9 |
+
from langchain.chains import RetrievalQA
|
10 |
+
from langchain.chains.llm import LLMChain
|
11 |
+
from langchain_core.runnables import RunnablePassthrough
|
12 |
+
from prompt_instructions import get_interview_prompt_hr, get_report_prompt_hr
|
13 |
+
|
14 |
+
# Function to load documents based on file type
|
15 |
+
def load_document(file_path):
|
16 |
+
ext = os.path.splitext(file_path)[1].lower()
|
17 |
+
if ext == ".txt":
|
18 |
+
with open(file_path, "r", encoding="utf-8") as f:
|
19 |
+
text = f.read()
|
20 |
+
return [Document(page_content=text, metadata={"source": file_path})]
|
21 |
+
elif ext == ".pdf":
|
22 |
+
try:
|
23 |
+
with fitz.open(file_path) as pdf:
|
24 |
+
text = ""
|
25 |
+
for page in pdf:
|
26 |
+
text += page.get_text()
|
27 |
+
return [Document(page_content=text, metadata={"source": file_path})]
|
28 |
+
except Exception as e:
|
29 |
+
raise RuntimeError(f"Error loading PDF file: {e}")
|
30 |
+
else:
|
31 |
+
raise RuntimeError(f"Unsupported file format: {ext}")
|
32 |
+
|
33 |
+
# Function to set up knowledge retrieval
|
34 |
+
def setup_knowledge_retrieval(llm, language='english', file_path=None):
|
35 |
+
embedding_model = OpenAIEmbeddings()
|
36 |
+
|
37 |
+
if file_path:
|
38 |
+
# Load and split the document
|
39 |
+
documents = load_document(file_path)
|
40 |
+
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
|
41 |
+
texts = text_splitter.split_documents(documents)
|
42 |
+
|
43 |
+
# Create a new FAISS index from the document
|
44 |
+
faiss_index_path = "knowledge/faiss_index_hr_documents"
|
45 |
+
try:
|
46 |
+
documents_faiss_index = FAISS.from_documents(texts, embedding_model)
|
47 |
+
documents_faiss_index.save_local(faiss_index_path)
|
48 |
+
print(f"New FAISS vector store created and saved at {faiss_index_path}")
|
49 |
+
except Exception as e:
|
50 |
+
raise RuntimeError(f"Error during FAISS index creation: {e}")
|
51 |
+
else:
|
52 |
+
raise RuntimeError("No document provided for knowledge retrieval setup.")
|
53 |
+
|
54 |
+
documents_retriever = documents_faiss_index.as_retriever()
|
55 |
+
|
56 |
+
# Prompt template for the interview
|
57 |
+
interview_prompt_template = """
|
58 |
+
Use the following pieces of context to answer the question at the end.
|
59 |
+
If you don't know the answer, just say that you don't know, don't try to make up an answer.
|
60 |
+
Keep the answer as concise as possible.
|
61 |
+
{context}
|
62 |
+
Question: {question}
|
63 |
+
Helpful Answer:"""
|
64 |
+
interview_prompt = PromptTemplate.from_template(interview_prompt_template)
|
65 |
+
|
66 |
+
# Prompt template for the report
|
67 |
+
report_prompt_template = """
|
68 |
+
Use the following pieces of context to generate a report at the end.
|
69 |
+
If you don't know the answer, just say that you don't know, don't try to make up an answer.
|
70 |
+
Keep the answer as concise as possible.
|
71 |
+
{context}
|
72 |
+
Question: {question}
|
73 |
+
Helpful Answer:"""
|
74 |
+
report_prompt = PromptTemplate.from_template(report_prompt_template)
|
75 |
+
|
76 |
+
# Create RetrievalQA chains
|
77 |
+
interview_chain = RetrievalQA.from_chain_type(
|
78 |
+
llm=llm,
|
79 |
+
chain_type="stuff",
|
80 |
+
retriever=documents_retriever,
|
81 |
+
chain_type_kwargs={"prompt": interview_prompt}
|
82 |
+
)
|
83 |
+
|
84 |
+
report_chain = RetrievalQA.from_chain_type(
|
85 |
+
llm=llm,
|
86 |
+
chain_type="stuff",
|
87 |
+
retriever=documents_retriever,
|
88 |
+
chain_type_kwargs={"prompt": report_prompt}
|
89 |
+
)
|
90 |
+
|
91 |
+
return interview_chain, report_chain, documents_retriever
|
92 |
+
|
93 |
+
def get_next_response(interview_chain, message, history, question_count):
|
94 |
+
if question_count >= 5:
|
95 |
+
return "Thank you for your responses. I will now prepare a report."
|
96 |
+
|
97 |
+
if not interview_chain:
|
98 |
+
return "Error: Knowledge base not loaded. Please contact an admin."
|
99 |
+
|
100 |
+
# Generate the next question using RetrievalQA
|
101 |
+
response = interview_chain.invoke({"query": message})
|
102 |
+
next_question = response.get("result", "Could you provide more details on that?")
|
103 |
+
|
104 |
+
return next_question
|
105 |
+
|
106 |
+
def generate_report(report_chain, history, language):
|
107 |
+
combined_history = "\n".join(history)
|
108 |
+
|
109 |
+
# If report_chain is not available, return a fallback report
|
110 |
+
if not report_chain:
|
111 |
+
print("[DEBUG] Report chain not available. Generating a fallback HR report.")
|
112 |
+
fallback_report = f"""
|
113 |
+
HR Report in {language}:
|
114 |
+
Interview Summary:
|
115 |
+
{combined_history}
|
116 |
+
|
117 |
+
Assessment:
|
118 |
+
Based on the responses, the candidate's strengths, areas for improvement, and overall fit for the role have been noted. No additional knowledge-based insights due to missing vector database.
|
119 |
+
"""
|
120 |
+
return fallback_report
|
121 |
+
|
122 |
+
# Generate report using the retrieval chain
|
123 |
+
result = report_chain.invoke({"query": f"Please provide an HR report based on the interview in {language}. Interview history: {combined_history}"})
|
124 |
+
|
125 |
+
return result.get("result", "Unable to generate report due to insufficient information.")
|
126 |
+
|
127 |
+
def get_initial_question(interview_chain):
|
128 |
+
if not interview_chain:
|
129 |
+
return "Please introduce yourself and tell me a little bit about your professional background."
|
130 |
+
|
131 |
+
result = interview_chain.invoke({"query": "What should be the first question in an HR interview?"})
|
132 |
+
return result.get("result", "Could you tell me a little bit about yourself and your professional background?")
|
133 |
+
|
134 |
+
|
135 |
+
|
m6.py
ADDED
@@ -0,0 +1,245 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import json
|
3 |
+
from collections import deque
|
4 |
+
from dotenv import load_dotenv
|
5 |
+
import gradio as gr
|
6 |
+
from langchain_openai import ChatOpenAI
|
7 |
+
from langchain.schema import HumanMessage, SystemMessage
|
8 |
+
from openai import OpenAI
|
9 |
+
import tempfile
|
10 |
+
import time
|
11 |
+
|
12 |
+
# Load environment variables
|
13 |
+
load_dotenv()
|
14 |
+
|
15 |
+
# Function to read questions from JSON
|
16 |
+
def read_questions_from_json(file_path):
|
17 |
+
if not os.path.exists(file_path):
|
18 |
+
raise FileNotFoundError(f"The file '{file_path}' does not exist.")
|
19 |
+
|
20 |
+
with open(file_path, 'r') as f:
|
21 |
+
questions_list = json.load(f)
|
22 |
+
|
23 |
+
if not questions_list:
|
24 |
+
raise ValueError("The JSON file is empty or has invalid content.")
|
25 |
+
|
26 |
+
return questions_list
|
27 |
+
|
28 |
+
# Function to convert text to speech
|
29 |
+
def convert_text_to_speech(text):
|
30 |
+
start_time = time.time()
|
31 |
+
try:
|
32 |
+
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
|
33 |
+
response = client.audio.speech.create(model="tts-1", voice="alloy", input=text)
|
34 |
+
|
35 |
+
# Save the audio stream to a temporary file
|
36 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file:
|
37 |
+
for chunk in response.iter_bytes():
|
38 |
+
tmp_file.write(chunk)
|
39 |
+
temp_audio_path = tmp_file.name
|
40 |
+
|
41 |
+
print(f"DEBUG - Text-to-speech conversion time: {time.time() - start_time:.2f} seconds")
|
42 |
+
return temp_audio_path
|
43 |
+
|
44 |
+
except Exception as e:
|
45 |
+
print(f"Error during text-to-speech conversion: {e}")
|
46 |
+
return None
|
47 |
+
|
48 |
+
# Function to transcribe audio
|
49 |
+
def transcribe_audio(audio_file_path):
|
50 |
+
start_time = time.time()
|
51 |
+
try:
|
52 |
+
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
|
53 |
+
with open(audio_file_path, "rb") as audio_file:
|
54 |
+
transcription = client.audio.transcriptions.create(
|
55 |
+
model="whisper-1",
|
56 |
+
file=audio_file
|
57 |
+
)
|
58 |
+
print(f"DEBUG - Audio transcription time: {time.time() - start_time:.2f} seconds")
|
59 |
+
return transcription.text
|
60 |
+
except Exception as e:
|
61 |
+
print(f"Error during audio transcription: {e}")
|
62 |
+
return None
|
63 |
+
|
64 |
+
# Conduct interview and handle user input
|
65 |
+
def conduct_interview(questions, language="English", history_limit=5):
|
66 |
+
start_time = time.time()
|
67 |
+
openai_api_key = os.getenv("OPENAI_API_KEY")
|
68 |
+
if not openai_api_key:
|
69 |
+
raise RuntimeError("OpenAI API key not found. Please add it to your .env file as OPENAI_API_KEY.")
|
70 |
+
|
71 |
+
chat = ChatOpenAI(
|
72 |
+
openai_api_key=openai_api_key, model="gpt-4o", temperature=0.7, max_tokens=750
|
73 |
+
)
|
74 |
+
|
75 |
+
conversation_history = deque(maxlen=history_limit)
|
76 |
+
system_prompt = (f"You are Sarah, an empathetic HR interviewer conducting a technical interview in {language}. "
|
77 |
+
"Respond to user follow-up questions politely and concisely. If the user is confused, provide clear clarification.")
|
78 |
+
|
79 |
+
interview_data = []
|
80 |
+
current_question_index = [0] # Use a list to hold the index
|
81 |
+
is_interview_finished = False
|
82 |
+
|
83 |
+
initial_message = ("👋 Hi there, I'm Sarah, your friendly AI HR assistant! "
|
84 |
+
"I'll guide you through a series of interview questions to learn more about you. "
|
85 |
+
"Take your time and answer each question thoughtfully.")
|
86 |
+
final_message = "That wraps up our interview. Thank you so much for your responses—it's been great learning more about you!"
|
87 |
+
print(f"DEBUG - conduct_interview setup time: {time.time() - start_time:.2f} seconds")
|
88 |
+
|
89 |
+
def interview_step(user_input, audio_input, history):
|
90 |
+
nonlocal current_question_index
|
91 |
+
nonlocal is_interview_finished
|
92 |
+
|
93 |
+
step_start_time = time.time()
|
94 |
+
|
95 |
+
# Transcribe audio input if provided
|
96 |
+
if audio_input:
|
97 |
+
user_input = transcribe_audio(audio_input)
|
98 |
+
print("Transcription:", user_input)
|
99 |
+
|
100 |
+
if user_input.lower() in ["exit", "quit"]:
|
101 |
+
history.append({"role": "assistant", "content": "The interview has ended at your request. Thank you for your time!"})
|
102 |
+
is_interview_finished = True
|
103 |
+
return history, "", None
|
104 |
+
|
105 |
+
# If interview is finished, do nothing
|
106 |
+
if is_interview_finished:
|
107 |
+
return history, "", None
|
108 |
+
|
109 |
+
question_text = questions[current_question_index[0]]
|
110 |
+
history_content = "\n".join([f"Q: {entry['question']}\nA: {entry['answer']}" for entry in conversation_history])
|
111 |
+
combined_prompt = (f"{system_prompt}\n\nPrevious conversation history:\n{history_content}\n\n"
|
112 |
+
f"Current question: {question_text}\nUser's input: {user_input}\n\n"
|
113 |
+
"Respond in a warm and conversational way, offering natural follow-ups if needed.")
|
114 |
+
|
115 |
+
messages = [
|
116 |
+
SystemMessage(content=system_prompt),
|
117 |
+
HumanMessage(content=combined_prompt)
|
118 |
+
]
|
119 |
+
|
120 |
+
chat_start_time = time.time()
|
121 |
+
response = chat.invoke(messages)
|
122 |
+
print(f"DEBUG - Chat response time: {time.time() - chat_start_time:.2f} seconds")
|
123 |
+
response_content = response.content.strip()
|
124 |
+
|
125 |
+
# Convert response to speech
|
126 |
+
audio_file_path = convert_text_to_speech(response_content)
|
127 |
+
|
128 |
+
conversation_history.append({"question": question_text, "answer": user_input})
|
129 |
+
interview_data.append({"question": question_text, "answer": user_input})
|
130 |
+
|
131 |
+
# Use the correct format for messages
|
132 |
+
history.append({"role": "user", "content": user_input})
|
133 |
+
history.append({"role": "assistant", "content": response_content})
|
134 |
+
|
135 |
+
if current_question_index[0] + 1 < len(questions):
|
136 |
+
current_question_index[0] += 1
|
137 |
+
next_question = f"Alright, let's move on. {questions[current_question_index[0]]}"
|
138 |
+
next_question_audio_path = convert_text_to_speech(next_question)
|
139 |
+
history.append({"role": "assistant", "content": next_question})
|
140 |
+
print(f"DEBUG - Interview step time: {time.time() - step_start_time:.2f} seconds")
|
141 |
+
return history, "", next_question_audio_path
|
142 |
+
|
143 |
+
else:
|
144 |
+
# Convert final message to speech and play it
|
145 |
+
final_message_audio_path = convert_text_to_speech(final_message)
|
146 |
+
history.append({"role": "assistant", "content": final_message})
|
147 |
+
|
148 |
+
# Convert the last question to speech
|
149 |
+
last_question_audio_path = convert_text_to_speech(questions[current_question_index[0]])
|
150 |
+
is_interview_finished = True
|
151 |
+
print(f"DEBUG - Interview step time: {time.time() - step_start_time:.2f} seconds")
|
152 |
+
return history, "", last_question_audio_path
|
153 |
+
|
154 |
+
return interview_step, initial_message, final_message
|
155 |
+
|
156 |
+
# Gradio interface
|
157 |
+
def main():
|
158 |
+
QUESTIONS_FILE_PATH = "questions.json" # Ensure you have a questions.json file with your interview questions
|
159 |
+
|
160 |
+
try:
|
161 |
+
questions = read_questions_from_json(QUESTIONS_FILE_PATH)
|
162 |
+
interview_func, initial_message, final_message = conduct_interview(questions)
|
163 |
+
|
164 |
+
css = """
|
165 |
+
.contain { display: flex; flex-direction: column; }
|
166 |
+
.gradio-container { height: 100vh !important; }
|
167 |
+
#component-0 { height: 100%; }
|
168 |
+
.chatbot { flex-grow: 1; overflow: auto; height: 100px; }
|
169 |
+
.chatbot .wrap.svelte-1275q59.wrap.svelte-1275q59 {flex-wrap : nowrap !important}
|
170 |
+
.user > div > .message {background-color : #dcf8c6 !important}
|
171 |
+
.bot > div > .message {background-color : #f7f7f8 !important}
|
172 |
+
"""
|
173 |
+
|
174 |
+
with gr.Blocks(css=css) as demo:
|
175 |
+
gr.Markdown("""
|
176 |
+
<h1 style='text-align: center; margin-bottom: 1rem'>👋 Welcome to Your AI HR Interview Assistant</h1>
|
177 |
+
""")
|
178 |
+
|
179 |
+
start_btn = gr.Button("Start Interview", variant="primary")
|
180 |
+
|
181 |
+
gr.Markdown("""
|
182 |
+
<p style='text-align: center; margin-bottom: 1rem'>I will ask you a series of questions. Please answer honestly and thoughtfully. When you are ready, click "Start Interview" to begin.</p>
|
183 |
+
""")
|
184 |
+
|
185 |
+
chatbot = gr.Chatbot(label="Interview Chat", elem_id="chatbot", height=650, type='messages')
|
186 |
+
audio_input = gr.Audio(sources=["microphone"], type="filepath", label="Record Your Answer")
|
187 |
+
user_input = gr.Textbox(label="Your Response", placeholder="Type your answer here or use the microphone...", lines=1)
|
188 |
+
|
189 |
+
audio_output = gr.Audio(label="Response Audio", autoplay=True)
|
190 |
+
|
191 |
+
with gr.Row():
|
192 |
+
submit_btn = gr.Button("Submit", variant="primary")
|
193 |
+
clear_btn = gr.Button("Clear Chat")
|
194 |
+
|
195 |
+
def start_interview():
|
196 |
+
history = []
|
197 |
+
|
198 |
+
# Convert and play initial message
|
199 |
+
start_time = time.time()
|
200 |
+
initial_audio_path = convert_text_to_speech(initial_message)
|
201 |
+
|
202 |
+
# Combine initial message and first question
|
203 |
+
first_question = "Let's begin! Here's your first question: " + questions[0]
|
204 |
+
combined_message = initial_message + " " + first_question
|
205 |
+
|
206 |
+
# Convert combined message to speech
|
207 |
+
combined_audio_path = convert_text_to_speech(combined_message)
|
208 |
+
|
209 |
+
history.append({"role": "assistant", "content": combined_message})
|
210 |
+
|
211 |
+
print(f"DEBUG - Initial message audio time: {time.time() - start_time:.2f} seconds")
|
212 |
+
|
213 |
+
return history, "", combined_audio_path
|
214 |
+
|
215 |
+
def clear_interview():
|
216 |
+
# Reset the interview state
|
217 |
+
interview_func, initial_message, final_message = conduct_interview(questions)
|
218 |
+
|
219 |
+
return [], "", None
|
220 |
+
|
221 |
+
def interview_step_wrapper(user_response, audio_response, history):
|
222 |
+
history, _, audio_path = interview_func(user_response, audio_response, history)
|
223 |
+
time.sleep(0.1) # Reduced delay
|
224 |
+
return history, "", audio_path
|
225 |
+
|
226 |
+
def on_enter_submit(history, user_response):
|
227 |
+
if not user_response.strip():
|
228 |
+
return history, "", None
|
229 |
+
history, _, audio_path = interview_step_wrapper(user_response, None, history)
|
230 |
+
time.sleep(0.1) # Reduced delay
|
231 |
+
return history, "", audio_path
|
232 |
+
|
233 |
+
audio_input.stop_recording(interview_step_wrapper, inputs=[user_input, audio_input, chatbot], outputs=[chatbot, user_input, audio_output])
|
234 |
+
start_btn.click(start_interview, inputs=[], outputs=[chatbot, user_input, audio_output])
|
235 |
+
submit_btn.click(interview_step_wrapper, inputs=[user_input, audio_input, chatbot], outputs=[chatbot, user_input, audio_output])
|
236 |
+
user_input.submit(on_enter_submit, inputs=[chatbot, user_input], outputs=[chatbot, user_input, audio_output])
|
237 |
+
clear_btn.click(clear_interview, inputs=[], outputs=[chatbot, user_input, audio_output])
|
238 |
+
|
239 |
+
demo.launch()
|
240 |
+
|
241 |
+
except Exception as e:
|
242 |
+
print(f"Error: {e}")
|
243 |
+
|
244 |
+
if __name__ == "__main__":
|
245 |
+
main()
|
professional_machine_learning_engineer_exam_guide_english.pdf
ADDED
Binary file (217 kB). View file
|
|
prompt_instructions.py
ADDED
@@ -0,0 +1,181 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from datetime import datetime
|
2 |
+
|
3 |
+
current_datetime = datetime.now()
|
4 |
+
current_date = current_datetime.strftime("%Y-%m-%d")
|
5 |
+
|
6 |
+
# Initial Interview Messages
|
7 |
+
def get_interview_initial_message_hr(n_of_questions):
|
8 |
+
return f"""Hello, I'm an AI HR assistant. I'll be conducting this interview.
|
9 |
+
I will ask you about {n_of_questions} questions.
|
10 |
+
Please answer truthfully and to the best of your ability.
|
11 |
+
Could you please tell me which language you prefer to use for this interview?"""
|
12 |
+
|
13 |
+
def get_interview_initial_message_sarah(n_of_questions):
|
14 |
+
return f"""Hello, I'm Sarah, an AI assistant for technical interviews.
|
15 |
+
I will guide you through the process and ask you around {n_of_questions} questions.
|
16 |
+
Please feel free to share as much or as little as you're comfortable with."""
|
17 |
+
|
18 |
+
def get_interview_initial_message_aaron(n_of_questions):
|
19 |
+
return f"""Hello, I'm Aaron, an AI interviewer for behavioral and leadership assessments.
|
20 |
+
I will be asking you approximately {n_of_questions} questions. Be concise and direct in your responses.
|
21 |
+
Let's begin!"""
|
22 |
+
|
23 |
+
# HR Interview Prompts
|
24 |
+
def get_interview_prompt_hr(language, n_of_questions):
|
25 |
+
return f"""You are an AI HR interviewer, conducting an interview in {language}.
|
26 |
+
Use the following context and interview history to guide your response:
|
27 |
+
|
28 |
+
Context from knowledge base: {{context}}
|
29 |
+
|
30 |
+
Previous interview history:
|
31 |
+
{{history}}
|
32 |
+
|
33 |
+
Current question number: {{question_number}}/{n_of_questions}
|
34 |
+
|
35 |
+
Respond to the candidate's input briefly and directly in {language}.
|
36 |
+
Ask specific, detailed questions relevant to the job and the candidate's experience.
|
37 |
+
Remember all the previous answers given by the candidate.
|
38 |
+
If the candidate asks about a previous question, answer like an HR professional and then continue with the next question.
|
39 |
+
Keep in mind that you have a total of {n_of_questions} questions.
|
40 |
+
After {n_of_questions} interactions, indicate that you will prepare a report based on the gathered information and the provided document.
|
41 |
+
"""
|
42 |
+
|
43 |
+
def get_interview_prompt_sarah_v3(language, index, n_of_questions):
|
44 |
+
return f"""You are Sarah, an empathic and compassionate HR interviewer conducting an interview in {language}.
|
45 |
+
Use the following context and interview history to guide your response:
|
46 |
+
|
47 |
+
Previous interview history:
|
48 |
+
{{history}}
|
49 |
+
|
50 |
+
Current question number: {index + 1}/{n_of_questions}
|
51 |
+
|
52 |
+
Respond directly in {language}. Ask a specific, professional HR-related question.
|
53 |
+
You must remember all the previous answers given by the candidate, and use this information if necessary.
|
54 |
+
Keep the tone professional but approachable.
|
55 |
+
Here's your question: {{question}}
|
56 |
+
"""
|
57 |
+
|
58 |
+
def get_interview_prompt_aaron(language, n_of_questions):
|
59 |
+
return f"""You are Aaron, a direct, results-oriented interviewer conducting a professional interview in {language}.
|
60 |
+
Use the following context and interview history to guide your response:
|
61 |
+
|
62 |
+
Previous interview history:
|
63 |
+
{{history}}
|
64 |
+
|
65 |
+
Current question number: {{question_number}}/{n_of_questions}
|
66 |
+
|
67 |
+
Respond directly in {language}. Ask a precise, results-focused question that helps evaluate the candidate's suitability for the role.
|
68 |
+
Remember all the previous answers given by the candidate.
|
69 |
+
Keep the tone professional and efficient.
|
70 |
+
"""
|
71 |
+
|
72 |
+
# Default HR Questions for Non-Technical Interviews
|
73 |
+
def get_default_hr_questions(index):
|
74 |
+
default_questions = [
|
75 |
+
"Can you please introduce yourself and share a bit about your professional background?",
|
76 |
+
"What are your career goals for the next few years?",
|
77 |
+
"Why did you apply for this position, and what excites you about this role?",
|
78 |
+
"Can you describe a challenging situation you’ve faced at work and how you handled it?",
|
79 |
+
"How do you prioritize tasks when you have multiple deadlines to meet?",
|
80 |
+
"Can you provide an example of a time when you worked in a team to achieve a common goal?",
|
81 |
+
"What is your preferred style of communication when working with your team or manager?",
|
82 |
+
"How do you handle constructive feedback and what’s a time you’ve grown from it?",
|
83 |
+
"What do you consider your greatest strengths and areas for improvement?",
|
84 |
+
"Is there anything you'd like to ask us or share that wasn’t covered in the interview?"
|
85 |
+
]
|
86 |
+
if 0 <= index - 1 < len(default_questions):
|
87 |
+
return default_questions[index - 1]
|
88 |
+
return "That's all for now. Thank you for your time!"
|
89 |
+
|
90 |
+
# Report Prompts
|
91 |
+
def get_report_prompt_hr(language):
|
92 |
+
return f"""You are an HR professional preparing a report in {language}.
|
93 |
+
Use the following context and interview history to create your report:
|
94 |
+
|
95 |
+
Context from knowledge base: {{context}}
|
96 |
+
|
97 |
+
Complete interview history:
|
98 |
+
{{history}}
|
99 |
+
|
100 |
+
Prepare a brief report in {language} based strictly on the information gathered during the interview and the provided document.
|
101 |
+
Date: {current_date}
|
102 |
+
|
103 |
+
Report Structure:
|
104 |
+
|
105 |
+
Candidate Overview:
|
106 |
+
- Name (if provided)
|
107 |
+
- Position applied for (if discernible from context)
|
108 |
+
|
109 |
+
Assessment Summary:
|
110 |
+
- Key strengths based on the interview
|
111 |
+
- Areas of concern or further development
|
112 |
+
- Overall suitability for the role based on responses and provided document
|
113 |
+
|
114 |
+
Candidate's Experience and Skills:
|
115 |
+
- Relevant experience highlighted by the candidate
|
116 |
+
- Skills demonstrated during the interview
|
117 |
+
- Alignment with job requirements (based on the provided document)
|
118 |
+
|
119 |
+
Candidate's Responses:
|
120 |
+
- Communication skills
|
121 |
+
- Problem-solving abilities
|
122 |
+
- Behavioral traits observed
|
123 |
+
|
124 |
+
Recommendations:
|
125 |
+
- Next steps in the hiring process (e.g., further interviews, assessments)
|
126 |
+
- Any specific training or development if the candidate were to be hired
|
127 |
+
|
128 |
+
Concluding Remarks:
|
129 |
+
- Overall impression of the candidate
|
130 |
+
- Potential fit within the company culture
|
131 |
+
|
132 |
+
Ensure all sections are concise, focused, and evidence-based.
|
133 |
+
Avoid making assumptions and base any conclusions on the facts derived from the candidate's interview and the provided document.
|
134 |
+
"""
|
135 |
+
|
136 |
+
def get_report_prompt(language):
|
137 |
+
return f"""You are a technical interviewer preparing a report in {language}.
|
138 |
+
Use the following context and interview history to create your report:
|
139 |
+
|
140 |
+
Complete interview history:
|
141 |
+
{{history}}
|
142 |
+
|
143 |
+
Prepare a concise technical report based on the gathered information, including:
|
144 |
+
- Summary of the candidate’s technical knowledge
|
145 |
+
- Strengths and areas of improvement
|
146 |
+
- Recommendations for next steps in the hiring process
|
147 |
+
Date: {current_date}
|
148 |
+
|
149 |
+
Keep the report objective, fact-based, and focused on technical evaluation.
|
150 |
+
"""
|
151 |
+
|
152 |
+
|
153 |
+
# prompt_instructions.py
|
154 |
+
|
155 |
+
# HR Interview Prompts
|
156 |
+
def get_interview_prompt_technical(language, n_of_questions, question):
|
157 |
+
return f"""You are an AI Technical Interviewer conducting an interview in {language}.
|
158 |
+
Please follow these guidelines:
|
159 |
+
|
160 |
+
Current question number: {{question_number}}/{n_of_questions}
|
161 |
+
|
162 |
+
Respond to the candidate's input briefly and directly in {language}.
|
163 |
+
Pose the following technical question to the candidate:
|
164 |
+
{question}
|
165 |
+
"""
|
166 |
+
|
167 |
+
|
168 |
+
def get_interview_initial_message_hr(n_of_questions):
|
169 |
+
return f"""Hello, I'm your AI assistant. I'll be conducting this interview.
|
170 |
+
I will ask you {n_of_questions} questions to learn more about you.
|
171 |
+
Take your time and answer each question thoughtfully."""
|
172 |
+
|
173 |
+
|
174 |
+
# Example usage for testing:
|
175 |
+
if __name__ == "__main__":
|
176 |
+
print(get_interview_initial_message_hr(5))
|
177 |
+
print(get_default_hr_questions(1))
|
178 |
+
|
179 |
+
|
180 |
+
|
181 |
+
|
questions.py
ADDED
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import json
|
3 |
+
from dotenv import load_dotenv
|
4 |
+
import fitz # PyMuPDF
|
5 |
+
from langchain_openai import ChatOpenAI # Correct import from langchain-openai
|
6 |
+
from langchain.schema import HumanMessage, SystemMessage # For creating structured chat messages
|
7 |
+
|
8 |
+
QUESTIONS_PATH = "questions.json"
|
9 |
+
|
10 |
+
# Load environment variables
|
11 |
+
load_dotenv()
|
12 |
+
|
13 |
+
def split_text_into_chunks(text: str, chunk_size: int) -> list:
|
14 |
+
"""
|
15 |
+
Splits the text into chunks of a specified maximum size.
|
16 |
+
"""
|
17 |
+
# Trim the text to remove leading/trailing whitespace and reduce multiple spaces to a single space
|
18 |
+
cleaned_text = " ".join(text.split())
|
19 |
+
words = cleaned_text.split(" ")
|
20 |
+
|
21 |
+
chunks = []
|
22 |
+
current_chunk = []
|
23 |
+
current_length = 0
|
24 |
+
|
25 |
+
for word in words:
|
26 |
+
if current_length + len(word) + 1 > chunk_size:
|
27 |
+
chunks.append(" ".join(current_chunk))
|
28 |
+
current_chunk = [word]
|
29 |
+
current_length = len(word)
|
30 |
+
else:
|
31 |
+
current_chunk.append(word)
|
32 |
+
current_length += len(word) + 1
|
33 |
+
|
34 |
+
if current_chunk:
|
35 |
+
chunks.append(" ".join(current_chunk))
|
36 |
+
|
37 |
+
return chunks
|
38 |
+
|
39 |
+
|
40 |
+
def distribute_questions_across_chunks(n_chunks: int, n_questions: int) -> list:
|
41 |
+
"""
|
42 |
+
Distributes a specified number of questions across a specified number of chunks.
|
43 |
+
"""
|
44 |
+
questions_per_chunk = [1] * min(n_chunks, n_questions)
|
45 |
+
remaining_questions = n_questions - len(questions_per_chunk)
|
46 |
+
|
47 |
+
if remaining_questions > 0:
|
48 |
+
for i in range(len(questions_per_chunk)):
|
49 |
+
if remaining_questions == 0:
|
50 |
+
break
|
51 |
+
questions_per_chunk[i] += 1
|
52 |
+
remaining_questions -= 1
|
53 |
+
|
54 |
+
while len(questions_per_chunk) < n_chunks:
|
55 |
+
questions_per_chunk.append(0)
|
56 |
+
|
57 |
+
return questions_per_chunk
|
58 |
+
|
59 |
+
|
60 |
+
def extract_text_from_pdf(pdf_path):
|
61 |
+
text = ""
|
62 |
+
try:
|
63 |
+
print(f"[DEBUG] Opening PDF: {pdf_path}")
|
64 |
+
with fitz.open(pdf_path) as pdf:
|
65 |
+
print(f"[DEBUG] Extracting text from PDF: {pdf_path}")
|
66 |
+
for page in pdf:
|
67 |
+
text += page.get_text()
|
68 |
+
except Exception as e:
|
69 |
+
print(f"Error reading PDF: {e}")
|
70 |
+
raise RuntimeError("Unable to extract text from PDF.")
|
71 |
+
return text
|
72 |
+
|
73 |
+
|
74 |
+
def generate_questions_from_text(text, n_questions=5):
|
75 |
+
openai_api_key = os.getenv("OPENAI_API_KEY")
|
76 |
+
|
77 |
+
if not openai_api_key:
|
78 |
+
raise RuntimeError(
|
79 |
+
"OpenAI API key not found. Please add it to your .env file as OPENAI_API_KEY."
|
80 |
+
)
|
81 |
+
|
82 |
+
chat = ChatOpenAI(
|
83 |
+
openai_api_key=openai_api_key, model="gpt-4", temperature=0.7, max_tokens=750
|
84 |
+
)
|
85 |
+
|
86 |
+
messages = [
|
87 |
+
SystemMessage(
|
88 |
+
content="You are an expert interviewer who generates concise technical interview questions. Do not enumerate the questions. Answer only with questions."
|
89 |
+
),
|
90 |
+
HumanMessage(
|
91 |
+
content=f"Based on the following content, generate {n_questions} technical interview questions:\n{text}"
|
92 |
+
),
|
93 |
+
]
|
94 |
+
|
95 |
+
try:
|
96 |
+
print(f"[DEBUG] Sending request to OpenAI with {n_questions} questions.")
|
97 |
+
response = chat.invoke(messages)
|
98 |
+
questions = response.content.strip().split("\n\n")
|
99 |
+
questions = [q.strip() for q in questions if q.strip()]
|
100 |
+
except Exception as e:
|
101 |
+
print(f"[ERROR] Failed to generate questions: {e}")
|
102 |
+
questions = ["An error occurred while generating questions."]
|
103 |
+
|
104 |
+
return questions
|
105 |
+
|
106 |
+
|
107 |
+
def save_questions(questions):
|
108 |
+
with open(QUESTIONS_PATH, "w") as f:
|
109 |
+
json.dump(questions, f, indent=4)
|
110 |
+
|
111 |
+
|
112 |
+
def generate_and_save_questions_from_pdf(pdf_path, total_questions=5):
|
113 |
+
print(f"[INFO] Generating questions from PDF: {pdf_path}")
|
114 |
+
pdf_text = extract_text_from_pdf(pdf_path)
|
115 |
+
|
116 |
+
if not pdf_text.strip():
|
117 |
+
raise RuntimeError("The PDF content is empty or could not be read.")
|
118 |
+
|
119 |
+
chunk_size = 2000
|
120 |
+
chunks = split_text_into_chunks(pdf_text, chunk_size)
|
121 |
+
n_chunks = len(chunks)
|
122 |
+
|
123 |
+
questions_distribution = distribute_questions_across_chunks(n_chunks, total_questions)
|
124 |
+
combined_questions = []
|
125 |
+
|
126 |
+
for i, (chunk, n_questions) in enumerate(zip(chunks, questions_distribution)):
|
127 |
+
print(f"[DEBUG] Processing chunk {i + 1} of {n_chunks}")
|
128 |
+
if n_questions > 0:
|
129 |
+
questions = generate_questions_from_text(chunk, n_questions=n_questions)
|
130 |
+
combined_questions.extend(questions)
|
131 |
+
|
132 |
+
print(f"[INFO] Total questions generated: {len(combined_questions)}")
|
133 |
+
save_questions(combined_questions)
|
134 |
+
print(f"[INFO] Questions saved to {QUESTIONS_PATH}")
|
135 |
+
return combined_questions
|
136 |
+
|
137 |
+
|
138 |
+
if __name__ == "__main__":
|
139 |
+
pdf_path = "professional_machine_learning_engineer_exam_guide_english.pdf"
|
140 |
+
|
141 |
+
try:
|
142 |
+
generated_questions = generate_and_save_questions_from_pdf(
|
143 |
+
pdf_path, total_questions=5
|
144 |
+
)
|
145 |
+
print(f"Generated Questions:\n{json.dumps(generated_questions, indent=2)}")
|
146 |
+
except Exception as e:
|
147 |
+
print(f"Failed to generate questions: {e}")
|
questionsgr.py
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
from questions import generate_and_save_questions_from_pdf
|
3 |
+
|
4 |
+
def generate_questions(pdf_file, num_questions):
|
5 |
+
"""
|
6 |
+
Generates questions from a PDF file using the questions.py script.
|
7 |
+
|
8 |
+
Args:
|
9 |
+
pdf_file: The PDF file to generate questions from.
|
10 |
+
num_questions: The number of questions to generate.
|
11 |
+
|
12 |
+
Returns:
|
13 |
+
A string indicating success or failure, and a list of generated questions.
|
14 |
+
"""
|
15 |
+
try:
|
16 |
+
questions = generate_and_save_questions_from_pdf(pdf_file.name, total_questions=int(num_questions))
|
17 |
+
return f"✅ {len(questions)} questions generated and saved.", questions
|
18 |
+
except Exception as e:
|
19 |
+
return f"❌ Error: {e}", None
|
20 |
+
|
21 |
+
|
22 |
+
with gr.Blocks() as demo:
|
23 |
+
gr.Markdown("## 📄 PDF Question Generator")
|
24 |
+
with gr.Row():
|
25 |
+
pdf_input = gr.File(label="Upload PDF File", type="filepath") # Changed type to "filepath"
|
26 |
+
num_questions_input = gr.Number(label="Number of Questions", value=5)
|
27 |
+
generate_button = gr.Button("Generate Questions")
|
28 |
+
output_text = gr.Textbox(label="Output")
|
29 |
+
question_output = gr.JSON(label="Generated Questions")
|
30 |
+
|
31 |
+
generate_button.click(
|
32 |
+
generate_questions,
|
33 |
+
inputs=[pdf_input, num_questions_input],
|
34 |
+
outputs=[output_text, question_output]
|
35 |
+
)
|
36 |
+
|
37 |
+
if __name__ == "__main__":
|
38 |
+
demo.launch()
|
requirements.txt
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
python-dotenv==1.0.1
|
2 |
+
pandas==2.1.4
|
3 |
+
langchain==0.2.6
|
4 |
+
langchain-openai==0.1.14
|
5 |
+
langchain-core==0.2.11
|
6 |
+
langchain-ibm==0.1.8
|
7 |
+
langchain-community==0.2.6
|
8 |
+
ibm-watson-machine-learning==1.0.359
|
9 |
+
ipykernel
|
10 |
+
notebook
|
11 |
+
urllib3
|
12 |
+
requests==2.32.0
|
13 |
+
gradio
|
14 |
+
PyPDF2
|
15 |
+
python-docx
|
16 |
+
reportlab
|
17 |
+
openai
|
18 |
+
faiss-cpu
|
19 |
+
cryptography
|
20 |
+
pymysql
|
21 |
+
scikit-learn
|
22 |
+
pymupdf
|
23 |
+
|
requirements_dev.txt
ADDED
@@ -0,0 +1,178 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
aiofiles==23.2.1
|
2 |
+
aiohappyeyeballs==2.4.4
|
3 |
+
aiohttp==3.11.11
|
4 |
+
aiosignal==1.3.2
|
5 |
+
annotated-types==0.7.0
|
6 |
+
anyio==4.7.0
|
7 |
+
argon2-cffi==23.1.0
|
8 |
+
argon2-cffi-bindings==21.2.0
|
9 |
+
arrow==1.3.0
|
10 |
+
asttokens==2.4.1
|
11 |
+
async-lru==2.0.4
|
12 |
+
attrs==24.3.0
|
13 |
+
babel==2.16.0
|
14 |
+
beautifulsoup4==4.12.3
|
15 |
+
bleach==6.2.0
|
16 |
+
certifi==2024.12.14
|
17 |
+
cffi==1.17.1
|
18 |
+
chardet==5.2.0
|
19 |
+
charset-normalizer==3.4.1
|
20 |
+
click==8.1.8
|
21 |
+
colorama==0.4.6
|
22 |
+
comm==0.2.2
|
23 |
+
cryptography==44.0.0
|
24 |
+
dataclasses-json==0.6.7
|
25 |
+
debugpy==1.8.1
|
26 |
+
decorator==5.1.1
|
27 |
+
defusedxml==0.7.1
|
28 |
+
distro==1.9.0
|
29 |
+
executing==2.0.1
|
30 |
+
faiss-cpu==1.9.0.post1
|
31 |
+
fastapi==0.115.6
|
32 |
+
fastjsonschema==2.21.1
|
33 |
+
ffmpy==0.5.0
|
34 |
+
filelock==3.16.1
|
35 |
+
fqdn==1.5.1
|
36 |
+
frozenlist==1.5.0
|
37 |
+
fsspec==2024.12.0
|
38 |
+
gradio==5.9.1
|
39 |
+
gradio_client==1.5.2
|
40 |
+
greenlet==3.1.1
|
41 |
+
h11==0.14.0
|
42 |
+
httpcore==1.0.7
|
43 |
+
httpx==0.28.1
|
44 |
+
huggingface-hub==0.27.0
|
45 |
+
ibm-cos-sdk==2.13.6
|
46 |
+
ibm-cos-sdk-core==2.13.6
|
47 |
+
ibm-cos-sdk-s3transfer==2.13.6
|
48 |
+
ibm_watson_machine_learning==1.0.359
|
49 |
+
ibm_watsonx_ai==1.1.26
|
50 |
+
idna==3.10
|
51 |
+
importlib_metadata==8.5.0
|
52 |
+
ipykernel==6.29.4
|
53 |
+
ipython==8.25.0
|
54 |
+
isoduration==20.11.0
|
55 |
+
jedi==0.19.1
|
56 |
+
Jinja2==3.1.5
|
57 |
+
jiter==0.8.2
|
58 |
+
jmespath==1.0.1
|
59 |
+
joblib==1.4.2
|
60 |
+
json5==0.10.0
|
61 |
+
jsonpatch==1.33
|
62 |
+
jsonpointer==3.0.0
|
63 |
+
jsonschema==4.23.0
|
64 |
+
jsonschema-specifications==2024.10.1
|
65 |
+
jupyter-events==0.11.0
|
66 |
+
jupyter-lsp==2.2.5
|
67 |
+
jupyter_client==8.6.2
|
68 |
+
jupyter_core==5.7.2
|
69 |
+
jupyter_server==2.15.0
|
70 |
+
jupyter_server_terminals==0.5.3
|
71 |
+
jupyterlab==4.3.4
|
72 |
+
jupyterlab_pygments==0.3.0
|
73 |
+
jupyterlab_server==2.27.3
|
74 |
+
langchain==0.2.6
|
75 |
+
langchain-community==0.2.6
|
76 |
+
langchain-core==0.2.11
|
77 |
+
langchain-ibm==0.1.8
|
78 |
+
langchain-openai==0.1.14
|
79 |
+
langchain-text-splitters==0.2.2
|
80 |
+
langsmith==0.1.147
|
81 |
+
lomond==0.3.3
|
82 |
+
lxml==5.3.0
|
83 |
+
markdown-it-py==3.0.0
|
84 |
+
MarkupSafe==2.1.5
|
85 |
+
marshmallow==3.23.3
|
86 |
+
matplotlib-inline==0.1.7
|
87 |
+
mdurl==0.1.2
|
88 |
+
mistune==3.1.0
|
89 |
+
multidict==6.1.0
|
90 |
+
mypy-extensions==1.0.0
|
91 |
+
nbclient==0.10.2
|
92 |
+
nbconvert==7.16.5
|
93 |
+
nbformat==5.10.4
|
94 |
+
nest-asyncio==1.6.0
|
95 |
+
notebook==7.3.2
|
96 |
+
notebook_shim==0.2.4
|
97 |
+
numpy==1.26.4
|
98 |
+
openai==1.59.2
|
99 |
+
orjson==3.10.13
|
100 |
+
overrides==7.7.0
|
101 |
+
packaging==24.1
|
102 |
+
pandas==2.1.4
|
103 |
+
pandocfilters==1.5.1
|
104 |
+
parso==0.8.4
|
105 |
+
pillow==11.1.0
|
106 |
+
platformdirs==4.2.2
|
107 |
+
prometheus_client==0.21.1
|
108 |
+
prompt_toolkit==3.0.47
|
109 |
+
propcache==0.2.1
|
110 |
+
psutil==6.0.0
|
111 |
+
pure-eval==0.2.2
|
112 |
+
pycparser==2.22
|
113 |
+
pydantic==2.10.4
|
114 |
+
pydantic_core==2.27.2
|
115 |
+
pydub==0.25.1
|
116 |
+
Pygments==2.18.0
|
117 |
+
PyMuPDF==1.25.1
|
118 |
+
PyMySQL==1.1.1
|
119 |
+
PyPDF2==3.0.1
|
120 |
+
python-dateutil==2.9.0.post0
|
121 |
+
python-docx==1.1.2
|
122 |
+
python-dotenv==1.0.1
|
123 |
+
python-json-logger==3.2.1
|
124 |
+
python-multipart==0.0.20
|
125 |
+
pytz==2024.2
|
126 |
+
pywin32==306
|
127 |
+
pywinpty==2.0.14
|
128 |
+
PyYAML==6.0.2
|
129 |
+
pyzmq==26.0.3
|
130 |
+
referencing==0.35.1
|
131 |
+
regex==2024.11.6
|
132 |
+
reportlab==4.2.5
|
133 |
+
requests==2.32.0
|
134 |
+
requests-toolbelt==1.0.0
|
135 |
+
rfc3339-validator==0.1.4
|
136 |
+
rfc3986-validator==0.1.1
|
137 |
+
rich==13.9.4
|
138 |
+
rpds-py==0.22.3
|
139 |
+
ruff==0.8.5
|
140 |
+
safehttpx==0.1.6
|
141 |
+
scikit-learn==1.6.0
|
142 |
+
scipy==1.15.0
|
143 |
+
semantic-version==2.10.0
|
144 |
+
Send2Trash==1.8.3
|
145 |
+
setuptools==75.1.0
|
146 |
+
shellingham==1.5.4
|
147 |
+
six==1.16.0
|
148 |
+
sniffio==1.3.1
|
149 |
+
soupsieve==2.6
|
150 |
+
SQLAlchemy==2.0.36
|
151 |
+
stack-data==0.6.3
|
152 |
+
starlette==0.41.3
|
153 |
+
tabulate==0.9.0
|
154 |
+
tenacity==8.5.0
|
155 |
+
terminado==0.18.1
|
156 |
+
threadpoolctl==3.5.0
|
157 |
+
tiktoken==0.8.0
|
158 |
+
tinycss2==1.4.0
|
159 |
+
tomlkit==0.13.2
|
160 |
+
tornado==6.4.1
|
161 |
+
tqdm==4.67.1
|
162 |
+
traitlets==5.14.3
|
163 |
+
typer==0.15.1
|
164 |
+
types-python-dateutil==2.9.0.20241206
|
165 |
+
typing-inspect==0.9.0
|
166 |
+
typing_extensions==4.12.2
|
167 |
+
tzdata==2024.2
|
168 |
+
uri-template==1.3.0
|
169 |
+
urllib3==2.3.0
|
170 |
+
uvicorn==0.34.0
|
171 |
+
wcwidth==0.2.13
|
172 |
+
webcolors==24.11.1
|
173 |
+
webencodings==0.5.1
|
174 |
+
websocket-client==1.8.0
|
175 |
+
websockets==14.1
|
176 |
+
wheel==0.44.0
|
177 |
+
yarl==1.18.3
|
178 |
+
zipp==3.21.0
|
response.py
ADDED
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
def bot_response(history):
|
2 |
+
if not interview_state.interview_history:
|
3 |
+
|
4 |
+
reset_interview_action(interview_state.selected_interviewer)
|
5 |
+
|
6 |
+
if interview_state.interview_history[-1]["role"] == "user":
|
7 |
+
interview_state.question_count += 1
|
8 |
+
|
9 |
+
|
10 |
+
voice = interview_state.get_voice_setting()
|
11 |
+
|
12 |
+
if interview_state.question_count > interview_state.n_of_questions:
|
13 |
+
response = "That's all for now. Thank you for your time!"
|
14 |
+
interview_state.interview_finished = True
|
15 |
+
|
16 |
+
else:
|
17 |
+
# Select prompts based on interview type
|
18 |
+
if interview_state.interview_type == "hr":
|
19 |
+
if not interview_state.knowledge_retrieval_setup:
|
20 |
+
response = get_default_hr_questions(
|
21 |
+
interview_state.question_count
|
22 |
+
)
|
23 |
+
else:
|
24 |
+
if interview_state.question_count == 1:
|
25 |
+
response = get_initial_question(
|
26 |
+
interview_state.interview_chain
|
27 |
+
)
|
28 |
+
else:
|
29 |
+
response = get_next_response(
|
30 |
+
interview_state.interview_chain,
|
31 |
+
interview_state.interview_history[-1]["content"] if interview_state.interview_history[-1]["role"] == "user" else "",
|
32 |
+
[
|
33 |
+
msg["content"]
|
34 |
+
for msg in interview_state.interview_history
|
35 |
+
if msg.get("role") == "user"
|
36 |
+
],
|
37 |
+
interview_state.question_count,
|
38 |
+
)
|
39 |
+
elif interview_state.interview_type == "sarah":
|
40 |
+
response = get_next_response(
|
41 |
+
interview_state.interview_chain,
|
42 |
+
interview_state.interview_history[-1]["content"] if interview_state.interview_history[-1]["role"] == "user" else "",
|
43 |
+
[
|
44 |
+
msg["content"]
|
45 |
+
for msg in interview_state.interview_history
|
46 |
+
if msg.get("role") == "user"
|
47 |
+
],
|
48 |
+
interview_state.question_count,
|
49 |
+
)
|
50 |
+
elif interview_state.interview_type == "aaron":
|
51 |
+
response = get_next_response(
|
52 |
+
interview_state.interview_chain,
|
53 |
+
interview_state.interview_history[-1]["content"] if interview_state.interview_history[-1]["role"] == "user" else "",
|
54 |
+
[
|
55 |
+
msg["content"]
|
56 |
+
for msg in interview_state.interview_history
|
57 |
+
if msg.get("role") == "user"
|
58 |
+
],
|
59 |
+
interview_state.question_count,
|
60 |
+
)
|
61 |
+
|
62 |
+
else:
|
63 |
+
response = "Invalid interview type."
|
64 |
+
|
65 |
+
audio_buffer = BytesIO()
|
66 |
+
convert_text_to_speech(response, audio_buffer, voice)
|
67 |
+
audio_buffer.seek(0)
|
68 |
+
with tempfile.NamedTemporaryFile(
|
69 |
+
suffix=".mp3", delete=False
|
70 |
+
) as temp_file:
|
71 |
+
temp_audio_path = temp_file.name
|
72 |
+
temp_file.write(audio_buffer.getvalue())
|
73 |
+
interview_state.temp_audio_files.append(temp_audio_path)
|
74 |
+
|
75 |
+
|
76 |
+
history.append({"role": "assistant", "content": response})
|
77 |
+
interview_state.interview_history.append({"role": "assistant", "content": response})
|
78 |
+
|
79 |
+
if interview_state.interview_finished:
|
80 |
+
|
81 |
+
conclusion_message = "Thank you for being here. We will review your responses and provide feedback soon."
|
82 |
+
history.append(
|
83 |
+
{"role": "system", "content": conclusion_message}
|
84 |
+
)
|
85 |
+
interview_state.interview_history.append({"role": "system", "content": conclusion_message})
|
86 |
+
|
87 |
+
txt_path = save_interview_history(
|
88 |
+
[msg["content"] for msg in history if msg["role"] != "system"], interview_state.language
|
89 |
+
)
|
90 |
+
if txt_path:
|
91 |
+
return (
|
92 |
+
history,
|
93 |
+
gr.Audio(
|
94 |
+
value=temp_audio_path,
|
95 |
+
autoplay=True,
|
96 |
+
visible=True,
|
97 |
+
),
|
98 |
+
gr.File(visible=True, value=txt_path),
|
99 |
+
gr.Textbox(interactive=False)
|
100 |
+
)
|
101 |
+
else:
|
102 |
+
return (
|
103 |
+
history,
|
104 |
+
gr.Audio(
|
105 |
+
value=temp_audio_path,
|
106 |
+
autoplay=True,
|
107 |
+
visible=True,
|
108 |
+
),
|
109 |
+
None,
|
110 |
+
gr.Textbox(interactive=False)
|
111 |
+
)
|
112 |
+
|
113 |
+
return (
|
114 |
+
history,
|
115 |
+
gr.Audio(
|
116 |
+
value=temp_audio_path, autoplay=True, visible=True
|
117 |
+
),
|
118 |
+
None,
|
119 |
+
gr.Textbox(interactive=True)
|
120 |
+
)
|
settings.py
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
# Interview settings
|
2 |
+
language = "english" # Default language
|
3 |
+
n_of_questions = 5 # Default number of questions
|
split.py
ADDED
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import List, Tuple
|
2 |
+
import math
|
3 |
+
|
4 |
+
def split_text_into_chunks(text: str, chunk_size: int) -> List[str]:
|
5 |
+
"""
|
6 |
+
Splits the text into chunks of a specified maximum size.
|
7 |
+
"""
|
8 |
+
# Trim the text to remove leading/trailing whitespace and reduce multiple spaces to a single space
|
9 |
+
cleaned_text = " ".join(text.split())
|
10 |
+
words = cleaned_text.split(" ")
|
11 |
+
|
12 |
+
chunks = []
|
13 |
+
current_chunk = []
|
14 |
+
current_length = 0
|
15 |
+
|
16 |
+
for word in words:
|
17 |
+
if current_length + len(word) + 1 > chunk_size:
|
18 |
+
chunks.append(" ".join(current_chunk))
|
19 |
+
current_chunk = [word]
|
20 |
+
current_length = len(word)
|
21 |
+
else:
|
22 |
+
current_chunk.append(word)
|
23 |
+
current_length += len(word) + 1
|
24 |
+
|
25 |
+
if current_chunk:
|
26 |
+
chunks.append(" ".join(current_chunk))
|
27 |
+
|
28 |
+
return chunks
|
29 |
+
|
30 |
+
|
31 |
+
def distribute_questions_across_chunks(n_chunks: int, n_questions: int) -> List[int]:
|
32 |
+
"""
|
33 |
+
Distributes a specified number of questions across a specified number of chunks.
|
34 |
+
"""
|
35 |
+
# Initial allocation of at least one question to early chunks if possible
|
36 |
+
questions_per_chunk = [1] * min(n_chunks, n_questions)
|
37 |
+
|
38 |
+
remaining_questions = n_questions - len(questions_per_chunk)
|
39 |
+
|
40 |
+
# Distribute remaining questions evenly across chunks
|
41 |
+
if remaining_questions > 0:
|
42 |
+
for i in range(len(questions_per_chunk)):
|
43 |
+
if remaining_questions == 0:
|
44 |
+
break
|
45 |
+
questions_per_chunk[i] += 1
|
46 |
+
remaining_questions -= 1
|
47 |
+
|
48 |
+
# If chunks remain, add zeros to match the total chunks.
|
49 |
+
while len(questions_per_chunk) < n_chunks:
|
50 |
+
questions_per_chunk.append(0)
|
51 |
+
|
52 |
+
return questions_per_chunk
|
53 |
+
|
54 |
+
|
55 |
+
def generate_questions_for_text(text: str, chunk_size: int, n_questions: int) -> List[Tuple[str, int]]:
|
56 |
+
"""
|
57 |
+
Splits the text into chunks, distributes questions across them, and returns a list of
|
58 |
+
(chunk, number of questions).
|
59 |
+
"""
|
60 |
+
chunks = split_text_into_chunks(text, chunk_size)
|
61 |
+
n_chunks = len(chunks)
|
62 |
+
|
63 |
+
questions_distribution = distribute_questions_across_chunks(n_chunks, n_questions)
|
64 |
+
|
65 |
+
return list(zip(chunks, questions_distribution))
|
66 |
+
|
67 |
+
|
68 |
+
# Example usage
|
69 |
+
text = (
|
70 |
+
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin hendrerit urna "
|
71 |
+
"vel erat bibendum, eget condimentum ipsum interdum. Nulla facilisi. Quisque dictum "
|
72 |
+
"eros eu velit varius, eget faucibus mauris euismod. Etiam placerat nisi at urna maximus "
|
73 |
+
"viverra. Integer ut odio nec justo volutpat varius ut quis quam. Suspendisse potenti. "
|
74 |
+
"Donec vulputate quam quis metus sagittis, sed commodo justo ultricies. Nam ut velit "
|
75 |
+
"finibus, venenatis eros vel, consectetur arcu. Praesent vulputate at ligula non elementum. "
|
76 |
+
"Nulla varius condimentum justo, non placerat nisl ullamcorper eu."
|
77 |
+
)
|
78 |
+
|
79 |
+
chunk_size = 100 # Max length of each chunk in characters
|
80 |
+
n_questions = 5 # Total number of questions to be asked
|
81 |
+
|
82 |
+
result = generate_questions_for_text(text, chunk_size, n_questions)
|
83 |
+
|
84 |
+
for i, (chunk, num_questions) in enumerate(result):
|
85 |
+
print(f"Chunk {i + 1} ({len(chunk.split())} words):")
|
86 |
+
print(f"Questions: {num_questions}")
|
87 |
+
print(chunk)
|
88 |
+
print("-" * 40)
|
splitgpt.py
ADDED
@@ -0,0 +1,331 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import json
|
3 |
+
from dotenv import load_dotenv
|
4 |
+
import fitz # PyMuPDF
|
5 |
+
from langchain_openai import ChatOpenAI # Correct import from langchain-openai
|
6 |
+
from langchain.schema import HumanMessage, SystemMessage # For creating structured chat messages
|
7 |
+
|
8 |
+
QUESTIONS_PATH = "questions.json"
|
9 |
+
|
10 |
+
# Load environment variables
|
11 |
+
load_dotenv()
|
12 |
+
|
13 |
+
def split_text_into_chunks(text: str, chunk_size: int) -> list:
|
14 |
+
"""
|
15 |
+
Splits the text into chunks of a specified maximum size.
|
16 |
+
"""
|
17 |
+
# Trim the text to remove leading/trailing whitespace and reduce multiple spaces to a single space
|
18 |
+
cleaned_text = " ".join(text.split())
|
19 |
+
words = cleaned_text.split(" ")
|
20 |
+
|
21 |
+
chunks = []
|
22 |
+
current_chunk = []
|
23 |
+
current_length = 0
|
24 |
+
|
25 |
+
for word in words:
|
26 |
+
if current_length + len(word) + 1 > chunk_size:
|
27 |
+
chunks.append(" ".join(current_chunk))
|
28 |
+
current_chunk = [word]
|
29 |
+
current_length = len(word)
|
30 |
+
else:
|
31 |
+
current_chunk.append(word)
|
32 |
+
current_length += len(word) + 1
|
33 |
+
|
34 |
+
if current_chunk:
|
35 |
+
chunks.append(" ".join(current_chunk))
|
36 |
+
|
37 |
+
return chunks
|
38 |
+
|
39 |
+
|
40 |
+
def distribute_questions_across_chunks(n_chunks: int, n_questions: int) -> list:
|
41 |
+
"""
|
42 |
+
Distributes a specified number of questions across a specified number of chunks.
|
43 |
+
"""
|
44 |
+
questions_per_chunk = [1] * min(n_chunks, n_questions)
|
45 |
+
remaining_questions = n_questions - len(questions_per_chunk)
|
46 |
+
|
47 |
+
if remaining_questions > 0:
|
48 |
+
for i in range(len(questions_per_chunk)):
|
49 |
+
if remaining_questions == 0:
|
50 |
+
break
|
51 |
+
questions_per_chunk[i] += 1
|
52 |
+
remaining_questions -= 1
|
53 |
+
|
54 |
+
while len(questions_per_chunk) < n_chunks:
|
55 |
+
questions_per_chunk.append(0)
|
56 |
+
|
57 |
+
return questions_per_chunk
|
58 |
+
|
59 |
+
|
60 |
+
def extract_text_from_pdf(pdf_path):
|
61 |
+
text = ""
|
62 |
+
try:
|
63 |
+
print(f"[DEBUG] Opening PDF: {pdf_path}")
|
64 |
+
with fitz.open(pdf_path) as pdf:
|
65 |
+
print(f"[DEBUG] Extracting text from PDF: {pdf_path}")
|
66 |
+
for page in pdf:
|
67 |
+
text += page.get_text()
|
68 |
+
except Exception as e:
|
69 |
+
print(f"Error reading PDF: {e}")
|
70 |
+
raise RuntimeError("Unable to extract text from PDF.")
|
71 |
+
return text
|
72 |
+
|
73 |
+
|
74 |
+
def generate_questions_from_text(text, n_questions=5):
|
75 |
+
openai_api_key = os.getenv("OPENAI_API_KEY")
|
76 |
+
|
77 |
+
if not openai_api_key:
|
78 |
+
raise RuntimeError(
|
79 |
+
"OpenAI API key not found. Please add it to your .env file as OPENAI_API_KEY."
|
80 |
+
)
|
81 |
+
|
82 |
+
chat = ChatOpenAI(
|
83 |
+
openai_api_key=openai_api_key, model="gpt-4", temperature=0.7, max_tokens=750
|
84 |
+
)
|
85 |
+
|
86 |
+
messages = [
|
87 |
+
SystemMessage(
|
88 |
+
content="You are an expert interviewer who generates concise technical interview questions. Do not enumerate the questions. Answer only with questions."
|
89 |
+
),
|
90 |
+
HumanMessage(
|
91 |
+
content=f"Based on the following content, generate {n_questions} technical interview questions:\n{text}"
|
92 |
+
),
|
93 |
+
]
|
94 |
+
|
95 |
+
try:
|
96 |
+
print(f"[DEBUG] Sending request to OpenAI with {n_questions} questions.")
|
97 |
+
response = chat.invoke(messages)
|
98 |
+
questions = response.content.strip().split("\n\n")
|
99 |
+
questions = [q.strip() for q in questions if q.strip()]
|
100 |
+
except Exception as e:
|
101 |
+
print(f"[ERROR] Failed to generate questions: {e}")
|
102 |
+
questions = ["An error occurred while generating questions."]
|
103 |
+
|
104 |
+
return questions
|
105 |
+
|
106 |
+
|
107 |
+
def save_questions(questions):
|
108 |
+
with open(QUESTIONS_PATH, "w") as f:
|
109 |
+
json.dump(questions, f, indent=4)
|
110 |
+
|
111 |
+
|
112 |
+
def generate_and_save_questions_from_pdf(pdf_path, total_questions=5):
|
113 |
+
print(f"[INFO] Generating questions from PDF: {pdf_path}")
|
114 |
+
|
115 |
+
try:
|
116 |
+
pdf_text = extract_text_from_pdf(pdf_path)
|
117 |
+
|
118 |
+
if not pdf_text.strip():
|
119 |
+
raise RuntimeError("The PDF content is empty or could not be read.")
|
120 |
+
|
121 |
+
chunk_size = 2000
|
122 |
+
chunks = split_text_into_chunks(pdf_text, chunk_size)
|
123 |
+
n_chunks = len(chunks)
|
124 |
+
|
125 |
+
questions_distribution = distribute_questions_across_chunks(n_chunks, total_questions)
|
126 |
+
combined_questions = []
|
127 |
+
|
128 |
+
for i, (chunk, n_questions) in enumerate(zip(chunks, questions_distribution)):
|
129 |
+
print(f"[DEBUG] Processing chunk {i + 1} of {n_chunks}")
|
130 |
+
if n_questions > 0:
|
131 |
+
questions = generate_questions_from_text(chunk, n_questions=n_questions)
|
132 |
+
combined_questions.extend(questions)
|
133 |
+
|
134 |
+
if not combined_questions:
|
135 |
+
raise RuntimeError("No questions generated from the PDF content.")
|
136 |
+
|
137 |
+
print(f"[INFO] Total questions generated: {len(combined_questions)}")
|
138 |
+
save_questions(combined_questions)
|
139 |
+
print(f"[INFO] Questions saved to {QUESTIONS_PATH}")
|
140 |
+
|
141 |
+
# Return a status message and the JSON object
|
142 |
+
return "Questions generated successfully.", {"questions": combined_questions}
|
143 |
+
|
144 |
+
except Exception as e:
|
145 |
+
# Handle exceptions and return meaningful error messages
|
146 |
+
error_message = f"Error during question generation: {str(e)}"
|
147 |
+
print(f"[ERROR] {error_message}")
|
148 |
+
return error_message, {"questions": []}
|
149 |
+
|
150 |
+
|
151 |
+
|
152 |
+
|
153 |
+
|
154 |
+
|
155 |
+
import gradio as gr
|
156 |
+
import json
|
157 |
+
import os
|
158 |
+
import time
|
159 |
+
|
160 |
+
def generate_and_save_questions_from_pdf3_mock(pdf_path, total_questions=5):
|
161 |
+
print(f"[INFO] Generating questions from PDF: {pdf_path}")
|
162 |
+
|
163 |
+
if not os.path.exists(pdf_path):
|
164 |
+
yield "❌ Error: PDF file not found.", {}
|
165 |
+
return
|
166 |
+
|
167 |
+
yield "📄 PDF uploaded successfully. Processing started...", {}
|
168 |
+
|
169 |
+
try:
|
170 |
+
# Simulate PDF text extraction and processing
|
171 |
+
time.sleep(1)
|
172 |
+
pdf_text = "This is some mock PDF text for testing purposes."
|
173 |
+
|
174 |
+
if not pdf_text.strip():
|
175 |
+
yield "❌ Error: The PDF content is empty or could not be read.", {}
|
176 |
+
return
|
177 |
+
|
178 |
+
chunk_size = 2000
|
179 |
+
chunks = [pdf_text[i:i + chunk_size] for i in range(0, len(pdf_text), chunk_size)]
|
180 |
+
n_chunks = len(chunks)
|
181 |
+
|
182 |
+
yield f"🔄 Splitting text into {n_chunks} chunks...", {}
|
183 |
+
|
184 |
+
questions_distribution = [total_questions // n_chunks] * n_chunks
|
185 |
+
combined_questions = []
|
186 |
+
|
187 |
+
for i, (chunk, n_questions) in enumerate(zip(chunks, questions_distribution)):
|
188 |
+
yield f"🔄 Processing chunk {i + 1} of {n_chunks}...", {}
|
189 |
+
time.sleep(1) # Simulating processing time
|
190 |
+
combined_questions.append(f"Sample Question from Chunk {i + 1}")
|
191 |
+
|
192 |
+
if not combined_questions:
|
193 |
+
yield "❌ Error: No questions generated from the PDF content.", {}
|
194 |
+
return
|
195 |
+
|
196 |
+
yield f"✅ Total {len(combined_questions)} questions generated. Saving questions...", {}
|
197 |
+
save_path = "generated_questions_from_pdf.json"
|
198 |
+
with open(save_path, "w") as f:
|
199 |
+
json.dump({"questions": combined_questions}, f)
|
200 |
+
|
201 |
+
yield "✅ PDF processing complete. Questions saved successfully!", {"questions": combined_questions}
|
202 |
+
|
203 |
+
except Exception as e:
|
204 |
+
yield f"❌ Error during question generation: {str(e)}", {}
|
205 |
+
|
206 |
+
def generate_and_save_questions_from_pdf3_v1(pdf_path, total_questions=5):
|
207 |
+
print(f"[INFO] Generating questions from PDF: {pdf_path}")
|
208 |
+
|
209 |
+
if not os.path.exists(pdf_path):
|
210 |
+
yield "❌ Error: PDF file not found.", {}
|
211 |
+
return
|
212 |
+
|
213 |
+
yield "📄 PDF uploaded successfully. Processing started...", {}
|
214 |
+
|
215 |
+
try:
|
216 |
+
# Extract text from the PDF file
|
217 |
+
pdf_text = extract_text_from_pdf(pdf_path)
|
218 |
+
|
219 |
+
if not pdf_text.strip():
|
220 |
+
yield "❌ Error: The PDF content is empty or could not be read.", {}
|
221 |
+
return
|
222 |
+
|
223 |
+
# Split the PDF content into chunks
|
224 |
+
chunk_size = 2000 # Adjust this as necessary
|
225 |
+
chunks = split_text_into_chunks(pdf_text, chunk_size)
|
226 |
+
n_chunks = len(chunks)
|
227 |
+
|
228 |
+
yield f"🔄 Splitting text into {n_chunks} chunks...", {}
|
229 |
+
|
230 |
+
# Distribute the total number of questions across chunks
|
231 |
+
questions_distribution = distribute_questions_across_chunks(n_chunks, total_questions)
|
232 |
+
combined_questions = []
|
233 |
+
|
234 |
+
# Process each chunk and generate questions
|
235 |
+
for i, (chunk, n_questions) in enumerate(zip(chunks, questions_distribution)):
|
236 |
+
yield f"🔄 Processing chunk {i + 1} of {n_chunks}...", {}
|
237 |
+
if n_questions > 0:
|
238 |
+
questions = generate_questions_from_text(chunk, n_questions=n_questions)
|
239 |
+
combined_questions.extend(questions)
|
240 |
+
|
241 |
+
if not combined_questions:
|
242 |
+
yield "❌ Error: No questions generated from the PDF content.", {}
|
243 |
+
return
|
244 |
+
|
245 |
+
yield f"✅ Total {len(combined_questions)} questions generated. Saving questions...", {}
|
246 |
+
|
247 |
+
# Save generated questions to a file
|
248 |
+
save_path = "generated_questions_from_pdf.json"
|
249 |
+
with open(save_path, "w") as f:
|
250 |
+
json.dump({"questions": combined_questions}, f)
|
251 |
+
|
252 |
+
yield "✅ PDF processing complete. Questions saved successfully!", {"questions": combined_questions}
|
253 |
+
|
254 |
+
except Exception as e:
|
255 |
+
error_message = f"❌ Error during question generation: {str(e)}"
|
256 |
+
print(f"[ERROR] {error_message}")
|
257 |
+
yield error_message, {}
|
258 |
+
|
259 |
+
import json
|
260 |
+
import os
|
261 |
+
|
262 |
+
def generate_and_save_questions_from_pdf3(pdf_path, total_questions=5):
|
263 |
+
print(f"[INFO] Generating questions from PDF: {pdf_path}")
|
264 |
+
|
265 |
+
if not os.path.exists(pdf_path):
|
266 |
+
yield "❌ Error: PDF file not found.", {}
|
267 |
+
return
|
268 |
+
|
269 |
+
yield "📄 PDF uploaded successfully. Processing started...", {}
|
270 |
+
|
271 |
+
try:
|
272 |
+
# Extract text from the PDF file
|
273 |
+
pdf_text = extract_text_from_pdf(pdf_path)
|
274 |
+
|
275 |
+
if not pdf_text.strip():
|
276 |
+
yield "❌ Error: The PDF content is empty or could not be read.", {}
|
277 |
+
return
|
278 |
+
|
279 |
+
# Split the PDF content into chunks
|
280 |
+
chunk_size = 2000 # Adjust this as necessary
|
281 |
+
chunks = split_text_into_chunks(pdf_text, chunk_size)
|
282 |
+
n_chunks = len(chunks)
|
283 |
+
|
284 |
+
yield f"🔄 Splitting text into {n_chunks} chunks...", {}
|
285 |
+
|
286 |
+
# Distribute the total number of questions across chunks
|
287 |
+
questions_distribution = distribute_questions_across_chunks(n_chunks, total_questions)
|
288 |
+
combined_questions = []
|
289 |
+
|
290 |
+
# Process each chunk and generate questions
|
291 |
+
for i, (chunk, n_questions) in enumerate(zip(chunks, questions_distribution)):
|
292 |
+
yield f"🔄 Processing chunk {i + 1} of {n_chunks}...", {}
|
293 |
+
if n_questions > 0:
|
294 |
+
questions = generate_questions_from_text(chunk, n_questions=n_questions)
|
295 |
+
combined_questions.extend(questions)
|
296 |
+
|
297 |
+
if not combined_questions:
|
298 |
+
yield "❌ Error: No questions generated from the PDF content.", {}
|
299 |
+
return
|
300 |
+
|
301 |
+
yield f"✅ Total {len(combined_questions)} questions generated. Saving questions...", {}
|
302 |
+
|
303 |
+
# Save the combined questions in `generated_questions_from_pdf.json` (detailed version)
|
304 |
+
detailed_save_path = "generated_questions_from_pdf.json"
|
305 |
+
with open(detailed_save_path, "w") as f:
|
306 |
+
json.dump({"questions": combined_questions}, f)
|
307 |
+
|
308 |
+
# Save only the questions (overwrite `questions.json` if it already exists)
|
309 |
+
simple_save_path = "questions.json"
|
310 |
+
with open(simple_save_path, "w") as f:
|
311 |
+
json.dump(combined_questions, f)
|
312 |
+
|
313 |
+
yield "✅ PDF processing complete. Questions saved successfully!", {"questions": combined_questions}
|
314 |
+
|
315 |
+
except Exception as e:
|
316 |
+
error_message = f"❌ Error during question generation: {str(e)}"
|
317 |
+
print(f"[ERROR] {error_message}")
|
318 |
+
yield error_message, {}
|
319 |
+
|
320 |
+
|
321 |
+
|
322 |
+
if __name__ == "__main__":
|
323 |
+
pdf_path = "professional_machine_learning_engineer_exam_guide_english.pdf"
|
324 |
+
|
325 |
+
try:
|
326 |
+
generated_questions = generate_and_save_questions_from_pdf(
|
327 |
+
pdf_path, total_questions=5
|
328 |
+
)
|
329 |
+
print(f"Generated Questions:\n{json.dumps(generated_questions, indent=2)}")
|
330 |
+
except Exception as e:
|
331 |
+
print(f"Failed to generate questions: {e}")
|
utils.py
ADDED
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from datetime import datetime
|
2 |
+
from docx import Document
|
3 |
+
from docx.shared import Pt
|
4 |
+
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
|
5 |
+
from pathlib import Path
|
6 |
+
|
7 |
+
def save_interview_history_old(history, language):
|
8 |
+
"""Saves the interview history to a TXT file."""
|
9 |
+
file_name = f"interview_history_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
|
10 |
+
file_path = Path("hr_interviewer") / file_name
|
11 |
+
|
12 |
+
with open(file_path, "w", encoding="utf-8") as file:
|
13 |
+
for item in history:
|
14 |
+
file.write(f"{item}\n")
|
15 |
+
|
16 |
+
return file_path
|
17 |
+
|
18 |
+
|
19 |
+
import os
|
20 |
+
from datetime import datetime
|
21 |
+
|
22 |
+
def save_interview_history_fix(interview_history, language, folder_path="hr_interviewer"):
|
23 |
+
"""
|
24 |
+
Saves the interview history to a file in the specified folder.
|
25 |
+
|
26 |
+
Args:
|
27 |
+
interview_history: The content of the interview history as a string.
|
28 |
+
language: Language of the report.
|
29 |
+
folder_path: Folder path where the history file will be saved.
|
30 |
+
|
31 |
+
Returns:
|
32 |
+
The file path of the saved interview history.
|
33 |
+
"""
|
34 |
+
# Ensure the directory exists
|
35 |
+
os.makedirs(folder_path, exist_ok=True)
|
36 |
+
|
37 |
+
# Generate the filename with the current date and time
|
38 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
39 |
+
file_path = os.path.join(folder_path, f"interview_history_{timestamp}.txt")
|
40 |
+
|
41 |
+
try:
|
42 |
+
with open(file_path, "w", encoding="utf-8") as file:
|
43 |
+
file.write("\n".join(interview_history))
|
44 |
+
print(f"[DEBUG] Interview history saved at {file_path}")
|
45 |
+
return file_path
|
46 |
+
except Exception as e:
|
47 |
+
print(f"[ERROR] Failed to save interview history: {e}")
|
48 |
+
return None
|
49 |
+
|
50 |
+
import os
|
51 |
+
from datetime import datetime
|
52 |
+
|
53 |
+
def save_interview_history(interview_history, language, folder_path="hr_interviewer"):
|
54 |
+
os.makedirs(folder_path, exist_ok=True)
|
55 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
56 |
+
file_path = os.path.join(folder_path, f"interview_history_{timestamp}.txt")
|
57 |
+
|
58 |
+
try:
|
59 |
+
with open(file_path, "w", encoding="utf-8") as file:
|
60 |
+
file.write("\n".join(interview_history))
|
61 |
+
print(f"[DEBUG] Interview history saved at {file_path}")
|
62 |
+
return file_path
|
63 |
+
except Exception as e:
|
64 |
+
print(f"[ERROR] Failed to save interview history: {e}")
|
65 |
+
return None
|
66 |
+
|
67 |
+
|
68 |
+
|
69 |
+
def generate_interview_report(interview_history, language):
|
70 |
+
"""
|
71 |
+
Generates a report in DOCX format based on the interview history.
|
72 |
+
Args:
|
73 |
+
interview_history: A list of strings representing the interview history.
|
74 |
+
language: The language of the report.
|
75 |
+
Returns:
|
76 |
+
A tuple containing the report content as a string and the path to the generated DOCX file.
|
77 |
+
"""
|
78 |
+
doc = Document()
|
79 |
+
|
80 |
+
# Add title
|
81 |
+
title = doc.add_heading('Interview Report', level=1)
|
82 |
+
title.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
|
83 |
+
title_run = title.runs[0]
|
84 |
+
title_run.font.name = 'Arial'
|
85 |
+
title_run.font.size = Pt(16)
|
86 |
+
title_run.bold = True
|
87 |
+
|
88 |
+
# Add date
|
89 |
+
date_para = doc.add_paragraph(f"Date: {datetime.now().strftime('%Y-%m-%d')}")
|
90 |
+
date_para.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT
|
91 |
+
date_run = date_para.runs[0]
|
92 |
+
date_run.font.name = 'Arial'
|
93 |
+
date_run.font.size = Pt(11)
|
94 |
+
|
95 |
+
# Add interview history
|
96 |
+
doc.add_heading('Interview History', level=2)
|
97 |
+
for item in interview_history:
|
98 |
+
para = doc.add_paragraph(item)
|
99 |
+
para_run = para.runs[0]
|
100 |
+
para_run.font.name = 'Arial'
|
101 |
+
para_run.font.size = Pt(12)
|
102 |
+
|
103 |
+
# Save the document
|
104 |
+
file_name = f"interview_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.docx"
|
105 |
+
file_path = Path("hr_interviewer") / file_name
|
106 |
+
doc.save(file_path)
|
107 |
+
|
108 |
+
# Convert DOCX to string (for display in Gradio, etc.)
|
109 |
+
report_content = ""
|
110 |
+
for para in doc.paragraphs:
|
111 |
+
report_content += para.text + "\n"
|
112 |
+
|
113 |
+
return report_content, file_path
|
114 |
+
|
115 |
+
|
116 |
+
import json
|
117 |
+
from datetime import datetime
|
118 |
+
from docx import Document
|
119 |
+
from docx.shared import Pt
|
120 |
+
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
|
121 |
+
from pathlib import Path
|
122 |
+
|
123 |
+
# ... (other functions remain the same)
|
124 |
+
|
125 |
+
def load_config(config_path="hr_interviewer/config.json"):
|
126 |
+
"""Loads the configuration from a JSON file."""
|
127 |
+
try:
|
128 |
+
with open(config_path, "r") as f:
|
129 |
+
config = json.load(f)
|
130 |
+
except FileNotFoundError:
|
131 |
+
print(f"[WARNING] Config file not found at {config_path}. Using default settings.")
|
132 |
+
config = {} # Return empty dict to use defaults
|
133 |
+
except json.JSONDecodeError:
|
134 |
+
print(f"[ERROR] Error decoding JSON in {config_path}. Using default settings.")
|
135 |
+
config = {}
|
136 |
+
return config
|
137 |
+
|
138 |
+
def save_config(config, config_path="hr_interviewer/config.json"):
|
139 |
+
"""Saves the configuration to a JSON file."""
|
140 |
+
try:
|
141 |
+
with open(config_path, "w") as f:
|
142 |
+
json.dump(config, f, indent=4)
|
143 |
+
print(f"[INFO] Configuration saved to {config_path}")
|
144 |
+
return True
|
145 |
+
except Exception as e:
|
146 |
+
print(f"[ERROR] Failed to save configuration: {e}")
|
147 |
+
return False
|