CoolFace
Apppublic

shaukat71/Evaluation_api2

sourceHugging Faceupdated 10mo agoView on Hugging Face
0likes
Invoke_Ai.py657 linesDownload Raw Back to root
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    }