aswinprasath31's picture
Update src/app.py
ff091d9 verified
Raw History Blame Contribute Delete
5.88 kB
import os
import streamlit as st
import pandas as pd
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
import torch
from sentence_transformers import SentenceTransformer, util
# --- CONFIGURATION ---
NUM_QUESTIONS_TO_ASK = 5 # <--- NEW: Set the limit here
# --- HF cache setup in a writable folder (/tmp is safe in Docker) ---
HF_CACHE = "/tmp/hf_cache"
os.makedirs(HF_CACHE, exist_ok=True)
os.environ["TRANSFORMERS_CACHE"] = HF_CACHE
os.environ["HF_HOME"] = HF_CACHE
os.environ["HF_DATASETS_CACHE"] = HF_CACHE
os.environ["HF_MODULES_CACHE"] = HF_CACHE
st.set_page_config(page_title="Interviewer Bot", layout="wide")
st.title("πŸ“ Interviewer Bot")
st.write(f"AI-powered Interviewer Bot for {NUM_QUESTIONS_TO_ASK} random questions.")
# --- Load fine-tuned model (Explicitly passing cache_dir & fixing FutureWarnings) ---
@st.cache_resource(show_spinner=True)
def load_model(model_name, cache_dir):
# FIX: Explicitly setting clean_up_tokenization_spaces to suppress FutureWarning
tokenizer = AutoTokenizer.from_pretrained(
model_name,
cache_dir=cache_dir,
clean_up_tokenization_spaces=True
)
model = AutoModelForSeq2SeqLM.from_pretrained(model_name, cache_dir=cache_dir)
return tokenizer, model
MODEL_NAME = "aswinprasath31/interviewer-bot-finetuned-v1"
tokenizer, model = load_model(MODEL_NAME, HF_CACHE)
# --- Load and preprocess questions from a CSV file (Data Fix for combined Q&A) ---
@st.cache_data
def load_questions(csv_path="training_data.csv"):
df = pd.read_csv(csv_path, header=None)
if df.iloc[0, 0] == 'test':
df = df.iloc[1:].copy()
df[['Question', 'Answer']] = df[0].str.split('Answer:', expand=True)
df['Question'] = df['Question'].str.replace('Question:', '').str.strip()
df['Answer'] = df['Answer'].str.strip()
df.dropna(subset=['Question'], inplace=True)
return df[['Question', 'Answer']].reset_index(drop=True)
# Load the full question set once
full_questions_df = load_questions()
# --- Session state management and Random Sampling (CRITICAL CHANGE) ---
if "sampled_questions" not in st.session_state:
# Randomly sample the specified number of questions (without replacement)
# The 'min' ensures we don't crash if the CSV has fewer than 5 questions
N = min(NUM_QUESTIONS_TO_ASK, len(full_questions_df))
st.session_state.sampled_questions = full_questions_df.sample(n=N).reset_index(drop=True)
if "q_index" not in st.session_state:
st.session_state.q_index = 0
if "responses" not in st.session_state:
st.session_state.responses = []
# Now, all subsequent logic uses the sampled_questions dataframe
questions_df = st.session_state.sampled_questions
num_questions = len(questions_df)
# --- Sentence transformer for similarity scoring (Explicitly passing cache_dir) ---
@st.cache_resource(show_spinner=False)
def get_embedder(cache_folder):
return SentenceTransformer('all-MiniLM-L6-v2', cache_folder=cache_folder)
embedder = get_embedder(HF_CACHE)
def get_reference_answer(question, max_len=200):
inputs = tokenizer(question, return_tensors="pt")
with torch.no_grad():
outputs = model.generate(**inputs, max_new_tokens=max_len)
return tokenizer.decode(outputs[0], skip_special_tokens=True)
def compute_similarity(candidate_answer, reference_answer):
candidate_emb = embedder.encode(candidate_answer, convert_to_tensor=True)
reference_emb = embedder.encode(reference_answer, convert_to_tensor=True)
score = util.cos_sim(candidate_emb, reference_emb).item()
return round(score * 10, 2) # scale 0-10
# --- UI Layout ---
if st.session_state.q_index < num_questions:
st.progress(st.session_state.q_index / num_questions)
# Access the current question from the sampled list
question = questions_df.iloc[st.session_state.q_index]["Question"]
st.subheader(f"Question {st.session_state.q_index + 1}/{num_questions}")
st.write(f"πŸ’‘ {question}")
candidate_answer = st.text_area("Your Answer:")
if st.button("Submit Answer"):
if candidate_answer.strip() == "":
st.warning("⚠️ Please enter an answer before submitting.")
else:
# Generate the reference answer using your fine-tuned LLM
reference_answer = get_reference_answer(question)
score = compute_similarity(candidate_answer, reference_answer)
st.success("βœ… Reference Answer:")
st.write(reference_answer)
st.info(f"πŸ“ Score (0-10) based on similarity: {score}")
# Save response
st.session_state.responses.append({
"Question": question,
"Candidate Answer": candidate_answer,
"Reference Answer": reference_answer,
"Score": score
})
# Move to the next question
st.session_state.q_index += 1
st.rerun() # Rerun to display the next question or the completion message
else:
# Completion message
st.balloons()
st.success("πŸŽ‰ Interview completed!")
# Display results and download button
all_responses = pd.DataFrame(st.session_state.responses)
avg_score = all_responses['Score'].mean()
st.markdown(f"### Overall Average Score: **{avg_score:.2f}**")
st.dataframe(all_responses, hide_index=True)
csv = all_responses.to_csv(index=False).encode('utf-8')
st.download_button(
"πŸ“₯ Download Responses CSV",
data=csv,
file_name="interview_responses.csv",
type="primary"
)
# Optional: Reset button to start a new random interview
if st.button("Start New Interview"):
del st.session_state.sampled_questions
del st.session_state.q_index
del st.session_state.responses
st.rerun()