# 1. IMPORTS AND SETUP import os import uuid import gradio as gr from typing import TypedDict, List, Optional from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.tools import tool from pydantic import BaseModel, Field # CORRECTED LINE: Import directly from Pydantic from langgraph.graph import StateGraph, END import requests from bs4 import BeautifulSoup from pypdf import PdfReader # CORRECTED LINE: Import from 'pypdf' instead of 'PyPDF2' from threading import Thread print("--- Libraries imported. ---") # Set API keys from Hugging Face Space Secrets os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY") os.environ["LANGCHAIN_API_KEY"] = os.getenv("LANGCHAIN_API_KEY") os.environ["TAVILY_API_KEY"] = os.getenv("TAVILY_API_KEY") os.environ["LANGCHAIN_TRACING_V2"] = "true" os.environ["LANGCHAIN_PROJECT"] = "Deployed Career Navigator" # 2. LANGGRAPH AGENT BACKEND (The "Brain" of the App) class SkillAnalysis(BaseModel): technical_skills: List[str] = Field(description="List of top 5 technical skills.") soft_skills: List[str] = Field(description="List of top 3 soft skills.") class ResumeFeedback(BaseModel): strengths: List[str] = Field(description="Resume strengths.") gaps: List[str] = Field(description="Missing skills or experiences.") suggestions: List[str] = Field(description="Suggestions for improvement.") class CareerActionPlan(BaseModel): career_overview: str = Field(description="Overview of the chosen career.") skill_analysis: SkillAnalysis resume_feedback: ResumeFeedback learning_roadmap: str = Field(description="Markdown-formatted learning plan.") portfolio_plan: str = Field(description="Markdown-formatted portfolio project plan.") class TeamState(TypedDict): student_interests: str student_resume: str career_options: List[str] chosen_career: str market_analysis: Optional[SkillAnalysis] resume_analysis: Optional[ResumeFeedback] final_plan: Optional[CareerActionPlan] @tool def scrape_web_content(url: str) -> str: """Scrapes text content from a URL.""" try: response = requests.get(url, timeout=10) soup = BeautifulSoup(response.content, 'html.parser') return soup.get_text(separator=' ', strip=True)[:10000] except requests.RequestException: return "Error: Could not scrape the URL." llm = ChatOpenAI(model="gpt-4o", temperature=0) def job_market_analyst_agent(state: TeamState): print("--- 🕵️ Agent: Job Market Analyst ---") structured_llm = llm.with_structured_output(SkillAnalysis) prompt = ChatPromptTemplate.from_template( "You are an expert job market analyst. Based on the career of '{career}', identify the top 5 technical skills and top 3 soft skills required." ) chain = prompt | structured_llm analysis = chain.invoke({"career": state['chosen_career']}) return {"market_analysis": analysis} def resume_reviewer_agent(state: TeamState): print("--- 📝 Agent: Resume Reviewer ---") structured_llm = llm.with_structured_output(ResumeFeedback) prompt = ChatPromptTemplate.from_messages([ ("system", "You are an expert career coach. Compare the user's resume with the provided analysis of in-demand skills and provide feedback."), ("human", "User's Resume:\n{resume}\n\nRequired Skills Analysis:\n{skill_analysis}") ]) chain = prompt | structured_llm feedback = chain.invoke({ "resume": state["student_resume"], "skill_analysis": state["market_analysis"].dict() }) return {"resume_analysis": feedback} def lead_agent_node(state: TeamState): print("--- 👑 Agent: Lead Agent (Synthesizing & Planning) ---") structured_llm = llm.with_structured_output(CareerActionPlan) prompt = ChatPromptTemplate.from_template( "You are the lead career strategist. Synthesize all the provided information into a comprehensive Career Action Plan. " "Create a detailed 8-week learning roadmap and suggest 3 portfolio projects.\n\n" "Chosen Career: {career}\n" "Required Skills: {skills}\n" "Resume Feedback: {resume_feedback}" ) chain = prompt | structured_llm final_plan = chain.invoke({ "career": state["chosen_career"], "skills": state["market_analysis"].dict(), "resume_feedback": state["resume_analysis"].dict() }) return {"final_plan": final_plan} graph_builder = StateGraph(TeamState) graph_builder.add_node("analyze_market", job_market_analyst_agent) graph_builder.add_node("review_resume", resume_reviewer_agent) graph_builder.add_node("create_final_plan", lead_agent_node) graph_builder.set_entry_point("analyze_market") graph_builder.add_edge("analyze_market", "review_resume") graph_builder.add_edge("review_resume", "create_final_plan") graph_builder.add_edge("create_final_plan", END) navigator_agent = graph_builder.compile() print("--- LangGraph Agent Backend is ready. ---") # 3. HELPER FUNCTIONS FOR GRADIO def extract_text_from_pdf(pdf_file_obj): if not pdf_file_obj: return "", "Please upload a resume to begin." try: reader = PdfReader(pdf_file_obj.name) text = "".join(page.extract_text() or "" for page in reader.pages) if not text.strip(): return "", "Error: Could not extract text from the PDF. Please try a different file." return text, "" except Exception as e: return "", f"An error occurred while reading the PDF: {e}" def run_agent_and_update_ui(resume_text, chosen_career): if not resume_text: return { output_col: gr.update(visible=True), output_overview: gr.update(value="