Spaces:
Sleeping
Sleeping
Commit
•
ca7d30b
1
Parent(s):
f9d9053
Upload 3 files
Browse files- Behavioral Screen.py +232 -0
- Professional Screen.py +203 -0
- Resume Screen.py +212 -0
Behavioral Screen.py
ADDED
@@ -0,0 +1,232 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
from streamlit_lottie import st_lottie
|
3 |
+
from typing import Literal
|
4 |
+
from dataclasses import dataclass
|
5 |
+
import json
|
6 |
+
import base64
|
7 |
+
from langchain.memory import ConversationBufferMemory
|
8 |
+
from langchain.callbacks import get_openai_callback
|
9 |
+
from langchain.chat_models import ChatOpenAI
|
10 |
+
from langchain.chains import ConversationChain, RetrievalQA
|
11 |
+
from langchain.prompts.prompt import PromptTemplate
|
12 |
+
from langchain.text_splitter import NLTKTextSplitter
|
13 |
+
from langchain.embeddings import OpenAIEmbeddings
|
14 |
+
from langchain.vectorstores import FAISS
|
15 |
+
import nltk
|
16 |
+
from prompts.prompts import templates
|
17 |
+
# Audio
|
18 |
+
from speech_recognition.openai_whisper import save_wav_file, transcribe
|
19 |
+
from audio_recorder_streamlit import audio_recorder
|
20 |
+
from aws.synthesize_speech import synthesize_speech
|
21 |
+
from IPython.display import Audio
|
22 |
+
|
23 |
+
def load_lottiefile(filepath: str):
|
24 |
+
|
25 |
+
'''Load lottie animation file'''
|
26 |
+
|
27 |
+
with open(filepath, "r") as f:
|
28 |
+
return json.load(f)
|
29 |
+
|
30 |
+
st_lottie(load_lottiefile("images/welcome.json"), speed=1, reverse=False, loop=True, quality="high", height=300)
|
31 |
+
|
32 |
+
#st.markdown("""solutions to potential errors:""")
|
33 |
+
with st.expander("""Why did I encounter errors when I tried to talk to the AI Interviewer?"""):
|
34 |
+
st.write("""
|
35 |
+
This is because the app failed to record. Make sure that your microphone is connected and that you have given permission to the browser to access your microphone.""")
|
36 |
+
|
37 |
+
st.markdown("""\n""")
|
38 |
+
jd = st.text_area("""Please enter the job description here (If you don't have one, enter keywords, such as "communication" or "teamwork" instead): """)
|
39 |
+
auto_play = st.checkbox("Let AI interviewer speak! (Please don't switch during the interview)")
|
40 |
+
#st.toast("4097 tokens is roughly equivalent to around 800 to 1000 words or 3 minutes of speech. Please keep your answer within this limit.")
|
41 |
+
|
42 |
+
@dataclass
|
43 |
+
class Message:
|
44 |
+
'''dataclass for keeping track of the messages'''
|
45 |
+
origin: Literal["human", "ai"]
|
46 |
+
message: str
|
47 |
+
|
48 |
+
def autoplay_audio(file_path: str):
|
49 |
+
'''Play audio automatically'''
|
50 |
+
def update_audio():
|
51 |
+
global global_audio_md
|
52 |
+
with open(file_path, "rb") as f:
|
53 |
+
data = f.read()
|
54 |
+
b64 = base64.b64encode(data).decode()
|
55 |
+
global_audio_md = f"""
|
56 |
+
<audio controls autoplay="true">
|
57 |
+
<source src="data:audio/mp3;base64,{b64}" type="audio/mp3">
|
58 |
+
</audio>
|
59 |
+
"""
|
60 |
+
def update_markdown(audio_md):
|
61 |
+
st.markdown(audio_md, unsafe_allow_html=True)
|
62 |
+
update_audio()
|
63 |
+
update_markdown(global_audio_md)
|
64 |
+
|
65 |
+
def embeddings(text: str):
|
66 |
+
|
67 |
+
'''Create embeddings for the job description'''
|
68 |
+
|
69 |
+
nltk.download('punkt')
|
70 |
+
text_splitter = NLTKTextSplitter()
|
71 |
+
texts = text_splitter.split_text(text)
|
72 |
+
# Create emebeddings
|
73 |
+
embeddings = OpenAIEmbeddings()
|
74 |
+
docsearch = FAISS.from_texts(texts, embeddings)
|
75 |
+
retriever = docsearch.as_retriever(search_tupe='similarity search')
|
76 |
+
return retriever
|
77 |
+
|
78 |
+
def initialize_session_state():
|
79 |
+
|
80 |
+
'''Initialize session state variables'''
|
81 |
+
|
82 |
+
if "retriever" not in st.session_state:
|
83 |
+
st.session_state.retriever = embeddings(jd)
|
84 |
+
if "chain_type_kwargs" not in st.session_state:
|
85 |
+
Behavioral_Prompt = PromptTemplate(input_variables=["context", "question"],
|
86 |
+
template=templates.behavioral_template)
|
87 |
+
st.session_state.chain_type_kwargs = {"prompt": Behavioral_Prompt}
|
88 |
+
# interview history
|
89 |
+
if "history" not in st.session_state:
|
90 |
+
st.session_state.history = []
|
91 |
+
st.session_state.history.append(Message("ai", "Hello there! I am your interviewer today. I will access your soft skills through a series of questions. Let's get started! Please start by saying hello or introducing yourself. Note: The maximum length of your answer is 4097 tokens!"))
|
92 |
+
# token count
|
93 |
+
if "token_count" not in st.session_state:
|
94 |
+
st.session_state.token_count = 0
|
95 |
+
if "memory" not in st.session_state:
|
96 |
+
st.session_state.memory = ConversationBufferMemory()
|
97 |
+
if "guideline" not in st.session_state:
|
98 |
+
llm = ChatOpenAI(
|
99 |
+
model_name="gpt-3.5-turbo",
|
100 |
+
temperature=0.8, )
|
101 |
+
st.session_state.guideline = RetrievalQA.from_chain_type(
|
102 |
+
llm=llm,
|
103 |
+
chain_type_kwargs=st.session_state.chain_type_kwargs, chain_type='stuff',
|
104 |
+
retriever=st.session_state.retriever, memory=st.session_state.memory).run(
|
105 |
+
"Create an interview guideline and prepare total of 8 questions. Make sure the questions tests the soft skills")
|
106 |
+
# llm chain and memory
|
107 |
+
if "conversation" not in st.session_state:
|
108 |
+
llm = ChatOpenAI(
|
109 |
+
model_name = "gpt-3.5-turbo",
|
110 |
+
temperature = 0.8,)
|
111 |
+
PROMPT = PromptTemplate(
|
112 |
+
input_variables=["history", "input"],
|
113 |
+
template="""I want you to act as an interviewer strictly following the guideline in the current conversation.
|
114 |
+
Candidate has no idea what the guideline is.
|
115 |
+
Ask me questions and wait for my answers. Do not write explanations.
|
116 |
+
Ask question like a real person, only one question at a time.
|
117 |
+
Do not ask the same question.
|
118 |
+
Do not repeat the question.
|
119 |
+
Do ask follow-up questions if necessary.
|
120 |
+
You name is GPTInterviewer.
|
121 |
+
I want you to only reply as an interviewer.
|
122 |
+
Do not write all the conversation at once.
|
123 |
+
If there is an error, point it out.
|
124 |
+
|
125 |
+
Current Conversation:
|
126 |
+
{history}
|
127 |
+
|
128 |
+
Candidate: {input}
|
129 |
+
AI: """)
|
130 |
+
st.session_state.conversation = ConversationChain(prompt=PROMPT, llm=llm,
|
131 |
+
memory=st.session_state.memory)
|
132 |
+
if "feedback" not in st.session_state:
|
133 |
+
llm = ChatOpenAI(
|
134 |
+
model_name = "gpt-3.5-turbo",
|
135 |
+
temperature = 0.5,)
|
136 |
+
st.session_state.feedback = ConversationChain(
|
137 |
+
prompt=PromptTemplate(input_variables = ["history", "input"], template = templates.feedback_template),
|
138 |
+
llm=llm,
|
139 |
+
memory = st.session_state.memory,
|
140 |
+
)
|
141 |
+
|
142 |
+
def answer_call_back():
|
143 |
+
|
144 |
+
'''callback function for answering user input'''
|
145 |
+
|
146 |
+
with get_openai_callback() as cb:
|
147 |
+
# user input
|
148 |
+
human_answer = st.session_state.answer
|
149 |
+
# transcribe audio
|
150 |
+
if voice:
|
151 |
+
save_wav_file("temp/audio.wav", human_answer)
|
152 |
+
try:
|
153 |
+
input = transcribe("temp/audio.wav")
|
154 |
+
# save human_answer to history
|
155 |
+
except:
|
156 |
+
st.session_state.history.append(Message("ai", "Sorry, I didn't get that."))
|
157 |
+
return "Please try again."
|
158 |
+
else:
|
159 |
+
input = human_answer
|
160 |
+
|
161 |
+
st.session_state.history.append(
|
162 |
+
Message("human", input)
|
163 |
+
)
|
164 |
+
# OpenAI answer and save to history
|
165 |
+
llm_answer = st.session_state.conversation.run(input)
|
166 |
+
# speech synthesis and speak out
|
167 |
+
audio_file_path = synthesize_speech(llm_answer)
|
168 |
+
# create audio widget with autoplay
|
169 |
+
audio_widget = Audio(audio_file_path, autoplay=True)
|
170 |
+
# save audio data to history
|
171 |
+
st.session_state.history.append(
|
172 |
+
Message("ai", llm_answer)
|
173 |
+
)
|
174 |
+
st.session_state.token_count += cb.total_tokens
|
175 |
+
return audio_widget
|
176 |
+
|
177 |
+
### ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
|
178 |
+
if jd:
|
179 |
+
|
180 |
+
initialize_session_state()
|
181 |
+
credit_card_placeholder = st.empty()
|
182 |
+
col1, col2 = st.columns(2)
|
183 |
+
with col1:
|
184 |
+
feedback = st.button("Get Interview Feedback")
|
185 |
+
with col2:
|
186 |
+
guideline = st.button("Show me interview guideline!")
|
187 |
+
audio = None
|
188 |
+
chat_placeholder = st.container()
|
189 |
+
answer_placeholder = st.container()
|
190 |
+
|
191 |
+
if guideline:
|
192 |
+
st.write(st.session_state.guideline)
|
193 |
+
if feedback:
|
194 |
+
evaluation = st.session_state.feedback.run("please give evalution regarding the interview")
|
195 |
+
st.markdown(evaluation)
|
196 |
+
st.download_button(label="Download Interview Feedback", data=evaluation, file_name="interview_feedback.txt")
|
197 |
+
st.stop()
|
198 |
+
else:
|
199 |
+
with answer_placeholder:
|
200 |
+
voice: bool = st.checkbox("I would like to speak with AI Interviewer!")
|
201 |
+
if voice:
|
202 |
+
answer = audio_recorder(pause_threshold=2.5, sample_rate=44100)
|
203 |
+
#st.warning("An UnboundLocalError will occur if the microphone fails to record.")
|
204 |
+
else:
|
205 |
+
answer = st.chat_input("Your answer")
|
206 |
+
if answer:
|
207 |
+
st.session_state['answer'] = answer
|
208 |
+
audio = answer_call_back()
|
209 |
+
with chat_placeholder:
|
210 |
+
for answer in st.session_state.history:
|
211 |
+
if answer.origin == 'ai':
|
212 |
+
if auto_play and audio:
|
213 |
+
with st.chat_message("assistant"):
|
214 |
+
st.write(answer.message)
|
215 |
+
st.write(audio)
|
216 |
+
else:
|
217 |
+
with st.chat_message("assistant"):
|
218 |
+
st.write(answer.message)
|
219 |
+
else:
|
220 |
+
with st.chat_message("user"):
|
221 |
+
st.write(answer.message)
|
222 |
+
|
223 |
+
credit_card_placeholder.caption(f"""
|
224 |
+
Progress: {int(len(st.session_state.history) / 30 * 100)}% completed.
|
225 |
+
""")
|
226 |
+
|
227 |
+
else:
|
228 |
+
st.info("Please submit job description to start interview.")
|
229 |
+
|
230 |
+
|
231 |
+
|
232 |
+
|
Professional Screen.py
ADDED
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
from streamlit_lottie import st_lottie
|
3 |
+
from typing import Literal
|
4 |
+
from dataclasses import dataclass
|
5 |
+
import json
|
6 |
+
import base64
|
7 |
+
from langchain.memory import ConversationBufferMemory
|
8 |
+
from langchain.callbacks import get_openai_callback
|
9 |
+
from langchain.chat_models import ChatOpenAI
|
10 |
+
from langchain.chains import ConversationChain, RetrievalQA
|
11 |
+
from langchain.prompts.prompt import PromptTemplate
|
12 |
+
from langchain.text_splitter import NLTKTextSplitter
|
13 |
+
from langchain.embeddings import OpenAIEmbeddings
|
14 |
+
from langchain.vectorstores import FAISS
|
15 |
+
import nltk
|
16 |
+
from prompts.prompts import templates
|
17 |
+
# Audio
|
18 |
+
from speech_recognition.openai_whisper import save_wav_file, transcribe
|
19 |
+
from audio_recorder_streamlit import audio_recorder
|
20 |
+
from aws.synthesize_speech import synthesize_speech
|
21 |
+
from IPython.display import Audio
|
22 |
+
|
23 |
+
|
24 |
+
def load_lottiefile(filepath: str):
|
25 |
+
with open(filepath, "r") as f:
|
26 |
+
return json.load(f)
|
27 |
+
st_lottie(load_lottiefile("images/welcome.json"), speed=1, reverse=False, loop=True, quality="high", height=300)
|
28 |
+
|
29 |
+
#st.markdown("""solutions to potential errors:""")
|
30 |
+
with st.expander("""Why did I encounter errors when I tried to talk to the AI Interviewer?"""):
|
31 |
+
st.write("""
|
32 |
+
This is because the app failed to record. Make sure that your microphone is connected and that you have given permission to the browser to access your microphone.""")
|
33 |
+
|
34 |
+
jd = st.text_area("Please enter the job description here (If you don't have one, enter keywords, such as PostgreSQL or Python instead): ")
|
35 |
+
auto_play = st.checkbox("Let AI interviewer speak! (Please don't switch during the interview)")
|
36 |
+
|
37 |
+
#st.toast("4097 tokens is roughly equivalent to around 800 to 1000 words or 3 minutes of speech. Please keep your answer within this limit.")
|
38 |
+
|
39 |
+
@dataclass
|
40 |
+
class Message:
|
41 |
+
"""class for keeping track of interview history."""
|
42 |
+
origin: Literal["human", "ai"]
|
43 |
+
message: str
|
44 |
+
|
45 |
+
def save_vector(text):
|
46 |
+
"""embeddings"""
|
47 |
+
|
48 |
+
nltk.download('punkt')
|
49 |
+
text_splitter = NLTKTextSplitter()
|
50 |
+
texts = text_splitter.split_text(text)
|
51 |
+
# Create emebeddings
|
52 |
+
embeddings = OpenAIEmbeddings()
|
53 |
+
docsearch = FAISS.from_texts(texts, embeddings)
|
54 |
+
return docsearch
|
55 |
+
|
56 |
+
def initialize_session_state_jd():
|
57 |
+
""" initialize session states """
|
58 |
+
if 'jd_docsearch' not in st.session_state:
|
59 |
+
st.session_state.jd_docserch = save_vector(jd)
|
60 |
+
if 'jd_retriever' not in st.session_state:
|
61 |
+
st.session_state.jd_retriever = st.session_state.jd_docserch.as_retriever(search_type="similarity")
|
62 |
+
if 'jd_chain_type_kwargs' not in st.session_state:
|
63 |
+
Interview_Prompt = PromptTemplate(input_variables=["context", "question"],
|
64 |
+
template=templates.jd_template)
|
65 |
+
st.session_state.jd_chain_type_kwargs = {"prompt": Interview_Prompt}
|
66 |
+
if 'jd_memory' not in st.session_state:
|
67 |
+
st.session_state.jd_memory = ConversationBufferMemory()
|
68 |
+
# interview history
|
69 |
+
if "jd_history" not in st.session_state:
|
70 |
+
st.session_state.jd_history = []
|
71 |
+
st.session_state.jd_history.append(Message("ai",
|
72 |
+
"Hello, Welcome to the interview. I am your interviewer today. I will ask you professional questions regarding the job description you submitted."
|
73 |
+
"Please start by introducting a little bit about yourself. Note: The maximum length of your answer is 4097 tokens!"))
|
74 |
+
# token count
|
75 |
+
if "token_count" not in st.session_state:
|
76 |
+
st.session_state.token_count = 0
|
77 |
+
if "jd_guideline" not in st.session_state:
|
78 |
+
llm = ChatOpenAI(
|
79 |
+
model_name = "gpt-3.5-turbo",
|
80 |
+
temperature = 0.8,)
|
81 |
+
st.session_state.jd_guideline = RetrievalQA.from_chain_type(
|
82 |
+
llm=llm,
|
83 |
+
chain_type_kwargs=st.session_state.jd_chain_type_kwargs, chain_type='stuff',
|
84 |
+
retriever=st.session_state.jd_retriever, memory = st.session_state.jd_memory).run("Create an interview guideline and prepare only one questions for each topic. Make sure the questions tests the technical knowledge")
|
85 |
+
# llm chain and memory
|
86 |
+
if "jd_screen" not in st.session_state:
|
87 |
+
llm = ChatOpenAI(
|
88 |
+
model_name="gpt-3.5-turbo",
|
89 |
+
temperature=0.8, )
|
90 |
+
PROMPT = PromptTemplate(
|
91 |
+
input_variables=["history", "input"],
|
92 |
+
template="""I want you to act as an interviewer strictly following the guideline in the current conversation.
|
93 |
+
Candidate has no idea what the guideline is.
|
94 |
+
Ask me questions and wait for my answers. Do not write explanations.
|
95 |
+
Ask question like a real person, only one question at a time.
|
96 |
+
Do not ask the same question.
|
97 |
+
Do not repeat the question.
|
98 |
+
Do ask follow-up questions if necessary.
|
99 |
+
You name is GPTInterviewer.
|
100 |
+
I want you to only reply as an interviewer.
|
101 |
+
Do not write all the conversation at once.
|
102 |
+
If there is an error, point it out.
|
103 |
+
|
104 |
+
Current Conversation:
|
105 |
+
{history}
|
106 |
+
|
107 |
+
Candidate: {input}
|
108 |
+
AI: """)
|
109 |
+
|
110 |
+
st.session_state.jd_screen = ConversationChain(prompt=PROMPT, llm=llm,
|
111 |
+
memory=st.session_state.jd_memory)
|
112 |
+
if 'jd_feedback' not in st.session_state:
|
113 |
+
llm = ChatOpenAI(
|
114 |
+
model_name="gpt-3.5-turbo",
|
115 |
+
temperature=0.8, )
|
116 |
+
st.session_state.jd_feedback = ConversationChain(
|
117 |
+
prompt=PromptTemplate(input_variables=["history", "input"], template=templates.feedback_template),
|
118 |
+
llm=llm,
|
119 |
+
memory=st.session_state.jd_memory,
|
120 |
+
)
|
121 |
+
|
122 |
+
def answer_call_back():
|
123 |
+
with get_openai_callback() as cb:
|
124 |
+
# user input
|
125 |
+
human_answer = st.session_state.answer
|
126 |
+
# transcribe audio
|
127 |
+
if voice:
|
128 |
+
save_wav_file("temp/audio.wav", human_answer)
|
129 |
+
try:
|
130 |
+
input = transcribe("temp/audio.wav")
|
131 |
+
# save human_answer to history
|
132 |
+
except:
|
133 |
+
st.session_state.jd_history.append(Message("ai", "Sorry, I didn't get that."))
|
134 |
+
return "Please try again."
|
135 |
+
else:
|
136 |
+
input = human_answer
|
137 |
+
|
138 |
+
st.session_state.jd_history.append(
|
139 |
+
Message("human", input)
|
140 |
+
)
|
141 |
+
# OpenAI answer and save to history
|
142 |
+
llm_answer = st.session_state.jd_screen.run(input)
|
143 |
+
# speech synthesis and speak out
|
144 |
+
audio_file_path = synthesize_speech(llm_answer)
|
145 |
+
# create audio widget with autoplay
|
146 |
+
audio_widget = Audio(audio_file_path, autoplay=True)
|
147 |
+
# save audio data to history
|
148 |
+
st.session_state.jd_history.append(
|
149 |
+
Message("ai", llm_answer)
|
150 |
+
)
|
151 |
+
st.session_state.token_count += cb.total_tokens
|
152 |
+
return audio_widget
|
153 |
+
|
154 |
+
if jd:
|
155 |
+
# initialize session states
|
156 |
+
initialize_session_state_jd()
|
157 |
+
#st.write(st.session_state.jd_guideline)
|
158 |
+
credit_card_placeholder = st.empty()
|
159 |
+
col1, col2 = st.columns(2)
|
160 |
+
with col1:
|
161 |
+
feedback = st.button("Get Interview Feedback")
|
162 |
+
with col2:
|
163 |
+
guideline = st.button("Show me interview guideline!")
|
164 |
+
chat_placeholder = st.container()
|
165 |
+
answer_placeholder = st.container()
|
166 |
+
audio = None
|
167 |
+
# if submit email adress, get interview feedback imediately
|
168 |
+
if guideline:
|
169 |
+
st.write(st.session_state.jd_guideline)
|
170 |
+
if feedback:
|
171 |
+
evaluation = st.session_state.jd_feedback.run("please give evalution regarding the interview")
|
172 |
+
st.markdown(evaluation)
|
173 |
+
st.download_button(label="Download Interview Feedback", data=evaluation, file_name="interview_feedback.txt")
|
174 |
+
st.stop()
|
175 |
+
else:
|
176 |
+
with answer_placeholder:
|
177 |
+
voice: bool = st.checkbox("I would like to speak with AI Interviewer")
|
178 |
+
if voice:
|
179 |
+
answer = audio_recorder(pause_threshold = 2.5, sample_rate = 44100)
|
180 |
+
#st.warning("An UnboundLocalError will occur if the microphone fails to record.")
|
181 |
+
else:
|
182 |
+
answer = st.chat_input("Your answer")
|
183 |
+
if answer:
|
184 |
+
st.session_state['answer'] = answer
|
185 |
+
audio = answer_call_back()
|
186 |
+
with chat_placeholder:
|
187 |
+
for answer in st.session_state.jd_history:
|
188 |
+
if answer.origin == 'ai':
|
189 |
+
if auto_play and audio:
|
190 |
+
with st.chat_message("assistant"):
|
191 |
+
st.write(answer.message)
|
192 |
+
st.write(audio)
|
193 |
+
else:
|
194 |
+
with st.chat_message("assistant"):
|
195 |
+
st.write(answer.message)
|
196 |
+
else:
|
197 |
+
with st.chat_message("user"):
|
198 |
+
st.write(answer.message)
|
199 |
+
|
200 |
+
credit_card_placeholder.caption(f"""
|
201 |
+
Progress: {int(len(st.session_state.jd_history) / 30 * 100)}% completed.""")
|
202 |
+
else:
|
203 |
+
st.info("Please submit a job description to start the interview.")
|
Resume Screen.py
ADDED
@@ -0,0 +1,212 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# langchain: https://python.langchain.com/
|
2 |
+
from dataclasses import dataclass
|
3 |
+
import streamlit as st
|
4 |
+
from speech_recognition.openai_whisper import save_wav_file, transcribe
|
5 |
+
from audio_recorder_streamlit import audio_recorder
|
6 |
+
from langchain.callbacks import get_openai_callback
|
7 |
+
from langchain.chat_models import ChatOpenAI
|
8 |
+
from langchain.memory import ConversationBufferMemory
|
9 |
+
from langchain.chains import RetrievalQA, ConversationChain
|
10 |
+
from langchain.prompts.prompt import PromptTemplate
|
11 |
+
from prompts.prompts import templates
|
12 |
+
from typing import Literal
|
13 |
+
from aws.synthesize_speech import synthesize_speech
|
14 |
+
from langchain.embeddings import OpenAIEmbeddings
|
15 |
+
from langchain.vectorstores import FAISS
|
16 |
+
from langchain.text_splitter import NLTKTextSplitter
|
17 |
+
from PyPDF2 import PdfReader
|
18 |
+
from prompts.prompt_selector import prompt_sector
|
19 |
+
from streamlit_lottie import st_lottie
|
20 |
+
import json
|
21 |
+
from IPython.display import Audio
|
22 |
+
import nltk
|
23 |
+
|
24 |
+
|
25 |
+
def load_lottiefile(filepath: str):
|
26 |
+
with open(filepath, "r") as f:
|
27 |
+
return json.load(f)
|
28 |
+
st_lottie(load_lottiefile("images/welcome.json"), speed=1, reverse=False, loop=True, quality="high", height=300)
|
29 |
+
|
30 |
+
#st.markdown("""solutions to potential errors:""")
|
31 |
+
with st.expander("""Why did I encounter errors when I tried to talk to the AI Interviewer?"""):
|
32 |
+
st.write("""This is because the app failed to record. Make sure that your microphone is connected and that you have given permission to the browser to access your microphone.""")
|
33 |
+
with st.expander("""Why did I encounter errors when I tried to upload my resume?"""):
|
34 |
+
st.write("""
|
35 |
+
Please make sure your resume is in pdf format. More formats will be supported in the future.
|
36 |
+
""")
|
37 |
+
|
38 |
+
st.markdown("""\n""")
|
39 |
+
position = st.selectbox("Select the position you are applying for", ["Data Analyst", "Software Engineer", "Marketing"])
|
40 |
+
resume = st.file_uploader("Upload your resume", type=["pdf"])
|
41 |
+
auto_play = st.checkbox("Let AI interviewer speak! (Please don't switch during the interview)")
|
42 |
+
|
43 |
+
#st.toast("4097 tokens is roughly equivalent to around 800 to 1000 words or 3 minutes of speech. Please keep your answer within this limit.")
|
44 |
+
|
45 |
+
@dataclass
|
46 |
+
class Message:
|
47 |
+
"""Class for keeping track of interview history."""
|
48 |
+
origin: Literal["human", "ai"]
|
49 |
+
message: str
|
50 |
+
|
51 |
+
def save_vector(resume):
|
52 |
+
"""embeddings"""
|
53 |
+
nltk.download('punkt')
|
54 |
+
pdf_reader = PdfReader(resume)
|
55 |
+
text = ""
|
56 |
+
for page in pdf_reader.pages:
|
57 |
+
text += page.extract_text()
|
58 |
+
# Split the document into chunks
|
59 |
+
text_splitter = NLTKTextSplitter()
|
60 |
+
texts = text_splitter.split_text(text)
|
61 |
+
|
62 |
+
embeddings = OpenAIEmbeddings()
|
63 |
+
docsearch = FAISS.from_texts(texts, embeddings)
|
64 |
+
return docsearch
|
65 |
+
|
66 |
+
def initialize_session_state_resume():
|
67 |
+
# convert resume to embeddings
|
68 |
+
if 'docsearch' not in st.session_state:
|
69 |
+
st.session_state.docserch = save_vector(resume)
|
70 |
+
# retriever for resume screen
|
71 |
+
if 'retriever' not in st.session_state:
|
72 |
+
st.session_state.retriever = st.session_state.docserch.as_retriever(search_type="similarity")
|
73 |
+
# prompt for retrieving information
|
74 |
+
if 'chain_type_kwargs' not in st.session_state:
|
75 |
+
st.session_state.chain_type_kwargs = prompt_sector(position, templates)
|
76 |
+
# interview history
|
77 |
+
if "resume_history" not in st.session_state:
|
78 |
+
st.session_state.resume_history = []
|
79 |
+
st.session_state.resume_history.append(Message(origin="ai", message="Hello, I am your interivewer today. I will ask you some questions regarding your resume and your experience. Please start by saying hello or introducing yourself. Note: The maximum length of your answer is 4097 tokens!"))
|
80 |
+
# token count
|
81 |
+
if "token_count" not in st.session_state:
|
82 |
+
st.session_state.token_count = 0
|
83 |
+
# memory buffer for resume screen
|
84 |
+
if "resume_memory" not in st.session_state:
|
85 |
+
st.session_state.resume_memory = ConversationBufferMemory(human_prefix = "Candidate: ", ai_prefix = "Interviewer")
|
86 |
+
# guideline for resume screen
|
87 |
+
if "resume_guideline" not in st.session_state:
|
88 |
+
llm = ChatOpenAI(
|
89 |
+
model_name = "gpt-3.5-turbo",
|
90 |
+
temperature = 0.5,)
|
91 |
+
|
92 |
+
st.session_state.resume_guideline = RetrievalQA.from_chain_type(
|
93 |
+
llm=llm,
|
94 |
+
chain_type_kwargs=st.session_state.chain_type_kwargs, chain_type='stuff',
|
95 |
+
retriever=st.session_state.retriever, memory = st.session_state.resume_memory).run("Create an interview guideline and prepare only two questions for each topic. Make sure the questions tests the knowledge")
|
96 |
+
# llm chain for resume screen
|
97 |
+
if "resume_screen" not in st.session_state:
|
98 |
+
llm = ChatOpenAI(
|
99 |
+
model_name="gpt-3.5-turbo",
|
100 |
+
temperature=0.7, )
|
101 |
+
|
102 |
+
PROMPT = PromptTemplate(
|
103 |
+
input_variables=["history", "input"],
|
104 |
+
template= """I want you to act as an interviewer strictly following the guideline in the current conversation.
|
105 |
+
|
106 |
+
Ask me questions and wait for my answers like a human. Do not write explanations.
|
107 |
+
Candidate has no assess to the guideline.
|
108 |
+
Only ask one question at a time.
|
109 |
+
Do ask follow-up questions if you think it's necessary.
|
110 |
+
Do not ask the same question.
|
111 |
+
Do not repeat the question.
|
112 |
+
Candidate has no assess to the guideline.
|
113 |
+
You name is GPTInterviewer.
|
114 |
+
I want you to only reply as an interviewer.
|
115 |
+
Do not write all the conversation at once.
|
116 |
+
Candiate has no assess to the guideline.
|
117 |
+
|
118 |
+
Current Conversation:
|
119 |
+
{history}
|
120 |
+
|
121 |
+
Candidate: {input}
|
122 |
+
AI: """)
|
123 |
+
st.session_state.resume_screen = ConversationChain(prompt=PROMPT, llm = llm, memory = st.session_state.resume_memory)
|
124 |
+
# llm chain for generating feedback
|
125 |
+
if "resume_feedback" not in st.session_state:
|
126 |
+
llm = ChatOpenAI(
|
127 |
+
model_name="gpt-3.5-turbo",
|
128 |
+
temperature=0.5,)
|
129 |
+
st.session_state.resume_feedback = ConversationChain(
|
130 |
+
prompt=PromptTemplate(input_variables=["history","input"], template=templates.feedback_template),
|
131 |
+
llm=llm,
|
132 |
+
memory=st.session_state.resume_memory,
|
133 |
+
)
|
134 |
+
|
135 |
+
def answer_call_back():
|
136 |
+
with get_openai_callback() as cb:
|
137 |
+
human_answer = st.session_state.answer
|
138 |
+
if voice:
|
139 |
+
save_wav_file("temp/audio.wav", human_answer)
|
140 |
+
try:
|
141 |
+
input = transcribe("temp/audio.wav")
|
142 |
+
# save human_answer to history
|
143 |
+
except:
|
144 |
+
st.session_state.resume_history.append(Message("ai", "Sorry, I didn't get that."))
|
145 |
+
return "Please try again."
|
146 |
+
else:
|
147 |
+
input = human_answer
|
148 |
+
st.session_state.resume_history.append(
|
149 |
+
Message("human", input)
|
150 |
+
)
|
151 |
+
# OpenAI answer and save to history
|
152 |
+
llm_answer = st.session_state.resume_screen.run(input)
|
153 |
+
# speech synthesis and speak out
|
154 |
+
audio_file_path = synthesize_speech(llm_answer)
|
155 |
+
# create audio widget with autoplay
|
156 |
+
audio_widget = Audio(audio_file_path, autoplay=True)
|
157 |
+
# save audio data to history
|
158 |
+
st.session_state.resume_history.append(
|
159 |
+
Message("ai", llm_answer)
|
160 |
+
)
|
161 |
+
st.session_state.token_count += cb.total_tokens
|
162 |
+
return audio_widget
|
163 |
+
|
164 |
+
if position and resume:
|
165 |
+
# intialize session state
|
166 |
+
initialize_session_state_resume()
|
167 |
+
credit_card_placeholder = st.empty()
|
168 |
+
col1, col2 = st.columns(2)
|
169 |
+
with col1:
|
170 |
+
feedback = st.button("Get Interview Feedback")
|
171 |
+
with col2:
|
172 |
+
guideline = st.button("Show me interview guideline!")
|
173 |
+
chat_placeholder = st.container()
|
174 |
+
answer_placeholder = st.container()
|
175 |
+
audio = None
|
176 |
+
# if submit email adress, get interview feedback imediately
|
177 |
+
if guideline:
|
178 |
+
st.markdown(st.session_state.resume_guideline)
|
179 |
+
if feedback:
|
180 |
+
evaluation = st.session_state.resume_feedback.run("please give evalution regarding the interview")
|
181 |
+
st.markdown(evaluation)
|
182 |
+
st.download_button(label="Download Interview Feedback", data=evaluation, file_name="interview_feedback.txt")
|
183 |
+
st.stop()
|
184 |
+
else:
|
185 |
+
with answer_placeholder:
|
186 |
+
voice: bool = st.checkbox("I would like to speak with AI Interviewer!")
|
187 |
+
if voice:
|
188 |
+
answer = audio_recorder(pause_threshold=2, sample_rate=44100)
|
189 |
+
#st.warning("An UnboundLocalError will occur if the microphone fails to record.")
|
190 |
+
else:
|
191 |
+
answer = st.chat_input("Your answer")
|
192 |
+
if answer:
|
193 |
+
st.session_state['answer'] = answer
|
194 |
+
audio = answer_call_back()
|
195 |
+
|
196 |
+
with chat_placeholder:
|
197 |
+
for answer in st.session_state.resume_history:
|
198 |
+
if answer.origin == 'ai':
|
199 |
+
if auto_play and audio:
|
200 |
+
with st.chat_message("assistant"):
|
201 |
+
st.write(answer.message)
|
202 |
+
st.write(audio)
|
203 |
+
else:
|
204 |
+
with st.chat_message("assistant"):
|
205 |
+
st.write(answer.message)
|
206 |
+
else:
|
207 |
+
with st.chat_message("user"):
|
208 |
+
st.write(answer.message)
|
209 |
+
|
210 |
+
credit_card_placeholder.caption(f"""
|
211 |
+
Progress: {int(len(st.session_state.resume_history) / 30 * 100)}% completed.""")
|
212 |
+
|