Update app.py
Browse files
app.py
CHANGED
@@ -18,7 +18,6 @@ class AutonomousEmailAgent:
|
|
18 |
self.experiences = []
|
19 |
self.company_info = None
|
20 |
self.role_description = None
|
21 |
-
self.company_url = None
|
22 |
self.attempts = 0 # Counter for iterations
|
23 |
|
24 |
# Fetch LinkedIn data via Proxycurl
|
@@ -41,10 +40,20 @@ class AutonomousEmailAgent:
|
|
41 |
self.experiences = data.get("experiences", [])
|
42 |
print("LinkedIn data fetched successfully.")
|
43 |
else:
|
44 |
-
print("Error: Unable to fetch LinkedIn profile.
|
45 |
-
self.
|
46 |
-
|
47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
|
49 |
# Reason and Act via LLM: Let the LLM control reasoning and actions dynamically
|
50 |
def autonomous_reasoning(self):
|
@@ -60,13 +69,10 @@ class AutonomousEmailAgent:
|
|
60 |
- Candidate's Bio: {self.bio}
|
61 |
- Candidate's Skills: {', '.join(self.skills)}
|
62 |
- Candidate's Experiences: {', '.join([exp['title'] for exp in self.experiences])}
|
63 |
-
|
64 |
-
- Role Description: {self.role_description}
|
65 |
-
|
66 |
Based on this data, decide if it is sufficient to generate the email. If some information is missing or insufficient, respond with:
|
67 |
-
1. "
|
68 |
-
2. "
|
69 |
-
3. "fallback" to use default values.
|
70 |
|
71 |
After generating the email, reflect on whether the content aligns with the role and company and whether any improvements are needed. Respond clearly with one of the above options.
|
72 |
"""
|
@@ -86,7 +92,7 @@ class AutonomousEmailAgent:
|
|
86 |
"Content-Type": "application/json"
|
87 |
}
|
88 |
data = {
|
89 |
-
"model": "llama-3.1-70b-versatile",
|
90 |
"messages": [{"role": "user", "content": prompt}]
|
91 |
}
|
92 |
response = requests.post("https://api.groq.com/openai/v1/chat/completions", headers=headers, json=data)
|
@@ -94,10 +100,8 @@ class AutonomousEmailAgent:
|
|
94 |
print(f"Status Code: {response.status_code}")
|
95 |
if response.status_code == 200:
|
96 |
try:
|
97 |
-
result = response.json()
|
98 |
-
print(f"LLM Response: {json.dumps(result, indent=2)}")
|
99 |
-
|
100 |
-
# Check if 'choices' and the content are correctly structured in the response
|
101 |
choices = result.get("choices", [])
|
102 |
if choices and "message" in choices[0]:
|
103 |
content = choices[0]["message"]["content"]
|
@@ -110,110 +114,46 @@ class AutonomousEmailAgent:
|
|
110 |
print("Error: Response from Groq Cloud LLM is not valid JSON.")
|
111 |
return "Error: Response is not in JSON format."
|
112 |
else:
|
113 |
-
print(f"Error: Unable to connect to Groq Cloud LLM. Status Code: {response.status_code}
|
114 |
return "Error: Unable to generate response."
|
115 |
|
116 |
# Function to act on the LLM's structured instructions
|
117 |
def act_on_llm_instructions(self, reasoning_output):
|
118 |
-
print(f"LLM Instruction: {reasoning_output}")
|
119 |
instruction = reasoning_output.lower().strip()
|
120 |
|
121 |
-
if "
|
122 |
-
if self.attempts >= 5:
|
123 |
-
print("Max attempts reached. Proceeding with fallback option.")
|
124 |
-
return self.generate_fallback_email()
|
125 |
-
self.attempts += 1
|
126 |
-
self.fetch_company_url()
|
127 |
-
if self.company_url:
|
128 |
-
self.fetch_company_info_with_firecrawl(self.company_url)
|
129 |
-
return self.autonomous_reasoning()
|
130 |
-
|
131 |
-
elif "generate_email" in instruction:
|
132 |
return self.generate_email()
|
133 |
|
134 |
-
elif "fallback" in instruction
|
135 |
print("Action: Using fallback values for missing data.")
|
136 |
-
if not self.company_info:
|
137 |
-
self.company_info = "A leading company in its field."
|
138 |
-
if not self.role_description:
|
139 |
-
self.role_description = f"The role of {self.role} involves leadership and team management."
|
140 |
return self.generate_email()
|
141 |
|
142 |
else:
|
143 |
print("Error: Unrecognized instruction from LLM. Proceeding with available data.")
|
144 |
return self.generate_email()
|
145 |
|
146 |
-
#
|
147 |
-
def fetch_company_url(self):
|
148 |
-
serp_api_key = os.getenv("SERP_API_KEY")
|
149 |
-
print(f"Fetching company URL for {self.company_name} using SERP API...")
|
150 |
-
serp_url = f"https://serpapi.com/search.json?q={self.company_name}&api_key={serp_api_key}&num=1"
|
151 |
-
response = requests.get(serp_url)
|
152 |
-
|
153 |
-
if response.status_code == 200:
|
154 |
-
serp_data = response.json()
|
155 |
-
if 'organic_results' in serp_data and len(serp_data['organic_results']) > 0:
|
156 |
-
self.company_url = serp_data['organic_results'][0]['link']
|
157 |
-
print(f"Found company URL: {self.company_url}")
|
158 |
-
else:
|
159 |
-
print("No URL found for the company via SERP API.")
|
160 |
-
self.company_url = None
|
161 |
-
else:
|
162 |
-
print(f"Error fetching company URL: {response.status_code}")
|
163 |
-
|
164 |
-
# Fetch company information via Firecrawl API using company URL
|
165 |
-
def fetch_company_info_with_firecrawl(self, company_url):
|
166 |
-
firecrawl_api_key = os.getenv("FIRECRAWL_API_KEY")
|
167 |
-
print(f"Fetching company info for {company_url} using Firecrawl.")
|
168 |
-
headers = {"Authorization": f"Bearer {firecrawl_api_key}"}
|
169 |
-
firecrawl_url = "https://api.firecrawl.dev/v1/scrape"
|
170 |
-
data = {"url": company_url, "patterns": ["description", "about", "careers", "company overview"]}
|
171 |
-
|
172 |
-
response = requests.post(firecrawl_url, json=data, headers=headers)
|
173 |
-
if response.status_code == 200:
|
174 |
-
firecrawl_data = response.json()
|
175 |
-
self.company_info = firecrawl_data.get("description", "No detailed company info available.")
|
176 |
-
print(f"Company info fetched: {self.company_info}")
|
177 |
-
else:
|
178 |
-
print(f"Error: Unable to fetch company info via Firecrawl. Status code: {response.status_code}")
|
179 |
-
self.company_info = "A leading company in its field."
|
180 |
-
|
181 |
-
# Final Action: Generate the email using Groq Cloud LLM
|
182 |
def generate_email(self):
|
183 |
-
print("
|
184 |
-
|
185 |
-
|
186 |
-
|
|
|
187 |
|
188 |
-
|
189 |
-
1. **Why**: Explain why the candidate is passionate about this role and company.
|
190 |
-
2. **How**: Highlight the candidate’s skills and experiences.
|
191 |
-
3. **What**: Provide examples of past achievements.
|
192 |
-
4. **Call to Action**: Request a meeting or discussion.
|
193 |
|
194 |
-
|
195 |
-
- Skills: {', '.join(self.skills)}
|
196 |
-
- Experience: {', '.join([exp['title'] for exp in self.experiences])}
|
197 |
-
- Company information: {self.company_info}
|
198 |
|
199 |
-
Signature:
|
200 |
Best regards,
|
201 |
{self.user_name}
|
202 |
Email: {self.email}
|
203 |
Phone: {self.phone}
|
204 |
LinkedIn: {self.linkedin}
|
205 |
-
|
206 |
-
Limit the email to {self.word_limit} words.
|
207 |
"""
|
|
|
208 |
|
209 |
-
|
210 |
-
|
211 |
-
# Main loop following ReAct pattern
|
212 |
-
def run(self):
|
213 |
-
self.fetch_linkedin_data()
|
214 |
-
return self.autonomous_reasoning()
|
215 |
-
|
216 |
-
# Gradio UI setup remains the same
|
217 |
def gradio_ui():
|
218 |
name_input = gr.Textbox(label="Your Name", placeholder="Enter your name")
|
219 |
company_input = gr.Textbox(label="Company Name or URL", placeholder="Enter the company name or website URL")
|
|
|
18 |
self.experiences = []
|
19 |
self.company_info = None
|
20 |
self.role_description = None
|
|
|
21 |
self.attempts = 0 # Counter for iterations
|
22 |
|
23 |
# Fetch LinkedIn data via Proxycurl
|
|
|
40 |
self.experiences = data.get("experiences", [])
|
41 |
print("LinkedIn data fetched successfully.")
|
42 |
else:
|
43 |
+
print("Error: Unable to fetch LinkedIn profile. Status Code:", response.status_code)
|
44 |
+
self.use_default_profile()
|
45 |
+
|
46 |
+
# Set default profile information if LinkedIn scraping fails
|
47 |
+
def use_default_profile(self):
|
48 |
+
print("Using default profile values.")
|
49 |
+
self.bio = "A professional with a versatile background and extensive experience."
|
50 |
+
self.skills = ["Leadership", "Communication", "Problem-solving"]
|
51 |
+
self.experiences = [{"title": "Project Manager"}, {"title": "Team Leader"}]
|
52 |
+
|
53 |
+
# Main loop following ReAct pattern
|
54 |
+
def run(self):
|
55 |
+
self.fetch_linkedin_data()
|
56 |
+
return self.autonomous_reasoning()
|
57 |
|
58 |
# Reason and Act via LLM: Let the LLM control reasoning and actions dynamically
|
59 |
def autonomous_reasoning(self):
|
|
|
69 |
- Candidate's Bio: {self.bio}
|
70 |
- Candidate's Skills: {', '.join(self.skills)}
|
71 |
- Candidate's Experiences: {', '.join([exp['title'] for exp in self.experiences])}
|
72 |
+
|
|
|
|
|
73 |
Based on this data, decide if it is sufficient to generate the email. If some information is missing or insufficient, respond with:
|
74 |
+
1. "generate_email" to proceed with the email generation using available data.
|
75 |
+
2. "fallback" to use default values.
|
|
|
76 |
|
77 |
After generating the email, reflect on whether the content aligns with the role and company and whether any improvements are needed. Respond clearly with one of the above options.
|
78 |
"""
|
|
|
92 |
"Content-Type": "application/json"
|
93 |
}
|
94 |
data = {
|
95 |
+
"model": "llama-3.1-70b-versatile",
|
96 |
"messages": [{"role": "user", "content": prompt}]
|
97 |
}
|
98 |
response = requests.post("https://api.groq.com/openai/v1/chat/completions", headers=headers, json=data)
|
|
|
100 |
print(f"Status Code: {response.status_code}")
|
101 |
if response.status_code == 200:
|
102 |
try:
|
103 |
+
result = response.json()
|
104 |
+
print(f"LLM Response: {json.dumps(result, indent=2)}")
|
|
|
|
|
105 |
choices = result.get("choices", [])
|
106 |
if choices and "message" in choices[0]:
|
107 |
content = choices[0]["message"]["content"]
|
|
|
114 |
print("Error: Response from Groq Cloud LLM is not valid JSON.")
|
115 |
return "Error: Response is not in JSON format."
|
116 |
else:
|
117 |
+
print(f"Error: Unable to connect to Groq Cloud LLM. Status Code: {response.status_code}")
|
118 |
return "Error: Unable to generate response."
|
119 |
|
120 |
# Function to act on the LLM's structured instructions
|
121 |
def act_on_llm_instructions(self, reasoning_output):
|
122 |
+
print(f"LLM Instruction: {reasoning_output}")
|
123 |
instruction = reasoning_output.lower().strip()
|
124 |
|
125 |
+
if "generate_email" in instruction:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
126 |
return self.generate_email()
|
127 |
|
128 |
+
elif "fallback" in instruction:
|
129 |
print("Action: Using fallback values for missing data.")
|
|
|
|
|
|
|
|
|
130 |
return self.generate_email()
|
131 |
|
132 |
else:
|
133 |
print("Error: Unrecognized instruction from LLM. Proceeding with available data.")
|
134 |
return self.generate_email()
|
135 |
|
136 |
+
# Generate email based on the collected data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
137 |
def generate_email(self):
|
138 |
+
print("Generating email based on the provided and/or fallback data...")
|
139 |
+
email_content = f"""
|
140 |
+
Subject: Application for {self.role} at {self.company_name}
|
141 |
+
|
142 |
+
Dear Hiring Manager,
|
143 |
|
144 |
+
I am excited to apply for the {self.role} role at {self.company_name}. With a strong background in {self.bio}, I believe my skills in {', '.join(self.skills)} would make me a valuable addition to your team.
|
|
|
|
|
|
|
|
|
145 |
|
146 |
+
Please find my LinkedIn profile for more details: {self.linkedin}
|
|
|
|
|
|
|
147 |
|
|
|
148 |
Best regards,
|
149 |
{self.user_name}
|
150 |
Email: {self.email}
|
151 |
Phone: {self.phone}
|
152 |
LinkedIn: {self.linkedin}
|
|
|
|
|
153 |
"""
|
154 |
+
return email_content
|
155 |
|
156 |
+
# Gradio UI setup remains unchanged
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
157 |
def gradio_ui():
|
158 |
name_input = gr.Textbox(label="Your Name", placeholder="Enter your name")
|
159 |
company_input = gr.Textbox(label="Company Name or URL", placeholder="Enter the company name or website URL")
|