SyedAzlanzar commited on
Commit
67ba1ec
·
1 Parent(s): c232ec4

@feat : cover letter enhanced, cl upload file, comment out some hf code.

Browse files
README.md CHANGED
@@ -8,3 +8,8 @@ pinned: false
8
  ---
9
 
10
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
8
  ---
9
 
10
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
11
+
12
+
13
+ <!-- .\venv\Scripts\activate.bat -->
14
+ <!-- pip install -r requirements.txt -->
15
+ <!-- uvicorn app.main:app --reload -->
app/api/routes.py CHANGED
@@ -1,6 +1,6 @@
1
  from fastapi import APIRouter, HTTPException
2
  from app.models.schema import GenerateRequest, GenerateResponse
3
- from app.services.generator import generate_cover_letter
4
  from app.services.pdf_creator import save_pdf
5
  from app.services.resume_parser import extract_resume_text
6
  from app.utils.file_utils import generate_unique_filename
@@ -19,27 +19,27 @@ async def generate_cover_letter_api(data: GenerateRequest):
19
  raise HTTPException(status_code=400, detail="Job detail is too long")
20
 
21
  resume_text = extract_resume_text(data.resume_path)
22
- letter_text = await generate_cover_letter(data, resume_text)
23
- print(letter_text,"====================")
24
- md_cover_letter = build_cover_letter_md(
25
- your_name=data.full_name,
26
- postal_code=data.postal_code,
27
- city=data.city,
28
- email=data.email,
29
- phone=data.phone_number,
30
- job_title=data.job_title,
31
- company_name=data.company_name,
32
- generated_paragraphs=letter_text
33
- )
34
 
35
- text_cover_letter = convert_md_to_text(md_cover_letter)
36
 
37
  filename = generate_unique_filename()
38
- pdf_path = save_pdf(text_cover_letter, filename)
39
 
40
  return GenerateResponse(
41
  letter=letter_text,
42
- pdf_url=pdf_path
43
  )
44
 
45
  except HTTPException as http_exc:
@@ -72,4 +72,29 @@ async def upload_resume(resume: UploadFile = File(...)):
72
  "success": False,
73
  "error": str(e)
74
  }
75
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from fastapi import APIRouter, HTTPException
2
  from app.models.schema import GenerateRequest, GenerateResponse
3
+ from app.services.generator import coverLetterGenerativeAIBot
4
  from app.services.pdf_creator import save_pdf
5
  from app.services.resume_parser import extract_resume_text
6
  from app.utils.file_utils import generate_unique_filename
 
19
  raise HTTPException(status_code=400, detail="Job detail is too long")
20
 
21
  resume_text = extract_resume_text(data.resume_path)
22
+ letter_text = await coverLetterGenerativeAIBot(data, resume_text)
23
+
24
+ # md_cover_letter = build_cover_letter_md(
25
+ # your_name=data.full_name,
26
+ # postal_code=data.postal_code,
27
+ # city=data.city,
28
+ # email=data.email,
29
+ # phone=data.phone_number,
30
+ # job_title=data.job_title,
31
+ # company_name=data.company_name,
32
+ # generated_paragraphs=letter_text
33
+ # )
34
 
35
+ # text_cover_letter = convert_md_to_text(md_cover_letter)
36
 
37
  filename = generate_unique_filename()
38
+ # pdf_path = save_pdf(text_cover_letter, filename)
39
 
40
  return GenerateResponse(
41
  letter=letter_text,
42
+ pdf_url=""
43
  )
44
 
45
  except HTTPException as http_exc:
 
72
  "success": False,
73
  "error": str(e)
74
  }
75
+
76
+ @router.post("/upload-file")
77
+ async def upload_file(pdf: UploadFile = File(...)):
78
+ try:
79
+ # Read resume content
80
+ pdf_content = await pdf.read()
81
+
82
+ # Upload to HuggingFace Hub
83
+ pdf_url = storage_service.upload_file_to_hf(
84
+ file_content=pdf_content,
85
+ folder="cover-letters",
86
+ filename=pdf.filename
87
+ )
88
+
89
+ return {
90
+ "success": True,
91
+ "url": pdf_url
92
+ }
93
+
94
+ except Exception as e:
95
+ return {
96
+ "success": False,
97
+ "error": str(e)
98
+ }
99
+
100
+
app/main.py CHANGED
@@ -38,7 +38,6 @@ async def log_requests(request: Request, call_next):
38
 
