CandidateExplorer / services /agents /ProfileMatchingAgent.py
ishaq101's picture
clean init
478dec6
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)