Spaces:
Sleeping
Sleeping
UPDATE: antigravity test
Browse files- prompts.yaml +3 -3
- pyproject.toml +0 -14
- resumeRewritingService.py +36 -49
prompts.yaml
CHANGED
|
@@ -2,7 +2,7 @@ prompt: |
|
|
| 2 |
Act as a Senior Resume Writer and ATS Optimization Specialist with 20+ years of experience in recruitment.
|
| 3 |
|
| 4 |
I am providing you with:
|
| 5 |
-
1. A Current Resume (
|
| 6 |
2. A Target Job Description (JD).
|
| 7 |
3. The Current Date.
|
| 8 |
|
|
@@ -26,6 +26,7 @@ prompt: |
|
|
| 26 |
- Email Rule: You MUST replace the candidate's email domain with @atomicmail.io.
|
| 27 |
Example: john.doe@gmail.com becomes john.doe@atomicmail.io.
|
| 28 |
- Do not alter any other personal contact details.
|
|
|
|
| 29 |
|
| 30 |
4. Formatting Constraints:
|
| 31 |
- The output must be ONLY a code block containing the LaTeX code.
|
|
@@ -238,8 +239,7 @@ prompt: |
|
|
| 238 |
Target Job Description:
|
| 239 |
{{JOB_DESCRIPTION}}
|
| 240 |
|
| 241 |
-
|
| 242 |
-
{{CURRENT_RESUME}}
|
| 243 |
|
| 244 |
### OUTPUT INSTRUCTIONS
|
| 245 |
Generate ONLY the LaTeX code in a code block. Replace all placeholders with actual content from the resume. Ensure everything is aligned to the job description and immediately compilable.
|
|
|
|
| 2 |
Act as a Senior Resume Writer and ATS Optimization Specialist with 20+ years of experience in recruitment.
|
| 3 |
|
| 4 |
I am providing you with:
|
| 5 |
+
1. A Current Resume (attached as a file).
|
| 6 |
2. A Target Job Description (JD).
|
| 7 |
3. The Current Date.
|
| 8 |
|
|
|
|
| 26 |
- Email Rule: You MUST replace the candidate's email domain with @atomicmail.io.
|
| 27 |
Example: john.doe@gmail.com becomes john.doe@atomicmail.io.
|
| 28 |
- Do not alter any other personal contact details.
|
| 29 |
+
- ONLY include LinkedIn, GitHub, or Portfolio links if they explicitly appear in the original resume. Do not invent or add them. If they are missing, omit the corresponding `\mbox{...}` and `\AND` separators in the header.
|
| 30 |
|
| 31 |
4. Formatting Constraints:
|
| 32 |
- The output must be ONLY a code block containing the LaTeX code.
|
|
|
|
| 239 |
Target Job Description:
|
| 240 |
{{JOB_DESCRIPTION}}
|
| 241 |
|
| 242 |
+
|
|
|
|
| 243 |
|
| 244 |
### OUTPUT INSTRUCTIONS
|
| 245 |
Generate ONLY the LaTeX code in a code block. Replace all placeholders with actual content from the resume. Ensure everything is aligned to the job description and immediately compilable.
|
pyproject.toml
DELETED
|
@@ -1,14 +0,0 @@
|
|
| 1 |
-
[project]
|
| 2 |
-
name = "raftecresumetool"
|
| 3 |
-
version = "0.1.0"
|
| 4 |
-
description = "Add your description here"
|
| 5 |
-
readme = "README.md"
|
| 6 |
-
requires-python = ">=3.13"
|
| 7 |
-
dependencies = [
|
| 8 |
-
"gradio>=6.1.0",
|
| 9 |
-
"langchain>=1.1.3",
|
| 10 |
-
"langchain-core>=1.2.0",
|
| 11 |
-
"langchain-google-genai>=4.0.0",
|
| 12 |
-
"pdfplumber>=0.11.8",
|
| 13 |
-
"pyyaml>=6.0.3",
|
| 14 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
resumeRewritingService.py
CHANGED
|
@@ -1,72 +1,59 @@
|
|
| 1 |
-
|
| 2 |
-
from langchain_core.messages import HumanMessage
|
| 3 |
from utils import readPrompts
|
| 4 |
import os
|
| 5 |
-
import
|
| 6 |
-
import pdfplumber
|
| 7 |
-
from io import BytesIO
|
| 8 |
-
|
| 9 |
|
| 10 |
class ResumeEditService:
|
| 11 |
def __init__(self):
|
| 12 |
prompts = readPrompts(path=os.path.join(os.getcwd(), "prompts.yaml"))
|
| 13 |
self.prompt_template = prompts.get("prompt")
|
| 14 |
-
self.llm = self._initialize_llm()
|
| 15 |
-
|
| 16 |
-
def _initialize_llm(self):
|
| 17 |
-
"""Initialize the Google Generative AI model"""
|
| 18 |
-
return ChatGoogleGenerativeAI(
|
| 19 |
-
model="gemini-3-flash-preview",
|
| 20 |
-
temperature=0.3,
|
| 21 |
-
google_api_key=os.getenv("GOOGLE_API_KEY")
|
| 22 |
-
)
|
| 23 |
-
|
| 24 |
-
def _extract_text_from_pdf_base64(self, base64_pdf: str) -> str:
|
| 25 |
-
"""
|
| 26 |
-
Extract text from base64 encoded PDF
|
| 27 |
-
|
| 28 |
-
Args:
|
| 29 |
-
base64_pdf: Base64 encoded PDF string
|
| 30 |
-
|
| 31 |
-
Returns:
|
| 32 |
-
Extracted text from the PDF
|
| 33 |
-
"""
|
| 34 |
-
# Decode base64 to bytes
|
| 35 |
-
pdf_bytes = base64.b64decode(base64_pdf)
|
| 36 |
|
| 37 |
-
#
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
# Extract text using PdfPlumber
|
| 41 |
-
text = ""
|
| 42 |
-
|
| 43 |
-
with pdfplumber.open(pdf_file) as pdf:
|
| 44 |
-
for page in pdf.pages:
|
| 45 |
-
text += page.extract_text() or ""
|
| 46 |
-
|
| 47 |
-
return text.strip()
|
| 48 |
|
| 49 |
def generate_optimized_resume(self, resume_base64: str, job_description: str, current_date: str) -> str:
|
| 50 |
"""
|
| 51 |
-
Generate optimized resume based on job description
|
| 52 |
|
| 53 |
Args:
|
| 54 |
-
resume_base64: Base64 encoded PDF resume
|
| 55 |
job_description: The target job description
|
| 56 |
current_date: Current date for timeline adjustments
|
| 57 |
|
| 58 |
Returns:
|
| 59 |
-
|
| 60 |
"""
|
| 61 |
-
# Extract text from PDF
|
| 62 |
-
current_resume = self._extract_text_from_pdf_base64(resume_base64)
|
| 63 |
|
| 64 |
-
#
|
| 65 |
prompt = self.prompt_template.replace("{{CURRENT_DATE}}", current_date)
|
| 66 |
prompt = prompt.replace("{{JOB_DESCRIPTION}}", job_description)
|
| 67 |
-
prompt = prompt.replace("{{CURRENT_RESUME}}", current_resume)
|
| 68 |
|
| 69 |
-
#
|
| 70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
|
| 72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import google.generativeai as genai
|
|
|
|
| 2 |
from utils import readPrompts
|
| 3 |
import os
|
| 4 |
+
import re
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
class ResumeEditService:
|
| 7 |
def __init__(self):
|
| 8 |
prompts = readPrompts(path=os.path.join(os.getcwd(), "prompts.yaml"))
|
| 9 |
self.prompt_template = prompts.get("prompt")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
+
# Configure the Gemini API
|
| 12 |
+
genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
|
| 13 |
+
self.model = genai.GenerativeModel('gemini-1.5-flash')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
def generate_optimized_resume(self, resume_base64: str, job_description: str, current_date: str) -> str:
|
| 16 |
"""
|
| 17 |
+
Generate optimized resume based on job description using Gemini with file attachment.
|
| 18 |
|
| 19 |
Args:
|
| 20 |
+
resume_base64: Base64 encoded PDF/DOCX resume
|
| 21 |
job_description: The target job description
|
| 22 |
current_date: Current date for timeline adjustments
|
| 23 |
|
| 24 |
Returns:
|
| 25 |
+
Optimized resume in LaTeX format (code fences stripped)
|
| 26 |
"""
|
|
|
|
|
|
|
| 27 |
|
| 28 |
+
# Prepare the prompt
|
| 29 |
prompt = self.prompt_template.replace("{{CURRENT_DATE}}", current_date)
|
| 30 |
prompt = prompt.replace("{{JOB_DESCRIPTION}}", job_description)
|
|
|
|
| 31 |
|
| 32 |
+
# Prepare the inline data for the file attachment
|
| 33 |
+
# Assuming PDF for now based on app.py, but could support others.
|
| 34 |
+
# The app.py reads the file and base64 encodes it without a mime prefix.
|
| 35 |
+
# We need to construct the part for the model.
|
| 36 |
+
|
| 37 |
+
file_part = {
|
| 38 |
+
"mime_type": "application/pdf",
|
| 39 |
+
"data": resume_base64
|
| 40 |
+
}
|
| 41 |
|
| 42 |
+
# Generate content
|
| 43 |
+
response = self.model.generate_content([prompt, file_part])
|
| 44 |
+
|
| 45 |
+
# Extract text
|
| 46 |
+
response_text = response.text
|
| 47 |
+
|
| 48 |
+
# Strip markdown code fences if present
|
| 49 |
+
cleaned_text = self._strip_code_fences(response_text)
|
| 50 |
+
|
| 51 |
+
return cleaned_text
|
| 52 |
+
|
| 53 |
+
def _strip_code_fences(self, text: str) -> str:
|
| 54 |
+
"""Removes markdown code fences from the text."""
|
| 55 |
+
# Remove opening fence (e.g. ```latex, ```tex, ```)
|
| 56 |
+
text = re.sub(r'^```\w*\s*', '', text.strip())
|
| 57 |
+
# Remove closing fence
|
| 58 |
+
text = re.sub(r'\s*```$', '', text.strip())
|
| 59 |
+
return text.strip()
|