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)