Spaces:
Sleeping
Sleeping
chnage
Browse files- Notebooks/CodeForge.ipynb +0 -0
- app/agents/agents.py +10 -1
- app/graph.py +43 -0
- app/nodes/graphnodes.py +7 -4
- app/tools/tools.py +132 -0
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]
|