Update app.py
Browse files
app.py
CHANGED
@@ -12,6 +12,10 @@ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
|
12 |
PROXYCURL_API_KEY = os.getenv("PROXYCURL_API_KEY")
|
13 |
FIRECRAWL_API_KEY = os.getenv("FIRECRAWL_API_KEY")
|
14 |
|
|
|
|
|
|
|
|
|
15 |
# Function to fetch LinkedIn data using the Proxycurl API
|
16 |
def fetch_linkedin_data(linkedin_url):
|
17 |
api_key = os.getenv("PROXYCURL_API_KEY")
|
@@ -19,16 +23,20 @@ def fetch_linkedin_data(linkedin_url):
|
|
19 |
api_endpoint = 'https://nubela.co/proxycurl/api/v2/linkedin'
|
20 |
|
21 |
logging.info("Fetching LinkedIn data...")
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
|
|
|
|
|
|
|
|
32 |
|
33 |
# Function to fetch company information using Firecrawl API
|
34 |
def fetch_company_info(company_url):
|
@@ -48,53 +56,60 @@ def fetch_company_info(company_url):
|
|
48 |
}
|
49 |
|
50 |
logging.info("Fetching company information...")
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
|
|
|
|
|
|
|
|
58 |
|
59 |
# Function to structure the email dynamically based on inputs and fetched data
|
60 |
def structure_email(user_data, linkedin_info, company_info):
|
61 |
-
#
|
62 |
-
linkedin_role = linkedin_info.get('current_role', '
|
63 |
-
linkedin_skills = linkedin_info.get('skills', '
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
f"
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
f"
|
80 |
-
|
81 |
-
|
82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
83 |
|
84 |
-
# Function to validate the generated email for
|
85 |
def validate_email(email_content):
|
86 |
logging.info("Validating email content...")
|
87 |
logging.info(f"Email Content for Validation: {email_content}")
|
88 |
|
89 |
-
# Check if
|
90 |
-
|
91 |
-
"skills" in email_content and
|
92 |
-
"contribute" in email_content):
|
93 |
-
logging.info("Email content validation passed.")
|
94 |
-
return True
|
95 |
-
else:
|
96 |
-
logging.info("Email content validation failed.")
|
97 |
-
return False
|
98 |
|
99 |
# Function to generate email content using Nvidia Nemotron LLM (non-streaming for simplicity)
|
100 |
def generate_email_content(api_key, prompt):
|
@@ -113,13 +128,12 @@ def generate_email_content(api_key, prompt):
|
|
113 |
temperature=0.5,
|
114 |
top_p=1,
|
115 |
max_tokens=1024,
|
116 |
-
stream=False
|
117 |
)
|
118 |
|
119 |
if hasattr(response, 'choices') and len(response.choices) > 0:
|
120 |
email_content = response.choices[0].message.content
|
121 |
logging.info("Email content generated successfully.")
|
122 |
-
logging.info(f"Generated Email Content: {email_content}")
|
123 |
return email_content
|
124 |
else:
|
125 |
logging.error("Error: No choices found in the response.")
|
|
|
12 |
PROXYCURL_API_KEY = os.getenv("PROXYCURL_API_KEY")
|
13 |
FIRECRAWL_API_KEY = os.getenv("FIRECRAWL_API_KEY")
|
14 |
|
15 |
+
# Function to sanitize data by ensuring it's safe and clean for use
|
16 |
+
def sanitize_data(data, default_value=""):
|
17 |
+
return data.strip() if isinstance(data, str) and data.strip() else default_value
|
18 |
+
|
19 |
# Function to fetch LinkedIn data using the Proxycurl API
|
20 |
def fetch_linkedin_data(linkedin_url):
|
21 |
api_key = os.getenv("PROXYCURL_API_KEY")
|
|
|
23 |
api_endpoint = 'https://nubela.co/proxycurl/api/v2/linkedin'
|
24 |
|
25 |
logging.info("Fetching LinkedIn data...")
|
26 |
+
try:
|
27 |
+
response = requests.get(api_endpoint,
|
28 |
+
params={'url': linkedin_url},
|
29 |
+
headers=headers,
|
30 |
+
timeout=10)
|
31 |
+
if response.status_code == 200:
|
32 |
+
logging.info("LinkedIn data fetched successfully.")
|
33 |
+
return response.json()
|
34 |
+
else:
|
35 |
+
logging.error(f"Error fetching LinkedIn data: {response.text}")
|
36 |
+
return {"error": f"Error fetching LinkedIn data: {response.text}"}
|
37 |
+
except Exception as e:
|
38 |
+
logging.error(f"Exception during LinkedIn data fetch: {e}")
|
39 |
+
return {"error": f"Exception during LinkedIn data fetch: {e}"}
|
40 |
|
41 |
# Function to fetch company information using Firecrawl API
|
42 |
def fetch_company_info(company_url):
|
|
|
56 |
}
|
57 |
|
58 |
logging.info("Fetching company information...")
|
59 |
+
try:
|
60 |
+
response = requests.post(api_endpoint, json=data, headers=headers, timeout=15)
|
61 |
+
if response.status_code == 200:
|
62 |
+
logging.info("Company information fetched successfully.")
|
63 |
+
return response.json()
|
64 |
+
else:
|
65 |
+
logging.error(f"Error fetching company information: {response.text}")
|
66 |
+
return {"error": f"Error fetching company information: {response.text}"}
|
67 |
+
except Exception as e:
|
68 |
+
logging.error(f"Exception during company info fetch: {e}")
|
69 |
+
return {"error": f"Exception during company info fetch: {e}"}
|
70 |
|
71 |
# Function to structure the email dynamically based on inputs and fetched data
|
72 |
def structure_email(user_data, linkedin_info, company_info):
|
73 |
+
# Sanitize and extract the required information
|
74 |
+
linkedin_role = sanitize_data(linkedin_info.get('current_role', user_data['role']))
|
75 |
+
linkedin_skills = sanitize_data(linkedin_info.get('skills', 'relevant skills'))
|
76 |
+
linkedin_industry = sanitize_data(linkedin_info.get('industry', 'the industry'))
|
77 |
+
company_name = sanitize_data(user_data['company_url'] or company_info.get('company_name', 'the company'))
|
78 |
+
company_mission = sanitize_data(company_info.get('mission', f"{company_name}'s mission"))
|
79 |
+
company_goal = sanitize_data(company_info.get('goal', 'achieving excellence'))
|
80 |
+
|
81 |
+
# Construct the email dynamically based on the sanitized and available data
|
82 |
+
email_body = f"Dear Hiring Manager,\n\n"
|
83 |
+
|
84 |
+
# Introduction and relevance
|
85 |
+
email_body += f"I am writing to express my interest in the {sanitize_data(user_data['role'])} position at {company_name}. "
|
86 |
+
if company_mission:
|
87 |
+
email_body += f"{company_mission} strongly aligns with my passion and experience in {linkedin_industry}. "
|
88 |
+
|
89 |
+
# Skills and experience section
|
90 |
+
if linkedin_role:
|
91 |
+
email_body += f"As a {linkedin_role}, I have honed my skills in {linkedin_skills}. These experiences make me confident that I can contribute effectively to your team. "
|
92 |
+
else:
|
93 |
+
email_body += f"My experience in {linkedin_skills} equips me with the skills necessary to excel in this role. "
|
94 |
+
|
95 |
+
# Contribution and impact section
|
96 |
+
if company_goal:
|
97 |
+
email_body += f"I am excited about the opportunity to bring my expertise to {company_name} and support your team in achieving {company_goal}. "
|
98 |
+
|
99 |
+
# Call to action
|
100 |
+
email_body += f"I would welcome the chance to discuss how my background and skills align with the needs of your organization. "
|
101 |
+
email_body += "Thank you for your time and consideration. I look forward to the possibility of contributing to your team.\n\n"
|
102 |
+
email_body += f"Best regards,\n{sanitize_data(user_data['name'])}"
|
103 |
+
|
104 |
+
return email_body
|
105 |
|
106 |
+
# Function to validate the generated email for completeness and professionalism
|
107 |
def validate_email(email_content):
|
108 |
logging.info("Validating email content...")
|
109 |
logging.info(f"Email Content for Validation: {email_content}")
|
110 |
|
111 |
+
# Check if essential elements exist in the email content
|
112 |
+
return all(keyword in email_content for keyword in ["interest", "skills", "experience", "contribute", "Best regards"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
113 |
|
114 |
# Function to generate email content using Nvidia Nemotron LLM (non-streaming for simplicity)
|
115 |
def generate_email_content(api_key, prompt):
|
|
|
128 |
temperature=0.5,
|
129 |
top_p=1,
|
130 |
max_tokens=1024,
|
131 |
+
stream=False
|
132 |
)
|
133 |
|
134 |
if hasattr(response, 'choices') and len(response.choices) > 0:
|
135 |
email_content = response.choices[0].message.content
|
136 |
logging.info("Email content generated successfully.")
|
|
|
137 |
return email_content
|
138 |
else:
|
139 |
logging.error("Error: No choices found in the response.")
|