39
  @app.get("/")
40
  def ping():
41
- print()
42
  return {"status": "ok"}
43
 
44
 
 
38
 
39
  @app.get("/")
40
  def ping():
 
41
  return {"status": "ok"}
42
 
43
 
app/services/generator.py CHANGED
@@ -2,238 +2,361 @@ from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
2
  import torch
3
  from app.models.schema import GenerateRequest
4
  import os
 
 
5
 
6
 
7
- os.environ["TRANSFORMERS_CACHE"] = "/code/backend/transformers_cache"
8
- os.makedirs(os.environ["TRANSFORMERS_CACHE"], exist_ok=True)
9
 
10
 
11
- # Initialization model immediately after server is Up!
12
- device = "cuda" if torch.cuda.is_available() else "cpu"
13
- dtype = torch.float16 if device == "cuda" else torch.float32
14
 
15
- # TinyLlama/TinyLlama-1.1B-Chat-v1.0
16
- model_name = "Qwen/Qwen2.5-0.5B-Instruct"
17
- tokenizer = AutoTokenizer.from_pretrained(model_name)
18
- model = AutoModelForCausalLM.from_pretrained(
19
- model_name,
20
- dtype="auto",
21
- device_map="auto"
22
- )
23
 
24
- model = model.to(device)
25
 
26
 
27
- async def generate_cover_letter(data: GenerateRequest, resume_text: str) -> str:
28
- try:
29
 
30
- altered_job_details = await job_details_alteration(data.job_details)
31
 
32
 
