Rohitface commited on
Commit
d06f8e2
·
verified ·
1 Parent(s): b1c819f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +58 -67
app.py CHANGED
@@ -1,4 +1,5 @@
1
- # Deploys the LangGraph agent with a feature-rich Gradio UI on Hugging Face Spaces.
 
2
 
3
  # 1. IMPORTS AND SETUP
4
  import os
@@ -8,21 +9,16 @@ from typing import TypedDict, List, Optional
8
  from langchain_openai import ChatOpenAI
9
  from langchain_core.prompts import ChatPromptTemplate
10
  from langchain_core.tools import tool
11
- from langchain_core.pydantic_v1 import BaseModel, Field
12
  from langgraph.graph import StateGraph, END
13
  import requests
14
  from bs4 import BeautifulSoup
15
- from PyPDF2 import PdfReader
16
  from threading import Thread
17
 
18
- # Import the stream_executor to allow the UI to update progressively
19
- from langchain.callbacks.base import BaseCallbackHandler
20
- from langchain_core.runnables import RunnableConfig
21
-
22
  print("--- Libraries imported. ---")
23
 
24
- # IMPORTANT: Set your API keys in the Hugging Face Space "Secrets"
25
- # The names should be OPENAI_API_KEY, LANGCHAIN_API_KEY, and TAVILY_API_KEY
26
  os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY")
27
  os.environ["LANGCHAIN_API_KEY"] = os.getenv("LANGCHAIN_API_KEY")
28
  os.environ["TAVILY_API_KEY"] = os.getenv("TAVILY_API_KEY")
@@ -32,7 +28,6 @@ os.environ["LANGCHAIN_PROJECT"] = "Deployed Career Navigator"
32
 
33
  # 2. LANGGRAPH AGENT BACKEND (The "Brain" of the App)
34
 
35
- # Pydantic Models for structured data
36
  class SkillAnalysis(BaseModel):
37
  technical_skills: List[str] = Field(description="List of top 5 technical skills.")
38
  soft_skills: List[str] = Field(description="List of top 3 soft skills.")
@@ -49,7 +44,6 @@ class CareerActionPlan(BaseModel):
49
  learning_roadmap: str = Field(description="Markdown-formatted learning plan.")
50
  portfolio_plan: str = Field(description="Markdown-formatted portfolio project plan.")
51
 
52
- # Agent State
53
  class TeamState(TypedDict):
54
  student_interests: str
55
  student_resume: str
@@ -59,7 +53,6 @@ class TeamState(TypedDict):
59
  resume_analysis: Optional[ResumeFeedback]
60
  final_plan: Optional[CareerActionPlan]
61
 
62
- # Tools
63
  @tool
64
  def scrape_web_content(url: str) -> str:
65
  """Scrapes text content from a URL."""
@@ -70,11 +63,9 @@ def scrape_web_content(url: str) -> str:
70
  except requests.RequestException:
71
  return "Error: Could not scrape the URL."
72
 
73
- # Specialist Agents
74
  llm = ChatOpenAI(model="gpt-4o", temperature=0)
75
 
76
  def job_market_analyst_agent(state: TeamState):
77
- # This is a simplified version for faster UI response. A full version would be more robust.
78
  print("--- 🕵️ Agent: Job Market Analyst ---")
79
  structured_llm = llm.with_structured_output(SkillAnalysis)
80
  prompt = ChatPromptTemplate.from_template(
@@ -116,7 +107,6 @@ def lead_agent_node(state: TeamState):
116
  })
117
  return {"final_plan": final_plan}
118
 
119
- # Graph Definition
120
  graph_builder = StateGraph(TeamState)
121
  graph_builder.add_node("analyze_market", job_market_analyst_agent)
122
  graph_builder.add_node("review_resume", resume_reviewer_agent)
@@ -131,31 +121,40 @@ print("--- LangGraph Agent Backend is ready. ---")
131
 
132
 
133
  # 3. HELPER FUNCTIONS FOR GRADIO
134
- def extract_text_from_pdf(pdf_file):
135
- """Extracts text from an uploaded PDF file."""
136
- if not pdf_file:
137
- return ""
138
- reader = PdfReader(pdf_file.name)
139
- text = ""
140
- for page in reader.pages:
141
- text += page.extract_text() or ""
142
- return text
143
-
144
- def run_agent_process(resume_text, chosen_career, progress=gr.Progress(track_tqdm=True)):
145
- """The main function to run the agent and yield updates for the UI."""
146
- initial_state = {
147
- "student_resume": resume_text,
148
- "chosen_career": chosen_career,
 
 
 
 
 
 
 
 
 
149
  }
