Update app.py
Browse files
app.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
import gradio as gr
|
2 |
import requests
|
3 |
import os
|
4 |
-
from bs4 import BeautifulSoup #
|
5 |
|
6 |
# Load API keys securely from environment variables
|
7 |
proxycurl_api_key = os.getenv("PROXYCURL_API_KEY") # Proxycurl API key
|
@@ -23,15 +23,17 @@ class EmailAgent:
|
|
23 |
self.company_info = None
|
24 |
self.role_description = None
|
25 |
|
26 |
-
# Reason: Decide what information is needed
|
27 |
def reason_about_data(self):
|
28 |
-
print("Reasoning:
|
29 |
if not self.linkedin_url:
|
30 |
-
print("Warning: LinkedIn URL missing.
|
31 |
if not self.company_name:
|
32 |
-
print("Warning: Company name missing.
|
33 |
-
|
34 |
-
|
|
|
|
|
35 |
def fetch_linkedin_data(self):
|
36 |
if not self.linkedin_url:
|
37 |
print("Action: No LinkedIn URL provided, using default bio.")
|
@@ -39,10 +41,8 @@ class EmailAgent:
|
|
39 |
self.skills = ["Adaptable", "Hardworking"]
|
40 |
self.experiences = ["Worked across various industries"]
|
41 |
else:
|
42 |
-
print("Action: Fetching LinkedIn data
|
43 |
-
headers = {
|
44 |
-
"Authorization": f"Bearer {proxycurl_api_key}",
|
45 |
-
}
|
46 |
url = f"https://nubela.co/proxycurl/api/v2/linkedin?url={self.linkedin_url}"
|
47 |
response = requests.get(url, headers=headers)
|
48 |
if response.status_code == 200:
|
@@ -55,17 +55,15 @@ class EmailAgent:
|
|
55 |
self.bio = "A professional with diverse experience."
|
56 |
self.skills = ["Adaptable", "Hardworking"]
|
57 |
self.experiences = ["Worked across various industries"]
|
58 |
-
|
59 |
-
# Action: Fetch company information via Proxycurl
|
60 |
def fetch_company_info(self):
|
61 |
if not self.company_name:
|
62 |
print("Action: No company name provided, using default company info.")
|
63 |
self.company_info = "A leading company in its field, offering innovative solutions."
|
64 |
else:
|
65 |
print(f"Action: Fetching company info for {self.company_name}.")
|
66 |
-
headers = {
|
67 |
-
"Authorization": f"Bearer {proxycurl_api_key}",
|
68 |
-
}
|
69 |
url = f"https://nubela.co/proxycurl/api/v2/linkedin/company?company_name={self.company_name}"
|
70 |
response = requests.get(url, headers=headers)
|
71 |
if response.status_code == 200:
|
@@ -75,24 +73,22 @@ class EmailAgent:
|
|
75 |
print(f"Error: Unable to fetch company info for {self.company_name}. Using default info.")
|
76 |
self.company_info = "A leading company in its field, offering innovative solutions."
|
77 |
|
78 |
-
# Action: Scrape the company's website for role-specific information
|
79 |
def scrape_role_from_website(self):
|
80 |
print(f"Action: Scraping role description from the company's website for {self.role}.")
|
81 |
if not self.company_name:
|
82 |
print("Error: No company name or URL provided for scraping.")
|
83 |
return False
|
84 |
|
85 |
-
#
|
86 |
try:
|
87 |
response = requests.get(f"https://{self.company_name}.com/careers")
|
88 |
if response.status_code == 200:
|
89 |
soup = BeautifulSoup(response.text, 'html.parser')
|
90 |
-
# Look for any sections that might contain role descriptions
|
91 |
role_descriptions = soup.find_all(string=lambda text: self.role.lower() in text.lower())
|
92 |
if role_descriptions:
|
93 |
-
# If we find relevant role descriptions, use the first match
|
94 |
self.role_description = role_descriptions[0]
|
95 |
-
print(f"Found role description
|
96 |
return True
|
97 |
else:
|
98 |
print(f"No specific role description found on the website for {self.role}.")
|
@@ -104,20 +100,19 @@ class EmailAgent:
|
|
104 |
print(f"Error during scraping: {e}")
|
105 |
return False
|
106 |
|
107 |
-
# Action: Use default logic
|
108 |
def use_default_role_description(self):
|
109 |
print(f"Action: Using default logic for the role of {self.role}.")
|
110 |
-
self.role_description = f"The role of {self.role} at {self.company_name} involves mentoring
|
111 |
|
112 |
-
# Reflection: Check if
|
113 |
def reflect_on_data(self):
|
114 |
-
print("Reflection: Do
|
115 |
-
# Allow the email to be generated with default values if data is missing
|
116 |
if not self.bio or not self.skills or not self.company_info:
|
117 |
print("Warning: Some critical information is missing. Proceeding with default values.")
|
118 |
return True
|
119 |
-
|
120 |
-
# Action: Generate the email using Groq Cloud LLM
|
121 |
def generate_email(self):
|
122 |
print("Action: Generating the email with the gathered information.")
|
123 |
prompt = f"""
|
@@ -126,7 +121,7 @@ class EmailAgent:
|
|
126 |
|
127 |
Focus on relevant skills and experiences from LinkedIn, such as {', '.join(self.skills)},
|
128 |
that directly align with the role of {self.role}.
|
129 |
-
Highlight only the skills and experiences that relate to
|
130 |
|
131 |
The company info is: {self.company_info}.
|
132 |
The role involves: {self.role_description}.
|
@@ -142,70 +137,8 @@ class EmailAgent:
|
|
142 |
"""
|
143 |
|
144 |
url = "https://api.groq.com/openai/v1/chat/completions"
|
145 |
-
headers = {
|
146 |
-
"Authorization": f"Bearer {groq_api_key}",
|
147 |
-
"Content-Type": "application/json",
|
148 |
-
}
|
149 |
|
150 |
data = {
|
151 |
"messages": [{"role": "user", "content": prompt}],
|
152 |
-
"model": "llama3-8b
|
153 |
-
}
|
154 |
-
|
155 |
-
response = requests.post(url, headers=headers, json=data)
|
156 |
-
if response.status_code == 200:
|
157 |
-
return response.json()["choices"][0]["message"]["content"].strip()
|
158 |
-
else:
|
159 |
-
print(f"Error: {response.status_code}, {response.text}")
|
160 |
-
return "Error generating email. Please check your API key or try again later."
|
161 |
-
|
162 |
-
# Main loop following ReAct pattern
|
163 |
-
def run(self):
|
164 |
-
self.reason_about_data() # Reason
|
165 |
-
self.fetch_linkedin_data() # Action
|
166 |
-
self.fetch_company_info() # Action
|
167 |
-
# Try to scrape the company's website for role-specific information
|
168 |
-
if not self.scrape_role_from_website():
|
169 |
-
self.use_default_role_description() # Use default logic if scraping fails
|
170 |
-
if self.reflect_on_data(): # Reflection
|
171 |
-
return self.generate_email() # Final Action
|
172 |
-
else:
|
173 |
-
return "Error: Not enough data to generate the email."
|
174 |
-
|
175 |
-
# Define the Gradio interface and the main app logic
|
176 |
-
def gradio_ui():
|
177 |
-
# Input fields
|
178 |
-
name_input = gr.Textbox(label="Your Name", placeholder="Enter your name")
|
179 |
-
company_input = gr.Textbox(label="Company Name or URL", placeholder="Enter the company name or website URL")
|
180 |
-
role_input = gr.Textbox(label="Role Applying For", placeholder="Enter the role you are applying for")
|
181 |
-
email_input = gr.Textbox(label="Your Email Address", placeholder="Enter your email address")
|
182 |
-
phone_input = gr.Textbox(label="Your Phone Number", placeholder="Enter your phone number")
|
183 |
-
linkedin_input = gr.Textbox(label="Your LinkedIn URL", placeholder="Enter your LinkedIn profile URL")
|
184 |
-
word_limit_slider = gr.Slider(minimum=50, maximum=300, step=10, label="Email Word Limit", value=150) # New slider for word limit
|
185 |
-
|
186 |
-
# Output field
|
187 |
-
email_output = gr.Textbox(label="Generated Email", placeholder="Your generated email will appear here", lines=10)
|
188 |
-
|
189 |
-
# Function to create and run the email agent
|
190 |
-
def create_email(name, company_name, role, email, phone, linkedin_url, word_limit):
|
191 |
-
agent = EmailAgent(linkedin_url, company_name, role, word_limit, name, email, phone, linkedin_url)
|
192 |
-
return agent.run()
|
193 |
-
|
194 |
-
# Gradio interface
|
195 |
-
demo = gr.Interface(
|
196 |
-
fn=create_email,
|
197 |
-
inputs=[name_input, company_input, role_input, email_input, phone_input, linkedin_input, word_limit_slider],
|
198 |
-
outputs=[email_output],
|
199 |
-
title="Email Writing AI Agent with ReAct",
|
200 |
-
description="Generate a professional email for a job application using LinkedIn data, company info, and role description.",
|
201 |
-
allow_flagging="never"
|
202 |
-
)
|
203 |
-
|
204 |
-
# Launch the Gradio app
|
205 |
-
demo.launch()
|
206 |
-
|
207 |
-
# Start the Gradio app when running the script
|
208 |
-
if __name__ == "__main__":
|
209 |
-
gradio_ui()
|
210 |
-
|
211 |
-
|
|
|
1 |
import gradio as gr
|
2 |
import requests
|
3 |
import os
|
4 |
+
from bs4 import BeautifulSoup # For scraping company and role info
|
5 |
|
6 |
# Load API keys securely from environment variables
|
7 |
proxycurl_api_key = os.getenv("PROXYCURL_API_KEY") # Proxycurl API key
|
|
|
23 |
self.company_info = None
|
24 |
self.role_description = None
|
25 |
|
26 |
+
# Reason: Decide what information is needed and if we need to take additional steps
|
27 |
def reason_about_data(self):
|
28 |
+
print("Reasoning: Deciding what data we need...")
|
29 |
if not self.linkedin_url:
|
30 |
+
print("Warning: LinkedIn URL missing. Proceeding with default bio.")
|
31 |
if not self.company_name:
|
32 |
+
print("Warning: Company name missing. Proceeding with default company info.")
|
33 |
+
if not self.role:
|
34 |
+
print("Warning: Role missing. We will use general logic for the role.")
|
35 |
+
|
36 |
+
# Action: Fetch LinkedIn data via Proxycurl (acting based on reasoning)
|
37 |
def fetch_linkedin_data(self):
|
38 |
if not self.linkedin_url:
|
39 |
print("Action: No LinkedIn URL provided, using default bio.")
|
|
|
41 |
self.skills = ["Adaptable", "Hardworking"]
|
42 |
self.experiences = ["Worked across various industries"]
|
43 |
else:
|
44 |
+
print("Action: Fetching LinkedIn data via Proxycurl.")
|
45 |
+
headers = {"Authorization": f"Bearer {proxycurl_api_key}"}
|
|
|
|
|
46 |
url = f"https://nubela.co/proxycurl/api/v2/linkedin?url={self.linkedin_url}"
|
47 |
response = requests.get(url, headers=headers)
|
48 |
if response.status_code == 200:
|
|
|
55 |
self.bio = "A professional with diverse experience."
|
56 |
self.skills = ["Adaptable", "Hardworking"]
|
57 |
self.experiences = ["Worked across various industries"]
|
58 |
+
|
59 |
+
# Action: Fetch company information via Proxycurl or use defaults
|
60 |
def fetch_company_info(self):
|
61 |
if not self.company_name:
|
62 |
print("Action: No company name provided, using default company info.")
|
63 |
self.company_info = "A leading company in its field, offering innovative solutions."
|
64 |
else:
|
65 |
print(f"Action: Fetching company info for {self.company_name}.")
|
66 |
+
headers = {"Authorization": f"Bearer {proxycurl_api_key}"}
|
|
|
|
|
67 |
url = f"https://nubela.co/proxycurl/api/v2/linkedin/company?company_name={self.company_name}"
|
68 |
response = requests.get(url, headers=headers)
|
69 |
if response.status_code == 200:
|
|
|
73 |
print(f"Error: Unable to fetch company info for {self.company_name}. Using default info.")
|
74 |
self.company_info = "A leading company in its field, offering innovative solutions."
|
75 |
|
76 |
+
# Action: Scrape the company's website for role-specific information or use defaults
|
77 |
def scrape_role_from_website(self):
|
78 |
print(f"Action: Scraping role description from the company's website for {self.role}.")
|
79 |
if not self.company_name:
|
80 |
print("Error: No company name or URL provided for scraping.")
|
81 |
return False
|
82 |
|
83 |
+
# Try scraping the website for role descriptions
|
84 |
try:
|
85 |
response = requests.get(f"https://{self.company_name}.com/careers")
|
86 |
if response.status_code == 200:
|
87 |
soup = BeautifulSoup(response.text, 'html.parser')
|
|
|
88 |
role_descriptions = soup.find_all(string=lambda text: self.role.lower() in text.lower())
|
89 |
if role_descriptions:
|
|
|
90 |
self.role_description = role_descriptions[0]
|
91 |
+
print(f"Found role description: {self.role_description}")
|
92 |
return True
|
93 |
else:
|
94 |
print(f"No specific role description found on the website for {self.role}.")
|
|
|
100 |
print(f"Error during scraping: {e}")
|
101 |
return False
|
102 |
|
103 |
+
# Action: Use default logic for role description if no role is available
|
104 |
def use_default_role_description(self):
|
105 |
print(f"Action: Using default logic for the role of {self.role}.")
|
106 |
+
self.role_description = f"The role of {self.role} at {self.company_name} involves mentoring and leading teams in innovative technology solutions."
|
107 |
|
108 |
+
# Reflection: Check if we have enough data to generate the email
|
109 |
def reflect_on_data(self):
|
110 |
+
print("Reflection: Do we have enough data?")
|
|
|
111 |
if not self.bio or not self.skills or not self.company_info:
|
112 |
print("Warning: Some critical information is missing. Proceeding with default values.")
|
113 |
return True
|
114 |
+
|
115 |
+
# Final Action: Generate the email using Groq Cloud LLM based on gathered data
|
116 |
def generate_email(self):
|
117 |
print("Action: Generating the email with the gathered information.")
|
118 |
prompt = f"""
|
|
|
121 |
|
122 |
Focus on relevant skills and experiences from LinkedIn, such as {', '.join(self.skills)},
|
123 |
that directly align with the role of {self.role}.
|
124 |
+
Highlight only the skills and experiences that relate to leadership, mentoring, technology, and innovation.
|
125 |
|
126 |
The company info is: {self.company_info}.
|
127 |
The role involves: {self.role_description}.
|
|
|
137 |
"""
|
138 |
|
139 |
url = "https://api.groq.com/openai/v1/chat/completions"
|
140 |
+
headers = {"Authorization": f"Bearer {groq_api_key}", "Content-Type": "application/json"}
|
|
|
|
|
|
|
141 |
|
142 |
data = {
|
143 |
"messages": [{"role": "user", "content": prompt}],
|
144 |
+
"model": "llama3-8b
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|