ak0601 commited on
Commit
7892b25
·
verified ·
1 Parent(s): f1d36f3

Update app_hug.py

Browse files
Files changed (1) hide show
  1. app_hug.py +784 -23
app_hug.py CHANGED
@@ -1,3 +1,761 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from fastapi import FastAPI, HTTPException, BackgroundTasks, Depends, Header, status, Query
2
  from fastapi.middleware.cors import CORSMiddleware
3
  from fastapi.security import APIKeyHeader
@@ -262,11 +1020,11 @@ def refine_message_with_feedback(
262
 
263
  BODY:
264
  [Your refined email body content here]
265
- Keep the same tone and intent as the original message, but incorporate the feedback to improve it.
266
  """
267
  try:
268
  messages = [
269
- SystemMessage(content="You are a professional recruitment message writer. Refine the given message based on feedback while maintaining professionalism and the original intent."),
270
  HumanMessage(content=prompt)
271
  ]
272
  response = llm.invoke(messages, config={"callbacks": [langfuse_handler]})
@@ -314,18 +1072,19 @@ def generate_recruitment_message_with_subject(
314
  # Outreach: Only request consent
315
  if msg_type == "outreach":
316
  prompt = f"""
317
- Generate a professional initial outreach message to a candidate.
318
- - Introduce yourself as {recruiter} from {org}
319
- - Clearly state you are reaching out about an open role ({role_title}) at {company}
320
- - Ask if they are open to learning more or interested in a quick chat.
 
 
321
  - Do NOT discuss any job evaluation or judgment.
322
- - Explicitly request their consent to share more details. E.g., 'Would you be open to hearing more about this opportunity?'
323
- - Keep it short and friendly.
324
  - No placeholders like [Candidate Name] or [Role Title] in the output.
325
  """
326
  else:
327
  base_prompt = f"""
328
- Generate a professional recruitment {msg_type} with the following details:
329
  - Company hiring: {company}
330
  - Role: {role_title}
331
  - Recruiter: {recruiter} from {org}
@@ -337,22 +1096,24 @@ def generate_recruitment_message_with_subject(
337
  prompt = base_prompt
338
  if msg_type == "introductory":
339
  prompt += """
340
- Create an introductory message that:
341
- - Thanks the candidate for their initial response
342
- - Provides more details about the role and company
343
- - Explains why this opportunity aligns with their background
344
- - Suggests next steps (like a call or meeting)
345
- - Maintains a warm, professional tone
346
- - if in the Evaluation the verdict is rejected, Send a rejection message instead.
 
347
  """
348
  else: # followup
349
  prompt += """
350
- Create a follow-up message that:
351
- - References previous communication
352
- - Reiterates interest in the candidate
353
- - Addresses any potential concerns
354
- - Provides additional compelling reasons to consider the role
355
- - Includes a clear call to action
 
356
  """
357
 
358
  # Use feedback if provided
@@ -381,7 +1142,7 @@ def generate_recruitment_message_with_subject(
381
 
382
  try:
383
  messages = [
384
- SystemMessage(content="You are a professional recruitment message writer. Generate both an email subject line and body content. Follow the exact format requested."),
385
  HumanMessage(content=prompt)
386
  ]
387
  response = llm.invoke(messages, config={"callbacks": [langfuse_handler]})
 
1
+ # from fastapi import FastAPI, HTTPException, BackgroundTasks, Depends, Header, status, Query
2
+ # from fastapi.middleware.cors import CORSMiddleware
3
+ # from fastapi.security import APIKeyHeader
4
+ # from pydantic import BaseModel, EmailStr, Field
5
+ # from typing import Optional, Tuple
6
+ # from enum import Enum
7
+ # import os
8
+ # import base64
9
+ # import pickle
10
+ # import pandas as pd
11
+ # from dotenv import load_dotenv
12
+ # from langchain_openai import ChatOpenAI
13
+ # from langchain.schema import HumanMessage, SystemMessage
14
+ # from email.mime.text import MIMEText
15
+ # from google.auth.transport.requests import Request
16
+ # from google.oauth2.credentials import Credentials
17
+ # from google_auth_oauthlib.flow import InstalledAppFlow
18
+ # from googleapiclient.discovery import build
19
+ # from googleapiclient.errors import HttpError
20
+ # from datetime import datetime
21
+ # import json
22
+ # from langfuse import Langfuse
23
+ # from langfuse.langchain import CallbackHandler
24
+
25
+
26
+ # load_dotenv()
27
+ # langfuse = Langfuse(
28
+ # secret_key=os.getenv("SECRET_KEY"),
29
+ # public_key=os.getenv("PUBLIC_KEY"),
30
+ # host=os.getenv("APP_HOST")
31
+ # )
32
+ # langfuse_handler = CallbackHandler()
33
+
34
+
35
+ # # Load environment variables (not needed on Hugging Face, but harmless)
36
+ # # ------------------------------------------
37
+ # # Security Configuration
38
+ # # ------------------------------------------
39
+ # API_PASSWORD = os.getenv("API_PASSWORD", "Synapse@2025") # Can be overridden by env var
40
+ # api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
41
+
42
+ # def verify_api_key(api_key: Optional[str] = Depends(api_key_header)) -> str:
43
+ # """
44
+ # Verify the API key/password provided in the request header.
45
+ # """
46
+ # if api_key is None:
47
+ # raise HTTPException(
48
+ # status_code=status.HTTP_401_UNAUTHORIZED,
49
+ # detail="API Key required. Please provide X-API-Key header.",
50
+ # headers={"WWW-Authenticate": "ApiKey"},
51
+ # )
52
+
53
+ # if api_key != API_PASSWORD:
54
+ # raise HTTPException(
55
+ # status_code=status.HTTP_401_UNAUTHORIZED,
56
+ # detail="Invalid API Key",
57
+ # headers={"WWW-Authenticate": "ApiKey"},
58
+ # )
59
+
60
+ # return api_key
61
+
62
+ # # ------------------------------------------
63
+ # # Helper: Write GOOGLE_CREDENTIALS_JSON to file if needed
64
+ # # ------------------------------------------
65
+ # def ensure_credentials_file():
66
+ # credentials_env = os.getenv("GOOGLE_CREDENTIALS_JSON")
67
+ # credentials_path = "credentials_SYNAPSE.json"
68
+ # if not os.path.exists(credentials_path):
69
+ # if not credentials_env:
70
+ # raise Exception("GOOGLE_CREDENTIALS_JSON not found in environment variables.")
71
+ # try:
72
+ # parsed_json = json.loads(credentials_env)
73
+ # except json.JSONDecodeError:
74
+ # raise Exception("Invalid JSON in GOOGLE_CREDENTIALS_JSON")
75
+ # with open(credentials_path, "w") as f:
76
+ # json.dump(parsed_json, f, indent=2)
77
+ # return credentials_path
78
+
79
+ # # ------------------------------------------
80
+ # # FastAPI app
81
+ # # ------------------------------------------
82
+ # app = FastAPI(title="Recruitment Message Generator API", version="1.0.0")
83
+ # app.add_middleware(
84
+ # CORSMiddleware,
85
+ # allow_origins=["*"],
86
+ # allow_credentials=True,
87
+ # allow_methods=["*"],
88
+ # allow_headers=["*"],
89
+ # )
90
+
91
+ # SCOPES = ["https://www.googleapis.com/auth/gmail.send"]
92
+ # openai_api_key = os.getenv("OPENAI_API_KEY")
93
+
94
+ # # ------------------------------------------
95
+ # # Enums and Models
96
+ # # ------------------------------------------
97
+ # class MessageType(str, Enum):
98
+ # OUTREACH = "outreach"
99
+ # INTRODUCTORY = "introductory"
100
+ # FOLLOWUP = "followup"
101
+
102
+ # class GenerateMessageRequest(BaseModel):
103
+ # job_evaluation: Optional[str] = Field(None, description="(Optional) Recruiter's evaluation of candidate for the job")
104
+ # sender_email: EmailStr
105
+ # reply_to_email: Optional[EmailStr] = Field(None, description="Recruiter's email for reply-to header")
106
+ # recipient_email: EmailStr
107
+ # candidate_name: str
108
+ # current_role: str
109
+ # current_company: str
110
+ # company_name: str
111
+ # role: str
112
+ # recruiter_name: str
113
+ # organisation: str
114
+ # message_type: MessageType
115
+ # send_email: bool = False
116
+ # past_conversation: Optional[str] = Field(None, description="(Optional) Previous messages with candidate")
117
+
118
+ # class FeedbackRequest(BaseModel):
119
+ # message: str
120
+ # feedback: str
121
+
122
+ # class AuthenticateRequest(BaseModel):
123
+ # email: EmailStr
124
+
125
+ # class AuthenticateResponse(BaseModel):
126
+ # success: bool
127
+ # message: str
128
+ # error: Optional[str] = None
129
+
130
+ # class MessageResponse(BaseModel):
131
+ # success: bool
132
+ # message: str
133
+ # email_sent: bool = False
134
+ # email_subject: Optional[str] = None
135
+ # error: Optional[str] = None
136
+
137
+ # class SendMessageRequest(BaseModel):
138
+ # subject: str
139
+ # email_body: str
140
+ # sender_email: EmailStr
141
+ # recipient_email: EmailStr
142
+ # reply_to_email: Optional[EmailStr] = None
143
+
144
+ # # ------------------------------------------
145
+ # # Gmail Helper Functions
146
+ # # ------------------------------------------
147
+ # def get_token_file_path(email: str) -> str:
148
+ # tokens_dir = "gmail_tokens"
149
+ # if not os.path.exists(tokens_dir):
150
+ # os.makedirs(tokens_dir)
151
+ # safe_email = email.replace("@", "_at_").replace(".", "_dot_")
152
+ # return os.path.join(tokens_dir, f"token_{safe_email}.pickle")
153
+
154
+ # def check_user_token_exists(email: str) -> bool:
155
+ # token_file = get_token_file_path(email)
156
+ # return os.path.exists(token_file)
157
+
158
+ # def load_user_credentials(email: str):
159
+ # token_file = get_token_file_path(email)
160
+ # if os.path.exists(token_file):
161
+ # try:
162
+ # with open(token_file, 'rb') as token:
163
+ # creds = pickle.load(token)
164
+ # return creds
165
+ # except Exception:
166
+ # if os.path.exists(token_file):
167
+ # os.remove(token_file)
168
+ # return None
169
+
170
+ # def save_user_credentials(email: str, creds):
171
+ # token_file = get_token_file_path(email)
172
+ # with open(token_file, 'wb') as token:
173
+ # pickle.dump(creds, token)
174
+
175
+ # def create_new_credentials(email: str):
176
+ # credentials_path = ensure_credentials_file()
177
+ # flow = InstalledAppFlow.from_client_secrets_file(
178
+ # credentials_path, SCOPES
179
+ # )
180
+ # creds = flow.run_local_server(port=0)
181
+ # save_user_credentials(email, creds)
182
+ # return creds
183
+
184
+ # def authenticate_gmail(email: str, create_if_missing: bool = False):
185
+ # creds = load_user_credentials(email)
186
+ # if creds:
187
+ # if creds.expired and creds.refresh_token:
188
+ # try:
189
+ # creds.refresh(Request())
190
+ # save_user_credentials(email, creds)
191
+ # except Exception:
192
+ # if create_if_missing:
193
+ # try:
194
+ # creds = create_new_credentials(email)
195
+ # except:
196
+ # return None
197
+ # else:
198
+ # return None
199
+ # elif not creds.valid:
200
+ # creds = None
201
+ # if not creds:
202
+ # if create_if_missing:
203
+ # try:
204
+ # creds = create_new_credentials(email)
205
+ # except:
206
+ # return None
207
+ # else:
208
+ # return None
209
+ # try:
210
+ # service = build("gmail", "v1", credentials=creds)
211
+ # return service
212
+ # except Exception:
213
+ # return None
214
+
215
+ # from email.mime.text import MIMEText
216
+
217
+ # def create_email_message(sender: str, to: str, subject: str, message_text: str, reply_to: Optional[str] = None, is_html: bool = False):
218
+ # if is_html:
219
+ # message = MIMEText(message_text, "html")
220
+ # else:
221
+ # message = MIMEText(message_text, "plain")
222
+ # message["To"] = to
223
+ # message["From"] = sender
224
+ # message["Subject"] = subject
225
+ # if reply_to:
226
+ # message["Reply-To"] = reply_to
227
+ # raw_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
228
+ # return {"raw": raw_message}
229
+
230
+ # def send_gmail_message(service, user_id: str, message: dict):
231
+ # try:
232
+ # result = service.users().messages().send(userId=user_id, body=message).execute()
233
+ # return result is not None
234
+ # except HttpError:
235
+ # return False
236
+
237
+ # # ------------------------------------------
238
+ # # LLM (OpenAI) Message Generation Helpers
239
+ # # ------------------------------------------
240
+ # def refine_message_with_feedback(
241
+ # original_message: str,
242
+ # feedback: str,
243
+ # ) -> Tuple[str, str]:
244
+ # api_key = os.getenv("OPENAI_API_KEY")
245
+ # llm = ChatOpenAI(
246
+ # model="gpt-4o-mini",
247
+ # temperature=0.7,
248
+ # max_tokens=600,
249
+ # openai_api_key=api_key
250
+ # )
251
+ # prompt = f"""
252
+ # Please refine the following recruitment message based on the provided feedback:
253
+
254
+ # ORIGINAL MESSAGE:
255
+ # {original_message}
256
+
257
+ # FEEDBACK:
258
+ # {feedback}
259
+
260
+ # Please provide your response in the following format:
261
+ # SUBJECT: [Your subject line here]
262
+
263
+ # BODY:
264
+ # [Your refined email body content here]
265
+ # Keep the same tone and intent as the original message, but incorporate the feedback to improve it.
266
+ # """
267
+ # try:
268
+ # messages = [
269
+ # SystemMessage(content="You are a professional recruitment message writer. Refine the given message based on feedback while maintaining professionalism and the original intent."),
270
+ # HumanMessage(content=prompt)
271
+ # ]
272
+ # response = llm.invoke(messages, config={"callbacks": [langfuse_handler]})
273
+ # content = response.content.strip()
274
+ # subject_line = ""
275
+ # body_content = ""
276
+ # lines = content.split('\n')
277
+ # body_found = False
278
+ # body_lines = []
279
+ # for line in lines:
280
+ # if line.strip().startswith('SUBJECT:'):
281
+ # subject_line = line.replace('SUBJECT:', '').strip()
282
+ # elif line.strip().startswith('BODY:'):
283
+ # body_found = True
284
+ # elif body_found and line.strip():
285
+ # body_lines.append(line)
286
+ # body_content = '\n'.join(body_lines).strip()
287
+ # if not subject_line:
288
+ # subject_line = "Recruitment Opportunity - Updated"
289
+ # if not body_content:
290
+ # body_content = content
291
+ # return subject_line, body_content
292
+ # except Exception as e:
293
+ # raise HTTPException(status_code=500, detail=f"Error refining message: {str(e)}")
294
+
295
+ # def generate_recruitment_message_with_subject(
296
+ # msg_type: str,
297
+ # company: str,
298
+ # role_title: str,
299
+ # recruiter: str,
300
+ # org: str,
301
+ # candidate: str,
302
+ # current_pos: str,
303
+ # evaluation: Optional[str] = None,
304
+ # feedback: Optional[str] = None,
305
+ # past_conversation: Optional[str] = None
306
+ # ) -> Tuple[str, str]:
307
+ # api_key = os.getenv("OPENAI_API_KEY")
308
+ # llm = ChatOpenAI(
309
+ # model="gpt-4o-mini",
310
+ # temperature=0.7,
311
+ # max_tokens=600,
312
+ # openai_api_key=api_key
313
+ # )
314
+ # # Outreach: Only request consent
315
+ # if msg_type == "outreach":
316
+ # prompt = f"""
317
+ # Generate a professional initial outreach message to a candidate.
318
+ # - Introduce yourself as {recruiter} from {org}
319
+ # - Clearly state you are reaching out about an open role ({role_title}) at {company}
320
+ # - Ask if they are open to learning more or interested in a quick chat.
321
+ # - Do NOT discuss any job evaluation or judgment.
322
+ # - Explicitly request their consent to share more details. E.g., 'Would you be open to hearing more about this opportunity?'
323
+ # - Keep it short and friendly.
324
+ # - No placeholders like [Candidate Name] or [Role Title] in the output.
325
+ # """
326
+ # else:
327
+ # base_prompt = f"""
328
+ # Generate a professional recruitment {msg_type} with the following details:
329
+ # - Company hiring: {company}
330
+ # - Role: {role_title}
331
+ # - Recruiter: {recruiter} from {org}
332
+ # - Candidate: {candidate}
333
+ # - Candidate's current position: {current_pos}
334
+ # """
335
+ # if evaluation:
336
+ # base_prompt += f"- Evaluation: {evaluation}\n"
337
+ # prompt = base_prompt
338
+ # if msg_type == "introductory":
339
+ # prompt += """
340
+ # Create an introductory message that:
341
+ # - Thanks the candidate for their initial response
342
+ # - Provides more details about the role and company
343
+ # - Explains why this opportunity aligns with their background
344
+ # - Suggests next steps (like a call or meeting)
345
+ # - Maintains a warm, professional tone
346
+ # - if in the Evaluation the verdict is rejected, Send a rejection message instead.
347
+ # """
348
+ # else: # followup
349
+ # prompt += """
350
+ # Create a follow-up message that:
351
+ # - References previous communication
352
+ # - Reiterates interest in the candidate
353
+ # - Addresses any potential concerns
354
+ # - Provides additional compelling reasons to consider the role
355
+ # - Includes a clear call to action
356
+ # """
357
+
358
+ # # Use feedback if provided
359
+ # if feedback:
360
+ # prompt += f"\n\nPlease modify the message based on this feedback: {feedback}"
361
+
362
+ # # Use past conversation if provided
363
+ # if past_conversation:
364
+ # prompt += f"""
365
+ # Use the following past conversation as context to inform your reply:
366
+
367
+ # PAST CONVERSATION:
368
+ # {past_conversation}
369
+
370
+ # Write a reply message to the candidate, maintaining professionalism and continuity.
371
+ # """
372
+
373
+ # prompt += """
374
+
375
+ # Please provide your response in the following format:
376
+ # SUBJECT: [Your subject line here]
377
+
378
+ # BODY:
379
+ # [Your email body content here]
380
+ # """
381
+
382
+ # try:
383
+ # messages = [
384
+ # SystemMessage(content="You are a professional recruitment message writer. Generate both an email subject line and body content. Follow the exact format requested."),
385
+ # HumanMessage(content=prompt)
386
+ # ]
387
+ # response = llm.invoke(messages, config={"callbacks": [langfuse_handler]})
388
+ # content = response.content.strip()
389
+ # subject_line = ""
390
+ # body_content = ""
391
+ # lines = content.split('\n')
392
+ # body_found = False
393
+ # body_lines = []
394
+ # for line in lines:
395
+ # if line.strip().startswith('SUBJECT:'):
396
+ # subject_line = line.replace('SUBJECT:', '').strip()
397
+ # elif line.strip().startswith('BODY:'):
398
+ # body_found = True
399
+ # elif body_found and line.strip():
400
+ # body_lines.append(line)
401
+ # body_content = '\n'.join(body_lines).strip()
402
+ # if not subject_line:
403
+ # subject_line = f"Opportunity at {company} - {role_title}"
404
+ # if not body_content:
405
+ # body_content = content
406
+ # return subject_line, body_content
407
+ # except Exception as e:
408
+ # raise HTTPException(status_code=500, detail=f"Error generating message: {str(e)}")
409
+
410
+
411
+ # def format_email_html(body: str, sender_name: Optional[str]=None, sender_org: Optional[str]=None):
412
+ # """Wrap plain text body in an HTML template for better appearance."""
413
+ # import re
414
+
415
+ # # Smart paragraphing and formatting
416
+ # body = body.strip()
417
+
418
+ # # Convert markdown-style formatting
419
+ # body = re.sub(r'\*\*(.*?)\*\*', r'<strong>\1</strong>', body) # Bold text
420
+ # body = re.sub(r'\*(.*?)\*', r'<em>\1</em>', body) # Italic text
421
+
422
+ # # Smart paragraphing
423
+ # body = re.sub(r"\n\s*\n", "</p><p>", body) # Double newlines => new paragraph
424
+ # body = re.sub(r"\n", "<br>", body) # Single newlines => <br>
425
+
426
+ # # Optional signature with better styling
427
+ # signature = ""
428
+ # if sender_name or sender_org:
429
+ # signature = f"""
430
+ # <div style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #e0e0e0;">
431
+ # <p style="margin: 0; color: #666; font-size: 14px;">Best regards,</p>
432
+ # """
433
+ # if sender_name:
434
+ # signature += f'<p style="margin: 5px 0 0 0; font-weight: 600; color: #333; font-size: 16px;">{sender_name}</p>'
435
+ # if sender_org:
436
+ # signature += f'<p style="margin: 3px 0 0 0; color: #666; font-size: 14px;">{sender_org}</p>'
437
+ # signature += "</div>"
438
+
439
+ # html = f"""
440
+ # <!DOCTYPE html>
441
+ # <html>
442
+ # <head>
443
+ # <meta charset="UTF-8">
444
+ # <meta name="viewport" content="width=device-width, initial-scale=1.0">
445
+ # <title>Recruitment Message</title>
446
+ # </head>
447
+ # <body style="
448
+ # font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
449
+ # color: #333333;
450
+ # line-height: 1.6;
451
+ # max-width: 600px;
452
+ # margin: 0 auto;
453
+ # padding: 20px;
454
+ # background-color: #f8f9fa;
455
+ # ">
456
+ # <div style="
457
+ # background-color: #ffffff;
458
+ # border-radius: 8px;
459
+ # box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
460
+ # padding: 30px;
461
+ # border: 1px solid #e9ecef;
462
+ # ">
463
+ # <!-- Header -->
464
+ # <div style="
465
+ # border-bottom: 2px solid #007bff;
466
+ # padding-bottom: 15px;
467
+ # margin-bottom: 25px;
468
+ # ">
469
+ # <h2 style="
470
+ # margin: 0;
471
+ # color: #007bff;
472
+ # font-size: 20px;
473
+ # font-weight: 600;
474
+ # ">Professional Recruitment Message</h2>
475
+ # </div>
476
+
477
+ # <!-- Main Content -->
478
+ # <div style="
479
+ # font-size: 15px;
480
+ # color: #444;
481
+ # line-height: 1.7;
482
+ # ">
483
+ # <p style="margin: 0 0 15px 0;">{body}</p>
484
+ # </div>
485
+
486
+ # <!-- Signature -->
487
+ # {signature}
488
+
489
+ # <!-- Footer -->
490
+ # <div style="
491
+ # margin-top: 30px;
492
+ # padding-top: 15px;
493
+ # border-top: 1px solid #e0e0e0;
494
+ # text-align: center;
495
+ # font-size: 12px;
496
+ # color: #888;
497
+ # ">
498
+ # <p style="margin: 0;">This message was generated by Synapse Recruitment Platform</p>
499
+ # <p style="margin: 5px 0 0 0;">Please reply to this email for any inquiries</p>
500
+ # </div>
501
+ # </div>
502
+ # </body>
503
+ # </html>
504
+ # """
505
+ # return html
506
+
507
+
508
+ # # ------------------------------------------
509
+ # # FastAPI Endpoints
510
+ # # ------------------------------------------
511
+ # @app.get("/")
512
+ # async def root():
513
+ # """Root endpoint - public access"""
514
+ # return {
515
+ # "message": "Recruitment Message Generator API",
516
+ # "version": "1.0.0",
517
+ # "authentication": "Required for all endpoints except / and /health",
518
+ # "auth_header": "X-API-Key",
519
+ # "endpoints": [
520
+ # "/generate-message",
521
+ # "/refine-message",
522
+ # "/authenticate",
523
+ # "/send-message",
524
+ # "/docs"
525
+ # ]
526
+ # }
527
+
528
+ # @app.post("/send-message", response_model=MessageResponse, dependencies=[Depends(verify_api_key)])
529
+ # async def send_message(
530
+ # subject: str = Query(..., description="Email subject"),
531
+ # email_body: str = Query(..., description="Email body content"),
532
+ # sender_email: str = Query(..., description="Sender's email address"),
533
+ # recipient_email: str = Query(..., description="Recipient's email address"),
534
+ # reply_to_email: Optional[str] = Query(None, description="Reply-to email address")
535
+ # ):
536
+ # try:
537
+ # # Authenticate sender
538
+ # service = authenticate_gmail(sender_email)
539
+ # if not service:
540
+ # return MessageResponse(
541
+ # success=False,
542
+ # message="",
543
+ # error="Gmail authentication failed"
544
+ # )
545
+
546
+
547
+ # formatted_html = format_email_html(
548
+ # email_body
549
+ # )
550
+ # # Create the email
551
+ # email_message = create_email_message(
552
+ # sender=sender_email,
553
+ # to=recipient_email,
554
+ # subject=subject,
555
+ # message_text=formatted_html,
556
+ # reply_to=reply_to_email,
557
+ # is_html=True
558
+ # )
559
+ # # Send the email
560
+ # email_sent = send_gmail_message(service, "me", email_message)
561
+ # if email_sent:
562
+ # return MessageResponse(
563
+ # success=True,
564
+ # message="Email sent successfully.",
565
+ # email_sent=True,
566
+ # email_subject=subject
567
+ # )
568
+ # else:
569
+ # return MessageResponse(
570
+ # success=False,
571
+ # message="",
572
+ # email_sent=False,
573
+ # email_subject=subject,
574
+ # error="Failed to send via Gmail API"
575
+ # )
576
+ # except Exception as e:
577
+ # return MessageResponse(
578
+ # success=False,
579
+ # message="",
580
+ # error=str(e)
581
+ # )
582
+
583
+ # @app.post("/generate-message", response_model=MessageResponse, dependencies=[Depends(verify_api_key)])
584
+ # async def generate_message(
585
+ # background_tasks: BackgroundTasks,
586
+ # sender_email: str = Query(..., description="Sender's email address"),
587
+ # recipient_email: str = Query(..., description="Recipient's email address"),
588
+ # candidate_name: str = Query(..., description="Candidate's name"),
589
+ # current_role: str = Query(..., description="Candidate's current role"),
590
+ # current_company: str = Query(..., description="Candidate's current company"),
591
+ # company_name: str = Query(..., description="Company name for the job"),
592
+ # role: str = Query(..., description="Job role title"),
593
+ # recruiter_name: str = Query(..., description="Recruiter's name"),
594
+ # organisation: str = Query(..., description="Recruiting organisation"),
595
+ # message_type: MessageType = Query(..., description="Type of message"),
596
+ # job_evaluation: Optional[str] = Query(None, description="Recruiter's evaluation of candidate for the job"),
597
+ # reply_to_email: Optional[str] = Query(None, description="Recruiter's email for reply-to header"),
598
+ # send_email: bool = Query(False, description="Whether to send the email"),
599
+ # past_conversation: Optional[str] = Query(None, description="Previous messages with candidate")
600
+ # ):
601
+ # try:
602
+ # current_position = f"{current_role} at {current_company}"
603
+ # email_subject, generated_message = generate_recruitment_message_with_subject(
604
+ # msg_type=message_type.value.replace('followup', 'follow-up'),
605
+ # company=company_name,
606
+ # role_title=role,
607
+ # recruiter=recruiter_name,
608
+ # org=organisation,
609
+ # candidate=candidate_name,
610
+ # current_pos=current_position,
611
+ # evaluation=job_evaluation,
612
+ # past_conversation=past_conversation
613
+ # )
614
+ # email_sent = False
615
+ # if send_email:
616
+ # registered_users = []
617
+ # if os.path.exists("registered_users.csv"):
618
+ # df = pd.read_csv("registered_users.csv")
619
+ # registered_users = df['email'].tolist() if 'email' in df.columns else []
620
+ # if sender_email.lower() not in [user.lower() for user in registered_users]:
621
+ # return MessageResponse(
622
+ # success=True,
623
+ # message=generated_message,
624
+ # email_sent=False,
625
+ # email_subject=email_subject,
626
+ # error="Email not sent: Sender email is not registered"
627
+ # )
628
+ # service = authenticate_gmail(sender_email)
629
+ # if service:
630
+ # formatted_html = format_email_html(
631
+ # generated_message,
632
+ # sender_name=recruiter_name,
633
+ # sender_org=organisation
634
+ # )
635
+ # email_message = create_email_message(
636
+ # sender=sender_email,
637
+ # to=recipient_email,
638
+ # subject=email_subject,
639
+ # message_text=formatted_html,
640
+ # reply_to=reply_to_email,
641
+ # is_html=True
642
+ # )
643
+ # email_sent = send_gmail_message(service, "me", email_message)
644
+ # if not email_sent:
645
+ # return MessageResponse(
646
+ # success=True,
647
+ # message=generated_message,
648
+ # email_sent=False,
649
+ # email_subject=email_subject,
650
+ # error="Email not sent: Failed to send via Gmail API"
651
+ # )
652
+ # else:
653
+ # return MessageResponse(
654
+ # success=True,
655
+ # message=generated_message,
656
+ # email_sent=False,
657
+ # email_subject=email_subject,
658
+ # error="Email not sent: Gmail authentication failed"
659
+ # )
660
+ # return MessageResponse(
661
+ # success=True,
662
+ # message=generated_message,
663
+ # email_sent=email_sent,
664
+ # email_subject=email_subject
665
+ # )
666
+ # except Exception as e:
667
+ # return MessageResponse(
668
+ # success=False,
669
+ # message="",
670
+ # error=str(e)
671
+ # )
672
+
673
+ # @app.post("/refine-message", response_model=MessageResponse, dependencies=[Depends(verify_api_key)])
674
+ # async def refine_message(request: FeedbackRequest):
675
+ # try:
676
+ # email_subject, refined_message = refine_message_with_feedback(
677
+ # original_message=request.message,
678
+ # feedback=request.feedback
679
+ # )
680
+ # return MessageResponse(
681
+ # success=True,
682
+ # message=refined_message,
683
+ # email_sent=False,
684
+ # email_subject=email_subject
685
+ # )
686
+ # except Exception as e:
687
+ # return MessageResponse(
688
+ # success=False,
689
+ # message="",
690
+ # error=str(e)
691
+ # )
692
+
693
+ # @app.post("/authenticate", response_model=AuthenticateResponse, dependencies=[Depends(verify_api_key)])
694
+ # async def authenticate_user(request: AuthenticateRequest):
695
+ # try:
696
+ # if check_user_token_exists(request.email):
697
+ # service = authenticate_gmail(request.email, create_if_missing=False)
698
+ # if service:
699
+ # return AuthenticateResponse(
700
+ # success=True,
701
+ # message="User already authenticated and token is valid"
702
+ # )
703
+ # else:
704
+ # service = authenticate_gmail(request.email, create_if_missing=True)
705
+ # if service:
706
+ # return AuthenticateResponse(
707
+ # success=True,
708
+ # message="Token refreshed successfully"
709
+ # )
710
+ # else:
711
+ # return AuthenticateResponse(
712
+ # success=False,
713
+ # message="Failed to refresh token",
714
+ # error="Could not refresh existing token. Please check credentials.json"
715
+ # )
716
+ # else:
717
+ # try:
718
+ # creds = create_new_credentials(request.email)
719
+ # if creds:
720
+ # return AuthenticateResponse(
721
+ # success=True,
722
+ # message="Authentication successful. Token created and saved."
723
+ # )
724
+ # else:
725
+ # return AuthenticateResponse(
726
+ # success=False,
727
+ # message="Authentication failed",
728
+ # error="Failed to create credentials"
729
+ # )
730
+ # except Exception as e:
731
+ # return AuthenticateResponse(
732
+ # success=False,
733
+ # message="Authentication failed",
734
+ # error=str(e)
735
+ # )
736
+ # except Exception as e:
737
+ # return AuthenticateResponse(
738
+ # success=False,
739
+ # message="Authentication error",
740
+ # error=str(e)
741
+ # )
742
+
743
+ # @app.get("/health")
744
+ # async def health_check():
745
+ # """Health check endpoint - public access"""
746
+ # return {"status": "healthy", "timestamp": datetime.utcnow().isoformat()}
747
+
748
+ # # Protected documentation endpoint
749
+ # @app.get("/docs", dependencies=[Depends(verify_api_key)])
750
+ # async def get_documentation():
751
+ # """This will be handled by FastAPI's built-in docs"""
752
+ # pass
753
+
754
+ # if __name__ == "__main__":
755
+ # import uvicorn
756
+ # uvicorn.run(app, host="0.0.0.0", port=8000)
757
+
758
+
759
  from fastapi import FastAPI, HTTPException, BackgroundTasks, Depends, Header, status, Query
760
  from fastapi.middleware.cors import CORSMiddleware
761
  from fastapi.security import APIKeyHeader
 
1020
 
1021
  BODY:
1022
  [Your refined email body content here]
1023
+ Keep the same warm, conversational tone and intent as the original message, but incorporate the feedback to improve it. Make sure the message feels personal and approachable.
1024
  """
1025
  try:
1026
  messages = [
1027
+ SystemMessage(content="You are a friendly and approachable recruitment specialist. Refine the given message based on feedback while maintaining a warm, conversational tone and the original intent."),
1028
  HumanMessage(content=prompt)
1029
  ]
1030
  response = llm.invoke(messages, config={"callbacks": [langfuse_handler]})
 
1072
  # Outreach: Only request consent
1073
  if msg_type == "outreach":
1074
  prompt = f"""
1075
+ Generate a warm and friendly initial outreach message to a candidate.
1076
+ - Introduce yourself as {recruiter} from {org} in a casual, approachable way
1077
+ - Mention you came across their profile and were impressed by their background
1078
+ - Share that you're reaching out about an exciting opportunity ({role_title}) at {company}
1079
+ - Ask if they'd be interested in learning more about this role
1080
+ - Use a conversational tone - think of it as reaching out to a colleague or friend
1081
  - Do NOT discuss any job evaluation or judgment.
1082
+ - Keep it concise but warm and engaging
 
1083
  - No placeholders like [Candidate Name] or [Role Title] in the output.
1084
  """
1085
  else:
1086
  base_prompt = f"""
1087
+ Generate a friendly and professional recruitment {msg_type} with the following details:
1088
  - Company hiring: {company}
1089
  - Role: {role_title}
1090
  - Recruiter: {recruiter} from {org}
 
1096
  prompt = base_prompt
1097
  if msg_type == "introductory":
1098
  prompt += """
1099
+ Create a friendly and engaging introductory message that:
1100
+ - Thanks the candidate warmly for their response and interest
1101
+ - Shares exciting details about the role and company culture
1102
+ - Explains why you think this opportunity would be a great fit for their background
1103
+ - Suggests next steps (like a casual chat or coffee meeting) in a relaxed way
1104
+ - Uses a conversational, approachable tone while staying professional
1105
+ - Shows genuine enthusiasm about the opportunity
1106
+ - if in the Evaluation the verdict is rejected, Send a polite rejection message instead.
1107
  """
1108
  else: # followup
1109
  prompt += """
1110
+ Create a friendly and follow-up message that:
1111
+ - References your previous conversation in a warm, personal way
1112
+ - Shows continued enthusiasm and interest in the candidate
1113
+ - Addresses any concerns they might have in a supportive manner
1114
+ - Shares additional exciting reasons why this role would be perfect for them
1115
+ - Includes a relaxed but clear call to action
1116
+ - Uses a conversational tone that feels like catching up with someone
1117
  """
1118
 
1119
  # Use feedback if provided
 
1142
 
1143
  try:
1144
  messages = [
1145
+ SystemMessage(content="You are a friendly and approachable recruitment specialist who writes warm, engaging messages. Generate both an email subject line and body content. Use a conversational tone that feels personal and genuine while maintaining professionalism. Follow the exact format requested."),
1146
  HumanMessage(content=prompt)
1147
  ]
1148
  response = llm.invoke(messages, config={"callbacks": [langfuse_handler]})