33
- prompt = f"""
34
- # AI Writer Bot Prompt
35
 
36
- You are an expert writer that creates personalized, human-written paragraphs based on job descriptions and CV information. Your goal is to craft authentic paragraphs that sound like they're written by a Pakistani professional with 6.5 IELTS band score - natural, clear, but not overly sophisticated or AI-generated.
37
 
38
- ## Input Requirements
39
 
40
- - ** Job Title :** Exact title of the position being applied for
41
- - **Job Description (JD):** Complete job posting with requirements, responsibilities, and company information
42
- - **CV/Resume:** Tailored resume specifically prepared for the target role
43
 
44
 
45
- Job Title:
46
- {data.job_title}
47
 
48
- Job Description:
49
- {altered_job_details}
50
 
51
- Resume Content:
52
- {resume_text}
53
-
54
- ## Output Format
55
-
56
- Provide only the paragraph content in this format:
57
-
58
- Dear {data.company_name},
59
-
60
- ## Core Writing Instructions
61
-
62
- ### Content Strategy (German Market Focus)
63
-
64
- 1. **Opening:** State the position you're applying for and briefly explain your interest in the company mentioned in the job details
65
- 2. **Body:** Focus on what value you bring to the company (not what you can gain from them)
66
- 3. **Skills Gap Handling:** If you lack certain required skills, show eagerness to learn and provide examples of quick skill acquisition
67
- 4. **Experience Mismatch:** If years of experience don't perfectly match (e.g., job asks 5+, you have ~4.5), emphasize the quality and relevance of your experience
68
- 5. **Closing:** Professional closing with availability and next steps
69
-
70
- ### Language Requirements
71
-
72
- - **Tone:** Professional and formal (German market prefers formal tone over conversational)
73
- - **Length:** Concise and focused (3 paragraphs preferred for German market, maximum 4 paragraphs)
74
- - **Total word count:** 250-400 words maximum
75
- - **Per paragraph:** 70-100 words (3-4 sentences each)
76
- - **Opening:** 50-70 words
77
- - **Body:** 80-100 words
78
- - **Closing:** 50-70 words
79
- - **Format:** Use continuous text only - no bullet points or lists within the paragraph body
80
- - **Avoid These Overused Words:**
81
- - Robust → use "strong," "reliable," or "sturdy"
82
- - Keen → use "eager," "interested," or "enthusiastic"
83
- - Scalable → use "that can handle growth"
84
- - Leverage → use "use"
85
- - Utilize → use "use"
86
- - Innovative → describe specific innovation
87
- - Optimal → use "best," "ideal," or "most effective"
88
- - Capability → use "ability," "feature," or "function"
89
- - Interface → use "connect," "work with," or describe interaction
90
- - Efficient → use "quick," "streamlined," or describe time savings
91
- - Paradigm → use "model," "approach," or "way of thinking"
92
- - Facilitate → use "help," "make easier," or "support"
93
- - Optimize → use "improve," "refine," or describe enhancement
94
-
95
- ### Specific Content Elements to Include When Relevant
96
 
97
- #### German Language Skills
98
 
99
- - Mention motivation for learning German and timeline goals
100
- - Address language barrier concerns proactively
101
 
102
- #### Technology Learning Appetite
103
 
104
- - Show willingness to learn new technologies (e.g., SAP, Vue.js, Ai, LLMs)
105
- - Highlight strong fundamentals that enable quick learning
106
- - Provide examples of past rapid skill acquisition
107
 
108
- #### Experience Quality Over Quantity
109
-
110
- - Emphasize depth and relevance of experience
111
- - Highlight specific achievements and impact
112
- - Show progression and growth in current role
113
-
114
- ## Output Rules
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
- 1. **Content Only:** Provide only the greeting and 3-paragraph body content - no headers, contact information, date, subject line, or closing signature
117
- 2. **Customization:** Every paragraph must be specifically tailored to the job description
118
- 3. **Authenticity:** Write like a real human - natural flow, simple language, avoid overly polished corporate speak
119
- 4. **Specificity:** Reference specific company details, role requirements, and technologies mentioned in the JD
120
 
121
- ## Quality Checklist
 
 
 
122
 
123
- Before finalizing, ensure the paragraphs meet these criteria:
 
124
 
125
- - [ ] Addresses the specific role and company mentioned in the job details by name
126
- - [ ] Highlights 2-3 most relevant experiences from the CV
127
- - [ ] Addresses any obvious skill gaps with learning commitment
128
- - [ ] Uses natural, varied language (avoiding the banned word list)
129
- - [ ] Uses simple, clear language that sounds human-written (not AI-generated)
130
- - [ ] Avoids overly sophisticated vocabulary or complex sentence structures
131
- - [ ] Includes a clear call to action
132
- - [ ] Stays within 3-4 paragraph limit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
 
134
- Remember: Your goal is to write paragraphs that sounds like it was written by a real person with good English skills, not by AI. Keep it natural, straightforward, and genuine.
 
135
 
136
- """
137
 
 
 
 
138
 
139
- print(len(prompt),'length')
 
 
 
 
 
 
140
 
 
 
141
  messages = [
142
- {"role": "system", "content": prompt}
 
143
  ]
144
- # Apply chat template
145
- text = tokenizer.apply_chat_template(
146
- messages,
147
- tokenize=False,
148
- add_generation_prompt=True
149
- )
150
-
151
- # Tokenize input
152
- model_inputs = tokenizer(
153
- [text],
154
- return_tensors="pt",
155
- padding=True,
156
- truncation=True,
157
- max_length=16000
158
- ).to(model.device)
159
-
160
- # Generate with parameters optimized for markdown
161
- generated_ids = model.generate(
162
- **model_inputs,
163
- max_new_tokens=7192, # Increased for longer markdown content
164
- do_sample=True,
165
- temperature=0.8, # Slightly lower for more consistent formatting
166
- top_p=0.9,
167
- top_k=50,
168
- repetition_penalty=1.15, # Higher to avoid repetitive formatting
169
- eos_token_id=tokenizer.eos_token_id,
170
- pad_token_id=tokenizer.pad_token_id
171
- )
172
-
173
- generated_only_ids = generated_ids[:, model_inputs.input_ids.shape[1]:]
174
-
175
 
176
- # Decode output
177
- response = tokenizer.decode(
178
- generated_only_ids[0],
179
- skip_special_tokens=True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  )
181
 
