jzou19950715's picture
Update app.py
84add52 verified
raw
history blame
15.6 kB
import base64
import io
import os
import gradio as gr
import numpy as np
import matplotlib.pyplot as plt
from typing import Dict, List, Tuple, Any
import json
from litellm import completion
import logging
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# System prompts for different AI roles
CONVERSATION_PROMPT = """
You are an expert career advisor collecting information through natural conversation.
Focus on gathering these key points naturally without explicitly asking:
1. Current Role & Salary:
- Current salary (must get specific number)
- Job title/role
- Company size/type
- Industry/sector
2. Experience & Skills:
- Years in current role
- Total years working
- Key skills and proficiencies
- Management/leadership experience
- Notable achievements
3. Education & Training:
- Highest degree
- Field of study
- Certifications
- Recent training/upskilling
4. Location & Work Setup:
- Current location/market
- Remote work status
- Willingness to relocate
- Preferred work arrangement
Conversation Guidelines:
- Be natural and conversational
- Show interest in their career journey
- Don't force structured responses
- Make salary discussions comfortable
- Build rapport through discussion
- Once you have sufficient information say:
"I have collected enough information for analysis. Please click 'Generate Analysis' to see your career projections."
"""
EXTRACTION_PROMPT = """
Analyze the conversation and extract numerical scores based on salary growth potential.
Convert qualitative information into scores from 0 to 1.
Scoring Guidelines:
1. Industry Score (0-1):
- 1.0: Cutting-edge tech (AI, quantum)
- 0.9: High-growth tech (cloud, cyber)
- 0.8: Established tech/finance
- 0.7: Healthcare/biotech
- 0.6: Traditional sectors
- 0.5: Declining industries
+0.1: Market leader company
+0.1: High growth market
2. Experience Score (0-1):
- 1.0: 15+ years with leadership
- 0.9: 10-15 years senior
- 0.8: 7-10 years mid-senior
- 0.7: 4-6 years mid-level
- 0.6: 2-3 years junior
- 0.5: Entry level
+0.1: Fast career progression
+0.1: Significant achievements
3. Education Score (0-1):
- 1.0: PhD from top school
- 0.9: Masters from top school
- 0.8: Bachelors from top school
- 0.7: Advanced degree
- 0.6: Bachelors degree
- 0.5: Other education
+0.1: Relevant certifications
+0.1: Ongoing education
4. Skills Score (0-1):
- 1.0: Rare, high-demand skills
- 0.9: Advanced technical
- 0.8: Strong tech + soft skills
- 0.7: Solid technical skills
- 0.6: Standard skills
- 0.5: Basic skills
+0.1: Multiple in-demand skills
+0.1: Proven implementations
5. Location Score (0-1):
- 1.0: Major tech hubs
- 0.9: Secondary tech hubs
- 0.8: Major cities
- 0.7: Growing cities
- 0.6: Regional cities
- 0.5: Small markets
+0.1: Remote flexibility
+0.1: High growth market
Return only a JSON object with these exact fields:
{
"industry_score": float,
"experience_score": float,
"education_score": float,
"skills_score": float,
"location_score": float,
"current_salary": float
}
Make reasonable assumptions for any missing information based on context clues in the conversation.
"""
class CodeEnvironment:
"""Safe environment for executing visualization code"""
def __init__(self):
self.globals = {
'np': np,
'plt': plt
}
self.locals = {}
def execute(self, code: str) -> Dict[str, Any]:
"""Execute code and capture outputs"""
result = {'figures': [], 'error': None}
try:
# Execute code in safe environment
exec(code, self.globals, self.locals)
# Capture generated plots
for i in plt.get_fignums():
fig = plt.figure(i)
buf = io.BytesIO()
fig.savefig(buf, format='png', dpi=300)
buf.seek(0)
img_str = base64.b64encode(buf.read()).decode()
result['figures'].append(f"data:image/png;base64,{img_str}")
plt.close(fig)
except Exception as e:
result['error'] = str(e)
return result
class MonteCarloSimulator:
"""Handles Monte Carlo simulation for salary projections"""
def __init__(self):
self.years = 5
self.num_paths = 1000
def run_simulation(self, profile: Dict[str, float]) -> np.ndarray:
"""Run Monte Carlo simulation for salary projections"""
# Initialize paths array
paths = np.zeros((self.num_paths, self.years + 1))
paths[:, 0] = float(profile['current_salary'])
# Calculate growth parameters from profile
params = self._calculate_parameters(profile)
# Generate paths
for path in range(self.num_paths):
salary = paths[path, 0]
for year in range(1, self.years + 1):
# Calculate growth with randomness
growth = self._calculate_growth(params)
# Update salary
salary *= (1 + growth)
paths[path, year] = salary
return paths
def _calculate_parameters(self, profile: Dict[str, float]) -> Dict[str, float]:
"""Calculate simulation parameters from profile"""
return {
'base_growth': 0.02 + (profile['industry_score'] * 0.04),
'skill_premium': 0.01 + (profile['skills_score'] * 0.02),
'experience_premium': 0.01 + (profile['experience_score'] * 0.02),
'education_premium': 0.005 + (profile['education_score'] * 0.015),
'location_premium': 0.01 + (profile['location_score'] * 0.02),
'volatility': 0.05 + (profile['industry_score'] * 0.05),
'disruption_chance': 0.1,
'disruption_impact': 0.2
}
def _calculate_growth(self, params: Dict[str, float]) -> float:
"""Calculate annual growth rate with randomness"""
# Base growth plus premiums
growth = (params['base_growth'] +
params['skill_premium'] +
params['experience_premium'] +
params['education_premium'] +
params['location_premium'])
# Add random volatility
growth += np.random.normal(0, params['volatility'])
# Possible disruption event
if np.random.random() < params['disruption_chance']:
disruption = params['disruption_impact'] * np.random.random()
if np.random.random() < 0.7: # 70% positive disruption
growth += disruption
else:
growth -= disruption
# Apply reasonable bounds
return max(min(growth, 0.25), -0.1) # -10% to +25%
class CareerAdvisor:
"""Main career advisor system"""
def __init__(self, api_key: str):
self.api_key = api_key
self.chat_history = []
self.code_env = CodeEnvironment()
self.simulator = MonteCarloSimulator()
def chat(self, message: str) -> str:
"""Handle conversation with user"""
messages = [
{"role": "system", "content": CONVERSATION_PROMPT},
*self.chat_history,
{"role": "user", "content": message}
]
response = completion(
model="gpt-4o-mini",
messages=messages,
api_key=self.api_key
)
self.chat_history.extend([
{"role": "user", "content": message},
{"role": "assistant", "content": response.choices[0].message.content}
])
return response.choices[0].message.content
def extract_profile(self) -> Dict[str, float]:
"""Extract numerical profile from conversation"""
conversation = "\n".join([
f"{msg['role']}: {msg['content']}"
for msg in self.chat_history
])
messages = [
{"role": "system", "content": EXTRACTION_PROMPT},
{"role": "user", "content": f"Extract profile from:\n{conversation}"}
]
response = completion(
model="gpt-4o-mini",
messages=messages,
api_key=self.api_key,
response_format={"type": "json_object"}
)
return json.loads(response.choices[0].message.content)
def generate_visualization(self, paths: np.ndarray) -> str:
"""Generate visualization of simulation results"""
viz_code = """
plt.style.use('dark_background')
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10), height_ratios=[2, 1])
fig.tight_layout(pad=4)
# Plot 1: Salary Projections
years = list(range(paths.shape[1]))
# Plot confidence intervals
percentiles = [(5, 95), (10, 90), (25, 75)]
alphas = [0.1, 0.2, 0.3]
for (lower, upper), alpha in zip(percentiles, alphas):
lower_bound = np.percentile(paths, lower, axis=0)
upper_bound = np.percentile(paths, upper, axis=0)
ax1.fill_between(years, lower_bound, upper_bound, alpha=alpha, color='blue')
# Plot median line
median = np.percentile(paths, 50, axis=0)
ax1.plot(years, median, color='white', linewidth=2, label='Expected Path')
# Customize plot
ax1.set_title('Salary Growth Projections', pad=20)
ax1.set_xlabel('Years')
ax1.set_ylabel('Salary ($)')
ax1.grid(True, alpha=0.2)
ax1.legend()
# Format axes
ax1.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'${x:,.0f}'))
ax1.set_xticks(years)
ax1.set_xticklabels(['Current'] + [f'Year {i+1}' for i in range(len(years)-1)])
# Plot 2: Final Distribution
ax2.hist(paths[:, -1], bins=50, color='blue', alpha=0.7)
ax2.set_title('Final Salary Distribution', pad=20)
ax2.set_xlabel('Salary ($)')
ax2.set_ylabel('Frequency')
ax2.grid(True, alpha=0.2)
ax2.xaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'${x:,.0f}'))
"""
result = self.code_env.execute(viz_code)
return result['figures'][0] if result['figures'] else None
def generate_summary(self, profile: Dict[str, float], paths: np.ndarray) -> str:
"""Generate analysis summary"""
final_salaries = paths[:, -1]
initial_salary = paths[0, 0]
cagr = (np.median(final_salaries) / initial_salary) ** (1/5) - 1
return f"""
Career Profile Analysis
======================
Current Situation:
• Salary: ${profile['current_salary']:,.2f}
• Industry Position: {profile['industry_score']:.2f}/1.0
• Experience Level: {profile['experience_score']:.2f}/1.0
• Education Rating: {profile['education_score']:.2f}/1.0
• Skills Assessment: {profile['skills_score']:.2f}/1.0
• Location Impact: {profile['location_score']:.2f}/1.0
5-Year Projection:
• Conservative (25th percentile): ${np.percentile(final_salaries, 25):,.2f}
• Most Likely (Median): ${np.percentile(final_salaries, 50):,.2f}
• Optimistic (75th percentile): ${np.percentile(final_salaries, 75):,.2f}
• Expected Annual Growth: {cagr*100:.1f}%
Key Insights:
• Your profile suggests {cagr*100:.1f}% annual growth potential
{profile['industry_score']:.2f} industry score indicates {'strong' if profile['industry_score'] > 0.7 else 'moderate' if profile['industry_score'] > 0.5 else 'challenging'} growth environment
• Skills rating of {profile['skills_score']:.2f} suggests {'excellent' if profile['skills_score'] > 0.7 else 'good' if profile['skills_score'] > 0.5 else 'potential for'} career advancement
• Location score {profile['location_score']:.2f} {'enhances' if profile['location_score'] > 0.7 else 'supports' if profile['location_score'] > 0.5 else 'may limit'} opportunities
Based on {paths.shape[0]:,} simulated career paths
"""
def generate_analysis(self) -> Tuple[str, str]:
"""Generate complete analysis with visualization"""
try:
# Extract profile from conversation
profile = self.extract_profile()
# Run Monte Carlo simulation
paths = self.simulator.run_simulation(profile)
# Generate visualization
viz = self.generate_visualization(paths)
# Generate summary
summary = self.generate_summary(profile, paths)
return summary, viz
except Exception as e:
return f"Error generating analysis: {str(e)}", None
def create_interface():
"""Create the Gradio interface"""
advisor = None
def init_advisor(api_key: str):
nonlocal advisor
if not api_key.strip().startswith("sk-"):
return "Invalid API key format. Please check your key."
advisor = CareerAdvisor(api_key)
return "Advisor initialized! Let's discuss your career."
def chat(message: str, history: List):
if not advisor:
return "Please enter your API key first.", history
response = advisor.chat(message)
history.append((message, response))
return "", history
def analyze():
if not advisor:
return "Please enter your API key first.", None
summary, viz = advisor.generate_analysis()
return summary, viz
# Create interface
with gr.Blocks() as demo:
gr.Markdown("# AI Career Advisor with Monte Carlo Salary Projections")
with gr.Row():
api_key = gr.Textbox(
label="OpenAI API Key",
type="password",
placeholder="Enter your API key"
)
init_btn = gr.Button("Initialize Advisor")
status = gr.Textbox(label="Status")
with gr.Row():
with gr.Column(scale=1):
chatbot = gr.Chatbot(height=400)
msg = gr.Textbox(
label="Your message",
placeholder="Tell me about your career...",
lines=2
)
analyze_btn = gr.Button("Generate Analysis", variant="primary")
with gr.Column(scale=1):
analysis = gr.Textbox(
label="Analysis Report",
lines=20,
show_copy_button=True
)
plot = gr.Image(
label="Salary Projections",
show_download_button=True
)
# Wire up the interface
init_btn.click(
init_advisor,
inputs=[api_key],
outputs=[status]
)
msg.submit(
chat,
inputs=[msg, chatbot],
outputs=[msg, chatbot]
)
analyze_btn.click(
analyze,
outputs=[analysis, plot]
)
return demo
if __name__ == "__main__":
demo = create_interface()
demo.launch()