mirror of
https://github.com/Shubhamsaboo/awesome-llm-apps.git
synced 2026-05-02 00:44:04 -05:00
Merge pull request #56 from Madhuvod/ai-recruitment-agent
Added new demo
This commit is contained in:
103
ai_agent_tutorials/ai_recruitment_agent_team/README.md
Normal file
103
ai_agent_tutorials/ai_recruitment_agent_team/README.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# AI Recruitment Agent Team
|
||||
|
||||
An Agentic recruitment system built on phidata and Streamlitthat automates the technical hiring proces which helps the lives of recruiters easy. The agent team consists of multiple specialized agents working together to handle resume analysis, interview scheduling with zoom and candidate communications.
|
||||
|
||||
## Demo
|
||||
|
||||
## Features
|
||||
|
||||
- **Automated Resume Analysis**
|
||||
- Skills Matching based on the role requirements - [AI/ML Engineer, Frontend Engineer, Backend Engineer]
|
||||
- Experience Assessment- If the resume clears 70% of the requirements, the candidate is selected for the next round
|
||||
|
||||
- **Automated Communications**
|
||||
- Acceptance Email and a Technical Interview Email
|
||||
- Rejection Feedback
|
||||
- Interview Scheduling with Zoom
|
||||
|
||||
- **Intelligent Scheduling**
|
||||
- Automated Zoom Meeting Setup
|
||||
- Timezone Management
|
||||
- Calendar Integration
|
||||
- Reminder System
|
||||
|
||||
## Important Things to do before running the application
|
||||
|
||||
- Create/Use a new Gmail account for the recruiter
|
||||
- Enable 2-Step Verification and generate an App Password for the Gmail account
|
||||
- The App Password is a 16 digit code (use without spaces) that should be generated here - [Google App Password](https://support.google.com/accounts/answer/185833?hl=en) Please go through the steps to generate the password - it will of the format - 'afec wejf awoj fwrv' (remove the spaces and enter it in the streamlit app)
|
||||
- Create/ Use a Zoom account and go to the Zoom App Marketplace to get the API credentials :
|
||||
[Zoom Marketplace](https://marketplace.zoom.us)
|
||||
- Go to Developer Dashboard and create a new app - Select Server to Server OAuth and get the credentials, You see 3 credentials - Client ID, Client Secret and Account ID
|
||||
- After that, you need to add a few scopes to the app - so that the zoom link of the candidate is sent and created through the mail.
|
||||
- The Scopes are meeting:write:invite_links:admin, meeting:write:meeting:admin, meeting:write:meeting:master, meeting:write:invite_links:master, meeting:write:open_app:admin, user:read:email:admin, user:read:list_users:admin, billing:read:user_entitlement:admin, dashboard:read:list_meeting_participants:admin [last 3 are optional]
|
||||
|
||||
## How to Run
|
||||
|
||||
1. **Setup Environment**
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git
|
||||
cd ai_agent_tutorials/ai_recruitment_agent_team
|
||||
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
2. **Configure API Keys**
|
||||
- OpenAI API key for GPT-4o access
|
||||
- Zoom API credentials (Account ID, Client ID, Client Secret)
|
||||
- Email App Password of Recruiter's Email
|
||||
|
||||
3. **Run the Application**
|
||||
```bash
|
||||
streamlit run ai_recruitment_agent_team.py
|
||||
```
|
||||
|
||||
## System Components
|
||||
|
||||
- **Resume Analyzer Agent**
|
||||
- Skills matching algorithm
|
||||
- Experience verification
|
||||
- Technical assessment
|
||||
- Selection decision making
|
||||
|
||||
- **Email Communication Agent**
|
||||
- Professional email drafting
|
||||
- Automated notifications
|
||||
- Feedback communication
|
||||
- Follow-up management
|
||||
|
||||
- **Interview Scheduler Agent**
|
||||
- Zoom meeting coordination
|
||||
- Calendar management
|
||||
- Timezone handling
|
||||
- Reminder system
|
||||
|
||||
- **Candidate Experience**
|
||||
- Simple upload interface
|
||||
- Real-time feedback
|
||||
- Clear communication
|
||||
- Streamlined process
|
||||
|
||||
## Technical Stack
|
||||
|
||||
- **Framework**: Phidata
|
||||
- **Model**: OpenAI GPT-4o
|
||||
- **Integration**: Zoom API, EmailTools Tool from Phidata
|
||||
- **PDF Processing**: PyPDF2
|
||||
- **Time Management**: pytz
|
||||
- **State Management**: Streamlit Session State
|
||||
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This tool is designed to assist in the recruitment process but should not completely replace human judgment in hiring decisions. All automated decisions should be reviewed by human recruiters for final approval.
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Integration with ATS systems
|
||||
- Advanced candidate scoring
|
||||
- Video interview capabilities
|
||||
- Skills assessment integration
|
||||
- Multi-language support
|
||||
@@ -0,0 +1,521 @@
|
||||
from typing import Literal, Tuple, Dict, Optional
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import requests
|
||||
import PyPDF2
|
||||
from datetime import datetime, timedelta
|
||||
import pytz
|
||||
|
||||
import streamlit as st
|
||||
from phi.agent import Agent
|
||||
from phi.model.openai import OpenAIChat
|
||||
from phi.tools.email import EmailTools
|
||||
from phi.tools.zoom import ZoomTool
|
||||
from phi.utils.log import logger
|
||||
from streamlit_pdf_viewer import pdf_viewer
|
||||
|
||||
|
||||
|
||||
class CustomZoomTool(ZoomTool):
|
||||
def __init__(self, *, account_id: Optional[str] = None, client_id: Optional[str] = None, client_secret: Optional[str] = None, name: str = "zoom_tool"):
|
||||
super().__init__(account_id=account_id, client_id=client_id, client_secret=client_secret, name=name)
|
||||
self.token_url = "https://zoom.us/oauth/token"
|
||||
self.access_token = None
|
||||
self.token_expires_at = 0
|
||||
|
||||
def get_access_token(self) -> str:
|
||||
if self.access_token and time.time() < self.token_expires_at:
|
||||
return str(self.access_token)
|
||||
|
||||
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
||||
data = {"grant_type": "account_credentials", "account_id": self.account_id}
|
||||
|
||||
try:
|
||||
response = requests.post(self.token_url, headers=headers, data=data, auth=(self.client_id, self.client_secret))
|
||||
response.raise_for_status()
|
||||
|
||||
token_info = response.json()
|
||||
self.access_token = token_info["access_token"]
|
||||
expires_in = token_info["expires_in"]
|
||||
self.token_expires_at = time.time() + expires_in - 60
|
||||
|
||||
self._set_parent_token(str(self.access_token))
|
||||
return str(self.access_token)
|
||||
|
||||
except requests.RequestException as e:
|
||||
logger.error(f"Error fetching access token: {e}")
|
||||
return ""
|
||||
|
||||
def _set_parent_token(self, token: str) -> None:
|
||||
"""Helper method to set the token in the parent ZoomTool class"""
|
||||
if token:
|
||||
self._ZoomTool__access_token = token
|
||||
|
||||
|
||||
# Role requirements as a constant dictionary
|
||||
ROLE_REQUIREMENTS: Dict[str, str] = {
|
||||
"ai_ml_engineer": """
|
||||
Required Skills:
|
||||
- Python, PyTorch/TensorFlow
|
||||
- Machine Learning algorithms and frameworks
|
||||
- Deep Learning and Neural Networks
|
||||
- Data preprocessing and analysis
|
||||
- MLOps and model deployment
|
||||
- RAG, LLM, Finetuning and Prompt Engineering
|
||||
""",
|
||||
|
||||
"frontend_engineer": """
|
||||
Required Skills:
|
||||
- React/Vue.js/Angular
|
||||
- HTML5, CSS3, JavaScript/TypeScript
|
||||
- Responsive design
|
||||
- State management
|
||||
- Frontend testing
|
||||
""",
|
||||
|
||||
"backend_engineer": """
|
||||
Required Skills:
|
||||
- Python/Java/Node.js
|
||||
- REST APIs
|
||||
- Database design and management
|
||||
- System architecture
|
||||
- Cloud services (AWS/GCP/Azure)
|
||||
- Kubernetes, Docker, CI/CD
|
||||
"""
|
||||
}
|
||||
|
||||
|
||||
def init_session_state() -> None:
|
||||
"""Initialize only necessary session state variables."""
|
||||
defaults = {
|
||||
'candidate_email': "", 'openai_api_key': "", 'resume_text': "", 'analysis_complete': False,
|
||||
'is_selected': False, 'zoom_account_id': "", 'zoom_client_id': "", 'zoom_client_secret': "",
|
||||
'email_sender': "", 'email_passkey': "", 'company_name': "", 'current_pdf': None
|
||||
}
|
||||
for key, value in defaults.items():
|
||||
if key not in st.session_state:
|
||||
st.session_state[key] = value
|
||||
|
||||
|
||||
def create_resume_analyzer() -> Agent:
|
||||
"""Creates and returns a resume analysis agent."""
|
||||
if not st.session_state.openai_api_key:
|
||||
st.error("Please enter your OpenAI API key first.")
|
||||
return None
|
||||
|
||||
return Agent(
|
||||
model=OpenAIChat(
|
||||
id="gpt-4o",
|
||||
api_key=st.session_state.openai_api_key
|
||||
),
|
||||
description="You are an expert technical recruiter who analyzes resumes.",
|
||||
instructions=[
|
||||
"Analyze the resume against the provided job requirements",
|
||||
"Be lenient with AI/ML candidates who show strong potential",
|
||||
"Consider project experience as valid experience",
|
||||
"Value hands-on experience with key technologies",
|
||||
"Return a JSON response with selection decision and feedback"
|
||||
],
|
||||
markdown=True
|
||||
)
|
||||
|
||||
def create_email_agent() -> Agent:
|
||||
return Agent(
|
||||
model=OpenAIChat(
|
||||
id="gpt-4o",
|
||||
api_key=st.session_state.openai_api_key
|
||||
),
|
||||
tools=[EmailTools(
|
||||
receiver_email=st.session_state.candidate_email,
|
||||
sender_email=st.session_state.email_sender,
|
||||
sender_name=st.session_state.company_name,
|
||||
sender_passkey=st.session_state.email_passkey
|
||||
)],
|
||||
description="You are a professional recruitment coordinator handling email communications.",
|
||||
instructions=[
|
||||
"Draft and send professional recruitment emails",
|
||||
"Act like a human writing an email and use all lowercase letters",
|
||||
"Maintain a friendly yet professional tone",
|
||||
"Always end emails with exactly: 'best,\nthe ai recruiting team'",
|
||||
"Never include the sender's or receiver's name in the signature",
|
||||
f"The name of the company is '{st.session_state.company_name}'"
|
||||
],
|
||||
markdown=True,
|
||||
show_tool_calls=True
|
||||
)
|
||||
|
||||
|
||||
def create_scheduler_agent() -> Agent:
|
||||
zoom_tools = CustomZoomTool(
|
||||
account_id=st.session_state.zoom_account_id,
|
||||
client_id=st.session_state.zoom_client_id,
|
||||
client_secret=st.session_state.zoom_client_secret
|
||||
)
|
||||
|
||||
return Agent(
|
||||
name="Interview Scheduler",
|
||||
model=OpenAIChat(
|
||||
id="gpt-4o",
|
||||
api_key=st.session_state.openai_api_key
|
||||
),
|
||||
tools=[zoom_tools],
|
||||
description="You are an interview scheduling coordinator.",
|
||||
instructions=[
|
||||
"You are an expert at scheduling technical interviews using Zoom.",
|
||||
"Schedule interviews during business hours (9 AM - 5 PM EST)",
|
||||
"Create meetings with proper titles and descriptions",
|
||||
"Ensure all meeting details are included in responses",
|
||||
"Use ISO 8601 format for dates",
|
||||
"Handle scheduling errors gracefully"
|
||||
],
|
||||
markdown=True,
|
||||
show_tool_calls=True
|
||||
)
|
||||
|
||||
|
||||
def extract_text_from_pdf(pdf_file) -> str:
|
||||
try:
|
||||
pdf_reader = PyPDF2.PdfReader(pdf_file)
|
||||
text = ""
|
||||
for page in pdf_reader.pages:
|
||||
text += page.extract_text()
|
||||
return text
|
||||
except Exception as e:
|
||||
st.error(f"Error extracting PDF text: {str(e)}")
|
||||
return ""
|
||||
|
||||
|
||||
def analyze_resume(
|
||||
resume_text: str,
|
||||
role: Literal["ai_ml_engineer", "frontend_engineer", "backend_engineer"],
|
||||
analyzer: Agent
|
||||
) -> Tuple[bool, str]:
|
||||
try:
|
||||
response = analyzer.run(
|
||||
f"""Please analyze this resume against the following requirements and provide your response in valid JSON format:
|
||||
Role Requirements:
|
||||
{ROLE_REQUIREMENTS[role]}
|
||||
Resume Text:
|
||||
{resume_text}
|
||||
Your response must be a valid JSON object like this:
|
||||
{{
|
||||
"selected": true/false,
|
||||
"feedback": "Detailed feedback explaining the decision",
|
||||
"matching_skills": ["skill1", "skill2"],
|
||||
"missing_skills": ["skill3", "skill4"],
|
||||
"experience_level": "junior/mid/senior"
|
||||
}}
|
||||
Evaluation criteria:
|
||||
1. Match at least 70% of required skills
|
||||
2. Consider both theoretical knowledge and practical experience
|
||||
3. Value project experience and real-world applications
|
||||
4. Consider transferable skills from similar technologies
|
||||
5. Look for evidence of continuous learning and adaptability
|
||||
Important: Return ONLY the JSON object without any markdown formatting or backticks.
|
||||
"""
|
||||
)
|
||||
|
||||
assistant_message = next((msg.content for msg in response.messages if msg.role == 'assistant'), None)
|
||||
if not assistant_message:
|
||||
raise ValueError("No assistant message found in response.")
|
||||
|
||||
result = json.loads(assistant_message.strip())
|
||||
if not isinstance(result, dict) or not all(k in result for k in ["selected", "feedback"]):
|
||||
raise ValueError("Invalid response format")
|
||||
|
||||
return result["selected"], result["feedback"]
|
||||
|
||||
except (json.JSONDecodeError, ValueError) as e:
|
||||
st.error(f"Error processing response: {str(e)}")
|
||||
return False, f"Error analyzing resume: {str(e)}"
|
||||
|
||||
|
||||
def send_selection_email(email_agent: Agent, to_email: str, role: str) -> None:
|
||||
email_agent.run(
|
||||
f"""
|
||||
Send an email to {to_email} regarding their selection for the {role} position.
|
||||
The email should:
|
||||
1. Congratulate them on being selected
|
||||
2. Explain the next steps in the process
|
||||
3. Mention that they will receive interview details shortly
|
||||
4. The name of the company is 'AI Recruiting Team'
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def send_rejection_email(email_agent: Agent, to_email: str, role: str, feedback: str) -> None:
|
||||
"""
|
||||
Send a rejection email with constructive feedback.
|
||||
"""
|
||||
email_agent.run(
|
||||
f"""
|
||||
Send an email to {to_email} regarding their application for the {role} position.
|
||||
Use this specific style:
|
||||
1. use all lowercase letters
|
||||
2. be empathetic and human
|
||||
3. mention specific feedback from: {feedback}
|
||||
4. encourage them to upskill and try again
|
||||
5. suggest some learning resources based on missing skills
|
||||
6. end the email with exactly:
|
||||
best,
|
||||
the ai recruiting team
|
||||
|
||||
Do not include any names in the signature.
|
||||
The tone should be like a human writing a quick but thoughtful email.
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def schedule_interview(scheduler: Agent, candidate_email: str, email_agent: Agent, role: str) -> None:
|
||||
"""
|
||||
Schedule interviews during business hours (9 AM - 5 PM IST).
|
||||
"""
|
||||
try:
|
||||
# Get current time in IST
|
||||
ist_tz = pytz.timezone('Asia/Kolkata')
|
||||
current_time_ist = datetime.now(ist_tz)
|
||||
|
||||
tomorrow_ist = current_time_ist + timedelta(days=1)
|
||||
interview_time = tomorrow_ist.replace(hour=11, minute=0, second=0, microsecond=0)
|
||||
formatted_time = interview_time.strftime('%Y-%m-%dT%H:%M:%S')
|
||||
|
||||
meeting_response = scheduler.run(
|
||||
f"""Schedule a 60-minute technical interview with these specifications:
|
||||
- Title: '{role} Technical Interview'
|
||||
- Date: {formatted_time}
|
||||
- Timezone: IST (India Standard Time)
|
||||
- Attendee: {candidate_email}
|
||||
|
||||
Important Notes:
|
||||
- The meeting must be between 9 AM - 5 PM IST
|
||||
- Use IST (UTC+5:30) timezone for all communications
|
||||
- Include timezone information in the meeting details
|
||||
"""
|
||||
)
|
||||
|
||||
email_agent.run(
|
||||
f"""Send an interview confirmation email with these details:
|
||||
- Role: {role} position
|
||||
- Meeting Details: {meeting_response}
|
||||
|
||||
Important:
|
||||
- Clearly specify that the time is in IST (India Standard Time)
|
||||
- Ask the candidate to join 5 minutes early
|
||||
- Include timezone conversion link if possible
|
||||
- Ask him to be confident and not so nervous and prepare well for the interview
|
||||
"""
|
||||
)
|
||||
|
||||
st.success("Interview scheduled successfully! Check your email for details.")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error scheduling interview: {str(e)}")
|
||||
st.error("Unable to schedule interview. Please try again.")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
st.title("AI Recruitment System")
|
||||
|
||||
init_session_state()
|
||||
with st.sidebar:
|
||||
st.header("Configuration")
|
||||
|
||||
# OpenAI Configuration
|
||||
st.subheader("OpenAI Settings")
|
||||
api_key = st.text_input("OpenAI API Key", type="password", value=st.session_state.openai_api_key, help="Get your API key from platform.openai.com")
|
||||
if api_key: st.session_state.openai_api_key = api_key
|
||||
|
||||
st.subheader("Zoom Settings")
|
||||
zoom_account_id = st.text_input("Zoom Account ID", type="password", value=st.session_state.zoom_account_id)
|
||||
zoom_client_id = st.text_input("Zoom Client ID", type="password", value=st.session_state.zoom_client_id)
|
||||
zoom_client_secret = st.text_input("Zoom Client Secret", type="password", value=st.session_state.zoom_client_secret)
|
||||
|
||||
st.subheader("Email Settings")
|
||||
email_sender = st.text_input("Sender Email", value=st.session_state.email_sender, help="Email address to send from")
|
||||
email_passkey = st.text_input("Email App Password", type="password", value=st.session_state.email_passkey, help="App-specific password for email")
|
||||
company_name = st.text_input("Company Name", value=st.session_state.company_name, help="Name to use in email communications")
|
||||
|
||||
if zoom_account_id: st.session_state.zoom_account_id = zoom_account_id
|
||||
if zoom_client_id: st.session_state.zoom_client_id = zoom_client_id
|
||||
if zoom_client_secret: st.session_state.zoom_client_secret = zoom_client_secret
|
||||
if email_sender: st.session_state.email_sender = email_sender
|
||||
if email_passkey: st.session_state.email_passkey = email_passkey
|
||||
if company_name: st.session_state.company_name = company_name
|
||||
|
||||
required_configs = {'OpenAI API Key': st.session_state.openai_api_key, 'Zoom Account ID': st.session_state.zoom_account_id,
|
||||
'Zoom Client ID': st.session_state.zoom_client_id, 'Zoom Client Secret': st.session_state.zoom_client_secret,
|
||||
'Email Sender': st.session_state.email_sender, 'Email Password': st.session_state.email_passkey,
|
||||
'Company Name': st.session_state.company_name}
|
||||
|
||||
missing_configs = [k for k, v in required_configs.items() if not v]
|
||||
if missing_configs:
|
||||
st.warning(f"Please configure the following in the sidebar: {', '.join(missing_configs)}")
|
||||
return
|
||||
|
||||
if not st.session_state.openai_api_key:
|
||||
st.warning("Please enter your OpenAI API key in the sidebar to continue.")
|
||||
return
|
||||
|
||||
role = st.selectbox("Select the role you're applying for:", ["ai_ml_engineer", "frontend_engineer", "backend_engineer"])
|
||||
with st.expander("View Required Skills", expanded=True): st.markdown(ROLE_REQUIREMENTS[role])
|
||||
|
||||
# Add a "New Application" button before the resume upload
|
||||
if st.button("📝 New Application"):
|
||||
# Clear only the application-related states
|
||||
keys_to_clear = ['resume_text', 'analysis_complete', 'is_selected', 'candidate_email', 'current_pdf']
|
||||
for key in keys_to_clear:
|
||||
if key in st.session_state:
|
||||
st.session_state[key] = None if key == 'current_pdf' else ""
|
||||
st.rerun()
|
||||
|
||||
resume_file = st.file_uploader("Upload your resume (PDF)", type=["pdf"], key="resume_uploader")
|
||||
if resume_file is not None and resume_file != st.session_state.get('current_pdf'):
|
||||
st.session_state.current_pdf = resume_file
|
||||
st.session_state.resume_text = ""
|
||||
st.session_state.analysis_complete = False
|
||||
st.session_state.is_selected = False
|
||||
st.rerun()
|
||||
|
||||
if resume_file:
|
||||
st.subheader("Uploaded Resume")
|
||||
col1, col2 = st.columns([4, 1])
|
||||
|
||||
with col1:
|
||||
import tempfile, os
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp_file:
|
||||
tmp_file.write(resume_file.read())
|
||||
tmp_file_path = tmp_file.name
|
||||
resume_file.seek(0)
|
||||
try: pdf_viewer(tmp_file_path)
|
||||
finally: os.unlink(tmp_file_path)
|
||||
|
||||
with col2:
|
||||
st.download_button(label="📥 Download", data=resume_file, file_name=resume_file.name, mime="application/pdf")
|
||||
# Process the resume text
|
||||
if not st.session_state.resume_text:
|
||||
with st.spinner("Processing your resume..."):
|
||||
resume_text = extract_text_from_pdf(resume_file)
|
||||
if resume_text:
|
||||
st.session_state.resume_text = resume_text
|
||||
st.success("Resume processed successfully!")
|
||||
else:
|
||||
st.error("Could not process the PDF. Please try again.")
|
||||
|
||||
# Email input with session state
|
||||
email = st.text_input(
|
||||
"Candidate's email address",
|
||||
value=st.session_state.candidate_email,
|
||||
key="email_input"
|
||||
)
|
||||
st.session_state.candidate_email = email
|
||||
|
||||
# Analysis and next steps
|
||||
if st.session_state.resume_text and email and not st.session_state.analysis_complete:
|
||||
if st.button("Analyze Resume"):
|
||||
with st.spinner("Analyzing your resume..."):
|
||||
resume_analyzer = create_resume_analyzer()
|
||||
email_agent = create_email_agent() # Create email agent here
|
||||
|
||||
if resume_analyzer and email_agent:
|
||||
print("DEBUG: Starting resume analysis")
|
||||
is_selected, feedback = analyze_resume(
|
||||
st.session_state.resume_text,
|
||||
role,
|
||||
resume_analyzer
|
||||
)
|
||||
print(f"DEBUG: Analysis complete - Selected: {is_selected}, Feedback: {feedback}")
|
||||
|
||||
if is_selected:
|
||||
st.success("Congratulations! Your skills match our requirements.")
|
||||
st.session_state.analysis_complete = True
|
||||
st.session_state.is_selected = True
|
||||
st.rerun()
|
||||
else:
|
||||
st.warning("Unfortunately, your skills don't match our requirements.")
|
||||
st.write(f"Feedback: {feedback}")
|
||||
|
||||
# Send rejection email
|
||||
with st.spinner("Sending feedback email..."):
|
||||
try:
|
||||
send_rejection_email(
|
||||
email_agent=email_agent,
|
||||
to_email=email,
|
||||
role=role,
|
||||
feedback=feedback
|
||||
)
|
||||
st.info("We've sent you an email with detailed feedback.")
|
||||
except Exception as e:
|
||||
logger.error(f"Error sending rejection email: {e}")
|
||||
st.error("Could not send feedback email. Please try again.")
|
||||
|
||||
if st.session_state.get('analysis_complete') and st.session_state.get('is_selected', False):
|
||||
st.success("Congratulations! Your skills match our requirements.")
|
||||
st.info("Click 'Proceed with Application' to continue with the interview process.")
|
||||
|
||||
if st.button("Proceed with Application", key="proceed_button"):
|
||||
print("DEBUG: Proceed button clicked") # Debug
|
||||
with st.spinner("🔄 Processing your application..."):
|
||||
try:
|
||||
print("DEBUG: Creating email agent") # Debug
|
||||
email_agent = create_email_agent()
|
||||
print(f"DEBUG: Email agent created: {email_agent}") # Debug
|
||||
|
||||
print("DEBUG: Creating scheduler agent") # Debug
|
||||
scheduler_agent = create_scheduler_agent()
|
||||
print(f"DEBUG: Scheduler agent created: {scheduler_agent}") # Debug
|
||||
|
||||
# 3. Send selection email
|
||||
with st.status("📧 Sending confirmation email...", expanded=True) as status:
|
||||
print(f"DEBUG: Attempting to send email to {st.session_state.candidate_email}") # Debug
|
||||
send_selection_email(
|
||||
email_agent,
|
||||
st.session_state.candidate_email,
|
||||
role
|
||||
)
|
||||
print("DEBUG: Email sent successfully") # Debug
|
||||
status.update(label="✅ Confirmation email sent!")
|
||||
|
||||
# 4. Schedule interview
|
||||
with st.status("📅 Scheduling interview...", expanded=True) as status:
|
||||
print("DEBUG: Attempting to schedule interview") # Debug
|
||||
schedule_interview(
|
||||
scheduler_agent,
|
||||
st.session_state.candidate_email,
|
||||
email_agent,
|
||||
role
|
||||
)
|
||||
print("DEBUG: Interview scheduled successfully") # Debug
|
||||
status.update(label="✅ Interview scheduled!")
|
||||
|
||||
print("DEBUG: All processes completed successfully") # Debug
|
||||
st.success("""
|
||||
🎉 Application Successfully Processed!
|
||||
|
||||
Please check your email for:
|
||||
1. Selection confirmation ✅
|
||||
2. Interview details with Zoom link 🔗
|
||||
|
||||
Next steps:
|
||||
1. Review the role requirements
|
||||
2. Prepare for your technical interview
|
||||
3. Join the interview 5 minutes early
|
||||
""")
|
||||
|
||||
except Exception as e:
|
||||
print(f"DEBUG: Error occurred: {str(e)}") # Debug
|
||||
print(f"DEBUG: Error type: {type(e)}") # Debug
|
||||
import traceback
|
||||
print(f"DEBUG: Full traceback: {traceback.format_exc()}") # Debug
|
||||
st.error(f"An error occurred: {str(e)}")
|
||||
st.error("Please try again or contact support.")
|
||||
|
||||
# Reset button
|
||||
if st.sidebar.button("Reset Application"):
|
||||
for key in st.session_state.keys():
|
||||
if key != 'openai_api_key':
|
||||
del st.session_state[key]
|
||||
st.rerun()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,12 @@
|
||||
# Core dependencies
|
||||
phidata==2.7.3
|
||||
streamlit==1.40.2
|
||||
PyPDF2==3.0.1
|
||||
streamlit-pdf-viewer==0.0.19
|
||||
requests==2.32.3
|
||||
pytz==2023.4
|
||||
typing-extensions>=4.9.0
|
||||
|
||||
# Optional but recommended
|
||||
black>=24.1.1 # for code formatting
|
||||
python-dateutil>=2.8.2 # for date parsing
|
||||
Reference in New Issue
Block a user