182
- return response.strip()
183
-
184
-
185
-
186
- except Exception as e:
187
- return {"error": str(e)}
188
-
189
-
190
- async def job_details_alteration(job_details:str) -> str:
191
- try:
192
- prompt = f"""
193
- job_description: {job_details}
194
-
195
- Your task: Extract only the important parts and rewrite them clearly into 4 sections:
196
- - **Responsibilities** (what the candidate will do)
197
- - **Requirements / Qualifications** (skills, education, experience needed)
198
- - **Who You Are** (traits, mindset, culture fit)
199
- - **Preferred Candidate** (optional nice-to-have skills or experience)
200
 
201
- Rules:
202
- - Remove irrelevant parts such as "About Us", "Why Join Us", "Perks/Benefits", or generic company marketing.
203
- - Keep the output concise and professional.
204
- - If a section is not found, skip it. """
205
 
206
- messages = [
207
- {"role": "system", "content": prompt}
208
- ]
209
- text = tokenizer.apply_chat_template(
210
- messages,
211
- tokenize=False,
212
- add_generation_prompt=True
213
- )
214
- model_inputs = tokenizer(
215
- [text],
216
- return_tensors="pt",
217
- padding=True,
218
- truncation=True,
219
- max_length=2048
220
- ).to(model.device)
221
- generated_ids = model.generate(
222
- **model_inputs,
223
- max_new_tokens=8192,
224
- do_sample=True,
225
- temperature=0.5,
226
- top_p=0.9,
227
- top_k=50,
228
- repetition_penalty=1.15,
229
- eos_token_id=tokenizer.eos_token_id,
230
- pad_token_id=tokenizer.pad_token_id
231
- )
232
- generated_only_ids = generated_ids[:, model_inputs.input_ids.shape[1]:]
233
- response = tokenizer.decode(
234
- generated_only_ids[0],
235
- skip_special_tokens=True
236
- )
237
- return response.strip()
238
  except Exception as e:
239
- return {"error": str(e)}
 
 
2
  import torch
3
  from app.models.schema import GenerateRequest
4
  import os
5
+ from openai import OpenAI
6
+ import tiktoken
7
 
8
 
9
+ # os.environ["TRANSFORMERS_CACHE"] = "/code/backend/transformers_cache"
10
+ # os.makedirs(os.environ["TRANSFORMERS_CACHE"], exist_ok=True)
11
 
12
 
13
+ # # Initialization model immediately after server is Up!
14
+ # device = "cuda" if torch.cuda.is_available() else "cpu"
15
+ # dtype = torch.float16 if device == "cuda" else torch.float32
16
 
17
+ # # TinyLlama/TinyLlama-1.1B-Chat-v1.0
18
+ # model_name = "Qwen/Qwen2.5-0.5B-Instruct"
19
+ # tokenizer = AutoTokenizer.from_pretrained(model_name)
20
+ # model = AutoModelForCausalLM.from_pretrained(
21
+ # model_name,
22
+ # dtype="auto",
23
+ # device_map="auto"
24
+ # )
25
 
26
+ # model = model.to(device)
27
 
28
 
29
+ # async def generate_cover_letter(data: GenerateRequest, resume_text: str) -> str:
30
+ # try:
31
 
32
+ # altered_job_details = await job_details_alteration(data.job_details)
33
 
34
 
35
+ # prompt = f"""
36
+ # # AI Writer Bot Prompt
37
 
38
+ # You are an expert writer that creates personalized, human-written paragraphs based on job descriptions and CV information. Your goal is to craft authentic paragraphs that sound like they're written by a Pakistani professional with 6.5 IELTS band score - natural, clear, but not overly sophisticated or AI-generated.
39
 
40
+ # ## Input Requirements
41
 
42
+ # - ** Job Title :** Exact title of the position being applied for
43
+ # - **Job Description (JD):** Complete job posting with requirements, responsibilities, and company information
44
+ # - **CV/Resume:** Tailored resume specifically prepared for the target role
45
 
46
 
47
+ # Job Title:
48
+ # {data.job_title}
49
 
50
+ # Job Description:
51
+ # {altered_job_details}
52
 
