Spaces:
Sleeping
Sleeping
File size: 5,331 Bytes
73ee6c2 586b608 73ee6c2 586b608 73ee6c2 586b608 4ec07e1 73ee6c2 586b608 602f88e 586b608 73ee6c2 586b608 73ee6c2 586b608 73ee6c2 586b608 73ee6c2 586b608 73ee6c2 9002aed 586b608 9002aed 73ee6c2 9002aed 73ee6c2 9002aed 73ee6c2 9002aed 73ee6c2 9002aed 73ee6c2 9002aed 586b608 9002aed 73ee6c2 586b608 9002aed 586b608 9002aed 586b608 73ee6c2 586b608 73ee6c2 586b608 73ee6c2 586b608 73ee6c2 602f88e 73ee6c2 586b608 73ee6c2 602f88e 586b608 602f88e 586b608 73ee6c2 586b608 4ec07e1 586b608 4ec07e1 586b608 | 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 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | from app.state.state import OnboardingState
from langchain_core.messages import SystemMessage, HumanMessage, ToolMessage, AIMessage
from app.prompts.resume_agent_prompt import resume_agent_prompt
from app.prompts.jd_agent_prompt import jd_agent_prompt
from app.prompts.roadmap_planner_agent_prompt import roadmap_planner_agent_prompt
from app.agents.agents import resume_agent, jd_agent, roadmap_planner_agent, gap_analysis_agent, roadmap_planner_agent_tools
from app.prompts.gap_analysis_agent_prompt import gap_analysis_agent_prompt
import json
import logging
from app.tools.tools import *
from langchain_community.document_loaders import PyMuPDFLoader
from langgraph.prebuilt import ToolNode, tools_condition
from app.schemas.jd_extract_schema import JobDescriptionExtract
from app.schemas.resume_extract_schema import ResumeExtract
from app.schemas.skill_gap_analysis_schema import SkillGapAnalysis
logger = logging.getLogger(__name__)
def input_node(state: OnboardingState):
"""Load and extract text from resume PDF."""
file_path = state.get("file_path")
if not file_path:
return {"extraction_error": "Missing file_path in state"}
try:
loader = PyMuPDFLoader(file_path)
docs = loader.load()
resume_text = "\n".join([doc.page_content for doc in docs])
return {
"resume_text": resume_text,
"extraction_error": None
}
except Exception as e:
logger.error(f"Failed to load resume: {str(e)}")
return {
"extraction_error": f"Failed to load resume: {str(e)}"
}
def extractResumeDataNode(state: OnboardingState):
"""Extract structured resume data using resume agent."""
resume_text = state["resume_text"]
messages = [
SystemMessage(content=resume_agent_prompt),
HumanMessage(content=f"<resume_text>{resume_text}</resume_text>")
]
result = resume_agent.invoke(messages)
return {"resume_data": result["parsed"]}
# def extractJDDataNode(state: OnboardingState):
# """Extract structured job description data using JD agent."""
# jd_text = state.get("job_description", "")
# if not jd_text or len(jd_text.strip()) < 5:
# logger.warning("job_description text is missing from state")
# return {"JobDescriptionExtract_data": JobDescriptionExtract()}
# logger.info(f"Extracting JD from {len(jd_text)} characters")
# messages = [
# SystemMessage(content=jd_agent_prompt),
# HumanMessage(content=f"<job_description>{jd_text}</job_description>")
# ]
# try:
# result = jd_agent.invoke(messages)
# parsed_data = result.get("parsed") if isinstance(result, dict) else result
# if parsed_data.job_title is None and parsed_data.tools_technologies is None:
# logger.warning("JD extraction returned empty schema")
# else:
# logger.info(f"Successfully extracted job title: {parsed_data.job_title}")
# return {"JobDescriptionExtract_data": parsed_data}
# except Exception as e:
# logger.error(f"JD extraction failed: {str(e)}")
# return {"JobDescriptionExtract_data": JobDescriptionExtract()}
def skill_gap_node(state: OnboardingState):
"""Analyze skill gaps between resume and job description."""
resume_data = state["resume_data"]
candidate_name = state.get("candidate_name", "Candidate")
# Convert Pydantic models to lean dicts (exclude None values)
lean_resume_dict = resume_data.model_dump(exclude_none=True)
jd_text = state.get("job_description", "")
# Serialize to JSON
lean_resume_json = json.dumps(lean_resume_dict, indent=2)
# Clean prompt with proper formatting
prompt_text = f"""Analyze the skill gaps for the following candidate:
Candidate Name: {candidate_name}
Resume:
{lean_resume_json}
Job Description:
{jd_text}
Please provide a detailed skill gap analysis."""
messages = [
SystemMessage(content=gap_analysis_agent_prompt),
HumanMessage(content=prompt_text)
]
try:
result = gap_analysis_agent.invoke(messages)
return {"skill_gap_analysis_data": result["parsed"]}
except Exception as e:
logger.error(f"Skill gap analysis failed: {str(e)}")
return {"skill_gap_analysis_data": SkillGapAnalysis()}
def roadmap_planning_node(state: OnboardingState):
"""
Plan learning roadmap based on skill gaps.
This node decides which tools to call next based on the analysis.
"""
skill_gap_data = state["skill_gap_analysis_data"]
# Convert Pydantic model to dict, then to JSON
skill_gap_dict = skill_gap_data.model_dump()
skill_gap_json = json.dumps(skill_gap_dict, indent=2)
system_prompt = SystemMessage(content=roadmap_planner_agent_prompt)
input_msg = HumanMessage(content=f"<skill_gap_analysis>\n{skill_gap_json}\n</skill_gap_analysis>")
try:
response = roadmap_planner_agent.invoke([system_prompt, input_msg] + state.get("messages", []))
return {"messages": [response]}
except Exception as e:
logger.error(f"Roadmap planning failed: {str(e)}")
return {"messages": [AIMessage(content=f"Error in roadmap planning: {str(e)}")]}
# Initialize tool node for roadmap planner
tool_node = ToolNode(roadmap_planner_agent_tools) |