150
-
151
- # Use a thread to run the agent so the UI doesn't block
152
  final_state = navigator_agent.invoke(initial_state)
153
  plan = final_state['final_plan']
154
-
155
- return {
156
- # Update UI components with the final plan
157
  output_plan_state: plan,
158
- output_overview: gr.update(value=f"## 1. Career Overview: {plan.career_overview}", visible=True),
159
  output_skills: gr.update(
160
  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)}",
161
  visible=True
@@ -166,54 +165,45 @@ def run_agent_process(resume_text, chosen_career, progress=gr.Progress(track_tqd
166
  ),
167
  output_learning_plan: gr.update(value=f"## 4. Your 8-Week Learning Roadmap\n{plan.learning_roadmap}", visible=True),
168
  output_portfolio_plan: gr.update(value=f"## 5. Your Portfolio Project Plan\n{plan.portfolio_plan}", visible=True),
169
- # Hide the input section and show the output/chat sections
170
- input_row: gr.update(visible=False),
171
  chat_row: gr.update(visible=True)
172
  }
173
 
174
  def chat_with_agent(message, history, plan_state):
175
- """Handles the follow-up conversation with the agent."""
176
  if not plan_state:
177
- return "Please generate a plan first."
178
-
179
  prompt = ChatPromptTemplate.from_messages([
180
- ("system", "You are a helpful career coach. The user has just received the following career action plan. Answer their follow-up questions based on this plan.\n\n--- CAREER PLAN ---\n{plan_text}"),
181
- ("human", "{user_question}")
182
  ])
183
-
184
  chat_chain = prompt | llm
185
 
186
- # Convert Pydantic model to a string for the context
187
  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}"
188
 
189
- response = chat_chain.invoke({
190
- "plan_text": plan_text,
191
- "user_question": message
192
- })
193
-
194
  return response.content
195
 
196
 
197
  # 4. GRADIO UI DEFINITION
198
- with gr.Blocks(theme=gr.themes.Soft(), css=".gradio-container {background-color: #f0f4f9;}") as demo:
199
- # State object to hold the final plan for the chat
200
  output_plan_state = gr.State()
201
-
 
 
202
  gr.Markdown("# 🚀 Your AI Career Navigator")
203
  gr.Markdown("Upload your resume, select a target career, and get a personalized, data-driven action plan from a team of AI agents.")
204
 
205
- # Page 1: Input Section
206
  with gr.Row(visible=True) as input_row:
207
- with gr.Column(scale=1):
208
  input_pdf_resume = gr.File(label="Upload Your Resume (PDF)", file_types=[".pdf"])
209
  input_career_choice = gr.Dropdown(
210
  label="Select Your Target Career",
211
  choices=["Data Analyst", "Software Engineer", "Product Manager", "UX Designer", "AI/ML Engineer"],
212
  value="Data Analyst"
213
  )
214
- submit_button = gr.Button("Generate My Action Plan", variant="primary")
215
-
216
- # Page 2: Output Section (initially hidden)
217
  with gr.Column(visible=False) as output_col:
218
  output_overview = gr.Markdown(visible=False)
219
  output_skills = gr.Markdown(visible=False)
@@ -221,24 +211,25 @@ with gr.Blocks(theme=gr.themes.Soft(), css=".gradio-container {background-color:
221
  output_learning_plan = gr.Markdown(visible=False)
222
  output_portfolio_plan = gr.Markdown(visible=False)
223
 
224
- # Page 2: Chat Section (initially hidden)
225
  with gr.Row(visible=False) as chat_row:
226
  chat_interface = gr.ChatInterface(
227
  chat_with_agent,
228
- chatbot=gr.Chatbot(height=400),
229
  additional_inputs=[output_plan_state],
230
  title="Ask Follow-up Questions",
231
  description="Ask any questions about your generated plan."
232
  )
233
 
234
- # Event Handling
235
- submit_button.click(
236
  fn=extract_text_from_pdf,
237
  inputs=[input_pdf_resume],
238
- outputs=None # We will use the result in the next step
239
- ).then(
240
- fn=run_agent_process,
241
- inputs=[lambda *args: args[0], input_career_choice],
 
 
242
  outputs=[
243
  output_plan_state,
244
  output_overview,
@@ -247,10 +238,10 @@ with gr.Blocks(theme=gr.themes.Soft(), css=".gradio-container {background-color:
247
  output_learning_plan,
248
  output_portfolio_plan,
249
  input_row,
 
250
  chat_row
251
  ]
252
  )
253
 
254
- # Launch the app
255
  if __name__ == "__main__":
256
  demo.launch(debug=True)
 
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
 
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")
 
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.")
 
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
 
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."""
 
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(
 
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)
 
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
 
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)
 
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,
 
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)