53
+ # Resume Content:
54
+ # {resume_text}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
+ # ## Output Format
57
 
58
+ # Provide only the paragraph content in this format:
 
59
 
60
+ # Dear {data.company_name},
61
 
62
+ # ## Core Writing Instructions
 
 
63
 
64
+ # ### Content Strategy (German Market Focus)
65
+
66
+ # 1. **Opening:** State the position you're applying for and briefly explain your interest in the company mentioned in the job details
67
+ # 2. **Body:** Focus on what value you bring to the company (not what you can gain from them)
68
+ # 3. **Skills Gap Handling:** If you lack certain required skills, show eagerness to learn and provide examples of quick skill acquisition
69
+ # 4. **Experience Mismatch:** If years of experience don't perfectly match (e.g., job asks 5+, you have ~4.5), emphasize the quality and relevance of your experience
70
+ # 5. **Closing:** Professional closing with availability and next steps
71
+
72
+ # ### Language Requirements
73
+
74
+ # - **Tone:** Professional and formal (German market prefers formal tone over conversational)
75
+ # - **Length:** Concise and focused (3 paragraphs preferred for German market, maximum 4 paragraphs)
76
+ # - **Total word count:** 250-400 words maximum
77
+ # - **Per paragraph:** 70-100 words (3-4 sentences each)
78
+ # - **Opening:** 50-70 words
79
+ # - **Body:** 80-100 words
80
+ # - **Closing:** 50-70 words
81
+ # - **Format:** Use continuous text only - no bullet points or lists within the paragraph body
82
+ # - **Avoid These Overused Words:**
83
+ # - Robust → use "strong," "reliable," or "sturdy"
84
+ # - Keen → use "eager," "interested," or "enthusiastic"
85
+ # - Scalable → use "that can handle growth"
86
+ # - Leverage → use "use"
87
+ # - Utilize → use "use"
88
+ # - Innovative → describe specific innovation
89
+ # - Optimal → use "best," "ideal," or "most effective"
90
+ # - Capability → use "ability," "feature," or "function"
91
+ # - Interface → use "connect," "work with," or describe interaction
92
+ # - Efficient → use "quick," "streamlined," or describe time savings
93
+ # - Paradigm → use "model," "approach," or "way of thinking"
94
+ # - Facilitate → use "help," "make easier," or "support"
95
+ # - Optimize → use "improve," "refine," or describe enhancement
96
+
97
+ # ### Specific Content Elements to Include When Relevant
98
+
99
+ # #### German Language Skills
100
+
101
+ # - Mention motivation for learning German and timeline goals
102
+ # - Address language barrier concerns proactively
103
+
104
+ # #### Technology Learning Appetite
105
+
106
+ # - Show willingness to learn new technologies (e.g., SAP, Vue.js, Ai, LLMs)
107
+ # - Highlight strong fundamentals that enable quick learning
108
+ # - Provide examples of past rapid skill acquisition
109
+
110
+ # #### Experience Quality Over Quantity
111
+
112
+ # - Emphasize depth and relevance of experience
113
+ # - Highlight specific achievements and impact
114
+ # - Show progression and growth in current role
115
+
116
+ # ## Output Rules
117
+
118
+ # 1. **Content Only:** Provide only the greeting and 3-paragraph body content - no headers, contact information, date, subject line, or closing signature
119
+ # 2. **Customization:** Every paragraph must be specifically tailored to the job description
120
+ # 3. **Authenticity:** Write like a real human - natural flow, simple language, avoid overly polished corporate speak
121
+ # 4. **Specificity:** Reference specific company details, role requirements, and technologies mentioned in the JD
122
+
123
+ # ## Quality Checklist
124
+
125
+ # Before finalizing, ensure the paragraphs meet these criteria:
126
+
127
+ # - [ ] Addresses the specific role and company mentioned in the job details by name
128
+ # - [ ] Highlights 2-3 most relevant experiences from the CV
129
+ # - [ ] Addresses any obvious skill gaps with learning commitment
130
+ # - [ ] Uses natural, varied language (avoiding the banned word list)
131
+ # - [ ] Uses simple, clear language that sounds human-written (not AI-generated)
132
+ # - [ ] Avoids overly sophisticated vocabulary or complex sentence structures
133
+ # - [ ] Includes a clear call to action
134
+ # - [ ] Stays within 3-4 paragraph limit
135
+
136
+ # Remember: Your goal is to write paragraphs that sounds like it was written by a real person with good English skills, not by AI. Keep it natural, straightforward, and genuine.
137
+
138
+ # """
139
+
140
+
141
+ # print(len(prompt),'length')
142
+
143
+ # messages = [
144
+ # {"role": "system", "content": prompt}
145
+ # ]
146
+ # # Apply chat template
147
+ # text = tokenizer.apply_chat_template(
148
+ # messages,
149
+ # tokenize=False,
150
+ # add_generation_prompt=True
151
+ # )
152
+
153
+ # # Tokenize input
154
+ # model_inputs = tokenizer(
155
+ # [text],
156
+ # return_tensors="pt",
157
+ # padding=True,
158
+ # truncation=True,
159
+ # max_length=16000
160
+ # ).to(model.device)
161
+
162
+ # # Generate with parameters optimized for markdown
163
+ # generated_ids = model.generate(
164
+ # **model_inputs,
165
+ # max_new_tokens=7192, # Increased for longer markdown content
166
+ # do_sample=True,
167
+ # temperature=0.8, # Slightly lower for more consistent formatting
168
+ # top_p=0.9,
169
+ # top_k=50,
170
+ # repetition_penalty=1.15, # Higher to avoid repetitive formatting
171
+ # eos_token_id=tokenizer.eos_token_id,
172
+ # pad_token_id=tokenizer.pad_token_id
173
+ # )
174
+
175
+ # generated_only_ids = generated_ids[:, model_inputs.input_ids.shape[1]:]
176
+
177
+
178
+ # # Decode output
179
+ # response = tokenizer.decode(
180
+ # generated_only_ids[0],
181
+ # skip_special_tokens=True
182
+ # )
183
+
184
+ # return response.strip()
185
+
186
+
187
+
188
+ # except Exception as e:
189
+ # return {"error": str(e)}
190
+
191
+
192
+ # async def job_details_alteration(job_details:str) -> str:
193
+ # try:
194
+ # prompt = f"""
195
+ # job_description: {job_details}
196
+
197
+ # Your task: Extract only the important parts and rewrite them clearly into 4 sections:
198
+ # - **Responsibilities** (what the candidate will do)
199
+ # - **Requirements / Qualifications** (skills, education, experience needed)
200
+ # - **Who You Are** (traits, mindset, culture fit)
201
+ # - **Preferred Candidate** (optional nice-to-have skills or experience)
202
+
203
+ # Rules:
204
+ # - Remove irrelevant parts such as "About Us", "Why Join Us", "Perks/Benefits", or generic company marketing.
205
+ # - Keep the output concise and professional.
206
+ # - If a section is not found, skip it. """
207
+
208
+ # messages = [
209
+ # {"role": "system", "content": prompt}
210
+ # ]
211
+ # text = tokenizer.apply_chat_template(
212
+ # messages,
213
+ # tokenize=False,
214
+ # add_generation_prompt=True
215
+ # )
216
+ # model_inputs = tokenizer(
217
+ # [text],
218
+ # return_tensors="pt",
219
+ # padding=True,
220
+ # truncation=True,
221
+ # max_length=2048
222
+ # ).to(model.device)
223
+ # generated_ids = model.generate(
224
+ # **model_inputs,
225
+ # max_new_tokens=8192,
226
+ # do_sample=True,
227
+ # temperature=0.5,
228
+ # top_p=0.9,
229
+ # top_k=50,
230
+ # repetition_penalty=1.15,
231
+ # eos_token_id=tokenizer.eos_token_id,
232
+ # pad_token_id=tokenizer.pad_token_id
233
+ # )
234
+ # generated_only_ids = generated_ids[:, model_inputs.input_ids.shape[1]:]
235
+ # response = tokenizer.decode(
236
+ # generated_only_ids[0],
237
+ # skip_special_tokens=True
238
+ # )
239
+ # return response.strip()
240
+ # except Exception as e:
241
+ # return {"error": str(e)}
242
+
243
+
244
+ async def coverLetterGenerativeAIBot(data: GenerateRequest, resume_text: str) -> str:
245
+ try:
246
 
