Spaces:
Sleeping
Sleeping
Download main.py from deepsu/HR_TOOL_V2: direct link, hf CLI and curl.
- Browser
- Download file 2.78 kB
-
https://huggingface.co/spaces/deepsu/HR_TOOL_V2/resolve/main/main.py
- Command line
-
hf download hf://spaces/deepsu/HR_TOOL_V2/main.py
-
curl -L -o main.py https://huggingface.co/spaces/deepsu/HR_TOOL_V2/resolve/main/main.py
2.78 kB
| #import torch and fast api | |
| import torch | |
| from fastapi import FastAPI | |
| from pathlib import Path | |
| from pydantic import BaseModel | |
| from typing import List | |
| import time | |
| #import transformer modules | |
| from transformers import AutoTokenizer, AutoModelForCausalLM | |
| from peft import PeftModel | |
| #import sentence_transformers | |
| from sentence_transformers import SentenceTransformer | |
| # import modules | |
| from modules.resume_parser import ResumeParser | |
| from modules.comparison import Comparison | |
| app = FastAPI() | |
| def load_models(): | |
| BASE_DIR = Path(__file__).resolve().parent | |
| # ===== QWEN ===== | |
| base_model_name = "Qwen/Qwen2.5-0.5B-Instruct" | |
| adapter_path = BASE_DIR / "models/qwen_final" | |
| tokenizer = AutoTokenizer.from_pretrained(base_model_name) | |
| tokenizer.pad_token = tokenizer.eos_token | |
| base_model = AutoModelForCausalLM.from_pretrained( | |
| base_model_name, | |
| dtype=torch.float32, | |
| device_map="cpu" | |
| ) | |
| qwen_model = PeftModel.from_pretrained(base_model, str(adapter_path)) | |
| qwen_model.eval() | |
| # ===== MiniLM ===== | |
| minilm_model = SentenceTransformer(str(BASE_DIR / "models/mnr_1")) | |
| # ===== Inject ===== | |
| app.state.resume_parser = ResumeParser(qwen_model, tokenizer) | |
| app.state.comparison = Comparison(minilm_model) | |
| print("✅ All models loaded once") | |
| # --------------------------------------------- | |
| # HEALTH CHECK | |
| # --------------------------------------------- | |
| def home(): | |
| return {"status": "ATS API Running 🚀"} | |
| # --------------------------------------------- | |
| # MAIN ENDPOINT | |
| # --------------------------------------------- | |
| class ResumeMatchRequest(BaseModel): | |
| job_description:str | |
| resume: str | |
| async def match_resumes(request:ResumeMatchRequest): | |
| start = time.time() | |
| resume = request.resume | |
| job_description = request.job_description | |
| #load parser | |
| parser = app.state.resume_parser | |
| #resumes in parsed format | |
| resume = parser.format_data(resume) | |
| #resumes after generated | |
| resume = parser.parse(resume) | |
| splitted = resume.split("\n") | |
| name = splitted[0].split("name: ").pop() | |
| parsed_resume = "\n".join(splitted[1:]) | |
| #load comparison | |
| comparison = app.state.comparison | |
| # use all mini lm to encode | |
| with torch.no_grad(): | |
| jd = comparison.encode(job_description) | |
| res = comparison.encode(parsed_resume) | |
| score = comparison.compare(res, jd) | |
| end = time.time() | |
| return { | |
| "scores":score, | |
| "resume":{"name": name, "resume":parsed_resume}, | |
| "threshold":0.670, | |
| "process_time": f"{end-start} seconds", | |
| "message": "Candidate Accepted" if score>=0.670 else "Candidate Rejected" | |
| } |