AdaptiveEngineService / app /nodes /graphnodes.py
Gaykar's picture
made chnages in graph and tools
fd01f65
raw
history blame
5.46 kB
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
from app.prompts.gap_analysis_agent_prompt import gap_analysis_agent_prompt
from app.schemas.pydanticschema import ResumeExtract,JobDescriptionExtract,SkillGapAnalysis
import json
from app.tools.tools import *
from langchain_community.document_loaders import PyMuPDFLoader
from langgraph.prebuilt import ToolNode ,tools_condition
def input_node(state: OnboardingState):
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:
return {
"extraction_error": f"Failed to load resume: {str(e)}"
}
def extractResumeDataNode(state: OnboardingState):
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):
# 1. Safety Check: Is the text even in the state?
jd_text = state.get("job_description", "")
if not jd_text or len(jd_text.strip()) < 5:
print("DEBUGGER ERROR: job_description text is MISSING from state!")
return {"JobDescriptionExtract_data": JobDescriptionExtract()}
print(f"DEBUGGER: Sending {len(jd_text)} characters to JD Agent...")
messages = [
SystemMessage(content=jd_agent_prompt),
HumanMessage(content=f"EXTRACT FROM THIS TEXT:\n\n{jd_text}")
]
try:
# 2. Invoke the agent
result = jd_agent.invoke(messages)
# 3. Handle the 'parsed' key (ensure your chain is configured correctly)
# If result is already the Pydantic object, use it directly.
# If result is a dict with 'parsed', use result['parsed'].
parsed_data = result.get("parsed") if isinstance(result, dict) else result
# 4. Critical Check: Did it actually find anything?
if parsed_data.job_title is None and parsed_data.tools_technologies is None:
print("DEBUGGER WARNING: LLM returned empty schema! Checking prompt...")
else:
print(f"DEBUGGER SUCCESS: Extracted {parsed_data.job_title}")
return {"JobDescriptionExtract_data": parsed_data}
except Exception as e:
print(f"DEBUGGER CRITICAL: Invoke failed: {str(e)}")
return {"JobDescriptionExtract_data": JobDescriptionExtract()}
def skill_gap_node(state: OnboardingState):
resume_data = state["resume_data"]
candidate_name = state["candidate_name"]
# To remove noise and reduce size of the prompt.
lean_resume_dict = resume_data.model_dump(
exclude_none=True # Bonus: Automatically drops any fields that are None/null!
)
raw_jd = state["JobDescriptionExtract_data"]
# Strip the HR noise and text bloat
lean_jd_dict = raw_jd.model_dump(
exclude_none=True # Drops any null fields
)
lean_resume_json = json.dumps(lean_resume_dict, indent=2)
lean_jd_json = json.dumps(lean_jd_dict, indent=2)
messages = [
SystemMessage(content=gap_analysis_agent_prompt),
HumanMessage(content=f"Users Resume:UserName:<candidate_name>{candidate_name}</candidate_name> Resume:<lean_resume_json>{lean_resume_json}</lean_resume_json> Job Description:<lean_jd_json>{lean_jd_json}</lean_jd_json>"),
]
result = gap_analysis_agent.invoke(messages)
return {"skill_gap_analysis_data": result["parsed"]}
def finalize_state_node(state: OnboardingState):
"""
Final node that extracts structured data from the message scratchpad
and populates the main state keys. No global variables needed!
"""
final_roadmap = None
mermaid_code = None
# We search the messages in reverse to find the LATEST tool calls
for msg in reversed(state["messages"]):
# Check if the message has tool calls (this will be an AIMessage)
if hasattr(msg, "tool_calls") and msg.tool_calls:
for tool_call in msg.tool_calls:
# 1. Extract the Roadmap JSON
if tool_call["name"] == "submit_final_roadmap":
final_roadmap = tool_call["args"]
# 2. Extract the Mermaid String
elif tool_call["name"] == "submit_mermaid_visualization":
mermaid_code = tool_call["args"].get("mermaid_code")
# Once we have both, we can stop searching
if final_roadmap and mermaid_code:
break
return {
"final_roadmap": final_roadmap,
"mermaid_code": mermaid_code
}
tool_node = ToolNode(roadmap_planner_agent_tools)