247
+ user_content = f"""
248
+ You are a professional cover letter writer.
 
 
249
 
250
+ ## Input
251
+ - **Job Title:** {data.job_title}
252
+ - **Job Description:** {data.job_details}
253
+ - **Resume:** {resume_text}
254
 
255
+ ## Task
256
+ Write ONLY the four body paragraphs of a professional cover letter, following this structure:
257
 
258
+ 1. **Paragraph 1**: Introduction where you're studying, and that you're applying for a student/intern position while studying.
259
+ 2. **Paragraph 2**: Previous experience or academic projects, include 2 to 4 bullet points highlighting key projects or achievements.
260
+ 3. **Paragraph 3**: Why you want to join this company what attracts you to them and what you can contribute.
261
+ 4. **Paragraph 4**: Closing short and to the point, showing enthusiasm and availability.
262
+
263
+ ### Language Requirements
264
+
265
+ - **Tone:** Professional and formal (German market prefers formal tone over conversational)
266
+ - **Length:** Concise and focused (3 paragraphs preferred for German market, maximum 4 paragraphs)
267
+ - **Total word count:** 250-400 words maximum
268
+ - **Per paragraph:** 70-100 words (3-4 sentences each)
269
+ - **Opening:** 50-70 words
270
+ - **Body:** 80-100 words
271
+ - **Closing:** 50-70 words
272
+
273
+ - **Avoid These Overused Words:**
274
+ - Robust → use "strong," "reliable," or "sturdy"
275
+ - Keen → use "eager," "interested," or "enthusiastic"
276
+ - Scalable → use "that can handle growth"
277
+ - Leverage → use "use"
278
+ - Utilize → use "use"
279
+ - Innovative → describe specific innovation
280
+ - Optimal → use "best," "ideal," or "most effective"
281
+ - Capability → use "ability," "feature," or "function"
282
+ - Interface → use "connect," "work with," or describe interaction
283
+ - Efficient → use "quick," "streamlined," or describe time savings
284
+ - Paradigm → use "model," "approach," or "way of thinking"
285
+ - Facilitate → use "help," "make easier," or "support"
286
+ - Optimize → use "improve," "refine," or describe enhancement
287
+
288
+ #### German Language Skills
289
 
290
+ - Mention motivation for learning German and timeline goals
291
+ - Address language barrier concerns proactively
292
 
293
+ #### Technology Learning Appetite
294
 
295
+ - Show willingness to learn new technologies (e.g., SAP, Vue.js, Ai, LLMs)
296
+ - Highlight strong fundamentals that enable quick learning
297
+ - Provide examples of past rapid skill acquisition
298
 
299
+ ## Important:
300
+ - Do **not** include greetings (e.g., "Dear Hiring Manager")
301
+ - Do **not** include sign-offs (e.g., "Sincerely")
302
+ - Do **not** include headers or formatting
303
+ - Do **not** add number on paragraphs, also after each paragraph give a one line space gap then starts next paragraph
304
+ - Return **only** the four paragraphs
305
+ """
306
 
