diff --git "a/Notebooks/CodeForge.ipynb" "b/Notebooks/CodeForge.ipynb"
new file mode 100644--- /dev/null
+++ "b/Notebooks/CodeForge.ipynb"
@@ -0,0 +1,1848 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 38,
+ "id": "b75a238a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from typing import Any, Dict, List, Optional, Tuple,TypedDict,Literal\n",
+ "from typing import Annotated, Sequence\n",
+ "import os\n",
+ "from pydantic import BaseModel, Field\n",
+ "from langchain_groq import ChatGroq\n",
+ "from langchain_core.messages import SystemMessage, HumanMessage,ToolMessage,AIMessage\n",
+ "from langchain_core.tools import Tool\n",
+ "from langgraph.graph import StateGraph,END,START\n",
+ "from langgraph.types import interrupt \n",
+ "from langchain_core.prompts import ChatPromptTemplate,MessagesPlaceholder\n",
+ "from langchain_community.document_loaders import PyMuPDFLoader\n",
+ "import json\n",
+ "from pydantic import BaseModel, Field\n",
+ "from typing import List, Optional\n",
+ "from pprint import pprint\n",
+ "import os\n",
+ "from dotenv import load_dotenv\n",
+ "import json\n",
+ "from langchain_core.documents import Document\n",
+ "from langchain_huggingface import HuggingFaceEmbeddings\n",
+ "import os\n",
+ "from pinecone import Pinecone, ServerlessSpec\n",
+ "from pinecone_text.sparse import BM25Encoder\n",
+ "from langchain_community.embeddings import HuggingFaceEmbeddings\n",
+ "from langchain_community.retrievers import PineconeHybridSearchRetriever\n",
+ "import json\n",
+ "from langchain_core.documents import Document\n",
+ "from langchain_core.messages import BaseMessage\n",
+ "from langgraph.graph import add_messages\n",
+ "from langgraph.prebuilt import ToolNode ,tools_condition\n",
+ "import torch\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c7058b37",
+ "metadata": {},
+ "source": [
+ "Pydantic model of resume data extraction"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "69094b87",
+ "metadata": {},
+ "source": [
+ "**Defining the pydantic models to be used**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "7da5b1c6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "\n",
+ "\n",
+ "\n",
+ "class Skill(BaseModel):\n",
+ " name: str = Field(..., description=\"Skill name e.g. Python, Docker\")\n",
+ " category: Optional[str] = Field(\n",
+ " None, description=\"Category: Backend | ML | DevOps | Frontend | Other\"\n",
+ " )\n",
+ "\n",
+ "\n",
+ "class ExperienceItem(BaseModel):\n",
+ " job_title: str = Field(\n",
+ " ...,\n",
+ " description=\"Role title of the candidate. Example: 'Backend Intern', 'Software Engineer'\"\n",
+ " )\n",
+ "\n",
+ " experience_type: Optional[Literal['internship', 'full_time', 'contract', 'freelance']] = Field(\n",
+ " None,\n",
+ " description=\"Type of experience: internship, full_time, contract, or freelance\"\n",
+ " )\n",
+ "\n",
+ " duration_months: Optional[int] = Field(\n",
+ " None,\n",
+ " description=\"Duration of this role in months. Null if not explicitly mentioned\"\n",
+ " )\n",
+ "\n",
+ " technologies: Optional[List[str]] = Field(\n",
+ " default_factory=list,\n",
+ " description=\"Technologies, tools, or frameworks used in this role\"\n",
+ " )\n",
+ "\n",
+ " responsibilities: Optional[List[str]] = Field(\n",
+ " default_factory=list,\n",
+ " description=\"Key responsibilities, tasks, or learnings in concise bullet points\"\n",
+ " )\n",
+ "\n",
+ "class ProjectItem(BaseModel):\n",
+ " name: str = Field(..., description=\"Project name\")\n",
+ " technologies: List[str] = Field(\n",
+ " default_factory=list,\n",
+ " description=\"Technologies used in this project\"\n",
+ " )\n",
+ " what_was_built: Optional[str] = Field(\n",
+ " None,\n",
+ " description=\"One line — what problem it solved or what was built\"\n",
+ " )\n",
+ "\n",
+ "\n",
+ "class CertificationItem(BaseModel):\n",
+ " name: str = Field(..., description=\"Certification name\")\n",
+ " issuer: Optional[str] = Field(None, description=\"Issuing organization\")\n",
+ " topics_covered: List[str] = Field(\n",
+ " default_factory=list,\n",
+ " description=\"Key topics or skills the certification covers\"\n",
+ " )\n",
+ "\n",
+ "\n",
+ "class AchievementItem(BaseModel):\n",
+ " title: str = Field(..., description=\"Achievement title\")\n",
+ " domain: Optional[str] = Field(\n",
+ " None,\n",
+ " description=\"Domain of achievement e.g. Competitive Programming, Hackathon, Quiz\"\n",
+ " )\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "class ResumeExtract(BaseModel):\n",
+ "\n",
+ " \n",
+ " job_title: Optional[str] = Field(\n",
+ " None,\n",
+ " description=(\n",
+ " \"Primary job title or role of the candidate. \"\n",
+ " \"Examples: 'AI Engineer', 'Data Scientist', \"\n",
+ " \"'Construction Project Manager', 'Healthcare Representative'. \"\n",
+ " \"Should reflect the most recent or current role.\"\n",
+ " )\n",
+ " )\n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " total_experience_months: Optional[int] = Field(\n",
+ " 0,\n",
+ " description=(\n",
+ " \"Total professional work experience in months. \"\n",
+ " \"Includes internships and full-time roles. \"\n",
+ " \"0 if fresher or no experience found.\"\n",
+ " )\n",
+ " )\n",
+ " \n",
+ "\n",
+ " \n",
+ " skills: List[Skill] = Field(\n",
+ " default_factory=list,\n",
+ " description=\"Skills explicitly listed by the candidate\"\n",
+ " )\n",
+ " experience: List[ExperienceItem] = Field(\n",
+ " default_factory=list,\n",
+ " description=(\n",
+ " \"Each role as a separate entry. \"\n",
+ " \"No company name needed — focus on what was done and learned.\"\n",
+ " )\n",
+ " )\n",
+ " projects: List[ProjectItem] = Field(\n",
+ " default_factory=list,\n",
+ " description=\"Projects with technologies used and what was built\"\n",
+ " )\n",
+ " certifications: Optional[List[CertificationItem]] = Field(\n",
+ " None,\n",
+ " description=\"Certifications with topics they cover. None if not present.\"\n",
+ " )\n",
+ " achievements: Optional[List[AchievementItem]] = Field(\n",
+ " None,\n",
+ " description=\"Accomplishments that signal domain strength or soft skills. None if not present.\"\n",
+ " )\n",
+ "\n",
+ "\n",
+ " is_fresher: bool = Field(\n",
+ " ...,\n",
+ " description=(\n",
+ " \"Set to True if the candidate lacks full-time professional employment. \"\n",
+ " \"Academic projects, certifications, and internships are considered \"\n",
+ " \"part of the learning phase and do not qualify a candidate as 'non-fresher' hence is_.\"\n",
+ " )\n",
+ ")\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5b0756e0",
+ "metadata": {},
+ "source": [
+ "Pydantic model for job description"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "4b2441cd",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from pydantic import BaseModel, Field\n",
+ "from typing import List, Optional\n",
+ "\n",
+ "\n",
+ "class SkillRequirement(BaseModel):\n",
+ " name: str = Field(\n",
+ " ...,\n",
+ " description=\"Skill or technology required for the job (e.g., Python, SQL, React)\"\n",
+ " )\n",
+ " level: Optional[str] = Field(\n",
+ " None,\n",
+ " description=\"Expected proficiency level: beginner | intermediate | strong\"\n",
+ " )\n",
+ "\n",
+ "\n",
+ "class ResponsibilityItem(BaseModel):\n",
+ " description: str = Field(\n",
+ " ...,\n",
+ " description=\"Key responsibility or task expected from the candidate\"\n",
+ " )\n",
+ "\n",
+ "\n",
+ "class RequirementItem(BaseModel):\n",
+ " description: str = Field(\n",
+ " ...,\n",
+ " description=\"Qualification or requirement such as education, availability, etc.\"\n",
+ " )\n",
+ "\n",
+ "\n",
+ "class ConstraintItem(BaseModel):\n",
+ " type: str = Field(\n",
+ " ...,\n",
+ " description=\"Constraint type such as location, duration, eligibility\"\n",
+ " )\n",
+ " value: str = Field(\n",
+ " ...,\n",
+ " description=\"Constraint value (e.g., 'Pune only', '6 months', 'Fresher')\"\n",
+ " )\n",
+ "\n",
+ "\n",
+ "\n",
+ "class JobDescriptionExtract(BaseModel):\n",
+ " job_title: Optional[str] = Field(\n",
+ " None,\n",
+ " description=\"Job role/title (e.g., AI/ML Intern, Web Developer)\"\n",
+ " )\n",
+ "\n",
+ " company_name: Optional[str] = Field(\n",
+ " None,\n",
+ " description=\"Company offering the job\"\n",
+ " )\n",
+ "\n",
+ " location: Optional[str] = Field(\n",
+ " None,\n",
+ " description=\"Job location if specified\"\n",
+ " )\n",
+ "\n",
+ " employment_type: Optional[str] = Field(\n",
+ " None,\n",
+ " description=\"Type of job: internship, full-time, contract\"\n",
+ " )\n",
+ "\n",
+ " duration_months: Optional[int] = Field(\n",
+ " None,\n",
+ " description=\"Duration of role in months (for internships/contracts)\"\n",
+ " )\n",
+ "\n",
+ " is_fresher_allowed: Optional[bool] = Field(\n",
+ " None,\n",
+ " description=\"Whether freshers are eligible for this role\"\n",
+ " )\n",
+ "\n",
+ " skills_required: Optional[List[SkillRequirement]] = Field(\n",
+ " None,\n",
+ " description=\"List of required skills and expected levels\"\n",
+ " )\n",
+ "\n",
+ " tools_technologies: Optional[List[str]] = Field(\n",
+ " None,\n",
+ " description=\"Specific tools/frameworks mentioned (e.g., Pandas, WordPress)\"\n",
+ " )\n",
+ "\n",
+ " responsibilities: Optional[List[ResponsibilityItem]] = Field(\n",
+ " None,\n",
+ " description=\"Key job responsibilities\"\n",
+ " )\n",
+ "\n",
+ " requirements: Optional[List[RequirementItem]] = Field(\n",
+ " None,\n",
+ " description=\"General requirements like availability, qualifications\"\n",
+ " )\n",
+ "\n",
+ " constraints: Optional[List[ConstraintItem]] = Field(\n",
+ " None,\n",
+ " description=\"Special constraints like location restriction, duration, etc.\"\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4b12a3bc",
+ "metadata": {},
+ "source": [
+ "**Pydantic model for skill gap analysis**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "4f1341e0",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class SkillGap(BaseModel):\n",
+ " skill_name: str = Field(\n",
+ " ..., \n",
+ " description=\"The specific technology or tool missing or requiring an upgrade (e.g., 'PostgreSQL')\"\n",
+ " )\n",
+ " \n",
+ " gap_type: Literal[\"missing_foundation\", \"needs_advanced_upgrade\"] = Field(\n",
+ " ...,\n",
+ " description=(\n",
+ " \"missing_foundation: Candidate has no recorded experience in this core requirement. \"\n",
+ " \"needs_advanced_upgrade: Candidate knows the basics but needs role-specific advanced training.\"\n",
+ " )\n",
+ " )\n",
+ " \n",
+ " priority: Literal[\"high\", \"medium\", \"low\"] = Field(\n",
+ " ...,\n",
+ " description=\"How critical this skill is for the target job role.\"\n",
+ " )\n",
+ " \n",
+ " reasoning: str = Field(\n",
+ " ...,\n",
+ " description=(\n",
+ " \"The 'Reasoning Trace'. This MUST be provided for every skill gap identified. \"\n",
+ " \"Explain exactly WHY this gap was flagged based on the resume vs JD comparison. \"\n",
+ " \"Example: 'JD requires FastAPI; candidate has Python experience but no record of using FastAPI framework.'\"\n",
+ " )\n",
+ " )\n",
+ " \n",
+ " target_competency: str = Field(\n",
+ " ...,\n",
+ " description=\"The specific outcome the candidate needs to reach (e.g., 'Build asynchronous database endpoints')\"\n",
+ " )\n",
+ "\n",
+ "class SkillGapAnalysis(BaseModel):\n",
+ " job_title: str = Field(..., description=\"The target role from the JD\")\n",
+ " candidate_name: Optional[str] = Field(None, description=\"Extracted name from resume\")\n",
+ " \n",
+ " analyzed_gaps: List[SkillGap] = Field(\n",
+ " default_factory=list,\n",
+ " description=\"List of specific technical gaps found between Resume and JD\"\n",
+ " )\n",
+ " \n",
+ " is_fresher_adaptation_needed: bool = Field(\n",
+ " default=False,\n",
+ " description=\"True if foundational corporate/soft-skill modules should be added to the path.\"\n",
+ " )\n",
+ " \n",
+ " executive_summary: str = Field(\n",
+ " ...,\n",
+ " description=\"A 2-3 sentence overview of the candidate's readiness and the primary focus of the onboarding.\"\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "18663bb3",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class RoadmapStep(BaseModel):\n",
+ " course_id: str\n",
+ " title: str\n",
+ " reasoning: str = Field(..., description=\"Why this specific course was chosen for this user\")\n",
+ " is_foundation: bool\n",
+ " sequence_order: int = Field(..., description=\"The order in which the course should be taken\")\n",
+ "\n",
+ "class LearningRoadmap(BaseModel):\n",
+ " candidate_name: str\n",
+ " target_role: str\n",
+ " roadmap: List[RoadmapStep]\n",
+ " onboarding_summary: str"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "604e9728",
+ "metadata": {},
+ "source": [
+ "**Defining the agents to be used**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9036d57e",
+ "metadata": {},
+ "source": [
+ "Resume data extraction agent"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "14dab004",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "resume_agent=ChatGroq(\n",
+ " model=\"moonshotai/kimi-k2-instruct-0905\",\n",
+ " temperature=0.2,\n",
+ ")\n",
+ "\n",
+ "\n",
+ "resume_agent=resume_agent.with_structured_output(\n",
+ "\n",
+ " schema=ResumeExtract,\n",
+ " method=\"json_schema\",\n",
+ " include_raw=True,\n",
+ " strict=True\n",
+ ")\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7683eb69",
+ "metadata": {},
+ "source": [
+ "Job description data extraction agent"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "472dae2f",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "jd_agent=ChatGroq(\n",
+ " model=\"meta-llama/llama-4-scout-17b-16e-instruct\",\n",
+ " temperature=0.2,\n",
+ ")\n",
+ "\n",
+ "\n",
+ "jd_agent=jd_agent.with_structured_output(\n",
+ "\n",
+ " schema=JobDescriptionExtract,\n",
+ " method=\"json_schema\",\n",
+ " include_raw=True,\n",
+ " strict=True\n",
+ ")\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d14736d5",
+ "metadata": {},
+ "source": [
+ "defining the gap analysis agent"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "0d5e3b17",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "gap_analysis_agent=ChatGroq(\n",
+ " model=\"moonshotai/kimi-k2-instruct-0905\",\n",
+ " temperature=0.2,\n",
+ ")\n",
+ "\n",
+ "\n",
+ "gap_analysis_agent=gap_analysis_agent.with_structured_output(\n",
+ " schema=SkillGapAnalysis,\n",
+ " method=\"json_schema\",\n",
+ " include_raw=True,\n",
+ " strict=True\n",
+ ")\n",
+ "\n",
+ "\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "28bc58ad",
+ "metadata": {},
+ "source": [
+ "defining the roadmap planner agent"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 148,
+ "id": "0ccc026b",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "roadmap_planner_agent=ChatGroq(\n",
+ " model=\"moonshotai/kimi-k2-instruct-0905\",\n",
+ " temperature=0.2,\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2bd41131",
+ "metadata": {},
+ "source": [
+ "**Tools**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "c8827093",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Index ready: {'_response_info': {'raw_headers': {'connection': 'keep-alive',\n",
+ " 'content-length': '187',\n",
+ " 'content-type': 'application/json',\n",
+ " 'date': 'Sat, 21 Mar 2026 06:03:49 GMT',\n",
+ " 'grpc-status': '0',\n",
+ " 'server': 'envoy',\n",
+ " 'x-envoy-upstream-service-time': '102',\n",
+ " 'x-pinecone-request-latency-ms': '102',\n",
+ " 'x-pinecone-response-duration-ms': '104'}},\n",
+ " 'dimension': 384,\n",
+ " 'index_fullness': 0.0,\n",
+ " 'memoryFullness': 0.0,\n",
+ " 'metric': 'dotproduct',\n",
+ " 'namespaces': {'__default__': {'vector_count': 33}},\n",
+ " 'storageFullness': 0.0,\n",
+ " 'total_vector_count': 33,\n",
+ " 'vector_type': 'dense'}\n"
+ ]
+ }
+ ],
+ "source": [
+ "\n",
+ "\n",
+ "PINECONE_API_KEY = os.getenv(\"PINECONE_API_KEY\")\n",
+ "pc = Pinecone(api_key=PINECONE_API_KEY)\n",
+ "\n",
+ "index_name = \"catalog-embeddings\"\n",
+ "\n",
+ "\n",
+ "# Create index if not exists\n",
+ "if index_name not in pc.list_indexes().names():\n",
+ " pc.create_index(\n",
+ " name=index_name,\n",
+ " dimension=384,\n",
+ " metric=\"dotproduct\",\n",
+ " spec=ServerlessSpec(\n",
+ " cloud=\"aws\",\n",
+ " region=\"us-east-1\"\n",
+ " )\n",
+ " )\n",
+ " print(\"Index created.\")\n",
+ "\n",
+ "index = pc.Index(index_name)\n",
+ "print(\"Index ready:\", index.describe_index_stats())\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "44180d94",
+ "metadata": {},
+ "source": [
+ "Opening the docs for BM25 retriver"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "id": "7561b3a1",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import json\n",
+ "from langchain_core.documents import Document\n",
+ "\n",
+ "# Initialize the list\n",
+ "documents = []\n",
+ "\n",
+ "# Load the transformed catalog\n",
+ "with open(\"formatted_catalog.json\", \"r\") as f:\n",
+ " data = json.load(f)\n",
+ " for doc in data:\n",
+ " # Create a LangChain Document object for each entry\n",
+ " documents.append(\n",
+ " Document(\n",
+ " page_content=doc[\"page_content\"], \n",
+ " metadata=doc[\"metadata\"]\n",
+ " )\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "id": "f0845a99",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "device=torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "id": "c8e6d2a5",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "C:\\Users\\ATHARVA\\AppData\\Local\\Temp\\ipykernel_10256\\2526755923.py:1: LangChainDeprecationWarning: The class `HuggingFaceEmbeddings` was deprecated in LangChain 0.2.2 and will be removed in 1.0. An updated version of the class exists in the `langchain-huggingface package and should be used instead. To use it run `pip install -U `langchain-huggingface` and import as `from `langchain_huggingface import HuggingFaceEmbeddings``.\n",
+ " embeddings = HuggingFaceEmbeddings(model_name=\"sentence-transformers/all-MiniLM-L6-v2\", model_kwargs={\"device\": device})\n"
+ ]
+ },
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "ecc00257ca4d4f3ea6c7fde1e0823058",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "Loading weights: 0%| | 0/103 [00:00, ?it/s]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "BertModel LOAD REPORT from: sentence-transformers/all-MiniLM-L6-v2\n",
+ "Key | Status | | \n",
+ "------------------------+------------+--+-\n",
+ "embeddings.position_ids | UNEXPECTED | | \n",
+ "\n",
+ "Notes:\n",
+ "- UNEXPECTED\t:can be ignored when loading from different task/architecture; not ok if you expect identical arch.\n"
+ ]
+ }
+ ],
+ "source": [
+ "embeddings = HuggingFaceEmbeddings(model_name=\"sentence-transformers/all-MiniLM-L6-v2\", model_kwargs={\"device\": device})"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "id": "6bc7292f",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "dc16b2516c194394bec281f956ab47d2",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ " 0%| | 0/33 [00:00, ?it/s]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "bm25_encoder = BM25Encoder()\n",
+ "\n",
+ "bm25_encoder.fit([doc.page_content for doc in documents])\n",
+ "\n",
+ "retriever = PineconeHybridSearchRetriever(\n",
+ " embeddings=embeddings,\n",
+ " sparse_encoder=bm25_encoder,\n",
+ " index=index\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 26,
+ "id": "03c755a1",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from langchain_core.tools import tool\n",
+ "from typing import Optional\n",
+ "\n",
+ "@tool\n",
+ "def search_courses(query: str, level: str, category: str):\n",
+ " \"\"\"\n",
+ " Search the course catalog for relevant modules based on a skill query, \n",
+ " difficulty level, and technical category.\n",
+ " \n",
+ " Args:\n",
+ " query: The semantic search term (e.g., 'FastAPI', 'PostgreSQL', 'Docker').\n",
+ " level: The difficulty level required ('beginner', 'intermediate', or 'strong').\n",
+ " category: The technical domain ('Backend', 'Frontend', 'DevOps', 'Cybersecurity', 'Database', 'ML').\n",
+ " \"\"\"\n",
+ " \n",
+ " # Using the hybrid search logic you perfected\n",
+ " # The '$and' ensures the agent gets EXACTLY what fits the candidate's level\n",
+ " results = retriever.invoke(\n",
+ " query, \n",
+ " filter={\n",
+ " \"$and\": [\n",
+ " {\"level\": level},\n",
+ " {\"category\": category}\n",
+ " ]\n",
+ " }\n",
+ " )\n",
+ "\n",
+ " if not results:\n",
+ " return f\"No {level} level courses found in the {category} category for '{query}'.\"\n",
+ "\n",
+ " # Format the output so the Agent can read the metadata easily\n",
+ " formatted_output = []\n",
+ " for doc in results:\n",
+ " course_info = (\n",
+ " f\"ID: {doc.metadata.get('course_id')}\\n\"\n",
+ " f\"Title: {doc.metadata.get('title')}\\n\"\n",
+ " f\"Description: {doc.page_content}\\n\"\n",
+ " f\"Prerequisites: {doc.metadata.get('prerequisites')}\\n\"\n",
+ " f\"Duration: {doc.metadata.get('duration')} hours\\n\"\n",
+ " \"---\"\n",
+ " )\n",
+ " formatted_output.append(course_info)\n",
+ "\n",
+ " return \"\\n\".join(formatted_output)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 97,
+ "id": "9db28710",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import json\n",
+ "from typing import Optional, Dict, Any\n",
+ "from langchain_core.tools import tool\n",
+ "\n",
+ "class CourseLookup:\n",
+ " def __init__(self, catalog_path: str = \"course_catalog.json\"):\n",
+ " self.catalog_path = catalog_path\n",
+ " self.courses_map = {}\n",
+ " self._load_catalog()\n",
+ "\n",
+ " def _load_catalog(self):\n",
+ " \"\"\"Loads the catalog into a dictionary for O(1) lookup speed.\"\"\"\n",
+ " try:\n",
+ " with open(self.catalog_path, 'r') as f:\n",
+ " catalog = json.load(f)\n",
+ " # Key the dictionary by course_id for instant retrieval\n",
+ " self.courses_map = {course['course_id']: course for course in catalog}\n",
+ " except FileNotFoundError:\n",
+ " print(f\"Error: {self.catalog_path} not found.\")\n",
+ " except json.JSONDecodeError:\n",
+ " print(f\"Error: Failed to decode {self.catalog_path}.\")\n",
+ "\n",
+ " def get_course_details(self, course_id: str) -> Optional[Dict[str, Any]]:\n",
+ " \"\"\"Retrieves full details of a course by its ID.\"\"\"\n",
+ " return self.courses_map.get(course_id)\n",
+ "\n",
+ "\n",
+ "lookup_service = CourseLookup(\"Catalog.json\")\n",
+ "\n",
+ "@tool\n",
+ "def get_course_by_id(course_id: str) -> str:\n",
+ " \"\"\"\n",
+ " Retrieves full details for a specific course using its unique course_id.\n",
+ " Use this tool when you find a prerequisite ID in another course and \n",
+ " need to fetch its title, description, and duration to add to the roadmap.\n",
+ " \"\"\"\n",
+ " details = lookup_service.get_course_details(course_id)\n",
+ " if not details:\n",
+ " return f\"Error: Course with ID {course_id} not found in catalog.\"\n",
+ " \n",
+ " # Return a clean string for the agent to process\n",
+ " return json.dumps(details, indent=2)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 118,
+ "id": "09d238ab",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# This is your \"Global Level\" storage outside the Graph\n",
+ "final_roadmap_store = {}\n",
+ "\n",
+ "@tool(args_schema=LearningRoadmap)\n",
+ "def submit_final_roadmap(candidate_name, target_role, roadmap, onboarding_summary):\n",
+ " \"\"\"\n",
+ " STRICTLY call this tool to submit the final structured learning roadmap.\n",
+ " This saves the data to the global system and the graph state.\n",
+ " \"\"\"\n",
+ " global final_roadmap_store\n",
+ " \n",
+ " # Construct the structured JSON\n",
+ " result = {\n",
+ " \"candidate_name\": candidate_name,\n",
+ " \"target_role\": target_role,\n",
+ " \"onboarding_summary\": onboarding_summary,\n",
+ " \"roadmap\": [\n",
+ " step.model_dump() if hasattr(step, \"model_dump\") else step \n",
+ " for step in roadmap\n",
+ " ]\n",
+ " }\n",
+ " \n",
+ " # Store within the tool's global context\n",
+ " final_roadmap_store = result\n",
+ " \n",
+ " # Return to LangGraph (will be stored in state via a post-processing node)\n",
+ " return result"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 149,
+ "id": "6ad04bc6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "mermaid_roadmap_code = \"\" # This is the global variable\n",
+ "\n",
+ "@tool\n",
+ "def submit_mermaid_visualization(mermaid_code: str):\n",
+ " \"\"\"\n",
+ " STRICTLY call this tool to save the Mermaid.js visualization of the roadmap.\n",
+ " \"\"\"\n",
+ " # 1. Tell Python to use the variable from the outer scope\n",
+ " global mermaid_roadmap_code \n",
+ " \n",
+ " # 2. Now this assignment updates the global variable\n",
+ " mermaid_roadmap_code = mermaid_code\n",
+ " \n",
+ " return \"Mermaid visualization stored successfully.\""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 150,
+ "id": "285f74bb",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "roadmap_planner_agent_tools=[search_courses, get_course_by_id,submit_final_roadmap,submit_mermaid_visualization]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 151,
+ "id": "47564782",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "roadmap_planner_agent=roadmap_planner_agent.bind_tools(roadmap_planner_agent_tools)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2da3f43b",
+ "metadata": {},
+ "source": [
+ "**Trail resume path**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "id": "7cfbfc3a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "resumepath=r\"c:\\Users\\ATHARVA\\Downloads\\my codes\\python\\machine_learning\\Learning_Files\\ChirayuResume.pdf\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "14f4946c",
+ "metadata": {},
+ "source": [
+ "**Langgraph agent state**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 189,
+ "id": "5deda2bb",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class OnboardingState(TypedDict):\n",
+ " candidate_name: Optional[str]\n",
+ " resume_text: str \n",
+ " file_path: str \n",
+ " job_description: str \n",
+ " messages: Annotated[Sequence[BaseMessage], add_messages]\n",
+ " \n",
+ " # Analysis & Extraction Data\n",
+ " skill_gap_analysis_data: Optional[SkillGapAnalysis]\n",
+ " resume_data: Optional[ResumeExtract] \n",
+ " extraction_error: Optional[str] \n",
+ " JobDescriptionExtract_data: Optional[JobDescriptionExtract]\n",
+ " \n",
+ " # --- NEW KEYS FOR OUTPUT ---\n",
+ " mermaid_code: Optional[str] # Stores the Mermaid visualization string\n",
+ " final_roadmap: Optional[Dict] # Stores the final structured JSON roadmap"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e54bac6a",
+ "metadata": {},
+ "source": [
+ "**Prompts**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 123,
+ "id": "c8df9934",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from langchain_groq import ChatGroq\n",
+ "from langchain_core.prompts import ChatPromptTemplate\n",
+ "\n",
+ "\n",
+ "resume_agent_prompt = \"\"\"\n",
+ "\n",
+ "You are a precise resume parser. Your only job is to extract structured information from a raw resume text.\n",
+ "\n",
+ "\n",
+ "\n",
+ "- Extract ONLY what is explicitly present in the resume. Do NOT infer or hallucinate missing fields.\n",
+ "- current_role: the job title stated at the top of the resume or most recent role. If the candidate is a student with no job, set it to \"Student\".\n",
+ "- is_fresher: set True ONLY if the candidate has zero professional work experience. Having projects or certifications does NOT make someone non-fresher.\n",
+ "- total_experience_years: total years of professional work only. Set 0.0 for freshers.\n",
+ "- skills: extract from the explicit skills section only. Do NOT pull skills from project descriptions here.\n",
+ "- experience: each role is a SEPARATE entry. Ignore company name. Focus on job_title, technologies used, and what they did or learned.\n",
+ "- projects: extract each project separately. Capture technologies and one line on what was built.\n",
+ "- certifications: extract ONLY if present. Set null if none found. Include topics the certification covers.\n",
+ "- achievements: extract ONLY if present. Set null if none found. Include the domain (e.g. Hackathon, Quiz, Competitive Programming).\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "Return a single valid JSON object matching the schema. No extra text, no markdown, no explanation.\n",
+ "\n",
+ "\n",
+ "\n",
+ "\"\"\""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 124,
+ "id": "608efafd",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "jd_agent_prompt =\"\"\" \n",
+ "\n",
+ "You are a precise job description parser.\n",
+ "Extract structured information from the given job description.\n",
+ "\n",
+ "\n",
+ "\n",
+ "- Extract ONLY explicitly mentioned information. Do NOT infer or hallucinate.\n",
+ "\n",
+ "- Follow the provided schema strictly.\n",
+ "\n",
+ "- If a field is not present, return null (not empty list unless schema default applies).\n",
+ "\n",
+ "- Keep skills atomic (e.g., Python, SQL, React).\n",
+ "\n",
+ "- Do NOT mix fields:\n",
+ " - skills = only required skills\n",
+ " - responsibilities = what the candidate will do\n",
+ " - constraints = restrictions like location, duration, eligibility\n",
+ "\n",
+ "- Convert durations like \"6 months\" into integer months.\n",
+ "\n",
+ "- is_fresher_allowed:\n",
+ " - True only if explicitly allowed\n",
+ " - False only if explicitly restricted\n",
+ " \n",
+ "\n",
+ "\n",
+ "\n",
+ "Return a valid JSON object only.\n",
+ " \"\"\"\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 125,
+ "id": "8a6c1483",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "gap_analysis_agent_prompt=\"\"\"\n",
+ "\n",
+ "You are an expert technical assessor and the core intelligence of an AI-driven, adaptive onboarding engine[cite: 5]. \n",
+ "Your objective is to parse a new hire's current capabilities against a target job description and identify precise skill gaps to reach role-specific competency[cite: 5].\n",
+ "\n",
+ "\n",
+ "\n",
+ "Current corporate onboarding utilizes static, \"one-size-fits-all\" curricula, resulting in significant inefficiencies[cite: 3]. \n",
+ "Your ultimate goal is to solve this: you must ensure experienced hires do NOT waste time on known concepts, while ensuring beginners are NOT overwhelmed by advanced modules[cite: 3, 4].\n",
+ "\n",
+ "\n",
+ "\n",
+ "- Cross-reference the JD's `skills_required` and `tools_technologies` against the candidate's `skills_list`, `experience.technologies`, and `projects.technologies`.\n",
+ "- Identify Hard Gaps: Technologies explicitly required by the JD that are completely absent from the candidate's profile.\n",
+ "- Apply Adaptive Logic (Proficiency Gaps):\n",
+ " - For Experienced Hires: If they possess the skill, DO NOT flag it for basic training. Only flag a gap if they need an advanced, role-specific upgrade based on low duration of use.\n",
+ " - For Beginners/Freshers: Flag foundational gaps and prerequisites heavily to ensure they are prepared before tackling complex JD requirements.\n",
+ "- Keep skills atomic and highly specific (e.g., output \"FastAPI\" or \"PostgreSQL\", do NOT output vague terms like \"Backend Frameworks\").\n",
+ "- Do NOT hallucinate requirements that are not explicitly stated in the JD data.\n",
+ "- Do NOT attempt to build the curriculum or suggest courses yet. Your sole focus is diagnosing the gaps.\n",
+ "- Provide a concise `reasoning` string for each identified gap. This reasoning MUST justify why the gap exists based on the user's experience level to prove the adaptive logic.\n",
+ "\n",
+ "\n",
+ "Return a valid JSON object only.\n",
+ "\n",
+ "\n",
+ "\n",
+ "\"\"\""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 126,
+ "id": "059e5f86",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "roadmap_planner_agent_prompt=\"\"\" \n",
+ "\n",
+ "You are the \"Architect of Growth,\" an expert technical roadmap planner. \n",
+ "Your objective is to transform a \"Skill Gap Analysis\" into a logically sequenced, \n",
+ "personalized learning journey that ensures \"Role Competency\" in the minimum time possible.\n",
+ "\n",
+ "\n",
+ "\n",
+ "1. ANALYZE GAPS: Review the identified skill gaps, their priority, and the 'gap_type' (foundation vs upgrade).\n",
+ "2. INITIAL SEARCH (RAG): For every high/medium priority gap, call 'search_courses'.\n",
+ " - Match the 'level' and 'category' strictly.\n",
+ "3. DEPENDENCY RESOLUTION (The \"ID-Lookup\" Step):\n",
+ " - For every course retrieved, inspect the 'prerequisites' field (list of IDs).\n",
+ " - CHECK: Does the 'resume_data' show the candidate already knows these prerequisites?\n",
+ " - IF NOT: You MUST call 'get_course_by_id' for each missing prerequisite ID.\n",
+ " - RECURSION: If the prerequisite itself has prerequisites, repeat this step until the path is complete.\n",
+ "4. ADAPTIVE SEQUENCING:\n",
+ " - Always place Prerequisite modules BEFORE the target Skill Gap module.\n",
+ " - If 'is_fresher_adaptation_needed' is True, start the entire roadmap with the 'SOFT-AGILE-101' or similar professional module.\n",
+ "5. JUSTIFY: For every course (including prerequisites), provide a unique 'reasoning' trace.\n",
+ " - Example for Prereq: \"Added 'SQL Basics' because 'PostgreSQL Mastery' requires it, and your resume shows no prior database experience.\"\n",
+ "6.after you have a complete roadmap, call 'submit_final_roadmap' and 'submit_mermaid_visualization'.\n",
+ "\n",
+ "\n",
+ "\n",
+ "- STRICT ID USAGE: Use ONLY the 'course_id' returned by tools. Never guess an ID.\n",
+ "- REDUNDANCY CHECK: Do not assign a course if the candidate's projects or experience already prove mastery of that specific topic.\n",
+ "- PATH LENGTH: Prioritize the most critical 5-6 modules total to ensure the onboarding is high-impact and achievable.\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "- DO NOT provide a conversational response at the end. \n",
+ "- DO NOT just print JSON. \n",
+ "- You MUST call the 'submit_final_roadmap' and 'submit_mermaid_visualization' tool with the final plan.\n",
+ "- Ensure 'sequence_order' is 1, 2, 3...\n",
+ "\n",
+ "\n",
+ "\n",
+ "flowchart TD\n",
+ " A([Start — Rahul's current skills]):::start\n",
+ " subgraph W1[\"Week 1 — Core gaps\"]\n",
+ " B[CS-DOCKER-101\\nDocker & Containerization]:::gap\n",
+ " C[CS-PY-101\\nPython Fundamentals]:::known\n",
+ " end\n",
+ " subgraph W2[\"Week 2 — Role readiness\"]\n",
+ " D[CS-CICD-201\\nCI/CD with GitHub Actions]:::gap\n",
+ " end\n",
+ " Z([Role-ready — DevOps Engineer]):::done\n",
+ " A --> B & C\n",
+ " B --> D\n",
+ " D --> Z\n",
+ " classDef gap fill:#EEEDFE,stroke:#534AB7,color:#26215C\n",
+ " classDef known fill:#E1F5EE,stroke:#0F6E56,color:#085041\n",
+ " classDef start fill:#1D9E75,stroke:#0F6E56,color:#E1F5EE\n",
+ " classDef done fill:#534AB7,stroke:#3C3489,color:#EEEDFE\n",
+ "\n",
+ "\n",
+ "\n",
+ "\"\"\""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 127,
+ "id": "9c4dea1e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from langchain_community.document_loaders import PyMuPDFLoader\n",
+ "\n",
+ "def input_node(state: OnboardingState):\n",
+ "\n",
+ " file_path = state.get(\"file_path\") \n",
+ "\n",
+ " if not file_path:\n",
+ " return {\"extraction_error\": \"Missing file_path in state\"}\n",
+ "\n",
+ " try:\n",
+ " loader = PyMuPDFLoader(file_path)\n",
+ " docs = loader.load()\n",
+ "\n",
+ " \n",
+ " resume_text = \"\\n\".join([doc.page_content for doc in docs])\n",
+ "\n",
+ " return {\n",
+ " \"resume_text\": resume_text,\n",
+ " \"extraction_error\": None\n",
+ " }\n",
+ "\n",
+ " except Exception as e:\n",
+ " return {\n",
+ " \"extraction_error\": f\"Failed to load resume: {str(e)}\"\n",
+ " }"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 193,
+ "id": "eb13ffc0",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def extractResumeDataNode(state: OnboardingState):\n",
+ " \n",
+ " resume_text = state[\"resume_text\"]\n",
+ "\n",
+ " messages = [\n",
+ " SystemMessage(content=resume_agent_prompt),\n",
+ " HumanMessage(content=f\"{resume_text}\")\n",
+ " ]\n",
+ "\n",
+ " \n",
+ " result = resume_agent.invoke(messages)\n",
+ "\n",
+ " return {\"resume_data\": result[\"parsed\"]}"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 195,
+ "id": "330acef6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def extractJDDataNode(state: OnboardingState):\n",
+ " # 1. Safety Check: Is the text even in the state?\n",
+ " jd_text = state.get(\"job_description\", \"\")\n",
+ " \n",
+ " if not jd_text or len(jd_text.strip()) < 5:\n",
+ " print(\"DEBUGGER ERROR: job_description text is MISSING from state!\")\n",
+ " return {\"JobDescriptionExtract_data\": JobDescriptionExtract()}\n",
+ "\n",
+ " print(f\"DEBUGGER: Sending {len(jd_text)} characters to JD Agent...\")\n",
+ "\n",
+ " messages = [\n",
+ " SystemMessage(content=jd_agent_prompt),\n",
+ " HumanMessage(content=f\"EXTRACT FROM THIS TEXT:\\n\\n{jd_text}\")\n",
+ " ]\n",
+ "\n",
+ " try:\n",
+ " # 2. Invoke the agent\n",
+ " result = jd_agent.invoke(messages)\n",
+ " \n",
+ " # 3. Handle the 'parsed' key (ensure your chain is configured correctly)\n",
+ " # If result is already the Pydantic object, use it directly.\n",
+ " # If result is a dict with 'parsed', use result['parsed'].\n",
+ " parsed_data = result.get(\"parsed\") if isinstance(result, dict) else result\n",
+ "\n",
+ " # 4. Critical Check: Did it actually find anything?\n",
+ " if parsed_data.job_title is None and parsed_data.tools_technologies is None:\n",
+ " print(\"DEBUGGER WARNING: LLM returned empty schema! Checking prompt...\")\n",
+ " else:\n",
+ " print(f\"DEBUGGER SUCCESS: Extracted {parsed_data.job_title}\")\n",
+ "\n",
+ " return {\"JobDescriptionExtract_data\": parsed_data}\n",
+ " \n",
+ " except Exception as e:\n",
+ " print(f\"DEBUGGER CRITICAL: Invoke failed: {str(e)}\")\n",
+ " return {\"JobDescriptionExtract_data\": JobDescriptionExtract()}"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 200,
+ "id": "7352181c",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def skill_gap_node(state: OnboardingState):\n",
+ " \n",
+ " resume_data = state[\"resume_data\"] \n",
+ " candidate_name = state[\"candidate_name\"]\n",
+ " \n",
+ " # To remove noise and reduce size of the prompt.\n",
+ " lean_resume_dict = resume_data.model_dump(\n",
+ " exclude={\n",
+ " \"achievements\": True, # Drops the entire achievements list\n",
+ " \"skills\": {\"__all__\": {\"category\"}}, # Drops 'category' from every skill\n",
+ " \"experience\": {\"__all__\": {\"responsibilities\"}}, # Drops bullet points\n",
+ " \"projects\": {\"__all__\": {\"what_was_built\"}}, # Drops project descriptions\n",
+ " \"certifications\": {\"__all__\": {\"issuer\"}} # Drops the issuer\n",
+ " },\n",
+ " exclude_none=True # Bonus: Automatically drops any fields that are None/null!\n",
+ " )\n",
+ "\n",
+ " raw_jd = state[\"JobDescriptionExtract_data\"]\n",
+ " \n",
+ " # Strip the HR noise and text bloat\n",
+ " lean_jd_dict = raw_jd.model_dump(\n",
+ " exclude={\n",
+ " \"company_name\": True,\n",
+ " \"location\": True,\n",
+ " \"employment_type\": True,\n",
+ " \"duration_months\": True,\n",
+ " \"responsibilities\": True, # Dropping verbose bullet points\n",
+ " \"requirements\": True,\n",
+ " \"constraints\": True\n",
+ " },\n",
+ " exclude_none=True # Drops any null fields\n",
+ " )\n",
+ " \n",
+ " #Convert back to a JSON string if your prompt template requires it\n",
+ " \n",
+ " lean_resume_json = json.dumps(lean_resume_dict, indent=2)\n",
+ "\n",
+ "\n",
+ " lean_jd_json = json.dumps(lean_jd_dict, indent=2)\n",
+ "\n",
+ " messages = [\n",
+ " SystemMessage(content=gap_analysis_agent_prompt),\n",
+ " HumanMessage(content=f\"Users Resume:{lean_resume_json} Job Description:{lean_jd_json}\"),\n",
+ " \n",
+ " ]\n",
+ "\n",
+ " \n",
+ " result = gap_analysis_agent.invoke(messages)\n",
+ "\n",
+ " return {\"skill_gap_analysis_data\": result[\"parsed\"]}\n",
+ "\n",
+ "\n",
+ " \n",
+ "\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 201,
+ "id": "1fb2f0d1",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def roadmap_planning_node(state: OnboardingState):\n",
+ " \"\"\"\n",
+ " The agent's 'thinking' node. It looks at the Skill Gaps and \n",
+ " decides which tool to call next.\n",
+ " \"\"\"\n",
+ " skill_gap_data = state[\"skill_gap_analysis_data\"]\n",
+ "\n",
+ " skill_gap_data= skill_gap_data.model_dump()\n",
+ "\n",
+ " system_prompt = SystemMessage(content=roadmap_planner_agent_prompt)\n",
+ " input_msg = HumanMessage(content=f\" {skill_gap_data} \")\n",
+ " \n",
+ " response = roadmap_planner_agent.invoke([system_prompt, input_msg] + state[\"messages\"])\n",
+ " \n",
+ " return {\"messages\": [response]}"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 198,
+ "id": "cea90664",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def finalize_state_node(state: OnboardingState):\n",
+ " \"\"\"\n",
+ " Final node that extracts structured data from the message scratchpad\n",
+ " and populates the main state keys. No global variables needed!\n",
+ " \"\"\"\n",
+ " final_roadmap = None\n",
+ " mermaid_code = None\n",
+ "\n",
+ " # We search the messages in reverse to find the LATEST tool calls\n",
+ " for msg in reversed(state[\"messages\"]):\n",
+ " # Check if the message has tool calls (this will be an AIMessage)\n",
+ " if hasattr(msg, \"tool_calls\") and msg.tool_calls:\n",
+ " for tool_call in msg.tool_calls:\n",
+ " \n",
+ " # 1. Extract the Roadmap JSON\n",
+ " if tool_call[\"name\"] == \"submit_final_roadmap\":\n",
+ " final_roadmap = tool_call[\"args\"]\n",
+ " \n",
+ " # 2. Extract the Mermaid String\n",
+ " elif tool_call[\"name\"] == \"submit_mermaid_visualization\":\n",
+ " mermaid_code = tool_call[\"args\"].get(\"mermaid_code\")\n",
+ "\n",
+ " # Once we have both, we can stop searching\n",
+ " if final_roadmap and mermaid_code:\n",
+ " break\n",
+ "\n",
+ " \n",
+ " \n",
+ " return {\n",
+ " \"final_roadmap\": final_roadmap,\n",
+ " \"mermaid_code\": mermaid_code\n",
+ " }"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 179,
+ "id": "ba9f22e1",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "tool_node = ToolNode(roadmap_planner_agent_tools)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 202,
+ "id": "b5cfe4c3",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "builder = StateGraph(OnboardingState)\n",
+ "\n",
+ "# Define Nodes\n",
+ "builder.add_node(\"input_node\", input_node)\n",
+ "builder.add_node(\"resume_data_extraction\", extractResumeDataNode)\n",
+ "builder.add_node(\"jd_data_extraction\", extractJDDataNode)\n",
+ "builder.add_node(\"skill_gap_analysis\", skill_gap_node)\n",
+ "builder.add_node(\"roadmap_planning_agent\", roadmap_planning_node)\n",
+ "builder.add_node(\"tools\", tool_node) # Named 'tools' for tools_condition compatibility\n",
+ "builder.add_node(\"finalize_state\", finalize_state_node)\n",
+ "\n",
+ "# Define Entry Point and initial Extraction Parallelism\n",
+ "builder.set_entry_point(\"input_node\")\n",
+ "builder.add_edge(\"input_node\", \"resume_data_extraction\")\n",
+ "builder.add_edge(\"input_node\", \"jd_data_extraction\")\n",
+ "\n",
+ "# Join Extractions into Gap Analysis\n",
+ "builder.add_edge(\"resume_data_extraction\", \"skill_gap_analysis\")\n",
+ "builder.add_edge(\"jd_data_extraction\", \"skill_gap_analysis\")\n",
+ "\n",
+ "# Transition from Analysis to Planning Agent\n",
+ "builder.add_edge(\"skill_gap_analysis\", \"roadmap_planning_agent\")\n",
+ "\n",
+ "# Agentic ReAct Loop (Planning Agent <-> Tools)\n",
+ "builder.add_conditional_edges(\n",
+ " \"roadmap_planning_agent\",\n",
+ " tools_condition, # Automatically routes to 'tools' if tool_calls present, else to END\n",
+ ")\n",
+ "\n",
+ "# After tools run, loop back to the agent for next steps\n",
+ "builder.add_edge(\"tools\", \"roadmap_planning_agent\")\n",
+ "\n",
+ "builder.add_edge(\"roadmap_planning_agent\", \"finalize_state\")\n",
+ "\n",
+ "# Compile the Graph\n",
+ "graph = builder.compile()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 170,
+ "id": "53588a77",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "display(graph)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 187,
+ "id": "0edf8d59",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "jd_text=\"\"\"Job Title: Backend Developer\n",
+ "\n",
+ "Company name: CodeForge\n",
+ "We are hiring a Backend Developer to build scalable APIs and backend systems.\n",
+ "\n",
+ "Responsibilities:\n",
+ "- Develop REST APIs using FastAPI\n",
+ "- Design and manage PostgreSQL databases\n",
+ "- Implement authentication and authorization systems\n",
+ "- Optimize performance and scalability\n",
+ "\n",
+ "Requirements:\n",
+ "- Strong knowledge of Python\n",
+ "- Experience with FastAPI or Django\n",
+ "- Good understanding of SQL and database design\n",
+ "- Familiarity with Docker\n",
+ "\n",
+ "Constraints:\n",
+ "- Location: Pune only\n",
+ "- Full-time role \"\"\""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 203,
+ "id": "a95b4db7",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "DEBUGGER: Sending 525 characters to JD Agent...\n",
+ "DEBUGGER SUCCESS: Extracted Backend Developer\n"
+ ]
+ }
+ ],
+ "source": [
+ "initial_input = {\n",
+ " \"candidate_name\": \"Chirayu Jain\",\n",
+ " \"resume_text\": None,\n",
+ " \"job_description\": jd_text,\n",
+ " \"file_path\": resumepath,\n",
+ " \"resume_data\": None,\n",
+ " \"extraction_error\": None,\n",
+ " \"JobDescriptionExtract_data\": None,\n",
+ " \"skill_gap_analysis_data\": None\n",
+ " \n",
+ " \n",
+ "}\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "final_state = graph.invoke(initial_input)\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 204,
+ "id": "5afbce5b",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'candidate_name': 'Chirayu Jain',\n",
+ " 'resume_text': 'Chirayu Jain\\nSOFTWARE DEVELOPER\\n+91-9462128878 | chirayujain93@gmail.com | LinkedIn | GitHub | CodeChef | Leetcode\\nEDUCATION\\nIndian Institute of Information Technology, Kota (CGPA-8.1)\\nKota, Rajasthan\\nB.Tech in Electronics and Communication Engineering\\n2022 - Present\\nWORK EXPERIENCE\\nNAV India\\nJan 2026 - Present\\nTechnology Intern\\nOnsite – Jaipur, Rajasthan\\n• As part of the AI/ML team, built Flask API for the RTA investor verification team, implementing 2\\nconfidence-scoring pipelines using OCR extraction (PyMuPDF, Docling) to parse PDF/forms and\\nextract 3 key KYC investor attributes, reducing verification time from 20–30 minutes to 2–3 minutes.\\n• Developed a scalable document intelligence pipeline with parallel CPU processing using Docling OCR,\\nsemantic chunking, and LLM-based extraction to process 60+ page financial documents and identify\\ninvestor/investee details, reducing extraction time by 35%.\\n• Implementing Cron-based background workers to generate automated weekly email analytics reports\\ntracking API accuracy and confidence match-mismatch scores, improving scoring precision and reducing\\noverall document processing latency by 40%.\\nVestbox\\nApril 2025 - May 2025\\nSoftware Development intern\\nRemote\\n• Developed Groceazy app in Flutter (MVVM), integrating 10+ modules for grocery service , order management\\nand Applied clean code practices (SOLID, modular design) for scalability.\\n• Implemented secure user authentication and role-based access, ensuring smooth login and data protection.\\n• Built image-to-item price matcher using APIs across 5+ platforms, enabling best-price suggestions.\\nPROJECTS\\nHostel Bites – IIITK Canteen App | Flutter, Dart, Firebase, Cloud Firestore, Mapbox API\\n• Developed a Flutter app for hostel residents to order food, increasing canteen efficiency by 35%.\\n• Implemented role-based access for students, staff, and admin, increasing operational efficiency by 40%.\\n• Integrated Firebase Auth, Firestore, Storage, and Mapbox API for secure logins, and live order tracking.\\n• GitHub: Live link\\nJobShield - Fake Job Detector | Flutter, Dart, Python, Flask, SQLite\\n• Developed a Flask+Python fake job detection API with a custom NLP model, with 95% classification accuracy.\\n• Built a responsive web interface using Flutter (Web), integrated with the backend, enabling seamless real-time\\ndetection for 500+ job postings.\\n• Github: Live Link\\nTECHNICAL SKILLS\\nLanguages: C/C++ , Java , Python , JavaScript, Dart\\nFrontend: Flutter, HTML/CSS, ReactJS\\nBackend: Firebase, REST APIs, Flask, Django, NodeJS, ExpressJS\\nDatabase Management: MySQL, MongoDB, SQLite\\nTools and Frameworks: GitHub, Android Studio, Visual Studio Code, MATLAB, Cursor, Github Copilot\\nLibraries: Provider, Bloc, Scikit-learn, tensorflow\\nACHIEVEMENTS & CERTIFICATIONS\\n• Ranked 81 in Inter IIIT Coding Contest Optigo, competing against top-tier programmers - View credential.\\n• Obtained Certification in Graph Algorithms from AlgoUniversity - View credential.\\n• Achieved a 4-star CodeChef rating and earned the Knight(Max. 1888) badge on LeetCode.\\n• Developed an e-waste facility locator application for SIH’23, successfully advancing to the regionals.',\n",
+ " 'file_path': 'c:\\\\Users\\\\ATHARVA\\\\Downloads\\\\my codes\\\\python\\\\machine_learning\\\\Learning_Files\\\\ChirayuResume.pdf',\n",
+ " 'job_description': 'Job Title: Backend Developer\\n\\nCompany name: CodeForge\\nWe are hiring a Backend Developer to build scalable APIs and backend systems.\\n\\nResponsibilities:\\n- Develop REST APIs using FastAPI\\n- Design and manage PostgreSQL databases\\n- Implement authentication and authorization systems\\n- Optimize performance and scalability\\n\\nRequirements:\\n- Strong knowledge of Python\\n- Experience with FastAPI or Django\\n- Good understanding of SQL and database design\\n- Familiarity with Docker\\n\\nConstraints:\\n- Location: Pune only\\n- Full-time role ',\n",
+ " 'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'functions.search_courses:0', 'function': {'arguments': '{\"category\":\"Backend\",\"level\":\"beginner\",\"query\":\"FastAPI\"}', 'name': 'search_courses'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 32, 'prompt_tokens': 1669, 'total_tokens': 1701, 'completion_time': 0.081090275, 'completion_tokens_details': None, 'prompt_time': 0.054224996, 'prompt_tokens_details': {'cached_tokens': 1280}, 'queue_time': 0.278005602, 'total_time': 0.135315271}, 'model_name': 'moonshotai/kimi-k2-instruct-0905', 'system_fingerprint': 'fp_241bc7119c', 'service_tier': 'on_demand', 'finish_reason': 'tool_calls', 'logprobs': None, 'model_provider': 'groq'}, id='lc_run--019d109d-42ae-7050-b05b-ef26234889c6-0', tool_calls=[{'name': 'search_courses', 'args': {'category': 'Backend', 'level': 'beginner', 'query': 'FastAPI'}, 'id': 'functions.search_courses:0', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 1669, 'output_tokens': 32, 'total_tokens': 1701, 'input_token_details': {'cache_read': 1280}}),\n",
+ " ToolMessage(content=\"ID: CS-FAST-101\\nTitle: None\\nDescription: Title: REST API Development with FastAPI. Description: Build production-ready REST APIs using FastAPI, covering routing, request validation with Pydantic, and basic authentication.. Outcomes: Create REST endpoints with path and query params, Validate request/response with Pydantic models, Implement JWT-based authentication\\nPrerequisites: ['CS-PY-101']\\nDuration: 6 hours\\n---\", name='search_courses', id='a4c82290-1241-4d9f-b746-95edbed569d7', tool_call_id='functions.search_courses:0'),\n",
+ " AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'functions.search_courses:1', 'function': {'arguments': '{\"category\":\"Database\",\"level\":\"beginner\",\"query\":\"PostgreSQL\"}', 'name': 'search_courses'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 32, 'prompt_tokens': 1810, 'total_tokens': 1842, 'completion_time': 0.078748872, 'completion_tokens_details': None, 'prompt_time': 0.238576633, 'prompt_tokens_details': None, 'queue_time': 0.283975812, 'total_time': 0.317325505}, 'model_name': 'moonshotai/kimi-k2-instruct-0905', 'system_fingerprint': 'fp_00c37775b7', 'service_tier': 'on_demand', 'finish_reason': 'tool_calls', 'logprobs': None, 'model_provider': 'groq'}, id='lc_run--019d109d-4a27-70c1-bae4-9f895628b422-0', tool_calls=[{'name': 'search_courses', 'args': {'category': 'Database', 'level': 'beginner', 'query': 'PostgreSQL'}, 'id': 'functions.search_courses:1', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 1810, 'output_tokens': 32, 'total_tokens': 1842}),\n",
+ " ToolMessage(content='ID: CS-DB-101\\nTitle: None\\nDescription: Title: SQL Fundamentals for Backend Developers. Description: Foundational course covering relational database concepts, CRUD operations, and basic JOINs using SQLite and PostgreSQL.. Outcomes: Write basic SELECT queries with filters, Understand Primary and Foreign Keys, Perform data insertion, updates, and deletions\\nPrerequisites: []\\nDuration: 4 hours\\n---', name='search_courses', id='21676f09-7c22-4f04-8de5-4393254e9bc7', tool_call_id='functions.search_courses:1'),\n",
+ " AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'functions.search_courses:2', 'function': {'arguments': '{\"category\":\"DevOps\",\"level\":\"beginner\",\"query\":\"Docker\"}', 'name': 'search_courses'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 32, 'prompt_tokens': 1943, 'total_tokens': 1975, 'completion_time': 0.078745613, 'completion_tokens_details': None, 'prompt_time': 0.151167189, 'prompt_tokens_details': {'cached_tokens': 1280}, 'queue_time': 0.289579237, 'total_time': 0.229912802}, 'model_name': 'moonshotai/kimi-k2-instruct-0905', 'system_fingerprint': 'fp_05df423bab', 'service_tier': 'on_demand', 'finish_reason': 'tool_calls', 'logprobs': None, 'model_provider': 'groq'}, id='lc_run--019d109d-4e10-7312-a16a-8ef5f399c11c-0', tool_calls=[{'name': 'search_courses', 'args': {'category': 'DevOps', 'level': 'beginner', 'query': 'Docker'}, 'id': 'functions.search_courses:2', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 1943, 'output_tokens': 32, 'total_tokens': 1975, 'input_token_details': {'cache_read': 1280}}),\n",
+ " ToolMessage(content='ID: CS-DOCKER-101\\nTitle: None\\nDescription: Title: Docker & Containerization Fundamentals. Description: Learn containerization fundamentals — writing Dockerfiles, managing images, volumes, and running multi-container apps with Docker Compose.. Outcomes: Write efficient multi-stage Dockerfiles, Manage container lifecycle and networking, Orchestrate multi-service apps with Docker Compose\\nPrerequisites: []\\nDuration: 5 hours\\n---\\nID: CS-LINUX-101\\nTitle: None\\nDescription: Title: Linux & Shell Scripting for Developers. Description: Practical Linux skills for developers covering file system navigation, permissions, process management, and bash scripting.. Outcomes: Navigate and manage the Linux file system, Write bash scripts for automation, Manage processes, cron jobs, and system services\\nPrerequisites: []\\nDuration: 5 hours\\n---', name='search_courses', id='24c06dac-8359-4b62-9009-0143205216b9', tool_call_id='functions.search_courses:2'),\n",
+ " AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'functions.get_course_by_id:3', 'function': {'arguments': '{\"course_id\":\"CS-PY-101\"}', 'name': 'get_course_by_id'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 24, 'prompt_tokens': 2162, 'total_tokens': 2186, 'completion_time': 0.066258418, 'completion_tokens_details': None, 'prompt_time': 0.06777634, 'prompt_tokens_details': {'cached_tokens': 1792}, 'queue_time': 0.292961317, 'total_time': 0.134034758}, 'model_name': 'moonshotai/kimi-k2-instruct-0905', 'system_fingerprint': 'fp_00c37775b7', 'service_tier': 'on_demand', 'finish_reason': 'tool_calls', 'logprobs': None, 'model_provider': 'groq'}, id='lc_run--019d109d-5182-72f2-a111-97966407f56f-0', tool_calls=[{'name': 'get_course_by_id', 'args': {'course_id': 'CS-PY-101'}, 'id': 'functions.get_course_by_id:3', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 2162, 'output_tokens': 24, 'total_tokens': 2186, 'input_token_details': {'cache_read': 1792}}),\n",
+ " ToolMessage(content='{\\n \"course_id\": \"CS-PY-101\",\\n \"title\": \"Python Programming Fundamentals\",\\n \"level\": \"beginner\",\\n \"category\": \"Programming\",\\n \"description\": \"A comprehensive introduction to Python covering primitive and complex data types, control flow structures, function design, object-oriented programming principles, exception handling, and file I/O. Students build a strong syntactic and conceptual foundation before moving to applied domains.\",\\n \"learning_outcomes\": [\\n \"Write clean Python functions and classes\",\\n \"Understand list, dict, and set operations\",\\n \"Handle exceptions and file I/O\"\\n ],\\n \"prerequisites\": [],\\n \"estimated_duration_hours\": 6,\\n \"tags\": [\\n \"Python\",\\n \"Programming\",\\n \"OOP\"\\n ]\\n}', name='get_course_by_id', id='433e36da-b9d8-4ff7-a747-91231f523767', tool_call_id='functions.get_course_by_id:3'),\n",
+ " AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'functions.search_courses:4', 'function': {'arguments': '{\"category\":\"Backend\",\"level\":\"intermediate\",\"query\":\"FastAPI\"}', 'name': 'search_courses'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 32, 'prompt_tokens': 2368, 'total_tokens': 2400, 'completion_time': 0.091942552, 'completion_tokens_details': None, 'prompt_time': 0.063767504, 'prompt_tokens_details': {'cached_tokens': 2048}, 'queue_time': 0.281042933, 'total_time': 0.155710056}, 'model_name': 'moonshotai/kimi-k2-instruct-0905', 'system_fingerprint': 'fp_00c37775b7', 'service_tier': 'on_demand', 'finish_reason': 'tool_calls', 'logprobs': None, 'model_provider': 'groq'}, id='lc_run--019d109d-535a-7e82-8e04-294c8397192c-0', tool_calls=[{'name': 'search_courses', 'args': {'category': 'Backend', 'level': 'intermediate', 'query': 'FastAPI'}, 'id': 'functions.search_courses:4', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 2368, 'output_tokens': 32, 'total_tokens': 2400, 'input_token_details': {'cache_read': 2048}}),\n",
+ " ToolMessage(content=\"ID: CS-FAST-201\\nTitle: None\\nDescription: Title: Advanced API Design with FastAPI. Description: Deep dive into asynchronous programming, dependency injection, background tasks, and building secure RESTful services.. Outcomes: Implement OAuth2 authentication flows, Build async database endpoints with SQLAlchemy, Use dependency injection and middleware patterns\\nPrerequisites: ['CS-PY-101', 'CS-DB-101', 'CS-FAST-101']\\nDuration: 8 hours\\n---\", name='search_courses', id='002e76e3-19e3-43f8-84dd-b1de4773cc4c', tool_call_id='functions.search_courses:4'),\n",
+ " AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'functions.search_courses:5', 'function': {'arguments': '{\"category\":\"Database\",\"level\":\"intermediate\",\"query\":\"PostgreSQL\"}', 'name': 'search_courses'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 32, 'prompt_tokens': 2517, 'total_tokens': 2549, 'completion_time': 0.087624525, 'completion_tokens_details': None, 'prompt_time': 0.129532407, 'prompt_tokens_details': {'cached_tokens': 1536}, 'queue_time': 0.2779709, 'total_time': 0.217156932}, 'model_name': 'moonshotai/kimi-k2-instruct-0905', 'system_fingerprint': 'fp_241bc7119c', 'service_tier': 'on_demand', 'finish_reason': 'tool_calls', 'logprobs': None, 'model_provider': 'groq'}, id='lc_run--019d109d-5698-7983-b335-4301bd967286-0', tool_calls=[{'name': 'search_courses', 'args': {'category': 'Database', 'level': 'intermediate', 'query': 'PostgreSQL'}, 'id': 'functions.search_courses:5', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 2517, 'output_tokens': 32, 'total_tokens': 2549, 'input_token_details': {'cache_read': 1536}}),\n",
+ " ToolMessage(content=\"ID: CS-DB-301\\nTitle: None\\nDescription: Title: NoSQL Databases — MongoDB & Redis. Description: Introduction to document and key-value stores, covering MongoDB aggregation pipelines and Redis caching strategies.. Outcomes: Design document schemas in MongoDB, Build aggregation pipelines, Implement caching with Redis TTL strategies\\nPrerequisites: ['CS-DB-101']\\nDuration: 6 hours\\n---\\nID: CS-DB-201\\nTitle: None\\nDescription: Title: Advanced SQL — Indexing, Transactions & Query Optimization. Description: Covers advanced SQL techniques including window functions, CTEs, query execution plans, and transaction management.. Outcomes: Use window functions and CTEs, Analyze and optimize slow queries with EXPLAIN, Manage ACID transactions and deadlock prevention\\nPrerequisites: ['CS-DB-101']\\nDuration: 7 hours\\n---\", name='search_courses', id='961ae55c-605a-4795-97fe-7e0e637e84bb', tool_call_id='functions.search_courses:5'),\n",
+ " AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'functions.search_courses:6', 'function': {'arguments': '{\"category\":\"DevOps\",\"level\":\"intermediate\",\"query\":\"Docker\"}', 'name': 'search_courses'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 32, 'prompt_tokens': 2748, 'total_tokens': 2780, 'completion_time': 0.091283136, 'completion_tokens_details': None, 'prompt_time': 0.120342589, 'prompt_tokens_details': {'cached_tokens': 2304}, 'queue_time': 0.276559238, 'total_time': 0.211625725}, 'model_name': 'moonshotai/kimi-k2-instruct-0905', 'system_fingerprint': 'fp_241bc7119c', 'service_tier': 'on_demand', 'finish_reason': 'tool_calls', 'logprobs': None, 'model_provider': 'groq'}, id='lc_run--019d109d-59f8-7601-8215-7119a65a58bf-0', tool_calls=[{'name': 'search_courses', 'args': {'category': 'DevOps', 'level': 'intermediate', 'query': 'Docker'}, 'id': 'functions.search_courses:6', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 2748, 'output_tokens': 32, 'total_tokens': 2780, 'input_token_details': {'cache_read': 2304}}),\n",
+ " ToolMessage(content=\"ID: CS-CICD-201\\nTitle: None\\nDescription: Title: CI/CD Pipelines with GitHub Actions. Description: Build automated build, test, and deployment pipelines using GitHub Actions, with Docker integration and environment secrets management.. Outcomes: Create GitHub Actions workflows for CI/CD, Automate Docker image builds and pushes, Manage secrets and environment variables securely\\nPrerequisites: ['CS-DOCKER-101']\\nDuration: 6 hours\\n---\", name='search_courses', id='a45bf4ed-fc16-49b4-a2c8-6fcdcaa5d178', tool_call_id='functions.search_courses:6'),\n",
+ " AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'functions.search_courses:7', 'function': {'arguments': '{\"category\":\"Backend\",\"level\":\"beginner\",\"query\":\"SOFT-AGILE\"}', 'name': 'search_courses'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 35, 'prompt_tokens': 2892, 'total_tokens': 2927, 'completion_time': 0.100814652, 'completion_tokens_details': None, 'prompt_time': 0.03574403, 'prompt_tokens_details': {'cached_tokens': 2560}, 'queue_time': 0.278250856, 'total_time': 0.136558682}, 'model_name': 'moonshotai/kimi-k2-instruct-0905', 'system_fingerprint': 'fp_241bc7119c', 'service_tier': 'on_demand', 'finish_reason': 'tool_calls', 'logprobs': None, 'model_provider': 'groq'}, id='lc_run--019d109d-5d30-75a2-aadd-ca9c136b1561-0', tool_calls=[{'name': 'search_courses', 'args': {'category': 'Backend', 'level': 'beginner', 'query': 'SOFT-AGILE'}, 'id': 'functions.search_courses:7', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 2892, 'output_tokens': 35, 'total_tokens': 2927, 'input_token_details': {'cache_read': 2560}}),\n",
+ " ToolMessage(content=\"ID: CS-FAST-101\\nTitle: None\\nDescription: Title: REST API Development with FastAPI. Description: Build production-ready REST APIs using FastAPI, covering routing, request validation with Pydantic, and basic authentication.. Outcomes: Create REST endpoints with path and query params, Validate request/response with Pydantic models, Implement JWT-based authentication\\nPrerequisites: ['CS-PY-101']\\nDuration: 6 hours\\n---\", name='search_courses', id='c8ef5e39-a5ca-4ddf-ae0c-3598cd729a3e', tool_call_id='functions.search_courses:7'),\n",
+ " AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'functions.search_courses:8', 'function': {'arguments': '{\"category\":\"Backend\",\"level\":\"beginner\",\"query\":\"professional skills\"}', 'name': 'search_courses'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 32, 'prompt_tokens': 3036, 'total_tokens': 3068, 'completion_time': 0.095156634, 'completion_tokens_details': None, 'prompt_time': 0.164064894, 'prompt_tokens_details': {'cached_tokens': 1792}, 'queue_time': 0.286151251, 'total_time': 0.259221528}, 'model_name': 'moonshotai/kimi-k2-instruct-0905', 'system_fingerprint': 'fp_05df423bab', 'service_tier': 'on_demand', 'finish_reason': 'tool_calls', 'logprobs': None, 'model_provider': 'groq'}, id='lc_run--019d109d-6023-7b50-8bc0-b339c5e8255d-0', tool_calls=[{'name': 'search_courses', 'args': {'category': 'Backend', 'level': 'beginner', 'query': 'professional skills'}, 'id': 'functions.search_courses:8', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 3036, 'output_tokens': 32, 'total_tokens': 3068, 'input_token_details': {'cache_read': 1792}}),\n",
+ " ToolMessage(content=\"ID: CS-FAST-101\\nTitle: None\\nDescription: Title: REST API Development with FastAPI. Description: Build production-ready REST APIs using FastAPI, covering routing, request validation with Pydantic, and basic authentication.. Outcomes: Create REST endpoints with path and query params, Validate request/response with Pydantic models, Implement JWT-based authentication\\nPrerequisites: ['CS-PY-101']\\nDuration: 6 hours\\n---\", name='search_courses', id='408decc6-ae0e-4135-bbbf-faf8fcea465a', tool_call_id='functions.search_courses:8'),\n",
+ " AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'functions.search_courses:9', 'function': {'arguments': '{\"category\":\"Backend\",\"level\":\"beginner\",\"query\":\"agile\"}', 'name': 'search_courses'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 32, 'prompt_tokens': 3177, 'total_tokens': 3209, 'completion_time': 0.084078365, 'completion_tokens_details': None, 'prompt_time': 0.042401339, 'prompt_tokens_details': {'cached_tokens': 2816}, 'queue_time': 0.286148566, 'total_time': 0.126479704}, 'model_name': 'moonshotai/kimi-k2-instruct-0905', 'system_fingerprint': 'fp_05df423bab', 'service_tier': 'on_demand', 'finish_reason': 'tool_calls', 'logprobs': None, 'model_provider': 'groq'}, id='lc_run--019d109d-63c2-7273-9284-2681a833e38e-0', tool_calls=[{'name': 'search_courses', 'args': {'category': 'Backend', 'level': 'beginner', 'query': 'agile'}, 'id': 'functions.search_courses:9', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 3177, 'output_tokens': 32, 'total_tokens': 3209, 'input_token_details': {'cache_read': 2816}}),\n",
+ " ToolMessage(content=\"ID: CS-FAST-101\\nTitle: None\\nDescription: Title: REST API Development with FastAPI. Description: Build production-ready REST APIs using FastAPI, covering routing, request validation with Pydantic, and basic authentication.. Outcomes: Create REST endpoints with path and query params, Validate request/response with Pydantic models, Implement JWT-based authentication\\nPrerequisites: ['CS-PY-101']\\nDuration: 6 hours\\n---\", name='search_courses', id='c3d56212-f645-4bc8-8d1a-ac0078b5c524', tool_call_id='functions.search_courses:9'),\n",
+ " AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'functions.submit_final_roadmap:10', 'function': {'arguments': '{\"candidate_name\":\"Candidate\",\"onboarding_summary\":\"7-month intern with solid Python/Django experience; lacks production-grade FastAPI, PostgreSQL, and containerization skills. Fresher adaptation needed.\",\"roadmap\":[{\"course_id\":\"CS-PY-101\",\"is_foundation\":true,\"reasoning\":\"Fresher adaptation: ensure rock-solid Python base before specialized frameworks.\",\"sequence_order\":1,\"title\":\"Python Programming Fundamentals\"},{\"course_id\":\"CS-DB-101\",\"is_foundation\":true,\"reasoning\":\"Mandatory foundation for PostgreSQL mastery; candidate only knows MySQL/SQLite.\",\"sequence_order\":2,\"title\":\"SQL Fundamentals for Backend Developers\"},{\"course_id\":\"CS-FAST-101\",\"is_foundation\":false,\"reasoning\":\"Closes the high-priority FastAPI gap; introduces Pydantic and async endpoints.\",\"sequence_order\":3,\"title\":\"REST API Development with FastAPI\"},{\"course_id\":\"CS-DB-201\",\"is_foundation\":false,\"reasoning\":\"Elevates PostgreSQL skills to production level: CTEs, indexes, query plans.\",\"sequence_order\":4,\"title\":\"Advanced SQL — Indexing, Transactions \\\\u0026 Query Optimization\"},{\"course_id\":\"CS-DOCKER-101\",\"is_foundation\":false,\"reasoning\":\"Adds required containerization skills; no prior Docker experience.\",\"sequence_order\":5,\"title\":\"Docker \\\\u0026 Containerization Fundamentals\"},{\"course_id\":\"CS-FAST-201\",\"is_foundation\":false,\"reasoning\":\"Consolidates expertise: async DB integration, OAuth2, dependency injection.\",\"sequence_order\":6,\"title\":\"Advanced API Design with FastAPI\"}],\"target_role\":\"Backend Developer\"}', 'name': 'submit_final_roadmap'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 409, 'prompt_tokens': 3318, 'total_tokens': 3727, 'completion_time': 1.154857715, 'completion_tokens_details': None, 'prompt_time': 0.03128425, 'prompt_tokens_details': {'cached_tokens': 3072}, 'queue_time': 0.286355948, 'total_time': 1.186141965}, 'model_name': 'moonshotai/kimi-k2-instruct-0905', 'system_fingerprint': 'fp_05df423bab', 'service_tier': 'on_demand', 'finish_reason': 'tool_calls', 'logprobs': None, 'model_provider': 'groq'}, id='lc_run--019d109d-76a8-78d0-ab6d-6b29b90f5cc4-0', tool_calls=[{'name': 'submit_final_roadmap', 'args': {'candidate_name': 'Candidate', 'onboarding_summary': '7-month intern with solid Python/Django experience; lacks production-grade FastAPI, PostgreSQL, and containerization skills. Fresher adaptation needed.', 'roadmap': [{'course_id': 'CS-PY-101', 'is_foundation': True, 'reasoning': 'Fresher adaptation: ensure rock-solid Python base before specialized frameworks.', 'sequence_order': 1, 'title': 'Python Programming Fundamentals'}, {'course_id': 'CS-DB-101', 'is_foundation': True, 'reasoning': 'Mandatory foundation for PostgreSQL mastery; candidate only knows MySQL/SQLite.', 'sequence_order': 2, 'title': 'SQL Fundamentals for Backend Developers'}, {'course_id': 'CS-FAST-101', 'is_foundation': False, 'reasoning': 'Closes the high-priority FastAPI gap; introduces Pydantic and async endpoints.', 'sequence_order': 3, 'title': 'REST API Development with FastAPI'}, {'course_id': 'CS-DB-201', 'is_foundation': False, 'reasoning': 'Elevates PostgreSQL skills to production level: CTEs, indexes, query plans.', 'sequence_order': 4, 'title': 'Advanced SQL — Indexing, Transactions & Query Optimization'}, {'course_id': 'CS-DOCKER-101', 'is_foundation': False, 'reasoning': 'Adds required containerization skills; no prior Docker experience.', 'sequence_order': 5, 'title': 'Docker & Containerization Fundamentals'}, {'course_id': 'CS-FAST-201', 'is_foundation': False, 'reasoning': 'Consolidates expertise: async DB integration, OAuth2, dependency injection.', 'sequence_order': 6, 'title': 'Advanced API Design with FastAPI'}], 'target_role': 'Backend Developer'}, 'id': 'functions.submit_final_roadmap:10', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 3318, 'output_tokens': 409, 'total_tokens': 3727, 'input_token_details': {'cache_read': 3072}}),\n",
+ " ToolMessage(content='{\"candidate_name\": \"Candidate\", \"target_role\": \"Backend Developer\", \"onboarding_summary\": \"7-month intern with solid Python/Django experience; lacks production-grade FastAPI, PostgreSQL, and containerization skills. Fresher adaptation needed.\", \"roadmap\": [{\"course_id\": \"CS-PY-101\", \"title\": \"Python Programming Fundamentals\", \"reasoning\": \"Fresher adaptation: ensure rock-solid Python base before specialized frameworks.\", \"is_foundation\": true, \"sequence_order\": 1}, {\"course_id\": \"CS-DB-101\", \"title\": \"SQL Fundamentals for Backend Developers\", \"reasoning\": \"Mandatory foundation for PostgreSQL mastery; candidate only knows MySQL/SQLite.\", \"is_foundation\": true, \"sequence_order\": 2}, {\"course_id\": \"CS-FAST-101\", \"title\": \"REST API Development with FastAPI\", \"reasoning\": \"Closes the high-priority FastAPI gap; introduces Pydantic and async endpoints.\", \"is_foundation\": false, \"sequence_order\": 3}, {\"course_id\": \"CS-DB-201\", \"title\": \"Advanced SQL — Indexing, Transactions & Query Optimization\", \"reasoning\": \"Elevates PostgreSQL skills to production level: CTEs, indexes, query plans.\", \"is_foundation\": false, \"sequence_order\": 4}, {\"course_id\": \"CS-DOCKER-101\", \"title\": \"Docker & Containerization Fundamentals\", \"reasoning\": \"Adds required containerization skills; no prior Docker experience.\", \"is_foundation\": false, \"sequence_order\": 5}, {\"course_id\": \"CS-FAST-201\", \"title\": \"Advanced API Design with FastAPI\", \"reasoning\": \"Consolidates expertise: async DB integration, OAuth2, dependency injection.\", \"is_foundation\": false, \"sequence_order\": 6}]}', name='submit_final_roadmap', id='d6485be4-0b1e-4ee8-8c4f-ed5d2caa7380', tool_call_id='functions.submit_final_roadmap:10'),\n",
+ " AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'functions.submit_mermaid_visualization:11', 'function': {'arguments': '{\"mermaid_code\":\"flowchart TD\\\\n A([Start — Current skills: Python/Django]):::start\\\\n subgraph W1[\\\\\"Week 1 — Foundations\\\\\"]\\\\n B[CS-PY-101\\\\nPython Programming Fundamentals]:::foundation\\\\n end\\\\n subgraph W2[\\\\\"Week 2 — Database base\\\\\"]\\\\n C[CS-DB-101\\\\nSQL Fundamentals]:::foundation\\\\n end\\\\n subgraph W3[\\\\\"Week 3 — FastAPI core\\\\\"]\\\\n D[CS-FAST-101\\\\nREST API Development with FastAPI]:::gap\\\\n end\\\\n subgraph W4[\\\\\"Week 4 — PostgreSQL deep dive\\\\\"]\\\\n E[CS-DB-201\\\\nAdvanced SQL — Indexing \\\\u0026 Optimization]:::gap\\\\n end\\\\n subgraph W5[\\\\\"Week 5 — Containerization\\\\\"]\\\\n F[CS-DOCKER-101\\\\nDocker \\\\u0026 Containerization Fundamentals]:::gap\\\\n end\\\\n subgraph W6[\\\\\"Week 6 — Mastery\\\\\"]\\\\n G[CS-FAST-201\\\\nAdvanced API Design with FastAPI]:::gap\\\\n end\\\\n Z([Role-ready — Backend Developer]):::done\\\\n A --\\\\u003e B\\\\n B --\\\\u003e C\\\\n C --\\\\u003e D\\\\n D --\\\\u003e E\\\\n E --\\\\u003e F\\\\n F --\\\\u003e G\\\\n G --\\\\u003e Z\\\\n classDef foundation fill:#E1F5EE,stroke:#0F6E56,color:#085041\\\\n classDef gap fill:#EEEDFE,stroke:#534AB7,color:#26215C\\\\n classDef start fill:#1D9E75,stroke:#0F6E56,color:#E1F5EE\\\\n classDef done fill:#534AB7,stroke:#3C3489,color:#EEEDFE\"}', 'name': 'submit_mermaid_visualization'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 398, 'prompt_tokens': 4153, 'total_tokens': 4551, 'completion_time': 0.965973514, 'completion_tokens_details': None, 'prompt_time': 0.211512326, 'prompt_tokens_details': {'cached_tokens': 2816}, 'queue_time': 0.274676649, 'total_time': 1.1774858400000001}, 'model_name': 'moonshotai/kimi-k2-instruct-0905', 'system_fingerprint': 'fp_241bc7119c', 'service_tier': 'on_demand', 'finish_reason': 'tool_calls', 'logprobs': None, 'model_provider': 'groq'}, id='lc_run--019d109d-8491-7d20-9131-a43cdf0b8dd2-0', tool_calls=[{'name': 'submit_mermaid_visualization', 'args': {'mermaid_code': 'flowchart TD\\n A([Start — Current skills: Python/Django]):::start\\n subgraph W1[\"Week 1 — Foundations\"]\\n B[CS-PY-101\\nPython Programming Fundamentals]:::foundation\\n end\\n subgraph W2[\"Week 2 — Database base\"]\\n C[CS-DB-101\\nSQL Fundamentals]:::foundation\\n end\\n subgraph W3[\"Week 3 — FastAPI core\"]\\n D[CS-FAST-101\\nREST API Development with FastAPI]:::gap\\n end\\n subgraph W4[\"Week 4 — PostgreSQL deep dive\"]\\n E[CS-DB-201\\nAdvanced SQL — Indexing & Optimization]:::gap\\n end\\n subgraph W5[\"Week 5 — Containerization\"]\\n F[CS-DOCKER-101\\nDocker & Containerization Fundamentals]:::gap\\n end\\n subgraph W6[\"Week 6 — Mastery\"]\\n G[CS-FAST-201\\nAdvanced API Design with FastAPI]:::gap\\n end\\n Z([Role-ready — Backend Developer]):::done\\n A --> B\\n B --> C\\n C --> D\\n D --> E\\n E --> F\\n F --> G\\n G --> Z\\n classDef foundation fill:#E1F5EE,stroke:#0F6E56,color:#085041\\n classDef gap fill:#EEEDFE,stroke:#534AB7,color:#26215C\\n classDef start fill:#1D9E75,stroke:#0F6E56,color:#E1F5EE\\n classDef done fill:#534AB7,stroke:#3C3489,color:#EEEDFE'}, 'id': 'functions.submit_mermaid_visualization:11', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 4153, 'output_tokens': 398, 'total_tokens': 4551, 'input_token_details': {'cache_read': 2816}}),\n",
+ " ToolMessage(content='Mermaid visualization stored successfully.', name='submit_mermaid_visualization', id='12ce8f2b-6176-4099-b15d-720f6c73f904', tool_call_id='functions.submit_mermaid_visualization:11'),\n",
+ " AIMessage(content='', additional_kwargs={}, response_metadata={'token_usage': {'completion_tokens': 1, 'prompt_tokens': 4576, 'total_tokens': 4577, 'completion_time': 0.005792865, 'completion_tokens_details': None, 'prompt_time': 0.156326975, 'prompt_tokens_details': {'cached_tokens': 2304}, 'queue_time': None, 'total_time': 0.16211984}, 'model_name': 'moonshotai/kimi-k2-instruct-0905', 'system_fingerprint': 'fp_00c37775b7', 'service_tier': 'on_demand', 'finish_reason': 'stop', 'logprobs': None, 'model_provider': 'groq'}, id='lc_run--019d109d-aa2b-7521-9386-6b53dc792f85-0', tool_calls=[], invalid_tool_calls=[], usage_metadata={'input_tokens': 4576, 'output_tokens': 1, 'total_tokens': 4577, 'input_token_details': {'cache_read': 2304}})],\n",
+ " 'skill_gap_analysis_data': SkillGapAnalysis(job_title='Backend Developer', candidate_name=None, analyzed_gaps=[SkillGap(skill_name='FastAPI', gap_type='missing_foundation', priority='high', reasoning='JD explicitly requires FastAPI; candidate lists Flask and Django but has no record of FastAPI usage in internships or projects.', target_competency='Build async REST endpoints with FastAPI, Pydantic models, and automatic OpenAPI docs'), SkillGap(skill_name='PostgreSQL', gap_type='missing_foundation', priority='high', reasoning='JD mandates PostgreSQL; candidate only shows MySQL, SQLite, and MongoDB experience, with no PostgreSQL exposure.', target_competency='Design relational schemas, write optimized queries, and use advanced PostgreSQL features (JSONB, CTEs, indexes)'), SkillGap(skill_name='Docker', gap_type='missing_foundation', priority='medium', reasoning='JD lists Docker as required; candidate’s tech stack contains no containerization tooling or deployment experience.', target_competency='Containerize FastAPI services, manage multi-stage builds, and orchestrate with docker-compose')], is_fresher_adaptation_needed=True, executive_summary='Candidate has 7 months of internship experience and solid Python/Django exposure, but lacks production-grade FastAPI, PostgreSQL, and containerization skills required for the backend role.'),\n",
+ " 'resume_data': ResumeExtract(job_title='Software Developer', total_experience_months=7, skills=[Skill(name='C/C++', category='Other'), Skill(name='Java', category='Other'), Skill(name='Python', category='Backend'), Skill(name='JavaScript', category='Frontend'), Skill(name='Dart', category='Frontend'), Skill(name='Flutter', category='Frontend'), Skill(name='HTML/CSS', category='Frontend'), Skill(name='ReactJS', category='Frontend'), Skill(name='Firebase', category='Backend'), Skill(name='REST APIs', category='Backend'), Skill(name='Flask', category='Backend'), Skill(name='Django', category='Backend'), Skill(name='NodeJS', category='Backend'), Skill(name='ExpressJS', category='Backend'), Skill(name='MySQL', category='Other'), Skill(name='MongoDB', category='Other'), Skill(name='SQLite', category='Other'), Skill(name='GitHub', category='Other'), Skill(name='Android Studio', category='Other'), Skill(name='Visual Studio Code', category='Other'), Skill(name='MATLAB', category='Other'), Skill(name='Cursor', category='Other'), Skill(name='Github Copilot', category='Other'), Skill(name='Provider', category='Other'), Skill(name='Bloc', category='Other'), Skill(name='Scikit-learn', category='ML'), Skill(name='tensorflow', category='ML')], experience=[ExperienceItem(job_title='Technology Intern', experience_type='internship', duration_months=5, technologies=['Flask', 'PyMuPDF', 'Docling', 'OCR', 'LLM', 'Cron'], responsibilities=['Built Flask API for RTA investor verification with OCR extraction', 'Developed scalable document intelligence pipeline', 'Implementing Cron-based background workers for weekly analytics reports']), ExperienceItem(job_title='Software Development Intern', experience_type='internship', duration_months=2, technologies=['Flutter', 'Dart', 'MVVM', 'SOLID'], responsibilities=['Developed Groceazy grocery app with 10+ modules', 'Implemented secure user authentication and role-based access', 'Built image-to-item price matcher using APIs'])], projects=[ProjectItem(name='Hostel Bites – IIITK Canteen App', technologies=['Flutter', 'Dart', 'Firebase', 'Cloud Firestore', 'Mapbox API'], what_was_built='Flutter app for hostel food ordering with live tracking'), ProjectItem(name='JobShield - Fake Job Detector', technologies=['Flutter', 'Dart', 'Python', 'Flask', 'SQLite'], what_was_built='Fake job detection API with 95% accuracy and Flutter web interface')], certifications=[CertificationItem(name='Certification in Graph Algorithms', issuer='AlgoUniversity', topics_covered=['Graph Algorithms'])], achievements=[AchievementItem(title='Ranked 81 in Inter IIIT Coding Contest Optigo', domain='Competitive Programming'), AchievementItem(title='4-star CodeChef rating and Knight badge on LeetCode', domain='Competitive Programming'), AchievementItem(title='Advanced to regionals in SIH’23 with e-waste facility locator app', domain='Hackathon')], is_fresher=False),\n",
+ " 'extraction_error': None,\n",
+ " 'JobDescriptionExtract_data': JobDescriptionExtract(job_title='Backend Developer', company_name='CodeForge', location='Pune', employment_type='Full-time role', duration_months=None, is_fresher_allowed=None, skills_required=[SkillRequirement(name='Python', level=None), SkillRequirement(name='FastAPI', level=None), SkillRequirement(name='Django', level=None), SkillRequirement(name='SQL', level=None), SkillRequirement(name='PostgreSQL', level=None), SkillRequirement(name='Docker', level=None)], tools_technologies=None, responsibilities=[ResponsibilityItem(description='Develop REST APIs using FastAPI'), ResponsibilityItem(description='Design and manage PostgreSQL databases'), ResponsibilityItem(description='Implement authentication and authorization systems'), ResponsibilityItem(description='Optimize performance and scalability')], requirements=[RequirementItem(description='Strong knowledge of Python'), RequirementItem(description='Experience with FastAPI or Django'), RequirementItem(description='Good understanding of SQL and database design'), RequirementItem(description='Familiarity with Docker')], constraints=[ConstraintItem(type='location', value='Pune only'), ConstraintItem(type='employment type', value='Full-time role')]),\n",
+ " 'mermaid_code': 'flowchart TD\\n A([Start — Current skills: Python/Django]):::start\\n subgraph W1[\"Week 1 — Foundations\"]\\n B[CS-PY-101\\nPython Programming Fundamentals]:::foundation\\n end\\n subgraph W2[\"Week 2 — Database base\"]\\n C[CS-DB-101\\nSQL Fundamentals]:::foundation\\n end\\n subgraph W3[\"Week 3 — FastAPI core\"]\\n D[CS-FAST-101\\nREST API Development with FastAPI]:::gap\\n end\\n subgraph W4[\"Week 4 — PostgreSQL deep dive\"]\\n E[CS-DB-201\\nAdvanced SQL — Indexing & Optimization]:::gap\\n end\\n subgraph W5[\"Week 5 — Containerization\"]\\n F[CS-DOCKER-101\\nDocker & Containerization Fundamentals]:::gap\\n end\\n subgraph W6[\"Week 6 — Mastery\"]\\n G[CS-FAST-201\\nAdvanced API Design with FastAPI]:::gap\\n end\\n Z([Role-ready — Backend Developer]):::done\\n A --> B\\n B --> C\\n C --> D\\n D --> E\\n E --> F\\n F --> G\\n G --> Z\\n classDef foundation fill:#E1F5EE,stroke:#0F6E56,color:#085041\\n classDef gap fill:#EEEDFE,stroke:#534AB7,color:#26215C\\n classDef start fill:#1D9E75,stroke:#0F6E56,color:#E1F5EE\\n classDef done fill:#534AB7,stroke:#3C3489,color:#EEEDFE',\n",
+ " 'final_roadmap': {'candidate_name': 'Candidate',\n",
+ " 'onboarding_summary': '7-month intern with solid Python/Django experience; lacks production-grade FastAPI, PostgreSQL, and containerization skills. Fresher adaptation needed.',\n",
+ " 'roadmap': [{'course_id': 'CS-PY-101',\n",
+ " 'is_foundation': True,\n",
+ " 'reasoning': 'Fresher adaptation: ensure rock-solid Python base before specialized frameworks.',\n",
+ " 'sequence_order': 1,\n",
+ " 'title': 'Python Programming Fundamentals'},\n",
+ " {'course_id': 'CS-DB-101',\n",
+ " 'is_foundation': True,\n",
+ " 'reasoning': 'Mandatory foundation for PostgreSQL mastery; candidate only knows MySQL/SQLite.',\n",
+ " 'sequence_order': 2,\n",
+ " 'title': 'SQL Fundamentals for Backend Developers'},\n",
+ " {'course_id': 'CS-FAST-101',\n",
+ " 'is_foundation': False,\n",
+ " 'reasoning': 'Closes the high-priority FastAPI gap; introduces Pydantic and async endpoints.',\n",
+ " 'sequence_order': 3,\n",
+ " 'title': 'REST API Development with FastAPI'},\n",
+ " {'course_id': 'CS-DB-201',\n",
+ " 'is_foundation': False,\n",
+ " 'reasoning': 'Elevates PostgreSQL skills to production level: CTEs, indexes, query plans.',\n",
+ " 'sequence_order': 4,\n",
+ " 'title': 'Advanced SQL — Indexing, Transactions & Query Optimization'},\n",
+ " {'course_id': 'CS-DOCKER-101',\n",
+ " 'is_foundation': False,\n",
+ " 'reasoning': 'Adds required containerization skills; no prior Docker experience.',\n",
+ " 'sequence_order': 5,\n",
+ " 'title': 'Docker & Containerization Fundamentals'},\n",
+ " {'course_id': 'CS-FAST-201',\n",
+ " 'is_foundation': False,\n",
+ " 'reasoning': 'Consolidates expertise: async DB integration, OAuth2, dependency injection.',\n",
+ " 'sequence_order': 6,\n",
+ " 'title': 'Advanced API Design with FastAPI'}],\n",
+ " 'target_role': 'Backend Developer'}}"
+ ]
+ },
+ "execution_count": 204,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "final_state"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "050d8619",
+ "metadata": {},
+ "source": [
+ "**Final state**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e451edbe",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 217,
+ "id": "40a999a5",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "skill_gap_data = final_state['final_roadmap']"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 218,
+ "id": "13d64ecc",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'candidate_name': 'Candidate',\n",
+ " 'onboarding_summary': '7-month intern with solid Python/Django experience; lacks production-grade FastAPI, PostgreSQL, and containerization skills. Fresher adaptation needed.',\n",
+ " 'roadmap': [{'course_id': 'CS-PY-101',\n",
+ " 'is_foundation': True,\n",
+ " 'reasoning': 'Fresher adaptation: ensure rock-solid Python base before specialized frameworks.',\n",
+ " 'sequence_order': 1,\n",
+ " 'title': 'Python Programming Fundamentals'},\n",
+ " {'course_id': 'CS-DB-101',\n",
+ " 'is_foundation': True,\n",
+ " 'reasoning': 'Mandatory foundation for PostgreSQL mastery; candidate only knows MySQL/SQLite.',\n",
+ " 'sequence_order': 2,\n",
+ " 'title': 'SQL Fundamentals for Backend Developers'},\n",
+ " {'course_id': 'CS-FAST-101',\n",
+ " 'is_foundation': False,\n",
+ " 'reasoning': 'Closes the high-priority FastAPI gap; introduces Pydantic and async endpoints.',\n",
+ " 'sequence_order': 3,\n",
+ " 'title': 'REST API Development with FastAPI'},\n",
+ " {'course_id': 'CS-DB-201',\n",
+ " 'is_foundation': False,\n",
+ " 'reasoning': 'Elevates PostgreSQL skills to production level: CTEs, indexes, query plans.',\n",
+ " 'sequence_order': 4,\n",
+ " 'title': 'Advanced SQL — Indexing, Transactions & Query Optimization'},\n",
+ " {'course_id': 'CS-DOCKER-101',\n",
+ " 'is_foundation': False,\n",
+ " 'reasoning': 'Adds required containerization skills; no prior Docker experience.',\n",
+ " 'sequence_order': 5,\n",
+ " 'title': 'Docker & Containerization Fundamentals'},\n",
+ " {'course_id': 'CS-FAST-201',\n",
+ " 'is_foundation': False,\n",
+ " 'reasoning': 'Consolidates expertise: async DB integration, OAuth2, dependency injection.',\n",
+ " 'sequence_order': 6,\n",
+ " 'title': 'Advanced API Design with FastAPI'}],\n",
+ " 'target_role': 'Backend Developer'}"
+ ]
+ },
+ "execution_count": 218,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "skill_gap_data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 228,
+ "id": "25a6b5b4",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import json\n",
+ "\n",
+ "# Define the keys your React frontend actually needs\n",
+ "REQUIRED_KEYS = [\"candidate_name\", \"skill_gap_analysis_data\", \"mermaid_code\", \"final_roadmap\"]\n",
+ "\n",
+ "def export_ui_payload(state, filename=\"ai_output.json\"):\n",
+ " \"\"\"\n",
+ " Extracts specific keys from the graph state and ensures \n",
+ " Pydantic objects are dumped to dicts for JSON compatibility.\n",
+ " \"\"\"\n",
+ " ui_data = {}\n",
+ "\n",
+ " for key in REQUIRED_KEYS:\n",
+ " # Get the value from the state\n",
+ " val = state.get(key)\n",
+ " \n",
+ " if val is None:\n",
+ " continue\n",
+ "\n",
+ " # Check if the value is a Pydantic object (has .model_dump())\n",
+ " # This fixes the \"skill_gap_analysis_data as a string\" issue\n",
+ " if hasattr(val, \"model_dump\"):\n",
+ " ui_data[key] = val.model_dump()\n",
+ " else:\n",
+ " # If it's already a dict (final_roadmap) or string (mermaid_code)\n",
+ " ui_data[key] = val\n",
+ "\n",
+ " # Save to the local file\n",
+ " with open(filename, \"w\", encoding=\"utf-8\") as f:\n",
+ " json.dump(ui_data, f, indent=2)\n",
+ " \n",
+ " print(f\"✅ UI Payload successfully exported to {filename}\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 229,
+ "id": "26c10157",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "✅ UI Payload successfully exported to ai_output.json\n"
+ ]
+ }
+ ],
+ "source": [
+ "export_ui_payload(final_state)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 215,
+ "id": "bae943d7",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "with open(\"skill_gap_output.json\", \"w\") as f:\n",
+ " json.dump(skill_gap_data.model_dump(), f, indent=2)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 224,
+ "id": "9b464835",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "skill_gap_data=final_state['skill_gap_analysis_data']"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 225,
+ "id": "79375428",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "skill_gap_data=skill_gap_data.model_dump_json(indent=1)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 226,
+ "id": "f9db0000",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "('{\\n'\n",
+ " ' \"job_title\": \"Backend Developer\",\\n'\n",
+ " ' \"candidate_name\": null,\\n'\n",
+ " ' \"analyzed_gaps\": [\\n'\n",
+ " ' {\\n'\n",
+ " ' \"skill_name\": \"FastAPI\",\\n'\n",
+ " ' \"gap_type\": \"missing_foundation\",\\n'\n",
+ " ' \"priority\": \"high\",\\n'\n",
+ " ' \"reasoning\": \"JD explicitly requires FastAPI; candidate lists Flask and '\n",
+ " 'Django but has no record of FastAPI usage in internships or projects.\",\\n'\n",
+ " ' \"target_competency\": \"Build async REST endpoints with FastAPI, Pydantic '\n",
+ " 'models, and automatic OpenAPI docs\"\\n'\n",
+ " ' },\\n'\n",
+ " ' {\\n'\n",
+ " ' \"skill_name\": \"PostgreSQL\",\\n'\n",
+ " ' \"gap_type\": \"missing_foundation\",\\n'\n",
+ " ' \"priority\": \"high\",\\n'\n",
+ " ' \"reasoning\": \"JD mandates PostgreSQL; candidate only shows MySQL, SQLite, '\n",
+ " 'and MongoDB experience, with no PostgreSQL exposure.\",\\n'\n",
+ " ' \"target_competency\": \"Design relational schemas, write optimized queries, '\n",
+ " 'and use advanced PostgreSQL features (JSONB, CTEs, indexes)\"\\n'\n",
+ " ' },\\n'\n",
+ " ' {\\n'\n",
+ " ' \"skill_name\": \"Docker\",\\n'\n",
+ " ' \"gap_type\": \"missing_foundation\",\\n'\n",
+ " ' \"priority\": \"medium\",\\n'\n",
+ " ' \"reasoning\": \"JD lists Docker as required; candidate’s tech stack '\n",
+ " 'contains no containerization tooling or deployment experience.\",\\n'\n",
+ " ' \"target_competency\": \"Containerize FastAPI services, manage multi-stage '\n",
+ " 'builds, and orchestrate with docker-compose\"\\n'\n",
+ " ' }\\n'\n",
+ " ' ],\\n'\n",
+ " ' \"is_fresher_adaptation_needed\": true,\\n'\n",
+ " ' \"executive_summary\": \"Candidate has 7 months of internship experience and '\n",
+ " 'solid Python/Django exposure, but lacks production-grade FastAPI, '\n",
+ " 'PostgreSQL, and containerization skills required for the backend role.\"\\n'\n",
+ " '}')\n"
+ ]
+ }
+ ],
+ "source": [
+ "pprint(skill_gap_data)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "bbb0dac2",
+ "metadata": {},
+ "source": [
+ "**Evaluation**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8be93713",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "pro_env",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.10"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}