{ "cells": [ { "cell_type": "code", "execution_count": 134, "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", "from langgraph.checkpoint.memory import MemorySaver\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": 42, "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": 43, "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": 44, "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": 45, "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": 158, "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": 163, "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": 164, "id": "0d5e3b17", "metadata": {}, "outputs": [], "source": [ "gap_analysis_agent=ChatGroq(\n", " model=\"openai/gpt-oss-120b\",\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": 165, "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": 50, "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 20:13:46 GMT',\n", " 'grpc-status': '0',\n", " 'server': 'envoy',\n", " 'x-envoy-upstream-service-time': '67',\n", " 'x-pinecone-request-latency-ms': '66',\n", " 'x-pinecone-response-duration-ms': '69'}},\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": 51, "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": 52, "id": "f0845a99", "metadata": {}, "outputs": [], "source": [ "device=torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n" ] }, { "cell_type": "code", "execution_count": 53, "id": "c8e6d2a5", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "dcedae00e39246819acfab4728d68e9c", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Loading weights: 0%| | 0/103 [00:00 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": 106, "id": "09d238ab", "metadata": {}, "outputs": [], "source": [ "\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", " \n", " \n", " # Return to LangGraph (will be stored in state via a post-processing node)\n", " return result" ] }, { "cell_type": "code", "execution_count": 107, "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": 108, "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": 109, "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": 110, "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": 111, "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": 112, "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": 113, "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": 114, "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": 115, "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": 166, "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": 167, "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": 168, "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": 169, "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": 170, "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": 171, "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": 154, "id": "ba9f22e1", "metadata": {}, "outputs": [], "source": [ "tool_node = ToolNode(roadmap_planner_agent_tools)" ] }, { "cell_type": "code", "execution_count": 172, "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,\n", " {\n", " \"tools\": \"tools\", # If tool_calls exist, go to tools\n", " \"__end__\": \"finalize_state\" # If finished, go to finalize_state\n", " }\n", ")\n", "\n", "# 2. Loop back to agent after tools\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": 139, "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": 173, "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": 177, "id": "da3df5a4", "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=\"hook_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": 182, "id": "4577f33b", "metadata": {}, "outputs": [], "source": [ "from langchain_core.callbacks import BaseCallbackHandler\n", "\n", "class ExportHook(BaseCallbackHandler):\n", " def __init__(self, filename=\"k_output.json\"):\n", " self.filename = filename\n", "\n", " def on_chain_end(self, outputs: dict, *, run_id, parent_run_id=None, **kwargs):\n", " \"\"\"\n", " parent_run_id is None ONLY for the top-level graph execution.\n", " This ensures it runs exactly once when the entire graph finishes.\n", " \"\"\"\n", " # 1. Only run for the root graph (no parent)\n", " if parent_run_id is not None:\n", " return\n", "\n", " # 2. Safety check: ensure 'outputs' is actually a dictionary\n", " if not isinstance(outputs, dict):\n", " return\n", "\n", " print(f\"📦 Exporting final graph payload...\")\n", " export_ui_payload(outputs, filename=self.filename)\n", "\n", "# Usage remains the same\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "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", "checkpointer = MemorySaver() \n", "graph = builder.compile(checkpointer=checkpointer)\n", "\n", "THREAD_ID = str(\"sex-thread-id\")\n", "\n", "config = {\"configurable\": {\"thread_id\": THREAD_ID,\"langgraph_user_id\": \"Chirayu Jain\"}, \"callbacks\": [ExportHook]}\n", "\n", "final_state = graph.invoke(initial_input, config=config)\n", "\n" ] }, { "cell_type": "code", "execution_count": 127, "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': 1640, 'total_tokens': 1672, 'completion_time': 0.079339676, 'completion_tokens_details': None, 'prompt_time': 0.038547803, 'prompt_tokens_details': {'cached_tokens': 1280}, 'queue_time': 0.281301725, 'total_time': 0.117887479}, '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--019d1214-3656-7230-9aa7-a78c866d9fd4-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': 1640, 'output_tokens': 32, 'total_tokens': 1672, '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='3b97803f-8036-43b8-a31d-2f1b50d72c14', 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': 1781, 'total_tokens': 1813, 'completion_time': 0.078526168, 'completion_tokens_details': None, 'prompt_time': 0.050791493, 'prompt_tokens_details': {'cached_tokens': 1280}, 'queue_time': 0.287734077, 'total_time': 0.129317661}, '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--019d1214-3fa2-76b0-9dc8-1d2c0b01ff61-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': 1781, 'output_tokens': 32, 'total_tokens': 1813, 'input_token_details': {'cache_read': 1280}}),\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='64af5ac2-b743-4c32-a73b-4fd8dc35a130', 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': 1914, 'total_tokens': 1946, 'completion_time': 0.081289553, 'completion_tokens_details': None, 'prompt_time': 0.038772376, 'prompt_tokens_details': {'cached_tokens': 1792}, 'queue_time': 0.287688574, 'total_time': 0.120061929}, '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--019d1214-4326-7e41-a4b4-a509a4c5dd26-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': 1914, 'output_tokens': 32, 'total_tokens': 1946, 'input_token_details': {'cache_read': 1792}}),\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='39673178-0b41-453c-80ea-5d360a741d29', 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': 2133, 'total_tokens': 2157, 'completion_time': 0.067035318, 'completion_tokens_details': None, 'prompt_time': 0.101946396, 'prompt_tokens_details': {'cached_tokens': 1536}, 'queue_time': 0.280975424, 'total_time': 0.168981714}, '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--019d1214-46a1-7213-82bc-083edbd92ffb-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': 2133, 'output_tokens': 24, 'total_tokens': 2157, 'input_token_details': {'cache_read': 1536}}),\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='c811ad56-358d-4556-a77f-def55fa2c7eb', 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\":\"beginner\",\"query\":\"professional skills\"}', 'name': 'search_courses'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 32, 'prompt_tokens': 2339, 'total_tokens': 2371, 'completion_time': 0.095311384, 'completion_tokens_details': None, 'prompt_time': 0.071666636, 'prompt_tokens_details': {'cached_tokens': 1792}, 'queue_time': 0.287408461, 'total_time': 0.16697802}, '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--019d1214-4896-79b0-994f-43c80e89e639-0', tool_calls=[{'name': 'search_courses', 'args': {'category': 'Backend', 'level': 'beginner', 'query': 'professional skills'}, 'id': 'functions.search_courses:4', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 2339, 'output_tokens': 32, 'total_tokens': 2371, '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='021c4097-6b82-4a44-8694-9c6610baa1ec', tool_call_id='functions.search_courses:4'),\n", " AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'functions.search_courses:5', 'function': {'arguments': '{\"category\":\"Backend\",\"level\":\"beginner\",\"query\":\"agile professional skills\"}', 'name': 'search_courses'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 34, 'prompt_tokens': 2480, 'total_tokens': 2514, 'completion_time': 0.093498688, 'completion_tokens_details': None, 'prompt_time': 0.028057542, 'prompt_tokens_details': {'cached_tokens': 2304}, 'queue_time': 0.284874636, 'total_time': 0.12155623}, '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--019d1214-4bd5-7262-bb3f-898f9b25b128-0', tool_calls=[{'name': 'search_courses', 'args': {'category': 'Backend', 'level': 'beginner', 'query': 'agile professional skills'}, 'id': 'functions.search_courses:5', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 2480, 'output_tokens': 34, 'total_tokens': 2514, 'input_token_details': {'cache_read': 2304}}),\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='48de42c6-381f-4dd2-a6ce-6d820c64975a', tool_call_id='functions.search_courses:5'),\n", " AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'functions.search_courses:6', 'function': {'arguments': '{\"category\":\"Backend\",\"level\":\"beginner\",\"query\":\"soft skills\"}', 'name': 'search_courses'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 32, 'prompt_tokens': 2623, 'total_tokens': 2655, 'completion_time': 0.092443261, 'completion_tokens_details': None, 'prompt_time': 0.07918841, 'prompt_tokens_details': {'cached_tokens': 2304}, 'queue_time': 0.284959911, 'total_time': 0.171631671}, '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--019d1214-4f15-7942-9c7e-9a48b17e1d04-0', tool_calls=[{'name': 'search_courses', 'args': {'category': 'Backend', 'level': 'beginner', 'query': 'soft skills'}, 'id': 'functions.search_courses:6', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 2623, 'output_tokens': 32, 'total_tokens': 2655, 'input_token_details': {'cache_read': 2304}}),\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='e5585d48-0791-4a21-8e87-42f0bde91e2f', 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': 2764, 'total_tokens': 2799, 'completion_time': 0.102256011, 'completion_tokens_details': None, 'prompt_time': 0.153939367, 'prompt_tokens_details': {'cached_tokens': 2048}, 'queue_time': 0.282748693, 'total_time': 0.256195378}, '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--019d1214-52a0-7a52-9cbf-57ddf6335d6d-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': 2764, 'output_tokens': 35, 'total_tokens': 2799, 'input_token_details': {'cache_read': 2048}}),\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='75dc79e6-2341-4884-aa47-6ee66f1655ce', 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\"}', 'name': 'search_courses'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 31, 'prompt_tokens': 2908, 'total_tokens': 2939, 'completion_time': 0.091088768, 'completion_tokens_details': None, 'prompt_time': 0.170027002, 'prompt_tokens_details': {'cached_tokens': 1280}, 'queue_time': 0.281245115, 'total_time': 0.26111577}, '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--019d1214-5637-7533-8d4d-73bd8a9e4a8d-0', tool_calls=[{'name': 'search_courses', 'args': {'category': 'Backend', 'level': 'beginner', 'query': 'professional'}, 'id': 'functions.search_courses:8', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 2908, 'output_tokens': 31, 'total_tokens': 2939, '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='0013afed-d715-4e7c-bfaf-79f242fcb16b', tool_call_id='functions.search_courses:8'),\n", " AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'functions.submit_final_roadmap:9', 'function': {'arguments': '{\"candidate_name\":\"Candidate\",\"onboarding_summary\":\"The candidate has solid Python/Django experience but lacks exposure to FastAPI, PostgreSQL, and Docker—core requirements for this backend role. As a fresher, foundational training is prioritized to build missing competencies quickly and effectively.\",\"roadmap\":[{\"course_id\":\"CS-PY-101\",\"is_foundation\":true,\"reasoning\":\"Prerequisite for FastAPI course; ensures strong Python foundation before API development.\",\"sequence_order\":1,\"title\":\"Python Programming Fundamentals\"},{\"course_id\":\"CS-FAST-101\",\"is_foundation\":true,\"reasoning\":\"High-priority gap: JD requires FastAPI; candidate has no prior experience with this framework.\",\"sequence_order\":2,\"title\":\"REST API Development with FastAPI\"},{\"course_id\":\"CS-DB-101\",\"is_foundation\":true,\"reasoning\":\"High-priority gap: JD requires PostgreSQL; candidate only has MySQL/SQLite experience.\",\"sequence_order\":3,\"title\":\"SQL Fundamentals for Backend Developers\"},{\"course_id\":\"CS-DOCKER-101\",\"is_foundation\":false,\"reasoning\":\"Medium-priority gap: JD requires Docker; candidate has no containerization experience.\",\"sequence_order\":4,\"title\":\"Docker \\\\u0026 Containerization Fundamentals\"}],\"target_role\":\"Backend Developer\"}', 'name': 'submit_final_roadmap'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 317, 'prompt_tokens': 3048, 'total_tokens': 3365, 'completion_time': 0.865973233, 'completion_tokens_details': None, 'prompt_time': 0.098265353, 'prompt_tokens_details': {'cached_tokens': 2560}, 'queue_time': 0.280870105, 'total_time': 0.964238586}, '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--019d1214-59d4-76e1-98c6-61e42b9047b9-0', tool_calls=[{'name': 'submit_final_roadmap', 'args': {'candidate_name': 'Candidate', 'onboarding_summary': 'The candidate has solid Python/Django experience but lacks exposure to FastAPI, PostgreSQL, and Docker—core requirements for this backend role. As a fresher, foundational training is prioritized to build missing competencies quickly and effectively.', 'roadmap': [{'course_id': 'CS-PY-101', 'is_foundation': True, 'reasoning': 'Prerequisite for FastAPI course; ensures strong Python foundation before API development.', 'sequence_order': 1, 'title': 'Python Programming Fundamentals'}, {'course_id': 'CS-FAST-101', 'is_foundation': True, 'reasoning': 'High-priority gap: JD requires FastAPI; candidate has no prior experience with this framework.', 'sequence_order': 2, 'title': 'REST API Development with FastAPI'}, {'course_id': 'CS-DB-101', 'is_foundation': True, 'reasoning': 'High-priority gap: JD requires PostgreSQL; candidate only has MySQL/SQLite experience.', 'sequence_order': 3, 'title': 'SQL Fundamentals for Backend Developers'}, {'course_id': 'CS-DOCKER-101', 'is_foundation': False, 'reasoning': 'Medium-priority gap: JD requires Docker; candidate has no containerization experience.', 'sequence_order': 4, 'title': 'Docker & Containerization Fundamentals'}], 'target_role': 'Backend Developer'}, 'id': 'functions.submit_final_roadmap:9', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 3048, 'output_tokens': 317, 'total_tokens': 3365, 'input_token_details': {'cache_read': 2560}}),\n", " ToolMessage(content='{\"candidate_name\": \"Candidate\", \"target_role\": \"Backend Developer\", \"onboarding_summary\": \"The candidate has solid Python/Django experience but lacks exposure to FastAPI, PostgreSQL, and Docker—core requirements for this backend role. As a fresher, foundational training is prioritized to build missing competencies quickly and effectively.\", \"roadmap\": [{\"course_id\": \"CS-PY-101\", \"title\": \"Python Programming Fundamentals\", \"reasoning\": \"Prerequisite for FastAPI course; ensures strong Python foundation before API development.\", \"is_foundation\": true, \"sequence_order\": 1}, {\"course_id\": \"CS-FAST-101\", \"title\": \"REST API Development with FastAPI\", \"reasoning\": \"High-priority gap: JD requires FastAPI; candidate has no prior experience with this framework.\", \"is_foundation\": true, \"sequence_order\": 2}, {\"course_id\": \"CS-DB-101\", \"title\": \"SQL Fundamentals for Backend Developers\", \"reasoning\": \"High-priority gap: JD requires PostgreSQL; candidate only has MySQL/SQLite experience.\", \"is_foundation\": true, \"sequence_order\": 3}, {\"course_id\": \"CS-DOCKER-101\", \"title\": \"Docker & Containerization Fundamentals\", \"reasoning\": \"Medium-priority gap: JD requires Docker; candidate has no containerization experience.\", \"is_foundation\": false, \"sequence_order\": 4}]}', name='submit_final_roadmap', id='27e25caf-4dc9-480c-abcd-245d7dd322a1', tool_call_id='functions.submit_final_roadmap:9'),\n", " AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'functions.submit_mermaid_visualization:10', 'function': {'arguments': '{\"mermaid_code\":\"flowchart TD\\\\n A([Start — Candidate\\'s current skills]):::start\\\\n subgraph W1[\\\\\"Week 1 — Core foundations\\\\\"]\\\\n B[CS-PY-101\\\\nPython Programming Fundamentals]:::foundation\\\\n C[CS-FAST-101\\\\nREST API Development with FastAPI]:::gap\\\\n end\\\\n subgraph W2[\\\\\"Week 2 — Data \\\\u0026 deployment\\\\\"]\\\\n D[CS-DB-101\\\\nSQL Fundamentals for Backend Developers]:::gap\\\\n E[CS-DOCKER-101\\\\nDocker \\\\u0026 Containerization Fundamentals]:::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 Z\\\\n classDef gap fill:#EEEDFE,stroke:#534AB7,color:#26215C\\\\n classDef foundation fill:#E1F5EE,stroke:#0F6E56,color:#085041\\\\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': 281, 'prompt_tokens': 3695, 'total_tokens': 3976, 'completion_time': 0.591981415, 'completion_tokens_details': None, 'prompt_time': 0.13173194, 'prompt_tokens_details': {'cached_tokens': 2816}, 'queue_time': 0.279077701, 'total_time': 0.723713355}, '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--019d1214-5f0b-7961-97e0-127ed75ed0dc-0', tool_calls=[{'name': 'submit_mermaid_visualization', 'args': {'mermaid_code': 'flowchart TD\\n A([Start — Candidate\\'s current skills]):::start\\n subgraph W1[\"Week 1 — Core foundations\"]\\n B[CS-PY-101\\nPython Programming Fundamentals]:::foundation\\n C[CS-FAST-101\\nREST API Development with FastAPI]:::gap\\n end\\n subgraph W2[\"Week 2 — Data & deployment\"]\\n D[CS-DB-101\\nSQL Fundamentals for Backend Developers]:::gap\\n E[CS-DOCKER-101\\nDocker & Containerization Fundamentals]:::gap\\n end\\n Z([Role-ready — Backend Developer]):::done\\n A --> B\\n B --> C\\n C --> D\\n D --> E\\n E --> Z\\n classDef gap fill:#EEEDFE,stroke:#534AB7,color:#26215C\\n classDef foundation fill:#E1F5EE,stroke:#0F6E56,color:#085041\\n classDef start fill:#1D9E75,stroke:#0F6E56,color:#E1F5EE\\n classDef done fill:#534AB7,stroke:#3C3489,color:#EEEDFE'}, 'id': 'functions.submit_mermaid_visualization:10', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 3695, 'output_tokens': 281, 'total_tokens': 3976, 'input_token_details': {'cache_read': 2816}}),\n", " ToolMessage(content='Mermaid visualization stored successfully.', name='submit_mermaid_visualization', id='fede2020-92df-406c-a533-94aaba4301c0', tool_call_id='functions.submit_mermaid_visualization:10'),\n", " AIMessage(content='', additional_kwargs={}, response_metadata={'token_usage': {'completion_tokens': 1, 'prompt_tokens': 4001, 'total_tokens': 4002, 'completion_time': 0.017057827, 'completion_tokens_details': None, 'prompt_time': 0.01031025, 'prompt_tokens_details': {'cached_tokens': 3840}, 'queue_time': None, 'total_time': 0.027368077}, 'model_name': 'moonshotai/kimi-k2-instruct-0905', 'system_fingerprint': 'fp_241bc7119c', 'service_tier': 'on_demand', 'finish_reason': 'stop', 'logprobs': None, 'model_provider': 'groq'}, id='lc_run--019d1214-6352-7e00-875a-f255a90094ea-0', tool_calls=[], invalid_tool_calls=[], usage_metadata={'input_tokens': 4001, 'output_tokens': 1, 'total_tokens': 4002, 'input_token_details': {'cache_read': 3840}})],\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 requires FastAPI; candidate's resume shows no experience with FastAPI in skills, experience, or projects.\", target_competency='Develop high-performance asynchronous APIs using FastAPI'), SkillGap(skill_name='PostgreSQL', gap_type='missing_foundation', priority='high', reasoning='JD requires PostgreSQL; candidate has MySQL and SQLite experience but no record of using PostgreSQL.', target_competency='Design, query, and maintain PostgreSQL databases for backend services'), SkillGap(skill_name='Docker', gap_type='missing_foundation', priority='medium', reasoning=\"JD requires Docker for containerization; candidate's resume does not list Docker experience.\", target_competency='Containerize and deploy backend applications using Docker')], is_fresher_adaptation_needed=True, executive_summary=\"The candidate possesses solid Python and Django experience but lacks exposure to key backend infrastructure required for this role. As a fresher, foundational training is needed for FastAPI, PostgreSQL, and Docker to meet the job's core competencies.\"),\n", " 'resume_data': ResumeExtract(job_title='Software Developer', total_experience_months=7, skills=[Skill(name='C/C++', category='Other'), Skill(name='Java', category='Backend'), 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 confidence-scoring pipelines', 'Developed scalable document intelligence pipeline for 60+ page financial docs', 'Implementing Cron-based workers for weekly email analytics reports']), ExperienceItem(job_title='Software Development Intern', experience_type='internship', duration_months=2, technologies=['Flutter', 'Dart', 'MVVM'], responsibilities=['Developed Groceazy grocery app with 10+ modules', 'Implemented secure user auth and role-based access', 'Built image-to-item price matcher across 5+ platforms'])], 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 and role-based access'), 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=True),\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 — Candidate\\'s current skills]):::start\\n subgraph W1[\"Week 1 — Core foundations\"]\\n B[CS-PY-101\\nPython Programming Fundamentals]:::foundation\\n C[CS-FAST-101\\nREST API Development with FastAPI]:::gap\\n end\\n subgraph W2[\"Week 2 — Data & deployment\"]\\n D[CS-DB-101\\nSQL Fundamentals for Backend Developers]:::gap\\n E[CS-DOCKER-101\\nDocker & Containerization Fundamentals]:::gap\\n end\\n Z([Role-ready — Backend Developer]):::done\\n A --> B\\n B --> C\\n C --> D\\n D --> E\\n E --> Z\\n classDef gap fill:#EEEDFE,stroke:#534AB7,color:#26215C\\n classDef foundation 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", " 'final_roadmap': {'candidate_name': 'Candidate',\n", " 'onboarding_summary': 'The candidate has solid Python/Django experience but lacks exposure to FastAPI, PostgreSQL, and Docker—core requirements for this backend role. As a fresher, foundational training is prioritized to build missing competencies quickly and effectively.',\n", " 'roadmap': [{'course_id': 'CS-PY-101',\n", " 'is_foundation': True,\n", " 'reasoning': 'Prerequisite for FastAPI course; ensures strong Python foundation before API development.',\n", " 'sequence_order': 1,\n", " 'title': 'Python Programming Fundamentals'},\n", " {'course_id': 'CS-FAST-101',\n", " 'is_foundation': True,\n", " 'reasoning': 'High-priority gap: JD requires FastAPI; candidate has no prior experience with this framework.',\n", " 'sequence_order': 2,\n", " 'title': 'REST API Development with FastAPI'},\n", " {'course_id': 'CS-DB-101',\n", " 'is_foundation': True,\n", " 'reasoning': 'High-priority gap: JD requires PostgreSQL; candidate only has MySQL/SQLite experience.',\n", " 'sequence_order': 3,\n", " 'title': 'SQL Fundamentals for Backend Developers'},\n", " {'course_id': 'CS-DOCKER-101',\n", " 'is_foundation': False,\n", " 'reasoning': 'Medium-priority gap: JD requires Docker; candidate has no containerization experience.',\n", " 'sequence_order': 4,\n", " 'title': 'Docker & Containerization Fundamentals'}],\n", " 'target_role': 'Backend Developer'}}" ] }, "execution_count": 127, "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": 128, "id": "40a999a5", "metadata": {}, "outputs": [], "source": [ "skill_gap_data = final_state['final_roadmap']" ] }, { "cell_type": "code", "execution_count": 129, "id": "13d64ecc", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'candidate_name': 'Candidate',\n", " 'onboarding_summary': 'The candidate has solid Python/Django experience but lacks exposure to FastAPI, PostgreSQL, and Docker—core requirements for this backend role. As a fresher, foundational training is prioritized to build missing competencies quickly and effectively.',\n", " 'roadmap': [{'course_id': 'CS-PY-101',\n", " 'is_foundation': True,\n", " 'reasoning': 'Prerequisite for FastAPI course; ensures strong Python foundation before API development.',\n", " 'sequence_order': 1,\n", " 'title': 'Python Programming Fundamentals'},\n", " {'course_id': 'CS-FAST-101',\n", " 'is_foundation': True,\n", " 'reasoning': 'High-priority gap: JD requires FastAPI; candidate has no prior experience with this framework.',\n", " 'sequence_order': 2,\n", " 'title': 'REST API Development with FastAPI'},\n", " {'course_id': 'CS-DB-101',\n", " 'is_foundation': True,\n", " 'reasoning': 'High-priority gap: JD requires PostgreSQL; candidate only has MySQL/SQLite experience.',\n", " 'sequence_order': 3,\n", " 'title': 'SQL Fundamentals for Backend Developers'},\n", " {'course_id': 'CS-DOCKER-101',\n", " 'is_foundation': False,\n", " 'reasoning': 'Medium-priority gap: JD requires Docker; candidate has no containerization experience.',\n", " 'sequence_order': 4,\n", " 'title': 'Docker & Containerization Fundamentals'}],\n", " 'target_role': 'Backend Developer'}" ] }, "execution_count": 129, "metadata": {}, "output_type": "execute_result" } ], "source": [ "skill_gap_data" ] }, { "cell_type": "code", "execution_count": 130, "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": 131, "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": null, "id": "92693ebd", "metadata": {}, "outputs": [], "source": [ "from langchain_core.callbacks import BaseCallbackHandler\n", "\n", "class ExportHook(BaseCallbackHandler):\n", " def on_chain_end(self, outputs: dict, **kwargs):\n", " # 'outputs' is your final Graph state\n", " export_ui_payload(outputs)\n", "\n", "# To use it:\n", "my_hook = ExportHook()\n", "app.invoke(inputs, {\"callbacks\": [my_hook]})\n" ] }, { "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 }