307
+ openAiApiClient = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
308
+
309
  messages = [
310
+ {"role": "system", "content": "You are a professional cover letter body paragraphs generator."},
311
+ {"role": "user", "content": user_content}
312
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
 
314
+ # response = openAiApiClient.chat.completions.create(
315
+ # model="gpt-4-0613",
316
+ # messages=messages,
317
+ # max_tokens=6092
318
+ # )
319
+ # Choose model and max token limit
320
+ model = "gpt-4-0613"
321
+ max_total_tokens = 8192 # hard limit for gpt-4-0613
322
+
323
+ # Load tokenizer
324
+ encoding = tiktoken.encoding_for_model(model)
325
+
326
+ # Count tokens in the messages
327
+ def count_message_tokens(messages, encoding):
328
+ tokens_per_message = 3 # Every message object has a cost (role + content + metadata)
329
+ tokens_per_name = 1 # If 'name' is present
330
+ total_tokens = 0
331
+
332
+ for message in messages:
333
+ total_tokens += tokens_per_message
334
+ for key, value in message.items():
335
+ total_tokens += len(encoding.encode(value))
336
+ if key == "name":
337
+ total_tokens += tokens_per_name
338
+ total_tokens += 3 # Every reply is primed with <|start|>assistant<|message|>
339
+ return total_tokens
340
+
341
+ input_tokens = count_message_tokens(messages, encoding)
342
+ available_output_tokens = max_total_tokens - input_tokens
343
+
344
+ # Ensure we don't go negative
345
+ if available_output_tokens < 1:
346
+ raise ValueError("Input messages are too long. Reduce their size.")
347
+
348
+ # Call API
349
+ response = openAiApiClient.chat.completions.create(
350
+ model=model,
351
+ messages=messages,
352
+ max_tokens=available_output_tokens
353
  )
354
 
355
+ ai_text = response.choices[0].message.content
356
+
357
+ return ai_text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
 
 
 
 
 
359
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
360
  except Exception as e:
361
+ print(f"Error: {e}")
362
+ return ""
app/services/hf_storage_service.py CHANGED
@@ -18,11 +18,11 @@ class HuggingFaceStorageService:
18
  raise ValueError("Folder must be 'resumes' or 'cover-letters'")
19
 
20
  if filename is None:
21
- filename = f"{uuid.uuid4().hex}.pdf"
22
 
23
  # Create a unique path with date
24
  timestamp = datetime.now().strftime("%Y/%m/%d")
25
- file_path = f"{folder}/{timestamp}/{filename}"
26
 
27
  # Save bytes to temp file
28
  with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(filename)[1]) as temp_file:
 
