lucyber/interview_evaluate
0
1import streamlit as st
2import re
3import tempfile
4import os
5from transformers import pipeline
6
7# ========================= CONFIG =========================
8st.set_page_config(page_title="AI Interview Assessment", layout="wide")
9
10HF_PHI3_MODEL = "mistralai/Mistral-7B-Instruct-v0.2"
11HF_WHISPER_MODEL = "openai/whisper-large-v3"
12
13INTERVIEW_QUESTIONS = [
14 "Can you share any specific challenges you faced while working on certification and how you overcame them?",
15 "Can you describe your experience with transfer learning in TensorFlow? How did it benefit your projects?",
16 "Describe a complex TensorFlow model you have built and the steps you took to ensure its accuracy and efficiency.",
17 "Explain how to implement dropout in a TensorFlow model and the effect it has on training.",
18 "Describe the process of building a convolutional neural network (CNN) using TensorFlow for image classification."
19]
20
21CRITERIA = (
22 "Kriteria Penilaian:\n"
23 "0 - not answer the question\n"
24 "1 - the answer is not relevan for question\n"
25 "2 - Understand for general question\n"
26 "3 - Understand with practice solution\n"
27 "4 - Deep understanding with inovative solution\n"
28)
29
30# ========================= PIPELINE CACHE =========================
31@st.cache_resource
32def get_asr_pipeline():
33 return pipeline(
34 task="automatic-speech-recognition",
35 model=HF_WHISPER_MODEL
36 )
37
38@st.cache_resource
39def get_llm_pipeline():
40 return pipeline(
41 task="text-generation",
42 model=HF_PHI3_MODEL
43 )
44
45# ========================= FUNCTIONS =========================
46def transcribe_via_hf(video_bytes):
47 """
48 Transkripsi video/audio menggunakan Whisper lokal di HF Space.
49 """
50 asr = get_asr_pipeline()
51
52 # Simpan input video sementara
53 with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp_in:
54 tmp_in.write(video_bytes)
55 tmp_in.flush()
56 tmp_in_path = tmp_in.name
57
58 # Simpan output audio sementara
59 with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp_out:
60 tmp_out_path = tmp_out.name
61
62 # Convert to WAV mono 16k
63 try:
64 subprocess.run(
65 [
66 "ffmpeg", "-y", "-i", tmp_in_path,
67 "-ac", "1", "-ar", "16000",
68 tmp_out_path
69 ],
70 stdout=subprocess.PIPE,
71 stderr=subprocess.PIPE,
72 check=True
73 )
74 except Exception as e:
75 return f"ERROR FFMPEG CONVERT: {e}"
76
77 # Transcribe WAV
78 try:
79 result = asr(tmp_out_path)
80 if isinstance(result, dict):
81 return result.get("text", "")
82 elif isinstance(result, list) and isinstance(result[0], dict):
83 return result[0].get("text", "")
84 return str(result)
85 except Exception as e:
86 return f"ERROR TRANSCRIBE: {e}"
87 finally:
88 try: os.remove(tmp_in_path)
89 except: pass
90 try: os.remove(tmp_out_path)
91 except: pass
92
93
94def phi3_api(prompt):
95 """
96 Generate text menggunakan Phi-3 lokal (pipeline HF).
97 """
98 llm = get_llm_pipeline()
99
100 try:
101 out = llm(prompt, max_new_tokens=200, do_sample=False)
102 if isinstance(out, list) and len(out) > 0:
103 return out[0]["generated_text"]
104 return str(out)
105 except Exception as e:
106 return f"ERROR: {e}"
107
108
109def prompt_for_classification(question, answer):
110 return (
111 "You are an expert HR interviewer and technical evaluator. Your task is to objectively assess the "
112 "candidate's response based solely on the provided transcript. You must classify the answer using a strict "
113 "0 until 4 scoring rubric.\n\n"
114
115 f"{CRITERIA}\n\n"
116
117 "Evaluation Rules:\n"
118 "- Evaluate ONLY based on the candidate's answer.\n"
119 "- Do NOT add missing information, assumptions, or corrections.\n"
120 "- Judge relevance, accuracy, clarity, and depth based on the rubric.\n"
121 "- Your explanation must be concise and directly tied to the rubric.\n"
122 "- You MUST follow the output format exactly.\n\n"
123
124 f"Question:\n{question}\n\n"
125 f"Candidate Answer (Transcript):\n{answer}\n\n"
126
127 "Required Output Format:\n"
128 "KLASIFIKASI: <angka>\n"
129 "ALASAN: <teks>\n"
130 )
131
132def parse_model_output(text):
133 score_match = re.search(r"KLASIFIKASI[:\- ]*([0-4])", text, re.IGNORECASE)
134 if not score_match:
135 score_match = re.search(r"\b([0-4])\b", text)
136
137 score = int(score_match.group(1)) if score_match else None
138
139 reason_match = re.search(r"ALASAN[:\-]\s*(.+)", text, re.IGNORECASE | re.DOTALL)
140 reason = reason_match.group(1).strip() if reason_match else text
141
142 return score, reason
143
144
145# ========================= SESSION INIT =========================
146for key, default in {
147 "page": "input",
148 "results": [],
149 "nama": "",
150 "processing_done": False
151}.items():
152 st.session_state.setdefault(key, default)
153
154
155# ========================= PAGE INPUT =========================
156if st.session_state.page == "input":
157 st.title("๐ฅ AI-Powered Interview Assessment System")
158 st.write("Upload **5 video interview** lalu klik mulai analisis.")
159
160 with st.form("upload_form"):
161 nama = st.text_input("Nama Pelamar")
162 uploaded = st.file_uploader(
163 "Upload 5 Video (1 โ 5)",
164 type=["mp4", "mov", "mkv", "webm"],
165 accept_multiple_files=True
166 )
167 submit = st.form_submit_button("Mulai Proses Analisis")
168
169 if submit:
170 if not nama:
171 st.error("Nama wajib diisi.")
172 elif not uploaded or len(uploaded) != 5:
173 st.error("Harap upload tepat 5 video.")
174 else:
175 st.session_state.nama = nama
176 st.session_state.uploaded = uploaded
177 st.session_state.results = []
178 st.session_state.page = "result"
179 st.session_state.processing_done = True
180 st.rerun()
181
182
183# ========================= PAGE RESULT =========================
184if st.session_state.processing_done and st.session_state.page == "result":
185 st.title("๐ Hasil Penilaian Interview")
186 st.write(f"**Nama Pelamar:** {st.session_state.nama}")
187
188 progress = st.empty()
189
190 if len(st.session_state.results) == 0:
191 for idx, vid in enumerate(st.session_state.uploaded):
192 progress.info(f"Memproses Video {idx+1}...")
193
194 bytes_data = vid.read()
195 transcript = transcribe_via_hf(bytes_data)
196 prompt = prompt_for_classification(INTERVIEW_QUESTIONS[idx], transcript)
197 raw_output = phi3_api(prompt)
198 score, reason = parse_model_output(raw_output)
199
200 st.session_state.results.append({
201 "question": INTERVIEW_QUESTIONS[idx],
202 "transcript": transcript,
203 "score": score,
204 "reason": reason,
205 "raw_model": raw_output
206 })
207
208 progress.success(f"Video {idx+1} selesai โ")
209
210 scores = [r["score"] for r in st.session_state.results if r["score"] is not None]
211
212 if len(scores) == 5:
213 final_score = sum(scores) / 5
214 st.markdown(f"### โญ Skor Akhir: **{final_score:.2f} / 4**")
215 else:
216 st.error("Skor tidak semua berhasil diproses. Cek raw output model.")
217
218 st.markdown("---")
219
220 for i, r in enumerate(st.session_state.results):
221 st.subheader(f"๐ฌ Video {i+1}")
222 st.write(f"**Pertanyaan:** {r['question']}")
223 st.write(f"**Transkrip:** {r['transcript']}")
224 st.write(f"**Skor:** {r['score']}")
225 st.write(f"**Alasan:** {r['reason']}")
226
227 with st.expander("Raw Output Model"):
228 st.code(r["raw_model"])
229
230 st.markdown("---")
231
232 if st.button("๐ Kembali"):
233 st.session_state.page = "input"
234 st.session_state.processing_done = False
235 st.session_state.results = []
236 st.session_state.nama = ""
237 st.rerun()
238 