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)