Gaykar commited on
Commit
4ec07e1
·
1 Parent(s): baea076
Notebooks/CodeForge.ipynb CHANGED
The diff for this file is too large to render. See raw diff
 
app/agents/agents.py CHANGED
@@ -1,5 +1,11 @@
1
  from langchain_groq import ChatGroq
2
  from app.schemas.pydanticschema import ResumeExtract,JobDescriptionExtract,SkillGapAnalysis
 
 
 
 
 
 
3
 
4
  resume_agent=ChatGroq(
5
  model="moonshotai/kimi-k2-instruct-0905",
@@ -14,6 +20,7 @@ resume_agent=resume_agent.with_structured_output(
14
  include_raw=True,
15
  strict=True
16
  )
 
17
  jd_agent=ChatGroq(
18
  model="meta-llama/llama-4-scout-17b-16e-instruct",
19
  temperature=0.2,
@@ -47,4 +54,6 @@ gap_analysis_agent=gap_analysis_agent.with_structured_output(
47
  roadmap_planner_agent=ChatGroq(
48
  model="moonshotai/kimi-k2-instruct-0905",
49
  temperature=0.2,
50
- )
 
 
 
1
  from langchain_groq import ChatGroq
2
  from app.schemas.pydanticschema import ResumeExtract,JobDescriptionExtract,SkillGapAnalysis
3
+ from app.core.config import settings
4
+ from app.tools.tools import roadmap_planner_agent_tools
5
+ import os
6
+
7
+ if "GROQ_API_KEY" not in os.environ:
8
+ os.environ["GROQ_API_KEY"] = settings.GROQ_API_KEY
9
 
10
  resume_agent=ChatGroq(
11
  model="moonshotai/kimi-k2-instruct-0905",
 
20
  include_raw=True,
21
  strict=True
22
  )
23
+
24
  jd_agent=ChatGroq(
25
  model="meta-llama/llama-4-scout-17b-16e-instruct",
26
  temperature=0.2,
 
54
  roadmap_planner_agent=ChatGroq(
55
  model="moonshotai/kimi-k2-instruct-0905",
56
  temperature=0.2,
57
+ )
58
+
59
+ roadmap_planner_agent=roadmap_planner_agent.bind_tools(roadmap_planner_agent_tools)
app/graph.py CHANGED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ from app.state.state import OnboardingState
3
+ from app.nodes.graphnodes import *
4
+ from langgraph.prebuilt import ToolNode ,tools_condition
5
+ from langgraph.graph import StateGraph,END,START
6
+
7
+ builder = StateGraph(OnboardingState)
8
+
9
+ # Define Nodes
10
+ builder.add_node("input_node", input_node)
11
+ builder.add_node("resume_data_extraction", extractResumeDataNode)
12
+ builder.add_node("jd_data_extraction", extractJDDataNode)
13
+ builder.add_node("skill_gap_analysis", skill_gap_node)
14
+ builder.add_node("roadmap_planning_agent", roadmap_planning_node)
15
+ builder.add_node("tools", tool_node) # Named 'tools' for tools_condition compatibility
16
+ builder.add_node("finalize_state", finalize_state_node)
17
+
18
+ # Define Entry Point and initial Extraction Parallelism
19
+ builder.set_entry_point("input_node")
20
+ builder.add_edge("input_node", "resume_data_extraction")
21
+ builder.add_edge("input_node", "jd_data_extraction")
22
+
23
+ # Join Extractions into Gap Analysis
24
+ builder.add_edge("resume_data_extraction", "skill_gap_analysis")
25
+ builder.add_edge("jd_data_extraction", "skill_gap_analysis")
26
+
27
+ # Transition from Analysis to Planning Agent
28
+ builder.add_edge("skill_gap_analysis", "roadmap_planning_agent")
29
+
30
+ # Agentic ReAct Loop (Planning Agent <-> Tools)
31
+ builder.add_conditional_edges(
32
+ "roadmap_planning_agent",
33
+ tools_condition,
34
+ {
35
+ "tools": "tools",
36
+ "__end__": "finalize_state" # tools_condition returns '__end__' when no tools are called
37
+ }
38
+ )
39
+
40
+ builder.add_edge("tools", "roadmap_planning_agent")
41
+ builder.add_edge("finalize_state", END)
42
+
43
+ graph = builder.compile()
app/nodes/graphnodes.py CHANGED
@@ -1,4 +1,3 @@
1
-
2
  from app.state.state import OnboardingState
3
  from langchain_core.messages import SystemMessage, HumanMessage,ToolMessage,AIMessage
4
  from app.prompts.resume_agent_prompt import resume_agent_prompt
@@ -8,9 +7,9 @@ from app.agents.agents import resume_agent,jd_agent,roadmap_planner_agent,gap_an
8
  from app.prompts.gap_analysis_agent_prompt import gap_analysis_agent_prompt
9
  from app.schemas.pydanticschema import ResumeExtract,JobDescriptionExtract,SkillGapAnalysis
10
  import json
11
-
12
-
13
  from langchain_community.document_loaders import PyMuPDFLoader
 
14
 
15
  def input_node(state: OnboardingState):
16
 
@@ -194,4 +193,8 @@ def finalize_state_node(state: OnboardingState):
194
  return {
195
  "final_roadmap": final_roadmap,
196
  "mermaid_code": mermaid_code
197
- }
 
 
 
 
 
 
1
  from app.state.state import OnboardingState
2
  from langchain_core.messages import SystemMessage, HumanMessage,ToolMessage,AIMessage
3
  from app.prompts.resume_agent_prompt import resume_agent_prompt
 
7
  from app.prompts.gap_analysis_agent_prompt import gap_analysis_agent_prompt
8
  from app.schemas.pydanticschema import ResumeExtract,JobDescriptionExtract,SkillGapAnalysis
9
  import json
10
+ from app.tools.tools import *
 
11
  from langchain_community.document_loaders import PyMuPDFLoader
12
+ from langgraph.prebuilt import ToolNode ,tools_condition
13
 
14
  def input_node(state: OnboardingState):
15
 
 
193
  return {
194
  "final_roadmap": final_roadmap,
195
  "mermaid_code": mermaid_code
196
+ }
197
+
198
+
199
+
200
+ tool_node = ToolNode(roadmap_planner_agent_tools)
app/tools/tools.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_core.tools import tool
2
+ from typing import Optional
3
+ from app.utils.vectordatabase import retriever
4
+ from app.schemas.pydanticschema import LearningRoadmap
5
+ import json
6
+ from typing import Dict, List,Any
7
+
8
+ @tool
9
+ def search_courses(query: str, level: str, category: str):
10
+ """
11
+ Search the course catalog for relevant modules based on a skill query,
12
+ difficulty level, and technical category.
13
+
14
+ Args:
15
+ query: The semantic search term (e.g., 'FastAPI', 'PostgreSQL', 'Docker').
16
+ level: The difficulty level required ('beginner', 'intermediate', or 'strong').
17
+ category: The technical domain ('Backend', 'Frontend', 'DevOps', 'Cybersecurity', 'Database', 'ML').
18
+ """
19
+
20
+ # Using the hybrid search logic you perfected
21
+ # The '$and' ensures the agent gets EXACTLY what fits the candidate's level
22
+ results = retriever.invoke(
23
+ query,
24
+ filter={
25
+ "$and": [
26
+ {"level": level},
27
+ {"category": category}
28
+ ]
29
+ }
30
+ )
31
+
32
+ if not results:
33
+ return f"No {level} level courses found in the {category} category for '{query}'."
34
+
35
+ # Format the output so the Agent can read the metadata easily
36
+ formatted_output = []
37
+ for doc in results:
38
+ course_info = (
39
+ f"ID: {doc.metadata.get('course_id')}\n"
40
+ f"Title: {doc.metadata.get('title')}\n"
41
+ f"Description: {doc.page_content}\n"
42
+ f"Prerequisites: {doc.metadata.get('prerequisites')}\n"
43
+ f"Duration: {doc.metadata.get('duration')} hours\n"
44
+ "---"
45
+ )
46
+ formatted_output.append(course_info)
47
+
48
+ return "\n".join(formatted_output)
49
+
50
+
51
+
52
+ @tool(args_schema=LearningRoadmap)
53
+ def submit_final_roadmap(candidate_name, target_role, roadmap, onboarding_summary):
54
+ """
55
+ STRICTLY call this tool to submit the final structured learning roadmap.
56
+ This saves the data to the global system and the graph state.
57
+ """
58
+
59
+ result = {
60
+ "candidate_name": candidate_name,
61
+ "target_role": target_role,
62
+ "onboarding_summary": onboarding_summary,
63
+ "roadmap": [
64
+ step.model_dump() if hasattr(step, "model_dump") else step
65
+ for step in roadmap
66
+ ]
67
+ }
68
+
69
+
70
+
71
+ # Return to LangGraph (will be stored in state via a post-processing node)
72
+ return result
73
+
74
+
75
+ @tool
76
+ def submit_mermaid_visualization(mermaid_code: str):
77
+ """
78
+ STRICTLY call this tool to save the Mermaid.js visualization of the roadmap.
79
+ """
80
+ # 1. Tell Python to use the variable from the outer scope
81
+
82
+
83
+ # 2. Now this assignment updates the global variable
84
+ mermaid_roadmap_code = mermaid_code
85
+
86
+ return "Mermaid visualization stored successfully."
87
+
88
+
89
+
90
+
91
+
92
+ class CourseLookup:
93
+ def __init__(self, catalog_path: str = "course_catalog.json"):
94
+ self.catalog_path = catalog_path
95
+ self.courses_map = {}
96
+ self._load_catalog()
97
+
98
+ def _load_catalog(self):
99
+ """Loads the catalog into a dictionary for O(1) lookup speed."""
100
+ try:
101
+ with open(self.catalog_path, 'r') as f:
102
+ catalog = json.load(f)
103
+ # Key the dictionary by course_id for instant retrieval
104
+ self.courses_map = {course['course_id']: course for course in catalog}
105
+ except FileNotFoundError:
106
+ print(f"Error: {self.catalog_path} not found.")
107
+ except json.JSONDecodeError:
108
+ print(f"Error: Failed to decode {self.catalog_path}.")
109
+
110
+ def get_course_details(self, course_id: str) -> Optional[Dict[str, Any]]:
111
+ """Retrieves full details of a course by its ID."""
112
+ return self.courses_map.get(course_id)
113
+
114
+
115
+ lookup_service = CourseLookup("Catalog.json")
116
+
117
+ @tool
118
+ def get_course_by_id(course_id: str) -> str:
119
+ """
120
+ Retrieves full details for a specific course using its unique course_id.
121
+ Use this tool when you find a prerequisite ID in another course and
122
+ need to fetch its title, description, and duration to add to the roadmap.
123
+ """
124
+ details = lookup_service.get_course_details(course_id)
125
+ if not details:
126
+ return f"Error: Course with ID {course_id} not found in catalog."
127
+
128
+ # Return a clean string for the agent to process
129
+ return json.dumps(details, indent=2)
130
+
131
+
132
+ roadmap_planner_agent_tools=[search_courses, get_course_by_id,submit_final_roadmap,submit_mermaid_visualization]