shaukat71/Evaluation_api2
0
1import os
2import json
3from langsmith import traceable
4from Prompts import * # cv_extract_prompt, JD_extract_prompt ,matching_prompt,prompt1,cv_skill_extraction_prompt,jd_skill_prompt #prompt1
5import time
6from skima import * #ResumJobAnalysis,JDAnalysis ,MatchingObject ,SkillMatching,SkillMatching_new ,cv_skill_extraction_skima,jd_skill_extraction_skima #ResumJobComparisonAnalysis
7from google import genai
8
9from google.genai import types
10from google.genai.errors import APIError, ServerError, ClientError
11import base64
12from docx import Document
13from dotenv import load_dotenv
14load_dotenv()
15# keywords_maching_generate,maching_generate,CVQuality_generate,matching_Skils_generate1,jd_Skils_generate1,cv_Skils_generate1
16keys = [
17 os.getenv('google_api_key1'),
18 os.getenv('google_api_key2'),
19 os.getenv('google_api_key3'),
20 os.getenv('google_api_key4'),
21 os.getenv('google_api_key5')
22]
23keys = [k for k in keys if k is not None] # Remove None values
24
25if not keys:
26 raise ValueError("❌ No valid API keys found! Check your environment variables.")
27
28print(f"✅ Loaded {len(keys)} valid API keys")
29def pdf_to_base64(pdf_path: str) -> bytes:
30 """Reads a PDF file and returns its base64-encoded bytes."""
31 with open(pdf_path, "rb") as f:
32 return base64.b64encode(f.read())
33
34def txt_to_base64(txt_path: str) -> bytes:
35 """Reads a TXT file and returns its base64-encoded bytes."""
36 with open(txt_path, "r", encoding="utf-8") as f:
37 text_content = f.read()
38 return base64.b64encode(text_content.encode("utf-8"))
39
40def docx_to_base64(docx_path: str) -> bytes:
41 """Reads a DOCX file and returns its base64-encoded text content."""
42 doc = Document(docx_path)
43 full_text = "\n".join([para.text for para in doc.paragraphs])
44 return base64.b64encode(full_text.encode("utf-8"))
45
46def file_to_part(file_path: str) -> types.Part:
47 """Automatically detects file type (PDF, TXT, or DOCX) and converts it to a Part."""
48 ext = os.path.splitext(file_path)[1].lower()
49
50 if ext == ".pdf":
51 data = pdf_to_base64(file_path)
52 return types.Part.from_bytes(mime_type="application/pdf", data=base64.b64decode(data))
53
54 elif ext == ".txt":
55 data = txt_to_base64(file_path)
56 return types.Part.from_bytes(mime_type="text/plain", data=base64.b64decode(data))
57
58 elif ext == ".docx":
59 data = docx_to_base64(file_path)
60 return types.Part.from_bytes(mime_type="text/plain", data=base64.b64decode(data))
61
62 else:
63 raise ValueError(f"Unsupported file type: {ext}. Only PDF, TXT, and DOCX are supported.")
64
65
66
67# ✅ SOLUTION 2: Use environment variable validation (RECOMMENDED)
68def load_api_keys():
69 """Load and validate API keys from environment variables."""
70 key_names = ['google_api_key1', 'google_api_key2', 'google_api_key3', 'google_api_key4', 'google_api_key5']
71 keys = []
72
73 for key_name in key_names:
74 api_key = os.getenv(key_name)
75 if api_key:
76 keys.append(api_key)
77 print(f"✅ Loaded {key_name}")
78 else:
79 print(f"⚠️ Missing {key_name}")
80
81 if not keys:
82 raise ValueError("❌ No valid API keys found! Set at least one google_api_key* environment variable.")
83
84 return keys
85
86keys = load_api_keys()
87
88
89################################################### Step 3 over all mtching #####################################
90@traceable(name="ATS Gemini Comparison")
91def keywords_maching_generate(cv_data,jd_data, prompt_text=keywords_matching_keyword, trace=None):
92 global keys
93 start = time.time()
94 last_error = None
95
96 # Validate keys at start of function
97 valid_keys = [k for k in keys if k]
98 if not valid_keys:
99 return {
100 "error": "No valid API keys",
101 "details": "All API keys are None or empty",
102 "status": "failed"
103 }
104
105 for i in range(len(valid_keys)):
106 api_key = valid_keys[0]
107
108 # Double-check the key exists
109 if not api_key:
110 print(f"⚠️ Skipping None API key at index {i}")
111 continue
112
113 try:
114 client = genai.Client(api_key=api_key)
115 model = "gemini-flash-lite-latest"
116
117 contents = [
118 types.Content(
119 role="user",
120 parts=[
121 types.Part.from_text(text=jd_data),
122 types.Part.from_text(text=cv_data),
123 types.Part.from_text(text=prompt_text),
124 ],
125 ),
126 ]
127
128 response = client.models.generate_content(
129 model=model,
130 contents=contents,
131 config={
132 "response_mime_type": "application/json",
133 "response_schema": OverallKeywordsMatched ,#ResumJobComparisonAnalysis
134 "temperature": 0.1
135 },
136 )
137
138 usage = response.model_dump().get("usage_metadata", {})
139 output_token = usage.get("candidates_token_count", 0)
140 total_token = usage.get("total_token_count", 0)
141 input_token = usage.get("prompt_token_count", 0)
142
143 elapsed = round(time.time() - start, 2)
144
145 if trace is not None:
146 trace.add_metadata({
147 "model": model,
148 "processing_time_sec": elapsed,
149 "input_tokens": input_token,
150 "output_tokens": output_token,
151 "total_tokens": total_token,
152 "response_length_chars": len(response.model_dump()["candidates"][0]["content"]["parts"][0]["text"]),
153 })
154 entire_json = json.loads(response.model_dump()["candidates"][0]["content"]["parts"][0]["text"])
155 return entire_json
156 # score_and_feedback = entire_json["Final_Score"]
157 # return score_and_feedback, entire_json
158
159 except (ServerError, ClientError, APIError) as e:
160 print("⚠️ ServerError: Model overloaded or unavailable. Please retry later.")
161 print(f"Details: {e}")
162 last_error = e
163 valid_keys.append(valid_keys.pop(0))
164 continue
165
166 except Exception as e:
167 print("❗ Unexpected error occurred.")
168 print(f"Details: {e}")
169 last_error = e
170 break
171
172 error_message = str(last_error) if last_error else "Unknown error after all retries"
173 print(f"❌ All retries exhausted. Final error: {error_message}")
174 return {
175 "error": "Failed to process CV",
176 "details": error_message,
177 "status": "failed"
178 }
179
180@traceable(name="ATS Gemini Comparison")
181def CVQuality_generate(cv_data, prompt_text=cv_quality_prompt, trace=None):
182 global keys
183 start = time.time()
184 last_error = None
185
186 # Validate keys at start of function
187 valid_keys = [k for k in keys if k]
188 if not valid_keys:
189 return {
190 "error": "No valid API keys",
191 "details": "All API keys are None or empty",
192 "status": "failed"
193 }
194
195 for i in range(len(valid_keys)):
196 api_key = valid_keys[0]
197
198 # Double-check the key exists
199 if not api_key:
200 print(f"⚠️ Skipping None API key at index {i}")
201 continue
202
203 try:
204 client = genai.Client(api_key=api_key)
205 model = "gemini-flash-lite-latest"
206
207 contents = [
208 types.Content(
209 role="user",
210 parts=[
211 types.Part.from_text(text=cv_data),
212 types.Part.from_text(text=prompt_text),
213 ],
214 ),
215 ]
216
217 response = client.models.generate_content(
218 model=model,
219 contents=contents,
220 config={
221 "response_mime_type": "application/json",
222 "response_schema": CVQuality, #ResumJobComparisonAnalysis
223 "temperature": 0.1
224 },
225 )
226
227 usage = response.model_dump().get("usage_metadata", {})
228 output_token = usage.get("candidates_token_count", 0)
229 total_token = usage.get("total_token_count", 0)
230 input_token = usage.get("prompt_token_count", 0)
231
232 elapsed = round(time.time() - start, 2)
233
234 if trace is not None:
235 trace.add_metadata({
236 "model": model,
237 "processing_time_sec": elapsed,
238 "input_tokens": input_token,
239 "output_tokens": output_token,
240 "total_tokens": total_token,
241 "response_length_chars": len(response.model_dump()["candidates"][0]["content"]["parts"][0]["text"]),
242 })
243 entire_json = json.loads(response.model_dump()["candidates"][0]["content"]["parts"][0]["text"])
244 return entire_json
245 # score_and_feedback = entire_json["Final_Score"]
246 # return score_and_feedback, entire_json
247
248 except (ServerError, ClientError, APIError) as e:
249 print("⚠️ ServerError: Model overloaded or unavailable. Please retry later.")
250 print(f"Details: {e}")
251 last_error = e
252 valid_keys.append(valid_keys.pop(0))
253 continue
254
255 except Exception as e:
256 print("❗ Unexpected error occurred.")
257 print(f"Details: {e}")
258 last_error = e
259 break
260
261 error_message = str(last_error) if last_error else "Unknown error after all retries"
262 print(f"❌ All retries exhausted. Final error: {error_message}")
263 return {
264 "error": "Failed to process CV",
265 "details": error_message,
266 "status": "failed"
267 }
268######################################################### only skill match ##############################################
269@traceable(name="ATS Gemini Comparison")
270def cv_Skils_generate1(file1, prompt_text=cv_skill_extraction_prompt, trace=None):
271 global keys
272 start = time.time()
273 last_error = None
274
275 # Validate keys at start of function
276 valid_keys = [k for k in keys if k]
277 if not valid_keys:
278 return {
279 "error": "No valid API keys",
280 "details": "All API keys are None or empty",
281 "status": "failed"
282 }
283
284 for i in range(len(valid_keys)):
285 api_key = valid_keys[0]
286
287 # Double-check the key exists
288 if not api_key:
289 print(f"⚠️ Skipping None API key at index {i}")
290 continue
291
292 try:
293 client = genai.Client(api_key=api_key)
294 model = "gemini-flash-lite-latest"#"gemini-2.5-flash"#
295
296 contents = [
297 types.Content(
298 role="user",
299 parts=[
300 file_to_part(file1),
301
302 types.Part.from_text(text=prompt_text),
303 ],
304 ),
305 ]
306
307 response = client.models.generate_content(
308 model=model,
309 contents=contents,
310 config={
311 "response_mime_type": "application/json",
312 "response_schema": cv_skill_extraction_skima,#SkillMatching #ResumJobComparisonAnalysis
313 "temperature": 0.1
314 },
315 )
316
317 usage = response.model_dump().get("usage_metadata", {})
318 output_token = usage.get("candidates_token_count", 0)
319 total_token = usage.get("total_token_count", 0)
320 input_token = usage.get("prompt_token_count", 0)
321
322 elapsed = round(time.time() - start, 2)
323
324 if trace is not None:
325 trace.add_metadata({
326 "model": model,
327 "processing_time_sec": elapsed,
328 "file1": os.path.basename(file1),
329 "input_tokens": input_token,
330 "output_tokens": output_token,
331 "total_tokens": total_token,
332 "response_length_chars": len(response.model_dump()["candidates"][0]["content"]["parts"][0]["text"]),
333 })
334 entire_json = json.loads(response.model_dump()["candidates"][0]["content"]["parts"][0]["text"])
335 return entire_json
336 # score_and_feedback = entire_json["Final_Score"]
337 # return score_and_feedback, entire_json
338
339 except (ServerError, ClientError, APIError) as e:
340 print("⚠️ ServerError: Model overloaded or unavailable. Please retry later.")
341 print(f"Details: {e}")
342 last_error = e
343 valid_keys.append(valid_keys.pop(0))
344 continue
345
346 except Exception as e:
347 print("❗ Unexpected error occurred.")
348 print(f"Details: {e}")
349 last_error = e
350 break
351
352 error_message = str(last_error) if last_error else "Unknown error after all retries"
353 print(f"❌ All retries exhausted. Final error: {error_message}")
354 return {
355 "error": "Failed to process CV",
356 "details": error_message,
357 "status": "failed"
358 }
359
360######################################################## extract jd skills ###############################################
361
362@traceable(name="ATS Gemini Comparison")
363def maching_generate(cv_data,jd_data, prompt_text=matching_prompt, trace=None):
364 global keys
365 start = time.time()
366 last_error = None
367
368 # Validate keys at start of function
369 valid_keys = [k for k in keys if k]
370 if not valid_keys:
371 return {
372 "error": "No valid API keys",
373 "details": "All API keys are None or empty",
374 "status": "failed"
375 }
376
377 for i in range(len(valid_keys)):
378 api_key = valid_keys[0]
379
380 # Double-check the key exists
381 if not api_key:
382 print(f"⚠️ Skipping None API key at index {i}")
383 continue
384
385 try:
386 client = genai.Client(api_key=api_key)
387 model = "gemini-flash-lite-latest"
388
389 contents = [
390 types.Content(
391 role="user",
392 parts=[
393 types.Part.from_text(text=jd_data),
394 types.Part.from_text(text=cv_data),
395 types.Part.from_text(text=prompt_text),
396 ],
397 ),
398 ]
399
400 response = client.models.generate_content(
401 model=model,
402 contents=contents,
403 config={
404 "response_mime_type": "application/json",
405 "response_schema": MatchingObject #ResumJobComparisonAnalysis
406 },
407 )
408
409 usage = response.model_dump().get("usage_metadata", {})
410 output_token = usage.get("candidates_token_count", 0)
411 total_token = usage.get("total_token_count", 0)
412 input_token = usage.get("prompt_token_count", 0)
413
414 elapsed = round(time.time() - start, 2)
415
416 if trace is not None:
417 trace.add_metadata({
418 "model": model,
419 "processing_time_sec": elapsed,
420 "input_tokens": input_token,
421 "output_tokens": output_token,
422 "total_tokens": total_token,
423 "response_length_chars": len(response.model_dump()["candidates"][0]["content"]["parts"][0]["text"]),
424 })
425 entire_json = json.loads(response.model_dump()["candidates"][0]["content"]["parts"][0]["text"])
426 return entire_json
427 # score_and_feedback = entire_json["Final_Score"]
428 # return score_and_feedback, entire_json
429
430 except (ServerError, ClientError, APIError) as e:
431 print("⚠️ ServerError: Model overloaded or unavailable. Please retry later.")
432 print(f"Details: {e}")
433 last_error = e
434 valid_keys.append(valid_keys.pop(0))
435 continue
436
437 except Exception as e:
438 print("❗ Unexpected error occurred.")
439 print(f"Details: {e}")
440 last_error = e
441 break
442
443 error_message = str(last_error) if last_error else "Unknown error after all retries"
444 print(f"❌ All retries exhausted. Final error: {error_message}")
445 return {
446 "error": "Failed to process CV",
447 "details": error_message,
448 "status": "failed"
449 }
450######################################################### only skill match ##############################################
451
452@traceable(name="ATS Gemini Comparison")
453def jd_Skils_generate1(file1, prompt_text=jd_skill_prompt, trace=None):
454 global keys
455 start = time.time()
456 last_error = None
457 def get_wighted_skills(entire_json):
458 try:
459 w_per_skill = len(entire_json["softskills"]) + len(entire_json["coreskills"])
460 w_per_skill = round(40/w_per_skill,2)
461 softskills = {i:w_per_skill for i in entire_json["softskills"]}
462 coreskills = {i:w_per_skill for i in entire_json["coreskills"]}
463 response = {"coreskills":coreskills,"softskills":softskills}
464 except Exception as e:
465 print(e)
466 response = False
467 return response
468
469 # Validate keys at start of function
470 valid_keys = [k for k in keys if k]
471 if not valid_keys:
472 return {
473 "error": "No valid API keys",
474 "details": "All API keys are None or empty",
475 "status": "failed"
476 }
477
478 for i in range(len(valid_keys)):
479 api_key = valid_keys[0]
480
481 # Double-check the key exists
482 if not api_key:
483 print(f"⚠️ Skipping None API key at index {i}")
484 continue
485
486 try:
487 client = genai.Client(api_key=api_key)
488 model = "gemini-flash-lite-latest"#"gemini-2.5-flash"#
489
490 contents = [
491 types.Content(
492 role="user",
493 parts=[
494 file_to_part(file1),
495
496 types.Part.from_text(text=prompt_text),
497 ],
498 ),
499 ]
500
501 response = client.models.generate_content(
502 model=model,
503 contents=contents,
504 config={
505 "response_mime_type": "application/json",
506 "response_schema": jd_skill_extraction_skima, #SkillMatching #ResumJobComparisonAnalysis
507 "temperature": 0.1
508 },
509 )
510
511 usage = response.model_dump().get("usage_metadata", {})
512 output_token = usage.get("candidates_token_count", 0)
513 total_token = usage.get("total_token_count", 0)
514 input_token = usage.get("prompt_token_count", 0)
515
516 elapsed = round(time.time() - start, 2)
517
518 if trace is not None:
519 trace.add_metadata({
520 "model": model,
521 "processing_time_sec": elapsed,
522 "file1": os.path.basename(file1),
523 "input_tokens": input_token,
524 "output_tokens": output_token,
525 "total_tokens": total_token,
526 "response_length_chars": len(response.model_dump()["candidates"][0]["content"]["parts"][0]["text"]),
527 })
528 entire_json = json.loads(response.model_dump()["candidates"][0]["content"]["parts"][0]["text"])
529 # entire_json = get_wighted_skills(entire_json)
530 return entire_json
531 # score_and_feedback = entire_json["Final_Score"]
532 # return score_and_feedback, entire_json
533
534 except (ServerError, ClientError, APIError) as e:
535 print("⚠️ ServerError: Model overloaded or unavailable. Please retry later.")
536 print(f"Details: {e}")
537 last_error = e
538 valid_keys.append(valid_keys.pop(0))
539 continue
540
541 except Exception as e:
542 print("❗ Unexpected error occurred.")
543 print(f"Details: {e}")
544 last_error = e
545 break
546
547 error_message = str(last_error) if last_error else "Unknown error after all retries"
548 print(f"❌ All retries exhausted. Final error: {error_message}")
549 return {
550 "error": "Failed to process CV",
551 "details": error_message,
552 "status": "failed"
553 }
554######################################################### matching skils ##############################################
555
556@traceable(name="ATS Gemini Comparison")
557def matching_Skils_generate1(cv_skills,jdskills, prompt_text=matching_prompt, trace=None):
558 global keys
559 start = time.time()
560 last_error = None
561 def get_wighted_skills(entire_json):
562 try:
563 w_per_skill = len(entire_json["softskills"]) + len(entire_json["coreskills"])
564 w_per_skill = round(40/w_per_skill,2)
565 softskills = {i:w_per_skill for i in entire_json["softskills"]}
566 coreskills = {i:w_per_skill for i in entire_json["coreskills"]}
567 response = {"coreskills":coreskills,"softskills":softskills}
568 except Exception as e:
569 print(e)
570 response = False
571 return response
572
573 # Validate keys at start of function
574 valid_keys = [k for k in keys if k]
575 if not valid_keys:
576 return {
577 "error": "No valid API keys",
578 "details": "All API keys are None or empty",
579 "status": "failed"
580 }
581
582 for i in range(len(valid_keys)):
583 api_key = valid_keys[0]
584
585 # Double-check the key exists
586 if not api_key:
587 print(f"⚠️ Skipping None API key at index {i}")
588 continue
589
590 try:
591 client = genai.Client(api_key=api_key)
592 model = "gemini-flash-lite-latest" #"gemini-2.5-flash"#
593
594 contents = [
595 types.Content(
596 role="user",
597 parts=[
598
599 types.Part.from_text(text=cv_skills),
600 types.Part.from_text(text=jdskills),
601 types.Part.from_text(text=prompt_text),
602 ],
603 ),
604 ]
605
606 response = client.models.generate_content(
607 model=model,
608 contents=contents,
609 config={
610 "response_mime_type": "application/json",
611 "response_schema": extracted_skill_matching_skima, #SkillMatching #ResumJobComparisonAnalysis
612 "temperature": 0.1
613 },
614 )
615
616 usage = response.model_dump().get("usage_metadata", {})
617 output_token = usage.get("candidates_token_count", 0)
618 total_token = usage.get("total_token_count", 0)
619 input_token = usage.get("prompt_token_count", 0)
620
621 elapsed = round(time.time() - start, 2)
622
623 if trace is not None:
624 trace.add_metadata({
625 "model": model,
626 "processing_time_sec": elapsed,
627 "file1": os.path.basename(file1),
628 "input_tokens": input_token,
629 "output_tokens": output_token,
630 "total_tokens": total_token,
631 "response_length_chars": len(response.model_dump()["candidates"][0]["content"]["parts"][0]["text"]),
632 })
633 entire_json = json.loads(response.model_dump()["candidates"][0]["content"]["parts"][0]["text"])
634
635 return entire_json
636
637
638 except (ServerError, ClientError, APIError) as e:
639 print("⚠️ ServerError: Model overloaded or unavailable. Please retry later.")
640 print(f"Details: {e}")
641 last_error = e
642 valid_keys.append(valid_keys.pop(0))
643 continue
644
645 except Exception as e:
646 print("❗ Unexpected error occurred.")
647 print(f"Details: {e}")
648 last_error = e
649 break
650
651 error_message = str(last_error) if last_error else "Unknown error after all retries"
652 print(f"❌ All retries exhausted. Final error: {error_message}")
653 return {
654 "error": "Failed to process CV",
655 "details": error_message,
656 "status": "failed"
657 }