18
  raise ValueError("Folder must be 'resumes' or 'cover-letters'")
19
 
20
  if filename is None:
21
+ filename = f"{uuid.uuid4().hex}"
22
 
23
  # Create a unique path with date
24
  timestamp = datetime.now().strftime("%Y/%m/%d")
25
+ file_path = f"{folder}/{timestamp}/{filename}.pdf"
26
 
27
  # Save bytes to temp file
28
  with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(filename)[1]) as temp_file:
app/services/pdf_creator.py CHANGED
@@ -1,7 +1,5 @@
1
  from fpdf import FPDF
2
  import os
3
- from datetime import datetime
4
- import io
5
  from app.services.hf_storage_service import HuggingFaceStorageService
6
  import unicodedata
7
  import markdown2
@@ -61,9 +59,16 @@ def build_cover_letter_md(
61
  job_title, company_name, generated_paragraphs
62
  ):
63
  template = f"""
 
 
 
64
 
65
- {generated_paragraphs}
66
 
 
 
 
 
67
  """
68
  return template
69
 
@@ -74,3 +79,5 @@ def convert_md_to_text(md_text: str) -> str:
74
  html = markdown2.markdown(md_text)
75
  plain_text = "".join(html.split("<")[0] if "<" in html else html for html in html.split(">"))
76
  return plain_text
 
 
 
1
  from fpdf import FPDF
2
  import os
 
 
3
  from app.services.hf_storage_service import HuggingFaceStorageService
4
  import unicodedata
5
  import markdown2
 
59
  job_title, company_name, generated_paragraphs
60
  ):
61
  template = f"""
62
+ {your_name}
63
+ {postal_code}, {city}
64
+ {email} | {phone}
65
 
66
+ Dear {company_name},
67
 
68
+ {generated_paragraphs}
69
+
70
+ Warm Regards,
71
+ {your_name}
72
  """
73
  return template
74
 
 
79
  html = markdown2.markdown(md_text)
80
  plain_text = "".join(html.split("<")[0] if "<" in html else html for html in html.split(">"))
81
  return plain_text
82
+
83
+
requirements.txt CHANGED
@@ -9,4 +9,7 @@ safetensors
9
  python-multipart
10
  huggingface_hub
11
  python-dotenv
12
- markdown2
 
 
 
 
9
  python-multipart
10
  huggingface_hub
11
  python-dotenv
12
+ markdown2
13
+ langchain
14
+ openai
15
+ tiktoken