final code changes
This commit is contained in:
parent
42cee9621a
commit
abc0c10e55
1 changed files with 137 additions and 123 deletions
|
|
@ -5,6 +5,7 @@ import json
|
||||||
import requests
|
import requests
|
||||||
import PyPDF2
|
import PyPDF2
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
import pytz
|
||||||
|
|
||||||
import streamlit as st
|
import streamlit as st
|
||||||
from phi.agent import Agent
|
from phi.agent import Agent
|
||||||
|
|
@ -13,33 +14,14 @@ from phi.tools.email import EmailTools
|
||||||
from phi.tools.zoom import ZoomTool
|
from phi.tools.zoom import ZoomTool
|
||||||
from phi.utils.log import logger
|
from phi.utils.log import logger
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
from streamlit_pdf_viewer import pdf_viewer
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
# Constants
|
|
||||||
FROM_EMAIL = "ryomensukuna64@gmail.com"
|
|
||||||
|
|
||||||
ACCOUNT_ID = os.getenv("ZOOM_ACCOUNT_ID")
|
|
||||||
CLIENT_ID = os.getenv("ZOOM_CLIENT_ID")
|
|
||||||
CLIENT_SECRET = os.getenv("ZOOM_CLIENT_SECRET")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CustomZoomTool(ZoomTool):
|
class CustomZoomTool(ZoomTool):
|
||||||
def __init__(
|
def __init__(self, *, account_id: Optional[str] = None, client_id: Optional[str] = None, client_secret: Optional[str] = None, name: str = "zoom_tool"):
|
||||||
self,
|
super().__init__(account_id=account_id, client_id=client_id, client_secret=client_secret, name=name)
|
||||||
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.token_url = "https://zoom.us/oauth/token"
|
||||||
self.access_token = None
|
self.access_token = None
|
||||||
self.token_expires_at = 0
|
self.token_expires_at = 0
|
||||||
|
|
@ -47,16 +29,12 @@ class CustomZoomTool(ZoomTool):
|
||||||
def get_access_token(self) -> str:
|
def get_access_token(self) -> str:
|
||||||
if self.access_token and time.time() < self.token_expires_at:
|
if self.access_token and time.time() < self.token_expires_at:
|
||||||
return str(self.access_token)
|
return str(self.access_token)
|
||||||
|
|
||||||
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
||||||
data = {"grant_type": "account_credentials", "account_id": self.account_id}
|
data = {"grant_type": "account_credentials", "account_id": self.account_id}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.post(
|
response = requests.post(self.token_url, headers=headers, data=data, auth=(self.client_id, self.client_secret))
|
||||||
self.token_url,
|
|
||||||
headers=headers,
|
|
||||||
data=data,
|
|
||||||
auth=(self.client_id, self.client_secret) # Use basic auth
|
|
||||||
)
|
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
token_info = response.json()
|
token_info = response.json()
|
||||||
|
|
@ -64,7 +42,6 @@ class CustomZoomTool(ZoomTool):
|
||||||
expires_in = token_info["expires_in"]
|
expires_in = token_info["expires_in"]
|
||||||
self.token_expires_at = time.time() + expires_in - 60
|
self.token_expires_at = time.time() + expires_in - 60
|
||||||
|
|
||||||
# Update this line to use the helper method
|
|
||||||
self._set_parent_token(str(self.access_token))
|
self._set_parent_token(str(self.access_token))
|
||||||
return str(self.access_token)
|
return str(self.access_token)
|
||||||
|
|
||||||
|
|
@ -114,11 +91,9 @@ ROLE_REQUIREMENTS: Dict[str, str] = {
|
||||||
def init_session_state() -> None:
|
def init_session_state() -> None:
|
||||||
"""Initialize only necessary session state variables."""
|
"""Initialize only necessary session state variables."""
|
||||||
defaults = {
|
defaults = {
|
||||||
'candidate_email': "",
|
'candidate_email': "", 'openai_api_key': "", 'resume_text': "", 'analysis_complete': False,
|
||||||
'openai_api_key': "",
|
'is_selected': False, 'zoom_account_id': "", 'zoom_client_id': "", 'zoom_client_secret': "",
|
||||||
'resume_text': "",
|
'email_sender': "", 'email_passkey': "", 'company_name': ""
|
||||||
'analysis_complete': False,
|
|
||||||
'is_selected': False
|
|
||||||
}
|
}
|
||||||
for key, value in defaults.items():
|
for key, value in defaults.items():
|
||||||
if key not in st.session_state:
|
if key not in st.session_state:
|
||||||
|
|
@ -155,9 +130,9 @@ def create_email_agent() -> Agent:
|
||||||
),
|
),
|
||||||
tools=[EmailTools(
|
tools=[EmailTools(
|
||||||
receiver_email=st.session_state.candidate_email,
|
receiver_email=st.session_state.candidate_email,
|
||||||
sender_email=FROM_EMAIL,
|
sender_email=st.session_state.email_sender,
|
||||||
sender_name="AI Recruitment Team",
|
sender_name=st.session_state.company_name,
|
||||||
sender_passkey=os.getenv("EMAIL_PASSKEY")
|
sender_passkey=st.session_state.email_passkey
|
||||||
)],
|
)],
|
||||||
description="You are a professional recruitment coordinator handling email communications.",
|
description="You are a professional recruitment coordinator handling email communications.",
|
||||||
instructions=[
|
instructions=[
|
||||||
|
|
@ -166,7 +141,7 @@ def create_email_agent() -> Agent:
|
||||||
"Maintain a friendly yet professional tone",
|
"Maintain a friendly yet professional tone",
|
||||||
"Always end emails with exactly: 'best,\nthe ai recruiting team'",
|
"Always end emails with exactly: 'best,\nthe ai recruiting team'",
|
||||||
"Never include the sender's or receiver's name in the signature",
|
"Never include the sender's or receiver's name in the signature",
|
||||||
"The name of the company is 'AI Recruiting Team'"
|
f"The name of the company is '{st.session_state.company_name}'"
|
||||||
],
|
],
|
||||||
markdown=True,
|
markdown=True,
|
||||||
show_tool_calls=True
|
show_tool_calls=True
|
||||||
|
|
@ -174,8 +149,11 @@ def create_email_agent() -> Agent:
|
||||||
|
|
||||||
|
|
||||||
def create_scheduler_agent() -> Agent:
|
def create_scheduler_agent() -> Agent:
|
||||||
|
zoom_tools = CustomZoomTool(
|
||||||
zoom_tools = CustomZoomTool(account_id=ACCOUNT_ID, client_id=CLIENT_ID, client_secret=CLIENT_SECRET)
|
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(
|
return Agent(
|
||||||
name="Interview Scheduler",
|
name="Interview Scheduler",
|
||||||
|
|
@ -216,8 +194,8 @@ def analyze_resume(
|
||||||
analyzer: Agent
|
analyzer: Agent
|
||||||
) -> Tuple[bool, str]:
|
) -> Tuple[bool, str]:
|
||||||
try:
|
try:
|
||||||
response = analyzer.run(f"""
|
response = analyzer.run(
|
||||||
Please analyze this resume against the following requirements and provide your response in valid JSON format:
|
f"""Please analyze this resume against the following requirements and provide your response in valid JSON format:
|
||||||
Role Requirements:
|
Role Requirements:
|
||||||
{ROLE_REQUIREMENTS[role]}
|
{ROLE_REQUIREMENTS[role]}
|
||||||
Resume Text:
|
Resume Text:
|
||||||
|
|
@ -237,24 +215,16 @@ def analyze_resume(
|
||||||
4. Consider transferable skills from similar technologies
|
4. Consider transferable skills from similar technologies
|
||||||
5. Look for evidence of continuous learning and adaptability
|
5. Look for evidence of continuous learning and adaptability
|
||||||
Important: Return ONLY the JSON object without any markdown formatting or backticks.
|
Important: Return ONLY the JSON object without any markdown formatting or backticks.
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
# Extract the assistant's message content
|
|
||||||
assistant_message = None
|
|
||||||
for message in response.messages:
|
|
||||||
if message.role == 'assistant':
|
|
||||||
assistant_message = message.content
|
|
||||||
break
|
|
||||||
|
|
||||||
|
assistant_message = next((msg.content for msg in response.messages if msg.role == 'assistant'), None)
|
||||||
if not assistant_message:
|
if not assistant_message:
|
||||||
raise ValueError("No assistant message found in response.")
|
raise ValueError("No assistant message found in response.")
|
||||||
|
|
||||||
result = json.loads(assistant_message.strip())
|
result = json.loads(assistant_message.strip())
|
||||||
|
if not isinstance(result, dict) or not all(k in result for k in ["selected", "feedback"]):
|
||||||
if not isinstance(result, dict):
|
raise ValueError("Invalid response format")
|
||||||
raise ValueError("Response is not a JSON object")
|
|
||||||
|
|
||||||
if "selected" not in result or "feedback" not in result:
|
|
||||||
raise ValueError("Missing required fields in response")
|
|
||||||
|
|
||||||
return result["selected"], result["feedback"]
|
return result["selected"], result["feedback"]
|
||||||
|
|
||||||
|
|
@ -301,29 +271,45 @@ def send_rejection_email(email_agent: Agent, to_email: str, role: str, feedback:
|
||||||
|
|
||||||
def schedule_interview(scheduler: Agent, candidate_email: str, email_agent: Agent, role: str) -> None:
|
def schedule_interview(scheduler: Agent, candidate_email: str, email_agent: Agent, role: str) -> None:
|
||||||
"""
|
"""
|
||||||
Basic interview scheduler that creates a Zoom meeting and sends email details.
|
Schedule interviews during business hours (9 AM - 5 PM IST).
|
||||||
Schedules interviews during business hours (9 AM - 5 PM EST).
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Calculate tomorrow at 2 PM EST (instead of UTC)
|
# Get current time in IST
|
||||||
tomorrow = datetime.now() + timedelta(days=1)
|
ist_tz = pytz.timezone('Asia/Kolkata')
|
||||||
interview_time = tomorrow.replace(hour=14, minute=0, second=0, microsecond=0)
|
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')
|
||||||
|
|
||||||
# 1. Schedule the meeting with EST timezone specification
|
|
||||||
meeting_response = scheduler.run(
|
meeting_response = scheduler.run(
|
||||||
f"Schedule a 60-minute meeting titled '{role} Technical Interview' "
|
f"""Schedule a 60-minute technical interview with these specifications:
|
||||||
f"for tomorrow at 2 PM EST (Eastern Time) 2024 with attendee {candidate_email}. "
|
- Title: '{role} Technical Interview'
|
||||||
f"Ensure the meeting is between 9 AM - 5 PM EST only."
|
- 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
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
# 2. Send email notification
|
|
||||||
email_agent.run(
|
email_agent.run(
|
||||||
f"Send an email to {candidate_email} about their scheduled interview for "
|
f"""Send an interview confirmation email with these details:
|
||||||
f"the {role} position. Include the Zoom meeting link from: {meeting_response}. "
|
- Role: {role} position
|
||||||
f"Make sure to specify that the time is in EST timezone."
|
- 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!")
|
st.success("Interview scheduled successfully! Check your email for details.")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error scheduling interview: {str(e)}")
|
logger.error(f"Error scheduling interview: {str(e)}")
|
||||||
|
|
@ -331,42 +317,70 @@ def schedule_interview(scheduler: Agent, candidate_email: str, email_agent: Agen
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""Main function to run the Streamlit application."""
|
|
||||||
st.title("AI Recruitment System")
|
st.title("AI Recruitment System")
|
||||||
|
|
||||||
# Initialize session state
|
|
||||||
init_session_state()
|
init_session_state()
|
||||||
|
|
||||||
# Sidebar for API key
|
|
||||||
with st.sidebar:
|
with st.sidebar:
|
||||||
st.header("Configuration")
|
st.header("Configuration")
|
||||||
api_key = st.text_input(
|
|
||||||
"Enter your 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
|
|
||||||
|
|
||||||
# Main content
|
# 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:
|
if not st.session_state.openai_api_key:
|
||||||
st.warning("Please enter your OpenAI API key in the sidebar to continue.")
|
st.warning("Please enter your OpenAI API key in the sidebar to continue.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Role selection with requirements display
|
role = st.selectbox("Select the role you're applying for:", ["ai_ml_engineer", "frontend_engineer", "backend_engineer"])
|
||||||
role = st.selectbox(
|
with st.expander("View Required Skills", expanded=True): st.markdown(ROLE_REQUIREMENTS[role])
|
||||||
"Select the role you're applying for:",
|
|
||||||
["ai_ml_engineer", "frontend_engineer", "backend_engineer"]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Display requirements for selected role
|
|
||||||
with st.expander("View Required Skills", expanded=True):
|
|
||||||
st.markdown(ROLE_REQUIREMENTS[role])
|
|
||||||
|
|
||||||
# Resume upload and processing
|
|
||||||
resume_file = st.file_uploader("Upload your resume (PDF)", type=["pdf"])
|
resume_file = st.file_uploader("Upload your resume (PDF)", type=["pdf"])
|
||||||
if resume_file and not st.session_state.resume_text:
|
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")
|
||||||
|
if st.button("🗑️ Clear"): st.session_state.resume_text = ""; st.rerun()
|
||||||
|
# Process the resume text
|
||||||
|
if not st.session_state.resume_text:
|
||||||
with st.spinner("Processing your resume..."):
|
with st.spinner("Processing your resume..."):
|
||||||
resume_text = extract_text_from_pdf(resume_file)
|
resume_text = extract_text_from_pdf(resume_file)
|
||||||
if resume_text:
|
if resume_text:
|
||||||
|
|
@ -377,7 +391,7 @@ def main() -> None:
|
||||||
|
|
||||||
# Email input with session state
|
# Email input with session state
|
||||||
email = st.text_input(
|
email = st.text_input(
|
||||||
"Your email address",
|
"Candidate's email address",
|
||||||
value=st.session_state.candidate_email,
|
value=st.session_state.candidate_email,
|
||||||
key="email_input"
|
key="email_input"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue