File size: 5,646 Bytes
478dec6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import asyncio
import os, sys
import time
from dotenv import load_dotenv
load_dotenv()
from typing import AsyncIterable
from typing import List, Optional, Dict
from langchain.schema import HumanMessage, AIMessage
from langchain.callbacks import AsyncIteratorCallbackHandler

# from fastapi.middleware.cors import CORSMiddleware
# from fastapi.responses import StreamingResponse
# from langchain.chat_models import ChatOpenAI
# from pydantic import BaseModel
# from langchain_google_genai import ChatGoogleGenerativeAI
from models.data_model import OutProfile, OutMatching
from services.llms.LLM import model_gemini, model_4o, model_4omini, model_5mini
from services.agents.LLMAgent import LLMAgent
from services.prompts.profile_matching import match_one_profile
from utils.utils import pdf_reader, ingest_one_profile, ingest_bulk_profile



async def helper_profile_match_prompt(profile: OutProfile) -> str:
    profile.high_edu_major_1 = profile.high_edu_major_1 if profile.high_edu_major_1 else "-"
    profile.high_edu_univ_1 = profile.high_edu_univ_1 if profile.high_edu_univ_1 else "-"
    profile.high_edu_gpa_1 = profile.high_edu_gpa_1 if profile.high_edu_gpa_1 else "-"
    profile.high_edu_major_2 = profile.high_edu_major_2 if profile.high_edu_major_2 else "-"
    profile.high_edu_univ_2 = profile.high_edu_univ_2 if profile.high_edu_univ_2 else "-"
    profile.high_edu_gpa_2 = profile.high_edu_gpa_2 if profile.high_edu_gpa_2 else "-"
    profile.high_edu_major_3 = profile.high_edu_major_3 if profile.high_edu_major_3 else "-"
    profile.high_edu_univ_3 = profile.high_edu_univ_3 if profile.high_edu_univ_3 else "-"
    profile.high_edu_gpa_3 = profile.high_edu_gpa_3 if profile.high_edu_gpa_3 else "-"
    profile.hardskills = profile.hardskills if profile.hardskills else []
    profile.softskills = profile.softskills if profile.softskills else []
    profile.certifications = profile.certifications if profile.certifications else []
    profile.business_domain_experiences = profile.business_domain_experiences if profile.business_domain_experiences else []
    
    profile_text = f"""
# Candidate Profile

**Education**
- Bachelor degree major: {profile.high_edu_major_1 or "-"}
- Bachelor university: {profile.high_edu_univ_1 or "-"}
- Bachelor GPA: {profile.high_edu_gpa_1 or "-"}

- Master degree major: {profile.high_edu_major_2 or "-"}
- Master university: {profile.high_edu_univ_2 or "-"}
- Master GPA: {profile.high_edu_gpa_2 or "-"}

- Doctor/Phd degree major: {profile.high_edu_major_3 or "-"}
- Doctor/Phd university: {profile.high_edu_univ_3 or "-"}
- Doctor/Phd GPA: {profile.high_edu_gpa_3 or "-"}

**Year of Working Experience**
{profile.yoe} year

**Hardskills**
{", ".join([p for p in profile.hardskills if p not in ['null', '']])}

**Softskills**
{", ".join([p for p in profile.softskills if p not in ['null', '']])}

**Certifications**
{", ".join([p for p in profile.certifications if p not in ['null', '']])}

**Business domain experiences**
{", ".join([p for p in profile.business_domain_experiences if p not in ['null', '']])}
""".strip()
    return profile_text
    

class ProfileMatchingAgent(LLMAgent):
    def __init__(self, model=model_5mini):
        super().__init__(model)
        self.agent_name = "ProfileMatchingAgent"
        self.prompt_template = match_one_profile

    async def _helper_matching_one(self, profile_id:int, profile_dict:Dict, profile_text:str, criteria:str) -> Dict:
        llm = self.model.with_structured_output(OutMatching)
        chain = self.prompt_template | llm
        input_chain = {
            "profile_text":profile_text,
            "criteria":criteria,
        }
        matched_profile = await chain.ainvoke(input_chain, config=None)
        print(f"_helper_matching_one/matched_profile:\n{matched_profile}")
        
        # matched_profile = matched_profile.model_dump()
        # matched_profile["profile_id"] = profile_id
        
        profile_dict["score"] = matched_profile.score
        profile_dict["reason"] = matched_profile.reason
        profile_dict["profile_id"] = profile_id

        return profile_dict
    
    async def matching_profile_bulk(self, profiles:List[OutProfile], criteria:str) -> Optional[List]:
        try:
            st = time.time()
            tasks = []
            profiles_text = []
            profiles_dict = []
            n_profiles = len(profiles)

            for profile_id, p in enumerate(profiles):
                print(f"Processing [{profile_id+1}/{n_profiles}]")
                profile_text = await helper_profile_match_prompt(p)
                profile_dict = p.model_dump()
                profiles_dict.append(profile_dict)
                profiles_text.append(profile_text)

                task = asyncio.create_task(self._helper_matching_one(profile_id=profile_id, 
                                                    profile_dict=profile_dict, 
                                                    profile_text=profile_text, 
                                                    criteria=criteria))
                tasks.append(task)
            
            profiles_with_scores = await asyncio.gather(*tasks)

            print(f"✅ Profile matching finished in {(time.time() - st)//60} min, {(time.time() - st)%60} sec")
            return profiles_with_scores
        except Exception as E:
            error_message = f"Processing profile matching error: {E}"
            print(error_message)
            exc_type, exc_obj, exc_tb = sys.exc_info()
            fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
            print(exc_type, fname, exc_tb.tb_lineno)