Rohitface commited on
Commit
77e690d
·
verified ·
1 Parent(s): 7193880

Upload 2 files

Browse files
Files changed (2) hide show
  1. app (1).py +247 -0
  2. requirements (1).txt +7 -0
app (1).py ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # === FINAL PRODUCTION-READY APP (v2): ADVANCED CAREER NAVIGATOR ===
2
+ # Corrected version with updated imports for PyPDF2 and Pydantic.
3
+
4
+ # 1. IMPORTS AND SETUP
5
+ import os
6
+ import uuid
7
+ import gradio as gr
8
+ from typing import TypedDict, List, Optional
9
+ from langchain_openai import ChatOpenAI
10
+ from langchain_core.prompts import ChatPromptTemplate
11
+ from langchain_core.tools import tool
12
+ from pydantic import BaseModel, Field # CORRECTED LINE: Import directly from Pydantic
13
+ from langgraph.graph import StateGraph, END
14
+ import requests
15
+ from bs4 import BeautifulSoup
16
+ from pypdf import PdfReader # CORRECTED LINE: Import from 'pypdf' instead of 'PyPDF2'
17
+ from threading import Thread
18
+
19
+ print("--- Libraries imported. ---")
20
+
21
+ # Set API keys from Hugging Face Space Secrets
22
+ os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY")
23
+ os.environ["LANGCHAIN_API_KEY"] = os.getenv("LANGCHAIN_API_KEY")
24
+ os.environ["TAVILY_API_KEY"] = os.getenv("TAVILY_API_KEY")
25
+ os.environ["LANGCHAIN_TRACING_V2"] = "true"
26
+ os.environ["LANGCHAIN_PROJECT"] = "Deployed Career Navigator"
27
+
28
+
29
+ # 2. LANGGRAPH AGENT BACKEND (The "Brain" of the App)
30
+
31
+ class SkillAnalysis(BaseModel):
32
+ technical_skills: List[str] = Field(description="List of top 5 technical skills.")
33
+ soft_skills: List[str] = Field(description="List of top 3 soft skills.")
34
+
35
+ class ResumeFeedback(BaseModel):
36
+ strengths: List[str] = Field(description="Resume strengths.")
37
+ gaps: List[str] = Field(description="Missing skills or experiences.")
38
+ suggestions: List[str] = Field(description="Suggestions for improvement.")
39
+
40
+ class CareerActionPlan(BaseModel):
41
+ career_overview: str = Field(description="Overview of the chosen career.")
42
+ skill_analysis: SkillAnalysis
43
+ resume_feedback: ResumeFeedback
44
+ learning_roadmap: str = Field(description="Markdown-formatted learning plan.")
45
+ portfolio_plan: str = Field(description="Markdown-formatted portfolio project plan.")
46
+
47
+ class TeamState(TypedDict):
48
+ student_interests: str
49
+ student_resume: str
50
+ career_options: List[str]
51
+ chosen_career: str
52
+ market_analysis: Optional[SkillAnalysis]
53
+ resume_analysis: Optional[ResumeFeedback]
54
+ final_plan: Optional[CareerActionPlan]
55
+
56
+ @tool
57
+ def scrape_web_content(url: str) -> str:
58
+ """Scrapes text content from a URL."""
59
+ try:
60
+ response = requests.get(url, timeout=10)
61
+ soup = BeautifulSoup(response.content, 'html.parser')
62
+ return soup.get_text(separator=' ', strip=True)[:10000]
63
+ except requests.RequestException:
64
+ return "Error: Could not scrape the URL."
65
+
66
+ llm = ChatOpenAI(model="gpt-4o", temperature=0)
67
+
68
+ def job_market_analyst_agent(state: TeamState):
69
+ print("--- 🕵️ Agent: Job Market Analyst ---")
70
+ structured_llm = llm.with_structured_output(SkillAnalysis)
71
+ prompt = ChatPromptTemplate.from_template(
72
+ "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."
73
+ )
74
+ chain = prompt | structured_llm
75
+ analysis = chain.invoke({"career": state['chosen_career']})
76
+ return {"market_analysis": analysis}
77
+
78
+ def resume_reviewer_agent(state: TeamState):
79
+ print("--- 📝 Agent: Resume Reviewer ---")
80
+ structured_llm = llm.with_structured_output(ResumeFeedback)
81
+ prompt = ChatPromptTemplate.from_messages([
82
+ ("system", "You are an expert career coach. Compare the user's resume with the provided analysis of in-demand skills and provide feedback."),
83
+ ("human", "User's Resume:\n{resume}\n\nRequired Skills Analysis:\n{skill_analysis}")
84
+ ])
85
+ chain = prompt | structured_llm
86
+ feedback = chain.invoke({
87
+ "resume": state["student_resume"],
88
+ "skill_analysis": state["market_analysis"].dict()
89
+ })
90
+ return {"resume_analysis": feedback}
91
+
92
+ def lead_agent_node(state: TeamState):
93
+ print("--- 👑 Agent: Lead Agent (Synthesizing & Planning) ---")
94
+ structured_llm = llm.with_structured_output(CareerActionPlan)
95
+ prompt = ChatPromptTemplate.from_template(
96
+ "You are the lead career strategist. Synthesize all the provided information into a comprehensive Career Action Plan. "
97
+ "Create a detailed 8-week learning roadmap and suggest 3 portfolio projects.\n\n"
98
+ "Chosen Career: {career}\n"
99
+ "Required Skills: {skills}\n"
100
+ "Resume Feedback: {resume_feedback}"
101
+ )
102
+ chain = prompt | structured_llm
103
+ final_plan = chain.invoke({
104
+ "career": state["chosen_career"],
105
+ "skills": state["market_analysis"].dict(),
106
+ "resume_feedback": state["resume_analysis"].dict()
107
+ })
108
+ return {"final_plan": final_plan}
109
+
110
+ graph_builder = StateGraph(TeamState)
111
+ graph_builder.add_node("analyze_market", job_market_analyst_agent)
112
+ graph_builder.add_node("review_resume", resume_reviewer_agent)
113
+ graph_builder.add_node("create_final_plan", lead_agent_node)
114
+ graph_builder.set_entry_point("analyze_market")
115
+ graph_builder.add_edge("analyze_market", "review_resume")
116
+ graph_builder.add_edge("review_resume", "create_final_plan")
117
+ graph_builder.add_edge("create_final_plan", END)
118
+ navigator_agent = graph_builder.compile()
119
+
120
+ print("--- LangGraph Agent Backend is ready. ---")
121
+
122
+
123
+ # 3. HELPER FUNCTIONS FOR GRADIO
124
+ def extract_text_from_pdf(pdf_file_obj):
125
+ if not pdf_file_obj:
126
+ return "", "Please upload a resume to begin."
127
+ try:
128
+ reader = PdfReader(pdf_file_obj.name)
129
+ text = "".join(page.extract_text() or "" for page in reader.pages)
130
+ if not text.strip():
131
+ return "", "Error: Could not extract text from the PDF. Please try a different file."
132
+ return text, ""
133
+ except Exception as e:
134
+ return "", f"An error occurred while reading the PDF: {e}"
135
+
136
+
137
+ def run_agent_and_update_ui(resume_text, chosen_career):
138
+ if not resume_text:
139
+ return {
140
+ output_col: gr.update(visible=True),
141
+ output_overview: gr.update(value="<h3 style='color:red;'>Please upload a resume first.</h3>", visible=True)
142
+ }
143
+
144
+ yield {
145
+ output_col: gr.update(visible=True),
146
+ input_row: gr.update(visible=False),
147
+ output_overview: gr.update(value="### 🧠 The AI agent team is analyzing your profile...", visible=True)
148
+ }
149
+
150
+ initial_state = { "student_resume": resume_text, "chosen_career": chosen_career }
151
+ final_state = navigator_agent.invoke(initial_state)
152
+ plan = final_state['final_plan']
153
+
154
+ # Final update
155
+ yield {
156
+ output_plan_state: plan,
157
+ output_overview: gr.update(value=f"## 1. Career Overview: {plan.career_overview}"),
158
+ output_skills: gr.update(
159
+ value=f"## 2. Job Market Skill Analysis\n**Top Technical Skills:** {', '.join(plan.skill_analysis.technical_skills)}\n\n**Top Soft Skills:** {', '.join(plan.skill_analysis.soft_skills)}",
160
+ visible=True
161
+ ),
162
+ output_resume_feedback: gr.update(
163
+ value=f"## 3. Your Resume Feedback\n**Strengths:** {' '.join(plan.resume_feedback.strengths)}\n\n**Gaps to Fill:** {', '.join(plan.resume_feedback.gaps)}\n\n**Suggestions:**\n" + "\n".join([f"- {s}" for s in plan.resume_feedback.suggestions]),
164
+ visible=True
165
+ ),
166
+ output_learning_plan: gr.update(value=f"## 4. Your 8-Week Learning Roadmap\n{plan.learning_roadmap}", visible=True),
167
+ output_portfolio_plan: gr.update(value=f"## 5. Your Portfolio Project Plan\n{plan.portfolio_plan}", visible=True),
168
+ chat_row: gr.update(visible=True)
169
+ }
170
+
171
+ def chat_with_agent(message, history, plan_state):
172
+ if not plan_state:
173
+ return "An error occurred. Please generate a new plan."
174
+
175
+ prompt = ChatPromptTemplate.from_messages([
176
+ ("system", "You are a helpful career coach. The user has just received the following career action plan. Answer their follow-up questions based ONLY on this plan.\n\n--- CAREER PLAN ---\n{plan_text}"),
177
+ ("user", "{user_question}")
178
+ ])
179
+ chat_chain = prompt | llm
180
+
181
+ plan_text = f"Career: {plan_state.career_overview}\nSkills: {plan_state.skill_analysis.dict()}\nResume Feedback: {plan_state.resume_feedback.dict()}\nLearning Plan: {plan_state.learning_roadmap}\nPortfolio Plan: {plan_state.portfolio_plan}"
182
+
183
+ response = chat_chain.invoke({"plan_text": plan_text, "user_question": message})
184
+ return response.content
185
+
186
+
187
+ # 4. GRADIO UI DEFINITION
188
+ with gr.Blocks(theme=gr.themes.Soft(), css="footer {visibility: hidden}") as demo:
189
+ output_plan_state = gr.State()
190
+ resume_text_state = gr.State()
191
+ error_text_state = gr.State()
192
+
193
+ gr.Markdown("# 🚀 Your AI Career Navigator")
194
+ gr.Markdown("Upload your resume, select a target career, and get a personalized, data-driven action plan from a team of AI agents.")
195
+
196
+ with gr.Row(visible=True) as input_row:
197
+ with gr.Column(scale=2):
198
+ input_pdf_resume = gr.File(label="Upload Your Resume (PDF)", file_types=[".pdf"])
199
+ input_career_choice = gr.Dropdown(
200
+ label="Select Your Target Career",
201
+ choices=["Data Analyst", "Software Engineer", "Product Manager", "UX Designer", "AI/ML Engineer"],
202
+ value="Data Analyst"
203
+ )
204
+ with gr.Column(scale=1, min_width=200):
205
+ submit_button = gr.Button("Generate My Action Plan", variant="primary", scale=2)
206
+
207
+ with gr.Column(visible=False) as output_col:
208
+ output_overview = gr.Markdown(visible=False)
209
+ output_skills = gr.Markdown(visible=False)
210
+ output_resume_feedback = gr.Markdown(visible=False)
211
+ output_learning_plan = gr.Markdown(visible=False)
212
+ output_portfolio_plan = gr.Markdown(visible=False)
213
+
214
+ with gr.Row(visible=False) as chat_row:
215
+ chat_interface = gr.ChatInterface(
216
+ chat_with_agent,
217
+ chatbot=gr.Chatbot(height=500, label="Chat with your Career Coach"),
218
+ additional_inputs=[output_plan_state],
219
+ title="Ask Follow-up Questions",
220
+ description="Ask any questions about your generated plan."
221
+ )
222
+
223
+ # Event Handling Logic
224
+ input_pdf_resume.upload(
225
+ fn=extract_text_from_pdf,
226
+ inputs=[input_pdf_resume],
227
+ outputs=[resume_text_state, error_text_state]
228
+ )
229
+
230
+ submit_button.click(
231
+ fn=run_agent_and_update_ui,
232
+ inputs=[resume_text_state, input_career_choice],
233
+ outputs=[
234
+ output_plan_state,
235
+ output_overview,
236
+ output_skills,
237
+ output_resume_feedback,
238
+ output_learning_plan,
239
+ output_portfolio_plan,
240
+ input_row,
241
+ output_col,
242
+ chat_row
243
+ ]
244
+ )
245
+
246
+ if __name__ == "__main__":
247
+ demo.launch(debug=True)
requirements (1).txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ langgraph>=0.1.0
2
+ langchain_openai
3
+ pydantic>=2.0
4
+ tavily-python
5
+ beautifulsoup4
6
+ gradio
7
+ pypdf